{"id":13526797,"url":"https://github.com/node-modules/common-bin","last_synced_at":"2025-06-14T03:07:58.470Z","repository":{"id":10088951,"uuid":"64394591","full_name":"node-modules/common-bin","owner":"node-modules","description":"Abstraction bin tool","archived":false,"fork":false,"pushed_at":"2023-03-20T05:05:52.000Z","size":123,"stargazers_count":189,"open_issues_count":3,"forks_count":21,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-05-21T12:56:31.646Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/node-modules.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS"}},"created_at":"2016-07-28T12:41:58.000Z","updated_at":"2025-01-16T05:45:48.000Z","dependencies_parsed_at":"2024-01-13T22:59:29.203Z","dependency_job_id":"c187f04f-f0f0-4f80-8b63-42d4fa06a59e","html_url":"https://github.com/node-modules/common-bin","commit_stats":{"total_commits":62,"total_committers":15,"mean_commits":4.133333333333334,"dds":0.6129032258064516,"last_synced_commit":"30ad0618204ee520a8e42b7bb8f8e7b75ab293f8"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/node-modules/common-bin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcommon-bin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcommon-bin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcommon-bin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcommon-bin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/node-modules","download_url":"https://codeload.github.com/node-modules/common-bin/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-modules%2Fcommon-bin/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259752078,"owners_count":22905972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T06:01:35.146Z","updated_at":"2025-06-14T03:07:58.451Z","avatar_url":"https://github.com/node-modules.png","language":"JavaScript","readme":"# common-bin\n\n[![NPM version][npm-image]][npm-url]\n[![Node.js CI](https://github.com/node-modules/common-bin/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/common-bin/actions/workflows/nodejs.yml)\n[![Test coverage][codecov-image]][codecov-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/common-bin.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/common-bin\n[codecov-image]: https://codecov.io/gh/node-modules/common-bin/branch/master/graph/badge.svg\n[codecov-url]: https://codecov.io/gh/node-modules/common-bin\n[snyk-image]: https://snyk.io/test/npm/common-bin/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/common-bin\n[download-image]: https://img.shields.io/npm/dm/common-bin.svg?style=flat-square\n[download-url]: https://npmjs.org/package/common-bin\n\nAbstraction bin tool wrap [yargs](http://yargs.js.org/), to provide more convenient usage, support async / await style.\n\n---\n\n## Install\n\n```bash\n$ npm i common-bin\n```\n\n## Build a bin tool for your team\n\nYou maybe need a custom xxx-bin to implement more custom features.\n\nNow you can implement a [Command](lib/command.js) sub class to do that.\n\n### Example: Write your own `git` command\n\nThis example will show you how to create a new `my-git` tool.\n\n- Full demo: [my-git](test/fixtures/my-git)\n\n```bash\ntest/fixtures/my-git\n├── bin\n│   └── my-git.js\n├── command\n│   ├── remote\n│   │   ├── add.js\n│   │   └── remove.js\n│   ├── clone.js\n│   └── remote.js\n├── index.js\n└── package.json\n```\n\n#### [my-git.js](test/fixtures/my-git/bin/my-git.js)\n\n```js\n#!/usr/bin/env node\n\n'use strict';\n\nconst Command = require('..');\nnew Command().start();\n```\n\n#### [Main Command](test/fixtures/my-git/index.js)\n\nJust extend `Command`, and use as your bin start point.\n\nYou can use `this.yargs` to custom yargs config, see http://yargs.js.org/docs for more detail.\n\n```js\nconst Command = require('common-bin');\nconst pkg = require('./package.json');\n\nclass MainCommand extends Command {\n  constructor(rawArgv) {\n    super(rawArgv);\n    this.usage = 'Usage: my-git \u003ccommand\u003e [options]';\n\n    // load entire command directory\n    this.load(path.join(__dirname, 'command'));\n\n    // or load special command file\n    // this.add(path.join(__dirname, 'test_command.js'));\n\n    // more custom with `yargs` api, such as you can use `my-git -V`\n    this.yargs.alias('V', 'version');\n  }\n}\n\nmodule.exports = MainCommand;\n```\n\n#### [CloneCommand](test/fixtures/my-git/command/clone.js)\n\n```js\nconst Command = require('common-bin');\nclass CloneCommand extends Command {\n  constructor(rawArgv) {\n    super(rawArgv);\n\n    this.options = {\n      depth: {\n        type: 'number',\n        description: 'Create a shallow clone with a history truncated to the specified number of commits',\n      },\n    };\n  }\n\n  async run({ argv }) {\n    console.log('git clone %s to %s with depth %d', argv._[0], argv._[1], argv.depth);\n  }\n\n  get description() {\n    return 'Clone a repository into a new directory';\n  }\n}\n\nmodule.exports = CloneCommand;\n```\n\n#### Run result\n\n```bash\n$ my-git clone gh://node-modules/common-bin dist --depth=1\n\ngit clone gh://node-modules/common-bin to dist with depth 1\n```\n\n## Concept\n\n### Command\n\nDefine the main logic of command\n\n**Method:**\n\n- `async start()` - start your program, only use once in your bin file.\n- `async run(context)`\n  - should implement this to provide command handler, will exec when not found sub command.\n  - Support generator / async function / normal function which return promise.\n  - `context` is `{ cwd, env, argv, rawArgv }`\n    - `cwd` - `process.cwd()`\n    - `env` - clone env object from `process.env`\n    - `argv` - argv parse result by yargs, `{ _: [ 'start' ], '$0': '/usr/local/bin/common-bin', baseDir: 'simple'}`\n    - `rawArgv` - the raw argv, `[ \"--baseDir=simple\" ]`\n- `load(fullPath)` - register the entire directory to commands\n- `add(name, target)` - register special command with command name, `target` could be full path of file or Class.\n- `alias(alias, name)` - register a command with an existing command\n- `showHelp()` - print usage message to console.\n- `options=` - a setter, shortcut for `yargs.options`\n- `usage=` - a setter, shortcut for `yargs.usage`\n\n**Properties:**\n\n- `description` - {String} a getter, only show this description when it's a sub command in help console\n- `helper` - {Object} helper instance\n- `yargs` - {Object} yargs instance for advanced custom usage\n- `options` - {Object} a setter, set yargs' options\n- `version` - {String} customize version, can be defined as a getter to support lazy load.\n- `parserOptions` - {Object} control `context` parse rule.\n  - `execArgv` - {Boolean} whether extract `execArgv` to `context.execArgv`\n  - `removeAlias` - {Boolean} whether remove alias key from `argv`\n  - `removeCamelCase` - {Boolean} whether remove camel case key from `argv`\n\nYou can define options by set `this.options`\n\n```js\nthis.options = {\n  baseDir: {\n    alias: 'b',\n    demandOption: true,\n    description: 'the target directory',\n    coerce: str =\u003e path.resolve(process.cwd(), str),\n  },\n  depth: {\n    description: 'level to clone',\n    type: 'number',\n    default: 1,\n  },\n  size: {\n    description: 'choose a size',\n    choices: ['xs', 's', 'm', 'l', 'xl']\n  },\n};\n```\n\nYou can define version by define `this.version` getter:\n\n```js\nget version() {\n  return 'v1.0.0';\n}\n```\n\n### Helper\n\n- `async forkNode(modulePath, args, opt)` - fork child process, wrap with promise and gracefull exit\n- `async spawn(cmd, args, opt)` - spawn a new process, wrap with promise and gracefull exit\n- `async npmInstall(npmCli, name, cwd)` - install node modules, wrap with promise\n- `async callFn(fn, args, thisArg)` - call fn, support gernerator / async / normal function return promise\n- `unparseArgv(argv, opts)` - unparse argv and change it to array style\n\n**Extend Helper**\n\n```js\n// index.js\nconst Command = require('common-bin');\nconst helper = require('./helper');\nclass MainCommand extends Command {\n  constructor(rawArgv) {\n    super(rawArgv);\n\n    // load sub command\n    this.load(path.join(__dirname, 'command'));\n\n    // custom helper\n    Object.assign(this.helper, helper);\n  }\n}\n```\n\n## Advanced Usage\n\n### Single Command\n\nJust need to provide `options` and `run()`.\n\n```js\nconst Command = require('common-bin');\nclass MainCommand extends Command {\n  constructor(rawArgv) {\n    super(rawArgv);\n    this.options = {\n      baseDir: {\n        description: 'target directory',\n      },\n    };\n  }\n\n  async run(context) {\n    console.log('run default command at %s', context.argv.baseDir);\n  }\n}\n```\n\n### Sub Command\n\nAlso support sub command such as `my-git remote add \u003cname\u003e \u003curl\u003e --tags`.\n\n```js\n// test/fixtures/my-git/command/remote.js\nclass RemoteCommand extends Command {\n  constructor(rawArgv) {\n    // DO NOT forgot to pass params to super\n    super(rawArgv);\n    // load sub command for directory\n    this.load(path.join(__dirname, 'remote'));\n  }\n\n  async run({ argv }) {\n    console.log('run remote command with %j', argv._);\n  }\n\n  get description() {\n    return 'Manage set of tracked repositories';\n  }\n}\n\n// test/fixtures/my-git/command/remote/add.js\nclass AddCommand extends Command {\n  constructor(rawArgv) {\n    super(rawArgv);\n\n    this.options = {\n      tags: {\n        type: 'boolean',\n        default: false,\n        description: 'imports every tag from the remote repository',\n      },\n    };\n\n  }\n\n  async run({ argv }) {\n    console.log('git remote add %s to %s with tags=%s', argv.name, argv.url, argv.tags);\n  }\n\n  get description() {\n    return 'Adds a remote named \u003cname\u003e for the repository at \u003curl\u003e';\n  }\n}\n```\n\nsee [remote.js](test/fixtures/my-git/command/remote.js) for more detail.\n\n\n### Async Support\n\n```js\nclass SleepCommand extends Command {\n  async run() {\n    await sleep('1s');\n    console.log('sleep 1s');\n  }\n\n  get description() {\n    return 'sleep showcase';\n  }\n}\n\nfunction sleep(ms) {\n  return new Promise(resolve =\u003e setTimeout(resolve, ms));\n}\n```\n\nsee [async-bin](test/fixtures/async-bin) for more detail.\n\n### Bash-Completions\n\n```bash\n$ # exec below will print usage for auto bash completion\n$ my-git completion\n$ # exec below will mount auto completion to your bash\n$ my-git completion \u003e\u003e ~/.bashrc\n```\n\n![Bash-Completions](https://cloud.githubusercontent.com/assets/227713/23980327/0a00e1a0-0a3a-11e7-81be-23b4d54d91ad.gif)\n\n\n## Migrating from v1 to v3\n\n### bin\n\n- `run` method is not longer exist.\n\n```js\n// 1.x\nconst run = require('common-bin').run;\nrun(require('../lib/my_program'));\n\n// 3.x\n// require a main Command\nconst Command = require('..');\nnew Command().start();\n```\n\n### Program\n\n- `Program` is just a `Command` sub class, you can call it `Main Command` now.\n- `addCommand()` is replace with `add()`.\n- Recommand to use `load()` to load the whole command directory.\n\n```js\n// 1.x\nthis.addCommand('test', path.join(__dirname, 'test_command.js'));\n\n// 3.x\nconst Command = require('common-bin');\nconst pkg = require('./package.json');\n\nclass MainCommand extends Command {\n  constructor() {\n    super();\n\n    this.add('test', path.join(__dirname, 'test_command.js'));\n    // or load the entire directory\n    this.load(path.join(__dirname, 'command'));\n  }\n}\n```\n\n### Command\n\n- `help()` is not use anymore.\n- should provide `name`, `description`, `options`.\n- `async run()` arguments had change to object, recommand to use destructuring style - `{ cwd, env, argv, rawArgv }`\n  - `argv` is an object parse by `yargs`, **not `args`.**\n  - `rawArgv` is equivalent to old `args`\n\n```js\n// 1.x\nclass TestCommand extends Command {\n  * run(cwd, args) {\n    console.log('run mocha test at %s with %j', cwd, args);\n  }\n}\n\n// 3.x\nclass TestCommand extends Command {\n  constructor() {\n    super();\n    // my-bin test --require=co-mocha\n    this.options = {\n      require: {\n        description: 'require module name',\n      },\n    };\n  }\n\n  async run({ cwd, env, argv, rawArgv }) {\n    console.log('run mocha test at %s with %j', cwd, argv);\n  }\n\n  get description() {\n    return 'unit test';\n  }\n}\n```\n\n### helper\n\n- `getIronNodeBin` is remove.\n- `child.kill` now support signal.\n\n## License\n\n[MIT](LICENSE)\n\u003c!-- GITCONTRIBUTOR_START --\u003e\n\n## Contributors\n\n|[\u003cimg src=\"https://avatars.githubusercontent.com/u/227713?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003eatian25\u003c/b\u003e\u003c/sub\u003e](https://github.com/atian25)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/156269?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003efengmk2\u003c/b\u003e\u003c/sub\u003e](https://github.com/fengmk2)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/360661?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003epopomore\u003c/b\u003e\u003c/sub\u003e](https://github.com/popomore)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/985607?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003edead-horse\u003c/b\u003e\u003c/sub\u003e](https://github.com/dead-horse)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/5856440?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003ewhxaxes\u003c/b\u003e\u003c/sub\u003e](https://github.com/whxaxes)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/9692408?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003eDiamondYuan\u003c/b\u003e\u003c/sub\u003e](https://github.com/DiamondYuan)\u003cbr/\u003e|\n| :---: | :---: | :---: | :---: | :---: | :---: |\n[\u003cimg src=\"https://avatars.githubusercontent.com/u/7477670?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003etenpend\u003c/b\u003e\u003c/sub\u003e](https://github.com/tenpend)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/6399899?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003ehacke2\u003c/b\u003e\u003c/sub\u003e](https://github.com/hacke2)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/11896359?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003eliuqipeng417\u003c/b\u003e\u003c/sub\u003e](https://github.com/liuqipeng417)\u003cbr/\u003e|[\u003cimg src=\"https://avatars.githubusercontent.com/u/36788851?v=4\" width=\"100px;\"/\u003e\u003cbr/\u003e\u003csub\u003e\u003cb\u003eJarvis2018\u003c/b\u003e\u003c/sub\u003e](https://github.com/Jarvis2018)\u003cbr/\u003e\n\nThis project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Sat Jun 04 2022 00:31:29 GMT+0800`.\n\n\u003c!-- GITCONTRIBUTOR_END --\u003e\n","funding_links":[],"categories":["Repository","JavaScript","others"],"sub_categories":["Command-line Utilities"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnode-modules%2Fcommon-bin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnode-modules%2Fcommon-bin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnode-modules%2Fcommon-bin/lists"}