This commit is contained in:
Tommy Parnell
2022-07-08 20:03:30 -04:00
commit d9c6f73941
8 changed files with 1143 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
dist

15
Readme.md Normal file
View File

@@ -0,0 +1,15 @@
## CLI Example
A simple example of making gorgeous CLI's with Typescript, Commander.js, and ora.
Read this blog post for more information.
![a video demo](./cli.mp4)
## How to run this
`npm install`
`npm build`
`npm link`
You should now have `terribledev` as a command on your path

BIN
cli.mp4 Normal file

Binary file not shown.

27
index.ts Normal file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env node
import { Command } from 'commander'
import { spinnerError, stopSpinner } from './spinner';
import { widgets } from './widgets';
const program = new Command('Our New CLI');
program.option('-v, --verbose', 'verbose logging');
program.version('0.0.1');
program.addHelpCommand()
program.addCommand(widgets);
async function main() {
await program.parseAsync();
}
console.log() // log a new line so there is a nice space
main();
process.on('unhandledRejection', function (err: Error) {
const debug = program.opts().verbose;
if(debug) {
console.error(err.stack);
}
spinnerError()
stopSpinner()
program.error('', { exitCode: 1 });
})

1011
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "terribledev",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"build": "tsc --noEmit ./index.ts && esbuild index.ts --bundle --platform=node --format=cjs --outfile=dist/index.js",
"build:notype": "esbuild index.ts --bundle --platform=node --format=cjs --outfile=dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^18.0.3",
"commander": "^9.3.0",
"esbuild": "^0.14.48",
"ora": "^6.1.2"
},
"bin": {
"terribledev": "./dist/index.js"
}
}

32
spinner.ts Normal file
View File

@@ -0,0 +1,32 @@
import ora from 'ora';
const spinner = ora({
spinner: 'dots',
})
export const updateSpinnerText = (message: string) => {
if(spinner.isSpinning) {
spinner.text = message
return;
}
spinner.start(message)
}
export const stopSpinner = () => {
if(spinner.isSpinning) {
spinner.stop()
}
}
export const spinnerError = (message?: string) => {
if(spinner.isSpinning) {
spinner.fail(message)
}
}
export const spinnerSuccess = (message?: string) => {
if(spinner.isSpinning) {
spinner.succeed(message)
}
}
export const spinnerInfo = (message: string) => {
spinner.info(message)
}

34
widgets.ts Normal file
View File

@@ -0,0 +1,34 @@
import { Command } from "commander";
import { spinnerError, spinnerInfo, spinnerSuccess, updateSpinnerText } from "./spinner";
export const widgets = new Command("widgets");
widgets.command("list").action(async () => {
updateSpinnerText("Processing ");
// do work
await new Promise(resolve => setTimeout(resolve, 1000)); // emulate work
spinnerSuccess()
console.table([{ id: 1, name: "Tommy" }, { id: 2, name: "Bob" }]);
})
widgets.command("get")
.argument("widget id <id>", "the id of the widget")
.option("-f, --format <format>", "the format of the widget")
.action(async (id, options) => {
updateSpinnerText("Getting widget " + id);
await new Promise(resolve => setTimeout(resolve, 3000));
spinnerSuccess()
console.table({ id: 1, name: "Tommy" })
})
widgets.command("fail").action(async () => {
updateSpinnerText("Processing a handled failure ");
await new Promise(resolve => setTimeout(resolve, 3000));
spinnerError()
})
widgets.command("unhandled-error").action(async () => {
updateSpinnerText("Processing an unhandled failure ");
await new Promise(resolve => setTimeout(resolve, 3000));
throw new Error("Unhandled error");
})