From 943f9c072a434e62d95f575c5f635f6638e04c62 Mon Sep 17 00:00:00 2001 From: lemon-clown Date: Mon, 13 Jul 2020 00:27:16 +0800 Subject: [PATCH] :construction: [util-cli] feat: support parent options in action callback see https://github.com/tj/commander.js/pull/1129 --- .vscode/settings.json | 3 ++ utils/cli/src/commander.ts | 96 +++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d3d05cc..286062c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "cSpell.words": [ "Builtins", "Lerna", + "Subcommand", "autoprefixer", "autorestart", "axios", @@ -34,6 +35,7 @@ "postbuild", "postcss", "prebuild", + "probaby", "rapit", "readdir", "recognised", @@ -41,6 +43,7 @@ "rollup", "sourcemap", "styl", + "subcommands", "tada", "tsbuildinfo", "typeof", diff --git a/utils/cli/src/commander.ts b/utils/cli/src/commander.ts index 5a416c9..a97fd5b 100644 --- a/utils/cli/src/commander.ts +++ b/utils/cli/src/commander.ts @@ -1,5 +1,97 @@ import commander from 'commander' import { ColorfulChalkLogger } from '@barusu/chalk-logger' +import { CommandOptionConfig } from './option' + + +/** + * Callback for handling the command + * + * @param args command arguments + * @param options command options + * @param extra extra args (neither declared command arguments nor command options) + */ +export type CommandActionCallback = ( + args: string[], + options: T, + extra: string[], +) => void | Promise | never + + +export class Command extends commander.Command { + /** + * Register callback `fn` for the command. + * + * Examples: + * + * program + * .command('help') + * .description('display verbose help') + * .action(function() { + * // output help here + * }); + * + * @param {Function} fn + * @return {Command} `this` command for chaining + * @api public + */ + public action(fn: CommandActionCallback): this { + const self = this + + const listener = (args: string[]): void => { + // The .action callback takes an extra parameter which is the command or options. + const expectedArgsCount = self._args.length + + // const actionArgs: (string | Record | string[])[] = [ + const actionArgs: [string[], T, string[]] = [ + // Command arguments + args.slice(0, expectedArgsCount), + + // Command options + self.opts() as T, + + // Extra arguments so available too. + args.slice(expectedArgsCount) + ] + + const actionResult = fn.apply(self, actionArgs) + + // Remember result in case it is async. Assume parseAsync getting called on root. + let rootCommand: Command = self + while (rootCommand.parent != null) { + rootCommand = rootCommand.parent + } + rootCommand._actionResults.push(actionResult) + } + + self._actionHandler = listener + return self + } + + public opts(): Record { + const self = this + const nodes: Command[] = [self] + for (let parent = self.parent; parent != null; parent = parent.parent) { + nodes.push(parent) + } + + const options: Record = {} + for (let i = nodes.length - 1; i >= 0; --i) { + const o = nodes[i] + if (o._storeOptionsAsProperties) { + for (const option of o.options) { + const key = option.attributeName() + options[key] = (key === o._versionOptionName) ? o._version : o[key] + } + } else { + for (const key of Object.getOwnPropertyNames(o._optionValues)) { + options[key] = o._optionValues[key] + } + } + } + + return options + } +} export { commander } @@ -15,8 +107,8 @@ export function createTopCommand( commandName: string, version: string, logger: ColorfulChalkLogger, -): commander.Command { - const program = new commander.Command() +): Command { + const program = new Command() program .storeOptionsAsProperties(false)