@guanghechen/commander 4.6.0 → 4.7.1
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 +14 -0
- package/README.md +13 -15
- package/lib/cjs/browser.cjs +1779 -0
- package/lib/cjs/index.cjs +176 -1028
- package/lib/cjs/node.cjs +2165 -0
- package/lib/esm/browser.mjs +1764 -0
- package/lib/esm/index.mjs +175 -1021
- package/lib/esm/node.mjs +2145 -0
- package/lib/types/browser.d.ts +551 -0
- package/lib/types/index.d.ts +15 -191
- package/lib/types/node.d.ts +604 -0
- package/package.json +13 -10
package/lib/cjs/index.cjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var env = require('@guanghechen/env');
|
|
4
3
|
var reporter = require('@guanghechen/reporter');
|
|
5
|
-
var promises = require('node:fs/promises');
|
|
6
|
-
var path = require('node:path');
|
|
7
4
|
var fs = require('node:fs');
|
|
5
|
+
var path = require('node:path');
|
|
8
6
|
|
|
9
7
|
function _interopNamespaceDefault(e) {
|
|
10
8
|
var n = Object.create(null);
|
|
@@ -23,84 +21,8 @@ function _interopNamespaceDefault(e) {
|
|
|
23
21
|
return Object.freeze(n);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
27
24
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
28
|
-
|
|
29
|
-
const TERMINAL_STYLE = {
|
|
30
|
-
bold: '\x1b[1m',
|
|
31
|
-
italic: '\x1b[3m',
|
|
32
|
-
underline: '\x1b[4m',
|
|
33
|
-
cyan: '\x1b[36m',
|
|
34
|
-
dim: '\x1b[2m',
|
|
35
|
-
reset: '\x1b[0m',
|
|
36
|
-
};
|
|
37
|
-
function styleText(text, ...styles) {
|
|
38
|
-
return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const BUILTIN_LOG_LEVELS = ['debug', 'info', 'hint', 'warn', 'error'];
|
|
42
|
-
function resolveReporterLogLevel(raw) {
|
|
43
|
-
const normalized = raw.trim().toLowerCase();
|
|
44
|
-
return BUILTIN_LOG_LEVELS.find(level => level === normalized);
|
|
45
|
-
}
|
|
46
|
-
function setReporterLevel(ctx, level) {
|
|
47
|
-
const reporter = ctx.reporter;
|
|
48
|
-
reporter?.setLevel?.(level);
|
|
49
|
-
}
|
|
50
|
-
function setReporterFlight(ctx, flight) {
|
|
51
|
-
const reporter = ctx.reporter;
|
|
52
|
-
reporter?.setFlight?.(flight);
|
|
53
|
-
}
|
|
54
|
-
const logLevelOption = {
|
|
55
|
-
long: 'logLevel',
|
|
56
|
-
type: 'string',
|
|
57
|
-
args: 'required',
|
|
58
|
-
desc: 'Set log level',
|
|
59
|
-
default: 'info',
|
|
60
|
-
choices: [...BUILTIN_LOG_LEVELS],
|
|
61
|
-
coerce: (raw) => {
|
|
62
|
-
const level = resolveReporterLogLevel(raw);
|
|
63
|
-
if (level === undefined) {
|
|
64
|
-
throw new Error(`Invalid log level: ${raw}`);
|
|
65
|
-
}
|
|
66
|
-
return level;
|
|
67
|
-
},
|
|
68
|
-
apply: (value, ctx) => {
|
|
69
|
-
setReporterLevel(ctx, value);
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
const logDateOption = {
|
|
73
|
-
long: 'logDate',
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
args: 'none',
|
|
76
|
-
desc: 'Enable log timestamp',
|
|
77
|
-
default: true,
|
|
78
|
-
apply: (value, ctx) => {
|
|
79
|
-
setReporterFlight(ctx, { date: Boolean(value) });
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
const logColorfulOption = {
|
|
83
|
-
long: 'logColorful',
|
|
84
|
-
type: 'boolean',
|
|
85
|
-
args: 'none',
|
|
86
|
-
desc: 'Enable colorful log output',
|
|
87
|
-
default: true,
|
|
88
|
-
apply: (value, ctx) => {
|
|
89
|
-
setReporterFlight(ctx, { color: Boolean(value) });
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
const silentOption = {
|
|
93
|
-
long: 'silent',
|
|
94
|
-
type: 'boolean',
|
|
95
|
-
args: 'none',
|
|
96
|
-
desc: 'Suppress non-error output',
|
|
97
|
-
default: false,
|
|
98
|
-
apply: (value, ctx) => {
|
|
99
|
-
if (value) {
|
|
100
|
-
setReporterLevel(ctx, 'error');
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
25
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
104
26
|
|
|
105
27
|
class CommanderError extends Error {
|
|
106
28
|
kind;
|
|
@@ -118,11 +40,6 @@ class CommanderError extends Error {
|
|
|
118
40
|
|
|
119
41
|
const LONG_OPTION_REGEX = /^--[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
120
42
|
const NEGATIVE_OPTION_REGEX = /^--no-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
121
|
-
const PRESET_OPTS_FLAG = '--preset-opts';
|
|
122
|
-
const PRESET_ENVS_FLAG = '--preset-envs';
|
|
123
|
-
const PRESET_ROOT_FLAG = '--preset-root';
|
|
124
|
-
const DEFAULT_PRESET_OPTS_FILENAME = '.opt.local';
|
|
125
|
-
const DEFAULT_PRESET_ENVS_FILENAME = '.env.local';
|
|
126
43
|
function kebabToCamelCase(str) {
|
|
127
44
|
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
128
45
|
}
|
|
@@ -210,91 +127,27 @@ function tokenize(argv, commandPath) {
|
|
|
210
127
|
}
|
|
211
128
|
const BUILTIN_HELP_OPTION = {
|
|
212
129
|
long: 'help',
|
|
130
|
+
short: 'h',
|
|
213
131
|
type: 'boolean',
|
|
214
132
|
args: 'none',
|
|
215
133
|
desc: 'Show help information',
|
|
216
134
|
};
|
|
217
135
|
const BUILTIN_VERSION_OPTION = {
|
|
218
136
|
long: 'version',
|
|
137
|
+
short: 'V',
|
|
219
138
|
type: 'boolean',
|
|
220
139
|
args: 'none',
|
|
221
140
|
desc: 'Show version number',
|
|
222
141
|
};
|
|
223
|
-
const BUILTIN_COLOR_OPTION = {
|
|
224
|
-
long: 'color',
|
|
225
|
-
type: 'boolean',
|
|
226
|
-
args: 'none',
|
|
227
|
-
desc: 'Enable colored help output',
|
|
228
|
-
default: true,
|
|
229
|
-
};
|
|
230
|
-
function createBuiltinOptionState(enabled) {
|
|
231
|
-
return {
|
|
232
|
-
version: enabled,
|
|
233
|
-
color: enabled,
|
|
234
|
-
logLevel: enabled,
|
|
235
|
-
silent: enabled,
|
|
236
|
-
logDate: enabled,
|
|
237
|
-
logColorful: enabled,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
function isNoColorEnabled(envs) {
|
|
241
|
-
return envs['NO_COLOR'] !== undefined;
|
|
242
|
-
}
|
|
243
|
-
function normalizeBuiltinConfig(builtin) {
|
|
244
|
-
const resolved = {
|
|
245
|
-
option: createBuiltinOptionState(true),
|
|
246
|
-
};
|
|
247
|
-
if (builtin === undefined) {
|
|
248
|
-
return resolved;
|
|
249
|
-
}
|
|
250
|
-
if (builtin === true) {
|
|
251
|
-
return {
|
|
252
|
-
option: createBuiltinOptionState(true),
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
if (builtin === false) {
|
|
256
|
-
return {
|
|
257
|
-
option: createBuiltinOptionState(false),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
if (builtin.option !== undefined) {
|
|
261
|
-
if (builtin.option === false) {
|
|
262
|
-
resolved.option = createBuiltinOptionState(false);
|
|
263
|
-
}
|
|
264
|
-
else if (builtin.option === true) {
|
|
265
|
-
resolved.option = createBuiltinOptionState(true);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
if (builtin.option.version !== undefined)
|
|
269
|
-
resolved.option.version = builtin.option.version;
|
|
270
|
-
if (builtin.option.color !== undefined)
|
|
271
|
-
resolved.option.color = builtin.option.color;
|
|
272
|
-
if (builtin.option.logLevel !== undefined) {
|
|
273
|
-
resolved.option.logLevel = builtin.option.logLevel;
|
|
274
|
-
}
|
|
275
|
-
if (builtin.option.silent !== undefined)
|
|
276
|
-
resolved.option.silent = builtin.option.silent;
|
|
277
|
-
if (builtin.option.logDate !== undefined)
|
|
278
|
-
resolved.option.logDate = builtin.option.logDate;
|
|
279
|
-
if (builtin.option.logColorful !== undefined) {
|
|
280
|
-
resolved.option.logColorful = builtin.option.logColorful;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return resolved;
|
|
285
|
-
}
|
|
286
142
|
class Command {
|
|
287
143
|
#name;
|
|
288
144
|
#desc;
|
|
289
145
|
#version;
|
|
290
|
-
#
|
|
291
|
-
#builtin;
|
|
292
|
-
#presetConfig;
|
|
146
|
+
#helpSubcommandEnabled;
|
|
293
147
|
#reporter;
|
|
294
148
|
#parent;
|
|
295
149
|
#options = [];
|
|
296
150
|
#arguments = [];
|
|
297
|
-
#examples = [];
|
|
298
151
|
#subcommandsList = [];
|
|
299
152
|
#subcommandsMap = new Map();
|
|
300
153
|
#action = undefined;
|
|
@@ -302,9 +155,7 @@ class Command {
|
|
|
302
155
|
this.#name = config.name ?? '';
|
|
303
156
|
this.#desc = config.desc;
|
|
304
157
|
this.#version = config.version;
|
|
305
|
-
this.#
|
|
306
|
-
this.#builtin = normalizeBuiltinConfig(config.builtin);
|
|
307
|
-
this.#presetConfig = config.preset;
|
|
158
|
+
this.#helpSubcommandEnabled = config.help ?? false;
|
|
308
159
|
this.#reporter = config.reporter;
|
|
309
160
|
}
|
|
310
161
|
get name() {
|
|
@@ -316,12 +167,6 @@ class Command {
|
|
|
316
167
|
get version() {
|
|
317
168
|
return this.#version;
|
|
318
169
|
}
|
|
319
|
-
get builtin() {
|
|
320
|
-
return this.#builtinConfig;
|
|
321
|
-
}
|
|
322
|
-
get preset() {
|
|
323
|
-
return this.#presetConfig === undefined ? undefined : { ...this.#presetConfig };
|
|
324
|
-
}
|
|
325
170
|
get parent() {
|
|
326
171
|
return this.#parent;
|
|
327
172
|
}
|
|
@@ -331,9 +176,6 @@ class Command {
|
|
|
331
176
|
get arguments() {
|
|
332
177
|
return [...this.#arguments];
|
|
333
178
|
}
|
|
334
|
-
get examples() {
|
|
335
|
-
return this.#examples.map(example => ({ ...example }));
|
|
336
|
-
}
|
|
337
179
|
get subcommands() {
|
|
338
180
|
return new Map(this.#subcommandsMap);
|
|
339
181
|
}
|
|
@@ -352,22 +194,15 @@ class Command {
|
|
|
352
194
|
this.#action = fn;
|
|
353
195
|
return this;
|
|
354
196
|
}
|
|
355
|
-
example(title, usage, desc) {
|
|
356
|
-
this.#examples.push(this.#normalizeExample({ title, usage, desc }));
|
|
357
|
-
return this;
|
|
358
|
-
}
|
|
359
197
|
subcommand(name, cmd) {
|
|
360
|
-
if (name === 'help') {
|
|
361
|
-
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name', this.#getCommandPath());
|
|
198
|
+
if (this.#helpSubcommandEnabled && name === 'help') {
|
|
199
|
+
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
|
|
362
200
|
}
|
|
363
201
|
if (cmd.#parent && cmd.#parent !== this) {
|
|
364
202
|
throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
|
|
365
203
|
}
|
|
366
204
|
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
367
205
|
if (existing) {
|
|
368
|
-
if (existing.aliases.includes(name)) {
|
|
369
|
-
return this;
|
|
370
|
-
}
|
|
371
206
|
existing.aliases.push(name);
|
|
372
207
|
this.#subcommandsMap.set(name, cmd);
|
|
373
208
|
}
|
|
@@ -380,38 +215,35 @@ class Command {
|
|
|
380
215
|
return this;
|
|
381
216
|
}
|
|
382
217
|
async run(params) {
|
|
383
|
-
const { argv, envs, reporter } = params;
|
|
218
|
+
const { argv, envs, reporter: reporter$1 } = params;
|
|
384
219
|
try {
|
|
385
|
-
const
|
|
386
|
-
const
|
|
220
|
+
const processedArgv = this.#processHelpSubcommand(argv);
|
|
221
|
+
const routeResult = this.#route(processedArgv);
|
|
222
|
+
const { chain, remaining } = routeResult;
|
|
387
223
|
const leafCommand = chain[chain.length - 1];
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
ctx.controls = controlScanResult.controls;
|
|
396
|
-
ctx.sources.user.argv = [...controlScanResult.remaining];
|
|
397
|
-
if (ctx.controls.help) {
|
|
398
|
-
const helpCommand = this.#resolveHelpCommand(leafCommand, controlScanResult.helpTarget);
|
|
399
|
-
const helpColor = helpCommand.#resolveHelpColorFromTailArgv(controlScanResult.remaining, ctx.envs);
|
|
400
|
-
console.log(helpCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
224
|
+
const rootCommand = chain[0];
|
|
225
|
+
const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
|
|
226
|
+
const { optionTokens, restArgs } = tokenizeResult;
|
|
227
|
+
const hasUserHelp = leafCommand.#options.some(o => o.long === 'help');
|
|
228
|
+
const hasUserVersion = leafCommand.#options.some(o => o.long === 'version');
|
|
229
|
+
if (!hasUserHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
|
|
230
|
+
console.log(leafCommand.formatHelp());
|
|
401
231
|
return;
|
|
402
232
|
}
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
233
|
+
if (!hasUserVersion && leafCommand === rootCommand && leafCommand.#version) {
|
|
234
|
+
if (this.#hasFlag(optionTokens, 'version', 'V')) {
|
|
235
|
+
console.log(leafCommand.#version);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
406
238
|
}
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const parseResult = this.#parse(chain, resolveResult,
|
|
239
|
+
const resolveResult = this.#resolve(chain, optionTokens);
|
|
240
|
+
const ctx = {
|
|
241
|
+
cmd: leafCommand,
|
|
242
|
+
envs,
|
|
243
|
+
reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
|
|
244
|
+
argv,
|
|
245
|
+
};
|
|
246
|
+
const parseResult = this.#parse(chain, resolveResult, ctx, restArgs);
|
|
415
247
|
const actionParams = {
|
|
416
248
|
ctx: parseResult.ctx,
|
|
417
249
|
opts: parseResult.opts,
|
|
@@ -422,8 +254,7 @@ class Command {
|
|
|
422
254
|
await leafCommand.#runAction(actionParams);
|
|
423
255
|
}
|
|
424
256
|
else if (leafCommand.#subcommandsList.length > 0) {
|
|
425
|
-
|
|
426
|
-
console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
257
|
+
console.log(leafCommand.formatHelp());
|
|
427
258
|
}
|
|
428
259
|
else {
|
|
429
260
|
throw new CommanderError('ConfigurationError', `no action defined for command "${leafCommand.#getCommandPath()}"`, leafCommand.#getCommandPath());
|
|
@@ -438,49 +269,28 @@ class Command {
|
|
|
438
269
|
throw err;
|
|
439
270
|
}
|
|
440
271
|
}
|
|
441
|
-
|
|
442
|
-
const { argv, envs, reporter } = params;
|
|
443
|
-
const
|
|
444
|
-
const
|
|
272
|
+
parse(params) {
|
|
273
|
+
const { argv, envs, reporter: reporter$1 } = params;
|
|
274
|
+
const processedArgv = this.#processHelpSubcommand(argv);
|
|
275
|
+
const routeResult = this.#route(processedArgv);
|
|
276
|
+
const { chain, remaining } = routeResult;
|
|
445
277
|
const leafCommand = chain[chain.length - 1];
|
|
446
|
-
const
|
|
447
|
-
chain,
|
|
448
|
-
cmds: routeResult.cmds,
|
|
449
|
-
envs,
|
|
450
|
-
reporter,
|
|
451
|
-
});
|
|
452
|
-
const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
|
|
453
|
-
ctx.controls = controlScanResult.controls;
|
|
454
|
-
ctx.sources.user.argv = [...controlScanResult.remaining];
|
|
455
|
-
const optionPolicyMap = this.#buildOptionPolicyMap(chain);
|
|
456
|
-
const presetResult = await this.#preset(controlScanResult.remaining, ctx, optionPolicyMap);
|
|
457
|
-
ctx.sources = presetResult.sources;
|
|
458
|
-
ctx.envs = presetResult.envs;
|
|
459
|
-
const tokenizeResult = tokenize(presetResult.tailArgv, leafCommand.#getCommandPath());
|
|
278
|
+
const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
|
|
460
279
|
const { optionTokens, restArgs } = tokenizeResult;
|
|
461
|
-
const resolveResult = this.#resolve(chain, optionTokens
|
|
462
|
-
|
|
280
|
+
const resolveResult = this.#resolve(chain, optionTokens);
|
|
281
|
+
const ctx = {
|
|
282
|
+
cmd: leafCommand,
|
|
283
|
+
envs,
|
|
284
|
+
reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
|
|
285
|
+
argv,
|
|
286
|
+
};
|
|
287
|
+
return this.#parse(chain, resolveResult, ctx, restArgs);
|
|
463
288
|
}
|
|
464
289
|
formatHelp() {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const helpData = this.#buildHelpData();
|
|
470
|
-
if (!this.#shouldRenderStyledHelp(color)) {
|
|
471
|
-
return this.#renderHelpPlain(helpData);
|
|
472
|
-
}
|
|
473
|
-
return this.#renderHelpTerminal(helpData);
|
|
474
|
-
}
|
|
475
|
-
#shouldRenderStyledHelp(color) {
|
|
476
|
-
return color && process.stdout.isTTY === true;
|
|
477
|
-
}
|
|
478
|
-
#buildHelpData() {
|
|
479
|
-
const parseOptions = this.#resolveOptionPolicy().mergedOptions;
|
|
480
|
-
const allOptions = [...parseOptions, BUILTIN_HELP_OPTION];
|
|
481
|
-
if (this.#supportsBuiltinVersion()) {
|
|
482
|
-
allOptions.push(BUILTIN_VERSION_OPTION);
|
|
483
|
-
}
|
|
290
|
+
const lines = [];
|
|
291
|
+
const allOptions = this.#getMergedOptions();
|
|
292
|
+
lines.push(this.#desc);
|
|
293
|
+
lines.push('');
|
|
484
294
|
const commandPath = this.#getCommandPath();
|
|
485
295
|
let usage = `Usage: ${commandPath}`;
|
|
486
296
|
if (allOptions.length > 0)
|
|
@@ -498,128 +308,65 @@ class Command {
|
|
|
498
308
|
usage += ` [${arg.name}...]`;
|
|
499
309
|
}
|
|
500
310
|
}
|
|
501
|
-
|
|
502
|
-
for (const opt of allOptions) {
|
|
503
|
-
const kebabLong = camelToKebabCase$1(opt.long);
|
|
504
|
-
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
505
|
-
sig += `--${kebabLong}`;
|
|
506
|
-
if (opt.args !== 'none') {
|
|
507
|
-
sig += ' <value>';
|
|
508
|
-
}
|
|
509
|
-
let desc = opt.desc;
|
|
510
|
-
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
511
|
-
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
512
|
-
}
|
|
513
|
-
if (opt.choices) {
|
|
514
|
-
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
515
|
-
}
|
|
516
|
-
options.push({ sig, desc });
|
|
517
|
-
if (opt.type === 'boolean' &&
|
|
518
|
-
opt.args === 'none' &&
|
|
519
|
-
opt.long !== 'help' &&
|
|
520
|
-
opt.long !== 'version') {
|
|
521
|
-
options.push({
|
|
522
|
-
sig: ` --no-${kebabLong}`,
|
|
523
|
-
desc: `Negate --${kebabLong}`,
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
const commands = [];
|
|
528
|
-
if (this.#subcommandsList.length > 0) {
|
|
529
|
-
commands.push({ name: 'help', desc: 'Show help for a command' });
|
|
530
|
-
}
|
|
531
|
-
for (const entry of this.#subcommandsList) {
|
|
532
|
-
let name = entry.name;
|
|
533
|
-
if (entry.aliases.length > 0) {
|
|
534
|
-
name += `, ${entry.aliases.join(', ')}`;
|
|
535
|
-
}
|
|
536
|
-
commands.push({ name, desc: entry.command.#desc });
|
|
537
|
-
}
|
|
538
|
-
const examples = this.#examples.map(example => ({
|
|
539
|
-
title: example.title,
|
|
540
|
-
usage: commandPath ? `${commandPath} ${example.usage}` : example.usage,
|
|
541
|
-
desc: example.desc,
|
|
542
|
-
}));
|
|
543
|
-
return {
|
|
544
|
-
desc: this.#desc,
|
|
545
|
-
usage,
|
|
546
|
-
options,
|
|
547
|
-
commands,
|
|
548
|
-
examples,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
#renderHelpPlain(helpData) {
|
|
552
|
-
const lines = [];
|
|
553
|
-
lines.push(helpData.desc);
|
|
554
|
-
lines.push('');
|
|
555
|
-
lines.push(helpData.usage);
|
|
311
|
+
lines.push(usage);
|
|
556
312
|
lines.push('');
|
|
557
|
-
if (
|
|
313
|
+
if (allOptions.length > 0) {
|
|
558
314
|
lines.push('Options:');
|
|
559
|
-
const
|
|
560
|
-
for (const
|
|
315
|
+
const optLines = [];
|
|
316
|
+
for (const opt of allOptions) {
|
|
317
|
+
const kebabLong = camelToKebabCase$1(opt.long);
|
|
318
|
+
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
319
|
+
sig += `--${kebabLong}`;
|
|
320
|
+
if (opt.args !== 'none') {
|
|
321
|
+
sig += ' <value>';
|
|
322
|
+
}
|
|
323
|
+
let desc = opt.desc;
|
|
324
|
+
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
325
|
+
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
326
|
+
}
|
|
327
|
+
if (opt.choices) {
|
|
328
|
+
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
329
|
+
}
|
|
330
|
+
optLines.push({ sig, desc });
|
|
331
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
332
|
+
optLines.push({
|
|
333
|
+
sig: ` --no-${kebabLong}`,
|
|
334
|
+
desc: `Negate --${kebabLong}`,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const maxSigLen = Math.max(...optLines.map(l => l.sig.length));
|
|
339
|
+
for (const { sig, desc } of optLines) {
|
|
561
340
|
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
562
341
|
lines.push(` ${sig}${padding}${desc}`);
|
|
563
342
|
}
|
|
564
343
|
lines.push('');
|
|
565
344
|
}
|
|
566
|
-
|
|
345
|
+
const showHelpSubcommand = this.#helpSubcommandEnabled && this.#subcommandsList.length > 0;
|
|
346
|
+
if (this.#subcommandsList.length > 0) {
|
|
567
347
|
lines.push('Commands:');
|
|
568
|
-
const
|
|
569
|
-
|
|
348
|
+
const cmdLines = [];
|
|
349
|
+
if (showHelpSubcommand) {
|
|
350
|
+
cmdLines.push({ name: 'help', desc: 'Show help for a command' });
|
|
351
|
+
}
|
|
352
|
+
for (const entry of this.#subcommandsList) {
|
|
353
|
+
let name = entry.name;
|
|
354
|
+
if (entry.aliases.length > 0) {
|
|
355
|
+
name += `, ${entry.aliases.join(', ')}`;
|
|
356
|
+
}
|
|
357
|
+
cmdLines.push({ name, desc: entry.command.#desc });
|
|
358
|
+
}
|
|
359
|
+
const maxNameLen = Math.max(...cmdLines.map(l => l.name.length));
|
|
360
|
+
for (const { name, desc } of cmdLines) {
|
|
570
361
|
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
571
362
|
lines.push(` ${name}${padding}${desc}`);
|
|
572
363
|
}
|
|
573
364
|
lines.push('');
|
|
574
365
|
}
|
|
575
|
-
if (helpData.examples.length > 0) {
|
|
576
|
-
lines.push('Examples:');
|
|
577
|
-
for (const example of helpData.examples) {
|
|
578
|
-
lines.push(` - ${example.title}`);
|
|
579
|
-
lines.push(` ${example.usage}`);
|
|
580
|
-
lines.push(` ${example.desc}`);
|
|
581
|
-
lines.push('');
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
return lines.join('\n');
|
|
585
|
-
}
|
|
586
|
-
#renderHelpTerminal(helpData) {
|
|
587
|
-
const lines = [];
|
|
588
|
-
lines.push(helpData.desc);
|
|
589
|
-
lines.push('');
|
|
590
|
-
lines.push(styleText(helpData.usage, TERMINAL_STYLE.bold));
|
|
591
|
-
lines.push('');
|
|
592
|
-
if (helpData.options.length > 0) {
|
|
593
|
-
lines.push(styleText('Options:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
594
|
-
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
595
|
-
for (const { sig, desc } of helpData.options) {
|
|
596
|
-
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
597
|
-
lines.push(` ${styleText(sig, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
598
|
-
}
|
|
599
|
-
lines.push('');
|
|
600
|
-
}
|
|
601
|
-
if (helpData.commands.length > 0) {
|
|
602
|
-
lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
603
|
-
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
604
|
-
for (const { name, desc } of helpData.commands) {
|
|
605
|
-
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
606
|
-
lines.push(` ${styleText(name, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
607
|
-
}
|
|
608
|
-
lines.push('');
|
|
609
|
-
}
|
|
610
|
-
if (helpData.examples.length > 0) {
|
|
611
|
-
lines.push(styleText('Examples:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
612
|
-
for (const example of helpData.examples) {
|
|
613
|
-
lines.push(` - ${styleText(example.title, TERMINAL_STYLE.bold)}`);
|
|
614
|
-
lines.push(` ${styleText(example.usage, TERMINAL_STYLE.cyan)}`);
|
|
615
|
-
lines.push(` ${styleText(example.desc, TERMINAL_STYLE.italic, TERMINAL_STYLE.dim)}`);
|
|
616
|
-
lines.push('');
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
366
|
return lines.join('\n');
|
|
620
367
|
}
|
|
621
368
|
getCompletionMeta() {
|
|
622
|
-
const allOptions = this.#
|
|
369
|
+
const allOptions = this.#getMergedOptions();
|
|
623
370
|
const options = [];
|
|
624
371
|
for (const opt of allOptions) {
|
|
625
372
|
options.push({
|
|
@@ -645,407 +392,46 @@ class Command {
|
|
|
645
392
|
}),
|
|
646
393
|
};
|
|
647
394
|
}
|
|
648
|
-
#
|
|
649
|
-
|
|
395
|
+
#processHelpSubcommand(argv) {
|
|
396
|
+
if (!this.#helpSubcommandEnabled)
|
|
397
|
+
return argv;
|
|
398
|
+
if (argv.length < 1 || argv[0] !== 'help')
|
|
399
|
+
return argv;
|
|
400
|
+
if (argv.length === 1 || this.#subcommandsList.length === 0) {
|
|
401
|
+
return ['--help'];
|
|
402
|
+
}
|
|
403
|
+
const subName = argv[1];
|
|
404
|
+
const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
|
|
405
|
+
if (entry) {
|
|
406
|
+
return [subName, '--help', ...argv.slice(2)];
|
|
407
|
+
}
|
|
408
|
+
return argv;
|
|
650
409
|
}
|
|
651
410
|
#route(argv) {
|
|
652
411
|
const chain = [this];
|
|
653
|
-
const cmds = [];
|
|
654
412
|
let current = this;
|
|
655
413
|
let idx = 0;
|
|
656
414
|
while (idx < argv.length) {
|
|
657
415
|
const token = argv[idx];
|
|
658
416
|
if (token.startsWith('-'))
|
|
659
417
|
break;
|
|
660
|
-
const entry = current.#
|
|
418
|
+
const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
|
|
661
419
|
if (!entry)
|
|
662
420
|
break;
|
|
663
421
|
current = entry.command;
|
|
664
|
-
cmds.push(token);
|
|
665
422
|
chain.push(current);
|
|
666
423
|
idx += 1;
|
|
667
424
|
}
|
|
668
|
-
return { chain, remaining: argv.slice(idx)
|
|
669
|
-
}
|
|
670
|
-
#controlScan(tailArgv, leafCommand) {
|
|
671
|
-
const controls = { help: false, version: false };
|
|
672
|
-
const separatorIndex = tailArgv.indexOf('--');
|
|
673
|
-
const beforeSeparator = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
|
|
674
|
-
const afterSeparator = separatorIndex === -1 ? [] : tailArgv.slice(separatorIndex + 1);
|
|
675
|
-
let helpTarget;
|
|
676
|
-
let scanStartIndex = 0;
|
|
677
|
-
if (beforeSeparator[0] === 'help') {
|
|
678
|
-
controls.help = true;
|
|
679
|
-
scanStartIndex = 1;
|
|
680
|
-
const candidate = beforeSeparator[1];
|
|
681
|
-
if (candidate !== undefined && !candidate.startsWith('-')) {
|
|
682
|
-
helpTarget = candidate;
|
|
683
|
-
scanStartIndex = 2;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
const remainingBeforeSeparator = [];
|
|
687
|
-
for (let i = scanStartIndex; i < beforeSeparator.length; i += 1) {
|
|
688
|
-
const token = beforeSeparator[i];
|
|
689
|
-
if (token === '--help') {
|
|
690
|
-
controls.help = true;
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
if (token === '--version' && leafCommand.#supportsBuiltinVersion()) {
|
|
694
|
-
controls.version = true;
|
|
695
|
-
continue;
|
|
696
|
-
}
|
|
697
|
-
remainingBeforeSeparator.push(token);
|
|
698
|
-
}
|
|
699
|
-
const remaining = separatorIndex === -1
|
|
700
|
-
? remainingBeforeSeparator
|
|
701
|
-
: [...remainingBeforeSeparator, '--', ...afterSeparator];
|
|
702
|
-
return {
|
|
703
|
-
controls,
|
|
704
|
-
remaining,
|
|
705
|
-
helpTarget,
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
#createContext(params) {
|
|
709
|
-
const { chain, cmds, envs, reporter: reporter$1 } = params;
|
|
710
|
-
const leafCommand = chain[chain.length - 1];
|
|
711
|
-
const envSnapshot = { ...envs };
|
|
712
|
-
return {
|
|
713
|
-
cmd: leafCommand,
|
|
714
|
-
chain,
|
|
715
|
-
envs: envSnapshot,
|
|
716
|
-
controls: { help: false, version: false },
|
|
717
|
-
sources: {
|
|
718
|
-
preset: {
|
|
719
|
-
argv: [],
|
|
720
|
-
envs: {},
|
|
721
|
-
},
|
|
722
|
-
user: {
|
|
723
|
-
cmds: [...cmds],
|
|
724
|
-
argv: [],
|
|
725
|
-
envs: envSnapshot,
|
|
726
|
-
},
|
|
727
|
-
},
|
|
728
|
-
reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
#resolveHelpCommand(leafCommand, helpTarget) {
|
|
732
|
-
if (helpTarget === undefined) {
|
|
733
|
-
return leafCommand;
|
|
734
|
-
}
|
|
735
|
-
const target = leafCommand.#findSubcommandEntry(helpTarget);
|
|
736
|
-
if (target === undefined) {
|
|
737
|
-
return leafCommand;
|
|
738
|
-
}
|
|
739
|
-
return target.command;
|
|
740
|
-
}
|
|
741
|
-
async #preset(controlTailArgv, ctx, optionPolicyMap) {
|
|
742
|
-
const commandPath = ctx.chain[ctx.chain.length - 1].#getCommandPath();
|
|
743
|
-
const separatorIndex = controlTailArgv.indexOf('--');
|
|
744
|
-
const beforeSeparator = separatorIndex === -1 ? controlTailArgv : controlTailArgv.slice(0, separatorIndex);
|
|
745
|
-
const afterSeparator = separatorIndex === -1 ? [] : controlTailArgv.slice(separatorIndex + 1);
|
|
746
|
-
const rootScanResult = this.#scanPresetRootDirectives(beforeSeparator, commandPath);
|
|
747
|
-
const commandPreset = this.#resolveCommandPresetFromChain(ctx.chain);
|
|
748
|
-
const presetRoot = await this.#resolveEffectivePresetRoot(rootScanResult.cliPresetRoots, commandPreset, commandPath);
|
|
749
|
-
const fileScanResult = this.#scanPresetFileDirectives(rootScanResult.cleanArgv, commandPath);
|
|
750
|
-
const cleanArgv = separatorIndex === -1
|
|
751
|
-
? fileScanResult.cleanArgv
|
|
752
|
-
: [...fileScanResult.cleanArgv, '--', ...afterSeparator];
|
|
753
|
-
const presetOptsFiles = this.#resolvePresetFileSources({
|
|
754
|
-
cliFiles: fileScanResult.cliPresetOptsFiles,
|
|
755
|
-
commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.opt),
|
|
756
|
-
presetRoot,
|
|
757
|
-
defaultFilename: DEFAULT_PRESET_OPTS_FILENAME,
|
|
758
|
-
});
|
|
759
|
-
const presetEnvsFiles = this.#resolvePresetFileSources({
|
|
760
|
-
cliFiles: fileScanResult.cliPresetEnvsFiles,
|
|
761
|
-
commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.env),
|
|
762
|
-
presetRoot,
|
|
763
|
-
defaultFilename: DEFAULT_PRESET_ENVS_FILENAME,
|
|
764
|
-
});
|
|
765
|
-
const userSources = {
|
|
766
|
-
cmds: [...ctx.sources.user.cmds],
|
|
767
|
-
argv: [...cleanArgv],
|
|
768
|
-
envs: { ...ctx.sources.user.envs },
|
|
769
|
-
};
|
|
770
|
-
const presetArgv = [];
|
|
771
|
-
for (const file of presetOptsFiles) {
|
|
772
|
-
const content = await this.#readPresetFile(file, commandPath);
|
|
773
|
-
if (content === undefined) {
|
|
774
|
-
continue;
|
|
775
|
-
}
|
|
776
|
-
const tokens = this.#tokenizePresetOptions(content);
|
|
777
|
-
this.#validatePresetOptionTokens(tokens, file.displayPath, commandPath);
|
|
778
|
-
this.#assertPresetOptionFragments(tokens, file.displayPath, ctx.chain, optionPolicyMap);
|
|
779
|
-
presetArgv.push(...tokens);
|
|
780
|
-
}
|
|
781
|
-
const presetEnvs = {};
|
|
782
|
-
for (const file of presetEnvsFiles) {
|
|
783
|
-
const content = await this.#readPresetFile(file, commandPath);
|
|
784
|
-
if (content === undefined) {
|
|
785
|
-
continue;
|
|
786
|
-
}
|
|
787
|
-
let parsed;
|
|
788
|
-
try {
|
|
789
|
-
parsed = env.parse(content);
|
|
790
|
-
}
|
|
791
|
-
catch (error) {
|
|
792
|
-
throw new CommanderError('ConfigurationError', `failed to parse preset envs file "${file.displayPath}": ${error.message}`, commandPath);
|
|
793
|
-
}
|
|
794
|
-
Object.assign(presetEnvs, parsed);
|
|
795
|
-
}
|
|
796
|
-
const sources = {
|
|
797
|
-
user: userSources,
|
|
798
|
-
preset: {
|
|
799
|
-
argv: presetArgv,
|
|
800
|
-
envs: presetEnvs,
|
|
801
|
-
},
|
|
802
|
-
};
|
|
803
|
-
const envs = { ...sources.user.envs, ...sources.preset.envs };
|
|
804
|
-
const tailArgv = [...sources.preset.argv, ...sources.user.argv];
|
|
805
|
-
return { tailArgv, envs, sources };
|
|
806
|
-
}
|
|
807
|
-
#resolveCommandPresetFromChain(chain) {
|
|
808
|
-
for (let index = chain.length - 1; index >= 0; index -= 1) {
|
|
809
|
-
const preset = chain[index].#presetConfig;
|
|
810
|
-
if (preset?.root !== undefined) {
|
|
811
|
-
return preset;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
return undefined;
|
|
815
|
-
}
|
|
816
|
-
async #resolveEffectivePresetRoot(cliPresetRoots, commandPreset, commandPath) {
|
|
817
|
-
if (cliPresetRoots.length > 0) {
|
|
818
|
-
const root = cliPresetRoots[cliPresetRoots.length - 1];
|
|
819
|
-
return await this.#assertPresetRoot(root, PRESET_ROOT_FLAG, commandPath);
|
|
820
|
-
}
|
|
821
|
-
if (commandPreset?.root === undefined) {
|
|
822
|
-
return undefined;
|
|
823
|
-
}
|
|
824
|
-
return await this.#assertPresetRoot(commandPreset.root, 'command.preset.root', commandPath);
|
|
825
|
-
}
|
|
826
|
-
async #assertPresetRoot(root, sourceName, commandPath) {
|
|
827
|
-
if (!path.isAbsolute(root)) {
|
|
828
|
-
throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not an absolute directory`, commandPath);
|
|
829
|
-
}
|
|
830
|
-
let stats;
|
|
831
|
-
try {
|
|
832
|
-
stats = await promises.stat(root);
|
|
833
|
-
}
|
|
834
|
-
catch (error) {
|
|
835
|
-
throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" cannot be accessed (${error.message})`, commandPath);
|
|
836
|
-
}
|
|
837
|
-
if (!stats.isDirectory()) {
|
|
838
|
-
throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not a directory`, commandPath);
|
|
839
|
-
}
|
|
840
|
-
return root;
|
|
425
|
+
return { chain, remaining: argv.slice(idx) };
|
|
841
426
|
}
|
|
842
|
-
#
|
|
843
|
-
if (filepath === undefined) {
|
|
844
|
-
return undefined;
|
|
845
|
-
}
|
|
846
|
-
if (!this.#isValidPresetFileValue(filepath)) {
|
|
847
|
-
return undefined;
|
|
848
|
-
}
|
|
849
|
-
return filepath;
|
|
850
|
-
}
|
|
851
|
-
#resolvePresetFileSources(params) {
|
|
852
|
-
const { cliFiles, commandPresetFile, presetRoot, defaultFilename } = params;
|
|
853
|
-
if (cliFiles.length > 0) {
|
|
854
|
-
return cliFiles.map(filepath => ({
|
|
855
|
-
displayPath: filepath,
|
|
856
|
-
absolutePath: this.#resolvePresetFileAbsolutePath(filepath, presetRoot),
|
|
857
|
-
explicit: true,
|
|
858
|
-
}));
|
|
859
|
-
}
|
|
860
|
-
if (presetRoot === undefined) {
|
|
861
|
-
return [];
|
|
862
|
-
}
|
|
863
|
-
if (commandPresetFile !== undefined) {
|
|
864
|
-
return [
|
|
865
|
-
{
|
|
866
|
-
displayPath: commandPresetFile,
|
|
867
|
-
absolutePath: this.#resolvePresetFileAbsolutePath(commandPresetFile, presetRoot),
|
|
868
|
-
explicit: true,
|
|
869
|
-
},
|
|
870
|
-
];
|
|
871
|
-
}
|
|
872
|
-
const absolutePath = path.resolve(presetRoot, defaultFilename);
|
|
873
|
-
return [
|
|
874
|
-
{
|
|
875
|
-
displayPath: absolutePath,
|
|
876
|
-
absolutePath,
|
|
877
|
-
explicit: false,
|
|
878
|
-
},
|
|
879
|
-
];
|
|
880
|
-
}
|
|
881
|
-
#resolvePresetFileAbsolutePath(filepath, presetRoot) {
|
|
882
|
-
if (path.isAbsolute(filepath)) {
|
|
883
|
-
return filepath;
|
|
884
|
-
}
|
|
885
|
-
if (presetRoot !== undefined) {
|
|
886
|
-
return path.resolve(presetRoot, filepath);
|
|
887
|
-
}
|
|
888
|
-
return path.resolve(process.cwd(), filepath);
|
|
889
|
-
}
|
|
890
|
-
#assertPresetOptionFragments(tokens, filepath, chain, optionPolicyMap) {
|
|
891
|
-
if (tokens.length === 0) {
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
const commandPath = chain[chain.length - 1].#getCommandPath();
|
|
895
|
-
try {
|
|
896
|
-
const { optionTokens, restArgs } = tokenize(tokens, commandPath);
|
|
897
|
-
void restArgs;
|
|
898
|
-
const { argTokens } = this.#resolve(chain, optionTokens, optionPolicyMap);
|
|
899
|
-
if (argTokens.length > 0) {
|
|
900
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": token "${argTokens[0].original}" cannot be resolved as an option fragment`, commandPath);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
catch (error) {
|
|
904
|
-
if (error instanceof CommanderError) {
|
|
905
|
-
if (error.kind === 'ConfigurationError') {
|
|
906
|
-
throw error;
|
|
907
|
-
}
|
|
908
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": ${error.message}`, commandPath);
|
|
909
|
-
}
|
|
910
|
-
throw error;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
#scanPresetRootDirectives(argv, commandPath) {
|
|
914
|
-
const cleanArgv = [];
|
|
915
|
-
const cliPresetRoots = [];
|
|
916
|
-
let index = 0;
|
|
917
|
-
while (index < argv.length) {
|
|
918
|
-
const token = argv[index];
|
|
919
|
-
if (token === PRESET_ROOT_FLAG) {
|
|
920
|
-
const value = argv[index + 1];
|
|
921
|
-
if (value === undefined || value.length === 0) {
|
|
922
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
|
|
923
|
-
}
|
|
924
|
-
cliPresetRoots.push(value);
|
|
925
|
-
index += 2;
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
if (token.startsWith(`${PRESET_ROOT_FLAG}=`)) {
|
|
929
|
-
const value = token.slice(PRESET_ROOT_FLAG.length + 1);
|
|
930
|
-
if (value.length === 0) {
|
|
931
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
|
|
932
|
-
}
|
|
933
|
-
cliPresetRoots.push(value);
|
|
934
|
-
index += 1;
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
cleanArgv.push(token);
|
|
938
|
-
index += 1;
|
|
939
|
-
}
|
|
940
|
-
return { cleanArgv, cliPresetRoots };
|
|
941
|
-
}
|
|
942
|
-
#scanPresetFileDirectives(argv, commandPath) {
|
|
943
|
-
const cleanArgv = [];
|
|
944
|
-
const cliPresetOptsFiles = [];
|
|
945
|
-
const cliPresetEnvsFiles = [];
|
|
946
|
-
const assertAndPush = (flag, value) => {
|
|
947
|
-
this.#assertPresetFileValue(value, flag, commandPath);
|
|
948
|
-
if (flag === PRESET_OPTS_FLAG) {
|
|
949
|
-
cliPresetOptsFiles.push(value);
|
|
950
|
-
}
|
|
951
|
-
else {
|
|
952
|
-
cliPresetEnvsFiles.push(value);
|
|
953
|
-
}
|
|
954
|
-
};
|
|
955
|
-
let index = 0;
|
|
956
|
-
while (index < argv.length) {
|
|
957
|
-
const token = argv[index];
|
|
958
|
-
if (token === PRESET_OPTS_FLAG || token === PRESET_ENVS_FLAG) {
|
|
959
|
-
const value = argv[index + 1];
|
|
960
|
-
if (value === undefined || value.length === 0) {
|
|
961
|
-
throw new CommanderError('ConfigurationError', `missing value for "${token}"`, commandPath);
|
|
962
|
-
}
|
|
963
|
-
assertAndPush(token, value);
|
|
964
|
-
index += 2;
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
if (token.startsWith(`${PRESET_OPTS_FLAG}=`)) {
|
|
968
|
-
const value = token.slice(PRESET_OPTS_FLAG.length + 1);
|
|
969
|
-
if (value.length === 0) {
|
|
970
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_OPTS_FLAG}"`, commandPath);
|
|
971
|
-
}
|
|
972
|
-
assertAndPush(PRESET_OPTS_FLAG, value);
|
|
973
|
-
index += 1;
|
|
974
|
-
continue;
|
|
975
|
-
}
|
|
976
|
-
if (token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
|
|
977
|
-
const value = token.slice(PRESET_ENVS_FLAG.length + 1);
|
|
978
|
-
if (value.length === 0) {
|
|
979
|
-
throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ENVS_FLAG}"`, commandPath);
|
|
980
|
-
}
|
|
981
|
-
assertAndPush(PRESET_ENVS_FLAG, value);
|
|
982
|
-
index += 1;
|
|
983
|
-
continue;
|
|
984
|
-
}
|
|
985
|
-
cleanArgv.push(token);
|
|
986
|
-
index += 1;
|
|
987
|
-
}
|
|
988
|
-
return { cleanArgv, cliPresetOptsFiles, cliPresetEnvsFiles };
|
|
989
|
-
}
|
|
990
|
-
#isValidPresetFileValue(filepath) {
|
|
991
|
-
return filepath.length > 0 && !filepath.startsWith('..');
|
|
992
|
-
}
|
|
993
|
-
#assertPresetFileValue(filepath, directive, commandPath) {
|
|
994
|
-
if (this.#isValidPresetFileValue(filepath)) {
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
throw new CommanderError('ConfigurationError', `invalid value for "${directive}": "${filepath}" (must be non-empty and must not start with "..")`, commandPath);
|
|
998
|
-
}
|
|
999
|
-
async #readPresetFile(file, commandPath) {
|
|
1000
|
-
try {
|
|
1001
|
-
return await promises.readFile(file.absolutePath, 'utf8');
|
|
1002
|
-
}
|
|
1003
|
-
catch (error) {
|
|
1004
|
-
const ioError = error;
|
|
1005
|
-
if (!file.explicit && ioError.code === 'ENOENT') {
|
|
1006
|
-
return undefined;
|
|
1007
|
-
}
|
|
1008
|
-
throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${ioError.message}`, commandPath);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
#tokenizePresetOptions(content) {
|
|
1012
|
-
return content
|
|
1013
|
-
.split(/\s+/)
|
|
1014
|
-
.map(token => token.trim())
|
|
1015
|
-
.filter(token => token.length > 0);
|
|
1016
|
-
}
|
|
1017
|
-
#validatePresetOptionTokens(tokens, filepath, commandPath) {
|
|
1018
|
-
if (tokens.length === 0) {
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
1021
|
-
if (!tokens[0].startsWith('-')) {
|
|
1022
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": bare token "${tokens[0]}" cannot appear before any option token`, commandPath);
|
|
1023
|
-
}
|
|
1024
|
-
for (const token of tokens) {
|
|
1025
|
-
if (token === '--') {
|
|
1026
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": "--" is not allowed`, commandPath);
|
|
1027
|
-
}
|
|
1028
|
-
if (token === 'help' || token === '--help' || token === '--version') {
|
|
1029
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": control token "${token}" is not allowed`, commandPath);
|
|
1030
|
-
}
|
|
1031
|
-
if (token === PRESET_ROOT_FLAG ||
|
|
1032
|
-
token.startsWith(`${PRESET_ROOT_FLAG}=`) ||
|
|
1033
|
-
token === PRESET_OPTS_FLAG ||
|
|
1034
|
-
token.startsWith(`${PRESET_OPTS_FLAG}=`) ||
|
|
1035
|
-
token === PRESET_ENVS_FLAG ||
|
|
1036
|
-
token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
|
|
1037
|
-
throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": preset directive "${token}" is not allowed`, commandPath);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
#resolve(chain, tokens, optionPolicyMap) {
|
|
427
|
+
#resolve(chain, tokens) {
|
|
1042
428
|
const consumedTokens = new Map();
|
|
1043
429
|
let remaining = [...tokens];
|
|
1044
430
|
const shadowed = new Set();
|
|
1045
431
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
1046
432
|
const cmd = chain[i];
|
|
1047
|
-
const
|
|
1048
|
-
const result = cmd.#shift(remaining, shadowed,
|
|
433
|
+
const includeVersion = i === 0;
|
|
434
|
+
const result = cmd.#shift(remaining, shadowed, includeVersion);
|
|
1049
435
|
consumedTokens.set(cmd, result.consumed);
|
|
1050
436
|
remaining = result.remaining;
|
|
1051
437
|
for (const opt of cmd.#options) {
|
|
@@ -1062,7 +448,8 @@ class Command {
|
|
|
1062
448
|
}
|
|
1063
449
|
return { consumedTokens, argTokens };
|
|
1064
450
|
}
|
|
1065
|
-
#shift(tokens, shadowed,
|
|
451
|
+
#shift(tokens, shadowed, includeVersion) {
|
|
452
|
+
const allOptions = this.#getMergedOptions(includeVersion);
|
|
1066
453
|
const effectiveOptions = allOptions.filter(o => !shadowed.has(o.long));
|
|
1067
454
|
const optionByLong = new Map();
|
|
1068
455
|
const optionByShort = new Map();
|
|
@@ -1130,40 +517,34 @@ class Command {
|
|
|
1130
517
|
}
|
|
1131
518
|
return { consumed, remaining };
|
|
1132
519
|
}
|
|
1133
|
-
#parse(chain, resolveResult,
|
|
520
|
+
#parse(chain, resolveResult, ctx, restArgs) {
|
|
1134
521
|
const { consumedTokens, argTokens } = resolveResult;
|
|
1135
522
|
const leafCommand = chain[chain.length - 1];
|
|
1136
|
-
this.#validateMergedShortOptions(chain
|
|
523
|
+
this.#validateMergedShortOptions(chain);
|
|
1137
524
|
const optsMap = new Map();
|
|
1138
|
-
for (
|
|
1139
|
-
const
|
|
525
|
+
for (let i = 0; i < chain.length; i++) {
|
|
526
|
+
const cmd = chain[i];
|
|
527
|
+
const includeVersion = i === 0;
|
|
1140
528
|
const tokens = consumedTokens.get(cmd) ?? [];
|
|
1141
|
-
const opts = cmd.#parseOptions(tokens,
|
|
529
|
+
const opts = cmd.#parseOptions(tokens, includeVersion);
|
|
1142
530
|
optsMap.set(cmd, opts);
|
|
1143
|
-
for (const opt of
|
|
531
|
+
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
1144
532
|
if (opt.apply && opts[opt.long] !== undefined) {
|
|
1145
533
|
opt.apply(opts[opt.long], ctx);
|
|
1146
534
|
}
|
|
1147
535
|
}
|
|
1148
536
|
}
|
|
1149
|
-
const
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
if (Object.prototype.hasOwnProperty.call(leafParsedOpts, opt.long)) {
|
|
1153
|
-
leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
|
|
1154
|
-
}
|
|
537
|
+
const mergedOpts = {};
|
|
538
|
+
for (const cmd of chain) {
|
|
539
|
+
Object.assign(mergedOpts, optsMap.get(cmd) ?? {});
|
|
1155
540
|
}
|
|
1156
541
|
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
1157
542
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
1158
|
-
|
|
1159
|
-
...ctx,
|
|
1160
|
-
sources: this.#freezeInputSources(ctx.sources),
|
|
1161
|
-
};
|
|
1162
|
-
return { ctx: parseCtx, opts: leafLocalOpts, args, rawArgs };
|
|
543
|
+
return { ctx, opts: mergedOpts, args, rawArgs };
|
|
1163
544
|
}
|
|
1164
|
-
#parseOptions(tokens,
|
|
545
|
+
#parseOptions(tokens, includeVersion) {
|
|
546
|
+
const allOptions = this.#getMergedOptions(includeVersion);
|
|
1165
547
|
const opts = {};
|
|
1166
|
-
let sawColorToken = false;
|
|
1167
548
|
for (const opt of allOptions) {
|
|
1168
549
|
if (opt.default !== undefined) {
|
|
1169
550
|
opts[opt.long] = opt.default;
|
|
@@ -1191,9 +572,6 @@ class Command {
|
|
|
1191
572
|
i += 1;
|
|
1192
573
|
continue;
|
|
1193
574
|
}
|
|
1194
|
-
if (opt.long === 'color') {
|
|
1195
|
-
sawColorToken = true;
|
|
1196
|
-
}
|
|
1197
575
|
const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
|
|
1198
576
|
if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
|
|
1199
577
|
throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
|
|
@@ -1270,9 +648,6 @@ class Command {
|
|
|
1270
648
|
}
|
|
1271
649
|
}
|
|
1272
650
|
}
|
|
1273
|
-
if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
|
|
1274
|
-
opts['color'] = false;
|
|
1275
|
-
}
|
|
1276
651
|
return opts;
|
|
1277
652
|
}
|
|
1278
653
|
#convertValue(opt, rawValue) {
|
|
@@ -1343,60 +718,27 @@ class Command {
|
|
|
1343
718
|
}
|
|
1344
719
|
return raw;
|
|
1345
720
|
}
|
|
1346
|
-
#
|
|
1347
|
-
return this.#options.some(option => option.long === long);
|
|
1348
|
-
}
|
|
1349
|
-
#supportsBuiltinVersion() {
|
|
1350
|
-
return this.#parent === undefined && this.#version !== undefined && this.#builtin.option.version;
|
|
1351
|
-
}
|
|
1352
|
-
#resolveOptionPolicy() {
|
|
721
|
+
#getMergedOptions(includeVersion = !this.#parent) {
|
|
1353
722
|
const optionMap = new Map();
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
const hasUserLogColorful = this.#hasUserOption('logColorful');
|
|
1359
|
-
if (this.#builtin.option.color && !hasUserColor) {
|
|
1360
|
-
optionMap.set('color', BUILTIN_COLOR_OPTION);
|
|
1361
|
-
}
|
|
1362
|
-
if (this.#builtin.option.logLevel && !hasUserLogLevel) {
|
|
1363
|
-
optionMap.set('logLevel', logLevelOption);
|
|
1364
|
-
}
|
|
1365
|
-
if (this.#builtin.option.silent && !hasUserSilent) {
|
|
1366
|
-
optionMap.set('silent', silentOption);
|
|
1367
|
-
}
|
|
1368
|
-
if (this.#builtin.option.logDate && !hasUserLogDate) {
|
|
1369
|
-
optionMap.set('logDate', logDateOption);
|
|
723
|
+
const hasUserHelp = this.#options.some(o => o.long === 'help');
|
|
724
|
+
const hasUserVersion = this.#options.some(o => o.long === 'version');
|
|
725
|
+
if (!hasUserHelp) {
|
|
726
|
+
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
1370
727
|
}
|
|
1371
|
-
if (
|
|
1372
|
-
optionMap.set('
|
|
728
|
+
if (!hasUserVersion && includeVersion) {
|
|
729
|
+
optionMap.set('version', BUILTIN_VERSION_OPTION);
|
|
1373
730
|
}
|
|
1374
731
|
for (const opt of this.#options) {
|
|
1375
732
|
optionMap.set(opt.long, opt);
|
|
1376
733
|
}
|
|
1377
|
-
return
|
|
1378
|
-
mergedOptions: Array.from(optionMap.values()),
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
#buildOptionPolicyMap(chain) {
|
|
1382
|
-
const optionPolicyMap = new Map();
|
|
1383
|
-
for (const cmd of chain) {
|
|
1384
|
-
optionPolicyMap.set(cmd, cmd.#resolveOptionPolicy());
|
|
1385
|
-
}
|
|
1386
|
-
return optionPolicyMap;
|
|
734
|
+
return Array.from(optionMap.values());
|
|
1387
735
|
}
|
|
1388
|
-
#
|
|
1389
|
-
const policy = optionPolicyMap.get(cmd);
|
|
1390
|
-
if (policy !== undefined) {
|
|
1391
|
-
return policy;
|
|
1392
|
-
}
|
|
1393
|
-
throw new CommanderError('ConfigurationError', `missing option policy for command "${cmd.#getCommandPath()}"`, this.#getCommandPath());
|
|
1394
|
-
}
|
|
1395
|
-
#validateMergedShortOptions(chain, optionPolicyMap) {
|
|
736
|
+
#validateMergedShortOptions(chain) {
|
|
1396
737
|
const mergedByLong = new Map();
|
|
1397
|
-
for (
|
|
1398
|
-
const
|
|
1399
|
-
|
|
738
|
+
for (let i = 0; i < chain.length; i++) {
|
|
739
|
+
const cmd = chain[i];
|
|
740
|
+
const includeVersion = i === 0;
|
|
741
|
+
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
1400
742
|
mergedByLong.set(opt.long, opt);
|
|
1401
743
|
}
|
|
1402
744
|
}
|
|
@@ -1412,9 +754,6 @@ class Command {
|
|
|
1412
754
|
}
|
|
1413
755
|
}
|
|
1414
756
|
#validateOptionConfig(opt) {
|
|
1415
|
-
if (opt.long === 'help' || opt.long === 'version') {
|
|
1416
|
-
throw new CommanderError('ConfigurationError', `option long name "${opt.long}" is reserved`, this.#getCommandPath());
|
|
1417
|
-
}
|
|
1418
757
|
if (opt.type === 'boolean' && opt.args !== 'none') {
|
|
1419
758
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
1420
759
|
}
|
|
@@ -1464,21 +803,6 @@ class Command {
|
|
|
1464
803
|
}
|
|
1465
804
|
}
|
|
1466
805
|
}
|
|
1467
|
-
#normalizeExample(example) {
|
|
1468
|
-
const title = example.title.trim();
|
|
1469
|
-
const usage = example.usage.trim();
|
|
1470
|
-
const desc = example.desc.trim();
|
|
1471
|
-
if (!title) {
|
|
1472
|
-
throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
|
|
1473
|
-
}
|
|
1474
|
-
if (!usage) {
|
|
1475
|
-
throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
|
|
1476
|
-
}
|
|
1477
|
-
if (!desc) {
|
|
1478
|
-
throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
|
|
1479
|
-
}
|
|
1480
|
-
return { title, usage, desc };
|
|
1481
|
-
}
|
|
1482
806
|
async #runAction(params) {
|
|
1483
807
|
if (!this.#action)
|
|
1484
808
|
return;
|
|
@@ -1495,51 +819,16 @@ class Command {
|
|
|
1495
819
|
process.exit(1);
|
|
1496
820
|
}
|
|
1497
821
|
}
|
|
1498
|
-
#
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
return color;
|
|
1503
|
-
}
|
|
1504
|
-
const separatorIndex = tailArgv.indexOf('--');
|
|
1505
|
-
const scanTokens = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
|
|
1506
|
-
for (const token of scanTokens) {
|
|
1507
|
-
if (token === '--color') {
|
|
1508
|
-
color = true;
|
|
1509
|
-
continue;
|
|
1510
|
-
}
|
|
1511
|
-
if (token === '--no-color') {
|
|
1512
|
-
color = false;
|
|
1513
|
-
continue;
|
|
1514
|
-
}
|
|
1515
|
-
if (!token.startsWith('--color=')) {
|
|
1516
|
-
continue;
|
|
1517
|
-
}
|
|
1518
|
-
const value = token.slice('--color='.length);
|
|
1519
|
-
if (value === 'true') {
|
|
1520
|
-
color = true;
|
|
1521
|
-
}
|
|
1522
|
-
else if (value === 'false') {
|
|
1523
|
-
color = false;
|
|
822
|
+
#hasFlag(tokens, longName, shortName) {
|
|
823
|
+
for (const token of tokens) {
|
|
824
|
+
if (token.type === 'long' && token.name === longName) {
|
|
825
|
+
return true;
|
|
1524
826
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
827
|
+
if (token.type === 'short' && token.name === shortName) {
|
|
828
|
+
return true;
|
|
1527
829
|
}
|
|
1528
830
|
}
|
|
1529
|
-
return
|
|
1530
|
-
}
|
|
1531
|
-
#freezeInputSources(sources) {
|
|
1532
|
-
return Object.freeze({
|
|
1533
|
-
preset: Object.freeze({
|
|
1534
|
-
argv: Object.freeze([...sources.preset.argv]),
|
|
1535
|
-
envs: Object.freeze({ ...sources.preset.envs }),
|
|
1536
|
-
}),
|
|
1537
|
-
user: Object.freeze({
|
|
1538
|
-
cmds: Object.freeze([...sources.user.cmds]),
|
|
1539
|
-
argv: Object.freeze([...sources.user.argv]),
|
|
1540
|
-
envs: Object.freeze({ ...sources.user.envs }),
|
|
1541
|
-
}),
|
|
1542
|
-
});
|
|
831
|
+
return false;
|
|
1543
832
|
}
|
|
1544
833
|
#getCommandPath() {
|
|
1545
834
|
const parts = [];
|
|
@@ -1554,166 +843,13 @@ class Command {
|
|
|
1554
843
|
}
|
|
1555
844
|
}
|
|
1556
845
|
|
|
1557
|
-
function isIpv4(rawValue) {
|
|
1558
|
-
const parts = rawValue.split('.');
|
|
1559
|
-
if (parts.length !== 4) {
|
|
1560
|
-
return false;
|
|
1561
|
-
}
|
|
1562
|
-
for (const part of parts) {
|
|
1563
|
-
if (part.length < 1 || !/^\d+$/.test(part)) {
|
|
1564
|
-
return false;
|
|
1565
|
-
}
|
|
1566
|
-
if (part.length > 1 && part.startsWith('0')) {
|
|
1567
|
-
return false;
|
|
1568
|
-
}
|
|
1569
|
-
const value = Number(part);
|
|
1570
|
-
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
1571
|
-
return false;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
return true;
|
|
1575
|
-
}
|
|
1576
|
-
function countIpv6Segments(part, allowIpv4Tail) {
|
|
1577
|
-
if (!part) {
|
|
1578
|
-
return { count: 0, hasIpv4Tail: false };
|
|
1579
|
-
}
|
|
1580
|
-
const segments = part.split(':');
|
|
1581
|
-
let count = 0;
|
|
1582
|
-
let hasIpv4Tail = false;
|
|
1583
|
-
for (let i = 0; i < segments.length; ++i) {
|
|
1584
|
-
const segment = segments[i];
|
|
1585
|
-
const isLastSegment = i === segments.length - 1;
|
|
1586
|
-
if (!segment) {
|
|
1587
|
-
return null;
|
|
1588
|
-
}
|
|
1589
|
-
if (segment.includes('.')) {
|
|
1590
|
-
if (!allowIpv4Tail || !isLastSegment || hasIpv4Tail || !isIpv4(segment)) {
|
|
1591
|
-
return null;
|
|
1592
|
-
}
|
|
1593
|
-
hasIpv4Tail = true;
|
|
1594
|
-
count += 2;
|
|
1595
|
-
continue;
|
|
1596
|
-
}
|
|
1597
|
-
if (!/^[0-9A-Fa-f]{1,4}$/.test(segment)) {
|
|
1598
|
-
return null;
|
|
1599
|
-
}
|
|
1600
|
-
count += 1;
|
|
1601
|
-
}
|
|
1602
|
-
return { count, hasIpv4Tail };
|
|
1603
|
-
}
|
|
1604
|
-
function isIpv6(rawValue) {
|
|
1605
|
-
if (!rawValue || !/^[0-9A-Fa-f:.]+$/.test(rawValue)) {
|
|
1606
|
-
return false;
|
|
1607
|
-
}
|
|
1608
|
-
const doubleColonCount = rawValue.split('::').length - 1;
|
|
1609
|
-
if (doubleColonCount > 1) {
|
|
1610
|
-
return false;
|
|
1611
|
-
}
|
|
1612
|
-
if (doubleColonCount === 0) {
|
|
1613
|
-
const full = countIpv6Segments(rawValue, true);
|
|
1614
|
-
return full !== null && full.count === 8;
|
|
1615
|
-
}
|
|
1616
|
-
const [left, right] = rawValue.split('::');
|
|
1617
|
-
const leftPart = countIpv6Segments(left, right.length === 0);
|
|
1618
|
-
const rightPart = countIpv6Segments(right, true);
|
|
1619
|
-
if (!leftPart || !rightPart) {
|
|
1620
|
-
return false;
|
|
1621
|
-
}
|
|
1622
|
-
const totalSegments = leftPart.count + rightPart.count;
|
|
1623
|
-
return totalSegments < 8;
|
|
1624
|
-
}
|
|
1625
|
-
function isIp(rawValue) {
|
|
1626
|
-
return isIpv4(rawValue) || isIpv6(rawValue);
|
|
1627
|
-
}
|
|
1628
|
-
function isDomain(rawValue) {
|
|
1629
|
-
if (rawValue.length < 1 || rawValue.length > 253 || rawValue.endsWith('.')) {
|
|
1630
|
-
return false;
|
|
1631
|
-
}
|
|
1632
|
-
const labels = rawValue.split('.');
|
|
1633
|
-
if (labels.length < 2) {
|
|
1634
|
-
return false;
|
|
1635
|
-
}
|
|
1636
|
-
if (labels.some(label => label.length < 1 || label.length > 63)) {
|
|
1637
|
-
return false;
|
|
1638
|
-
}
|
|
1639
|
-
const labelPattern = /^[A-Za-z0-9-]+$/;
|
|
1640
|
-
if (labels.some(label => !labelPattern.test(label) || label.startsWith('-') || label.endsWith('-'))) {
|
|
1641
|
-
return false;
|
|
1642
|
-
}
|
|
1643
|
-
const topLevelLabel = labels[labels.length - 1];
|
|
1644
|
-
return /[A-Za-z]/.test(topLevelLabel);
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
class Coerce {
|
|
1648
|
-
constructor() { }
|
|
1649
|
-
static create(name, expectedType, validator, errorMessage) {
|
|
1650
|
-
return (rawValue) => {
|
|
1651
|
-
const value = Number(rawValue);
|
|
1652
|
-
if (!validator(value)) {
|
|
1653
|
-
throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
|
|
1654
|
-
}
|
|
1655
|
-
return value;
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
static choice(name, values, errorMessage) {
|
|
1659
|
-
return (rawValue) => {
|
|
1660
|
-
if (values.includes(rawValue)) {
|
|
1661
|
-
return rawValue;
|
|
1662
|
-
}
|
|
1663
|
-
throw new Error(errorMessage ?? `${name} is expected as one of [${values.join(', ')}], but got ${rawValue}`);
|
|
1664
|
-
};
|
|
1665
|
-
}
|
|
1666
|
-
static domain(name, errorMessage) {
|
|
1667
|
-
return (rawValue) => {
|
|
1668
|
-
if (isDomain(rawValue)) {
|
|
1669
|
-
return rawValue;
|
|
1670
|
-
}
|
|
1671
|
-
throw new Error(errorMessage ?? `${name} is expected as a valid domain, but got ${rawValue}`);
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1674
|
-
static host(name, errorMessage) {
|
|
1675
|
-
return (rawValue) => {
|
|
1676
|
-
if (isIp(rawValue) || isDomain(rawValue)) {
|
|
1677
|
-
return rawValue;
|
|
1678
|
-
}
|
|
1679
|
-
throw new Error(errorMessage ?? `${name} is expected as a valid host (IP or domain), but got ${rawValue}`);
|
|
1680
|
-
};
|
|
1681
|
-
}
|
|
1682
|
-
static integer(name, errorMessage) {
|
|
1683
|
-
return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
|
|
1684
|
-
}
|
|
1685
|
-
static ip(name, errorMessage) {
|
|
1686
|
-
return (rawValue) => {
|
|
1687
|
-
if (isIp(rawValue)) {
|
|
1688
|
-
return rawValue;
|
|
1689
|
-
}
|
|
1690
|
-
throw new Error(errorMessage ?? `${name} is expected as a valid IP address, but got ${rawValue}`);
|
|
1691
|
-
};
|
|
1692
|
-
}
|
|
1693
|
-
static number(name, errorMessage) {
|
|
1694
|
-
return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
|
|
1695
|
-
}
|
|
1696
|
-
static port(name, errorMessage) {
|
|
1697
|
-
return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
|
|
1698
|
-
}
|
|
1699
|
-
static positiveInteger(name, errorMessage) {
|
|
1700
|
-
return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
|
|
1701
|
-
}
|
|
1702
|
-
static positiveNumber(name, errorMessage) {
|
|
1703
|
-
return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
846
|
function camelToKebabCase(str) {
|
|
1708
847
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
1709
848
|
}
|
|
1710
849
|
class CompletionCommand extends Command {
|
|
1711
|
-
constructor(root, config
|
|
850
|
+
constructor(root, config) {
|
|
851
|
+
const paths = config.paths;
|
|
1712
852
|
const programName = config.programName ?? root.name ?? 'program';
|
|
1713
|
-
const paths = {
|
|
1714
|
-
...createDefaultCompletionPaths(programName),
|
|
1715
|
-
...config.paths,
|
|
1716
|
-
};
|
|
1717
853
|
super({ desc: 'Generate shell completion script' });
|
|
1718
854
|
this.option({
|
|
1719
855
|
long: 'bash',
|
|
@@ -1788,13 +924,6 @@ class CompletionCommand extends Command {
|
|
|
1788
924
|
});
|
|
1789
925
|
}
|
|
1790
926
|
}
|
|
1791
|
-
function createDefaultCompletionPaths(programName) {
|
|
1792
|
-
return {
|
|
1793
|
-
bash: `~/.local/share/bash-completion/completions/${programName}`,
|
|
1794
|
-
fish: `~/.config/fish/completions/${programName}.fish`,
|
|
1795
|
-
pwsh: '~/.config/powershell/Microsoft.PowerShell_profile.ps1',
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1798
927
|
function expandHome(filepath) {
|
|
1799
928
|
if (filepath.startsWith('~/') || filepath === '~') {
|
|
1800
929
|
const home = process.env['HOME'] || process.env['USERPROFILE'] || '';
|
|
@@ -2072,18 +1201,37 @@ class PwshCompletion {
|
|
|
2072
1201
|
}
|
|
2073
1202
|
}
|
|
2074
1203
|
|
|
1204
|
+
const logLevelOption = {
|
|
1205
|
+
long: 'logLevel',
|
|
1206
|
+
type: 'string',
|
|
1207
|
+
args: 'required',
|
|
1208
|
+
desc: 'Set log level',
|
|
1209
|
+
default: 'info',
|
|
1210
|
+
choices: reporter.LOG_LEVELS,
|
|
1211
|
+
coerce: (raw) => {
|
|
1212
|
+
const level = reporter.resolveLogLevel(raw);
|
|
1213
|
+
if (level === undefined) {
|
|
1214
|
+
throw new Error(`Invalid log level: ${raw}`);
|
|
1215
|
+
}
|
|
1216
|
+
return level;
|
|
1217
|
+
},
|
|
1218
|
+
apply: (value, ctx) => {
|
|
1219
|
+
ctx.reporter.setLevel(value);
|
|
1220
|
+
},
|
|
1221
|
+
};
|
|
1222
|
+
const silentOption = {
|
|
1223
|
+
long: 'silent',
|
|
1224
|
+
type: 'boolean',
|
|
1225
|
+
args: 'none',
|
|
1226
|
+
desc: 'Suppress non-error output',
|
|
1227
|
+
default: false,
|
|
1228
|
+
};
|
|
1229
|
+
|
|
2075
1230
|
exports.BashCompletion = BashCompletion;
|
|
2076
|
-
exports.Coerce = Coerce;
|
|
2077
1231
|
exports.Command = Command;
|
|
2078
1232
|
exports.CommanderError = CommanderError;
|
|
2079
1233
|
exports.CompletionCommand = CompletionCommand;
|
|
2080
1234
|
exports.FishCompletion = FishCompletion;
|
|
2081
1235
|
exports.PwshCompletion = PwshCompletion;
|
|
2082
|
-
exports.isDomain = isDomain;
|
|
2083
|
-
exports.isIp = isIp;
|
|
2084
|
-
exports.isIpv4 = isIpv4;
|
|
2085
|
-
exports.isIpv6 = isIpv6;
|
|
2086
|
-
exports.logColorfulOption = logColorfulOption;
|
|
2087
|
-
exports.logDateOption = logDateOption;
|
|
2088
1236
|
exports.logLevelOption = logLevelOption;
|
|
2089
1237
|
exports.silentOption = silentOption;
|