Starting CLI Development
Step by step initialize a CLI app
The very basics
A very bare bone node CLI app may look like this, where bin/index.js
contains an executable and the package.json
contains the necessary manifest to make it work:
.
├── README.md
├── bin
│ └── index.js
└── package.json
The bin file should contain a "shebang", #!/usr/bin/env node
, like this:
#!/usr/bin/env node
console.log('hello, world!);
To link the command associated with bin/index.js
, include these fields in package.json
:
"name": "@box/cli",
"main": "bin/index.js",
"bin": {
"box": "./bin/index.js"
}
This will link command line calls of box
to the associated bin/index.js
Then, for bash to be able to execute it, the bin/index.js
needs write access:
chmod +x ./bin/index.js
Now, run yarn link
:
yarn link v1.21.1
success Registered "@box/cli".
info You can now run `yarn link "@box/cli"` in the projects where you want to use this package and it will be used instead.
✨ Done in 0.05s.
Using commanderjs
Commanderjs is a godlike package to create cli apps. To a near minimum an app may look like this:
#!/usr/bin/env node
const program = require('commander');
program
.version('0.0.1')
.option('-h, --hash [hash]', 'download a box by hash')
.parse(process.argv);
console.log(program.hash);
Structuring the commands
Say the box
cli app will implement an install
and a list
sub-commands, it can look like this:
#!/usr/bin/env node
const program = require('commander');
program.version('0.0.1');
// command "install"
program
.command('install <hash>')
.description('download a box by hash')
.action(hash => {
console.log('installing %s', hash);
});
// command "list"
program
.command('list')
.description('list installed boxes')
.action(() => {
console.log('here are the installed boxes');
});
program.parse(process.argv);
now running box install XXX
will execute the install
subcommand with a hash
input, and running box list
will execute the list
subcommand.
Bundling with RollupJS
We may prefer to have some organization of our code, i.e., separate them into a few files in src/
and use a package bundler to automatically build them into a single bin/index.js
node executable, like this:
.
├── README.md
├── bin
│ └── index.js
├── package.json
└── src
├── api.js
└── index.js
We can use a bundler, such as rollup.js. Here is a very minimalistic config:
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'bin/index.js',
format: 'cjs',
},
};
Automatically add shebang
The bin entry point should have a shebang, but including the shebang in src/index.js
would not pass rollup compiling. there are a number of things we can do, although the one that makes most sence is to not include shebang in src/index
, but add the line during rollup compilation. we can do this by adding a banner
in rollup output:
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'bin/index.js',
format: 'cjs',
+ banner: '#!/usr/bin/env node\n', // add shebang
},
};
chmod
for rollup output
{
"scripts": {
"build": "rollup -c rollup.config.js && chmod a+x bin/index.js"
}
}
this will append a chmod
after every build
Watch file changes on src/
{
"scripts": {
"build": "rollup -c rollup.config.js && chmod a+x bin/index.js",
"dev": "rollup -w -c rollup.config.js"
}
}
this completes the rollup setup for now