@guanghechen/commander 4.5.0 → 4.6.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/lib/esm/index.mjs CHANGED
@@ -1,6 +1,9 @@
1
- import { LOG_LEVELS, resolveLogLevel, Reporter } from '@guanghechen/reporter';
2
- import * as fs from 'node:fs';
1
+ import { parse } from '@guanghechen/env';
2
+ import { Reporter } from '@guanghechen/reporter';
3
+ import { stat, readFile } from 'node:fs/promises';
3
4
  import * as path from 'node:path';
5
+ import path__default from 'node:path';
6
+ import * as fs from 'node:fs';
4
7
 
5
8
  const TERMINAL_STYLE = {
6
9
  bold: '\x1b[1m',
@@ -14,22 +17,35 @@ function styleText(text, ...styles) {
14
17
  return `${styles.join('')}${text}${TERMINAL_STYLE.reset}`;
15
18
  }
16
19
 
20
+ const BUILTIN_LOG_LEVELS = ['debug', 'info', 'hint', 'warn', 'error'];
21
+ function resolveReporterLogLevel(raw) {
22
+ const normalized = raw.trim().toLowerCase();
23
+ return BUILTIN_LOG_LEVELS.find(level => level === normalized);
24
+ }
25
+ function setReporterLevel(ctx, level) {
26
+ const reporter = ctx.reporter;
27
+ reporter?.setLevel?.(level);
28
+ }
29
+ function setReporterFlight(ctx, flight) {
30
+ const reporter = ctx.reporter;
31
+ reporter?.setFlight?.(flight);
32
+ }
17
33
  const logLevelOption = {
18
34
  long: 'logLevel',
19
35
  type: 'string',
20
36
  args: 'required',
21
37
  desc: 'Set log level',
22
38
  default: 'info',
23
- choices: LOG_LEVELS,
39
+ choices: [...BUILTIN_LOG_LEVELS],
24
40
  coerce: (raw) => {
25
- const level = resolveLogLevel(raw);
41
+ const level = resolveReporterLogLevel(raw);
26
42
  if (level === undefined) {
27
43
  throw new Error(`Invalid log level: ${raw}`);
28
44
  }
29
45
  return level;
30
46
  },
31
47
  apply: (value, ctx) => {
32
- ctx.reporter.setLevel(value);
48
+ setReporterLevel(ctx, value);
33
49
  },
34
50
  };
35
51
  const logDateOption = {
@@ -39,7 +55,7 @@ const logDateOption = {
39
55
  desc: 'Enable log timestamp',
40
56
  default: true,
41
57
  apply: (value, ctx) => {
42
- ctx.reporter.setFlight({ date: Boolean(value) });
58
+ setReporterFlight(ctx, { date: Boolean(value) });
43
59
  },
44
60
  };
45
61
  const logColorfulOption = {
@@ -49,7 +65,7 @@ const logColorfulOption = {
49
65
  desc: 'Enable colorful log output',
50
66
  default: true,
51
67
  apply: (value, ctx) => {
52
- ctx.reporter.setFlight({ color: Boolean(value) });
68
+ setReporterFlight(ctx, { color: Boolean(value) });
53
69
  },
54
70
  };
55
71
  const silentOption = {
@@ -60,7 +76,7 @@ const silentOption = {
60
76
  default: false,
61
77
  apply: (value, ctx) => {
62
78
  if (value) {
63
- ctx.reporter.setLevel('error');
79
+ setReporterLevel(ctx, 'error');
64
80
  }
65
81
  },
66
82
  };
@@ -81,6 +97,11 @@ class CommanderError extends Error {
81
97
 
82
98
  const LONG_OPTION_REGEX = /^--[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
83
99
  const NEGATIVE_OPTION_REGEX = /^--no-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
100
+ const PRESET_OPTS_FLAG = '--preset-opts';
101
+ const PRESET_ENVS_FLAG = '--preset-envs';
102
+ const PRESET_ROOT_FLAG = '--preset-root';
103
+ const DEFAULT_PRESET_OPTS_FILENAME = '.opt.local';
104
+ const DEFAULT_PRESET_ENVS_FILENAME = '.env.local';
84
105
  function kebabToCamelCase(str) {
85
106
  return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
86
107
  }
@@ -168,14 +189,12 @@ function tokenize(argv, commandPath) {
168
189
  }
169
190
  const BUILTIN_HELP_OPTION = {
170
191
  long: 'help',
171
- short: 'h',
172
192
  type: 'boolean',
173
193
  args: 'none',
174
194
  desc: 'Show help information',
175
195
  };
176
196
  const BUILTIN_VERSION_OPTION = {
177
197
  long: 'version',
178
- short: 'V',
179
198
  type: 'boolean',
180
199
  args: 'none',
181
200
  desc: 'Show version number',
@@ -189,6 +208,7 @@ const BUILTIN_COLOR_OPTION = {
189
208
  };
190
209
  function createBuiltinOptionState(enabled) {
191
210
  return {
211
+ version: enabled,
192
212
  color: enabled,
193
213
  logLevel: enabled,
194
214
  silent: enabled,
@@ -202,9 +222,6 @@ function isNoColorEnabled(envs) {
202
222
  function normalizeBuiltinConfig(builtin) {
203
223
  const resolved = {
204
224
  option: createBuiltinOptionState(true),
205
- command: {
206
- help: false,
207
- },
208
225
  };
209
226
  if (builtin === undefined) {
210
227
  return resolved;
@@ -212,13 +229,11 @@ function normalizeBuiltinConfig(builtin) {
212
229
  if (builtin === true) {
213
230
  return {
214
231
  option: createBuiltinOptionState(true),
215
- command: { help: true },
216
232
  };
217
233
  }
218
234
  if (builtin === false) {
219
235
  return {
220
236
  option: createBuiltinOptionState(false),
221
- command: { help: false },
222
237
  };
223
238
  }
224
239
  if (builtin.option !== undefined) {
@@ -229,6 +244,8 @@ function normalizeBuiltinConfig(builtin) {
229
244
  resolved.option = createBuiltinOptionState(true);
230
245
  }
231
246
  else {
247
+ if (builtin.option.version !== undefined)
248
+ resolved.option.version = builtin.option.version;
232
249
  if (builtin.option.color !== undefined)
233
250
  resolved.option.color = builtin.option.color;
234
251
  if (builtin.option.logLevel !== undefined) {
@@ -243,24 +260,15 @@ function normalizeBuiltinConfig(builtin) {
243
260
  }
244
261
  }
245
262
  }
246
- if (builtin.command !== undefined) {
247
- if (builtin.command === false) {
248
- resolved.command = { help: false };
249
- }
250
- else if (builtin.command === true) {
251
- resolved.command = { help: true };
252
- }
253
- else if (builtin.command.help !== undefined) {
254
- resolved.command.help = builtin.command.help;
255
- }
256
- }
257
263
  return resolved;
258
264
  }
259
265
  class Command {
260
266
  #name;
261
267
  #desc;
262
268
  #version;
269
+ #builtinConfig;
263
270
  #builtin;
271
+ #presetConfig;
264
272
  #reporter;
265
273
  #parent;
266
274
  #options = [];
@@ -273,7 +281,9 @@ class Command {
273
281
  this.#name = config.name ?? '';
274
282
  this.#desc = config.desc;
275
283
  this.#version = config.version;
284
+ this.#builtinConfig = config.builtin;
276
285
  this.#builtin = normalizeBuiltinConfig(config.builtin);
286
+ this.#presetConfig = config.preset;
277
287
  this.#reporter = config.reporter;
278
288
  }
279
289
  get name() {
@@ -285,6 +295,12 @@ class Command {
285
295
  get version() {
286
296
  return this.#version;
287
297
  }
298
+ get builtin() {
299
+ return this.#builtinConfig;
300
+ }
301
+ get preset() {
302
+ return this.#presetConfig === undefined ? undefined : { ...this.#presetConfig };
303
+ }
288
304
  get parent() {
289
305
  return this.#parent;
290
306
  }
@@ -320,14 +336,17 @@ class Command {
320
336
  return this;
321
337
  }
322
338
  subcommand(name, cmd) {
323
- if (this.#builtin.command.help && name === 'help') {
324
- throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name when help subcommand is enabled', this.#getCommandPath());
339
+ if (name === 'help') {
340
+ throw new CommanderError('ConfigurationError', '"help" is a reserved subcommand name', this.#getCommandPath());
325
341
  }
326
342
  if (cmd.#parent && cmd.#parent !== this) {
327
343
  throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
328
344
  }
329
345
  const existing = this.#subcommandsList.find(e => e.command === cmd);
330
346
  if (existing) {
347
+ if (existing.aliases.includes(name)) {
348
+ return this;
349
+ }
331
350
  existing.aliases.push(name);
332
351
  this.#subcommandsMap.set(name, cmd);
333
352
  }
@@ -342,32 +361,35 @@ class Command {
342
361
  async run(params) {
343
362
  const { argv, envs, reporter } = params;
344
363
  try {
345
- const processedArgv = this.#processHelpSubcommand(argv);
346
- const routeResult = this.#route(processedArgv);
347
- const { chain, remaining } = routeResult;
364
+ const routeResult = this.#route(argv);
365
+ const { chain } = routeResult;
348
366
  const leafCommand = chain[chain.length - 1];
349
- const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
350
- const { optionTokens, restArgs } = tokenizeResult;
351
- const optionPolicyMap = this.#buildOptionPolicyMap(chain);
352
- const leafPolicy = this.#mustGetOptionPolicy(optionPolicyMap, leafCommand);
353
- if (leafPolicy.enableBuiltinHelp && this.#hasFlag(optionTokens, 'help', 'h')) {
354
- const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs, leafPolicy);
355
- console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
367
+ const ctx = this.#createContext({
368
+ chain,
369
+ cmds: routeResult.cmds,
370
+ envs,
371
+ reporter,
372
+ });
373
+ const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
374
+ ctx.controls = controlScanResult.controls;
375
+ ctx.sources.user.argv = [...controlScanResult.remaining];
376
+ if (ctx.controls.help) {
377
+ const helpCommand = this.#resolveHelpCommand(leafCommand, controlScanResult.helpTarget);
378
+ const helpColor = helpCommand.#resolveHelpColorFromTailArgv(controlScanResult.remaining, ctx.envs);
379
+ console.log(helpCommand.#formatHelpForDisplay({ color: helpColor }));
356
380
  return;
357
381
  }
358
- if (leafPolicy.enableBuiltinVersion) {
359
- if (this.#hasFlag(optionTokens, 'version', 'V')) {
360
- console.log(leafCommand.#version);
361
- return;
362
- }
382
+ if (ctx.controls.version) {
383
+ console.log(leafCommand.#version);
384
+ return;
363
385
  }
386
+ const optionPolicyMap = this.#buildOptionPolicyMap(chain);
387
+ const presetResult = await this.#preset(controlScanResult.remaining, ctx, optionPolicyMap);
388
+ ctx.sources = presetResult.sources;
389
+ ctx.envs = presetResult.envs;
390
+ const tokenizeResult = tokenize(presetResult.tailArgv, leafCommand.#getCommandPath());
391
+ const { optionTokens, restArgs } = tokenizeResult;
364
392
  const resolveResult = this.#resolve(chain, optionTokens, optionPolicyMap);
365
- const ctx = {
366
- cmd: leafCommand,
367
- envs,
368
- reporter: reporter ?? this.#reporter ?? new Reporter(),
369
- argv,
370
- };
371
393
  const parseResult = this.#parse(chain, resolveResult, optionPolicyMap, ctx, restArgs);
372
394
  const actionParams = {
373
395
  ctx: parseResult.ctx,
@@ -379,7 +401,7 @@ class Command {
379
401
  await leafCommand.#runAction(actionParams);
380
402
  }
381
403
  else if (leafCommand.#subcommandsList.length > 0) {
382
- const helpColor = leafCommand.#resolveHelpColorOption(optionTokens, envs, leafPolicy);
404
+ const helpColor = leafCommand.#resolveHelpColorFromTailArgv(presetResult.tailArgv, ctx.envs);
383
405
  console.log(leafCommand.#formatHelpForDisplay({ color: helpColor }));
384
406
  }
385
407
  else {
@@ -395,22 +417,27 @@ class Command {
395
417
  throw err;
396
418
  }
397
419
  }
398
- parse(params) {
420
+ async parse(params) {
399
421
  const { argv, envs, reporter } = params;
400
- const processedArgv = this.#processHelpSubcommand(argv);
401
- const routeResult = this.#route(processedArgv);
402
- const { chain, remaining } = routeResult;
422
+ const routeResult = this.#route(argv);
423
+ const { chain } = routeResult;
403
424
  const leafCommand = chain[chain.length - 1];
404
- const tokenizeResult = tokenize(remaining, leafCommand.#getCommandPath());
405
- const { optionTokens, restArgs } = tokenizeResult;
425
+ const ctx = this.#createContext({
426
+ chain,
427
+ cmds: routeResult.cmds,
428
+ envs,
429
+ reporter,
430
+ });
431
+ const controlScanResult = this.#controlScan(routeResult.remaining, leafCommand);
432
+ ctx.controls = controlScanResult.controls;
433
+ ctx.sources.user.argv = [...controlScanResult.remaining];
406
434
  const optionPolicyMap = this.#buildOptionPolicyMap(chain);
435
+ const presetResult = await this.#preset(controlScanResult.remaining, ctx, optionPolicyMap);
436
+ ctx.sources = presetResult.sources;
437
+ ctx.envs = presetResult.envs;
438
+ const tokenizeResult = tokenize(presetResult.tailArgv, leafCommand.#getCommandPath());
439
+ const { optionTokens, restArgs } = tokenizeResult;
407
440
  const resolveResult = this.#resolve(chain, optionTokens, optionPolicyMap);
408
- const ctx = {
409
- cmd: leafCommand,
410
- envs,
411
- reporter: reporter ?? this.#reporter ?? new Reporter(),
412
- argv,
413
- };
414
441
  return this.#parse(chain, resolveResult, optionPolicyMap, ctx, restArgs);
415
442
  }
416
443
  formatHelp() {
@@ -428,7 +455,11 @@ class Command {
428
455
  return color && process.stdout.isTTY === true;
429
456
  }
430
457
  #buildHelpData() {
431
- const allOptions = this.#resolveOptionPolicy().mergedOptions;
458
+ const parseOptions = this.#resolveOptionPolicy().mergedOptions;
459
+ const allOptions = [...parseOptions, BUILTIN_HELP_OPTION];
460
+ if (this.#supportsBuiltinVersion()) {
461
+ allOptions.push(BUILTIN_VERSION_OPTION);
462
+ }
432
463
  const commandPath = this.#getCommandPath();
433
464
  let usage = `Usage: ${commandPath}`;
434
465
  if (allOptions.length > 0)
@@ -462,7 +493,10 @@ class Command {
462
493
  desc += ` [choices: ${opt.choices.join(', ')}]`;
463
494
  }
464
495
  options.push({ sig, desc });
465
- if (opt.type === 'boolean' && opt.args === 'none') {
496
+ if (opt.type === 'boolean' &&
497
+ opt.args === 'none' &&
498
+ opt.long !== 'help' &&
499
+ opt.long !== 'version') {
466
500
  options.push({
467
501
  sig: ` --no-${kebabLong}`,
468
502
  desc: `Negate --${kebabLong}`,
@@ -470,8 +504,7 @@ class Command {
470
504
  }
471
505
  }
472
506
  const commands = [];
473
- const showHelpSubcommand = this.#builtin.command.help && this.#subcommandsList.length > 0;
474
- if (showHelpSubcommand) {
507
+ if (this.#subcommandsList.length > 0) {
475
508
  commands.push({ name: 'help', desc: 'Show help for a command' });
476
509
  }
477
510
  for (const entry of this.#subcommandsList) {
@@ -594,50 +627,9 @@ class Command {
594
627
  #findSubcommandEntry(token) {
595
628
  return this.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
596
629
  }
597
- #createUnknownSubcommandError(subcommand) {
598
- const commandPath = this.#getCommandPath();
599
- return new CommanderError('UnknownSubcommand', `unknown subcommand "${subcommand}" for command "${commandPath}"`, commandPath);
600
- }
601
- #processHelpSubcommand(argv) {
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;
636
- }
637
- return argv;
638
- }
639
630
  #route(argv) {
640
631
  const chain = [this];
632
+ const cmds = [];
641
633
  let current = this;
642
634
  let idx = 0;
643
635
  while (idx < argv.length) {
@@ -648,10 +640,382 @@ class Command {
648
640
  if (!entry)
649
641
  break;
650
642
  current = entry.command;
643
+ cmds.push(token);
651
644
  chain.push(current);
652
645
  idx += 1;
653
646
  }
654
- return { chain, remaining: argv.slice(idx) };
647
+ return { chain, remaining: argv.slice(idx), cmds };
648
+ }
649
+ #controlScan(tailArgv, leafCommand) {
650
+ const controls = { help: false, version: false };
651
+ const separatorIndex = tailArgv.indexOf('--');
652
+ const beforeSeparator = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
653
+ const afterSeparator = separatorIndex === -1 ? [] : tailArgv.slice(separatorIndex + 1);
654
+ let helpTarget;
655
+ let scanStartIndex = 0;
656
+ if (beforeSeparator[0] === 'help') {
657
+ controls.help = true;
658
+ scanStartIndex = 1;
659
+ const candidate = beforeSeparator[1];
660
+ if (candidate !== undefined && !candidate.startsWith('-')) {
661
+ helpTarget = candidate;
662
+ scanStartIndex = 2;
663
+ }
664
+ }
665
+ const remainingBeforeSeparator = [];
666
+ for (let i = scanStartIndex; i < beforeSeparator.length; i += 1) {
667
+ const token = beforeSeparator[i];
668
+ if (token === '--help') {
669
+ controls.help = true;
670
+ continue;
671
+ }
672
+ if (token === '--version' && leafCommand.#supportsBuiltinVersion()) {
673
+ controls.version = true;
674
+ continue;
675
+ }
676
+ remainingBeforeSeparator.push(token);
677
+ }
678
+ const remaining = separatorIndex === -1
679
+ ? remainingBeforeSeparator
680
+ : [...remainingBeforeSeparator, '--', ...afterSeparator];
681
+ return {
682
+ controls,
683
+ remaining,
684
+ helpTarget,
685
+ };
686
+ }
687
+ #createContext(params) {
688
+ const { chain, cmds, envs, reporter } = params;
689
+ const leafCommand = chain[chain.length - 1];
690
+ const envSnapshot = { ...envs };
691
+ return {
692
+ cmd: leafCommand,
693
+ chain,
694
+ envs: envSnapshot,
695
+ controls: { help: false, version: false },
696
+ sources: {
697
+ preset: {
698
+ argv: [],
699
+ envs: {},
700
+ },
701
+ user: {
702
+ cmds: [...cmds],
703
+ argv: [],
704
+ envs: envSnapshot,
705
+ },
706
+ },
707
+ reporter: reporter ?? this.#reporter ?? new Reporter(),
708
+ };
709
+ }
710
+ #resolveHelpCommand(leafCommand, helpTarget) {
711
+ if (helpTarget === undefined) {
712
+ return leafCommand;
713
+ }
714
+ const target = leafCommand.#findSubcommandEntry(helpTarget);
715
+ if (target === undefined) {
716
+ return leafCommand;
717
+ }
718
+ return target.command;
719
+ }
720
+ async #preset(controlTailArgv, ctx, optionPolicyMap) {
721
+ const commandPath = ctx.chain[ctx.chain.length - 1].#getCommandPath();
722
+ const separatorIndex = controlTailArgv.indexOf('--');
723
+ const beforeSeparator = separatorIndex === -1 ? controlTailArgv : controlTailArgv.slice(0, separatorIndex);
724
+ const afterSeparator = separatorIndex === -1 ? [] : controlTailArgv.slice(separatorIndex + 1);
725
+ const rootScanResult = this.#scanPresetRootDirectives(beforeSeparator, commandPath);
726
+ const commandPreset = this.#resolveCommandPresetFromChain(ctx.chain);
727
+ const presetRoot = await this.#resolveEffectivePresetRoot(rootScanResult.cliPresetRoots, commandPreset, commandPath);
728
+ const fileScanResult = this.#scanPresetFileDirectives(rootScanResult.cleanArgv, commandPath);
729
+ const cleanArgv = separatorIndex === -1
730
+ ? fileScanResult.cleanArgv
731
+ : [...fileScanResult.cleanArgv, '--', ...afterSeparator];
732
+ const presetOptsFiles = this.#resolvePresetFileSources({
733
+ cliFiles: fileScanResult.cliPresetOptsFiles,
734
+ commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.opt),
735
+ presetRoot,
736
+ defaultFilename: DEFAULT_PRESET_OPTS_FILENAME,
737
+ });
738
+ const presetEnvsFiles = this.#resolvePresetFileSources({
739
+ cliFiles: fileScanResult.cliPresetEnvsFiles,
740
+ commandPresetFile: this.#normalizeCommandPresetFile(commandPreset?.env),
741
+ presetRoot,
742
+ defaultFilename: DEFAULT_PRESET_ENVS_FILENAME,
743
+ });
744
+ const userSources = {
745
+ cmds: [...ctx.sources.user.cmds],
746
+ argv: [...cleanArgv],
747
+ envs: { ...ctx.sources.user.envs },
748
+ };
749
+ const presetArgv = [];
750
+ for (const file of presetOptsFiles) {
751
+ const content = await this.#readPresetFile(file, commandPath);
752
+ if (content === undefined) {
753
+ continue;
754
+ }
755
+ const tokens = this.#tokenizePresetOptions(content);
756
+ this.#validatePresetOptionTokens(tokens, file.displayPath, commandPath);
757
+ this.#assertPresetOptionFragments(tokens, file.displayPath, ctx.chain, optionPolicyMap);
758
+ presetArgv.push(...tokens);
759
+ }
760
+ const presetEnvs = {};
761
+ for (const file of presetEnvsFiles) {
762
+ const content = await this.#readPresetFile(file, commandPath);
763
+ if (content === undefined) {
764
+ continue;
765
+ }
766
+ let parsed;
767
+ try {
768
+ parsed = parse(content);
769
+ }
770
+ catch (error) {
771
+ throw new CommanderError('ConfigurationError', `failed to parse preset envs file "${file.displayPath}": ${error.message}`, commandPath);
772
+ }
773
+ Object.assign(presetEnvs, parsed);
774
+ }
775
+ const sources = {
776
+ user: userSources,
777
+ preset: {
778
+ argv: presetArgv,
779
+ envs: presetEnvs,
780
+ },
781
+ };
782
+ const envs = { ...sources.user.envs, ...sources.preset.envs };
783
+ const tailArgv = [...sources.preset.argv, ...sources.user.argv];
784
+ return { tailArgv, envs, sources };
785
+ }
786
+ #resolveCommandPresetFromChain(chain) {
787
+ for (let index = chain.length - 1; index >= 0; index -= 1) {
788
+ const preset = chain[index].#presetConfig;
789
+ if (preset?.root !== undefined) {
790
+ return preset;
791
+ }
792
+ }
793
+ return undefined;
794
+ }
795
+ async #resolveEffectivePresetRoot(cliPresetRoots, commandPreset, commandPath) {
796
+ if (cliPresetRoots.length > 0) {
797
+ const root = cliPresetRoots[cliPresetRoots.length - 1];
798
+ return await this.#assertPresetRoot(root, PRESET_ROOT_FLAG, commandPath);
799
+ }
800
+ if (commandPreset?.root === undefined) {
801
+ return undefined;
802
+ }
803
+ return await this.#assertPresetRoot(commandPreset.root, 'command.preset.root', commandPath);
804
+ }
805
+ async #assertPresetRoot(root, sourceName, commandPath) {
806
+ if (!path__default.isAbsolute(root)) {
807
+ throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not an absolute directory`, commandPath);
808
+ }
809
+ let stats;
810
+ try {
811
+ stats = await stat(root);
812
+ }
813
+ catch (error) {
814
+ throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" cannot be accessed (${error.message})`, commandPath);
815
+ }
816
+ if (!stats.isDirectory()) {
817
+ throw new CommanderError('ConfigurationError', `invalid preset root from "${sourceName}": "${root}" is not a directory`, commandPath);
818
+ }
819
+ return root;
820
+ }
821
+ #normalizeCommandPresetFile(filepath) {
822
+ if (filepath === undefined) {
823
+ return undefined;
824
+ }
825
+ if (!this.#isValidPresetFileValue(filepath)) {
826
+ return undefined;
827
+ }
828
+ return filepath;
829
+ }
830
+ #resolvePresetFileSources(params) {
831
+ const { cliFiles, commandPresetFile, presetRoot, defaultFilename } = params;
832
+ if (cliFiles.length > 0) {
833
+ return cliFiles.map(filepath => ({
834
+ displayPath: filepath,
835
+ absolutePath: this.#resolvePresetFileAbsolutePath(filepath, presetRoot),
836
+ explicit: true,
837
+ }));
838
+ }
839
+ if (presetRoot === undefined) {
840
+ return [];
841
+ }
842
+ if (commandPresetFile !== undefined) {
843
+ return [
844
+ {
845
+ displayPath: commandPresetFile,
846
+ absolutePath: this.#resolvePresetFileAbsolutePath(commandPresetFile, presetRoot),
847
+ explicit: true,
848
+ },
849
+ ];
850
+ }
851
+ const absolutePath = path__default.resolve(presetRoot, defaultFilename);
852
+ return [
853
+ {
854
+ displayPath: absolutePath,
855
+ absolutePath,
856
+ explicit: false,
857
+ },
858
+ ];
859
+ }
860
+ #resolvePresetFileAbsolutePath(filepath, presetRoot) {
861
+ if (path__default.isAbsolute(filepath)) {
862
+ return filepath;
863
+ }
864
+ if (presetRoot !== undefined) {
865
+ return path__default.resolve(presetRoot, filepath);
866
+ }
867
+ return path__default.resolve(process.cwd(), filepath);
868
+ }
869
+ #assertPresetOptionFragments(tokens, filepath, chain, optionPolicyMap) {
870
+ if (tokens.length === 0) {
871
+ return;
872
+ }
873
+ const commandPath = chain[chain.length - 1].#getCommandPath();
874
+ try {
875
+ const { optionTokens, restArgs } = tokenize(tokens, commandPath);
876
+ void restArgs;
877
+ const { argTokens } = this.#resolve(chain, optionTokens, optionPolicyMap);
878
+ if (argTokens.length > 0) {
879
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": token "${argTokens[0].original}" cannot be resolved as an option fragment`, commandPath);
880
+ }
881
+ }
882
+ catch (error) {
883
+ if (error instanceof CommanderError) {
884
+ if (error.kind === 'ConfigurationError') {
885
+ throw error;
886
+ }
887
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": ${error.message}`, commandPath);
888
+ }
889
+ throw error;
890
+ }
891
+ }
892
+ #scanPresetRootDirectives(argv, commandPath) {
893
+ const cleanArgv = [];
894
+ const cliPresetRoots = [];
895
+ let index = 0;
896
+ while (index < argv.length) {
897
+ const token = argv[index];
898
+ if (token === PRESET_ROOT_FLAG) {
899
+ const value = argv[index + 1];
900
+ if (value === undefined || value.length === 0) {
901
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
902
+ }
903
+ cliPresetRoots.push(value);
904
+ index += 2;
905
+ continue;
906
+ }
907
+ if (token.startsWith(`${PRESET_ROOT_FLAG}=`)) {
908
+ const value = token.slice(PRESET_ROOT_FLAG.length + 1);
909
+ if (value.length === 0) {
910
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ROOT_FLAG}"`, commandPath);
911
+ }
912
+ cliPresetRoots.push(value);
913
+ index += 1;
914
+ continue;
915
+ }
916
+ cleanArgv.push(token);
917
+ index += 1;
918
+ }
919
+ return { cleanArgv, cliPresetRoots };
920
+ }
921
+ #scanPresetFileDirectives(argv, commandPath) {
922
+ const cleanArgv = [];
923
+ const cliPresetOptsFiles = [];
924
+ const cliPresetEnvsFiles = [];
925
+ const assertAndPush = (flag, value) => {
926
+ this.#assertPresetFileValue(value, flag, commandPath);
927
+ if (flag === PRESET_OPTS_FLAG) {
928
+ cliPresetOptsFiles.push(value);
929
+ }
930
+ else {
931
+ cliPresetEnvsFiles.push(value);
932
+ }
933
+ };
934
+ let index = 0;
935
+ while (index < argv.length) {
936
+ const token = argv[index];
937
+ if (token === PRESET_OPTS_FLAG || token === PRESET_ENVS_FLAG) {
938
+ const value = argv[index + 1];
939
+ if (value === undefined || value.length === 0) {
940
+ throw new CommanderError('ConfigurationError', `missing value for "${token}"`, commandPath);
941
+ }
942
+ assertAndPush(token, value);
943
+ index += 2;
944
+ continue;
945
+ }
946
+ if (token.startsWith(`${PRESET_OPTS_FLAG}=`)) {
947
+ const value = token.slice(PRESET_OPTS_FLAG.length + 1);
948
+ if (value.length === 0) {
949
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_OPTS_FLAG}"`, commandPath);
950
+ }
951
+ assertAndPush(PRESET_OPTS_FLAG, value);
952
+ index += 1;
953
+ continue;
954
+ }
955
+ if (token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
956
+ const value = token.slice(PRESET_ENVS_FLAG.length + 1);
957
+ if (value.length === 0) {
958
+ throw new CommanderError('ConfigurationError', `missing value for "${PRESET_ENVS_FLAG}"`, commandPath);
959
+ }
960
+ assertAndPush(PRESET_ENVS_FLAG, value);
961
+ index += 1;
962
+ continue;
963
+ }
964
+ cleanArgv.push(token);
965
+ index += 1;
966
+ }
967
+ return { cleanArgv, cliPresetOptsFiles, cliPresetEnvsFiles };
968
+ }
969
+ #isValidPresetFileValue(filepath) {
970
+ return filepath.length > 0 && !filepath.startsWith('..');
971
+ }
972
+ #assertPresetFileValue(filepath, directive, commandPath) {
973
+ if (this.#isValidPresetFileValue(filepath)) {
974
+ return;
975
+ }
976
+ throw new CommanderError('ConfigurationError', `invalid value for "${directive}": "${filepath}" (must be non-empty and must not start with "..")`, commandPath);
977
+ }
978
+ async #readPresetFile(file, commandPath) {
979
+ try {
980
+ return await readFile(file.absolutePath, 'utf8');
981
+ }
982
+ catch (error) {
983
+ const ioError = error;
984
+ if (!file.explicit && ioError.code === 'ENOENT') {
985
+ return undefined;
986
+ }
987
+ throw new CommanderError('ConfigurationError', `failed to read preset file "${file.displayPath}": ${ioError.message}`, commandPath);
988
+ }
989
+ }
990
+ #tokenizePresetOptions(content) {
991
+ return content
992
+ .split(/\s+/)
993
+ .map(token => token.trim())
994
+ .filter(token => token.length > 0);
995
+ }
996
+ #validatePresetOptionTokens(tokens, filepath, commandPath) {
997
+ if (tokens.length === 0) {
998
+ return;
999
+ }
1000
+ if (!tokens[0].startsWith('-')) {
1001
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": bare token "${tokens[0]}" cannot appear before any option token`, commandPath);
1002
+ }
1003
+ for (const token of tokens) {
1004
+ if (token === '--') {
1005
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": "--" is not allowed`, commandPath);
1006
+ }
1007
+ if (token === 'help' || token === '--help' || token === '--version') {
1008
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": control token "${token}" is not allowed`, commandPath);
1009
+ }
1010
+ if (token === PRESET_ROOT_FLAG ||
1011
+ token.startsWith(`${PRESET_ROOT_FLAG}=`) ||
1012
+ token === PRESET_OPTS_FLAG ||
1013
+ token.startsWith(`${PRESET_OPTS_FLAG}=`) ||
1014
+ token === PRESET_ENVS_FLAG ||
1015
+ token.startsWith(`${PRESET_ENVS_FLAG}=`)) {
1016
+ throw new CommanderError('ConfigurationError', `invalid preset options in "${filepath}": preset directive "${token}" is not allowed`, commandPath);
1017
+ }
1018
+ }
655
1019
  }
656
1020
  #resolve(chain, tokens, optionPolicyMap) {
657
1021
  const consumedTokens = new Map();
@@ -761,13 +1125,20 @@ class Command {
761
1125
  }
762
1126
  }
763
1127
  }
764
- const mergedOpts = {};
765
- for (const cmd of chain) {
766
- Object.assign(mergedOpts, optsMap.get(cmd) ?? {});
1128
+ const leafLocalOpts = {};
1129
+ const leafParsedOpts = optsMap.get(leafCommand) ?? {};
1130
+ for (const opt of leafCommand.#options) {
1131
+ if (Object.prototype.hasOwnProperty.call(leafParsedOpts, opt.long)) {
1132
+ leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
1133
+ }
767
1134
  }
768
1135
  const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
769
1136
  const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
770
- return { ctx, opts: mergedOpts, args, rawArgs };
1137
+ const parseCtx = {
1138
+ ...ctx,
1139
+ sources: this.#freezeInputSources(ctx.sources),
1140
+ };
1141
+ return { ctx: parseCtx, opts: leafLocalOpts, args, rawArgs };
771
1142
  }
772
1143
  #parseOptions(tokens, allOptions, envs) {
773
1144
  const opts = {};
@@ -954,29 +1325,19 @@ class Command {
954
1325
  #hasUserOption(long) {
955
1326
  return this.#options.some(option => option.long === long);
956
1327
  }
957
- #canUseBuiltinVersion() {
958
- return this.#version !== undefined;
1328
+ #supportsBuiltinVersion() {
1329
+ return this.#parent === undefined && this.#version !== undefined && this.#builtin.option.version;
959
1330
  }
960
1331
  #resolveOptionPolicy() {
961
1332
  const optionMap = new Map();
962
1333
  const hasUserColor = this.#hasUserOption('color');
963
- const hasUserHelp = this.#hasUserOption('help');
964
- const hasUserVersion = this.#hasUserOption('version');
965
1334
  const hasUserLogLevel = this.#hasUserOption('logLevel');
966
1335
  const hasUserSilent = this.#hasUserOption('silent');
967
1336
  const hasUserLogDate = this.#hasUserOption('logDate');
968
1337
  const hasUserLogColorful = this.#hasUserOption('logColorful');
969
- const enableBuiltinHelp = !hasUserHelp;
970
- const enableBuiltinVersion = !hasUserVersion && this.#canUseBuiltinVersion();
971
1338
  if (this.#builtin.option.color && !hasUserColor) {
972
1339
  optionMap.set('color', BUILTIN_COLOR_OPTION);
973
1340
  }
974
- if (enableBuiltinHelp) {
975
- optionMap.set('help', BUILTIN_HELP_OPTION);
976
- }
977
- if (enableBuiltinVersion) {
978
- optionMap.set('version', BUILTIN_VERSION_OPTION);
979
- }
980
1341
  if (this.#builtin.option.logLevel && !hasUserLogLevel) {
981
1342
  optionMap.set('logLevel', logLevelOption);
982
1343
  }
@@ -994,8 +1355,6 @@ class Command {
994
1355
  }
995
1356
  return {
996
1357
  mergedOptions: Array.from(optionMap.values()),
997
- enableBuiltinHelp,
998
- enableBuiltinVersion,
999
1358
  };
1000
1359
  }
1001
1360
  #buildOptionPolicyMap(chain) {
@@ -1032,6 +1391,9 @@ class Command {
1032
1391
  }
1033
1392
  }
