1import { assertNotStrictEqual, } from './typings/common-types.js'; 2import { isPromise } from './utils/is-promise.js'; 3import { applyMiddleware, commandMiddlewareFactory, } from './middleware.js'; 4import { parseCommand } from './parse-command.js'; 5import { isYargsInstance, } from './yargs-factory.js'; 6import { maybeAsyncResult } from './utils/maybe-async-result.js'; 7import whichModule from './utils/which-module.js'; 8const DEFAULT_MARKER = /(^\*)|(^\$0)/; 9export class CommandInstance { 10 constructor(usage, validation, globalMiddleware, shim) { 11 this.requireCache = new Set(); 12 this.handlers = {}; 13 this.aliasMap = {}; 14 this.frozens = []; 15 this.shim = shim; 16 this.usage = usage; 17 this.globalMiddleware = globalMiddleware; 18 this.validation = validation; 19 } 20 addDirectory(dir, req, callerFile, opts) { 21 opts = opts || {}; 22 if (typeof opts.recurse !== 'boolean') 23 opts.recurse = false; 24 if (!Array.isArray(opts.extensions)) 25 opts.extensions = ['js']; 26 const parentVisit = typeof opts.visit === 'function' ? opts.visit : (o) => o; 27 opts.visit = (obj, joined, filename) => { 28 const visited = parentVisit(obj, joined, filename); 29 if (visited) { 30 if (this.requireCache.has(joined)) 31 return visited; 32 else 33 this.requireCache.add(joined); 34 this.addHandler(visited); 35 } 36 return visited; 37 }; 38 this.shim.requireDirectory({ require: req, filename: callerFile }, dir, opts); 39 } 40 addHandler(cmd, description, builder, handler, commandMiddleware, deprecated) { 41 let aliases = []; 42 const middlewares = commandMiddlewareFactory(commandMiddleware); 43 handler = handler || (() => { }); 44 if (Array.isArray(cmd)) { 45 if (isCommandAndAliases(cmd)) { 46 [cmd, ...aliases] = cmd; 47 } 48 else { 49 for (const command of cmd) { 50 this.addHandler(command); 51 } 52 } 53 } 54 else if (isCommandHandlerDefinition(cmd)) { 55 let command = Array.isArray(cmd.command) || typeof cmd.command === 'string' 56 ? cmd.command 57 : this.moduleName(cmd); 58 if (cmd.aliases) 59 command = [].concat(command).concat(cmd.aliases); 60 this.addHandler(command, this.extractDesc(cmd), cmd.builder, cmd.handler, cmd.middlewares, cmd.deprecated); 61 return; 62 } 63 else if (isCommandBuilderDefinition(builder)) { 64 this.addHandler([cmd].concat(aliases), description, builder.builder, builder.handler, builder.middlewares, builder.deprecated); 65 return; 66 } 67 if (typeof cmd === 'string') { 68 const parsedCommand = parseCommand(cmd); 69 aliases = aliases.map(alias => parseCommand(alias).cmd); 70 let isDefault = false; 71 const parsedAliases = [parsedCommand.cmd].concat(aliases).filter(c => { 72 if (DEFAULT_MARKER.test(c)) { 73 isDefault = true; 74 return false; 75 } 76 return true; 77 }); 78 if (parsedAliases.length === 0 && isDefault) 79 parsedAliases.push('$0'); 80 if (isDefault) { 81 parsedCommand.cmd = parsedAliases[0]; 82 aliases = parsedAliases.slice(1); 83 cmd = cmd.replace(DEFAULT_MARKER, parsedCommand.cmd); 84 } 85 aliases.forEach(alias => { 86 this.aliasMap[alias] = parsedCommand.cmd; 87 }); 88 if (description !== false) { 89 this.usage.command(cmd, description, isDefault, aliases, deprecated); 90 } 91 this.handlers[parsedCommand.cmd] = { 92 original: cmd, 93 description, 94 handler, 95 builder: builder || {}, 96 middlewares, 97 deprecated, 98 demanded: parsedCommand.demanded, 99 optional: parsedCommand.optional, 100 }; 101 if (isDefault) 102 this.defaultCommand = this.handlers[parsedCommand.cmd]; 103 } 104 } 105 getCommandHandlers() { 106 return this.handlers; 107 } 108 getCommands() { 109 return Object.keys(this.handlers).concat(Object.keys(this.aliasMap)); 110 } 111 hasDefaultCommand() { 112 return !!this.defaultCommand; 113 } 114 runCommand(command, yargs, parsed, commandIndex, helpOnly, helpOrVersionSet) { 115 const commandHandler = this.handlers[command] || 116 this.handlers[this.aliasMap[command]] || 117 this.defaultCommand; 118 const currentContext = yargs.getInternalMethods().getContext(); 119 const parentCommands = currentContext.commands.slice(); 120 const isDefaultCommand = !command; 121 if (command) { 122 currentContext.commands.push(command); 123 currentContext.fullCommands.push(commandHandler.original); 124 } 125 const builderResult = this.applyBuilderUpdateUsageAndParse(isDefaultCommand, commandHandler, yargs, parsed.aliases, parentCommands, commandIndex, helpOnly, helpOrVersionSet); 126 return isPromise(builderResult) 127 ? builderResult.then(result => this.applyMiddlewareAndGetResult(isDefaultCommand, commandHandler, result.innerArgv, currentContext, helpOnly, result.aliases, yargs)) 128 : this.applyMiddlewareAndGetResult(isDefaultCommand, commandHandler, builderResult.innerArgv, currentContext, helpOnly, builderResult.aliases, yargs); 129 } 130 applyBuilderUpdateUsageAndParse(isDefaultCommand, commandHandler, yargs, aliases, parentCommands, commandIndex, helpOnly, helpOrVersionSet) { 131 const builder = commandHandler.builder; 132 let innerYargs = yargs; 133 if (isCommandBuilderCallback(builder)) { 134 const builderOutput = builder(yargs.getInternalMethods().reset(aliases), helpOrVersionSet); 135 if (isPromise(builderOutput)) { 136 return builderOutput.then(output => { 137 innerYargs = isYargsInstance(output) ? output : yargs; 138 return this.parseAndUpdateUsage(isDefaultCommand, commandHandler, innerYargs, parentCommands, commandIndex, helpOnly); 139 }); 140 } 141 } 142 else if (isCommandBuilderOptionDefinitions(builder)) { 143 innerYargs = yargs.getInternalMethods().reset(aliases); 144 Object.keys(commandHandler.builder).forEach(key => { 145 innerYargs.option(key, builder[key]); 146 }); 147 } 148 return this.parseAndUpdateUsage(isDefaultCommand, commandHandler, innerYargs, parentCommands, commandIndex, helpOnly); 149 } 150 parseAndUpdateUsage(isDefaultCommand, commandHandler, innerYargs, parentCommands, commandIndex, helpOnly) { 151 if (isDefaultCommand) 152 innerYargs.getInternalMethods().getUsageInstance().unfreeze(); 153 if (this.shouldUpdateUsage(innerYargs)) { 154 innerYargs 155 .getInternalMethods() 156 .getUsageInstance() 157 .usage(this.usageFromParentCommandsCommandHandler(parentCommands, commandHandler), commandHandler.description); 158 } 159 const innerArgv = innerYargs 160 .getInternalMethods() 161 .runYargsParserAndExecuteCommands(null, undefined, true, commandIndex, helpOnly); 162 return isPromise(innerArgv) 163 ? innerArgv.then(argv => ({ 164 aliases: innerYargs.parsed.aliases, 165 innerArgv: argv, 166 })) 167 : { 168 aliases: innerYargs.parsed.aliases, 169 innerArgv: innerArgv, 170 }; 171 } 172 shouldUpdateUsage(yargs) { 173 return (!yargs.getInternalMethods().getUsageInstance().getUsageDisabled() && 174 yargs.getInternalMethods().getUsageInstance().getUsage().length === 0); 175 } 176 usageFromParentCommandsCommandHandler(parentCommands, commandHandler) { 177 const c = DEFAULT_MARKER.test(commandHandler.original) 178 ? commandHandler.original.replace(DEFAULT_MARKER, '').trim() 179 : commandHandler.original; 180 const pc = parentCommands.filter(c => { 181 return !DEFAULT_MARKER.test(c); 182 }); 183 pc.push(c); 184 return `$0 ${pc.join(' ')}`; 185 } 186 applyMiddlewareAndGetResult(isDefaultCommand, commandHandler, innerArgv, currentContext, helpOnly, aliases, yargs) { 187 let positionalMap = {}; 188 if (helpOnly) 189 return innerArgv; 190 if (!yargs.getInternalMethods().getHasOutput()) { 191 positionalMap = this.populatePositionals(commandHandler, innerArgv, currentContext, yargs); 192 } 193 const middlewares = this.globalMiddleware 194 .getMiddleware() 195 .slice(0) 196 .concat(commandHandler.middlewares); 197 innerArgv = applyMiddleware(innerArgv, yargs, middlewares, true); 198 if (!yargs.getInternalMethods().getHasOutput()) { 199 const validation = yargs 200 .getInternalMethods() 201 .runValidation(aliases, positionalMap, yargs.parsed.error, isDefaultCommand); 202 innerArgv = maybeAsyncResult(innerArgv, result => { 203 validation(result); 204 return result; 205 }); 206 } 207 if (commandHandler.handler && !yargs.getInternalMethods().getHasOutput()) { 208 yargs.getInternalMethods().setHasOutput(); 209 const populateDoubleDash = !!yargs.getOptions().configuration['populate--']; 210 yargs 211 .getInternalMethods() 212 .postProcess(innerArgv, populateDoubleDash, false, false); 213 innerArgv = applyMiddleware(innerArgv, yargs, middlewares, false); 214 innerArgv = maybeAsyncResult(innerArgv, result => { 215 const handlerResult = commandHandler.handler(result); 216 return isPromise(handlerResult) 217 ? handlerResult.then(() => result) 218 : result; 219 }); 220 if (!isDefaultCommand) { 221 yargs.getInternalMethods().getUsageInstance().cacheHelpMessage(); 222 } 223 if (isPromise(innerArgv) && 224 !yargs.getInternalMethods().hasParseCallback()) { 225 innerArgv.catch(error => { 226 try { 227 yargs.getInternalMethods().getUsageInstance().fail(null, error); 228 } 229 catch (_err) { 230 } 231 }); 232 } 233 } 234 if (!isDefaultCommand) { 235 currentContext.commands.pop(); 236 currentContext.fullCommands.pop(); 237 } 238 return innerArgv; 239 } 240 populatePositionals(commandHandler, argv, context, yargs) { 241 argv._ = argv._.slice(context.commands.length); 242 const demanded = commandHandler.demanded.slice(0); 243 const optional = commandHandler.optional.slice(0); 244 const positionalMap = {}; 245 this.validation.positionalCount(demanded.length, argv._.length); 246 while (demanded.length) { 247 const demand = demanded.shift(); 248 this.populatePositional(demand, argv, positionalMap); 249 } 250 while (optional.length) { 251 const maybe = optional.shift(); 252 this.populatePositional(maybe, argv, positionalMap); 253 } 254 argv._ = context.commands.concat(argv._.map(a => '' + a)); 255 this.postProcessPositionals(argv, positionalMap, this.cmdToParseOptions(commandHandler.original), yargs); 256 return positionalMap; 257 } 258 populatePositional(positional, argv, positionalMap) { 259 const cmd = positional.cmd[0]; 260 if (positional.variadic) { 261 positionalMap[cmd] = argv._.splice(0).map(String); 262 } 263 else { 264 if (argv._.length) 265 positionalMap[cmd] = [String(argv._.shift())]; 266 } 267 } 268 cmdToParseOptions(cmdString) { 269 const parseOptions = { 270 array: [], 271 default: {}, 272 alias: {}, 273 demand: {}, 274 }; 275 const parsed = parseCommand(cmdString); 276 parsed.demanded.forEach(d => { 277 const [cmd, ...aliases] = d.cmd; 278 if (d.variadic) { 279 parseOptions.array.push(cmd); 280 parseOptions.default[cmd] = []; 281 } 282 parseOptions.alias[cmd] = aliases; 283 parseOptions.demand[cmd] = true; 284 }); 285 parsed.optional.forEach(o => { 286 const [cmd, ...aliases] = o.cmd; 287 if (o.variadic) { 288 parseOptions.array.push(cmd); 289 parseOptions.default[cmd] = []; 290 } 291 parseOptions.alias[cmd] = aliases; 292 }); 293 return parseOptions; 294 } 295 postProcessPositionals(argv, positionalMap, parseOptions, yargs) { 296 const options = Object.assign({}, yargs.getOptions()); 297 options.default = Object.assign(parseOptions.default, options.default); 298 for (const key of Object.keys(parseOptions.alias)) { 299 options.alias[key] = (options.alias[key] || []).concat(parseOptions.alias[key]); 300 } 301 options.array = options.array.concat(parseOptions.array); 302 options.config = {}; 303 const unparsed = []; 304 Object.keys(positionalMap).forEach(key => { 305 positionalMap[key].map(value => { 306 if (options.configuration['unknown-options-as-args']) 307 options.key[key] = true; 308 unparsed.push(`--${key}`); 309 unparsed.push(value); 310 }); 311 }); 312 if (!unparsed.length) 313 return; 314 const config = Object.assign({}, options.configuration, { 315 'populate--': false, 316 }); 317 const parsed = this.shim.Parser.detailed(unparsed, Object.assign({}, options, { 318 configuration: config, 319 })); 320 if (parsed.error) { 321 yargs 322 .getInternalMethods() 323 .getUsageInstance() 324 .fail(parsed.error.message, parsed.error); 325 } 326 else { 327 const positionalKeys = Object.keys(positionalMap); 328 Object.keys(positionalMap).forEach(key => { 329 positionalKeys.push(...parsed.aliases[key]); 330 }); 331 const defaults = yargs.getOptions().default; 332 Object.keys(parsed.argv).forEach(key => { 333 if (positionalKeys.includes(key)) { 334 if (!positionalMap[key]) 335 positionalMap[key] = parsed.argv[key]; 336 if (!Object.prototype.hasOwnProperty.call(defaults, key) && 337 Object.prototype.hasOwnProperty.call(argv, key) && 338 Object.prototype.hasOwnProperty.call(parsed.argv, key) && 339 (Array.isArray(argv[key]) || Array.isArray(parsed.argv[key]))) { 340 argv[key] = [].concat(argv[key], parsed.argv[key]); 341 } 342 else { 343 argv[key] = parsed.argv[key]; 344 } 345 } 346 }); 347 } 348 } 349 runDefaultBuilderOn(yargs) { 350 if (!this.defaultCommand) 351 return; 352 if (this.shouldUpdateUsage(yargs)) { 353 const commandString = DEFAULT_MARKER.test(this.defaultCommand.original) 354 ? this.defaultCommand.original 355 : this.defaultCommand.original.replace(/^[^[\]<>]*/, '$0 '); 356 yargs 357 .getInternalMethods() 358 .getUsageInstance() 359 .usage(commandString, this.defaultCommand.description); 360 } 361 const builder = this.defaultCommand.builder; 362 if (isCommandBuilderCallback(builder)) { 363 return builder(yargs, true); 364 } 365 else if (!isCommandBuilderDefinition(builder)) { 366 Object.keys(builder).forEach(key => { 367 yargs.option(key, builder[key]); 368 }); 369 } 370 return undefined; 371 } 372 moduleName(obj) { 373 const mod = whichModule(obj); 374 if (!mod) 375 throw new Error(`No command name given for module: ${this.shim.inspect(obj)}`); 376 return this.commandFromFilename(mod.filename); 377 } 378 commandFromFilename(filename) { 379 return this.shim.path.basename(filename, this.shim.path.extname(filename)); 380 } 381 extractDesc({ describe, description, desc }) { 382 for (const test of [describe, description, desc]) { 383 if (typeof test === 'string' || test === false) 384 return test; 385 assertNotStrictEqual(test, true, this.shim); 386 } 387 return false; 388 } 389 freeze() { 390 this.frozens.push({ 391 handlers: this.handlers, 392 aliasMap: this.aliasMap, 393 defaultCommand: this.defaultCommand, 394 }); 395 } 396 unfreeze() { 397 const frozen = this.frozens.pop(); 398 assertNotStrictEqual(frozen, undefined, this.shim); 399 ({ 400 handlers: this.handlers, 401 aliasMap: this.aliasMap, 402 defaultCommand: this.defaultCommand, 403 } = frozen); 404 } 405 reset() { 406 this.handlers = {}; 407 this.aliasMap = {}; 408 this.defaultCommand = undefined; 409 this.requireCache = new Set(); 410 return this; 411 } 412} 413export function command(usage, validation, globalMiddleware, shim) { 414 return new CommandInstance(usage, validation, globalMiddleware, shim); 415} 416export function isCommandBuilderDefinition(builder) { 417 return (typeof builder === 'object' && 418 !!builder.builder && 419 typeof builder.handler === 'function'); 420} 421function isCommandAndAliases(cmd) { 422 return cmd.every(c => typeof c === 'string'); 423} 424export function isCommandBuilderCallback(builder) { 425 return typeof builder === 'function'; 426} 427function isCommandBuilderOptionDefinitions(builder) { 428 return typeof builder === 'object'; 429} 430export function isCommandHandlerDefinition(cmd) { 431 return typeof cmd === 'object' && !Array.isArray(cmd); 432} 433