@guanghechen/commander 4.7.2 → 4.7.3
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/CHANGELOG.md +7 -0
- package/lib/cjs/browser.cjs +125 -17
- package/lib/cjs/index.cjs +176 -1028
- package/lib/cjs/node.cjs +177 -39
- package/lib/esm/browser.mjs +125 -17
- package/lib/esm/index.mjs +175 -1021
- package/lib/esm/node.mjs +177 -39
- package/lib/types/browser.d.ts +2 -1
- package/lib/types/index.d.ts +15 -191
- package/lib/types/node.d.ts +2 -1
- package/package.json +1 -1
package/lib/esm/index.mjs
CHANGED
|
@@ -1,85 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Reporter } from '@guanghechen/reporter';
|
|
3
|
-
import { stat, readFile } from 'node:fs/promises';
|
|
4
|
-
import * as path from 'node:path';
|
|
5
|
-
import path__default from 'node:path';
|
|
1
|
+
import { Reporter, LOG_LEVELS, resolveLogLevel } from '@guanghechen/reporter';
|
|
6
2
|
import * as fs from 'node:fs';
|
|
7
|
-
|
|
8
|
-
const TERMINAL_STYLE = {
|
|
9
|
-
bold: '\x1b[1m',
|
|
10
|
-
italic: '\x1b[3m',
|
|
11
|
-
underline: '\x1b[4m',
|
|
12
|
-
cyan: '\x1b[36m',
|
|
13
|
-
dim: '\x1b[2m',
|
|
14
|
-
reset: '\x1b[0m',
|
|
15
|
-
};
|
|
16
|
-
function styleText(text, ...styles) {
|
|
17
|
-
return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const BUILTIN_LOG_LEVELS = ['debug', 'info', 'hint', 'warn', 'error'];
|
|
21
|
-
function resolveReporterLogLevel(raw) {
|
|
22
|
-
const normalized = raw.trim().toLowerCase();
|
|
23
|
-
return BUILTIN_LOG_LEVELS.find(level => level === normalized);
|
|
24
|
-
}
|
|
25
|
-
function setReporterLevel(ctx, level) {
|
|
26
|
-
const reporter = ctx.reporter;
|
|
27
|
-
reporter?.setLevel?.(level);
|
|
28
|
-
}
|
|
29
|
-
function setReporterFlight(ctx, flight) {
|
|
30
|
-
const reporter = ctx.reporter;
|
|
31
|
-
reporter?.setFlight?.(flight);
|
|
32
|
-
}
|
|
33
|
-
const logLevelOption = {
|
|
34
|
-
long: 'logLevel',
|
|
35
|
-
type: 'string',
|
|
36
|
-
args: 'required',
|
|
37
|
-
desc: 'Set log level',
|
|
38
|
-
default: 'info',
|
|
39
|
-
choices: [...BUILTIN_LOG_LEVELS],
|
|
40
|
-
coerce: (raw) => {
|
|
41
|
-
const level = resolveReporterLogLevel(raw);
|
|
42
|
-
if (level === undefined) {
|
|
43
|
-
throw new Error(`Invalid log level: ${raw}`);
|
|
44
|
-
}
|
|
45
|
-
return level;
|
|
46
|
-
},
|
|
47
|
-
apply: (value, ctx) => {
|
|
48
|
-
setReporterLevel(ctx, value);
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
const logDateOption = {
|
|
52
|
-
long: 'logDate',
|
|
53
|
-
type: 'boolean',
|
|
54
|
-
args: 'none',
|
|
55
|
-
desc: 'Enable log timestamp',
|
|
56
|
-
default: true,
|
|
57
|
-
apply: (value, ctx) => {
|
|
58
|
-
setReporterFlight(ctx, { date: Boolean(value) });
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
const logColorfulOption = {
|
|
62
|
-
long: 'logColorful',
|
|
63
|
-
type: 'boolean',
|
|
64
|
-
args: 'none',
|
|
65
|
-
desc: 'Enable colorful log output',
|
|
66
|
-
default: true,
|
|
67
|
-
apply: (value, ctx) => {
|
|
68
|
-
setReporterFlight(ctx, { color: Boolean(value) });
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
const silentOption = {
|
|
72
|
-
long: 'silent',
|
|
73
|
-
type: 'boolean',
|
|
74
|
-
args: 'none',
|
|
75
|
-
desc: 'Suppress non-error output',
|
|
76
|
-
default: false,
|
|
77
|
-
apply: (value, ctx) => {
|
|
78
|
-
if (value) {
|
|
79
|
-
setReporterLevel(ctx, 'error');
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
};
|
|
3
|
+
import * as path from 'node:path';
|
|
83
4
|
|
|
84
5
|
class CommanderError extends Error {
|
|
85
6
|
kind;
|
|
@@ -97,11 +18,6 @@ class CommanderError extends Error {
|
|
|
97
18
|
|
|
98
19
|
const LONG_OPTION_REGEX = /^--[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
99
20
|
const NEGATIVE_OPTION_REGEX = /^--no-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
100
|
-
const PRESET_OPTS_FLAG = '--preset-opts';
|
|
101
|
-
const PRESET_ENVS_FLAG = '--preset-envs';
|
|
102
|
-
const PRESET_ROOT_FLAG = '--preset-root';
|
|
103
|
-
const DEFAULT_PRESET_OPTS_FILENAME = '.opt.local';
|
|
104
|
-
const DEFAULT_PRESET_ENVS_FILENAME = '.env.local';
|
|
105
21
|
function kebabToCamelCase(str) {
|
|
106
22
|
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
107
23
|
}
|
|
@@ -189,91 +105,27 @@ function tokenize(argv, commandPath) {
|
|
|
189
105
|
}
|
|
190
106
|
const BUILTIN_HELP_OPTION = {
|
|
191
107
|
long: 'help',
|
|
108
|
+
short: 'h',
|
|
192
109
|
type: 'boolean',
|
|
193
110
|
args: 'none',
|
|
194
111
|
desc: 'Show help information',
|
|
195
112
|
};
|
|
196
113
|
const BUILTIN_VERSION_OPTION = {
|
|
197
114
|
long: 'version',
|
|
115
|
+
short: 'V',
|
|
198
116
|
type: 'boolean',
|
|
199
117
|
args: 'none',
|
|
200
118
|
desc: 'Show version number',
|
|
201
119
|
};
|
|
202
|
-
const BUILTIN_COLOR_OPTION = {
|
|
203
|
-
long: 'color',
|
|
204
|
-
type: 'boolean',
|
|
205
|
-
args: 'none',
|
|
206
|
-
desc: 'Enable colored help output',
|
|
207
|
-
default: true,
|
|
208
|
-
};
|
|
209
|
-
function createBuiltinOptionState(enabled) {
|
|
210
|
-
return {
|
|
211
|
-
version: enabled,
|
|
212
|
-
color: enabled,
|
|
213
|
-
logLevel: enabled,
|
|
214
|
-
silent: enabled,
|
|
215
|
-
logDate: enabled,
|
|
216
|
-
logColorful: enabled,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function isNoColorEnabled(envs) {
|
|
220
|
-
return envs['NO_COLOR'] !== undefined;
|
|
221
|
-
}
|
|
222
|
-
function normalizeBuiltinConfig(builtin) {
|
|
223
|
-
const resolved = {
|
|
224
|
-
option: createBuiltinOptionState(true),
|
|
225
|
-
};
|
|
226
|
-
if (builtin === undefined) {
|
|
227
|
-
return resolved;
|
|
228
|
-
}
|
|
229
|
-
if (builtin === true) {
|
|
230
|
-
return {
|
|
231
|
-
option: createBuiltinOptionState(true),
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
if (builtin === false) {
|
|
235
|
-
return {
|
|
236
|
-
option: createBuiltinOptionState(false),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
if (builtin.option !== undefined) {
|
|
240
|
-
if (builtin.option === false) {
|
|
241
|
-
resolved.option = createBuiltinOptionState(false);
|
|
242
|
-
}
|
|
243
|
-
else if (builtin.option === true) {
|
|
244
|
-
resolved.option = createBuiltinOptionState(true);
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
if (builtin.option.version !== undefined)
|
|
248
|
-
resolved.option.version = builtin.option.version;
|
|
249
|
-
if (builtin.option.color !== undefined)
|
|
250
|
-
resolved.option.color = builtin.option.color;
|
|
251
|
-
if (builtin.option.logLevel !== undefined) {
|
|
252
|
-
resolved.option.logLevel = builtin.option.logLevel;
|
|
253
|
-
}
|
|
254
|
-
if (builtin.option.silent !== undefined)
|
|
255
|
-
resolved.option.silent = builtin.option.silent;
|
|
256
|
-
if (builtin.option.logDate !== undefined)
|
|
257
|
-
resolved.option.logDate = builtin.option.logDate;
|
|
258
|
-
if (builtin.option.logColorful !== undefined) {
|
|
259
|
-
resolved.option.logColorful = builtin.option.logColorful;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return resolved;
|
|
264
|
-
}
|
|
265
120
|
class Command {
|
|
266
121
|
#name;
|
|
267
122
|
#desc;
|
|
268
123
|
#version;
|
|
269
|
-
#
|
|
270
|
-
#builtin;
|
|
271
|
-
#presetConfig;
|
|
124
|
+
#helpSubcommandEnabled;
|
|
272
125
|
#reporter;
|
|
273
126
|
#parent;
|
|
274
127
|
#options = [];
|
|
275
128
|
#arguments = [];
|
|
276
|
-
#examples = [];
|
|
277
129
|
#subcommandsList = [];
|
|
278
130
|
#subcommandsMap = new Map();
|
|
279
131
|
#action = undefined;
|
|
@@ -281,9 +133,7 @@ class Command {
|
|
|
281
133
|
this.#name = config.name ?? '';
|
|
282
134
|
this.#desc = config.desc;
|
|
283
135
|
this.#version = config.version;
|
|
284
|
-
this.#
|
|
285
|
-
this.#builtin = normalizeBuiltinConfig(config.builtin);
|
|
286
|
-
this.#presetConfig = config.preset;
|
|
136
|
+
this.#helpSubcommandEnabled = config.help ?? false;
|
|
287
137
|
this.#reporter = config.reporter;
|
|
288
138
|
}
|
|
289
139
|
get name() {
|
|
@@ -295,12 +145,6 @@ class Command {
|
|
|
295
145
|
get version() {
|
|
296
146
|
return this.#version;
|
|
297
147
|
}
|
|
298
|
-
get builtin() {
|
|
299
|
-
return this.#builtinConfig;
|
|
300
|
-
}
|
|
301
|
-
get preset() {
|
|
302
|
-
return this.#presetConfig === undefined ? undefined : { ...this.#presetConfig };
|
|
303
|
-
}
|
|
304
148
|
get parent() {
|
|
305
149
|
return this.#parent;
|
|
306
150
|
}
|
|
@@ -310,9 +154,6 @@ class Command {
|
|
|
310
154
|
get arguments() {
|
|
311
155
|
return [...this.#arguments];
|
|
312
156
|
}
|
|
313
|
-
get examples() {
|
|
314
|
-
return this.#examples.map(example => ({ ...example }));
|
|
315
|
-
}
|
|
316
157
|
get subcommands() {
|
|
317
158
|
return new Map(this.#subcommandsMap);
|
|
318
159
|
}
|
|
@@ -331,22 +172,15 @@ class Command {
|
|
|
331
172
|
this.#action = fn;
|
|
332
173
|
return this;
|
|
333
174
|
}
|
|
334
|
-
example(title, usage, desc) {
|
|
335
|
-
this.#examples.push(this.#normalizeExample({ title, usage, desc }));
|
|
336
|
-
return this;
|
|
337
|
-
}
|
|
338
175
|
subcommand(name, cmd) {
|
|
339
|
-
if (name === 'help') {
|
|
340
|
-
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name', this.#getCommandPath());
|
|
176
|
+
if (this.#helpSubcommandEnabled && name === 'help') {
|
|
177
|
+
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
|
|
341
178
|
}
|
|
342
179
|
if (cmd.#parent && cmd.#parent !== this) {
|
|
343
180
|
throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
|
|
344
181
|
}
|
|
345
182
|
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
346
183
|
if (existing) {
|
|
347
|
-
if (existing.aliases.includes(name)) {
|
|
348
|
-
return this;
|
|
349
|
-
}
|
|
350
184
|
existing.aliases.push(name);
|
|
351
185
|
this.#subcommandsMap.set(name, cmd);
|
|
352
186
|
}
|
|
@@ -361,36 +195,33 @@ class Command {
|
|
|
361
195
|
async run(params) {
|
|
362
196
|
const { argv, envs, reporter } = params;
|
|
363
197
|
try {
|
|
364
|
-
const
|
|
365
|
-
const
|
|
198
|
+
const processedArgv = this.#processHelpSubcommand(argv);
|
|
199
|
+
const routeResult = this.#route(processedArgv);
|
|
200
|
+
const { chain, remaining } = routeResult;
|
|
366
201
|
const leafCommand = chain[chain.length - 1];
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
ctx.controls = controlScanResult.controls;
|
|
375
|
-
ctx.sources.user.argv = [...controlScanResult.remaining];
|
|
376
|
-
if (ctx.controls.help) {
|
|
377
|
-
const helpCommand = this.#resolveHelpCommand(leafCommand, controlScanResult.helpTarget);
|
|
378
|
-
const helpColor = helpCommand.#resolveHelpColorFromTailArgv(controlScanResult.remaining, ctx.envs);
|
|
379
|
-
console.log(helpCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
202
|
+
const rootCommand = chain[0];
|
|
203
|
+
const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
|
|
204
|
+
const { optionTokens, restArgs } = tokenizeResult;
|
|
205
|
+
const hasUserHelp = leafCommand.#options.some(o => o.long === 'help');
|
|
206
|
+
const hasUserVersion = leafCommand.#options.some(o => o.long === 'version');
|
|
207
|
+
if (!hasUserHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
|
|
208
|
+
console.log(leafCommand.formatHelp());
|
|
380
209
|
return;
|
|
381
210
|
}
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
|
|
211
|
+
if (!hasUserVersion && leafCommand === rootCommand && leafCommand.#version) {
|
|
212
|
+
if (this.#hasFlag(optionTokens, 'version', 'V')) {
|
|
213
|
+
console.log(leafCommand.#version);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
385
216
|
}
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const parseResult = this.#parse(chain, resolveResult,
|
|
217
|
+
const resolveResult = this.#resolve(chain, optionTokens);
|
|
218
|
+
const ctx = {
|
|
219
|
+
cmd: leafCommand,
|
|
220
|
+
envs,
|
|
221
|
+
reporter: reporter ?? this.#reporter ?? new Reporter(),
|
|
222
|
+
argv,
|
|
223
|
+
};
|
|
224
|
+
const parseResult = this.#parse(chain, resolveResult, ctx, restArgs);
|
|
394
225
|
const actionParams = {
|
|
395
226
|
ctx: parseResult.ctx,
|
|
396
227
|
opts: parseResult.opts,
|
|
@@ -401,8 +232,7 @@ class Command {
|
|
|
401
232
|
await leafCommand.#runAction(actionParams);
|
|
402
233
|
}
|
|
403
234
|
else if (leafCommand.#subcommandsList.length > 0) {
|
|
404
|
-
|
|
405
|
-
console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
235
|
+
console.log(leafCommand.formatHelp());
|
|
406
236
|
}
|
|
407
237
|
else {
|
|
408
238
|
throw new CommanderError('ConfigurationError', `no action defined for command "${leafCommand.#getCommandPath()}"`, leafCommand.#getCommandPath());
|
|
@@ -417,49 +247,28 @@ class Command {
|
|
|
417
247
|
throw err;
|
|
418
248
|
}
|
|
419
249
|
}
|
|
420
|
-
|
|
250
|
+
parse(params) {
|
|
421
251
|
const { argv, envs, reporter } = params;
|
|
422
|
-
const
|
|
423
|
-
const
|
|
252
|
+
const processedArgv = this.#processHelpSubcommand(argv);
|
|
253
|
+
const routeResult = this.#route(processedArgv);
|
|
254
|
+
const { chain, remaining } = routeResult;
|
|
424
255
|
const leafCommand = chain[chain.length - 1];
|
|
425
|
-
const
|
|
426
|
-
chain,
|
|
427
|
-
cmds: routeResult.cmds,
|
|
428
|
-
envs,
|
|
429
|
-
reporter,
|
|
430
|
-
});
|
|
431
|
-
const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
|
|
432
|
-
ctx.controls = controlScanResult.controls;
|
|
433
|
-
ctx.sources.user.argv = [...controlScanResult.remaining];
|
|
434
|
-
const optionPolicyMap = this.#buildOptionPolicyMap(chain);
|
|
435
|
-
const presetResult = await this.#preset(controlScanResult.remaining, ctx, optionPolicyMap);
|
|
436
|
-
ctx.sources = presetResult.sources;
|
|
437
|
-
ctx.envs = presetResult.envs;
|
|
438
|
-
const tokenizeResult = tokenize(presetResult.tailArgv, leafCommand.#getCommandPath());
|
|
256
|
+
const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
|
|
439
257
|
const { optionTokens, restArgs } = tokenizeResult;
|
|
440
|
-
const resolveResult = this.#resolve(chain, optionTokens
|
|
441
|
-
|
|
258
|
+
const resolveResult = this.#resolve(chain, optionTokens);
|
|
259
|
+
const ctx = {
|
|
260
|
+
cmd: leafCommand,
|
|
261
|
+
envs,
|
|
262
|
+
reporter: reporter ?? this.#reporter ?? new Reporter(),
|
|
263
|
+
argv,
|
|
264
|
+
};
|
|
265
|
+
return this.#parse(chain, resolveResult, ctx, restArgs);
|
|
442
266
|
}
|
|
443
267
|
formatHelp() {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const helpData = this.#buildHelpData();
|
|
449
|
-
if (!this.#shouldRenderStyledHelp(color)) {
|
|
450
|
-
return this.#renderHelpPlain(helpData);
|
|
451
|
-
}
|
|
452
|
-
return this.#renderHelpTerminal(helpData);
|
|
453
|
-
}
|
|
454
|
-
#shouldRenderStyledHelp(color) {
|
|
455
|
-
return color && process.stdout.isTTY === true;
|
|
456
|
-
}
|
|
457
|
-
#buildHelpData() {
|
|
458
|
-
const parseOptions = this.#resolveOptionPolicy().mergedOptions;
|
|
459
|
-
const allOptions = [...parseOptions, BUILTIN_HELP_OPTION];
|
|
460
|
-
if (this.#supportsBuiltinVersion()) {
|
|
461
|
-
allOptions.push(BUILTIN_VERSION_OPTION);
|
|
462
|
-
}
|
|
268
|
+
const lines = [];
|
|
269
|
+
const allOptions = this.#getMergedOptions();
|
|
270
|
+
lines.push(this.#desc);
|
|
271
|
+
lines.push('');
|
|
463
272
|
const commandPath = this.#getCommandPath();
|
|
464
273
|
let usage = `Usage: ${commandPath}`;
|
|
465
274
|
if (allOptions.length > 0)
|
|
@@ -477,128 +286,65 @@ class Command {
|
|
|
477
286
|
usage += ` [${arg.name}...]`;
|
|
478
287
|
}
|
|
479
288
|
}
|
|
480
|
-
|
|
481
|
-
for (const opt of allOptions) {
|
|
482
|
-
const kebabLong = camelToKebabCase$1(opt.long);
|
|
483
|
-
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
484
|
-
sig += `--${kebabLong}`;
|
|
485
|
-
if (opt.args !== 'none') {
|
|
486
|
-
sig += ' <value>';
|
|
487
|
-
}
|
|
488
|
-
let desc = opt.desc;
|
|
489
|
-
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
490
|
-
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
491
|
-
}
|
|
492
|
-
if (opt.choices) {
|
|
493
|
-
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
494
|
-
}
|
|
495
|
-
options.push({ sig, desc });
|
|
496
|
-
if (opt.type === 'boolean' &&
|
|
497
|
-
opt.args === 'none' &&
|
|
498
|
-
opt.long !== 'help' &&
|
|
499
|
-
opt.long !== 'version') {
|
|
500
|
-
options.push({
|
|
501
|
-
sig: ` --no-${kebabLong}`,
|
|
502
|
-
desc: `Negate --${kebabLong}`,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
const commands = [];
|
|
507
|
-
if (this.#subcommandsList.length > 0) {
|
|
508
|
-
commands.push({ name: 'help', desc: 'Show help for a command' });
|
|
509
|
-
}
|
|
510
|
-
for (const entry of this.#subcommandsList) {
|
|
511
|
-
let name = entry.name;
|
|
512
|
-
if (entry.aliases.length > 0) {
|
|
513
|
-
name += `, ${entry.aliases.join(', ')}`;
|
|
514
|
-
}
|
|
515
|
-
commands.push({ name, desc: entry.command.#desc });
|
|
516
|
-
}
|
|
517
|
-
const examples = this.#examples.map(example => ({
|
|
518
|
-
title: example.title,
|
|
519
|
-
usage: commandPath ? `${commandPath} ${example.usage}` : example.usage,
|
|
520
|
-
desc: example.desc,
|
|
521
|
-
}));
|
|
522
|
-
return {
|
|
523
|
-
desc: this.#desc,
|
|
524
|
-
usage,
|
|
525
|
-
options,
|
|
526
|
-
commands,
|
|
527
|
-
examples,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
#renderHelpPlain(helpData) {
|
|
531
|
-
const lines = [];
|
|
532
|
-
lines.push(helpData.desc);
|
|
533
|
-
lines.push('');
|
|
534
|
-
lines.push(helpData.usage);
|
|
289
|
+
lines.push(usage);
|
|
535
290
|
lines.push('');
|
|
536
|
-
if (
|
|
291
|
+
if (allOptions.length > 0) {
|
|
537
292
|
lines.push('Options:');
|
|
538
|
-
const
|
|
539
|
-
for (const
|
|
293
|
+
const optLines = [];
|
|
294
|
+
for (const opt of allOptions) {
|
|
295
|
+
const kebabLong = camelToKebabCase$1(opt.long);
|
|
296
|
+
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
297
|
+
sig += `--${kebabLong}`;
|
|
298
|
+
if (opt.args !== 'none') {
|
|
299
|
+
sig += ' <value>';
|
|
300
|
+
}
|
|
301
|
+
let desc = opt.desc;
|
|
302
|
+
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
303
|
+
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
304
|
+
}
|
|
305
|
+
if (opt.choices) {
|
|
306
|
+
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
307
|
+
}
|
|
308
|
+
optLines.push({ sig, desc });
|
|
309
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
310
|
+
optLines.push({
|
|
311
|
+
sig: ` --no-${kebabLong}`,
|
|
312
|
+
desc: `Negate --${kebabLong}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const maxSigLen = Math.max(...optLines.map(l => l.sig.length));
|
|
317
|
+
for (const { sig, desc } of optLines) {
|
|
540
318
|
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
541
319
|
lines.push(` ${sig}${padding}${desc}`);
|
|
542
320
|
}
|
|
543
321
|
lines.push('');
|
|
544
322
|
}
|
|
545
|
-
|
|
323
|
+
const showHelpSubcommand = this.#helpSubcommandEnabled && this.#subcommandsList.length > 0;
|
|
324
|
+
if (this.#subcommandsList.length > 0) {
|
|
546
325
|
lines.push('Commands:');
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
lines.push(` - ${example.title}`);
|
|
558
|
-
lines.push(` ${example.usage}`);
|
|
559
|
-
lines.push(` ${example.desc}`);
|
|
560
|
-
lines.push('');
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
return lines.join('\n');
|
|
564
|
-
}
|
|
565
|
-
#renderHelpTerminal(helpData) {
|
|
566
|
-
const lines = [];
|
|
567
|
-
lines.push(helpData.desc);
|
|
568
|
-
lines.push('');
|
|
569
|
-
lines.push(styleText(helpData.usage, TERMINAL_STYLE.bold));
|
|
570
|
-
lines.push('');
|
|
571
|
-
if (helpData.options.length > 0) {
|
|
572
|
-
lines.push(styleText('Options:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
573
|
-
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
574
|
-
for (const { sig, desc } of helpData.options) {
|
|
575
|
-
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
576
|
-
lines.push(` ${styleText(sig, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
326
|
+
const cmdLines = [];
|
|
327
|
+
if (showHelpSubcommand) {
|
|
328
|
+
cmdLines.push({ name: 'help', desc: 'Show help for a command' });
|
|
329
|
+
}
|
|
330
|
+
for (const entry of this.#subcommandsList) {
|
|
331
|
+
let name = entry.name;
|
|
332
|
+
if (entry.aliases.length > 0) {
|
|
333
|
+
name += `, ${entry.aliases.join(', ')}`;
|
|
334
|
+
}
|
|
335
|
+
cmdLines.push({ name, desc: entry.command.#desc });
|
|
577
336
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if (helpData.commands.length > 0) {
|
|
581
|
-
lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
582
|
-
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
583
|
-
for (const { name, desc } of helpData.commands) {
|
|
337
|
+
const maxNameLen = Math.max(...cmdLines.map(l => l.name.length));
|
|
338
|
+
for (const { name, desc } of cmdLines) {
|
|
584
339
|
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
585
|
-
lines.push(` ${
|
|
340
|
+
lines.push(` ${name}${padding}${desc}`);
|
|
586
341
|
}
|
|
587
342
|
lines.push('');
|
|
588
343
|
}
|
|
589
|
-
if (helpData.examples.length > 0) {
|
|
590
|
-
lines.push(styleText('Examples:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
591
|
-
for (const example of helpData.examples) {
|
|
592
|
-
lines.push(` - ${styleText(example.title, TERMINAL_STYLE.bold)}`);
|
|
593
|
-
lines.push(` ${styleText(example.usage, TERMINAL_STYLE.cyan)}`);
|
|
594
|
-
lines.push(` ${styleText(example.desc, TERMINAL_STYLE.italic, TERMINAL_STYLE.dim)}`);
|
|
595
|
-
lines.push('');
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
344
|
return lines.join('\n');
|
|
599
345
|
}
|
|
600
346
|
getCompletionMeta() {
|
|
601
|
-
const allOptions = this.#
|
|
347
|
+
const allOptions = this.#getMergedOptions();
|
|
602
348
|
const options = [];
|
|
603
349
|
for (const opt of allOptions) {
|
|
604
350
|
options.push({
|
|
@@ -624,407 +370,46 @@ class Command {
|
|
|
624
370
|
}),
|
|
625
371
|
};
|
|
626
372
|
}
|
|
627
|
-
#
|
|
628
|
-
|
|
373
|
+
#processHelpSubcommand(argv) {
|
|
374
|
+
if (!this.#helpSubcommandEnabled)
|
|
375
|
+
return argv;
|
|
376
|
+
if (argv.length < 1 || argv[0] !== 'help')
|
|
377
|
+
return argv;
|
|
378
|
+
if (argv.length === 1 || this.#subcommandsList.length === 0) {
|
|
379
|
+
return ['--help'];
|
|
380
|
+
}
|
|
381
|
+
const subName = argv[1];
|
|
382
|
+
const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
|
|
383
|
+
if (entry) {
|
|
384
|
+
return [subName, '--help', ...argv.slice(2)];
|
|
385
|
+
}
|
|
386
|
+
return argv;
|
|
629
387
|
}
|
|
630
388
|
#route(argv) {
|
|
631
389
|
const chain = [this];
|
|
632
|
-
const cmds = [];
|
|
633
390
|
let current = this;
|
|
634
391
|
let idx = 0;
|
|
635
392
|
while (idx < argv.length) {
|
|
636
393
|
const token = argv[idx];
|
|
637
394
|
if (token.startsWith('-'))
|
|
638
395
|
break;
|
|
639
|
-
const entry = current.#
|
|
396
|
+
const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
|
|
640
397
|
if (!entry)
|
|
641
398
|
break;
|
|
642
399
|
current = entry.command;
|
|
643
|
-
cmds.push(token);
|
|
644
400
|
chain.push(current);
|
|
645
401
|
idx += 1;
|
|
646
402
|
}
|
|
647
|
-
return { chain, remaining: argv.slice(idx)
|
|
648
|
-
}
|
|
649
|
-
#controlScan(tailArgv, leafCommand) {
|
|
650
|
-
const controls = { help: false, version: false };
|
|
651
|
-
const separatorIndex = tailArgv.indexOf('--');
|
|
652
|
-
const beforeSeparator = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
|
|
653
|
-
const afterSeparator = separatorIndex === -1 ? [] : tailArgv.slice(separatorIndex + 1);
|
|
654
|
-
let helpTarget;
|
|
655
|
-
let scanStartIndex = 0;
|
|
656
|
-
if (beforeSeparator[0] === 'help') {
|
|
657
|
-
controls.help = true;
|
|
658
|
-
scanStartIndex = 1;
|
|
659
|
-
const candidate = beforeSeparator[1];
|
|
660
|
-
if (candidate !== undefined && !candidate.startsWith('-')) {
|
|
661
|
-
helpTarget = candidate;
|
|
662
|
-
scanStartIndex = 2;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
const remainingBeforeSeparator = [];
|
|
666
|
-
for (let i = scanStartIndex; i < beforeSeparator.length; i += 1) {
|
|
667
|
-
const token = beforeSeparator[i];
|
|
668
|
-
if (token === '--help') {
|
|
669
|
-
controls.help = true;
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
if (token === '--version' && leafCommand.#supportsBuiltinVersion()) {
|
|
673
|
-
controls.version = true;
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
remainingBeforeSeparator.push(token);
|
|
677
|
-
}
|
|
678
|
-
const remaining = separatorIndex === -1
|
|
679
|
-
? remainingBeforeSeparator
|
|
680
|
-
: [...remainingBeforeSeparator, '--', ...afterSeparator];
|
|
681
|
-
return {
|
|
682
|
-
controls,
|
|
683
|
-
remaining,
|
|
684
|
-
helpTarget,
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
#createContext(params) {
|
|
688
|
-
const { chain, cmds, envs, reporter } = params;
|
|
689
|
-
const leafCommand = chain[chain.length - 1];
|
|
690
|
-
const envSnapshot = { ...envs };
|
|
691
|
-
return {
|
|
692
|
-
cmd: leafCommand,
|
|
693
|
-
chain,
|
|
694
|
-
envs: envSnapshot,
|
|
695
|
-
controls: { help: false, version: false },
|
|
696
|
-
sources: {
|
|
697
|
-
preset: {
|
|
698
|
-
argv: [],
|
|
699
|
-
envs: {},
|
|
700
|
-
},
|
|
701
|
-
user: {
|
|
702
|
-
cmds: [...cmds],
|
|
703
|
-
argv: [],
|
|
704
|
-
envs: envSnapshot,
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
reporter: reporter ?? this.#reporter ?? new Reporter(),
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
#resolveHelpCommand(leafCommand, helpTarget) {
|
|
711
|
-
if (helpTarget === undefined) {
|
|
712
|
-
return leafCommand;
|
|
713
|
-
}
|
|
714
|
-
const target = leafCommand.#findSubcommandEntry(helpTarget);
|
|
715
|
-
if (target === undefined) {
|
|
716
|
-
return leafCommand;
|
|
717
|
-
}
|
|
718
|
-
return target.command;
|
|
719
|
-
}
|
|
720
|
-
async #preset(controlTailArgv, ctx, optionPolicyMap) {
|
|
721
|
-
const commandPath = ctx.chain[ctx.chain.length - 1].#getCommandPath();
|
|
722
|
-
const separatorIndex = controlTailArgv.indexOf('--');
|
|
723
|
-
const beforeSeparator = separatorIndex === -1 ? controlTailArgv : controlTailArgv.slice(0, separatorIndex);
|
|
724
|
-
const afterSeparator = separatorIndex === -1 ? [] : controlTailArgv.slice(separatorIndex + 1);
|
|
725
|
-
const rootScanResult = this.#scanPresetRootDirectives(beforeSeparator, commandPath);
|
|
726
|
-
const commandPreset = this.#resolveCommandPresetFromChain(ctx.chain);
|
|
727
|
-
const presetRoot = await this.#resolveEffectivePresetRoot(rootScanResult.cliPresetRoots, commandPreset, commandPath);
|
|
728
|
-
const fileScanResult = this.#scanPresetFileDirectives(rootScanResult.cleanArgv, commandPath);
|
|
729
|
-
const cleanArgv = separatorIndex === -1
|
|
730
|
-
? fileScanResult.cleanArgv
|
|
731
|
-
: [...fileScanResult.cleanArgv, '--', ...afterSeparator];
|
|
732
|
-
const presetOptsFiles = this.#resolvePresetFileSources({
|
|
733
|
-
cliFiles: fileScanResult.cliPresetOptsFiles,
|
|
734
|
-
commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.opt),
|
|
735
|
-
presetRoot,
|
|
736
|
-
defaultFilename: DEFAULT_PRESET_OPTS_FILENAME,
|
|
737
|
-
});
|
|
738
|
-
const presetEnvsFiles = this.#resolvePresetFileSources({
|
|
739
|
-
cliFiles: fileScanResult.cliPresetEnvsFiles,
|
|
740
|
-
commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.env),
|
|
741
|
-
presetRoot,
|
|
742
|
-
defaultFilename: DEFAULT_PRESET_ENVS_FILENAME,
|
|
743
|
-
});
|
|
744
|
-
const userSources = {
|
|
745
|
-
cmds: [...ctx.sources.user.cmds],
|
|
746
|
-
argv: [...cleanArgv],
|
|
747
|
-
envs: { ...ctx.sources.user.envs },
|
|
748
|
-
};
|
|
749
|
-
const presetArgv = [];
|
|
750
|
-
for (const file of presetOptsFiles) {
|
|
751
|
-
const content = await this.#readPresetFile(file, commandPath);
|
|
752
|
-
if (content === undefined) {
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
const tokens = this.#tokenizePresetOptions(content);
|
|
756
|
-
this.#validatePresetOptionTokens(tokens, file.displayPath, commandPath);
|
|
757
|
-
this.#assertPresetOptionFragments(tokens, file.displayPath, ctx.chain, optionPolicyMap);
|
|
758
|
-
presetArgv.push(...tokens);
|
|
759
|
-
}
|
|
760
|
-
const presetEnvs = {};
|
|
761
|
-
for (const file of presetEnvsFiles) {
|
|
762
|
-
const content = await this.#readPresetFile(file, commandPath);
|
|
763
|
-
if (content === undefined) {
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
let parsed;
|
|
767
|
-
try {
|
|
768
|
-
parsed = parse(content);
|
|
769
|
-
}
|
|
770
|
-
catch (error) {
|
|
771
|
-
throw new CommanderError('ConfigurationError', `failed to parse preset envs file "${file.displayPath}": ${error.message}`, commandPath);
|
|
772
|
-
}
|
|
773
|
-
Object.assign(presetEnvs, parsed);
|
|
774
|
-
}
|
|
775
|
-
const sources = {
|
|
776
|
-
user: userSources,
|
|
777
|
-
preset: {
|
|
778
|
-
argv: presetArgv,
|
|
779
|
-
envs: presetEnvs,
|
|
780
|
-
},
|
|
781
|
-
};
|
|
782
|
-
const envs = { ...sources.user.envs, ...sources.preset.envs };
|
|
783
|
-
const tailArgv = [...sources.preset.argv, ...sources.user.argv];
|
|
784
|
-
return { tailArgv, envs, sources };
|
|
785
|
-
}
|
|
786
|
-
#resolveCommandPresetFromChain(chain) {
|
|
787
|
-
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
788
|
-
const preset = chain[index].#presetConfig;
|
|
789
|
-
if (preset?.root !== undefined) {
|
|
790
|
-
return preset;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return undefined;
|
|
403
|
+
return { chain, remaining: argv.slice(idx) };
|
|
794
404
|
}
|
|
795
|
-
|
|
796
|
-
if (cliPresetRoots.length > 0) {
|
|
797
|
-
const root = cliPresetRoots[cliPresetRoots.length - 1];
|
|
798
|
-
return await this.#assertPresetRoot(root, PRESET_ROOT_FLAG, commandPath);
|
|
799
|
-
}
|
|
800
|
-
if (commandPreset?.root === undefined) {
|
|
801
|
-
return undefined;
|
|
802
|
-
}
|
|
803
|
-
return await this.#assertPresetRoot(commandPreset.root, 'command.preset.root', commandPath);
|
|
804
|
-
}
|
|
805
|
-
async #assertPresetRoot(root, sourceName, commandPath) {
|
|
806
|
-
if (!path__default.isAbsolute(root)) {
|
|
807
|
-
throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not an absolute directory`, commandPath);
|
|
808
|
-
}
|
|
809
|
-
let stats;
|
|
810
|
-
try {
|
|
811
|
-
stats = await stat(root);
|
|
812
|
-
}
|
|
813
|
-
catch (error) {
|
|
814
|
-
throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" cannot be accessed (${error.message})`, commandPath);
|
|
815
|
-
}
|
|
816
|
-
if (!stats.isDirectory()) {
|
|
817
|
-
throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not a directory`, commandPath);
|
|
818
|
-
}
|
|
819
|
-
return root;
|
|
820
|
-
}
|
|
821
|
-
#normalizeCommandPresetFile(filepath) {
|
|
822
|
-
if (filepath === undefined) {
|
|
823
|
-
return undefined;
|
|
824
|
-
}
|
|
825
|
-
if (!this.#isValidPresetFileValue(filepath)) {
|
|
826
|
-
return undefined;
|
|
827
|
-
}
|
|
828
|
-
return filepath;
|
|
829
|
-
}
|
|
830
|
-
#resolvePresetFileSources(params) {
|
|
831
|
-
const { cliFiles, commandPresetFile, presetRoot, defaultFilename } = params;
|
|
832
|
-
if (cliFiles.length > 0) {
|
|
833
|
-
return cliFiles.map(filepath => ({
|
|
834
|
-
displayPath: filepath,
|
|
835
|
-
absolutePath: this.#resolvePresetFileAbsolutePath(filepath, presetRoot),
|
|
836
|
-
explicit: true,
|
|
837
|
-
}));
|
|
838
|
-
}
|
|
839
|
-
if (presetRoot === undefined) {
|
|
840
|
-
return [];
|
|
841
|
-
}
|
|
842
|
-
if (commandPresetFile !== undefined) {
|
|
843
|
-
return [
|
|
844
|
-
{
|
|
845
|
-
displayPath: commandPresetFile,
|
|
846
|
-
absolutePath: this.#resolvePresetFileAbsolutePath(commandPresetFile, presetRoot),
|
|
847
|
-
explicit: true,
|
|
848
|
-
},
|
|
849
|
-
];
|
|
850
|
-
}
|
|
851
|
-
const absolutePath = path__default.resolve(presetRoot, defaultFilename);
|
|
852
|
-
return [
|
|
853
|
-
{
|
|
854
|
-
displayPath: absolutePath,
|
|
855
|
-
absolutePath,
|
|
856
|
-
explicit: false,
|
|
857
|
-
},
|
|
858
|
-
];
|
|
859
|
-
}
|
|
860
|
-
#resolvePresetFileAbsolutePath(filepath, presetRoot) {
|
|
861
|
-
if (path__default.isAbsolute(filepath)) {
|
|
862
|
-
return filepath;
|
|
863
|
-
}
|
|
864
|
-
if (presetRoot !== undefined) {
|
|
865
|
-
return path__default.resolve(presetRoot, filepath);
|
|
866
|
-
}
|
|
867
|
-
return path__default.resolve(process.cwd(), filepath);
|
|
868
|
-
}
|
|
869
|
-
#assertPresetOptionFragments(tokens, filepath, chain, optionPolicyMap) {
|
|
870
|
-
if (tokens.length === 0) {
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
const commandPath = chain[chain.length - 1].#getCommandPath();
|
|
874
|
-
try {
|
|
875
|
-
const { optionTokens, restArgs } = tokenize(tokens, commandPath);
|
|
876
|
-
void restArgs;
|
|
877
|
-
const { argTokens } = this.#resolve(chain, optionTokens, optionPolicyMap);
|
|
878
|
-
if (argTokens.length > 0) {
|
|
879
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": token "${argTokens[0].original}" cannot be resolved as an option fragment`, commandPath);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
catch (error) {
|
|
883
|
-
if (error instanceof CommanderError) {
|
|
884
|
-
if (error.kind === 'ConfigurationError') {
|
|
885
|
-
throw error;
|
|
886
|
-
}
|
|
887
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": ${error.message}`, commandPath);
|
|
888
|
-
}
|
|
889
|
-
throw error;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
#scanPresetRootDirectives(argv, commandPath) {
|
|
893
|
-
const cleanArgv = [];
|
|
894
|
-
const cliPresetRoots = [];
|
|
895
|
-
let index = 0;
|
|
896
|
-
while (index < argv.length) {
|
|
897
|
-
const token = argv[index];
|
|
898
|
-
if (token === PRESET_ROOT_FLAG) {
|
|
899
|
-
const value = argv[index + 1];
|
|
900
|
-
if (value === undefined || value.length === 0) {
|
|
901
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
|
|
902
|
-
}
|
|
903
|
-
cliPresetRoots.push(value);
|
|
904
|
-
index += 2;
|
|
905
|
-
continue;
|
|
906
|
-
}
|
|
907
|
-
if (token.startsWith(`${PRESET_ROOT_FLAG}=`)) {
|
|
908
|
-
const value = token.slice(PRESET_ROOT_FLAG.length + 1);
|
|
909
|
-
if (value.length === 0) {
|
|
910
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
|
|
911
|
-
}
|
|
912
|
-
cliPresetRoots.push(value);
|
|
913
|
-
index += 1;
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
cleanArgv.push(token);
|
|
917
|
-
index += 1;
|
|
918
|
-
}
|
|
919
|
-
return { cleanArgv, cliPresetRoots };
|
|
920
|
-
}
|
|
921
|
-
#scanPresetFileDirectives(argv, commandPath) {
|
|
922
|
-
const cleanArgv = [];
|
|
923
|
-
const cliPresetOptsFiles = [];
|
|
924
|
-
const cliPresetEnvsFiles = [];
|
|
925
|
-
const assertAndPush = (flag, value) => {
|
|
926
|
-
this.#assertPresetFileValue(value, flag, commandPath);
|
|
927
|
-
if (flag === PRESET_OPTS_FLAG) {
|
|
928
|
-
cliPresetOptsFiles.push(value);
|
|
929
|
-
}
|
|
930
|
-
else {
|
|
931
|
-
cliPresetEnvsFiles.push(value);
|
|
932
|
-
}
|
|
933
|
-
};
|
|
934
|
-
let index = 0;
|
|
935
|
-
while (index < argv.length) {
|
|
936
|
-
const token = argv[index];
|
|
937
|
-
if (token === PRESET_OPTS_FLAG || token === PRESET_ENVS_FLAG) {
|
|
938
|
-
const value = argv[index + 1];
|
|
939
|
-
if (value === undefined || value.length === 0) {
|
|
940
|
-
throw new CommanderError('ConfigurationError', `missing value for "${token}"`, commandPath);
|
|
941
|
-
}
|
|
942
|
-
assertAndPush(token, value);
|
|
943
|
-
index += 2;
|
|
944
|
-
continue;
|
|
945
|
-
}
|
|
946
|
-
if (token.startsWith(`${PRESET_OPTS_FLAG}=`)) {
|
|
947
|
-
const value = token.slice(PRESET_OPTS_FLAG.length + 1);
|
|
948
|
-
if (value.length === 0) {
|
|
949
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_OPTS_FLAG}"`, commandPath);
|
|
950
|
-
}
|
|
951
|
-
assertAndPush(PRESET_OPTS_FLAG, value);
|
|
952
|
-
index += 1;
|
|
953
|
-
continue;
|
|
954
|
-
}
|
|
955
|
-
if (token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
|
|
956
|
-
const value = token.slice(PRESET_ENVS_FLAG.length + 1);
|
|
957
|
-
if (value.length === 0) {
|
|
958
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ENVS_FLAG}"`, commandPath);
|
|
959
|
-
}
|
|
960
|
-
assertAndPush(PRESET_ENVS_FLAG, value);
|
|
961
|
-
index += 1;
|
|
962
|
-
continue;
|
|
963
|
-
}
|
|
964
|
-
cleanArgv.push(token);
|
|
965
|
-
index += 1;
|
|
966
|
-
}
|
|
967
|
-
return { cleanArgv, cliPresetOptsFiles, cliPresetEnvsFiles };
|
|
968
|
-
}
|
|
969
|
-
#isValidPresetFileValue(filepath) {
|
|
970
|
-
return filepath.length > 0 && !filepath.startsWith('..');
|
|
971
|
-
}
|
|
972
|
-
#assertPresetFileValue(filepath, directive, commandPath) {
|
|
973
|
-
if (this.#isValidPresetFileValue(filepath)) {
|
|
974
|
-
return;
|
|
975
|
-
}
|
|
976
|
-
throw new CommanderError('ConfigurationError', `invalid value for "${directive}": "${filepath}" (must be non-empty and must not start with "..")`, commandPath);
|
|
977
|
-
}
|
|
978
|
-
async #readPresetFile(file, commandPath) {
|
|
979
|
-
try {
|
|
980
|
-
return await readFile(file.absolutePath, 'utf8');
|
|
981
|
-
}
|
|
982
|
-
catch (error) {
|
|
983
|
-
const ioError = error;
|
|
984
|
-
if (!file.explicit && ioError.code === 'ENOENT') {
|
|
985
|
-
return undefined;
|
|
986
|
-
}
|
|
987
|
-
throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${ioError.message}`, commandPath);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
#tokenizePresetOptions(content) {
|
|
991
|
-
return content
|
|
992
|
-
.split(/\s+/)
|
|
993
|
-
.map(token => token.trim())
|
|
994
|
-
.filter(token => token.length > 0);
|
|
995
|
-
}
|
|
996
|
-
#validatePresetOptionTokens(tokens, filepath, commandPath) {
|
|
997
|
-
if (tokens.length === 0) {
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
if (!tokens[0].startsWith('-')) {
|
|
1001
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": bare token "${tokens[0]}" cannot appear before any option token`, commandPath);
|
|
1002
|
-
}
|
|
1003
|
-
for (const token of tokens) {
|
|
1004
|
-
if (token === '--') {
|
|
1005
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": "--" is not allowed`, commandPath);
|
|
1006
|
-
}
|
|
1007
|
-
if (token === 'help' || token === '--help' || token === '--version') {
|
|
1008
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": control token "${token}" is not allowed`, commandPath);
|
|
1009
|
-
}
|
|
1010
|
-
if (token === PRESET_ROOT_FLAG ||
|
|
1011
|
-
token.startsWith(`${PRESET_ROOT_FLAG}=`) ||
|
|
1012
|
-
token === PRESET_OPTS_FLAG ||
|
|
1013
|
-
token.startsWith(`${PRESET_OPTS_FLAG}=`) ||
|
|
1014
|
-
token === PRESET_ENVS_FLAG ||
|
|
1015
|
-
token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
|
|
1016
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": preset directive "${token}" is not allowed`, commandPath);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
#resolve(chain, tokens, optionPolicyMap) {
|
|
405
|
+
#resolve(chain, tokens) {
|
|
1021
406
|
const consumedTokens = new Map();
|
|
1022
407
|
let remaining = [...tokens];
|
|
1023
408
|
const shadowed = new Set();
|
|
1024
409
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
1025
410
|
const cmd = chain[i];
|
|
1026
|
-
const
|
|
1027
|
-
const result = cmd.#shift(remaining, shadowed,
|
|
411
|
+
const includeVersion = i === 0;
|
|
412
|
+
const result = cmd.#shift(remaining, shadowed, includeVersion);
|
|
1028
413
|
consumedTokens.set(cmd, result.consumed);
|
|
1029
414
|
remaining = result.remaining;
|
|
1030
415
|
for (const opt of cmd.#options) {
|
|
@@ -1041,7 +426,8 @@ class Command {
|
|
|
1041
426
|
}
|
|
1042
427
|
return { consumedTokens, argTokens };
|
|
1043
428
|
}
|
|
1044
|
-
#shift(tokens, shadowed,
|
|
429
|
+
#shift(tokens, shadowed, includeVersion) {
|
|
430
|
+
const allOptions = this.#getMergedOptions(includeVersion);
|
|
1045
431
|
const effectiveOptions = allOptions.filter(o => !shadowed.has(o.long));
|
|
1046
432
|
const optionByLong = new Map();
|
|
1047
433
|
const optionByShort = new Map();
|
|
@@ -1109,40 +495,34 @@ class Command {
|
|
|
1109
495
|
}
|
|
1110
496
|
return { consumed, remaining };
|
|
1111
497
|
}
|
|
1112
|
-
#parse(chain, resolveResult,
|
|
498
|
+
#parse(chain, resolveResult, ctx, restArgs) {
|
|
1113
499
|
const { consumedTokens, argTokens } = resolveResult;
|
|
1114
500
|
const leafCommand = chain[chain.length - 1];
|
|
1115
|
-
this.#validateMergedShortOptions(chain
|
|
501
|
+
this.#validateMergedShortOptions(chain);
|
|
1116
502
|
const optsMap = new Map();
|
|
1117
|
-
for (
|
|
1118
|
-
const
|
|
503
|
+
for (let i = 0; i < chain.length; i++) {
|
|
504
|
+
const cmd = chain[i];
|
|
505
|
+
const includeVersion = i === 0;
|
|
1119
506
|
const tokens = consumedTokens.get(cmd) ?? [];
|
|
1120
|
-
const opts = cmd.#parseOptions(tokens,
|
|
507
|
+
const opts = cmd.#parseOptions(tokens, includeVersion);
|
|
1121
508
|
optsMap.set(cmd, opts);
|
|
1122
|
-
for (const opt of
|
|
509
|
+
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
1123
510
|
if (opt.apply && opts[opt.long] !== undefined) {
|
|
1124
511
|
opt.apply(opts[opt.long], ctx);
|
|
1125
512
|
}
|
|
1126
513
|
}
|
|
1127
514
|
}
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
if (Object.prototype.hasOwnProperty.call(leafParsedOpts, opt.long)) {
|
|
1132
|
-
leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
|
|
1133
|
-
}
|
|
515
|
+
const mergedOpts = {};
|
|
516
|
+
for (const cmd of chain) {
|
|
517
|
+
Object.assign(mergedOpts, optsMap.get(cmd) ?? {});
|
|
1134
518
|
}
|
|
1135
519
|
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
1136
520
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
1137
|
-
|
|
1138
|
-
...ctx,
|
|
1139
|
-
sources: this.#freezeInputSources(ctx.sources),
|
|
1140
|
-
};
|
|
1141
|
-
return { ctx: parseCtx, opts: leafLocalOpts, args, rawArgs };
|
|
521
|
+
return { ctx, opts: mergedOpts, args, rawArgs };
|
|
1142
522
|
}
|
|
1143
|
-
#parseOptions(tokens,
|
|
523
|
+
#parseOptions(tokens, includeVersion) {
|
|
524
|
+
const allOptions = this.#getMergedOptions(includeVersion);
|
|
1144
525
|
const opts = {};
|
|
1145
|
-
let sawColorToken = false;
|
|
1146
526
|
for (const opt of allOptions) {
|
|
1147
527
|
if (opt.default !== undefined) {
|
|
1148
528
|
opts[opt.long] = opt.default;
|
|
@@ -1170,9 +550,6 @@ class Command {
|
|
|
1170
550
|
i += 1;
|
|
1171
551
|
continue;
|
|
1172
552
|
}
|
|
1173
|
-
if (opt.long === 'color') {
|
|
1174
|
-
sawColorToken = true;
|
|
1175
|
-
}
|
|
1176
553
|
const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
|
|
1177
554
|
if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
|
|
1178
555
|
throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
|
|
@@ -1249,9 +626,6 @@ class Command {
|
|
|
1249
626
|
}
|
|
1250
627
|
}
|
|
1251
628
|
}
|
|
1252
|
-
if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
|
|
1253
|
-
opts['color'] = false;
|
|
1254
|
-
}
|
|
1255
629
|
return opts;
|
|
1256
630
|
}
|
|
1257
631
|
#convertValue(opt, rawValue) {
|
|
@@ -1322,60 +696,27 @@ class Command {
|
|
|
1322
696
|
}
|
|
1323
697
|
return raw;
|
|
1324
698
|
}
|
|
1325
|
-
#
|
|
1326
|
-
return this.#options.some(option => option.long === long);
|
|
1327
|
-
}
|
|
1328
|
-
#supportsBuiltinVersion() {
|
|
1329
|
-
return this.#parent === undefined && this.#version !== undefined && this.#builtin.option.version;
|
|
1330
|
-
}
|
|
1331
|
-
#resolveOptionPolicy() {
|
|
699
|
+
#getMergedOptions(includeVersion = !this.#parent) {
|
|
1332
700
|
const optionMap = new Map();
|
|
1333
|
-
const
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
const hasUserLogColorful = this.#hasUserOption('logColorful');
|
|
1338
|
-
if (this.#builtin.option.color && !hasUserColor) {
|
|
1339
|
-
optionMap.set('color', BUILTIN_COLOR_OPTION);
|
|
1340
|
-
}
|
|
1341
|
-
if (this.#builtin.option.logLevel && !hasUserLogLevel) {
|
|
1342
|
-
optionMap.set('logLevel', logLevelOption);
|
|
701
|
+
const hasUserHelp = this.#options.some(o => o.long === 'help');
|
|
702
|
+
const hasUserVersion = this.#options.some(o => o.long === 'version');
|
|
703
|
+
if (!hasUserHelp) {
|
|
704
|
+
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
1343
705
|
}
|
|
1344
|
-
if (
|
|
1345
|
-
optionMap.set('
|
|
1346
|
-
}
|
|
1347
|
-
if (this.#builtin.option.logDate && !hasUserLogDate) {
|
|
1348
|
-
optionMap.set('logDate', logDateOption);
|
|
1349
|
-
}
|
|
1350
|
-
if (this.#builtin.option.logColorful && !hasUserLogColorful) {
|
|
1351
|
-
optionMap.set('logColorful', logColorfulOption);
|
|
706
|
+
if (!hasUserVersion && includeVersion) {
|
|
707
|
+
optionMap.set('version', BUILTIN_VERSION_OPTION);
|
|
1352
708
|
}
|
|
1353
709
|
for (const opt of this.#options) {
|
|
1354
710
|
optionMap.set(opt.long, opt);
|
|
1355
711
|
}
|
|
1356
|
-
return
|
|
1357
|
-
mergedOptions: Array.from(optionMap.values()),
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
#buildOptionPolicyMap(chain) {
|
|
1361
|
-
const optionPolicyMap = new Map();
|
|
1362
|
-
for (const cmd of chain) {
|
|
1363
|
-
optionPolicyMap.set(cmd, cmd.#resolveOptionPolicy());
|
|
1364
|
-
}
|
|
1365
|
-
return optionPolicyMap;
|
|
1366
|
-
}
|
|
1367
|
-
#mustGetOptionPolicy(optionPolicyMap, cmd) {
|
|
1368
|
-
const policy = optionPolicyMap.get(cmd);
|
|
1369
|
-
if (policy !== undefined) {
|
|
1370
|
-
return policy;
|
|
1371
|
-
}
|
|
1372
|
-
throw new CommanderError('ConfigurationError', `missing option policy for command "${cmd.#getCommandPath()}"`, this.#getCommandPath());
|
|
712
|
+
return Array.from(optionMap.values());
|
|
1373
713
|
}
|
|
1374
|
-
#validateMergedShortOptions(chain
|
|
714
|
+
#validateMergedShortOptions(chain) {
|
|
1375
715
|
const mergedByLong = new Map();
|
|
1376
|
-
for (
|
|
1377
|
-
const
|
|
1378
|
-
|
|
716
|
+
for (let i = 0; i < chain.length; i++) {
|
|
717
|
+
const cmd = chain[i];
|
|
718
|
+
const includeVersion = i === 0;
|
|
719
|
+
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
1379
720
|
mergedByLong.set(opt.long, opt);
|
|
1380
721
|
}
|
|
1381
722
|
}
|
|
@@ -1391,9 +732,6 @@ class Command {
|
|
|
1391
732
|
}
|
|
1392
733
|
}
|
|
1393
734
|
#validateOptionConfig(opt) {
|
|
1394
|
-
if (opt.long === 'help' || opt.long === 'version') {
|
|
1395
|
-
throw new CommanderError('ConfigurationError', `option long name "${opt.long}" is reserved`, this.#getCommandPath());
|
|
1396
|
-
}
|
|
1397
735
|
if (opt.type === 'boolean' && opt.args !== 'none') {
|
|
1398
736
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
1399
737
|
}
|
|
@@ -1443,21 +781,6 @@ class Command {
|
|
|
1443
781
|
}
|
|
1444
782
|
}
|
|
1445
783
|
}
|
|
1446
|
-
#normalizeExample(example) {
|
|
1447
|
-
const title = example.title.trim();
|
|
1448
|
-
const usage = example.usage.trim();
|
|
1449
|
-
const desc = example.desc.trim();
|
|
1450
|
-
if (!title) {
|
|
1451
|
-
throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
|
|
1452
|
-
}
|
|
1453
|
-
if (!usage) {
|
|
1454
|
-
throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
|
|
1455
|
-
}
|
|
1456
|
-
if (!desc) {
|
|
1457
|
-
throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
|
|
1458
|
-
}
|
|
1459
|
-
return { title, usage, desc };
|
|
1460
|
-
}
|
|
1461
784
|
async #runAction(params) {
|
|
1462
785
|
if (!this.#action)
|
|
1463
786
|
return;
|
|
@@ -1474,51 +797,16 @@ class Command {
|
|
|
1474
797
|
process.exit(1);
|
|
1475
798
|
}
|
|
1476
799
|
}
|
|
1477
|
-
#
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
return color;
|
|
1482
|
-
}
|
|
1483
|
-
const separatorIndex = tailArgv.indexOf('--');
|
|
1484
|
-
const scanTokens = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
|
|
1485
|
-
for (const token of scanTokens) {
|
|
1486
|
-
if (token === '--color') {
|
|
1487
|
-
color = true;
|
|
1488
|
-
continue;
|
|
1489
|
-
}
|
|
1490
|
-
if (token === '--no-color') {
|
|
1491
|
-
color = false;
|
|
1492
|
-
continue;
|
|
1493
|
-
}
|
|
1494
|
-
if (!token.startsWith('--color=')) {
|
|
1495
|
-
continue;
|
|
1496
|
-
}
|
|
1497
|
-
const value = token.slice('--color='.length);
|
|
1498
|
-
if (value === 'true') {
|
|
1499
|
-
color = true;
|
|
1500
|
-
}
|
|
1501
|
-
else if (value === 'false') {
|
|
1502
|
-
color = false;
|
|
800
|
+
#hasFlag(tokens, longName, shortName) {
|
|
801
|
+
for (const token of tokens) {
|
|
802
|
+
if (token.type === 'long' && token.name === longName) {
|
|
803
|
+
return true;
|
|
1503
804
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
805
|
+
if (token.type === 'short' && token.name === shortName) {
|
|
806
|
+
return true;
|
|
1506
807
|
}
|
|
1507
808
|
}
|
|
1508
|
-
return
|
|
1509
|
-
}
|
|
1510
|
-
#freezeInputSources(sources) {
|
|
1511
|
-
return Object.freeze({
|
|
1512
|
-
preset: Object.freeze({
|
|
1513
|
-
argv: Object.freeze([...sources.preset.argv]),
|
|
1514
|
-
envs: Object.freeze({ ...sources.preset.envs }),
|
|
1515
|
-
}),
|
|
1516
|
-
user: Object.freeze({
|
|
1517
|
-
cmds: Object.freeze([...sources.user.cmds]),
|
|
1518
|
-
argv: Object.freeze([...sources.user.argv]),
|
|
1519
|
-
envs: Object.freeze({ ...sources.user.envs }),
|
|
1520
|
-
}),
|
|
1521
|
-
});
|
|
809
|
+
return false;
|
|
1522
810
|
}
|
|
1523
811
|
#getCommandPath() {
|
|
1524
812
|
const parts = [];
|
|
@@ -1533,166 +821,13 @@ class Command {
|
|
|
1533
821
|
}
|
|
1534
822
|
}
|
|
1535
823
|
|
|
1536
|
-
function isIpv4(rawValue) {
|
|
1537
|
-
const parts = rawValue.split('.');
|
|
1538
|
-
if (parts.length !== 4) {
|
|
1539
|
-
return false;
|
|
1540
|
-
}
|
|
1541
|
-
for (const part of parts) {
|
|
1542
|
-
if (part.length < 1 || !/^\d+$/.test(part)) {
|
|
1543
|
-
return false;
|
|
1544
|
-
}
|
|
1545
|
-
if (part.length > 1 && part.startsWith('0')) {
|
|
1546
|
-
return false;
|
|
1547
|
-
}
|
|
1548
|
-
const value = Number(part);
|
|
1549
|
-
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
1550
|
-
return false;
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
return true;
|
|
1554
|
-
}
|
|
1555
|
-
function countIpv6Segments(part, allowIpv4Tail) {
|
|
1556
|
-
if (!part) {
|
|
1557
|
-
return { count: 0, hasIpv4Tail: false };
|
|
1558
|
-
}
|
|
1559
|
-
const segments = part.split(':');
|
|
1560
|
-
let count = 0;
|
|
1561
|
-
let hasIpv4Tail = false;
|
|
1562
|
-
for (let i = 0; i < segments.length; ++i) {
|
|
1563
|
-
const segment = segments[i];
|
|
1564
|
-
const isLastSegment = i === segments.length - 1;
|
|
1565
|
-
if (!segment) {
|
|
1566
|
-
return null;
|
|
1567
|
-
}
|
|
1568
|
-
if (segment.includes('.')) {
|
|
1569
|
-
if (!allowIpv4Tail || !isLastSegment || hasIpv4Tail || !isIpv4(segment)) {
|
|
1570
|
-
return null;
|
|
1571
|
-
}
|
|
1572
|
-
hasIpv4Tail = true;
|
|
1573
|
-
count += 2;
|
|
1574
|
-
continue;
|
|
1575
|
-
}
|
|
1576
|
-
if (!/^[0-9A-Fa-f]{1,4}$/.test(segment)) {
|
|
1577
|
-
return null;
|
|
1578
|
-
}
|
|
1579
|
-
count += 1;
|
|
1580
|
-
}
|
|
1581
|
-
return { count, hasIpv4Tail };
|
|
1582
|
-
}
|
|
1583
|
-
function isIpv6(rawValue) {
|
|
1584
|
-
if (!rawValue || !/^[0-9A-Fa-f:.]+$/.test(rawValue)) {
|
|
1585
|
-
return false;
|
|
1586
|
-
}
|
|
1587
|
-
const doubleColonCount = rawValue.split('::').length - 1;
|
|
1588
|
-
if (doubleColonCount > 1) {
|
|
1589
|
-
return false;
|
|
1590
|
-
}
|
|
1591
|
-
if (doubleColonCount === 0) {
|
|
1592
|
-
const full = countIpv6Segments(rawValue, true);
|
|
1593
|
-
return full !== null && full.count === 8;
|
|
1594
|
-
}
|
|
1595
|
-
const [left, right] = rawValue.split('::');
|
|
1596
|
-
const leftPart = countIpv6Segments(left, right.length === 0);
|
|
1597
|
-
const rightPart = countIpv6Segments(right, true);
|
|
1598
|
-
if (!leftPart || !rightPart) {
|
|
1599
|
-
return false;
|
|
1600
|
-
}
|
|
1601
|
-
const totalSegments = leftPart.count + rightPart.count;
|
|
1602
|
-
return totalSegments < 8;
|
|
1603
|
-
}
|
|
1604
|
-
function isIp(rawValue) {
|
|
1605
|
-
return isIpv4(rawValue) || isIpv6(rawValue);
|
|
1606
|
-
}
|
|
1607
|
-
function isDomain(rawValue) {
|
|
1608
|
-
if (rawValue.length < 1 || rawValue.length > 253 || rawValue.endsWith('.')) {
|
|
1609
|
-
return false;
|
|
1610
|
-
}
|
|
1611
|
-
const labels = rawValue.split('.');
|
|
1612
|
-
if (labels.length < 2) {
|
|
1613
|
-
return false;
|
|
1614
|
-
}
|
|
1615
|
-
if (labels.some(label => label.length < 1 || label.length > 63)) {
|
|
1616
|
-
return false;
|
|
1617
|
-
}
|
|
1618
|
-
const labelPattern = /^[A-Za-z0-9-]+$/;
|
|
1619
|
-
if (labels.some(label => !labelPattern.test(label) || label.startsWith('-') || label.endsWith('-'))) {
|
|
1620
|
-
return false;
|
|
1621
|
-
}
|
|
1622
|
-
const topLevelLabel = labels[labels.length - 1];
|
|
1623
|
-
return /[A-Za-z]/.test(topLevelLabel);
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
class Coerce {
|
|
1627
|
-
constructor() { }
|
|
1628
|
-
static create(name, expectedType, validator, errorMessage) {
|
|
1629
|
-
return (rawValue) => {
|
|
1630
|
-
const value = Number(rawValue);
|
|
1631
|
-
if (!validator(value)) {
|
|
1632
|
-
throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
|
|
1633
|
-
}
|
|
1634
|
-
return value;
|
|
1635
|
-
};
|
|
1636
|
-
}
|
|
1637
|
-
static choice(name, values, errorMessage) {
|
|
1638
|
-
return (rawValue) => {
|
|
1639
|
-
if (values.includes(rawValue)) {
|
|
1640
|
-
return rawValue;
|
|
1641
|
-
}
|
|
1642
|
-
throw new Error(errorMessage ?? `${name} is expected as one of [${values.join(', ')}], but got ${rawValue}`);
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
static domain(name, errorMessage) {
|
|
1646
|
-
return (rawValue) => {
|
|
1647
|
-
if (isDomain(rawValue)) {
|
|
1648
|
-
return rawValue;
|
|
1649
|
-
}
|
|
1650
|
-
throw new Error(errorMessage ?? `${name} is expected as a valid domain, but got ${rawValue}`);
|
|
1651
|
-
};
|
|
1652
|
-
}
|
|
1653
|
-
static host(name, errorMessage) {
|
|
1654
|
-
return (rawValue) => {
|
|
1655
|
-
if (isIp(rawValue) || isDomain(rawValue)) {
|
|
1656
|
-
return rawValue;
|
|
1657
|
-
}
|
|
1658
|
-
throw new Error(errorMessage ?? `${name} is expected as a valid host (IP or domain), but got ${rawValue}`);
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
static integer(name, errorMessage) {
|
|
1662
|
-
return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
|
|
1663
|
-
}
|
|
1664
|
-
static ip(name, errorMessage) {
|
|
1665
|
-
return (rawValue) => {
|
|
1666
|
-
if (isIp(rawValue)) {
|
|
1667
|
-
return rawValue;
|
|
1668
|
-
}
|
|
1669
|
-
throw new Error(errorMessage ?? `${name} is expected as a valid IP address, but got ${rawValue}`);
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
static number(name, errorMessage) {
|
|
1673
|
-
return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
|
|
1674
|
-
}
|
|
1675
|
-
static port(name, errorMessage) {
|
|
1676
|
-
return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
|
|
1677
|
-
}
|
|
1678
|
-
static positiveInteger(name, errorMessage) {
|
|
1679
|
-
return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
|
|
1680
|
-
}
|
|
1681
|
-
static positiveNumber(name, errorMessage) {
|
|
1682
|
-
return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
824
|
function camelToKebabCase(str) {
|
|
1687
825
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1688
826
|
}
|
|
1689
827
|
class CompletionCommand extends Command {
|
|
1690
|
-
constructor(root, config
|
|
828
|
+
constructor(root, config) {
|
|
829
|
+
const paths = config.paths;
|
|
1691
830
|
const programName = config.programName ?? root.name ?? 'program';
|
|
1692
|
-
const paths = {
|
|
1693
|
-
...createDefaultCompletionPaths(programName),
|
|
1694
|
-
...config.paths,
|
|
1695
|
-
};
|
|
1696
831
|
super({ desc: 'Generate shell completion script' });
|
|
1697
832
|
this.option({
|
|
1698
833
|
long: 'bash',
|
|
@@ -1767,13 +902,6 @@ class CompletionCommand extends Command {
|
|
|
1767
902
|
});
|
|
1768
903
|
}
|
|
1769
904
|
}
|
|
1770
|
-
function createDefaultCompletionPaths(programName) {
|
|
1771
|
-
return {
|
|
1772
|
-
bash: `~/.local/share/bash-completion/completions/${programName}`,
|
|
1773
|
-
fish: `~/.config/fish/completions/${programName}.fish`,
|
|
1774
|
-
pwsh: '~/.config/powershell/Microsoft.PowerShell_profile.ps1',
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1777
905
|
function expandHome(filepath) {
|
|
1778
906
|
if (filepath.startsWith('~/') || filepath === '~') {
|
|
1779
907
|
const home = process.env['HOME'] || process.env['USERPROFILE'] || '';
|
|
@@ -2051,4 +1179,30 @@ class PwshCompletion {
|
|
|
2051
1179
|
}
|
|
2052
1180
|
}
|
|
2053
1181
|
|
|
2054
|
-
|
|
1182
|
+
const logLevelOption = {
|
|
1183
|
+
long: 'logLevel',
|
|
1184
|
+
type: 'string',
|
|
1185
|
+
args: 'required',
|
|
1186
|
+
desc: 'Set log level',
|
|
1187
|
+
default: 'info',
|
|
1188
|
+
choices: LOG_LEVELS,
|
|
1189
|
+
coerce: (raw) => {
|
|
1190
|
+
const level = resolveLogLevel(raw);
|
|
1191
|
+
if (level === undefined) {
|
|
1192
|
+
throw new Error(`Invalid log level: ${raw}`);
|
|
1193
|
+
}
|
|
1194
|
+
return level;
|
|
1195
|
+
},
|
|
1196
|
+
apply: (value, ctx) => {
|
|
1197
|
+
ctx.reporter.setLevel(value);
|
|
1198
|
+
},
|
|
1199
|
+
};
|
|
1200
|
+
const silentOption = {
|
|
1201
|
+
long: 'silent',
|
|
1202
|
+
type: 'boolean',
|
|
1203
|
+
args: 'none',
|
|
1204
|
+
desc: 'Suppress non-error output',
|
|
1205
|
+
default: false,
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
export { BashCompletion, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, logLevelOption, silentOption };
|