1 | import { assertNotStrictEqual, } from './typings/common-types.js';
|
---|
2 | import { isPromise } from './utils/is-promise.js';
|
---|
3 | import { applyMiddleware, commandMiddlewareFactory, } from './middleware.js';
|
---|
4 | import { parseCommand } from './parse-command.js';
|
---|
5 | import { isYargsInstance, } from './yargs-factory.js';
|
---|
6 | import { maybeAsyncResult } from './utils/maybe-async-result.js';
|
---|
7 | import whichModule from './utils/which-module.js';
|
---|
8 | const DEFAULT_MARKER = /(^\*)|(^\$0)/;
|
---|
9 | export 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 | }
|
---|
413 | export function command(usage, validation, globalMiddleware, shim) {
|
---|
414 | return new CommandInstance(usage, validation, globalMiddleware, shim);
|
---|
415 | }
|
---|
416 | export function isCommandBuilderDefinition(builder) {
|
---|
417 | return (typeof builder === 'object' &&
|
---|
418 | !!builder.builder &&
|
---|
419 | typeof builder.handler === 'function');
|
---|
420 | }
|
---|
421 | function isCommandAndAliases(cmd) {
|
---|
422 | return cmd.every(c => typeof c === 'string');
|
---|
423 | }
|
---|
424 | export function isCommandBuilderCallback(builder) {
|
---|
425 | return typeof builder === 'function';
|
---|
426 | }
|
---|
427 | function isCommandBuilderOptionDefinitions(builder) {
|
---|
428 | return typeof builder === 'object';
|
---|
429 | }
|
---|
430 | export function isCommandHandlerDefinition(cmd) {
|
---|
431 | return typeof cmd === 'object' && !Array.isArray(cmd);
|
---|
432 | }
|
---|