Creating a CLI with TypeScript
- 8/12/2018
- ·
- #typescript
- #howto
- #react
With a tiny bit of configuration, the same statically-typed goodness we’re used to in web applications is just as accessible–and just as helpful–for building command line interfaces. TypeScript simplifies JavaScript at scale. Who said it had to be in the browser?
Configuration
First things first: let’s set up a new project
$ mkdir ts-cli && cd ts-cli
$ npm init # etc
$ npm install --save-dev @types/node
We’ll also want to add a simple tsconfig.json
for the TypeScript compiler.
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"strict": true,
"esModuleInterop": true
}
}
Finally, we’ll need a runtime. Some day deno
will be neatly packaged
up for public consumption. Some day. In the meantime, ts-node
will do the
trick:
$ npm install -g ts-node
Configuration, check. We’re ready to set about building the CLI.
The CLI
Here’s scaffolding for a command-line interface with two commands, echo
and
help
:
#!/usr/bin/env ts-node
import * as path from 'path';
type Command = (...args: string[]) => Promise<void>;
const CLIFILE = path.relative(process.cwd(), process.argv[1]);
const commandUsage = (subcmd: string, details: string) => {
console.log(`Usage: ${CLIFILE} ${subcmd} ${details}`);
process.exit(2);
};
const commands: { [s: string]: Command } = {
async help() {
usage();
},
async echo(...args: string[]) {
if (args.length < 1) {
commandUsage('echo', '<string>');
}
console.log(args.join(' '));
},
};
const usage = () => {
const commandKeys = Object.keys(commands);
console.log(`Demo TypeScript CLI
Usage: ./${CLIFILE} ${commandKeys.join('|')}
`);
};
const key = process.argv[2];
const handler = commands[key];
if (typeof handler !== 'function') {
usage();
process.exit(2);
}
commands[key](...process.argv.slice(3));
Like any good interface, most of the code here is error-handling and
documentation. There’s a usage
function to describe the interface’s commands,
a small wrapper for commandUsage
, and a tiny bit of bootstrap logic to kick
it all off. Still, it’s enough to prove the toolchain and provide a jumping-off
point for more sophisticated TypeScript applications.
Let’s set the executable bit and see how things go.
$ chmod +x cli.ts
# See usage information
$ ./cli.ts
Demo TypeScript CLI
Usage: ./cli.ts help|echo
# And actually echo something
$ ./cli.ts echo 'Hello, world!'
Hello, world!
Perfect. With less than five minutes’ work we’ve got ourselves a CLI. From here, we can add more commands, optional flags, or whatever other inputs our application needs–now with static type-checking all the way down!