common-bin v3.0.1
common-bin
Abstraction bin tool wrap yargs, to provide more convenient usage, support async / await style.
Install
$ npm i common-binBuild a bin tool for your team
You maybe need a custom xxx-bin to implement more custom features.
Now you can implement a Command sub class to do that.
Example: Write your own git command
This example will show you how to create a new my-git tool.
- Full demo: my-git
test/fixtures/my-git
├── bin
│ └── my-git.js
├── command
│ ├── remote
│ │ ├── add.js
│ │ └── remove.js
│ ├── clone.js
│ └── remote.js
├── index.js
└── package.jsonmy-git.js
#!/usr/bin/env node
'use strict';
const Command = require('..');
new Command().start();Main Command
Just extend Command, and use as your bin start point.
You can use this.yargs to custom yargs config, see http://yargs.js.org/docs for more detail.
const Command = require('common-bin');
const pkg = require('./package.json');
class MainCommand extends Command {
constructor(rawArgv) {
super(rawArgv);
this.usage = 'Usage: my-git <command> [options]';
// load entire command directory
this.load(path.join(__dirname, 'command'));
// or load special command file
// this.add(path.join(__dirname, 'test_command.js'));
// more custom with `yargs` api, such as you can use `my-git -V`
this.yargs.alias('V', 'version');
}
}
module.exports = MainCommand;CloneCommand
const Command = require('common-bin');
class CloneCommand extends Command {
constructor(rawArgv) {
super(rawArgv);
this.options = {
depth: {
type: 'number',
description: 'Create a shallow clone with a history truncated to the specified number of commits',
},
};
}
async run({ argv }) {
console.log('git clone %s to %s with depth %d', argv._[0], argv._[1], argv.depth);
}
get description() {
return 'Clone a repository into a new directory';
}
}
module.exports = CloneCommand;Run result
$ my-git clone gh://node-modules/common-bin dist --depth=1
git clone gh://node-modules/common-bin to dist with depth 1Concept
Command
Define the main logic of command
Method:
async start()- start your program, only use once in your bin file.async run(context)- should implement this to provide command handler, will exec when not found sub command.
- Support generator / async function / normal function which return promise.
contextis{ cwd, env, argv, rawArgv }cwd-process.cwd()env- clone env object fromprocess.envargv- argv parse result by yargs,{ _: [ 'start' ], '$0': '/usr/local/bin/common-bin', baseDir: 'simple'}rawArgv- the raw argv,[ "--baseDir=simple" ]
load(fullPath)- register the entire directory to commandsadd(name, target)- register special command with command name,targetcould be full path of file or Class.alias(alias, name)- register a command with an existing commandshowHelp()- print usage message to console.options=- a setter, shortcut foryargs.optionsusage=- a setter, shortcut foryargs.usage
Properties:
description- {String} a getter, only show this description when it's a sub command in help consolehelper- {Object} helper instanceyargs- {Object} yargs instance for advanced custom usageoptions- {Object} a setter, set yargs' optionsversion- {String} customize version, can be defined as a getter to support lazy load.parserOptions- {Object} controlcontextparse rule.execArgv- {Boolean} whether extractexecArgvtocontext.execArgvremoveAlias- {Boolean} whether remove alias key fromargvremoveCamelCase- {Boolean} whether remove camel case key fromargv
You can define options by set this.options
this.options = {
baseDir: {
alias: 'b',
demandOption: true,
description: 'the target directory',
coerce: str => path.resolve(process.cwd(), str),
},
depth: {
description: 'level to clone',
type: 'number',
default: 1,
},
size: {
description: 'choose a size',
choices: ['xs', 's', 'm', 'l', 'xl']
},
};You can define version by define this.version getter:
get version() {
return 'v1.0.0';
}Helper
async forkNode(modulePath, args, opt)- fork child process, wrap with promise and gracefull exitasync spawn(cmd, args, opt)- spawn a new process, wrap with promise and gracefull exitasync npmInstall(npmCli, name, cwd)- install node modules, wrap with promiseasync callFn(fn, args, thisArg)- call fn, support gernerator / async / normal function return promiseunparseArgv(argv, opts)- unparse argv and change it to array style
Extend Helper
// index.js
const Command = require('common-bin');
const helper = require('./helper');
class MainCommand extends Command {
constructor(rawArgv) {
super(rawArgv);
// load sub command
this.load(path.join(__dirname, 'command'));
// custom helper
Object.assign(this.helper, helper);
}
}Advanced Usage
Single Command
Just need to provide options and run().
const Command = require('common-bin');
class MainCommand extends Command {
constructor(rawArgv) {
super(rawArgv);
this.options = {
baseDir: {
description: 'target directory',
},
};
}
async run(context) {
console.log('run default command at %s', context.argv.baseDir);
}
}Sub Command
Also support sub command such as my-git remote add <name> <url> --tags.
// test/fixtures/my-git/command/remote.js
class RemoteCommand extends Command {
constructor(rawArgv) {
// DO NOT forgot to pass params to super
super(rawArgv);
// load sub command for directory
this.load(path.join(__dirname, 'remote'));
}
async run({ argv }) {
console.log('run remote command with %j', argv._);
}
get description() {
return 'Manage set of tracked repositories';
}
}
// test/fixtures/my-git/command/remote/add.js
class AddCommand extends Command {
constructor(rawArgv) {
super(rawArgv);
this.options = {
tags: {
type: 'boolean',
default: false,
description: 'imports every tag from the remote repository',
},
};
}
async run({ argv }) {
console.log('git remote add %s to %s with tags=%s', argv.name, argv.url, argv.tags);
}
get description() {
return 'Adds a remote named <name> for the repository at <url>';
}
}see remote.js for more detail.
Async Support
class SleepCommand extends Command {
async run() {
await sleep('1s');
console.log('sleep 1s');
}
get description() {
return 'sleep showcase';
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}see async-bin for more detail.
Bash-Completions
$ # exec below will print usage for auto bash completion
$ my-git completion
$ # exec below will mount auto completion to your bash
$ my-git completion >> ~/.bashrc
Migrating from v1 to v3
bin
runmethod is not longer exist.
// 1.x
const run = require('common-bin').run;
run(require('../lib/my_program'));
// 3.x
// require a main Command
const Command = require('..');
new Command().start();Program
Programis just aCommandsub class, you can call itMain Commandnow.addCommand()is replace withadd().- Recommand to use
load()to load the whole command directory.
// 1.x
this.addCommand('test', path.join(__dirname, 'test_command.js'));
// 3.x
const Command = require('common-bin');
const pkg = require('./package.json');
class MainCommand extends Command {
constructor() {
super();
this.add('test', path.join(__dirname, 'test_command.js'));
// or load the entire directory
this.load(path.join(__dirname, 'command'));
}
}Command
help()is not use anymore.- should provide
name,description,options. async run()arguments had change to object, recommand to use destructuring style -{ cwd, env, argv, rawArgv }argvis an object parse byyargs, notargs.rawArgvis equivalent to oldargs
// 1.x
class TestCommand extends Command {
* run(cwd, args) {
console.log('run mocha test at %s with %j', cwd, args);
}
}
// 3.x
class TestCommand extends Command {
constructor() {
super();
// my-bin test --require=co-mocha
this.options = {
require: {
description: 'require module name',
},
};
}
async run({ cwd, env, argv, rawArgv }) {
console.log('run mocha test at %s with %j', cwd, argv);
}
get description() {
return 'unit test';
}
}helper
getIronNodeBinis remove.child.killnow support signal.
License
Contributors
| atian25 | fengmk2 | popomore | dead-horse | whxaxes | DiamondYuan |
|---|---|---|---|---|---|
| tenpend | hacke2 | liuqipeng417 | Jarvis2018 |
This project follows the git-contributor spec, auto updated at Sat Jun 04 2022 00:31:29 GMT+0800.
3 years ago
3 years ago
4 years ago
4 years ago
5 years ago
6 years ago
6 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago