@guanghechen/commander 4.5.1 → 4.7.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.
@@ -1,28 +1,98 @@
1
1
  'use strict';
2
2
 
3
+ var promises = require('node:fs/promises');
4
+ var path = require('node:path');
5
+ var env = require('@guanghechen/env');
3
6
  var reporter = require('@guanghechen/reporter');
4
7
  var fs = require('node:fs');
5
- var path = require('node:path');
6
8
 
7
- function _interopNamespaceDefault(e) {
8
- var n = Object.create(null);
9
- if (e) {
10
- Object.keys(e).forEach(function (k) {
11
- if (k !== 'default') {
12
- var d = Object.getOwnPropertyDescriptor(e, k);
13
- Object.defineProperty(n, k, d.get ? d : {
14
- enumerable: true,
15
- get: function () { return e[k]; }
16
- });
9
+ const WINDOWS_DRIVE_ABSOLUTE_REGEX = /^[a-zA-Z]:[\\/]/;
10
+ function isAbsolutePath(filepath) {
11
+ return (filepath.startsWith('/') ||
12
+ filepath.startsWith('\\\\') ||
13
+ WINDOWS_DRIVE_ABSOLUTE_REGEX.test(filepath));
14
+ }
15
+ function resolvePathFrom(base, fragment) {
16
+ const useWindowsStyle = WINDOWS_DRIVE_ABSOLUTE_REGEX.test(base);
17
+ const normalizedBase = base.replace(/\\/g, '/');
18
+ const normalizedFragment = fragment.replace(/\\/g, '/');
19
+ const source = isAbsolutePath(normalizedFragment)
20
+ ? normalizedFragment
21
+ : `${normalizedBase.replace(/\/$/, '')}/${normalizedFragment}`;
22
+ const prefix = useWindowsStyle ? source.slice(0, 2) : '';
23
+ const body = useWindowsStyle ? source.slice(2) : source;
24
+ const stack = [];
25
+ for (const token of body.split('/')) {
26
+ if (token === '' || token === '.') {
27
+ continue;
28
+ }
29
+ if (token === '..') {
30
+ if (stack.length > 0) {
31
+ stack.pop();
17
32
  }
18
- });
33
+ continue;
34
+ }
35
+ stack.push(token);
36
+ }
37
+ if (useWindowsStyle) {
38
+ const resolved = `${prefix}/${stack.join('/')}`;
39
+ return resolved.endsWith('/') ? resolved.slice(0, -1) : resolved;
40
+ }
41
+ return `/${stack.join('/')}`;
42
+ }
43
+ function createUnsupportedFsError(operation) {
44
+ return new Error(`runtime does not support file-system operation: ${operation}`);
45
+ }
46
+ function getFallbackCwd() {
47
+ const proc = globalThis.process;
48
+ if (proc && typeof proc.cwd === 'function') {
49
+ return proc.cwd();
19
50
  }
20
- n.default = e;
21
- return Object.freeze(n);
51
+ return '/';
52
+ }
53
+ function createBrowserCommandRuntime() {
54
+ return {
55
+ cwd: () => getFallbackCwd(),
56
+ isAbsolute: filepath => isAbsolutePath(filepath),
57
+ resolve: (...paths) => {
58
+ if (paths.length === 0) {
59
+ return getFallbackCwd();
60
+ }
61
+ let resolved = getFallbackCwd();
62
+ for (const path of paths) {
63
+ if (path.length === 0) {
64
+ continue;
65
+ }
66
+ resolved = resolvePathFrom(resolved, path);
67
+ }
68
+ return resolved;
69
+ },
70
+ readFile: async () => {
71
+ throw createUnsupportedFsError('readFile');
72
+ },
73
+ stat: async () => {
74
+ throw createUnsupportedFsError('stat');
75
+ },
76
+ };
77
+ }
78
+
79
+ let defaultRuntime = createBrowserCommandRuntime();
80
+ function getDefaultCommandRuntime() {
81
+ return defaultRuntime;
82
+ }
83
+ function setDefaultCommandRuntime(runtime) {
84
+ defaultRuntime = runtime;
22
85
  }
23
86
 
24
- var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
25
- var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
87
+ function createNodeCommandRuntime() {
88
+ return {
89
+ cwd: () => process.cwd(),
90
+ isAbsolute: filepath => path.isAbsolute(filepath),
91
+ resolve: (...paths) => path.resolve(...paths),
92
+ readFile: filepath => promises.readFile(filepath, 'utf8'),
93
+ stat: filepath => promises.stat(filepath),
94
+ };
95
+ }
26
96
 
27
97
  const TERMINAL_STYLE = {
28
98
  bold: '\x1b[1m',
@@ -36,22 +106,35 @@ function styleText(text, ...styles) {
36
106
  return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
37
107
  }
38
108
 
109
+ const BUILTIN_LOG_LEVELS = ['debug', 'info', 'hint', 'warn', 'error'];
110
+ function resolveReporterLogLevel(raw) {
111
+ const normalized = raw.trim().toLowerCase();
112
+ return BUILTIN_LOG_LEVELS.find(level => level === normalized);
113
+ }
114
+ function setReporterLevel(ctx, level) {
115
+ const reporter = ctx.reporter;
116
+ reporter?.setLevel?.(level);
117
+ }
118
+ function setReporterFlight(ctx, flight) {
119
+ const reporter = ctx.reporter;
120
+ reporter?.setFlight?.(flight);
121
+ }
39
122
  const logLevelOption = {
40
123
  long: 'logLevel',
41
124
  type: 'string',
42
125
  args: 'required',
43
126
  desc: 'Set log level',
44
127
  default: 'info',
45
- choices: reporter.LOG_LEVELS,
128
+ choices: [...BUILTIN_LOG_LEVELS],
46
129
  coerce: (raw) => {
47
- const level = reporter.resolveLogLevel(raw);
130
+ const level = resolveReporterLogLevel(raw);
48
131
  if (level === undefined) {
49
132
  throw new Error(`Invalid log level: ${raw}`);
50
133
  }
51
134
  return level;
52
135
  },
53
136
  apply: (value, ctx) => {
54
- ctx.reporter.setLevel(value);
137
+ setReporterLevel(ctx, value);
55
138
  },
56
139
  };
57
140
  const logDateOption = {
@@ -61,7 +144,7 @@ const logDateOption = {
61
144
  desc: 'Enable log timestamp',
62
145
  default: true,
63
146
  apply: (value, ctx) => {
64
- ctx.reporter.setFlight({ date: Boolean(value) });
147
+ setReporterFlight(ctx, { date: Boolean(value) });
65
148
  },
66
149
  };
67
150
  const logColorfulOption = {
@@ -71,7 +154,7 @@ const logColorfulOption = {
71
154
  desc: 'Enable colorful log output',
72
155
  default: true,
73
156
  apply: (value, ctx) => {
74
- ctx.reporter.setFlight({ color: Boolean(value) });
157
+ setReporterFlight(ctx, { color: Boolean(value) });
75
158
  },
76
159
  };
77
160
  const silentOption = {
@@ -82,7 +165,7 @@ const silentOption = {
82
165
  default: false,
83
166
  apply: (value, ctx) => {
84
167
  if (value) {
85
- ctx.reporter.setLevel('error');
168
+ setReporterLevel(ctx, 'error');
86
169
  }
87
170
  },
88
171
  };
@@ -103,6 +186,11 @@ class CommanderError extends Error {
103
186
 
104
187
  const LONG_OPTION_REGEX = /^--[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
105
188
  const NEGATIVE_OPTION_REGEX = /^--no-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
189
+ const PRESET_OPTS_FLAG = '--preset-opts';
190
+ const PRESET_ENVS_FLAG = '--preset-envs';
191
+ const PRESET_ROOT_FLAG = '--preset-root';
192
+ const DEFAULT_PRESET_OPTS_FILENAME = '.opt.local';
193
+ const DEFAULT_PRESET_ENVS_FILENAME = '.env.local';
106
194
  function kebabToCamelCase(str) {
107
195
  return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
108
196
  }
@@ -190,14 +278,12 @@ function tokenize(argv, commandPath) {
190
278
  }
191
279
  const BUILTIN_HELP_OPTION = {
192
280
  long: 'help',
193
- short: 'h',
194
281
  type: 'boolean',
195
282
  args: 'none',
196
283
  desc: 'Show help information',
197
284
  };
198
285
  const BUILTIN_VERSION_OPTION = {
199
286
  long: 'version',
200
- short: 'V',
201
287
  type: 'boolean',
202
288
  args: 'none',
203
289
  desc: 'Show version number',
@@ -211,6 +297,7 @@ const BUILTIN_COLOR_OPTION = {
211
297
  };
212
298
  function createBuiltinOptionState(enabled) {
213
299
  return {
300
+ version: enabled,
214
301
  color: enabled,
215
302
  logLevel: enabled,
216
303
  silent: enabled,
@@ -224,9 +311,6 @@ function isNoColorEnabled(envs) {
224
311
  function normalizeBuiltinConfig(builtin) {
225
312
  const resolved = {
226
313
  option: createBuiltinOptionState(true),
227
- command: {
228
- help: false,
229
- },
230
314
  };
231
315
  if (builtin === undefined) {
232
316
  return resolved;
@@ -234,13 +318,11 @@ function normalizeBuiltinConfig(builtin) {
234
318
  if (builtin === true) {
235
319
  return {
236
320
  option: createBuiltinOptionState(true),
237
- command: { help: true },
238
321
  };
239
322
  }
240
323
  if (builtin === false) {
241
324
  return {
242
325
  option: createBuiltinOptionState(false),
243
- command: { help: false },
244
326
  };
245
327
  }
246
328
  if (builtin.option !== undefined) {
@@ -251,6 +333,8 @@ function normalizeBuiltinConfig(builtin) {
251
333
  resolved.option = createBuiltinOptionState(true);
252
334
  }
253
335
  else {
336
+ if (builtin.option.version !== undefined)
337
+ resolved.option.version = builtin.option.version;
254
338
  if (builtin.option.color !== undefined)
255
339
  resolved.option.color = builtin.option.color;
256
340
  if (builtin.option.logLevel !== undefined) {
@@ -265,25 +349,17 @@ function normalizeBuiltinConfig(builtin) {
265
349
  }
266
350
  }
267
351
  }
268
- if (builtin.command !== undefined) {
269
- if (builtin.command === false) {
270
- resolved.command = { help: false };
271
- }
272
- else if (builtin.command === true) {
273
- resolved.command = { help: true };
274
- }
275
- else if (builtin.command.help !== undefined) {
276
- resolved.command.help = builtin.command.help;
277
- }
278
- }
279
352
  return resolved;
280
353
  }
281
354
  class Command {
282
355
  #name;
283
356
  #desc;
284
357
  #version;
358
+ #builtinConfig;
285
359
  #builtin;
360
+ #presetConfig;
286
361
  #reporter;
362
+ #runtime;
287
363
  #parent;
288
364
  #options = [];
289
365
  #arguments = [];
@@ -295,8 +371,11 @@ class Command {
295
371
  this.#name = config.name ?? '';
296
372
  this.#desc = config.desc;
297
373
  this.#version = config.version;
374
+ this.#builtinConfig = config.builtin;
298
375
  this.#builtin = normalizeBuiltinConfig(config.builtin);
376
+ this.#presetConfig = config.preset;
299
377
  this.#reporter = config.reporter;
378
+ this.#runtime = config.runtime ?? getDefaultCommandRuntime();
300
379
  }
301
380
  get name() {
302
381
  return this.#name || undefined;
@@ -307,6 +386,12 @@ class Command {
307
386
  get version() {
308
387
  return this.#version;
309
388
  }
389
+ get builtin() {
390
+ return this.#builtinConfig;
391
+ }
392
+ get preset() {
393
+ return this.#presetConfig === undefined ? undefined : { ...this.#presetConfig };
394
+ }
310
395
  get parent() {
311
396
  return this.#parent;
312
397
  }
@@ -342,14 +427,17 @@ class Command {
342
427
  return this;
343
428
  }
344
429
  subcommand(name, cmd) {
345
- if (this.#builtin.command.help && name === 'help') {
346
- throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
430
+ if (name === 'help') {
431
+ throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name', this.#getCommandPath());
347
432
  }
348
433
  if (cmd.#parent && cmd.#parent !== this) {
349
434
  throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
350
435
  }
351
436
  const existing = this.#subcommandsList.find(e => e.command === cmd);
352
437
  if (existing) {
438
+ if (existing.aliases.includes(name)) {
439
+ return this;
440
+ }
353
441
  existing.aliases.push(name);
354
442
  this.#subcommandsMap.set(name, cmd);
355
443
  }
@@ -362,34 +450,37 @@ class Command {
362
450
  return this;
363
451
  }
364
452
  async run(params) {
365
- const { argv, envs, reporter: reporter$1 } = params;
453
+ const { argv, envs, reporter } = params;
366
454
  try {
367
- const processedArgv = this.#processHelpSubcommand(argv);
368
- const routeResult = this.#route(processedArgv);
369
- const { chain, remaining } = routeResult;
455
+ const routeResult = this.#route(argv);
456
+ const { chain } = routeResult;
370
457
  const leafCommand = chain[chain.length - 1];
371
- const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
372
- const { optionTokens, restArgs } = tokenizeResult;
373
- const optionPolicyMap = this.#buildOptionPolicyMap(chain);
374
- const leafPolicy = this.#mustGetOptionPolicy(optionPolicyMap, leafCommand);
375
- if (leafPolicy.enableBuiltinHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
376
- const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs, leafPolicy);
377
- console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
458
+ const ctx = this.#createContext({
459
+ chain,
460
+ cmds: routeResult.cmds,
461
+ envs,
462
+ reporter,
463
+ });
464
+ const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
465
+ ctx.controls = controlScanResult.controls;
466
+ ctx.sources.user.argv = [...controlScanResult.remaining];
467
+ if (ctx.controls.help) {
468
+ const helpCommand = this.#resolveHelpCommand(leafCommand, controlScanResult.helpTarget);
469
+ const helpColor = helpCommand.#resolveHelpColorFromTailArgv(controlScanResult.remaining, ctx.envs);
470
+ console.log(helpCommand.#formatHelpForDisplay({ color: helpColor }));
378
471
  return;
379
472
  }
380
- if (leafPolicy.enableBuiltinVersion) {
381
- if (this.#hasFlag(optionTokens, 'version', 'V')) {
382
- console.log(leafCommand.#version);
383
- return;
384
- }
473
+ if (ctx.controls.version) {
474
+ console.log(leafCommand.#version);
475
+ return;
385
476
  }
477
+ const optionPolicyMap = this.#buildOptionPolicyMap(chain);
478
+ const presetResult = await this.#preset(controlScanResult.remaining, ctx, optionPolicyMap);
479
+ ctx.sources = presetResult.sources;
480
+ ctx.envs = presetResult.envs;
481
+ const tokenizeResult = tokenize(presetResult.tailArgv, leafCommand.#getCommandPath());
482
+ const { optionTokens, restArgs } = tokenizeResult;
386
483
  const resolveResult = this.#resolve(chain, optionTokens, optionPolicyMap);
387
- const ctx = {
388
- cmd: leafCommand,
389
- envs,
390
- reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
391
- argv,
392
- };
393
484
  const parseResult = this.#parse(chain, resolveResult, optionPolicyMap, ctx, restArgs);
394
485
  const actionParams = {
395
486
  ctx: parseResult.ctx,
@@ -401,7 +492,7 @@ class Command {
401
492
  await leafCommand.#runAction(actionParams);
402
493
  }
403
494
  else if (leafCommand.#subcommandsList.length > 0) {
404
- const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs, leafPolicy);
495
+ const helpColor = leafCommand.#resolveHelpColorFromTailArgv(presetResult.tailArgv, ctx.envs);
405
496
  console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
406
497
  }
407
498
  else {
@@ -417,22 +508,27 @@ class Command {
417
508
  throw err;
418
509
  }
419
510
  }
420
- parse(params) {
421
- const { argv, envs, reporter: reporter$1 } = params;
422
- const processedArgv = this.#processHelpSubcommand(argv);
423
- const routeResult = this.#route(processedArgv);
424
- const { chain, remaining } = routeResult;
511
+ async parse(params) {
512
+ const { argv, envs, reporter } = params;
513
+ const routeResult = this.#route(argv);
514
+ const { chain } = routeResult;
425
515
  const leafCommand = chain[chain.length - 1];
426
- const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
427
- const { optionTokens, restArgs } = tokenizeResult;
516
+ const ctx = this.#createContext({
517
+ chain,
518
+ cmds: routeResult.cmds,
519
+ envs,
520
+ reporter,
521
+ });
522
+ const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
523
+ ctx.controls = controlScanResult.controls;
524
+ ctx.sources.user.argv = [...controlScanResult.remaining];
428
525
  const optionPolicyMap = this.#buildOptionPolicyMap(chain);
526
+ const presetResult = await this.#preset(controlScanResult.remaining, ctx, optionPolicyMap);
527
+ ctx.sources = presetResult.sources;
528
+ ctx.envs = presetResult.envs;
529
+ const tokenizeResult = tokenize(presetResult.tailArgv, leafCommand.#getCommandPath());
530
+ const { optionTokens, restArgs } = tokenizeResult;
429
531
  const resolveResult = this.#resolve(chain, optionTokens, optionPolicyMap);
430
- const ctx = {
431
- cmd: leafCommand,
432
- envs,
433
- reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
434
- argv,
435
- };
436
532
  return this.#parse(chain, resolveResult, optionPolicyMap, ctx, restArgs);
437
533
  }
438
534
  formatHelp() {
@@ -450,7 +546,11 @@ class Command {
450
546
  return color && process.stdout.isTTY === true;
451
547
  }
452
548
  #buildHelpData() {
453
- const allOptions = this.#resolveOptionPolicy().mergedOptions;
549
+ const parseOptions = this.#resolveOptionPolicy().mergedOptions;
550
+ const allOptions = [...parseOptions, BUILTIN_HELP_OPTION];
551
+ if (this.#supportsBuiltinVersion()) {
552
+ allOptions.push(BUILTIN_VERSION_OPTION);
553
+ }
454
554
  const commandPath = this.#getCommandPath();
455
555
  let usage = `Usage: ${commandPath}`;
456
556
  if (allOptions.length > 0)
@@ -484,7 +584,10 @@ class Command {
484
584
  desc += ` [choices: ${opt.choices.join(', ')}]`;
485
585
  }
486
586
  options.push({ sig, desc });
487
- if (opt.type === 'boolean' && opt.args === 'none') {
587
+ if (opt.type === 'boolean' &&
588
+ opt.args === 'none' &&
589
+ opt.long !== 'help' &&
590
+ opt.long !== 'version') {
488
591
  options.push({
489
592
  sig: ` --no-${kebabLong}`,
490
593
  desc: `Negate --${kebabLong}`,
@@ -492,8 +595,7 @@ class Command {
492
595
  }
493
596
  }
494
597
  const commands = [];
495
- const showHelpSubcommand = this.#builtin.command.help && this.#subcommandsList.length > 0;
496
- if (showHelpSubcommand) {
598
+ if (this.#subcommandsList.length > 0) {
497
599
  commands.push({ name: 'help', desc: 'Show help for a command' });
498
600
  }
499
601
  for (const entry of this.#subcommandsList) {
@@ -616,50 +718,9 @@ class Command {
616
718
  #findSubcommandEntry(token) {
617
719
  return this.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
618
720
  }
619
- #createUnknownSubcommandError(subcommand) {
620
- const commandPath = this.#getCommandPath();
621
- return new CommanderError('UnknownSubcommand', `unknown subcommand "${subcommand}" for command "${commandPath}"`, commandPath);
622
- }
623
- #processHelpSubcommand(argv) {
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;
658
- }
659
- return argv;
660
- }
661
721
  #route(argv) {
662
722
  const chain = [this];
723
+ const cmds = [];
663
724
  let current = this;
664
725
  let idx = 0;
665
726
  while (idx < argv.length) {
@@ -670,10 +731,382 @@ class Command {
670
731
  if (!entry)
671
732
  break;
672
733
  current = entry.command;
734
+ cmds.push(token);
673
735
  chain.push(current);
674
736
  idx += 1;
675
737
  }
676
- return { chain, remaining: argv.slice(idx) };
738
+ return { chain, remaining: argv.slice(idx), cmds };
739
+ }
740
+ #controlScan(tailArgv, leafCommand) {
741
+ const controls = { help: false, version: false };
742
+ const separatorIndex = tailArgv.indexOf('--');
743
+ const beforeSeparator = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
744
+ const afterSeparator = separatorIndex === -1 ? [] : tailArgv.slice(separatorIndex + 1);
745
+ let helpTarget;
746
+ let scanStartIndex = 0;
747
+ if (beforeSeparator[0] === 'help') {
748
+ controls.help = true;
749
+ scanStartIndex = 1;
750
+ const candidate = beforeSeparator[1];
751
+ if (candidate !== undefined && !candidate.startsWith('-')) {
752
+ helpTarget = candidate;
753
+ scanStartIndex = 2;
754
+ }
755
+ }
756
+ const remainingBeforeSeparator = [];
757
+ for (let i = scanStartIndex; i < beforeSeparator.length; i += 1) {
758
+ const token = beforeSeparator[i];
759
+ if (token === '--help') {
760
+ controls.help = true;
761
+ continue;
762
+ }
763
+ if (token === '--version' && leafCommand.#supportsBuiltinVersion()) {
764
+ controls.version = true;
765
+ continue;
766
+ }
767
+ remainingBeforeSeparator.push(token);
768
+ }
769
+ const remaining = separatorIndex === -1
770
+ ? remainingBeforeSeparator
771
+ : [...remainingBeforeSeparator, '--', ...afterSeparator];
772
+ return {
773
+ controls,
774
+ remaining,
775
+ helpTarget,
776
+ };
777
+ }
778
+ #createContext(params) {
779
+ const { chain, cmds, envs, reporter: reporter$1 } = params;
780
+ const leafCommand = chain[chain.length - 1];
781
+ const envSnapshot = { ...envs };
782
+ return {
783
+ cmd: leafCommand,
784
+ chain,
785
+ envs: envSnapshot,
786
+ controls: { help: false, version: false },
787
+ sources: {
788
+ preset: {
789
+ argv: [],
790
+ envs: {},
791
+ },
792
+ user: {
793
+ cmds: [...cmds],
794
+ argv: [],
795
+ envs: envSnapshot,
796
+ },
797
+ },
798
+ reporter: reporter$1 ?? this.#reporter ?? new reporter.Reporter(),
799
+ };
800
+ }
801
+ #resolveHelpCommand(leafCommand, helpTarget) {
802
+ if (helpTarget === undefined) {
803
+ return leafCommand;
804
+ }
805
+ const target = leafCommand.#findSubcommandEntry(helpTarget);
806
+ if (target === undefined) {
807
+ return leafCommand;
808
+ }
809
+ return target.command;
810
+ }
811
+ async #preset(controlTailArgv, ctx, optionPolicyMap) {
812
+ const commandPath = ctx.chain[ctx.chain.length - 1].#getCommandPath();
813
+ const separatorIndex = controlTailArgv.indexOf('--');
814
+ const beforeSeparator = separatorIndex === -1 ? controlTailArgv : controlTailArgv.slice(0, separatorIndex);
815
+ const afterSeparator = separatorIndex === -1 ? [] : controlTailArgv.slice(separatorIndex + 1);
816
+ const rootScanResult = this.#scanPresetRootDirectives(beforeSeparator, commandPath);
817
+ const commandPreset = this.#resolveCommandPresetFromChain(ctx.chain);
818
+ const presetRoot = await this.#resolveEffectivePresetRoot(rootScanResult.cliPresetRoots, commandPreset, commandPath);
819
+ const fileScanResult = this.#scanPresetFileDirectives(rootScanResult.cleanArgv, commandPath);
820
+ const cleanArgv = separatorIndex === -1
821
+ ? fileScanResult.cleanArgv
822
+ : [...fileScanResult.cleanArgv, '--', ...afterSeparator];
823
+ const presetOptsFiles = this.#resolvePresetFileSources({
824
+ cliFiles: fileScanResult.cliPresetOptsFiles,
825
+ commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.opt),
826
+ presetRoot,
827
+ defaultFilename: DEFAULT_PRESET_OPTS_FILENAME,
828
+ });
829
+ const presetEnvsFiles = this.#resolvePresetFileSources({
830
+ cliFiles: fileScanResult.cliPresetEnvsFiles,
831
+ commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.env),
832
+ presetRoot,
833
+ defaultFilename: DEFAULT_PRESET_ENVS_FILENAME,
834
+ });
835
+ const userSources = {
836
+ cmds: [...ctx.sources.user.cmds],
837
+ argv: [...cleanArgv],
838
+ envs: { ...ctx.sources.user.envs },
839
+ };
840
+ const presetArgv = [];
841
+ for (const file of presetOptsFiles) {
842
+ const content = await this.#readPresetFile(file, commandPath);
843
+ if (content === undefined) {
844
+ continue;
845
+ }
846
+ const tokens = this.#tokenizePresetOptions(content);
847
+ this.#validatePresetOptionTokens(tokens, file.displayPath, commandPath);
848
+ this.#assertPresetOptionFragments(tokens, file.displayPath, ctx.chain, optionPolicyMap);
849
+ presetArgv.push(...tokens);
850
+ }
851
+ const presetEnvs = {};
852
+ for (const file of presetEnvsFiles) {
853
+ const content = await this.#readPresetFile(file, commandPath);
854
+ if (content === undefined) {
855
+ continue;
856
+ }
857
+ let parsed;
858
+ try {
859
+ parsed = env.parse(content);
860
+ }
861
+ catch (error) {
862
+ throw new CommanderError('ConfigurationError', `failed to parse preset envs file "${file.displayPath}": ${error.message}`, commandPath);
863
+ }
864
+ Object.assign(presetEnvs, parsed);
865
+ }
866
+ const sources = {
867
+ user: userSources,
868
+ preset: {
869
+ argv: presetArgv,
870
+ envs: presetEnvs,
871
+ },
872
+ };
873
+ const envs = { ...sources.user.envs, ...sources.preset.envs };
874
+ const tailArgv = [...sources.preset.argv, ...sources.user.argv];
875
+ return { tailArgv, envs, sources };
876
+ }
877
+ #resolveCommandPresetFromChain(chain) {
878
+ for (let index = chain.length - 1; index >= 0; index -= 1) {
879
+ const preset = chain[index].#presetConfig;
880
+ if (preset?.root !== undefined) {
881
+ return preset;
882
+ }
883
+ }
884
+ return undefined;
885
+ }
886
+ async #resolveEffectivePresetRoot(cliPresetRoots, commandPreset, commandPath) {
887
+ if (cliPresetRoots.length > 0) {
888
+ const root = cliPresetRoots[cliPresetRoots.length - 1];
889
+ return await this.#assertPresetRoot(root, PRESET_ROOT_FLAG, commandPath);
890
+ }
891
+ if (commandPreset?.root === undefined) {
892
+ return undefined;
893
+ }
894
+ return await this.#assertPresetRoot(commandPreset.root, 'command.preset.root', commandPath);
895
+ }
896
+ async #assertPresetRoot(root, sourceName, commandPath) {
897
+ if (!this.#runtime.isAbsolute(root)) {
898
+ throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not an absolute directory`, commandPath);
899
+ }
900
+ let stats;
901
+ try {
902
+ stats = await this.#runtime.stat(root);
903
+ }
904
+ catch (error) {
905
+ throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" cannot be accessed (${error.message})`, commandPath);
906
+ }
907
+ if (!stats.isDirectory()) {
908
+ throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not a directory`, commandPath);
909
+ }
910
+ return root;
911
+ }
912
+ #normalizeCommandPresetFile(filepath) {
913
+ if (filepath === undefined) {
914
+ return undefined;
915
+ }
916
+ if (!this.#isValidPresetFileValue(filepath)) {
917
+ return undefined;
918
+ }
919
+ return filepath;
920
+ }
921
+ #resolvePresetFileSources(params) {
922
+ const { cliFiles, commandPresetFile, presetRoot, defaultFilename } = params;
923
+ if (cliFiles.length > 0) {
924
+ return cliFiles.map(filepath => ({
925
+ displayPath: filepath,
926
+ absolutePath: this.#resolvePresetFileAbsolutePath(filepath, presetRoot),
927
+ explicit: true,
928
+ }));
929
+ }
930
+ if (presetRoot === undefined) {
931
+ return [];
932
+ }
933
+ if (commandPresetFile !== undefined) {
934
+ return [
935
+ {
936
+ displayPath: commandPresetFile,
937
+ absolutePath: this.#resolvePresetFileAbsolutePath(commandPresetFile, presetRoot),
938
+ explicit: true,
939
+ },
940
+ ];
941
+ }
942
+ const absolutePath = this.#runtime.resolve(presetRoot, defaultFilename);
943
+ return [
944
+ {
945
+ displayPath: absolutePath,
946
+ absolutePath,
947
+ explicit: false,
948
+ },
949
+ ];
950
+ }
951
+ #resolvePresetFileAbsolutePath(filepath, presetRoot) {
952
+ if (this.#runtime.isAbsolute(filepath)) {
953
+ return filepath;
954
+ }
955
+ if (presetRoot !== undefined) {
956
+ return this.#runtime.resolve(presetRoot, filepath);
957
+ }
958
+ return this.#runtime.resolve(this.#runtime.cwd(), filepath);
959
+ }
960
+ #assertPresetOptionFragments(tokens, filepath, chain, optionPolicyMap) {
961
+ if (tokens.length === 0) {
962
+ return;
963
+ }
964
+ const commandPath = chain[chain.length - 1].#getCommandPath();
965
+ try {
966
+ const { optionTokens, restArgs } = tokenize(tokens, commandPath);
967
+ void restArgs;
968
+ const { argTokens } = this.#resolve(chain, optionTokens, optionPolicyMap);
969
+ if (argTokens.length > 0) {
970
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": token "${argTokens[0].original}" cannot be resolved as an option fragment`, commandPath);
971
+ }
972
+ }
973
+ catch (error) {
974
+ if (error instanceof CommanderError) {
975
+ if (error.kind === 'ConfigurationError') {
976
+ throw error;
977
+ }
978
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": ${error.message}`, commandPath);
979
+ }
980
+ throw error;
981
+ }
982
+ }
983
+ #scanPresetRootDirectives(argv, commandPath) {
984
+ const cleanArgv = [];
985
+ const cliPresetRoots = [];
986
+ let index = 0;
987
+ while (index < argv.length) {
988
+ const token = argv[index];
989
+ if (token === PRESET_ROOT_FLAG) {
990
+ const value = argv[index + 1];
991
+ if (value === undefined || value.length === 0) {
992
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
993
+ }
994
+ cliPresetRoots.push(value);
995
+ index += 2;
996
+ continue;
997
+ }
998
+ if (token.startsWith(`${PRESET_ROOT_FLAG}=`)) {
999
+ const value = token.slice(PRESET_ROOT_FLAG.length + 1);
1000
+ if (value.length === 0) {
1001
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
1002
+ }
1003
+ cliPresetRoots.push(value);
1004
+ index += 1;
1005
+ continue;
1006
+ }
1007
+ cleanArgv.push(token);
1008
+ index += 1;
1009
+ }
1010
+ return { cleanArgv, cliPresetRoots };
1011
+ }
1012
+ #scanPresetFileDirectives(argv, commandPath) {
1013
+ const cleanArgv = [];
1014
+ const cliPresetOptsFiles = [];
1015
+ const cliPresetEnvsFiles = [];
1016
+ const assertAndPush = (flag, value) => {
1017
+ this.#assertPresetFileValue(value, flag, commandPath);
1018
+ if (flag === PRESET_OPTS_FLAG) {
1019
+ cliPresetOptsFiles.push(value);
1020
+ }
1021
+ else {
1022
+ cliPresetEnvsFiles.push(value);
1023
+ }
1024
+ };
1025
+ let index = 0;
1026
+ while (index < argv.length) {
1027
+ const token = argv[index];
1028
+ if (token === PRESET_OPTS_FLAG || token === PRESET_ENVS_FLAG) {
1029
+ const value = argv[index + 1];
1030
+ if (value === undefined || value.length === 0) {
1031
+ throw new CommanderError('ConfigurationError', `missing value for "${token}"`, commandPath);
1032
+ }
1033
+ assertAndPush(token, value);
1034
+ index += 2;
1035
+ continue;
1036
+ }
1037
+ if (token.startsWith(`${PRESET_OPTS_FLAG}=`)) {
1038
+ const value = token.slice(PRESET_OPTS_FLAG.length + 1);
1039
+ if (value.length === 0) {
1040
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_OPTS_FLAG}"`, commandPath);
1041
+ }
1042
+ assertAndPush(PRESET_OPTS_FLAG, value);
1043
+ index += 1;
1044
+ continue;
1045
+ }
1046
+ if (token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
1047
+ const value = token.slice(PRESET_ENVS_FLAG.length + 1);
1048
+ if (value.length === 0) {
1049
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ENVS_FLAG}"`, commandPath);
1050
+ }
1051
+ assertAndPush(PRESET_ENVS_FLAG, value);
1052
+ index += 1;
1053
+ continue;
1054
+ }
1055
+ cleanArgv.push(token);
1056
+ index += 1;
1057
+ }
1058
+ return { cleanArgv, cliPresetOptsFiles, cliPresetEnvsFiles };
1059
+ }
1060
+ #isValidPresetFileValue(filepath) {
1061
+ return filepath.length > 0 && !filepath.startsWith('..');
1062
+ }
1063
+ #assertPresetFileValue(filepath, directive, commandPath) {
1064
+ if (this.#isValidPresetFileValue(filepath)) {
1065
+ return;
1066
+ }
1067
+ throw new CommanderError('ConfigurationError', `invalid value for "${directive}": "${filepath}" (must be non-empty and must not start with "..")`, commandPath);
1068
+ }
1069
+ async #readPresetFile(file, commandPath) {
1070
+ try {
1071
+ return await this.#runtime.readFile(file.absolutePath);
1072
+ }
1073
+ catch (error) {
1074
+ const ioError = error;
1075
+ if (!file.explicit && ioError.code === 'ENOENT') {
1076
+ return undefined;
1077
+ }
1078
+ throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${error.message}`, commandPath);
1079
+ }
1080
+ }
1081
+ #tokenizePresetOptions(content) {
1082
+ return content
1083
+ .split(/\s+/)
1084
+ .map(token => token.trim())
1085
+ .filter(token => token.length > 0);
1086
+ }
1087
+ #validatePresetOptionTokens(tokens, filepath, commandPath) {
1088
+ if (tokens.length === 0) {
1089
+ return;
1090
+ }
1091
+ if (!tokens[0].startsWith('-')) {
1092
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": bare token "${tokens[0]}" cannot appear before any option token`, commandPath);
1093
+ }
1094
+ for (const token of tokens) {
1095
+ if (token === '--') {
1096
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": "--" is not allowed`, commandPath);
1097
+ }
1098
+ if (token === 'help' || token === '--help' || token === '--version') {
1099
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": control token "${token}" is not allowed`, commandPath);
1100
+ }
1101
+ if (token === PRESET_ROOT_FLAG ||
1102
+ token.startsWith(`${PRESET_ROOT_FLAG}=`) ||
1103
+ token === PRESET_OPTS_FLAG ||
1104
+ token.startsWith(`${PRESET_OPTS_FLAG}=`) ||
1105
+ token === PRESET_ENVS_FLAG ||
1106
+ token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
1107
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": preset directive "${token}" is not allowed`, commandPath);
1108
+ }
1109
+ }
677
1110
  }
678
1111
  #resolve(chain, tokens, optionPolicyMap) {
679
1112
  const consumedTokens = new Map();
@@ -783,13 +1216,20 @@ class Command {
783
1216
  }
784
1217
  }
785
1218
  }
786
- const mergedOpts = {};
787
- for (const cmd of chain) {
788
- Object.assign(mergedOpts, optsMap.get(cmd) ?? {});
1219
+ const leafLocalOpts = {};
1220
+ const leafParsedOpts = optsMap.get(leafCommand) ?? {};
1221
+ for (const opt of leafCommand.#options) {
1222
+ if (Object.prototype.hasOwnProperty.call(leafParsedOpts, opt.long)) {
1223
+ leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
1224
+ }
789
1225
  }
790
1226
  const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
791
1227
  const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
792
- return { ctx, opts: mergedOpts, args, rawArgs };
1228
+ const parseCtx = {
1229
+ ...ctx,
1230
+ sources: this.#freezeInputSources(ctx.sources),
1231
+ };
1232
+ return { ctx: parseCtx, opts: leafLocalOpts, args, rawArgs };
793
1233
  }
794
1234
  #parseOptions(tokens, allOptions, envs) {
795
1235
  const opts = {};
@@ -976,29 +1416,19 @@ class Command {
976
1416
  #hasUserOption(long) {
977
1417
  return this.#options.some(option => option.long === long);
978
1418
  }
979
- #canUseBuiltinVersion() {
980
- return this.#version !== undefined;
1419
+ #supportsBuiltinVersion() {
1420
+ return this.#parent === undefined && this.#version !== undefined && this.#builtin.option.version;
981
1421
  }
982
1422
  #resolveOptionPolicy() {
983
1423
  const optionMap = new Map();
984
1424
  const hasUserColor = this.#hasUserOption('color');
985
- const hasUserHelp = this.#hasUserOption('help');
986
- const hasUserVersion = this.#hasUserOption('version');
987
1425
  const hasUserLogLevel = this.#hasUserOption('logLevel');
988
1426
  const hasUserSilent = this.#hasUserOption('silent');
989
1427
  const hasUserLogDate = this.#hasUserOption('logDate');
990
1428
  const hasUserLogColorful = this.#hasUserOption('logColorful');
991
- const enableBuiltinHelp = !hasUserHelp;
992
- const enableBuiltinVersion = !hasUserVersion && this.#canUseBuiltinVersion();
993
1429
  if (this.#builtin.option.color && !hasUserColor) {
994
1430
  optionMap.set('color', BUILTIN_COLOR_OPTION);
995
1431
  }
996
- if (enableBuiltinHelp) {
997
- optionMap.set('help', BUILTIN_HELP_OPTION);
998
- }
999
- if (enableBuiltinVersion) {
1000
- optionMap.set('version', BUILTIN_VERSION_OPTION);
1001
- }
1002
1432
  if (this.#builtin.option.logLevel && !hasUserLogLevel) {
1003
1433
  optionMap.set('logLevel', logLevelOption);
1004
1434
  }
@@ -1016,8 +1446,6 @@ class Command {
1016
1446
  }
1017
1447
  return {
1018
1448
  mergedOptions: Array.from(optionMap.values()),
1019
- enableBuiltinHelp,
1020
- enableBuiltinVersion,
1021
1449
  };
1022
1450
  }
1023
1451
  #buildOptionPolicyMap(chain) {
@@ -1054,6 +1482,9 @@ class Command {
1054
1482
  }
1055
1483
  }
