@guanghechen/commander 4.2.0 → 4.4.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - enforce per-node help subcommand semantics
8
+
9
+ ## 4.3.0
10
+
11
+ ### Minor Changes
12
+
13
+ - feat: add fluent help examples and styled help renderer
14
+
3
15
  ## 4.2.0
4
16
 
5
17
  ### Minor Changes
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: { logLevel: true, silent: true, logDate: true, logColorful: true },
236
+ option: createBuiltinOptionState(true),
211
237
  command: { help: true },
212
238
  };
213
239
  }
214
240
  if (builtin === false) {
215
241
  return {
216
- option: { logLevel: false, silent: false, logDate: false, logColorful: false },
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 = { logLevel: false, silent: false, logDate: false, logColorful: false };
248
+ resolved.option = createBuiltinOptionState(false);
223
249
  }
224
250
  else if (builtin.option === true) {
225
- resolved.option = { logLevel: true, silent: true, logDate: true, logColorful: true };
251
+ resolved.option = createBuiltinOptionState(true);
226
252
  }
227
253
  else {
228
- if (builtin.option.logLevel !== undefined)
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
- console.log(leafCommand.formatHelp());
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
- console.log(leafCommand.formatHelp());
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
- const lines = [];
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
- lines.push(usage);
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);
522
+ lines.push('');
523
+ lines.push(helpData.usage);
422
524
  lines.push('');
423
- if (allOptions.length > 0) {
525
+ if (helpData.options.length > 0) {
424
526
  lines.push('Options:');
425
- const optLines = [];
426
- for (const opt of allOptions) {
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
- const showHelpSubcommand = this.#builtin.command.help && this.#subcommandsList.length > 0;
456
- if (this.#subcommandsList.length > 0) {
534
+ if (helpData.commands.length > 0) {
457
535
  lines.push('Commands:');
458
- const cmdLines = [];
459
- if (showHelpSubcommand) {
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() {
@@ -502,18 +613,48 @@ class Command {
502
613
  }),
503
614
  };
504
615
  }
616
+ #findSubcommandEntry(token) {
617
+ return this.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
618
+ }
619
+ #createUnknownSubcommandError(subcommand) {
620
+ const commandPath = this.#getCommandPath();
621
+ return new CommanderError('UnknownSubcommand', `unknown subcommand "${subcommand}" for command "${commandPath}"`, commandPath);
622
+ }
505
623
  #processHelpSubcommand(argv) {
506
- if (!this.#builtin.command.help)
507
- return argv;
508
- if (argv.length < 1 || argv[0] !== 'help')
509
- return argv;
510
- if (argv.length === 1 || this.#subcommandsList.length === 0) {
511
- return ['--help'];
512
- }
513
- const subName = argv[1];
514
- const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
515
- if (entry) {
516
- return [subName, '--help', ...argv.slice(2)];
624
+ let current = this;
625
+ for (let i = 0; i < argv.length; ++i) {
626
+ const token = argv[i];
627
+ if (token.startsWith('-')) {
628
+ return argv;
629
+ }
630
+ if (token === 'help') {
631
+ if (!current.#builtin.command.help) {
632
+ if (current.#subcommandsList.length > 0) {
633
+ throw current.#createUnknownSubcommandError('help');
634
+ }
635
+ return argv;
636
+ }
637
+ if (current.#subcommandsList.length === 0) {
638
+ return argv;
639
+ }
640
+ const target = argv[i + 1];
641
+ if (target === undefined) {
642
+ return [...argv.slice(0, i), '--help'];
643
+ }
644
+ const targetEntry = current.#findSubcommandEntry(target);
645
+ if (targetEntry === undefined) {
646
+ throw current.#createUnknownSubcommandError(target);
647
+ }
648
+ if (argv[i + 2] !== undefined) {
649
+ throw new CommanderError('UnexpectedArgument', 'help subcommand accepts at most one subcommand argument', current.#getCommandPath());
650
+ }
651
+ return [...argv.slice(0, i), target, '--help'];
652
+ }
653
+ const entry = current.#findSubcommandEntry(token);
654
+ if (entry === undefined) {
655
+ return argv;
656
+ }
657
+ current = entry.command;
517
658
  }
518
659
  return argv;
519
660
  }
@@ -525,7 +666,7 @@ class Command {
525
666
  const token = argv[idx];
526
667
  if (token.startsWith('-'))
527
668
  break;
528
- const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
669
+ const entry = current.#findSubcommandEntry(token);
529
670
  if (!entry)
530
671
  break;
531
672
  current = entry.command;
@@ -636,7 +777,7 @@ class Command {
636
777
  const cmd = chain[i];
637
778
  const includeVersion = i === 0;
638
779
  const tokens = consumedTokens.get(cmd) ?? [];
639
- const opts = cmd.#parseOptions(tokens, includeVersion);
780
+ const opts = cmd.#parseOptions(tokens, includeVersion, ctx.envs);
640
781
  optsMap.set(cmd, opts);
641
782
  for (const opt of cmd.#getMergedOptions(includeVersion)) {
642
783
  if (opt.apply && opts[opt.long] !== undefined) {
@@ -652,9 +793,10 @@ class Command {
652
793
  const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
653
794
  return { ctx, opts: mergedOpts, args, rawArgs };
654
795
  }
655
- #parseOptions(tokens, includeVersion) {
796
+ #parseOptions(tokens, includeVersion, envs) {
656
797
  const allOptions = this.#getMergedOptions(includeVersion);
657
798
  const opts = {};
799
+ let sawColorToken = false;
658
800
  for (const opt of allOptions) {
659
801
  if (opt.default !== undefined) {
660
802
  opts[opt.long] = opt.default;
@@ -682,6 +824,9 @@ class Command {
682
824
  i += 1;
683
825
  continue;
684
826
  }
827
+ if (opt.long === 'color') {
828
+ sawColorToken = true;
829
+ }
685
830
  const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
686
831
  if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
687
832
  throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
@@ -758,6 +903,9 @@ class Command {
758
903
  }
759
904
  }
760
905
  }
906
+ if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
907
+ opts['color'] = false;
908
+ }
761
909
  return opts;
762
910
  }
763
911
  #convertValue(opt, rawValue) {
@@ -830,12 +978,16 @@ class Command {
830
978
  }
831
979
  #getMergedOptions(includeVersion = !this.#parent) {
832
980
  const optionMap = new Map();
981
+ const hasUserColor = this.#options.some(o => o.long === 'color');
833
982
  const hasUserHelp = this.#options.some(o => o.long === 'help');
834
983
  const hasUserVersion = this.#options.some(o => o.long === 'version');
835
984
  const hasUserLogLevel = this.#options.some(o => o.long === 'logLevel');
836
985
  const hasUserSilent = this.#options.some(o => o.long === 'silent');
837
986
  const hasUserLogDate = this.#options.some(o => o.long === 'logDate');
838
987
  const hasUserLogColorful = this.#options.some(o => o.long === 'logColorful');
988
+ if (this.#builtin.option.color && !hasUserColor) {
989
+ optionMap.set('color', BUILTIN_COLOR_OPTION);
990
+ }
839
991
  if (!hasUserHelp) {
840
992
  optionMap.set('help', BUILTIN_HELP_OPTION);
841
993
  }
@@ -929,6 +1081,21 @@ class Command {
929
1081
  }
930
1082
  }
931
1083
  }
1084
+ #normalizeExample(example) {
1085
+ const title = example.title.trim();
1086
+ const usage = example.usage.trim();
1087
+ const desc = example.desc.trim();
1088
+ if (!title) {
1089
+ throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
1090
+ }
1091
+ if (!usage) {
1092
+ throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
1093
+ }
1094
+ if (!desc) {
1095
+ throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
1096
+ }
1097
+ return { title, usage, desc };
1098
+ }
932
1099
  async #runAction(params) {
933
1100
  if (!this.#action)
934
1101
  return;
@@ -945,6 +1112,34 @@ class Command {
945
1112
  process.exit(1);
946
1113
  }
947
1114
  }
1115
+ #resolveHelpColorOption(tokens, envs) {
1116
+ const colorOption = this.#getMergedOptions().find(opt => opt.long === 'color');
1117
+ let color = !isNoColorEnabled(envs);
1118
+ if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
1119
+ return color;
1120
+ }
1121
+ for (const token of tokens) {
1122
+ if (token.type !== 'long' || token.name !== 'color') {
1123
+ continue;
1124
+ }
1125
+ const eqIdx = token.resolved.indexOf('=');
1126
+ if (eqIdx === -1) {
1127
+ color = true;
1128
+ continue;
1129
+ }
1130
+ const value = token.resolved.slice(eqIdx + 1);
1131
+ if (value === 'true') {
1132
+ color = true;
1133
+ }
1134
+ else if (value === 'false') {
1135
+ color = false;
1136
+ }
1137
+ else {
1138
+ throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--color". Use "true" or "false"`, this.#getCommandPath());
1139
+ }
1140
+ }
1141
+ return color;
1142
+ }
948
1143
  #hasFlag(tokens, longName, shortName) {
949
1144
  for (const token of tokens) {
950
1145
  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: { logLevel: true, silent: true, logDate: true, logColorful: true },
214
+ option: createBuiltinOptionState(true),
189
215
  command: { help: true },
190
216
  };
191
217
  }
192
218
  if (builtin === false) {
193
219
  return {
194
- option: { logLevel: false, silent: false, logDate: false, logColorful: false },
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 = { logLevel: false, silent: false, logDate: false, logColorful: false };
226
+ resolved.option = createBuiltinOptionState(false);
201
227
  }
202
228
  else if (builtin.option === true) {
203
- resolved.option = { logLevel: true, silent: true, logDate: true, logColorful: true };
229
+ resolved.option = createBuiltinOptionState(true);
204
230
  }
205
231
  else {
206
- if (builtin.option.logLevel !== undefined)
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
- console.log(leafCommand.formatHelp());
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
- console.log(leafCommand.formatHelp());
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
- const lines = [];
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
- lines.push(usage);
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
- if (allOptions.length > 0) {
501
+ lines.push(helpData.usage);
502
+ lines.push('');
503
+ if (helpData.options.length > 0) {
402
504
  lines.push('Options:');
403
- const optLines = [];
404
- for (const opt of allOptions) {
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
- const showHelpSubcommand = this.#builtin.command.help && this.#subcommandsList.length > 0;
434
- if (this.#subcommandsList.length > 0) {
512
+ if (helpData.commands.length > 0) {
435
513
  lines.push('Commands:');
436
- const cmdLines = [];
437
- if (showHelpSubcommand) {
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() {
@@ -480,18 +591,48 @@ class Command {
480
591
  }),
481
592
  };
482
593
  }
594
+ #findSubcommandEntry(token) {
595
+ return this.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
596
+ }
597
+ #createUnknownSubcommandError(subcommand) {
598
+ const commandPath = this.#getCommandPath();
599
+ return new CommanderError('UnknownSubcommand', `unknown subcommand "${subcommand}" for command "${commandPath}"`, commandPath);
600
+ }
483
601
  #processHelpSubcommand(argv) {
484
- if (!this.#builtin.command.help)
485
- return argv;
486
- if (argv.length < 1 || argv[0] !== 'help')
487
- return argv;
488
- if (argv.length === 1 || this.#subcommandsList.length === 0) {
489
- return ['--help'];
490
- }
491
- const subName = argv[1];
492
- const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
493
- if (entry) {
494
- return [subName, '--help', ...argv.slice(2)];
602
+ let current = this;
603
+ for (let i = 0; i < argv.length; ++i) {
604
+ const token = argv[i];
605
+ if (token.startsWith('-')) {
606
+ return argv;
607
+ }
608
+ if (token === 'help') {
609
+ if (!current.#builtin.command.help) {
610
+ if (current.#subcommandsList.length > 0) {
611
+ throw current.#createUnknownSubcommandError('help');
612
+ }
613
+ return argv;
614
+ }
615
+ if (current.#subcommandsList.length === 0) {
616
+ return argv;
617
+ }
618
+ const target = argv[i + 1];
619
+ if (target === undefined) {
620
+ return [...argv.slice(0, i), '--help'];
621
+ }
622
+ const targetEntry = current.#findSubcommandEntry(target);
623
+ if (targetEntry === undefined) {
624
+ throw current.#createUnknownSubcommandError(target);
625
+ }
626
+ if (argv[i + 2] !== undefined) {
627
+ throw new CommanderError('UnexpectedArgument', 'help subcommand accepts at most one subcommand argument', current.#getCommandPath());
628
+ }
629
+ return [...argv.slice(0, i), target, '--help'];
630
+ }
631
+ const entry = current.#findSubcommandEntry(token);
632
+ if (entry === undefined) {
633
+ return argv;
634
+ }
635
+ current = entry.command;
495
636
  }
496
637
  return argv;
497
638
  }
@@ -503,7 +644,7 @@ class Command {
503
644
  const token = argv[idx];
504
645
  if (token.startsWith('-'))
505
646
  break;
506
- const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
647
+ const entry = current.#findSubcommandEntry(token);
507
648
  if (!entry)
508
649
  break;
509
650
  current = entry.command;
@@ -614,7 +755,7 @@ class Command {
614
755
  const cmd = chain[i];
615
756
  const includeVersion = i === 0;
616
757
  const tokens = consumedTokens.get(cmd) ?? [];
617
- const opts = cmd.#parseOptions(tokens, includeVersion);
758
+ const opts = cmd.#parseOptions(tokens, includeVersion, ctx.envs);
618
759
  optsMap.set(cmd, opts);
619
760
  for (const opt of cmd.#getMergedOptions(includeVersion)) {
620
761
  if (opt.apply && opts[opt.long] !== undefined) {
@@ -630,9 +771,10 @@ class Command {
630
771
  const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
631
772
  return { ctx, opts: mergedOpts, args, rawArgs };
632
773
  }
633
- #parseOptions(tokens, includeVersion) {
774
+ #parseOptions(tokens, includeVersion, envs) {
634
775
  const allOptions = this.#getMergedOptions(includeVersion);
635
776
  const opts = {};
777
+ let sawColorToken = false;
636
778
  for (const opt of allOptions) {
637
779
  if (opt.default !== undefined) {
638
780
  opts[opt.long] = opt.default;
@@ -660,6 +802,9 @@ class Command {
660
802
  i += 1;
661
803
  continue;
662
804
  }
805
+ if (opt.long === 'color') {
806
+ sawColorToken = true;
807
+ }
663
808
  const isNegativeToken = token.original.toLowerCase().startsWith('--no-');
664
809
  if (isNegativeToken && !(opt.type === 'boolean' && opt.args === 'none')) {
665
810
  throw new CommanderError('NegativeOptionType', `"--no-${camelToKebabCase$1(opt.long)}" can only be used with boolean options`, this.#getCommandPath());
@@ -736,6 +881,9 @@ class Command {
736
881
  }
737
882
  }
738
883
  }
884
+ if (isNoColorEnabled(envs) && !sawColorToken && opts['color'] === true) {
885
+ opts['color'] = false;
886
+ }
739
887
  return opts;
740
888
  }
741
889
  #convertValue(opt, rawValue) {
@@ -808,12 +956,16 @@ class Command {
808
956
  }
809
957
  #getMergedOptions(includeVersion = !this.#parent) {
810
958
  const optionMap = new Map();
959
+ const hasUserColor = this.#options.some(o => o.long === 'color');
811
960
  const hasUserHelp = this.#options.some(o => o.long === 'help');
812
961
  const hasUserVersion = this.#options.some(o => o.long === 'version');
813
962
  const hasUserLogLevel = this.#options.some(o => o.long === 'logLevel');
814
963
  const hasUserSilent = this.#options.some(o => o.long === 'silent');
815
964
  const hasUserLogDate = this.#options.some(o => o.long === 'logDate');
816
965
  const hasUserLogColorful = this.#options.some(o => o.long === 'logColorful');
966
+ if (this.#builtin.option.color && !hasUserColor) {
967
+ optionMap.set('color', BUILTIN_COLOR_OPTION);
968
+ }
817
969
  if (!hasUserHelp) {
818
970
  optionMap.set('help', BUILTIN_HELP_OPTION);
819
971
  }
@@ -907,6 +1059,21 @@ class Command {
907
1059
  }
908
1060
  }
909
1061
  }
1062
+ #normalizeExample(example) {
1063
+ const title = example.title.trim();
1064
+ const usage = example.usage.trim();
1065
+ const desc = example.desc.trim();
1066
+ if (!title) {
1067
+ throw new CommanderError('ConfigurationError', 'example title cannot be empty', this.#getCommandPath());
1068
+ }
1069
+ if (!usage) {
1070
+ throw new CommanderError('ConfigurationError', 'example usage cannot be empty', this.#getCommandPath());
1071
+ }
1072
+ if (!desc) {
1073
+ throw new CommanderError('ConfigurationError', 'example description cannot be empty', this.#getCommandPath());
1074
+ }
1075
+ return { title, usage, desc };
1076
+ }
910
1077
  async #runAction(params) {
911
1078
  if (!this.#action)
912
1079
  return;
@@ -923,6 +1090,34 @@ class Command {
923
1090
  process.exit(1);
924
1091
  }
925
1092
  }
1093
+ #resolveHelpColorOption(tokens, envs) {
1094
+ const colorOption = this.#getMergedOptions().find(opt => opt.long === 'color');
1095
+ let color = !isNoColorEnabled(envs);
1096
+ if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
1097
+ return color;
1098
+ }
1099
+ for (const token of tokens) {
1100
+ if (token.type !== 'long' || token.name !== 'color') {
1101
+ continue;
1102
+ }
1103
+ const eqIdx = token.resolved.indexOf('=');
1104
+ if (eqIdx === -1) {
1105
+ color = true;
1106
+ continue;
1107
+ }
1108
+ const value = token.resolved.slice(eqIdx + 1);
1109
+ if (value === 'true') {
1110
+ color = true;
1111
+ }
1112
+ else if (value === 'false') {
1113
+ color = false;
1114
+ }
1115
+ else {
1116
+ throw new CommanderError('InvalidBooleanValue', `invalid value "${value}" for boolean option "--color". Use "true" or "false"`, this.#getCommandPath());
1117
+ }
1118
+ }
1119
+ return color;
1120
+ }
926
1121
  #hasFlag(tokens, longName, shortName) {
927
1122
  for (const token of tokens) {
928
1123
  if (token.type === 'long' && token.name === longName) {
@@ -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 */
@@ -212,7 +224,7 @@ interface ICommandParseResult {
212
224
  rawArgs: string[];
213
225
  }
214
226
  /** Error kinds for command parsing */
215
- type ICommanderErrorKind = 'InvalidOptionFormat' | 'InvalidNegativeOption' | 'NegativeOptionWithValue' | 'NegativeOptionType' | 'UnknownOption' | 'UnexpectedArgument' | 'MissingValue' | 'InvalidType' | 'UnsupportedShortSyntax' | 'OptionConflict' | 'MissingRequired' | 'InvalidChoice' | 'InvalidBooleanValue' | 'MissingRequiredArgument' | 'TooManyArguments' | 'ConfigurationError';
227
+ type ICommanderErrorKind = 'InvalidOptionFormat' | 'InvalidNegativeOption' | 'NegativeOptionWithValue' | 'NegativeOptionType' | 'UnknownOption' | 'UnknownSubcommand' | 'UnexpectedArgument' | 'MissingValue' | 'InvalidType' | 'UnsupportedShortSyntax' | 'OptionConflict' | 'MissingRequired' | 'InvalidChoice' | 'InvalidBooleanValue' | 'MissingRequiredArgument' | 'TooManyArguments' | 'ConfigurationError';
216
228
  /** Commander error with structured information */
217
229
  declare class CommanderError extends Error {
218
230
  readonly kind: ICommanderErrorKind;
@@ -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanghechen/commander",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "description": "A minimal, type-safe command-line interface builder with fluent API",
5
5
  "author": {
6
6
  "name": "guanghechen",