clap-ts 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +718 -102
- package/dist/argfile.d.ts +55 -0
- package/dist/argfile.js +155 -0
- package/dist/completions.d.ts +4 -1
- package/dist/completions.js +199 -16
- package/dist/config.d.ts +75 -0
- package/dist/config.js +134 -0
- package/dist/help.d.ts +4 -4
- package/dist/help.js +276 -81
- package/dist/index.d.ts +2 -3
- package/dist/index.js +3 -3
- package/dist/install.d.ts +55 -0
- package/dist/install.js +185 -0
- package/dist/log.d.ts +78 -0
- package/dist/log.js +164 -0
- package/dist/man.d.ts +28 -0
- package/dist/man.js +234 -0
- package/dist/markdown.d.ts +17 -0
- package/dist/markdown.js +165 -0
- package/dist/output.d.ts +111 -0
- package/dist/output.js +356 -0
- package/dist/parser.d.ts +40 -19
- package/dist/parser.js +789 -461
- package/dist/plugins.d.ts +58 -0
- package/dist/plugins.js +145 -0
- package/dist/progress.d.ts +89 -0
- package/dist/progress.js +205 -0
- package/dist/prompt.d.ts +99 -0
- package/dist/prompt.js +299 -0
- package/dist/runner.d.ts +5 -2
- package/dist/runner.js +341 -135
- package/dist/spec.d.ts +83 -0
- package/dist/spec.js +124 -0
- package/dist/testing.d.ts +59 -0
- package/dist/testing.js +113 -0
- package/dist/types.d.ts +279 -11
- package/dist/validation.js +191 -69
- package/package.json +63 -6
package/dist/runner.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports inferSubcommands, subcommandRequired, allowExternalSubcommands,
|
|
5
5
|
* argsConflictsWithSubcommands, argRequiredElseHelp, and custom styles.
|
|
6
6
|
*/
|
|
7
|
-
import { CliParseError, collectGlobalArgs, getRawArgs, mergeGlobalArgs, parseArgs, } from './parser.js';
|
|
7
|
+
import { CliParseError, coerceValue, collectGlobalArgs, getRawArgs, hasSubCommands, kebabToCamel, mergeGlobalArgs, parseArgs, subCommandsOf, } from './parser.js';
|
|
8
8
|
import { validate } from './validation.js';
|
|
9
9
|
import { showError, showHelp, showVersion } from './help.js';
|
|
10
10
|
// ---- defineCommand ----
|
|
@@ -53,104 +53,199 @@ export function defineArgs(args) {
|
|
|
53
53
|
export function defineArg(arg) {
|
|
54
54
|
return arg;
|
|
55
55
|
}
|
|
56
|
-
// ----
|
|
56
|
+
// ---- Config Layer ----
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* The config section that applies to a command, built by walking the path from
|
|
59
|
+
* the root. A nested object named for a subcommand scopes its contents to that
|
|
60
|
+
* command; scalar keys stay in scope all the way down.
|
|
60
61
|
*/
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
function configForPath(config, path) {
|
|
63
|
+
let scope = config;
|
|
64
|
+
const merged = {};
|
|
65
|
+
const takeScalars = (from) => {
|
|
66
|
+
for (const key of Object.keys(from)) {
|
|
67
|
+
const value = from[key];
|
|
68
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
merged[key] = value;
|
|
66
72
|
}
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
};
|
|
74
|
+
takeScalars(scope);
|
|
75
|
+
for (const segment of path) {
|
|
76
|
+
const next = scope[segment];
|
|
77
|
+
if (next === null || typeof next !== 'object' || Array.isArray(next)) {
|
|
78
|
+
break;
|
|
69
79
|
}
|
|
80
|
+
scope = next;
|
|
81
|
+
takeScalars(scope);
|
|
70
82
|
}
|
|
71
|
-
|
|
72
|
-
return matches[0];
|
|
73
|
-
}
|
|
74
|
-
if (matches.length > 1) {
|
|
75
|
-
return 'ambiguous';
|
|
76
|
-
}
|
|
77
|
-
return undefined;
|
|
83
|
+
return merged;
|
|
78
84
|
}
|
|
79
85
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
86
|
+
* Fill args the command line and environment left alone from the config.
|
|
87
|
+
* A default already applied during parsing loses to a config value, which is
|
|
88
|
+
* what makes the precedence CLI > env > config > default.
|
|
82
89
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
90
|
+
function applyConfig(result, command, source, path) {
|
|
91
|
+
const argsDef = command.args ?? {};
|
|
92
|
+
// Nothing to fill means nothing to load: with a thunk, a command line that
|
|
93
|
+
// answered every argument never touches the filesystem.
|
|
94
|
+
let anyOpen = false;
|
|
95
|
+
for (const key of Object.keys(argsDef)) {
|
|
96
|
+
const from = result.valueSources.get(key);
|
|
97
|
+
if (from !== 'cli' && from !== 'env') {
|
|
98
|
+
anyOpen = true;
|
|
91
99
|
break;
|
|
92
100
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (prefixMatch === 'ambiguous') {
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
if (prefixMatch) {
|
|
117
|
-
parentNames.push(current.meta.name);
|
|
118
|
-
current = prefixMatch.def;
|
|
119
|
-
remaining.shift();
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
101
|
+
}
|
|
102
|
+
if (!anyOpen) {
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
const config = typeof source === 'function' ? source() : source;
|
|
106
|
+
if (config === undefined) {
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
const section = configForPath(config, path);
|
|
110
|
+
const args = { ...result.args };
|
|
111
|
+
const valueSources = new Map(result.valueSources);
|
|
112
|
+
let changed = false;
|
|
113
|
+
for (const key of Object.keys(argsDef)) {
|
|
114
|
+
const raw = section[key];
|
|
115
|
+
if (raw === undefined) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const source = valueSources.get(key);
|
|
119
|
+
if (source === 'cli' || source === 'env') {
|
|
120
|
+
continue;
|
|
123
121
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
const def = argsDef[key];
|
|
123
|
+
const name = def.type === 'positional' ? `<${def.valueName ?? key}>` : `--${def.long ?? key}`;
|
|
124
|
+
const coerce = (value) => coerceValue(String(value), def, `config:${name}`);
|
|
125
|
+
args[key] = Array.isArray(raw) ? raw.map((v) => String(coerce(v))) : coerce(raw);
|
|
126
|
+
args[kebabToCamel(key)] = args[key];
|
|
127
|
+
valueSources.set(key, 'config');
|
|
128
|
+
changed = true;
|
|
129
|
+
}
|
|
130
|
+
return changed ? { ...result, args, valueSources } : result;
|
|
131
|
+
}
|
|
132
|
+
/** Required args that argv, the environment and the config all left empty. */
|
|
133
|
+
function missingRequired(command, result) {
|
|
134
|
+
const argsDef = command.args ?? {};
|
|
135
|
+
const missing = [];
|
|
136
|
+
for (const key of Object.keys(argsDef)) {
|
|
137
|
+
const def = argsDef[key];
|
|
138
|
+
if (def.required !== true || result.args[key] !== undefined) {
|
|
139
|
+
continue;
|
|
128
140
|
}
|
|
129
|
-
//
|
|
141
|
+
// requiredUnlessPresent and friends may excuse it; leave those to validate.
|
|
142
|
+
if (def.requiredUnlessPresent !== undefined || def.requiredUnlessPresentAll !== undefined) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
missing.push({
|
|
146
|
+
key,
|
|
147
|
+
def,
|
|
148
|
+
label: def.type === 'positional' ? `<${def.valueName ?? key}>` : `--${def.long ?? key}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return missing;
|
|
152
|
+
}
|
|
153
|
+
/** Fold values supplied by a fillMissing hook into the result. */
|
|
154
|
+
function applyFilled(result, command, filled) {
|
|
155
|
+
const argsDef = command.args ?? {};
|
|
156
|
+
const args = { ...result.args };
|
|
157
|
+
const explicitlySet = new Set(result.explicitlySet);
|
|
158
|
+
const valueSources = new Map(result.valueSources);
|
|
159
|
+
for (const key of Object.keys(filled)) {
|
|
160
|
+
const def = argsDef[key];
|
|
161
|
+
const raw = filled[key];
|
|
162
|
+
if (def === undefined || raw === undefined) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const name = def.type === 'positional' ? `<${def.valueName ?? key}>` : `--${def.long ?? key}`;
|
|
166
|
+
const coerce = (value) => typeof value === 'boolean' ? value : coerceValue(String(value), def, `prompt:${name}`);
|
|
167
|
+
args[key] = Array.isArray(raw) ? raw.map((v) => String(coerce(v))) : coerce(raw);
|
|
168
|
+
args[kebabToCamel(key)] = args[key];
|
|
169
|
+
explicitlySet.add(key);
|
|
170
|
+
valueSources.set(key, 'prompt');
|
|
171
|
+
}
|
|
172
|
+
return { ...result, args, explicitlySet, valueSources };
|
|
173
|
+
}
|
|
174
|
+
// ---- Global Args ----
|
|
175
|
+
/**
|
|
176
|
+
* Overlay inherited global args onto a command. The command's own args win, and
|
|
177
|
+
* a command with nothing to inherit is returned untouched so the parser's spec
|
|
178
|
+
* cache still hits.
|
|
179
|
+
*/
|
|
180
|
+
function applyGlobals(command, globals, propagatedVersion) {
|
|
181
|
+
let hasGlobals = false;
|
|
182
|
+
for (const _key in globals) {
|
|
183
|
+
hasGlobals = true;
|
|
130
184
|
break;
|
|
131
185
|
}
|
|
132
|
-
|
|
186
|
+
const needsVersion = propagatedVersion !== undefined && command.meta.version === undefined;
|
|
187
|
+
if (!hasGlobals && !needsVersion) {
|
|
188
|
+
return command;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...command,
|
|
192
|
+
meta: needsVersion ? { ...command.meta, version: propagatedVersion } : command.meta,
|
|
193
|
+
args: hasGlobals ? mergeGlobalArgs(globals, command.args ?? {}) : command.args,
|
|
194
|
+
};
|
|
133
195
|
}
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Walk a chain of subcommand names from a command, for `app help sub subsub`.
|
|
198
|
+
* Returns the deepest command reached and the names of its ancestors.
|
|
199
|
+
*/
|
|
200
|
+
function resolveHelpTarget(root, rootParents, path) {
|
|
201
|
+
let current = root;
|
|
202
|
+
const parentNames = [...rootParents];
|
|
203
|
+
for (const name of path) {
|
|
204
|
+
const next = subCommandsOf(current)[name];
|
|
205
|
+
if (next === undefined) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
parentNames.push(current.meta.name);
|
|
209
|
+
current = next;
|
|
210
|
+
}
|
|
211
|
+
return { command: current, parentNames };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Overlay global values captured at ancestor levels onto the resolved command's
|
|
215
|
+
* result. A value given again at this level always wins.
|
|
216
|
+
*/
|
|
217
|
+
function applyInheritedGlobals(result, inherited) {
|
|
218
|
+
if (inherited.size === 0) {
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
const args = { ...result.args };
|
|
222
|
+
const explicitlySet = new Set(result.explicitlySet);
|
|
223
|
+
const valueSources = new Map(result.valueSources);
|
|
224
|
+
for (const [key, value] of inherited) {
|
|
225
|
+
if (explicitlySet.has(key)) {
|
|
226
|
+
continue;
|
|
139
227
|
}
|
|
228
|
+
args[kebabToCamel(key)] = value;
|
|
229
|
+
args[key] = value;
|
|
230
|
+
explicitlySet.add(key);
|
|
231
|
+
valueSources.set(key, 'cli');
|
|
140
232
|
}
|
|
141
|
-
return
|
|
233
|
+
return { ...result, args, explicitlySet, valueSources };
|
|
142
234
|
}
|
|
143
235
|
// ---- runCommand ----
|
|
144
236
|
/**
|
|
145
237
|
* Run a specific command with pre-parsed arguments.
|
|
146
238
|
* Executes the setup -> run -> cleanup lifecycle.
|
|
147
239
|
*/
|
|
148
|
-
export async function runCommand(command, args, rawArgs = [], subCommand) {
|
|
240
|
+
export async function runCommand(command, args, rawArgs = [], subCommand, valueSources = new Map(), io) {
|
|
149
241
|
const ctx = {
|
|
150
242
|
rawArgs,
|
|
151
243
|
args,
|
|
152
244
|
cmd: command,
|
|
153
245
|
subCommand,
|
|
246
|
+
valueSources,
|
|
247
|
+
stdout: io?.stdout ?? process.stdout,
|
|
248
|
+
stderr: io?.stderr ?? process.stderr,
|
|
154
249
|
data: {},
|
|
155
250
|
};
|
|
156
251
|
let runError;
|
|
@@ -227,32 +322,44 @@ function simpleCharDistance(a, b, maxDist) {
|
|
|
227
322
|
}
|
|
228
323
|
// ---- runMain helpers ----
|
|
229
324
|
/** Handle --help request with mode and style support. */
|
|
230
|
-
function handleHelpRequest(command, parentNames,
|
|
231
|
-
showHelp(command, parentNames.length > 0 ? parentNames : undefined, isShortHelp, styles);
|
|
232
|
-
|
|
233
|
-
process.exit(0);
|
|
234
|
-
}
|
|
325
|
+
function handleHelpRequest(command, parentNames, isShortHelp, io) {
|
|
326
|
+
showHelp(command, parentNames.length > 0 ? parentNames : undefined, isShortHelp, io.styles, io.stdout);
|
|
327
|
+
io.finish(0);
|
|
235
328
|
}
|
|
236
329
|
/** Handle --version request. */
|
|
237
|
-
function handleVersionRequest(effectiveCommand, rootCommand,
|
|
330
|
+
function handleVersionRequest(effectiveCommand, rootCommand, isShort, io) {
|
|
238
331
|
const versionMeta = effectiveCommand.meta.version ? effectiveCommand.meta : rootCommand.meta;
|
|
239
|
-
showVersion(versionMeta);
|
|
240
|
-
|
|
241
|
-
process.exit(0);
|
|
242
|
-
}
|
|
332
|
+
showVersion(versionMeta, isShort, io.stdout);
|
|
333
|
+
io.finish(0);
|
|
243
334
|
}
|
|
244
335
|
/** Handle an unrecognized subcommand with typo suggestion. */
|
|
245
|
-
function handleUnrecognizedSubcommand(unknownName, command, parentNames,
|
|
246
|
-
const allNames = collectSubcommandNames(command
|
|
336
|
+
function handleUnrecognizedSubcommand(unknownName, command, parentNames, io) {
|
|
337
|
+
const allNames = collectSubcommandNames(subCommandsOf(command));
|
|
247
338
|
let msg = `unrecognized subcommand '${unknownName}'`;
|
|
248
339
|
const bestMatch = findClosestSubcommand(unknownName, allNames);
|
|
249
340
|
if (bestMatch) {
|
|
250
341
|
msg += `\n\n tip: a similar subcommand exists: '${bestMatch}'`;
|
|
251
342
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
343
|
+
fail(msg, command, parentNames, io);
|
|
344
|
+
}
|
|
345
|
+
function makeRunIO(opts) {
|
|
346
|
+
const shouldExit = opts?.exit !== false;
|
|
347
|
+
return {
|
|
348
|
+
stdout: opts?.stdout ?? process.stdout,
|
|
349
|
+
stderr: opts?.stderr ?? process.stderr,
|
|
350
|
+
styles: opts?.styles,
|
|
351
|
+
finish(code) {
|
|
352
|
+
opts?.onExit?.(code);
|
|
353
|
+
if (shouldExit) {
|
|
354
|
+
process.exit(code);
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/** Print a usage error against the given command and settle on exit code 2. */
|
|
360
|
+
function fail(message, command, parentNames, io) {
|
|
361
|
+
showError(message, command, parentNames.length > 0 ? parentNames : undefined, io.styles, io.stderr);
|
|
362
|
+
io.finish(2);
|
|
256
363
|
}
|
|
257
364
|
// ---- runMain ----
|
|
258
365
|
/**
|
|
@@ -265,105 +372,204 @@ function handleUnrecognizedSubcommand(unknownName, command, parentNames, shouldE
|
|
|
265
372
|
* ```
|
|
266
373
|
*/
|
|
267
374
|
export async function runMain(rootCommand, opts) {
|
|
375
|
+
const io = makeRunIO(opts);
|
|
268
376
|
const shouldExit = opts?.exit !== false;
|
|
269
377
|
const showHelpOnEmpty = opts?.showHelpOnEmpty !== false;
|
|
270
|
-
|
|
378
|
+
// Tracked through the descent so an error names the command that failed,
|
|
379
|
+
// not the root.
|
|
380
|
+
let errorCommand = rootCommand;
|
|
381
|
+
let errorParents = [];
|
|
271
382
|
try {
|
|
272
|
-
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
383
|
+
let rawArgs = getRawArgs(opts?.argv, rootCommand.meta.noBinaryName === true);
|
|
384
|
+
// multicall: the name the binary was invoked under selects the subcommand.
|
|
385
|
+
if (rootCommand.meta.multicall && opts?.argv === undefined) {
|
|
386
|
+
const invoked = process.argv[1];
|
|
387
|
+
if (invoked !== undefined) {
|
|
388
|
+
const base = invoked.slice(invoked.lastIndexOf('/') + 1).replace(/\.[cm]?[jt]s$/, '');
|
|
389
|
+
rawArgs = [base, ...rawArgs];
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let command = rootCommand;
|
|
393
|
+
let effectiveCommand = rootCommand;
|
|
394
|
+
let inheritedGlobals = {};
|
|
395
|
+
// Values of global args given at an ancestor level. clap carries these down
|
|
396
|
+
// to the command that finally runs, so `app --verbose serve` reaches serve.
|
|
397
|
+
const inheritedValues = new Map();
|
|
398
|
+
let propagatedVersion;
|
|
399
|
+
let argv = rawArgs;
|
|
400
|
+
const parentNames = [];
|
|
401
|
+
let parseResult = parseArgs([], rootCommand);
|
|
402
|
+
let externalSubcommand;
|
|
403
|
+
// Descend the subcommand chain one level at a time, parsing as we go: only
|
|
404
|
+
// the arg spec can say whether a bare token is a subcommand or the value of
|
|
405
|
+
// a preceding flag.
|
|
406
|
+
for (;;) {
|
|
407
|
+
inheritedGlobals = mergeGlobalArgs(inheritedGlobals, collectGlobalArgs(command));
|
|
408
|
+
if (command.meta.propagateVersion && command.meta.version !== undefined) {
|
|
409
|
+
propagatedVersion = command.meta.version;
|
|
410
|
+
}
|
|
411
|
+
effectiveCommand = applyGlobals(command, inheritedGlobals, propagatedVersion);
|
|
412
|
+
errorCommand = effectiveCommand;
|
|
413
|
+
errorParents = [...parentNames];
|
|
414
|
+
parseResult = parseArgs(argv, effectiveCommand);
|
|
415
|
+
for (const warning of parseResult.warnings) {
|
|
416
|
+
io.stderr.write(`warning: ${warning}\n`);
|
|
417
|
+
}
|
|
418
|
+
if (command.meta.deprecated !== undefined && command.meta.deprecated !== false) {
|
|
419
|
+
const reason = typeof command.meta.deprecated === 'string' ? `: ${command.meta.deprecated}` : '';
|
|
420
|
+
const instead = command.meta.replacedBy === undefined ? '' : `; use '${command.meta.replacedBy}' instead`;
|
|
421
|
+
io.stderr.write(`warning: '${command.meta.name}' is deprecated${reason}${instead}\n`);
|
|
279
422
|
}
|
|
423
|
+
// The built-in `help` subcommand: `app help`, `app help sub sub`.
|
|
424
|
+
if (parseResult.subCommand === undefined &&
|
|
425
|
+
hasSubCommands(command) &&
|
|
426
|
+
command.meta.disableHelpSubcommand !== true &&
|
|
427
|
+
parseResult.positionals[0] === 'help') {
|
|
428
|
+
const target = resolveHelpTarget(effectiveCommand, parentNames, parseResult.positionals.slice(1));
|
|
429
|
+
handleHelpRequest(target.command, target.parentNames, false, io);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
for (const key of Object.keys(inheritedGlobals)) {
|
|
433
|
+
if (parseResult.explicitlySet.has(key)) {
|
|
434
|
+
const value = parseResult.args[key];
|
|
435
|
+
if (value !== undefined) {
|
|
436
|
+
inheritedValues.set(key, value);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (parseResult.subCommand === undefined ||
|
|
441
|
+
parseResult.helpRequested ||
|
|
442
|
+
parseResult.versionRequested) {
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
if (parseResult.subCommandIsExternal) {
|
|
446
|
+
externalSubcommand = parseResult.subCommand;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
if (command.meta.argsConflictsWithSubcommands && parseResult.explicitlySet.size > 0) {
|
|
450
|
+
fail('arguments cannot be used with subcommands', effectiveCommand, parentNames, io);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const next = subCommandsOf(command)[parseResult.subCommand];
|
|
454
|
+
if (next === undefined) {
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
// A parent's own constraints still apply to the flags given before the
|
|
458
|
+
// subcommand, unless it opted out with subcommandNegatesReqs.
|
|
459
|
+
if (!command.meta.subcommandNegatesReqs) {
|
|
460
|
+
validate(parseResult, effectiveCommand);
|
|
461
|
+
}
|
|
462
|
+
parentNames.push(command.meta.name);
|
|
463
|
+
command = next;
|
|
464
|
+
argv = parseResult.subCommandArgs;
|
|
465
|
+
}
|
|
466
|
+
// External subcommand: hand the name and the untouched remainder to the
|
|
467
|
+
// command that declared allowExternalSubcommands.
|
|
468
|
+
if (externalSubcommand !== undefined) {
|
|
469
|
+
if (effectiveCommand.run) {
|
|
470
|
+
const parser = effectiveCommand.externalSubcommandValueParser;
|
|
471
|
+
const externalArgs = parser === undefined
|
|
472
|
+
? parseResult.subCommandArgs
|
|
473
|
+
: parseResult.subCommandArgs.map((value) => {
|
|
474
|
+
try {
|
|
475
|
+
return String(parser(value));
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
479
|
+
throw new CliParseError(`invalid value '${value}': ${message}`);
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
await runCommand(effectiveCommand, parseResult.args, externalArgs, externalSubcommand, parseResult.valueSources, io);
|
|
483
|
+
}
|
|
484
|
+
io.finish(0);
|
|
280
485
|
return;
|
|
281
486
|
}
|
|
282
|
-
// Merge global args from parent into resolved command
|
|
283
|
-
const globalArgs = collectGlobalArgs(rootCommand);
|
|
284
|
-
const effectiveCommand = {
|
|
285
|
-
...command,
|
|
286
|
-
args: command.args ? mergeGlobalArgs(globalArgs, command.args) : globalArgs,
|
|
287
|
-
};
|
|
288
|
-
// Parse remaining args against the resolved command
|
|
289
|
-
const parseResult = parseArgs(remainingArgs, effectiveCommand);
|
|
290
487
|
// Handle --help
|
|
291
488
|
if (parseResult.helpRequested) {
|
|
292
|
-
handleHelpRequest(effectiveCommand, parentNames,
|
|
489
|
+
handleHelpRequest(effectiveCommand, parentNames, parseResult.helpIsShort, io);
|
|
293
490
|
return;
|
|
294
491
|
}
|
|
295
492
|
// Handle --version
|
|
296
493
|
if (parseResult.versionRequested) {
|
|
297
|
-
handleVersionRequest(effectiveCommand, rootCommand,
|
|
494
|
+
handleVersionRequest(effectiveCommand, rootCommand, parseResult.versionIsShort, io);
|
|
298
495
|
return;
|
|
299
496
|
}
|
|
300
497
|
// Show help if no args and command has subcommands
|
|
301
498
|
if (showHelpOnEmpty &&
|
|
302
499
|
rawArgs.length === 0 &&
|
|
303
|
-
rootCommand
|
|
304
|
-
|
|
305
|
-
handleHelpRequest(rootCommand, [], shouldExit, false, styles);
|
|
500
|
+
hasSubCommands(rootCommand)) {
|
|
501
|
+
handleHelpRequest(rootCommand, [], false, io);
|
|
306
502
|
return;
|
|
307
503
|
}
|
|
308
504
|
// argRequiredElseHelp: show help if no args were explicitly provided
|
|
309
505
|
if (command.meta.argRequiredElseHelp && parseResult.explicitlySet.size === 0) {
|
|
310
|
-
handleHelpRequest(
|
|
506
|
+
handleHelpRequest(effectiveCommand, parentNames, false, io);
|
|
311
507
|
return;
|
|
312
508
|
}
|
|
313
509
|
// If we resolved to a parent command that has subcommands but no run handler,
|
|
314
510
|
// and the user didn't pass a valid subcommand, show help or error
|
|
315
|
-
if (command
|
|
316
|
-
Object.keys(command.subCommands).length > 0 &&
|
|
511
|
+
if (hasSubCommands(command) &&
|
|
317
512
|
!command.run &&
|
|
318
513
|
!parseResult.subCommand) {
|
|
319
514
|
// subcommandRequired: error if no subcommand
|
|
320
515
|
if (command.meta.subcommandRequired) {
|
|
321
516
|
if (parseResult.positionals.length > 0) {
|
|
322
|
-
handleUnrecognizedSubcommand(parseResult.positionals[0],
|
|
517
|
+
handleUnrecognizedSubcommand(parseResult.positionals[0], effectiveCommand, parentNames, io);
|
|
323
518
|
}
|
|
324
519
|
else {
|
|
325
|
-
|
|
326
|
-
if (shouldExit) {
|
|
327
|
-
process.exit(2);
|
|
328
|
-
}
|
|
520
|
+
fail('a subcommand is required but one was not provided', effectiveCommand, parentNames, io);
|
|
329
521
|
}
|
|
330
522
|
return;
|
|
331
523
|
}
|
|
332
524
|
if (parseResult.positionals.length > 0) {
|
|
333
|
-
handleUnrecognizedSubcommand(parseResult.positionals[0],
|
|
525
|
+
handleUnrecognizedSubcommand(parseResult.positionals[0], effectiveCommand, parentNames, io);
|
|
334
526
|
return;
|
|
335
527
|
}
|
|
336
|
-
handleHelpRequest(
|
|
528
|
+
handleHelpRequest(effectiveCommand, parentNames, false, io);
|
|
337
529
|
return;
|
|
338
530
|
}
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
531
|
+
// Fold ancestor-provided global values into the command that runs, then
|
|
532
|
+
// let the config fill whatever is still on its default.
|
|
533
|
+
let finalResult = applyInheritedGlobals(parseResult, inheritedValues);
|
|
534
|
+
if (opts?.config !== undefined) {
|
|
535
|
+
finalResult = applyConfig(finalResult, effectiveCommand, opts.config, [
|
|
536
|
+
...parentNames.slice(1),
|
|
537
|
+
...(parentNames.length > 0 ? [command.meta.name] : []),
|
|
538
|
+
]);
|
|
539
|
+
}
|
|
540
|
+
// Last chance to supply what is still missing, before validation says no.
|
|
541
|
+
if (opts?.fillMissing !== undefined) {
|
|
542
|
+
const missing = missingRequired(effectiveCommand, finalResult);
|
|
543
|
+
if (missing.length > 0) {
|
|
544
|
+
const filled = await opts.fillMissing(missing, effectiveCommand);
|
|
545
|
+
if (filled !== undefined) {
|
|
546
|
+
finalResult = applyFilled(finalResult, effectiveCommand, filled);
|
|
547
|
+
}
|
|
346
548
|
}
|
|
347
|
-
return;
|
|
348
549
|
}
|
|
349
550
|
// Validate parsed args
|
|
350
|
-
validate(
|
|
551
|
+
validate(finalResult, effectiveCommand);
|
|
351
552
|
// Run the command
|
|
352
|
-
await runCommand(effectiveCommand,
|
|
553
|
+
await runCommand(effectiveCommand, finalResult.args, rawArgs, undefined, finalResult.valueSources, io);
|
|
554
|
+
// If the command's run() didn't call process.exit() itself, exit cleanly.
|
|
555
|
+
// This prevents the process from hanging when the caller uses `void runMain()`
|
|
556
|
+
// instead of `await runMain()` — the unawaited Promise would otherwise keep
|
|
557
|
+
// the event loop alive due to pending NAPI handles or other resources.
|
|
558
|
+
io.finish(0);
|
|
353
559
|
}
|
|
354
560
|
catch (error) {
|
|
355
561
|
if (error instanceof CliParseError) {
|
|
356
|
-
|
|
357
|
-
if (shouldExit) {
|
|
358
|
-
process.exit(2);
|
|
359
|
-
}
|
|
562
|
+
fail(error.message, errorCommand, errorParents, io);
|
|
360
563
|
return;
|
|
361
564
|
}
|
|
362
565
|
// Unexpected error
|
|
363
|
-
if (shouldExit) {
|
|
566
|
+
if (shouldExit || opts?.onExit !== undefined) {
|
|
364
567
|
const message = error instanceof Error ? error.message : String(error);
|
|
365
|
-
|
|
366
|
-
|
|
568
|
+
io.stderr.write(`error: ${message}\n`);
|
|
569
|
+
io.finish(1);
|
|
570
|
+
if (!shouldExit) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
367
573
|
}
|
|
368
574
|
throw error;
|
|
369
575
|
}
|