@guanghechen/commander 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +23 -0
- package/lib/cjs/index.cjs +225 -60
- package/lib/esm/index.mjs +225 -60
- package/lib/types/index.d.ts +15 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -202,6 +202,29 @@ new Command({ name: 'example', description: 'Option types demo' })
|
|
|
202
202
|
})
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
+
### Help Examples
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { Command } from '@guanghechen/commander'
|
|
209
|
+
|
|
210
|
+
const cli = new Command({ name: 'mycli', desc: 'My CLI tool' })
|
|
211
|
+
|
|
212
|
+
cli
|
|
213
|
+
.example('Initialize Project', 'init my-app', 'Create project scaffold')
|
|
214
|
+
.example('Watch Build', 'build --watch', 'Rebuild on file changes')
|
|
215
|
+
.action(() => {})
|
|
216
|
+
|
|
217
|
+
await cli.run({ argv: ['--help'], envs: process.env })
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`usage` 是相对当前 command path 的片段,help 中会自动补齐前缀,例如 `mycli build --watch`。
|
|
221
|
+
|
|
222
|
+
`--color` / `--no-color` 仅控制 help 文本的终端着色;
|
|
223
|
+
`--log-colorful` / `--no-log-colorful` 控制 `Reporter` 的日志着色。
|
|
224
|
+
|
|
225
|
+
当环境变量 `NO_COLOR` 存在时,help 渲染默认视为 `--no-color`;
|
|
226
|
+
显式传入 `--color` 可以覆盖这个默认值。
|
|
227
|
+
|
|
205
228
|
## Reference
|
|
206
229
|
|
|
207
230
|
- [homepage][homepage]
|
package/lib/cjs/index.cjs
CHANGED
|
@@ -24,6 +24,18 @@ function _interopNamespaceDefault(e) {
|
|
|
24
24
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
25
25
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
26
26
|
|
|
27
|
+
const TERMINAL_STYLE = {
|
|
28
|
+
bold: '\x1b[1m',
|
|
29
|
+
italic: '\x1b[3m',
|
|
30
|
+
underline: '\x1b[4m',
|
|
31
|
+
cyan: '\x1b[36m',
|
|
32
|
+
dim: '\x1b[2m',
|
|
33
|
+
reset: '\x1b[0m',
|
|
34
|
+
};
|
|
35
|
+
function styleText(text, ...styles) {
|
|
36
|
+
return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
const logLevelOption = {
|
|
28
40
|
long: 'logLevel',
|
|
29
41
|
type: 'string',
|
|
@@ -190,14 +202,28 @@ const BUILTIN_VERSION_OPTION = {
|
|
|
190
202
|
args: 'none',
|
|
191
203
|
desc: 'Show version number',
|
|
192
204
|
};
|
|
205
|
+
const BUILTIN_COLOR_OPTION = {
|
|
206
|
+
long: 'color',
|
|
207
|
+
type: 'boolean',
|
|
208
|
+
args: 'none',
|
|
209
|
+
desc: 'Enable colored help output',
|
|
210
|
+
default: true,
|
|
211
|
+
};
|
|
212
|
+
function createBuiltinOptionState(enabled) {
|
|
213
|
+
return {
|
|
214
|
+
color: enabled,
|
|
215
|
+
logLevel: enabled,
|
|
216
|
+
silent: enabled,
|
|
217
|
+
logDate: enabled,
|
|
218
|
+
logColorful: enabled,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function isNoColorEnabled(envs) {
|
|
222
|
+
return envs['NO_COLOR'] !== undefined;
|
|
223
|
+
}
|
|
193
224
|
function normalizeBuiltinConfig(builtin) {
|
|
194
225
|
const resolved = {
|
|
195
|
-
option:
|
|
196
|
-
logLevel: true,
|
|
197
|
-
silent: true,
|
|
198
|
-
logDate: true,
|
|
199
|
-
logColorful: true,
|
|
200
|
-
},
|
|
226
|
+
option: createBuiltinOptionState(true),
|
|
201
227
|
command: {
|
|
202
228
|
help: false,
|
|
203
229
|
},
|
|
@@ -207,26 +233,29 @@ function normalizeBuiltinConfig(builtin) {
|
|
|
207
233
|
}
|
|
208
234
|
if (builtin === true) {
|
|
209
235
|
return {
|
|
210
|
-
option:
|
|
236
|
+
option: createBuiltinOptionState(true),
|
|
211
237
|
command: { help: true },
|
|
212
238
|
};
|
|
213
239
|
}
|
|
214
240
|
if (builtin === false) {
|
|
215
241
|
return {
|
|
216
|
-
option:
|
|
242
|
+
option: createBuiltinOptionState(false),
|
|
217
243
|
command: { help: false },
|
|
218
244
|
};
|
|
219
245
|
}
|
|
220
246
|
if (builtin.option !== undefined) {
|
|
221
247
|
if (builtin.option === false) {
|
|
222
|
-
resolved.option =
|
|
248
|
+
resolved.option = createBuiltinOptionState(false);
|
|
223
249
|
}
|
|
224
250
|
else if (builtin.option === true) {
|
|
225
|
-
resolved.option =
|
|
251
|
+
resolved.option = createBuiltinOptionState(true);
|
|
226
252
|
}
|
|
227
253
|
else {
|
|
228
|
-
if (builtin.option.
|
|
254
|
+
if (builtin.option.color !== undefined)
|
|
255
|
+
resolved.option.color = builtin.option.color;
|
|
256
|
+
if (builtin.option.logLevel !== undefined) {
|
|
229
257
|
resolved.option.logLevel = builtin.option.logLevel;
|
|
258
|
+
}
|
|
230
259
|
if (builtin.option.silent !== undefined)
|
|
231
260
|
resolved.option.silent = builtin.option.silent;
|
|
232
261
|
if (builtin.option.logDate !== undefined)
|
|
@@ -258,6 +287,7 @@ class Command {
|
|
|
258
287
|
#parent;
|
|
259
288
|
#options = [];
|
|
260
289
|
#arguments = [];
|
|
290
|
+
#examples = [];
|
|
261
291
|
#subcommandsList = [];
|
|
262
292
|
#subcommandsMap = new Map();
|
|
263
293
|
#action = undefined;
|
|
@@ -286,6 +316,9 @@ class Command {
|
|
|
286
316
|
get arguments() {
|
|
287
317
|
return [...this.#arguments];
|
|
288
318
|
}
|
|
319
|
+
get examples() {
|
|
320
|
+
return this.#examples.map(example => ({ ...example }));
|
|
321
|
+
}
|
|
289
322
|
get subcommands() {
|
|
290
323
|
return new Map(this.#subcommandsMap);
|
|
291
324
|
}
|
|
@@ -304,6 +337,10 @@ class Command {
|
|
|
304
337
|
this.#action = fn;
|
|
305
338
|
return this;
|
|
306
339
|
}
|
|
340
|
+
example(title, usage, desc) {
|
|
341
|
+
this.#examples.push(this.#normalizeExample({ title, usage, desc }));
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
307
344
|
subcommand(name, cmd) {
|
|
308
345
|
if (this.#builtin.command.help && name === 'help') {
|
|
309
346
|
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
|
|
@@ -337,7 +374,8 @@ class Command {
|
|
|
337
374
|
const hasUserHelp = leafCommand.#options.some(o => o.long === 'help');
|
|
338
375
|
const hasUserVersion = leafCommand.#options.some(o => o.long === 'version');
|
|
339
376
|
if (!hasUserHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
|
|
340
|
-
|
|
377
|
+
const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs);
|
|
378
|
+
console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
341
379
|
return;
|
|
342
380
|
}
|
|
343
381
|
if (!hasUserVersion && leafCommand === rootCommand && leafCommand.#version) {
|
|
@@ -364,7 +402,8 @@ class Command {
|
|
|
364
402
|
await leafCommand.#runAction(actionParams);
|
|
365
403
|
}
|
|
366
404
|
else if (leafCommand.#subcommandsList.length > 0) {
|
|
367
|
-
|
|
405
|
+
const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs);
|
|
406
|
+
console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
368
407
|
}
|
|
369
408
|
else {
|
|
370
409
|
throw new CommanderError('ConfigurationError', `no action defined for command "${leafCommand.#getCommandPath()}"`, leafCommand.#getCommandPath());
|
|
@@ -397,10 +436,21 @@ class Command {
|
|
|
397
436
|
return this.#parse(chain, resolveResult, ctx, restArgs);
|
|
398
437
|
}
|
|
399
438
|
formatHelp() {
|
|
400
|
-
|
|
439
|
+
return this.#renderHelpPlain(this.#buildHelpData());
|
|
440
|
+
}
|
|
441
|
+
#formatHelpForDisplay(params = {}) {
|
|
442
|
+
const { color = true } = params;
|
|
443
|
+
const helpData = this.#buildHelpData();
|
|
444
|
+
if (!this.#shouldRenderStyledHelp(color)) {
|
|
445
|
+
return this.#renderHelpPlain(helpData);
|
|
446
|
+
}
|
|
447
|
+
return this.#renderHelpTerminal(helpData);
|
|
448
|
+
}
|
|
449
|
+
#shouldRenderStyledHelp(color) {
|
|
450
|
+
return color && process.stdout.isTTY === true;
|
|
451
|
+
}
|
|
452
|
+
#buildHelpData() {
|
|
401
453
|
const allOptions = this.#getMergedOptions();
|
|
402
|
-
lines.push(this.#desc);
|
|
403
|
-
lines.push('');
|
|
404
454
|
const commandPath = this.#getCommandPath();
|
|
405
455
|
let usage = `Usage: ${commandPath}`;
|
|
406
456
|
if (allOptions.length > 0)
|
|
@@ -418,61 +468,122 @@ class Command {
|
|
|
418
468
|
usage += ` [${arg.name}...]`;
|
|
419
469
|
}
|
|
420
470
|
}
|
|
421
|
-
|
|
471
|
+
const options = [];
|
|
472
|
+
for (const opt of allOptions) {
|
|
473
|
+
const kebabLong = camelToKebabCase$1(opt.long);
|
|
474
|
+
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
475
|
+
sig += `--${kebabLong}`;
|
|
476
|
+
if (opt.args !== 'none') {
|
|
477
|
+
sig += ' <value>';
|
|
478
|
+
}
|
|
479
|
+
let desc = opt.desc;
|
|
480
|
+
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
481
|
+
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
482
|
+
}
|
|
483
|
+
if (opt.choices) {
|
|
484
|
+
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
485
|
+
}
|
|
486
|
+
options.push({ sig, desc });
|
|
487
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
488
|
+
options.push({
|
|
489
|
+
sig: ` --no-${kebabLong}`,
|
|
490
|
+
desc: `Negate --${kebabLong}`,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const commands = [];
|
|
495
|
+
const showHelpSubcommand = this.#builtin.command.help && this.#subcommandsList.length > 0;
|
|
496
|
+
if (showHelpSubcommand) {
|
|
497
|
+
commands.push({ name: 'help', desc: 'Show help for a command' });
|
|
498
|
+
}
|
|
499
|
+
for (const entry of this.#subcommandsList) {
|
|
500
|
+
let name = entry.name;
|
|
501
|
+
if (entry.aliases.length > 0) {
|
|
502
|
+
name += `, ${entry.aliases.join(', ')}`;
|
|
503
|
+
}
|
|
504
|
+
commands.push({ name, desc: entry.command.#desc });
|
|
505
|
+
}
|
|
506
|
+
const examples = this.#examples.map(example => ({
|
|
507
|
+
title: example.title,
|
|
508
|
+
usage: commandPath ? `${commandPath} ${example.usage}` : example.usage,
|
|
509
|
+
desc: example.desc,
|
|
510
|
+
}));
|
|
511
|
+
return {
|
|
512
|
+
desc: this.#desc,
|
|
513
|
+
usage,
|
|
514
|
+
options,
|
|
515
|
+
commands,
|
|
516
|
+
examples,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
#renderHelpPlain(helpData) {
|
|
520
|
+
const lines = [];
|
|
521
|
+
lines.push(helpData.desc);
|
|
422
522
|
lines.push('');
|
|
423
|
-
|
|
523
|
+
lines.push(helpData.usage);
|
|
524
|
+
lines.push('');
|
|
525
|
+
if (helpData.options.length > 0) {
|
|
424
526
|
lines.push('Options:');
|
|
425
|
-
const
|
|
426
|
-
for (const
|
|
427
|
-
const kebabLong = camelToKebabCase$1(opt.long);
|
|
428
|
-
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
429
|
-
sig += `--${kebabLong}`;
|
|
430
|
-
if (opt.args !== 'none') {
|
|
431
|
-
sig += ' <value>';
|
|
432
|
-
}
|
|
433
|
-
let desc = opt.desc;
|
|
434
|
-
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
435
|
-
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
436
|
-
}
|
|
437
|
-
if (opt.choices) {
|
|
438
|
-
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
439
|
-
}
|
|
440
|
-
optLines.push({ sig, desc });
|
|
441
|
-
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
442
|
-
optLines.push({
|
|
443
|
-
sig: ` --no-${kebabLong}`,
|
|
444
|
-
desc: `Negate --${kebabLong}`,
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
const maxSigLen = Math.max(...optLines.map(l => l.sig.length));
|
|
449
|
-
for (const { sig, desc } of optLines) {
|
|
527
|
+
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
528
|
+
for (const { sig, desc } of helpData.options) {
|
|
450
529
|
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
451
530
|
lines.push(` ${sig}${padding}${desc}`);
|
|
452
531
|
}
|
|
453
532
|
lines.push('');
|
|
454
533
|
}
|
|
455
|
-
|
|
456
|
-
if (this.#subcommandsList.length > 0) {
|
|
534
|
+
if (helpData.commands.length > 0) {
|
|
457
535
|
lines.push('Commands:');
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
cmdLines.push({ name: 'help', desc: 'Show help for a command' });
|
|
461
|
-
}
|
|
462
|
-
for (const entry of this.#subcommandsList) {
|
|
463
|
-
let name = entry.name;
|
|
464
|
-
if (entry.aliases.length > 0) {
|
|
465
|
-
name += `, ${entry.aliases.join(', ')}`;
|
|
466
|
-
}
|
|
467
|
-
cmdLines.push({ name, desc: entry.command.#desc });
|
|
468
|
-
}
|
|
469
|
-
const maxNameLen = Math.max(...cmdLines.map(l => l.name.length));
|
|
470
|
-
for (const { name, desc } of cmdLines) {
|
|
536
|
+
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
537
|
+
for (const { name, desc } of helpData.commands) {
|
|
471
538
|
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
472
539
|
lines.push(` ${name}${padding}${desc}`);
|
|
473
540
|
}
|
|
474
541
|
lines.push('');
|
|
475
542
|
}
|
|
543
|
+
if (helpData.examples.length > 0) {
|
|
544
|
+
lines.push('Examples:');
|
|
545
|
+
for (const example of helpData.examples) {
|
|
546
|
+
lines.push(` - ${example.title}`);
|
|
547
|
+
lines.push(` ${example.usage}`);
|
|
548
|
+
lines.push(` ${example.desc}`);
|
|
549
|
+
lines.push('');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return lines.join('\n');
|
|
553
|
+
}
|
|
554
|
+
#renderHelpTerminal(helpData) {
|
|
555
|
+
const lines = [];
|
|
556
|
+
lines.push(helpData.desc);
|
|
557
|
+
lines.push('');
|
|
558
|
+
lines.push(styleText(helpData.usage, TERMINAL_STYLE.bold));
|
|
559
|
+
lines.push('');
|
|
560
|
+
if (helpData.options.length > 0) {
|
|
561
|
+
lines.push(styleText('Options:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
562
|
+
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
563
|
+
for (const { sig, desc } of helpData.options) {
|
|
564
|
+
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
565
|
+
lines.push(` ${styleText(sig, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
566
|
+
}
|
|
567
|
+
lines.push('');
|
|
568
|
+
}
|
|
569
|
+
if (helpData.commands.length > 0) {
|
|
570
|
+
lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
571
|
+
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
572
|
+
for (const { name, desc } of helpData.commands) {
|
|
573
|
+
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
574
|
+
lines.push(` ${styleText(name, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
575
|
+
}
|
|
576
|
+
lines.push('');
|
|
577
|
+
}
|
|
578
|
+
if (helpData.examples.length > 0) {
|
|
579
|
+
lines.push(styleText('Examples:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
580
|
+
for (const example of helpData.examples) {
|
|
581
|
+
lines.push(` - ${styleText(example.title, TERMINAL_STYLE.bold)}`);
|
|
582
|
+
lines.push(` ${styleText(example.usage, TERMINAL_STYLE.cyan)}`);
|
|
583
|
+
lines.push(` ${styleText(example.desc, TERMINAL_STYLE.italic, TERMINAL_STYLE.dim)}`);
|
|
584
|
+
lines.push('');
|
|
585
|
+
}
|
|
586
|
+
}
|
|
476
587
|
return lines.join('\n');
|
|
477
588
|
}
|
|
478
589
|
getCompletionMeta() {
|
|
@@ -636,7 +747,7 @@ class Command {
|
|
|
636
747
|
const cmd = chain[i];
|
|
637
748
|
const includeVersion = i === 0;
|
|
638
749
|
const tokens = consumedTokens.get(cmd) ?? [];
|
|
639
|
-
const opts = cmd.#parseOptions(tokens, includeVersion);
|
|
750
|
+
const opts = cmd.#parseOptions(tokens, includeVersion, ctx.envs);
|
|
640
751
|
optsMap.set(cmd, opts);
|
|
641
752
|
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
642
753
|
if (opt.apply && opts[opt.long] !== undefined) {
|
|
@@ -652,9 +763,10 @@ class Command {
|
|
|
652
763
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
653
764
|
return { ctx, opts: mergedOpts, args, rawArgs };
|
|
654
765
|
}
|
|
655
|
-
#parseOptions(tokens, includeVersion) {
|
|
766
|
+
#parseOptions(tokens, includeVersion, envs) {
|
|
656
767
|
const allOptions = this.#getMergedOptions(includeVersion);
|
|
657
768
|
const opts = {};
|
|
769
|
+
let sawColorToken = false;
|
|
658
770
|
for (const opt of allOptions) {
|
|
659
771
|
if (opt.default !== undefined) {
|
|
660
772
|
opts[opt.long] = opt.default;
|
|
@@ -682,6 +794,9 @@ class Command {
|
|
|
682
794
|
i += 1;
|
|
683
795
|
continue;
|
|
684
796
|
}
|
|
797
|
+
if (opt.long === 'color') {
|
|
798
|
+
sawColorToken = true;
|
|
799
|
+
}
|
|
685
800
|
const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
|
|
686
801
|
if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
|
|
687
802
|
throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
|
|
@@ -758,6 +873,9 @@ class Command {
|
|
|
758
873
|
}
|
|
759
874
|
}
|
|
760
875
|
}
|
|
876
|
+
if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
|
|
877
|
+
opts['color'] = false;
|
|
878
|
+
}
|
|
761
879
|
return opts;
|
|
762
880
|
}
|
|
763
881
|
#convertValue(opt, rawValue) {
|
|
@@ -830,12 +948,16 @@ class Command {
|
|
|
830
948
|
}
|
|
831
949
|
#getMergedOptions(includeVersion = !this.#parent) {
|
|
832
950
|
const optionMap = new Map();
|
|
951
|
+
const hasUserColor = this.#options.some(o => o.long === 'color');
|
|
833
952
|
const hasUserHelp = this.#options.some(o => o.long === 'help');
|
|
834
953
|
const hasUserVersion = this.#options.some(o => o.long === 'version');
|
|
835
954
|
const hasUserLogLevel = this.#options.some(o => o.long === 'logLevel');
|
|
836
955
|
const hasUserSilent = this.#options.some(o => o.long === 'silent');
|
|
837
956
|
const hasUserLogDate = this.#options.some(o => o.long === 'logDate');
|
|
838
957
|
const hasUserLogColorful = this.#options.some(o => o.long === 'logColorful');
|
|
958
|
+
if (this.#builtin.option.color && !hasUserColor) {
|
|
959
|
+
optionMap.set('color', BUILTIN_COLOR_OPTION);
|
|
960
|
+
}
|
|
839
961
|
if (!hasUserHelp) {
|
|
840
962
|
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
841
963
|
}
|
|
@@ -929,6 +1051,21 @@ class Command {
|
|
|
929
1051
|
}
|
|
930
1052
|
}
|
|
931
1053
|
}
|
|
1054
|
+
#normalizeExample(example) {
|
|
1055
|
+
const title = example.title.trim();
|
|
1056
|
+
const usage = example.usage.trim();
|
|
1057
|
+
const desc = example.desc.trim();
|
|
1058
|
+
if (!title) {
|
|
1059
|
+
throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
|
|
1060
|
+
}
|
|
1061
|
+
if (!usage) {
|
|
1062
|
+
throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
|
|
1063
|
+
}
|
|
1064
|
+
if (!desc) {
|
|
1065
|
+
throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
|
|
1066
|
+
}
|
|
1067
|
+
return { title, usage, desc };
|
|
1068
|
+
}
|
|
932
1069
|
async #runAction(params) {
|
|
933
1070
|
if (!this.#action)
|
|
934
1071
|
return;
|
|
@@ -945,6 +1082,34 @@ class Command {
|
|
|
945
1082
|
process.exit(1);
|
|
946
1083
|
}
|
|
947
1084
|
}
|
|
1085
|
+
#resolveHelpColorOption(tokens, envs) {
|
|
1086
|
+
const colorOption = this.#getMergedOptions().find(opt => opt.long === 'color');
|
|
1087
|
+
let color = !isNoColorEnabled(envs);
|
|
1088
|
+
if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
|
|
1089
|
+
return color;
|
|
1090
|
+
}
|
|
1091
|
+
for (const token of tokens) {
|
|
1092
|
+
if (token.type !== 'long' || token.name !== 'color') {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1096
|
+
if (eqIdx === -1) {
|
|
1097
|
+
color = true;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const value = token.resolved.slice(eqIdx + 1);
|
|
1101
|
+
if (value === 'true') {
|
|
1102
|
+
color = true;
|
|
1103
|
+
}
|
|
1104
|
+
else if (value === 'false') {
|
|
1105
|
+
color = false;
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--color". Use "true" or "false"`, this.#getCommandPath());
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return color;
|
|
1112
|
+
}
|
|
948
1113
|
#hasFlag(tokens, longName, shortName) {
|
|
949
1114
|
for (const token of tokens) {
|
|
950
1115
|
if (token.type === 'long' && token.name === longName) {
|
package/lib/esm/index.mjs
CHANGED
|
@@ -2,6 +2,18 @@ import { LOG_LEVELS, resolveLogLevel, Reporter } from '@guanghechen/reporter';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
|
|
5
|
+
const TERMINAL_STYLE = {
|
|
6
|
+
bold: '\x1b[1m',
|
|
7
|
+
italic: '\x1b[3m',
|
|
8
|
+
underline: '\x1b[4m',
|
|
9
|
+
cyan: '\x1b[36m',
|
|
10
|
+
dim: '\x1b[2m',
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
};
|
|
13
|
+
function styleText(text, ...styles) {
|
|
14
|
+
return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
const logLevelOption = {
|
|
6
18
|
long: 'logLevel',
|
|
7
19
|
type: 'string',
|
|
@@ -168,14 +180,28 @@ const BUILTIN_VERSION_OPTION = {
|
|
|
168
180
|
args: 'none',
|
|
169
181
|
desc: 'Show version number',
|
|
170
182
|
};
|
|
183
|
+
const BUILTIN_COLOR_OPTION = {
|
|
184
|
+
long: 'color',
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
args: 'none',
|
|
187
|
+
desc: 'Enable colored help output',
|
|
188
|
+
default: true,
|
|
189
|
+
};
|
|
190
|
+
function createBuiltinOptionState(enabled) {
|
|
191
|
+
return {
|
|
192
|
+
color: enabled,
|
|
193
|
+
logLevel: enabled,
|
|
194
|
+
silent: enabled,
|
|
195
|
+
logDate: enabled,
|
|
196
|
+
logColorful: enabled,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function isNoColorEnabled(envs) {
|
|
200
|
+
return envs['NO_COLOR'] !== undefined;
|
|
201
|
+
}
|
|
171
202
|
function normalizeBuiltinConfig(builtin) {
|
|
172
203
|
const resolved = {
|
|
173
|
-
option:
|
|
174
|
-
logLevel: true,
|
|
175
|
-
silent: true,
|
|
176
|
-
logDate: true,
|
|
177
|
-
logColorful: true,
|
|
178
|
-
},
|
|
204
|
+
option: createBuiltinOptionState(true),
|
|
179
205
|
command: {
|
|
180
206
|
help: false,
|
|
181
207
|
},
|
|
@@ -185,26 +211,29 @@ function normalizeBuiltinConfig(builtin) {
|
|
|
185
211
|
}
|
|
186
212
|
if (builtin === true) {
|
|
187
213
|
return {
|
|
188
|
-
option:
|
|
214
|
+
option: createBuiltinOptionState(true),
|
|
189
215
|
command: { help: true },
|
|
190
216
|
};
|
|
191
217
|
}
|
|
192
218
|
if (builtin === false) {
|
|
193
219
|
return {
|
|
194
|
-
option:
|
|
220
|
+
option: createBuiltinOptionState(false),
|
|
195
221
|
command: { help: false },
|
|
196
222
|
};
|
|
197
223
|
}
|
|
198
224
|
if (builtin.option !== undefined) {
|
|
199
225
|
if (builtin.option === false) {
|
|
200
|
-
resolved.option =
|
|
226
|
+
resolved.option = createBuiltinOptionState(false);
|
|
201
227
|
}
|
|
202
228
|
else if (builtin.option === true) {
|
|
203
|
-
resolved.option =
|
|
229
|
+
resolved.option = createBuiltinOptionState(true);
|
|
204
230
|
}
|
|
205
231
|
else {
|
|
206
|
-
if (builtin.option.
|
|
232
|
+
if (builtin.option.color !== undefined)
|
|
233
|
+
resolved.option.color = builtin.option.color;
|
|
234
|
+
if (builtin.option.logLevel !== undefined) {
|
|
207
235
|
resolved.option.logLevel = builtin.option.logLevel;
|
|
236
|
+
}
|
|
208
237
|
if (builtin.option.silent !== undefined)
|
|
209
238
|
resolved.option.silent = builtin.option.silent;
|
|
210
239
|
if (builtin.option.logDate !== undefined)
|
|
@@ -236,6 +265,7 @@ class Command {
|
|
|
236
265
|
#parent;
|
|
237
266
|
#options = [];
|
|
238
267
|
#arguments = [];
|
|
268
|
+
#examples = [];
|
|
239
269
|
#subcommandsList = [];
|
|
240
270
|
#subcommandsMap = new Map();
|
|
241
271
|
#action = undefined;
|
|
@@ -264,6 +294,9 @@ class Command {
|
|
|
264
294
|
get arguments() {
|
|
265
295
|
return [...this.#arguments];
|
|
266
296
|
}
|
|
297
|
+
get examples() {
|
|
298
|
+
return this.#examples.map(example => ({ ...example }));
|
|
299
|
+
}
|
|
267
300
|
get subcommands() {
|
|
268
301
|
return new Map(this.#subcommandsMap);
|
|
269
302
|
}
|
|
@@ -282,6 +315,10 @@ class Command {
|
|
|
282
315
|
this.#action = fn;
|
|
283
316
|
return this;
|
|
284
317
|
}
|
|
318
|
+
example(title, usage, desc) {
|
|
319
|
+
this.#examples.push(this.#normalizeExample({ title, usage, desc }));
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
285
322
|
subcommand(name, cmd) {
|
|
286
323
|
if (this.#builtin.command.help && name === 'help') {
|
|
287
324
|
throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
|
|
@@ -315,7 +352,8 @@ class Command {
|
|
|
315
352
|
const hasUserHelp = leafCommand.#options.some(o => o.long === 'help');
|
|
316
353
|
const hasUserVersion = leafCommand.#options.some(o => o.long === 'version');
|
|
317
354
|
if (!hasUserHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
|
|
318
|
-
|
|
355
|
+
const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs);
|
|
356
|
+
console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
319
357
|
return;
|
|
320
358
|
}
|
|
321
359
|
if (!hasUserVersion && leafCommand === rootCommand && leafCommand.#version) {
|
|
@@ -342,7 +380,8 @@ class Command {
|
|
|
342
380
|
await leafCommand.#runAction(actionParams);
|
|
343
381
|
}
|
|
344
382
|
else if (leafCommand.#subcommandsList.length > 0) {
|
|
345
|
-
|
|
383
|
+
const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs);
|
|
384
|
+
console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
|
|
346
385
|
}
|
|
347
386
|
else {
|
|
348
387
|
throw new CommanderError('ConfigurationError', `no action defined for command "${leafCommand.#getCommandPath()}"`, leafCommand.#getCommandPath());
|
|
@@ -375,10 +414,21 @@ class Command {
|
|
|
375
414
|
return this.#parse(chain, resolveResult, ctx, restArgs);
|
|
376
415
|
}
|
|
377
416
|
formatHelp() {
|
|
378
|
-
|
|
417
|
+
return this.#renderHelpPlain(this.#buildHelpData());
|
|
418
|
+
}
|
|
419
|
+
#formatHelpForDisplay(params = {}) {
|
|
420
|
+
const { color = true } = params;
|
|
421
|
+
const helpData = this.#buildHelpData();
|
|
422
|
+
if (!this.#shouldRenderStyledHelp(color)) {
|
|
423
|
+
return this.#renderHelpPlain(helpData);
|
|
424
|
+
}
|
|
425
|
+
return this.#renderHelpTerminal(helpData);
|
|
426
|
+
}
|
|
427
|
+
#shouldRenderStyledHelp(color) {
|
|
428
|
+
return color && process.stdout.isTTY === true;
|
|
429
|
+
}
|
|
430
|
+
#buildHelpData() {
|
|
379
431
|
const allOptions = this.#getMergedOptions();
|
|
380
|
-
lines.push(this.#desc);
|
|
381
|
-
lines.push('');
|
|
382
432
|
const commandPath = this.#getCommandPath();
|
|
383
433
|
let usage = `Usage: ${commandPath}`;
|
|
384
434
|
if (allOptions.length > 0)
|
|
@@ -396,61 +446,122 @@ class Command {
|
|
|
396
446
|
usage += ` [${arg.name}...]`;
|
|
397
447
|
}
|
|
398
448
|
}
|
|
399
|
-
|
|
449
|
+
const options = [];
|
|
450
|
+
for (const opt of allOptions) {
|
|
451
|
+
const kebabLong = camelToKebabCase$1(opt.long);
|
|
452
|
+
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
453
|
+
sig += `--${kebabLong}`;
|
|
454
|
+
if (opt.args !== 'none') {
|
|
455
|
+
sig += ' <value>';
|
|
456
|
+
}
|
|
457
|
+
let desc = opt.desc;
|
|
458
|
+
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
459
|
+
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
460
|
+
}
|
|
461
|
+
if (opt.choices) {
|
|
462
|
+
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
463
|
+
}
|
|
464
|
+
options.push({ sig, desc });
|
|
465
|
+
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
466
|
+
options.push({
|
|
467
|
+
sig: ` --no-${kebabLong}`,
|
|
468
|
+
desc: `Negate --${kebabLong}`,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const commands = [];
|
|
473
|
+
const showHelpSubcommand = this.#builtin.command.help && this.#subcommandsList.length > 0;
|
|
474
|
+
if (showHelpSubcommand) {
|
|
475
|
+
commands.push({ name: 'help', desc: 'Show help for a command' });
|
|
476
|
+
}
|
|
477
|
+
for (const entry of this.#subcommandsList) {
|
|
478
|
+
let name = entry.name;
|
|
479
|
+
if (entry.aliases.length > 0) {
|
|
480
|
+
name += `, ${entry.aliases.join(', ')}`;
|
|
481
|
+
}
|
|
482
|
+
commands.push({ name, desc: entry.command.#desc });
|
|
483
|
+
}
|
|
484
|
+
const examples = this.#examples.map(example => ({
|
|
485
|
+
title: example.title,
|
|
486
|
+
usage: commandPath ? `${commandPath} ${example.usage}` : example.usage,
|
|
487
|
+
desc: example.desc,
|
|
488
|
+
}));
|
|
489
|
+
return {
|
|
490
|
+
desc: this.#desc,
|
|
491
|
+
usage,
|
|
492
|
+
options,
|
|
493
|
+
commands,
|
|
494
|
+
examples,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
#renderHelpPlain(helpData) {
|
|
498
|
+
const lines = [];
|
|
499
|
+
lines.push(helpData.desc);
|
|
400
500
|
lines.push('');
|
|
401
|
-
|
|
501
|
+
lines.push(helpData.usage);
|
|
502
|
+
lines.push('');
|
|
503
|
+
if (helpData.options.length > 0) {
|
|
402
504
|
lines.push('Options:');
|
|
403
|
-
const
|
|
404
|
-
for (const
|
|
405
|
-
const kebabLong = camelToKebabCase$1(opt.long);
|
|
406
|
-
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
407
|
-
sig += `--${kebabLong}`;
|
|
408
|
-
if (opt.args !== 'none') {
|
|
409
|
-
sig += ' <value>';
|
|
410
|
-
}
|
|
411
|
-
let desc = opt.desc;
|
|
412
|
-
if (opt.default !== undefined && opt.type !== 'boolean') {
|
|
413
|
-
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
414
|
-
}
|
|
415
|
-
if (opt.choices) {
|
|
416
|
-
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
417
|
-
}
|
|
418
|
-
optLines.push({ sig, desc });
|
|
419
|
-
if (opt.type === 'boolean' && opt.args === 'none') {
|
|
420
|
-
optLines.push({
|
|
421
|
-
sig: ` --no-${kebabLong}`,
|
|
422
|
-
desc: `Negate --${kebabLong}`,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const maxSigLen = Math.max(...optLines.map(l => l.sig.length));
|
|
427
|
-
for (const { sig, desc } of optLines) {
|
|
505
|
+
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
506
|
+
for (const { sig, desc } of helpData.options) {
|
|
428
507
|
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
429
508
|
lines.push(` ${sig}${padding}${desc}`);
|
|
430
509
|
}
|
|
431
510
|
lines.push('');
|
|
432
511
|
}
|
|
433
|
-
|
|
434
|
-
if (this.#subcommandsList.length > 0) {
|
|
512
|
+
if (helpData.commands.length > 0) {
|
|
435
513
|
lines.push('Commands:');
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
cmdLines.push({ name: 'help', desc: 'Show help for a command' });
|
|
439
|
-
}
|
|
440
|
-
for (const entry of this.#subcommandsList) {
|
|
441
|
-
let name = entry.name;
|
|
442
|
-
if (entry.aliases.length > 0) {
|
|
443
|
-
name += `, ${entry.aliases.join(', ')}`;
|
|
444
|
-
}
|
|
445
|
-
cmdLines.push({ name, desc: entry.command.#desc });
|
|
446
|
-
}
|
|
447
|
-
const maxNameLen = Math.max(...cmdLines.map(l => l.name.length));
|
|
448
|
-
for (const { name, desc } of cmdLines) {
|
|
514
|
+
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
515
|
+
for (const { name, desc } of helpData.commands) {
|
|
449
516
|
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
450
517
|
lines.push(` ${name}${padding}${desc}`);
|
|
451
518
|
}
|
|
452
519
|
lines.push('');
|
|
453
520
|
}
|
|
521
|
+
if (helpData.examples.length > 0) {
|
|
522
|
+
lines.push('Examples:');
|
|
523
|
+
for (const example of helpData.examples) {
|
|
524
|
+
lines.push(` - ${example.title}`);
|
|
525
|
+
lines.push(` ${example.usage}`);
|
|
526
|
+
lines.push(` ${example.desc}`);
|
|
527
|
+
lines.push('');
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return lines.join('\n');
|
|
531
|
+
}
|
|
532
|
+
#renderHelpTerminal(helpData) {
|
|
533
|
+
const lines = [];
|
|
534
|
+
lines.push(helpData.desc);
|
|
535
|
+
lines.push('');
|
|
536
|
+
lines.push(styleText(helpData.usage, TERMINAL_STYLE.bold));
|
|
537
|
+
lines.push('');
|
|
538
|
+
if (helpData.options.length > 0) {
|
|
539
|
+
lines.push(styleText('Options:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
540
|
+
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
541
|
+
for (const { sig, desc } of helpData.options) {
|
|
542
|
+
const padding = ' '.repeat(maxSigLen - sig.length + 2);
|
|
543
|
+
lines.push(` ${styleText(sig, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
544
|
+
}
|
|
545
|
+
lines.push('');
|
|
546
|
+
}
|
|
547
|
+
if (helpData.commands.length > 0) {
|
|
548
|
+
lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
549
|
+
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
550
|
+
for (const { name, desc } of helpData.commands) {
|
|
551
|
+
const padding = ' '.repeat(maxNameLen - name.length + 2);
|
|
552
|
+
lines.push(` ${styleText(name, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
553
|
+
}
|
|
554
|
+
lines.push('');
|
|
555
|
+
}
|
|
556
|
+
if (helpData.examples.length > 0) {
|
|
557
|
+
lines.push(styleText('Examples:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
558
|
+
for (const example of helpData.examples) {
|
|
559
|
+
lines.push(` - ${styleText(example.title, TERMINAL_STYLE.bold)}`);
|
|
560
|
+
lines.push(` ${styleText(example.usage, TERMINAL_STYLE.cyan)}`);
|
|
561
|
+
lines.push(` ${styleText(example.desc, TERMINAL_STYLE.italic, TERMINAL_STYLE.dim)}`);
|
|
562
|
+
lines.push('');
|
|
563
|
+
}
|
|
564
|
+
}
|
|
454
565
|
return lines.join('\n');
|
|
455
566
|
}
|
|
456
567
|
getCompletionMeta() {
|
|
@@ -614,7 +725,7 @@ class Command {
|
|
|
614
725
|
const cmd = chain[i];
|
|
615
726
|
const includeVersion = i === 0;
|
|
616
727
|
const tokens = consumedTokens.get(cmd) ?? [];
|
|
617
|
-
const opts = cmd.#parseOptions(tokens, includeVersion);
|
|
728
|
+
const opts = cmd.#parseOptions(tokens, includeVersion, ctx.envs);
|
|
618
729
|
optsMap.set(cmd, opts);
|
|
619
730
|
for (const opt of cmd.#getMergedOptions(includeVersion)) {
|
|
620
731
|
if (opt.apply && opts[opt.long] !== undefined) {
|
|
@@ -630,9 +741,10 @@ class Command {
|
|
|
630
741
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
631
742
|
return { ctx, opts: mergedOpts, args, rawArgs };
|
|
632
743
|
}
|
|
633
|
-
#parseOptions(tokens, includeVersion) {
|
|
744
|
+
#parseOptions(tokens, includeVersion, envs) {
|
|
634
745
|
const allOptions = this.#getMergedOptions(includeVersion);
|
|
635
746
|
const opts = {};
|
|
747
|
+
let sawColorToken = false;
|
|
636
748
|
for (const opt of allOptions) {
|
|
637
749
|
if (opt.default !== undefined) {
|
|
638
750
|
opts[opt.long] = opt.default;
|
|
@@ -660,6 +772,9 @@ class Command {
|
|
|
660
772
|
i += 1;
|
|
661
773
|
continue;
|
|
662
774
|
}
|
|
775
|
+
if (opt.long === 'color') {
|
|
776
|
+
sawColorToken = true;
|
|
777
|
+
}
|
|
663
778
|
const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
|
|
664
779
|
if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
|
|
665
780
|
throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
|
|
@@ -736,6 +851,9 @@ class Command {
|
|
|
736
851
|
}
|
|
737
852
|
}
|
|
738
853
|
}
|
|
854
|
+
if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
|
|
855
|
+
opts['color'] = false;
|
|
856
|
+
}
|
|
739
857
|
return opts;
|
|
740
858
|
}
|
|
741
859
|
#convertValue(opt, rawValue) {
|
|
@@ -808,12 +926,16 @@ class Command {
|
|
|
808
926
|
}
|
|
809
927
|
#getMergedOptions(includeVersion = !this.#parent) {
|
|
810
928
|
const optionMap = new Map();
|
|
929
|
+
const hasUserColor = this.#options.some(o => o.long === 'color');
|
|
811
930
|
const hasUserHelp = this.#options.some(o => o.long === 'help');
|
|
812
931
|
const hasUserVersion = this.#options.some(o => o.long === 'version');
|
|
813
932
|
const hasUserLogLevel = this.#options.some(o => o.long === 'logLevel');
|
|
814
933
|
const hasUserSilent = this.#options.some(o => o.long === 'silent');
|
|
815
934
|
const hasUserLogDate = this.#options.some(o => o.long === 'logDate');
|
|
816
935
|
const hasUserLogColorful = this.#options.some(o => o.long === 'logColorful');
|
|
936
|
+
if (this.#builtin.option.color && !hasUserColor) {
|
|
937
|
+
optionMap.set('color', BUILTIN_COLOR_OPTION);
|
|
938
|
+
}
|
|
817
939
|
if (!hasUserHelp) {
|
|
818
940
|
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
819
941
|
}
|
|
@@ -907,6 +1029,21 @@ class Command {
|
|
|
907
1029
|
}
|
|
908
1030
|
}
|
|
909
1031
|
}
|
|
1032
|
+
#normalizeExample(example) {
|
|
1033
|
+
const title = example.title.trim();
|
|
1034
|
+
const usage = example.usage.trim();
|
|
1035
|
+
const desc = example.desc.trim();
|
|
1036
|
+
if (!title) {
|
|
1037
|
+
throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
|
|
1038
|
+
}
|
|
1039
|
+
if (!usage) {
|
|
1040
|
+
throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
|
|
1041
|
+
}
|
|
1042
|
+
if (!desc) {
|
|
1043
|
+
throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
|
|
1044
|
+
}
|
|
1045
|
+
return { title, usage, desc };
|
|
1046
|
+
}
|
|
910
1047
|
async #runAction(params) {
|
|
911
1048
|
if (!this.#action)
|
|
912
1049
|
return;
|
|
@@ -923,6 +1060,34 @@ class Command {
|
|
|
923
1060
|
process.exit(1);
|
|
924
1061
|
}
|
|
925
1062
|
}
|
|
1063
|
+
#resolveHelpColorOption(tokens, envs) {
|
|
1064
|
+
const colorOption = this.#getMergedOptions().find(opt => opt.long === 'color');
|
|
1065
|
+
let color = !isNoColorEnabled(envs);
|
|
1066
|
+
if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
|
|
1067
|
+
return color;
|
|
1068
|
+
}
|
|
1069
|
+
for (const token of tokens) {
|
|
1070
|
+
if (token.type !== 'long' || token.name !== 'color') {
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1074
|
+
if (eqIdx === -1) {
|
|
1075
|
+
color = true;
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
const value = token.resolved.slice(eqIdx + 1);
|
|
1079
|
+
if (value === 'true') {
|
|
1080
|
+
color = true;
|
|
1081
|
+
}
|
|
1082
|
+
else if (value === 'false') {
|
|
1083
|
+
color = false;
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--color". Use "true" or "false"`, this.#getCommandPath());
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return color;
|
|
1090
|
+
}
|
|
926
1091
|
#hasFlag(tokens, longName, shortName) {
|
|
927
1092
|
for (const token of tokens) {
|
|
928
1093
|
if (token.type === 'long' && token.name === longName) {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -93,6 +93,8 @@ interface ICommandArgumentConfig<T = unknown> {
|
|
|
93
93
|
coerce?: (rawValue: string) => T;
|
|
94
94
|
}
|
|
95
95
|
interface ICommandBuiltinOptionConfig {
|
|
96
|
+
/** Enable built-in --color/--no-color option for help rendering (defaults respect NO_COLOR) */
|
|
97
|
+
color?: boolean;
|
|
96
98
|
/** Enable built-in --log-level option */
|
|
97
99
|
logLevel?: boolean;
|
|
98
100
|
/** Enable built-in --silent option */
|
|
@@ -112,6 +114,15 @@ interface ICommandBuiltinConfig {
|
|
|
112
114
|
/** Built-in command configuration */
|
|
113
115
|
command?: boolean | ICommandBuiltinCommandConfig;
|
|
114
116
|
}
|
|
117
|
+
/** Command example configuration */
|
|
118
|
+
interface ICommandExample {
|
|
119
|
+
/** Example title */
|
|
120
|
+
title: string;
|
|
121
|
+
/** Usage fragment relative to command path */
|
|
122
|
+
usage: string;
|
|
123
|
+
/** Example description */
|
|
124
|
+
desc: string;
|
|
125
|
+
}
|
|
115
126
|
/** Command configuration */
|
|
116
127
|
interface ICommandConfig {
|
|
117
128
|
/** Command name (only for root command) */
|
|
@@ -133,6 +144,7 @@ interface ICommand {
|
|
|
133
144
|
readonly parent: ICommand | undefined;
|
|
134
145
|
readonly options: ICommandOptionConfig[];
|
|
135
146
|
readonly arguments: ICommandArgumentConfig[];
|
|
147
|
+
readonly examples: ICommandExample[];
|
|
136
148
|
readonly subcommands: Map<string, ICommand>;
|
|
137
149
|
}
|
|
138
150
|
/** Execution context */
|
|
@@ -283,10 +295,12 @@ declare class Command implements ICommand {
|
|
|
283
295
|
get parent(): Command | undefined;
|
|
284
296
|
get options(): ICommandOptionConfig[];
|
|
285
297
|
get arguments(): ICommandArgumentConfig[];
|
|
298
|
+
get examples(): ICommandExample[];
|
|
286
299
|
get subcommands(): Map<string, ICommand>;
|
|
287
300
|
option<T>(opt: ICommandOptionConfig<T>): this;
|
|
288
301
|
argument<T>(arg: ICommandArgumentConfig<T>): this;
|
|
289
302
|
action(fn: ICommandAction): this;
|
|
303
|
+
example(title: string, usage: string, desc: string): this;
|
|
290
304
|
subcommand(name: string, cmd: Command): this;
|
|
291
305
|
run(params: ICommandRunParams): Promise<void>;
|
|
292
306
|
parse(params: ICommandRunParams): ICommandParseResult;
|
|
@@ -423,4 +437,4 @@ declare const logColorfulOption: ICommandOptionConfig<boolean>;
|
|
|
423
437
|
declare const silentOption: ICommandOptionConfig<boolean>;
|
|
424
438
|
|
|
425
439
|
export { BashCompletion, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, logColorfulOption, logDateOption, logLevelOption, silentOption };
|
|
426
|
-
export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandBuiltinCommandConfig, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandConfig, ICommandContext, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandShiftResult, ICommandToken, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType };
|
|
440
|
+
export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandBuiltinCommandConfig, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandConfig, ICommandContext, ICommandExample, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandShiftResult, ICommandToken, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType };
|