1034
1393
  #validateOptionConfig(opt) {
1394
+ if (opt.long === 'help' || opt.long === 'version') {
1395
+ throw new CommanderError('ConfigurationError', `option long name "${opt.long}" is reserved`, this.#getCommandPath());
1396
+ }
1035
1397
  if (opt.type === 'boolean' && opt.args !== 'none') {
1036
1398
  throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
1037
1399
  }
@@ -1112,22 +1474,27 @@ class Command {
1112
1474
  process.exit(1);
1113
1475
  }
1114
1476
  }
1115
- #resolveHelpColorOption(tokens, envs, policy = this.#resolveOptionPolicy()) {
1477
+ #resolveHelpColorFromTailArgv(tailArgv, envs, policy = this.#resolveOptionPolicy()) {
1116
1478
  const colorOption = policy.mergedOptions.find(opt => opt.long === 'color');
1117
1479
  let color = !isNoColorEnabled(envs);
1118
1480
  if (!colorOption || colorOption.type !== 'boolean' || colorOption.args !== 'none') {
1119
1481
  return color;
1120
1482
  }
1121
- for (const token of tokens) {
1122
- if (token.type !== 'long' || token.name !== 'color') {
1483
+ const separatorIndex = tailArgv.indexOf('--');
1484
+ const scanTokens = separatorIndex === -1 ? tailArgv : tailArgv.slice(0, separatorIndex);
1485
+ for (const token of scanTokens) {
1486
+ if (token === '--color') {
1487
+ color = true;
1123
1488
  continue;
1124
1489
  }
1125
- const eqIdx = token.resolved.indexOf('=');
1126
- if (eqIdx === -1) {
1127
- color = true;
1490
+ if (token === '--no-color') {
1491
+ color = false;
1492
+ continue;
1493
+ }
1494
+ if (!token.startsWith('--color=')) {
1128
1495
  continue;
1129
1496
  }
1130
- const value = token.resolved.slice(eqIdx + 1);
1497
+ const value = token.slice('--color='.length);
1131
1498
  if (value === 'true') {
1132
1499
  color = true;
1133
1500
  }
@@ -1140,16 +1507,18 @@ class Command {
1140
1507
  }
1141
1508
  return color;
1142
1509
  }
1143
- #hasFlag(tokens, longName, shortName) {
1144
- for (const token of tokens) {
1145
- if (token.type === 'long' && token.name === longName) {
1146
- return true;
1147
- }
1148
- if (token.type === 'short' && token.name === shortName) {
1149
- return true;
1150
- }
1151
- }
1152
- return false;
1510
+ #freezeInputSources(sources) {
1511
+ return Object.freeze({
1512
+ preset: Object.freeze({
1513
+ argv: Object.freeze([...sources.preset.argv]),
1514
+ envs: Object.freeze({ ...sources.preset.envs }),
1515
+ }),
1516
+ user: Object.freeze({
1517
+ cmds: Object.freeze([...sources.user.cmds]),
1518
+ argv: Object.freeze([...sources.user.argv]),
1519
+ envs: Object.freeze({ ...sources.user.envs }),
1520
+ }),
1521
+ });
1153
1522
  }
1154
1523
  #getCommandPath() {
1155
1524
  const parts = [];
@@ -1164,6 +1533,96 @@ class Command {
1164
1533
  }
1165
1534
  }
1166
1535
 
1536
+ function isIpv4(rawValue) {
1537
+ const parts = rawValue.split('.');
1538
+ if (parts.length !== 4) {
1539
+ return false;
1540
+ }
1541
+ for (const part of parts) {
1542
+ if (part.length < 1 || !/^\d+$/.test(part)) {
1543
+ return false;
1544
+ }
1545
+ if (part.length > 1 && part.startsWith('0')) {
1546
+ return false;
1547
+ }
1548
+ const value = Number(part);
1549
+ if (!Number.isInteger(value) || value < 0 || value > 255) {
1550
+ return false;
1551
+ }
1552
+ }
1553
+ return true;
1554
+ }
1555
+ function countIpv6Segments(part, allowIpv4Tail) {
1556
+ if (!part) {
1557
+ return { count: 0, hasIpv4Tail: false };
1558
+ }
1559
+ const segments = part.split(':');
1560
+ let count = 0;
1561
+ let hasIpv4Tail = false;
1562
+ for (let i = 0; i < segments.length; ++i) {
1563
+ const segment = segments[i];
1564
+ const isLastSegment = i === segments.length - 1;
1565
+ if (!segment) {
1566
+ return null;
1567
+ }
1568
+ if (segment.includes('.')) {
1569
+ if (!allowIpv4Tail || !isLastSegment || hasIpv4Tail || !isIpv4(segment)) {
1570
+ return null;
1571
+ }
1572
+ hasIpv4Tail = true;
1573
+ count += 2;
1574
+ continue;
1575
+ }
1576
+ if (!/^[0-9A-Fa-f]{1,4}$/.test(segment)) {
1577
+ return null;
1578
+ }
1579
+ count += 1;
1580
+ }
1581
+ return { count, hasIpv4Tail };
1582
+ }
1583
+ function isIpv6(rawValue) {
1584
+ if (!rawValue || !/^[0-9A-Fa-f:.]+$/.test(rawValue)) {
1585
+ return false;
1586
+ }
1587
+ const doubleColonCount = rawValue.split('::').length - 1;
1588
+ if (doubleColonCount > 1) {
1589
+ return false;
1590
+ }
1591
+ if (doubleColonCount === 0) {
1592
+ const full = countIpv6Segments(rawValue, true);
1593
+ return full !== null && full.count === 8;
1594
+ }
1595
+ const [left, right] = rawValue.split('::');
1596
+ const leftPart = countIpv6Segments(left, right.length === 0);
1597
+ const rightPart = countIpv6Segments(right, true);
1598
+ if (!leftPart || !rightPart) {
1599
+ return false;
1600
+ }
1601
+ const totalSegments = leftPart.count + rightPart.count;
1602
+ return totalSegments < 8;
1603
+ }
1604
+ function isIp(rawValue) {
1605
+ return isIpv4(rawValue) || isIpv6(rawValue);
1606
+ }
1607
+ function isDomain(rawValue) {
1608
+ if (rawValue.length < 1 || rawValue.length > 253 || rawValue.endsWith('.')) {
1609
+ return false;
1610
+ }
1611
+ const labels = rawValue.split('.');
1612
+ if (labels.length < 2) {
1613
+ return false;
1614
+ }
1615
+ if (labels.some(label => label.length < 1 || label.length > 63)) {
1616
+ return false;
1617
+ }
1618
+ const labelPattern = /^[A-Za-z0-9-]+$/;
1619
+ if (labels.some(label => !labelPattern.test(label) || label.startsWith('-') || label.endsWith('-'))) {
1620
+ return false;
1621
+ }
1622
+ const topLevelLabel = labels[labels.length - 1];
1623
+ return /[A-Za-z]/.test(topLevelLabel);
1624
+ }
1625
+
1167
1626
  class Coerce {
1168
1627
  constructor() { }
1169
1628
  static create(name, expectedType, validator, errorMessage) {
@@ -1175,12 +1634,47 @@ class Coerce {
1175
1634
  return value;
1176
1635
  };
1177
1636
  }
1178
- static number(name, errorMessage) {
1179
- return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
1637
+ static choice(name, values, errorMessage) {
1638
+ return (rawValue) => {
1639
+ if (values.includes(rawValue)) {
1640
+ return rawValue;
1641
+ }
1642
+ throw new Error(errorMessage ?? `${name} is expected as one of [${values.join(', ')}], but got ${rawValue}`);
1643
+ };
1644
+ }
1645
+ static domain(name, errorMessage) {
1646
+ return (rawValue) => {
1647
+ if (isDomain(rawValue)) {
1648
+ return rawValue;
1649
+ }
1650
+ throw new Error(errorMessage ?? `${name} is expected as a valid domain, but got ${rawValue}`);
1651
+ };
1652
+ }
1653
+ static host(name, errorMessage) {
1654
+ return (rawValue) => {
1655
+ if (isIp(rawValue) || isDomain(rawValue)) {
1656
+ return rawValue;
1657
+ }
1658
+ throw new Error(errorMessage ?? `${name} is expected as a valid host (IP or domain), but got ${rawValue}`);
1659
+ };
1180
1660
  }
1181
1661
  static integer(name, errorMessage) {
1182
1662
  return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
1183
1663
  }
1664
+ static ip(name, errorMessage) {
1665
+ return (rawValue) => {
1666
+ if (isIp(rawValue)) {
1667
+ return rawValue;
1668
+ }
1669
+ throw new Error(errorMessage ?? `${name} is expected as a valid IP address, but got ${rawValue}`);
1670
+ };
1671
+ }
1672
+ static number(name, errorMessage) {
1673
+ return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
1674
+ }
1675
+ static port(name, errorMessage) {
1676
+ return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
1677
+ }
1184
1678
  static positiveInteger(name, errorMessage) {
1185
1679
  return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
1186
1680
  }
@@ -1193,9 +1687,12 @@ function camelToKebabCase(str) {
1193
1687
  return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
1194
1688
  }
1195
1689
  class CompletionCommand extends Command {
1196
- constructor(root, config) {
1197
- const paths = config.paths;
1690
+ constructor(root, config = {}) {
1198
1691
  const programName = config.programName ?? root.name ?? 'program';
1692
+ const paths = {
1693
+ ...createDefaultCompletionPaths(programName),
1694
+ ...config.paths,
1695
+ };
1199
1696
  super({ desc: 'Generate shell completion script' });
1200
1697
  this.option({
1201
1698
  long: 'bash',
@@ -1270,6 +1767,13 @@ class CompletionCommand extends Command {
1270
1767
  });
1271
1768
  }
1272
1769
  }
1770
+ function createDefaultCompletionPaths(programName) {
1771
+ return {
1772
+ bash: `~/.local/share/bash-completion/completions/${programName}`,
1773
+ fish: `~/.config/fish/completions/${programName}.fish`,
1774
+ pwsh: '~/.config/powershell/Microsoft.PowerShell_profile.ps1',
1775
+ };
1776
+ }
1273
1777
  function expandHome(filepath) {
1274
1778
  if (filepath.startsWith('~/') || filepath === '~') {
1275
1779
  const home = process.env['HOME'] || process.env['USERPROFILE'] || '';
@@ -1547,4 +2051,4 @@ class PwshCompletion {
1547
2051
  }
1548
2052
  }
1549
2053
 
1550
- export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, logColorfulOption, logDateOption, logLevelOption, silentOption };
2054
+ export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, logLevelOption, silentOption };