@tedslittlerobot/commandeer
v1.4.0
Published
A wrapper around CommanderJS with a few common utilities.
Downloads
128
Readme
Commandeer
A wrapper around CommanderJS with a few common utilities.
Also supplies integrations with:
- CommanderJS A CLI library for NodeJS
- Listr2 A library for managing concurrent and consecutive task lists in NodeJS
- Margaret Lanterman A library for managing streaming things to log files
- Gloucester A library for managing verbosity state
Installation
npm i @tedslittlerobot/commandeer
Usage
TSConfig
There is a base tsconfig.json which can be used as follows. Note that you must still provide all paths, as paths within an extended config are relative to where the config is extended from.
{
"extends": "./node_modules/@tedslittlerobot/commandeer/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"baseUrl": "./",
"outDir": "build",
"paths": {
"src/*": ["./src/*"]
},
},
"include": [
"src/**/*.ts"
],
"exclude": ["node_modules"]
}
Writing CLI Apps
Commandeer introduces a convention for registering commands and groups in commander.
src
├── index.ts # The root index.ts should define the program
├── commands
│ ├── config.ts # This should export the Command definition for a command
│ ├── users # This is a sub-command / command group
│ │ ├── index.ts # This should export the Group definition
│ │ ├── details.ts # This should export the Command definition for a command
│ │ └── create.ts # This should export the Command definition for a command
│ └── index.ts # This should export an array of all commands
├── helpers # A directory for generic helpers
│ ├── my-api.ts
│ └── wordart.ts
└── tasks # A directory of Listr2 Task Lists
└── user-details
├── index.ts # This should export the task list
├── api-call.ts # More complex individual tasks may have their own file
├── evaluate-study.test.ts # And their own tests
├── evaluate-study.test.ts # And their own tests
├── io.ts # ... more complex task file
└── types.ts # And potentially some specific type definitions
Root index.ts
Firstly, your main/root index.ts / entry point should be:
#! /usr/bin/env node
import {env} from 'node:process';
import commands from 'src/commands/index.js';
import {runProgram} from '@tedslittlerobot/commandeer/lib/commander';
runProgram(
'my-command-name',
env.COMMANDER_VERSION ?? 'vx.x.x',
'A vulnerability scan checker for trialmotif',
commands,
);
Commands Index File commands/index.ts
The commands index file should export an array of all groups and top level commands.
import users from './users/index.js';
const commands = [users];
export default commands;
Command Group Definition commands/users/index.ts
A Command Group contains some configuration options and some sub commands:
import type {CommandGroup} from '@tedslittlerobot/commandeer';
import create from './create.js';
import details from './details.js';
const group: CommandGroup = {
type: 'group',
name: 'users',
description: 'Some commands to peform user actions',
commands: [create, details],
};
export default group;
Command Definition commands/users/index.ts
A Command Group contains some configuration options and some sub commands:
import type {CommandRegistrar} from '@tedslittlerobot/commandeer/lib/commander';
import {type Command} from 'commander';
type Options = {
output?: string;
};
const command: CommandRegistrar = {
type: 'command',
name: 'details',
description: 'Get some user details',
config(command) {
command
.argument('<userId>', 'The user ID')
.option('-o, --output <file>', 'Optional location to store manifest file');
},
async action(
userId: string,
options: Options,
command: Command,
) {
console.info(`User details for ${userId}`);
},
completions() {
return [];
},
};
export default command;
Command Definition With Listr2 commands/users/index.ts
A Command Group contains some configuration options and some sub commands:
import type {CommandRegistrar} from '@tedslittlerobot/commandeer/lib/commander';
import {runTasks} from '@tedslittlerobot/commandeer/lib/listr';
import {type Command} from 'commander';
import tasks, {summarise} from 'src/tasks/user-details.js';
type Options = {
output?: string;
};
const command: CommandRegistrar = {
type: 'command',
name: 'details',
description: 'Get some user details',
config(command) {
command
.argument('<userId>', 'The user ID')
.option('-o, --output <file>', 'Optional location to store output file');
},
async action(
userId: string,
options: Options,
command: Command,
) {
summarise( // A summarise method to handle final output
await runTasks(tasks(options.output)), // And an async run of the relevant tasks
);
},
completions() {
return [];
},
};
export default command;
Task List File tasks/user-details/index.ts
import {stderr, stdout} from 'node:process';
import {type ListrTask} from 'listr2';
import chalk, {type ChalkInstance} from 'chalk';
import apiCall from './user-details.api.js';
type MvmTaskContext = {
payload: Record<string, string>;
name: string;
};
export default function mvmTasks(
userId: string,
outputFile?: string,
format: ChalkInstance = chalk.cyan.bold,
): Array<ListrTask<MvmTaskContext>> {
return [
{
title: format('Getting user details from API'),
async task(context, task) {
context.payload = await apiCall(
userId,
['config option 1', 'config option 1']
);
task.title = 'Details retrieved from API';
},
},
{
title: format('Parsing API Payload'),
async task(context) {
context.name = context.payload.name;
},
},
];
}
export function summarise(({payload, name}): MvmTaskContext) {
const table = new CliTable3();
table.push(payload);
strerr.write(table.toString()); // For user friendly output to stderr
stdout.write(JSON.stringify({name})); // eg. for piping to stdout
}