1056
1484
  #validateOptionConfig(opt) {
1485
+ if (opt.long === 'help' || opt.long === 'version') {
1486
+ throw new CommanderError('ConfigurationError', `option long name "${opt.long}" is reserved`, this.#getCommandPath());
1487
+ }
1057
1488
  if (opt.type === 'boolean' && opt.args !== 'none') {
1058
1489
  throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
1059
1490
  }
@@ -1134,22 +1565,27 @@ class Command {
1134
1565
  process.exit(1);
1135
1566
  }
1136
1567
  }
1137
- #resolveHelpColorOption(tokens, envs, policy = this.#resolveOptionPolicy()) {
1568
+ #resolveHelpColorFromTailArgv(tailArgv, envs, policy = this.#resolveOptionPolicy()) {
1138
1569
  const colorOption = policy.mergedOptions.find(opt => opt.long === 'color');
1139
1570
  let color = !isNoColorEnabled(envs);
1140
1571
  if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
1141
1572
  return color;
1142
1573
  }
1143
- for (const token of tokens) {
1144
- if (token.type !== 'long' || token.name !== 'color') {
1574
+ const separatorIndex = tailArgv.indexOf('--');
1575
+ const scanTokens = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
1576
+ for (const token of scanTokens) {
1577
+ if (token === '--color') {
1578
+ color = true;
1145
1579
  continue;
1146
1580
  }
1147
- const eqIdx = token.resolved.indexOf('=');
1148
- if (eqIdx === -1) {
1149
- color = true;
1581
+ if (token === '--no-color') {
1582
+ color = false;
1150
1583
  continue;
1151
1584
  }
1152
- const value = token.resolved.slice(eqIdx + 1);
1585
+ if (!token.startsWith('--color=')) {
1586
+ continue;
1587
+ }
1588
+ const value = token.slice('--color='.length);
1153
1589
  if (value === 'true') {
1154
1590
  color = true;
1155
1591
  }
@@ -1162,16 +1598,18 @@ class Command {
1162
1598
  }
1163
1599
  return color;
1164
1600
  }
1165
- #hasFlag(tokens, longName, shortName) {
1166
- for (const token of tokens) {
1167
- if (token.type === 'long' && token.name === longName) {
1168
- return true;
1169
- }
1170
- if (token.type === 'short' && token.name === shortName) {
1171
- return true;
1172
- }
1173
- }
1174
- return false;
1601
+ #freezeInputSources(sources) {
1602
+ return Object.freeze({
1603
+ preset: Object.freeze({
1604
+ argv: Object.freeze([...sources.preset.argv]),
1605
+ envs: Object.freeze({ ...sources.preset.envs }),
1606
+ }),
1607
+ user: Object.freeze({
1608
+ cmds: Object.freeze([...sources.user.cmds]),
1609
+ argv: Object.freeze([...sources.user.argv]),
1610
+ envs: Object.freeze({ ...sources.user.envs }),
1611
+ }),
1612
+ });
1175
1613
  }
1176
1614
  #getCommandPath() {
1177
1615
  const parts = [];
@@ -1340,9 +1778,12 @@ function camelToKebabCase(str) {
1340
1778
  return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
1341
1779
  }
1342
1780
  class CompletionCommand extends Command {
1343
- constructor(root, config) {
1344
- const paths = config.paths;
1781
+ constructor(root, config = {}) {
1345
1782
  const programName = config.programName ?? root.name ?? 'program';
1783
+ const paths = {
1784
+ ...createDefaultCompletionPaths(programName),
1785
+ ...config.paths,
1786
+ };
1346
1787
  super({ desc: 'Generate shell completion script' });
1347
1788
  this.option({
1348
1789
  long: 'bash',
@@ -1404,11 +1845,11 @@ class CompletionCommand extends Command {
1404
1845
  if (writeOpt !== undefined) {
1405
1846
  const filePath = typeof writeOpt === 'string' && writeOpt !== '' ? writeOpt : paths[shell];
1406
1847
  const expandedPath = expandHome(filePath);
1407
- const dir = path__namespace.dirname(expandedPath);
1408
- if (!fs__namespace.existsSync(dir)) {
1409
- fs__namespace.mkdirSync(dir, { recursive: true });
1848
+ const dir = path.dirname(expandedPath);
1849
+ if (!fs.existsSync(dir)) {
1850
+ fs.mkdirSync(dir, { recursive: true });
1410
1851
  }
1411
- fs__namespace.writeFileSync(expandedPath, script, 'utf-8');
1852
+ fs.writeFileSync(expandedPath, script, 'utf-8');
1412
1853
  console.log(`Completion script written to: ${expandedPath}`);
1413
1854
  }
1414
1855
  else {
@@ -1417,6 +1858,13 @@ class CompletionCommand extends Command {
1417
1858
  });
1418
1859
  }
1419
1860
  }
1861
+ function createDefaultCompletionPaths(programName) {
1862
+ return {
1863
+ bash: `~/.local/share/bash-completion/completions/${programName}`,
1864
+ fish: `~/.config/fish/completions/${programName}.fish`,
1865
+ pwsh: '~/.config/powershell/Microsoft.PowerShell_profile.ps1',
1866
+ };
1867
+ }
1420
1868
  function expandHome(filepath) {
1421
1869
  if (filepath.startsWith('~/') || filepath === '~') {
1422
1870
  const home = process.env['HOME'] || process.env['USERPROFILE'] || '';
@@ -1694,6 +2142,8 @@ class PwshCompletion {
1694
2142
  }
1695
2143
  }
1696
2144
 
2145
+ setDefaultCommandRuntime(createNodeCommandRuntime());
2146
+
1697
2147
  exports.BashCompletion = BashCompletion;
1698
2148
  exports.Coerce = Coerce;
1699
2149
  exports.Command = Command;
@@ -1701,6 +2151,9 @@ exports.CommanderError = CommanderError;
1701
2151
  exports.CompletionCommand = CompletionCommand;
1702
2152
  exports.FishCompletion = FishCompletion;
1703
2153
  exports.PwshCompletion = PwshCompletion;
2154
+ exports.createBrowserCommandRuntime = createBrowserCommandRuntime;
2155
+ exports.createNodeCommandRuntime = createNodeCommandRuntime;
2156
+ exports.getDefaultCommandRuntime = getDefaultCommandRuntime;
1704
2157
  exports.isDomain = isDomain;
1705
2158
  exports.isIp = isIp;
1706
2159
  exports.isIpv4 = isIpv4;
@@ -1708,4 +2161,5 @@ exports.isIpv6 = isIpv6;
1708
2161
  exports.logColorfulOption = logColorfulOption;
1709
2162
  exports.logDateOption = logDateOption;
1710
2163
  exports.logLevelOption = logLevelOption;
2164
+ exports.setDefaultCommandRuntime = setDefaultCommandRuntime;
1711
2165
  exports.silentOption = silentOption;