cli-api 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { a as getExecuteHandler, c as createChalk, i as OptType, n as Command, o as hasSubCommands, r as ExecutionContext, s as isExecutable } from "./interfaces-COq24bNI.mjs";
1
+ import { a as getExecuteHandler, c as createChalk, i as OptType, n as Command, o as hasSubCommands, r as ExecutionContext, s as isExecutable } from "./interfaces-CfKTHP7y.mjs";
2
2
  import Path from "path";
3
3
  import stringWidth from "string-width";
4
4
  import FileSys from "fs";
@@ -31,6 +31,9 @@ function print(str = "") {
31
31
  function printLn(...args) {
32
32
  console.log(...args);
33
33
  }
34
+ function printErrLn(...args) {
35
+ process.stderr.write(args.map(String).join(" ") + "\n");
36
+ }
34
37
  /**
35
38
  * Semantic categories for user-facing CLI errors.
36
39
  */
@@ -55,15 +58,18 @@ const ERROR_PRESENTATION = {
55
58
  }
56
59
  };
57
60
  function blockError(str, style, chalk) {
58
- const lines = str.split("\n");
59
- const width = Math.max(...lines.map((l) => stringWidth(l))) + 4;
61
+ const lines = wrapText(str, Math.max(getTerminalWidth() - 4, 1));
62
+ const width = Math.max(...lines.map((l) => stringWidth(l)), 0) + 4;
60
63
  const colorize = chalk.bgHex(ERROR_PRESENTATION[style].color).hex("#FEFBEC");
61
- printLn(colorize(space(width)));
64
+ printErrLn(colorize(space(width)));
62
65
  for (const line of lines) {
63
66
  const txt = ` ${line}`;
64
- printLn(colorize(txt + space(width, txt)));
67
+ printErrLn(colorize(txt + space(width, txt)));
65
68
  }
66
- printLn(colorize(space(width)));
69
+ printErrLn(colorize(space(width)));
70
+ }
71
+ function inlineError(str) {
72
+ printErrLn(str);
67
73
  }
68
74
  /**
69
75
  * Creates a structured CLI error.
@@ -94,7 +100,11 @@ function getErrorExitCode(error) {
94
100
  * @returns Nothing.
95
101
  */
96
102
  function printError(error, chalk) {
97
- blockError(error.message, error.type, chalk);
103
+ if (chalk.level > 0) {
104
+ blockError(error.message, error.type, chalk);
105
+ return;
106
+ }
107
+ inlineError(error.message);
98
108
  }
99
109
  function toArray(x) {
100
110
  if (!x) return EMPTY_ARRAY;
@@ -116,7 +126,7 @@ function space(len, str) {
116
126
  return len > 0 ? " ".repeat(len) : "";
117
127
  }
118
128
  function getTerminalWidth() {
119
- return process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
129
+ return process.stderr.columns && process.stderr.columns > 0 ? process.stderr.columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
120
130
  }
121
131
  function wrapText(text, width) {
122
132
  if (width <= 0) return text.split("\n");
@@ -165,12 +175,6 @@ function sortBy(arr, cmp) {
165
175
  return keys.map((i) => arr[i]);
166
176
  }
167
177
 
168
- //#endregion
169
- //#region src/commands/version.ts
170
- const versionCommand = new Command("version").describe("Displays current version").run(async (_, __, context) => {
171
- printLn(context.app._version);
172
- });
173
-
174
178
  //#endregion
175
179
  //#region src/options.ts
176
180
  /**
@@ -181,8 +185,8 @@ const versionCommand = new Command("version").describe("Displays current version
181
185
  */
182
186
  var UnknownOptionError = class extends Error {
183
187
  option;
184
- constructor(option) {
185
- super(`option ${option} not recognized`);
188
+ constructor(option, formattedOption = option) {
189
+ super(`option ${formattedOption} not recognized`);
186
190
  this.option = option;
187
191
  }
188
192
  };
@@ -212,6 +216,86 @@ function getMinRequiredCount(value) {
212
216
  if (typeof value === "number") return value;
213
217
  return 0;
214
218
  }
219
+ function getFixedPositionalCount(arg) {
220
+ const minRequired = getMinRequiredCount(arg.required);
221
+ if (isRepeatable(arg.repeatable)) {
222
+ const maxRepeatCount = getMaxRepeatCount(arg.repeatable);
223
+ if (maxRepeatCount !== void 0 && maxRepeatCount === minRequired) return minRequired;
224
+ return;
225
+ }
226
+ return minRequired > 0 ? 1 : void 0;
227
+ }
228
+ function getTrailingFixedRequiredCount(positonals, startIndex) {
229
+ let total = 0;
230
+ for (let i = startIndex; i < positonals.length; ++i) {
231
+ const fixedCount = getFixedPositionalCount(positonals[i]);
232
+ if (fixedCount === void 0 || fixedCount === 0) return;
233
+ total += fixedCount;
234
+ }
235
+ return total;
236
+ }
237
+ function describeItem(itemName) {
238
+ return itemName ? ` for ${itemName}` : "";
239
+ }
240
+ function formatToken(value, chalk) {
241
+ return chalk.level > 0 ? chalk.bold(value) : `\`${value}\``;
242
+ }
243
+ function formatItem(kind, value, chalk) {
244
+ return `${kind} ${formatToken(value, chalk)}`;
245
+ }
246
+ function formatPath(value, chalk) {
247
+ const fullPath = Path.resolve(value);
248
+ if (chalk.level === 0) return `"${fullPath}"`;
249
+ return chalk.underline(fullPath);
250
+ }
251
+ function describeOptionType(type, enumValues) {
252
+ if (Array.isArray(type) || type === OptType.ENUM) return `one of: ${(enumValues ?? (Array.isArray(type) ? type : [])).join(", ")}`;
253
+ switch (type) {
254
+ case OptType.BOOL: return "a boolean";
255
+ case OptType.INT: return "an integer";
256
+ case OptType.FLOAT: return "a number";
257
+ case OptType.STRING: return "a string";
258
+ case OptType.INPUT_FILE: return "a readable file path";
259
+ case OptType.INPUT_DIRECTORY: return "a readable directory path";
260
+ case OptType.OUTPUT_FILE: return "a writable file path";
261
+ case OptType.OUTPUT_DIRECTORY: return "a writable directory path";
262
+ case OptType.EMPTY_DIRECTORY: return "an empty directory path";
263
+ }
264
+ return "a valid value";
265
+ }
266
+ function ensureFiniteNumber(value, numericValue, type, itemName) {
267
+ if (Number.isNaN(numericValue) || !Number.isFinite(numericValue)) throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected ${describeOptionType(type)})`);
268
+ return numericValue;
269
+ }
270
+ function createPathError(kind, path, message, itemName) {
271
+ return /* @__PURE__ */ new Error(`${kind} ${path} ${message}${describeItem(itemName)}`);
272
+ }
273
+ function ensureInputDirectory(dir, chalk, itemName) {
274
+ const normalizedDir = Path.normalize(dir);
275
+ const fullPath = formatPath(normalizedDir, chalk);
276
+ const stat = statSync(normalizedDir);
277
+ if (!stat) throw createPathError("Directory", fullPath, "does not exist", itemName);
278
+ if (!stat.isDirectory()) throw createPathError("Directory", fullPath, "is not a directory", itemName);
279
+ try {
280
+ FileSys.accessSync(normalizedDir, FileSys.constants.R_OK | FileSys.constants.X_OK);
281
+ } catch {
282
+ throw createPathError("Directory", fullPath, "is not readable", itemName);
283
+ }
284
+ return normalizedDir;
285
+ }
286
+ function ensureOutputDirectory(dir, chalk, itemName) {
287
+ const normalizedDir = Path.normalize(dir);
288
+ const fullPath = formatPath(normalizedDir, chalk);
289
+ const stat = statSync(normalizedDir);
290
+ if (!stat) throw createPathError("Directory", fullPath, "does not exist", itemName);
291
+ if (!stat.isDirectory()) throw createPathError("Directory", fullPath, "is not a directory", itemName);
292
+ try {
293
+ FileSys.accessSync(normalizedDir, FileSys.constants.W_OK);
294
+ } catch {
295
+ throw createPathError("Directory", fullPath, "is not writable", itemName);
296
+ }
297
+ return normalizedDir;
298
+ }
215
299
  function assertValidCount(name, value, { allowZero = false } = {}) {
216
300
  if (!Number.isInteger(value) || value < 0 || !allowZero && value === 0) throw new Error(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer`);
217
301
  }
@@ -228,10 +312,12 @@ function validatePositionalDefinitions(cmd) {
228
312
  if (!repeatable) throw new Error(`"${arg.name}" argument cannot use a numeric required count unless it is repeatable`);
229
313
  }
230
314
  if (typeof arg.repeatable === "number") assertValidCount(`"${arg.name}" argument repeatable count`, arg.repeatable);
231
- if (repeatable && i < cmd.positonals.length - 1) throw new Error("Only the last argument can be repeatable");
315
+ if (repeatable && i < cmd.positonals.length - 1) {
316
+ if (getTrailingFixedRequiredCount(cmd.positonals, i + 1) === void 0) throw new Error("Repeatable arguments can only be followed by required arguments with fixed counts");
317
+ }
232
318
  if (maxRepeatCount !== void 0 && minRequired > maxRepeatCount) throw new Error(`"${arg.name}" argument requires at least ${minRequired} values but allows at most ${maxRepeatCount}`);
233
319
  if (encounteredOptionalPositional && minRequired > 0) throw new Error("Required arguments cannot come after optional arguments");
234
- if (minRequired === 0) encounteredOptionalPositional = true;
320
+ if (minRequired === 0 && !repeatable) encounteredOptionalPositional = true;
235
321
  }
236
322
  }
237
323
  function pushRepeatableValue(target, value, itemName, maxCount, kind) {
@@ -307,9 +393,39 @@ function validateCommandConfig(cmd) {
307
393
  }
308
394
  }
309
395
  }
310
- function parseArgs(cmd, argv) {
396
+ function assignPositionalArguments(positonals, rawArgs, opts, chalk) {
397
+ if (!positonals?.length) return [...rawArgs];
311
398
  const args = [];
399
+ let rawArgIdx = 0;
400
+ for (let i = 0; i < positonals.length; ++i) {
401
+ const def = positonals[i];
402
+ const k = def.key ?? def.name;
403
+ if (isRepeatable(def.repeatable)) {
404
+ const trailingFixedRequiredCount = i < positonals.length - 1 ? getTrailingFixedRequiredCount(positonals, i + 1) ?? 0 : 0;
405
+ const valueCount = Math.max(0, rawArgs.length - rawArgIdx - trailingFixedRequiredCount);
406
+ const maxRepeatCount = getMaxRepeatCount(def.repeatable);
407
+ const arr = opts[k] ??= [];
408
+ if (maxRepeatCount !== void 0 && valueCount > maxRepeatCount) throw new Error(`"${def.name}" argument allows at most ${maxRepeatCount} value${maxRepeatCount === 1 ? "" : "s"}`);
409
+ for (let j = 0; j < valueCount; ++j) {
410
+ let value$1 = rawArgs[rawArgIdx++];
411
+ if (def.type != null) value$1 = coerceType(value$1, def.type, chalk, formatItem("argument", def.name, chalk), getEnumValues(def));
412
+ pushRepeatableValue(arr, value$1, def.name, maxRepeatCount, "argument");
413
+ args.push(value$1);
414
+ }
415
+ continue;
416
+ }
417
+ if (rawArgIdx >= rawArgs.length) continue;
418
+ let value = rawArgs[rawArgIdx++];
419
+ if (def.type != null) value = coerceType(value, def.type, chalk, formatItem("argument", def.name, chalk), getEnumValues(def));
420
+ opts[k] = value;
421
+ args.push(value);
422
+ }
423
+ if (rawArgIdx < rawArgs.length) args.push(...rawArgs.slice(rawArgIdx));
424
+ return args;
425
+ }
426
+ function parseArgs(cmd, argv, chalk, { skipRequiredOptions = false, skipRequiredPositionals = false } = {}) {
312
427
  const opts = Object.create(null);
428
+ const rawArgs = [];
313
429
  let parseFlags = true;
314
430
  const allOptions = getOptions(cmd);
315
431
  validateCommandConfig(cmd);
@@ -342,7 +458,6 @@ function parseArgs(cmd, argv) {
342
458
  if (!findOpt(ch)) return ch;
343
459
  }
344
460
  };
345
- let argIdx = 0;
346
461
  for (let i = 0; i < argv.length; ++i) {
347
462
  let token = argv[i];
348
463
  if (parseFlags && token === "--") {
@@ -359,15 +474,15 @@ function parseArgs(cmd, argv) {
359
474
  }
360
475
  const name = token.slice(2);
361
476
  const match = findOpt(name);
362
- if (!match) throw new UnknownOptionError(`--${name}`);
477
+ if (!match) throw new UnknownOptionError(`--${name}`, formatToken(`--${name}`, chalk));
363
478
  const { opt, negated } = match;
364
- if (negated && inlineValue !== void 0) throw new Error(`Option \`--no-${opt.name}\` does not take a value`);
479
+ if (negated && inlineValue !== void 0) throw new Error(`Option ${formatToken(`--no-${opt.name}`, chalk)} does not take a value`);
365
480
  let value = inlineValue;
366
481
  if (negated) value = getOptionNoPrefixValue(opt);
367
482
  else if (value === void 0) if (opt.valueNotRequired) value = getOptionImplicitValue(opt);
368
483
  else if (i < argv.length - 1) value = argv[++i];
369
- else throw new Error(`Missing required value for option \`${token}\``);
370
- if (opt.type != null) value = coerceType(value, opt.type, `option \`${token}\``, getEnumValues(opt));
484
+ else throw new Error(`Missing required value for option ${formatToken(token, chalk)}`);
485
+ if (opt.type != null) value = coerceType(value, opt.type, chalk, formatItem("option", token, chalk), getEnumValues(opt));
371
486
  const k = opt.key ?? opt.name;
372
487
  if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
373
488
  else opts[k] = value;
@@ -379,13 +494,13 @@ function parseArgs(cmd, argv) {
379
494
  inlineValue = equalIndex === -1 ? void 0 : clusterText.slice(equalIndex + 1);
380
495
  if (hasInlineAssignment) {
381
496
  const unknownOption = getUnknownShortOption(cluster);
382
- if (unknownOption !== void 0) throw new UnknownOptionError(`-${unknownOption}`);
497
+ if (unknownOption !== void 0) throw new UnknownOptionError(`-${unknownOption}`, formatToken(`-${unknownOption}`, chalk));
383
498
  }
384
499
  let j = 0;
385
500
  while (j < cluster.length) {
386
501
  const ch = cluster[j];
387
502
  const match = findOpt(ch);
388
- if (!match) throw new UnknownOptionError(`-${ch}`);
503
+ if (!match) throw new UnknownOptionError(`-${ch}`, formatToken(`-${ch}`, chalk));
389
504
  const { opt } = match;
390
505
  if (opt.valueNotRequired) {
391
506
  const k$1 = opt.key ?? opt.name;
@@ -398,124 +513,118 @@ function parseArgs(cmd, argv) {
398
513
  let value;
399
514
  const remainder = cluster.slice(j + 1);
400
515
  if (remainder.length && !hasInlineAssignment) value = remainder;
401
- else if (remainder.length && hasInlineAssignment) throw new Error(`Missing required value for option \`-${ch}\``);
516
+ else if (remainder.length && hasInlineAssignment) throw new Error(`Missing required value for option ${formatToken(`-${ch}`, chalk)}`);
402
517
  else if (inlineValue !== void 0 && remainder.length === 0) value = inlineValue;
403
518
  else if (i < argv.length - 1) value = argv[++i];
404
- else throw new Error(`Missing required value for option "-${ch}"`);
405
- if (opt.type != null) value = coerceType(value, opt.type, `option \`-${ch}\``, getEnumValues(opt));
519
+ else throw new Error(`Missing required value for option ${formatToken(`-${ch}`, chalk)}`);
520
+ if (opt.type != null) value = coerceType(value, opt.type, chalk, formatItem("option", `-${ch}`, chalk), getEnumValues(opt));
406
521
  const k = opt.key ?? opt.name;
407
522
  if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
408
523
  else opts[k] = value;
409
524
  break;
410
525
  }
411
526
  }
412
- } else {
413
- let value = token;
414
- const def = cmd.positonals?.[argIdx];
415
- if (def) {
416
- if (def.type != null) value = coerceType(value, def.type, `argument \`${def.name}\``, getEnumValues(def));
417
- const k = def.key ?? def.name;
418
- if (isRepeatable(def.repeatable)) {
419
- const arr = opts[k] ??= [];
420
- pushRepeatableValue(arr, value, def.name, getMaxRepeatCount(def.repeatable), "argument");
421
- for (i = i + 1; i < argv.length; ++i) {
422
- let v = argv[i];
423
- if (parseFlags && v === "--") {
424
- parseFlags = false;
425
- continue;
426
- }
427
- if (parseFlags && v.startsWith("-")) {
428
- i -= 1;
429
- break;
430
- }
431
- if (def.type != null) v = coerceType(v, def.type, `argument \`${def.name}\``, getEnumValues(def));
432
- pushRepeatableValue(arr, v, def.name, getMaxRepeatCount(def.repeatable), "argument");
433
- }
434
- argIdx = isRepeatable(def.repeatable) && cmd.positonals ? cmd.positonals.length : argIdx;
435
- args.push(...arr);
436
- continue;
437
- } else opts[k] = value;
438
- }
439
- args.push(value);
440
- ++argIdx;
441
- }
527
+ } else rawArgs.push(token);
442
528
  }
529
+ const args = assignPositionalArguments(cmd.positonals, rawArgs, opts, chalk);
443
530
  if (allOptions.length) for (const opt of allOptions) {
444
531
  const k = opt.key ?? opt.name;
445
532
  if (opts[k] === void 0) {
446
533
  if (opt.defaultValue !== void 0) opts[k] = resolve(opt.defaultValue);
447
- else if (opt.required) throw new Error(`\`${getOptName(opt)}\` option is required`);
534
+ else if (opt.required && !skipRequiredOptions) throw new Error(`${formatToken(getOptName(opt), chalk)} option is required`);
448
535
  }
449
536
  }
450
537
  if (cmd.positonals?.length) for (let i = 0; i < cmd.positonals.length; ++i) {
451
538
  const a = cmd.positonals[i];
452
539
  const minRequired = getMinRequiredCount(a.required);
453
- if (minRequired > 0 && argIdx <= i && !isRepeatable(a.repeatable)) throw new Error(`\`${a.name}\` argument is required`);
454
540
  const k = a.key ?? a.name;
455
- if (isRepeatable(a.repeatable) && (opts[k]?.length ?? 0) < minRequired) throw new Error(`\`${a.name}\` argument requires at least ${minRequired} value${minRequired === 1 ? "" : "s"}`);
541
+ if (!isRepeatable(a.repeatable) && minRequired > 0 && opts[k] === void 0 && !skipRequiredPositionals) throw new Error(`${formatToken(a.name, chalk)} argument is required`);
542
+ if (isRepeatable(a.repeatable) && (opts[k]?.length ?? 0) < minRequired && !skipRequiredPositionals) throw new Error(`\`${a.name}\` argument requires at least ${minRequired} value${minRequired === 1 ? "" : "s"}`);
456
543
  if (k && opts[k] === void 0 && a.defaultValue !== void 0) opts[k] = resolve(a.defaultValue);
457
544
  }
458
545
  return [args, opts];
459
546
  }
460
- function coerceType(value, type, itemName, enumValues) {
547
+ function coerceType(value, type, chalk, itemName, enumValues) {
461
548
  const normalizedEnumValue = () => {
462
549
  const normalized = String(value).trim().toLowerCase();
463
550
  const allowedValues = enumValues ?? (Array.isArray(type) ? type : void 0);
464
- if (allowedValues !== void 0 && !allowedValues.includes(normalized)) {
465
- const itemText = itemName ? ` for ${itemName}` : "";
466
- throw new Error(`Invalid value "${value}"${itemText} (expected one of: ${allowedValues.join(", ")})`);
467
- }
551
+ if (allowedValues !== void 0 && !allowedValues.includes(normalized)) throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected one of: ${allowedValues.join(", ")})`);
468
552
  return normalized;
469
553
  };
470
554
  if (Array.isArray(type)) return normalizedEnumValue();
471
555
  switch (type) {
472
- case OptType.BOOL: return toBool(value);
473
- case OptType.INT: return Math.trunc(Number(value));
474
- case OptType.FLOAT: return Number(value);
556
+ case OptType.BOOL: try {
557
+ return toBool(value);
558
+ } catch {
559
+ throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected ${describeOptionType(type)})`);
560
+ }
561
+ case OptType.INT: return Math.trunc(ensureFiniteNumber(value, Number(value), type, itemName));
562
+ case OptType.FLOAT: return ensureFiniteNumber(value, Number(value), type, itemName);
475
563
  case OptType.ENUM: return normalizedEnumValue();
476
564
  case OptType.STRING: return String(value);
477
565
  case OptType.INPUT_FILE: {
478
566
  if (value === "-") return "/dev/stdin";
479
567
  const file = Path.normalize(value);
480
- const fullPath = Path.resolve(file);
568
+ const fullPath = formatPath(file, chalk);
481
569
  const stat = statSync(file);
482
- if (!stat) throw new Error(`File ${createChalk().underline(fullPath)} does not exist`);
483
- if (!stat.isFile()) throw new Error(`${createChalk().underline(fullPath)} is not a file`);
570
+ if (!stat) throw createPathError("File", fullPath, "does not exist", itemName);
571
+ if (!stat.isFile()) throw createPathError("File", fullPath, "is not a file", itemName);
484
572
  try {
485
573
  FileSys.accessSync(file, FileSys.constants.R_OK);
486
- } catch (err) {
487
- throw new Error(`${createChalk().underline(fullPath)} is not readable`);
574
+ } catch {
575
+ throw createPathError("File", fullPath, "is not readable", itemName);
488
576
  }
489
577
  return file;
490
578
  }
491
- case OptType.INPUT_DIRECTORY: {
492
- const dir = Path.normalize(value);
493
- FileSys.accessSync(dir, FileSys.constants.X_OK);
494
- return dir;
495
- }
579
+ case OptType.INPUT_DIRECTORY: return ensureInputDirectory(value, chalk, itemName);
496
580
  case OptType.OUTPUT_FILE: {
497
581
  if (value === "-") return "/dev/stdout";
498
582
  const file = Path.normalize(value);
583
+ const fullPath = formatPath(file, chalk);
499
584
  const stat = statSync(file);
500
585
  if (stat) {
501
- if (!stat.isFile()) throw new Error(`'${file}' is not a file`);
502
- FileSys.accessSync(file, FileSys.constants.W_OK);
503
- } else FileSys.accessSync(Path.dirname(file), FileSys.constants.W_OK);
586
+ if (!stat.isFile()) throw createPathError("File", fullPath, "is not a file", itemName);
587
+ try {
588
+ FileSys.accessSync(file, FileSys.constants.W_OK);
589
+ } catch {
590
+ throw createPathError("File", fullPath, "is not writable", itemName);
591
+ }
592
+ } else {
593
+ const parentDir = Path.dirname(file);
594
+ const fullParentDir = formatPath(parentDir, chalk);
595
+ const parentStat = statSync(parentDir);
596
+ if (!parentStat) throw createPathError("Directory", fullParentDir, "does not exist", itemName);
597
+ if (!parentStat.isDirectory()) throw createPathError("Directory", fullParentDir, "is not a directory", itemName);
598
+ try {
599
+ FileSys.accessSync(parentDir, FileSys.constants.W_OK);
600
+ } catch {
601
+ throw createPathError("Directory", fullParentDir, "is not writable", itemName);
602
+ }
603
+ }
504
604
  return file;
505
605
  }
506
- case OptType.OUTPUT_DIRECTORY:
507
- FileSys.accessSync(value, FileSys.constants.W_OK);
508
- return Path.normalize(value);
606
+ case OptType.OUTPUT_DIRECTORY: return ensureOutputDirectory(value, chalk, itemName);
509
607
  case OptType.EMPTY_DIRECTORY: {
510
608
  const dir = Path.normalize(value);
609
+ const fullPath = formatPath(dir, chalk);
511
610
  let files = [];
512
611
  try {
513
612
  files = FileSys.readdirSync(dir);
514
613
  } catch (err) {
515
- if (err?.code === "ENOENT") FileSys.accessSync(Path.dirname(dir), FileSys.constants.W_OK);
516
- else throw err;
614
+ if (err?.code === "ENOENT") {
615
+ const parentDir = Path.dirname(dir);
616
+ const fullParentDir = formatPath(parentDir, chalk);
617
+ const parentStat = statSync(parentDir);
618
+ if (!parentStat) throw createPathError("Directory", fullParentDir, "does not exist", itemName);
619
+ if (!parentStat.isDirectory()) throw createPathError("Directory", fullParentDir, "is not a directory", itemName);
620
+ try {
621
+ FileSys.accessSync(parentDir, FileSys.constants.W_OK);
622
+ } catch {
623
+ throw createPathError("Directory", fullParentDir, "is not writable", itemName);
624
+ }
625
+ } else throw createPathError("Directory", fullPath, "is not readable", itemName);
517
626
  }
518
- if (files.length) throw new Error(`${createChalk().underline(dir)} is not empty`);
627
+ if (files.length) throw createPathError("Directory", fullPath, "is not empty", itemName);
519
628
  return dir;
520
629
  }
521
630
  }
@@ -525,8 +634,8 @@ function getOptName(opt) {
525
634
  return (opt.name.length > 1 ? "--" : "-") + opt.name;
526
635
  }
527
636
  function findSubCommand(name, subCommands) {
528
- const cmdName = String(name).trim().replace(/^-{1,2}/, "").toLowerCase();
529
- return subCommands.find((c) => c.name === cmdName || includes(cmdName, c.alias));
637
+ const cmdName = String(name).trim().toLowerCase();
638
+ return subCommands.find((c) => c.name.toLowerCase() === cmdName || toArray(c.alias).some((alias) => alias.toLowerCase() === cmdName));
530
639
  }
531
640
  function getCommand(path, subCommands) {
532
641
  if (!path.length) throw new Error("Command path is required.");
@@ -551,41 +660,7 @@ function getCommand(path, subCommands) {
551
660
  }
552
661
 
553
662
  //#endregion
554
- //#region src/global-options.ts
555
- const HELP_OPTION = {
556
- name: "help",
557
- alias: "h",
558
- description: "Show help text",
559
- type: OptType.BOOL,
560
- valueNotRequired: true,
561
- valueIfSet: true
562
- };
563
- const COLOR_OPTION = {
564
- name: "color",
565
- description: "Control ANSI color output.",
566
- type: OptType.ENUM,
567
- enumValues: [
568
- "always",
569
- "never",
570
- "auto"
571
- ],
572
- valuePlaceholder: "WHEN",
573
- valueNotRequired: true,
574
- valueIfSet: "always",
575
- noPrefix: true,
576
- valueIfNoPrefix: "never",
577
- defaultValue: "auto"
578
- };
579
- function getGlobalOptions(app) {
580
- return sortOptions([
581
- HELP_OPTION,
582
- COLOR_OPTION,
583
- ...app._globalOptions ?? []
584
- ]);
585
- }
586
-
587
- //#endregion
588
- //#region src/app-help.ts
663
+ //#region src/print-command-help.ts
589
664
  function shouldWrapHelpEntry$1(label, description, labelWidth) {
590
665
  if (!description) return false;
591
666
  const terminalWidth = getTerminalWidth();
@@ -617,71 +692,6 @@ function printOptionEntries$1(entries) {
617
692
  if (printHelpEntry$1(label, description, width, forceWrap) && index < entries.length - 1) printLn();
618
693
  });
619
694
  }
620
- function printHelp(context, commands) {
621
- const app = context.app;
622
- const chalk = context.chalk;
623
- print(chalk.green(app.name));
624
- const { _author: author, _version: version } = app;
625
- if (version) print(` ver. ${chalk.yellow(version)}`);
626
- if (author) print(` by ${chalk.cyan(author)}`);
627
- printLn();
628
- if (app.description) {
629
- printLn();
630
- printLn(app.description);
631
- }
632
- printLn();
633
- printLn(chalk.yellow("Usage:"));
634
- print(` ${chalk.cyan(getProcName(app))}`);
635
- if (hasSubCommands(app)) print(` ${chalk.gray("[")}--GLOBAL-OPTIONS${chalk.gray("]")} ${chalk.gray("<")}COMMAND${chalk.gray(">")}`);
636
- printLn("\n");
637
- if (commands.length) printAvailableCommands(commands, "Commands:", chalk);
638
- const globalOptions = getGlobalOptions(app);
639
- if (globalOptions.length) {
640
- printLn();
641
- printLn(chalk.yellow("Global Options:"));
642
- printOptionEntries$1(globalOptions.map((option) => formatOption(option, chalk)));
643
- }
644
- }
645
- function printAvailableCommands(commands, title, chalk) {
646
- if (!commands.length) return;
647
- printLn(chalk.yellow(title));
648
- const width = Math.max(...commands.map((c) => stringWidth(c.name))) + 2;
649
- for (const cmd of commands) printHelpEntry$1(chalk.green(cmd.name), cmd.description, width);
650
- }
651
-
652
- //#endregion
653
- //#region src/print-command-help.ts
654
- function shouldWrapHelpEntry(label, description, labelWidth) {
655
- if (!description) return false;
656
- const terminalWidth = getTerminalWidth();
657
- const inlineIndent = labelWidth + 4;
658
- const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
659
- return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
660
- }
661
- function printHelpEntry(label, description, labelWidth, forceWrap = false) {
662
- print(` ${label}`);
663
- if (!description) {
664
- printLn();
665
- return false;
666
- }
667
- if (!(forceWrap || shouldWrapHelpEntry(label, description, labelWidth))) {
668
- printLn(`${space(labelWidth + 2, label)}${description}`);
669
- return false;
670
- }
671
- printLn();
672
- const terminalWidth = getTerminalWidth();
673
- const descriptionIndent = " ".repeat(10);
674
- const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
675
- for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
676
- return true;
677
- }
678
- function printOptionEntries(entries) {
679
- const width = Math.max(...entries.map((line) => stringWidth(line[0])));
680
- const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry(label, description, width));
681
- entries.forEach(([label, description], index) => {
682
- if (printHelpEntry(label, description, width, forceWrap) && index < entries.length - 1) printLn();
683
- });
684
- }
685
695
  function getCommandLabel(context, path) {
686
696
  const proc = context.chalk.cyan(getProcName(context.app));
687
697
  if (!path.length) return proc;
@@ -737,12 +747,12 @@ function printCommandHelp(context, cmd, path = []) {
737
747
  const allOptions = getOptions(cmd);
738
748
  if (allOptions.length) {
739
749
  printLn(chalk.yellow("\nOptions:"));
740
- printOptionEntries(allOptions.map((option) => formatOption(option, chalk)));
750
+ printOptionEntries$1(allOptions.map((option) => formatOption(option, chalk)));
741
751
  }
742
752
  if (cmd.positonals?.length) {
743
753
  printLn(chalk.yellow("\nArguments:"));
744
754
  const width = Math.max(...cmd.positonals.map((arg) => stringWidth(arg.name)));
745
- for (const arg of cmd.positonals) printHelpEntry(chalk.green(arg.name), arg.description, width);
755
+ for (const arg of cmd.positonals) printHelpEntry$1(chalk.green(arg.name), arg.description, width);
746
756
  }
747
757
  }
748
758
  if (hasSubCommands(cmd)) {
@@ -753,7 +763,7 @@ function printCommandHelp(context, cmd, path = []) {
753
763
  if (globalOptions.length) {
754
764
  printLn();
755
765
  printLn(chalk.yellow("Global Options:"));
756
- printOptionEntries(globalOptions.map((option) => formatOption(option, chalk)));
766
+ printOptionEntries$1(globalOptions.map((option) => formatOption(option, chalk)));
757
767
  }
758
768
  if (cmd.alias) {
759
769
  const aliases = toArray(cmd.alias);
@@ -762,23 +772,176 @@ function printCommandHelp(context, cmd, path = []) {
762
772
  }
763
773
 
764
774
  //#endregion
765
- //#region src/commands/command-help.ts
766
- const helpCommand = new Command("help").describe("Displays help for a command").arg("command", {
767
- description: "The command path.",
768
- repeatable: true
769
- }).run(async (commandPath, _, context) => {
775
+ //#region src/builtins.ts
776
+ const DEFAULT_HELP_CONFIG = {
777
+ name: "help",
778
+ alias: "h"
779
+ };
780
+ const DEFAULT_VERSION_CONFIG = { name: "version" };
781
+ const DEFAULT_COLOR_CONFIG = { name: "color" };
782
+ function resolveBuiltinConfig(defaults, config) {
783
+ return {
784
+ ...defaults,
785
+ ...config ?? {}
786
+ };
787
+ }
788
+ function getHelpConfig(app) {
789
+ return resolveBuiltinConfig(DEFAULT_HELP_CONFIG, app._helpConfig);
790
+ }
791
+ function getVersionConfig(app) {
792
+ return resolveBuiltinConfig(DEFAULT_VERSION_CONFIG, app._versionConfig);
793
+ }
794
+ function getColorConfig(app) {
795
+ return {
796
+ ...DEFAULT_COLOR_CONFIG,
797
+ ...app._colorConfig ?? {}
798
+ };
799
+ }
800
+ function createBuiltinOption(key, config, description) {
801
+ if (config.disableOption) return;
802
+ return {
803
+ name: config.name,
804
+ ...config.alias !== void 0 ? { alias: config.alias } : {},
805
+ description,
806
+ key,
807
+ type: OptType.BOOL,
808
+ valueNotRequired: true,
809
+ valueIfSet: true
810
+ };
811
+ }
812
+ function createHelpCommand(app) {
813
+ const config = getHelpConfig(app);
814
+ if (config.disableCommand) return;
815
+ const command = new Command(config.name).describe("Displays help for a command").arg("command", {
816
+ description: "The command path.",
817
+ repeatable: true
818
+ }).run(async ({ command: commandPath = [] }, context) => {
819
+ const rootCommands = getRootCommands(context.app);
820
+ if (commandPath.length) {
821
+ const { command: command$1, path } = getCommand(commandPath, rootCommands);
822
+ printCommandHelp(context, command$1, path);
823
+ } else if (hasSubCommands(context.app)) printHelp(context, rootCommands);
824
+ else printCommandHelp(context, context.app, []);
825
+ return 0;
826
+ });
827
+ const aliases = toArray(config.alias);
828
+ if (aliases.length) command.aliases(...aliases);
829
+ return command;
830
+ }
831
+ function createVersionCommand(app) {
832
+ const config = getVersionConfig(app);
833
+ if (config.disableCommand) return;
834
+ const command = new Command(config.name).describe("Displays current version").run(async (_, context) => {
835
+ printLn(context.app._version);
836
+ return 0;
837
+ });
838
+ const aliases = toArray(config.alias);
839
+ if (aliases.length) command.aliases(...aliases);
840
+ return command;
841
+ }
842
+ function getHelpOption(app) {
843
+ return createBuiltinOption("help", getHelpConfig(app), "Show help text");
844
+ }
845
+ function getVersionOption(app) {
846
+ return createBuiltinOption("version", getVersionConfig(app), "Show current version");
847
+ }
848
+ function getColorOption(app) {
849
+ const config = getColorConfig(app);
850
+ if (config.disableOption) return;
851
+ return {
852
+ name: config.name,
853
+ ...config.alias !== void 0 ? { alias: config.alias } : {},
854
+ description: "Control ANSI color output.",
855
+ key: "color",
856
+ type: OptType.ENUM,
857
+ enumValues: [
858
+ "always",
859
+ "never",
860
+ "auto"
861
+ ],
862
+ valuePlaceholder: "WHEN",
863
+ valueNotRequired: true,
864
+ valueIfSet: "always",
865
+ noPrefix: true,
866
+ valueIfNoPrefix: "never",
867
+ defaultValue: "auto"
868
+ };
869
+ }
870
+ function getGlobalOptions(app) {
871
+ return sortOptions([...[
872
+ getHelpOption(app),
873
+ getVersionOption(app),
874
+ getColorOption(app)
875
+ ].filter((value) => value !== void 0), ...app._globalOptions ?? []]);
876
+ }
877
+ function getRootCommands(app) {
878
+ return [...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [], ...[createVersionCommand(app), createHelpCommand(app)].filter((value) => value !== void 0)];
879
+ }
880
+
881
+ //#endregion
882
+ //#region src/app-help.ts
883
+ function shouldWrapHelpEntry(label, description, labelWidth) {
884
+ if (!description) return false;
885
+ const terminalWidth = getTerminalWidth();
886
+ const inlineIndent = labelWidth + 4;
887
+ const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
888
+ return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
889
+ }
890
+ function printHelpEntry(label, description, labelWidth, forceWrap = false) {
891
+ print(` ${label}`);
892
+ if (!description) {
893
+ printLn();
894
+ return false;
895
+ }
896
+ if (!(forceWrap || shouldWrapHelpEntry(label, description, labelWidth))) {
897
+ printLn(`${space(labelWidth + 2, label)}${description}`);
898
+ return false;
899
+ }
900
+ printLn();
901
+ const terminalWidth = getTerminalWidth();
902
+ const descriptionIndent = " ".repeat(10);
903
+ const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
904
+ for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
905
+ return true;
906
+ }
907
+ function printOptionEntries(entries) {
908
+ const width = Math.max(...entries.map((line) => stringWidth(line[0])));
909
+ const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry(label, description, width));
910
+ entries.forEach(([label, description], index) => {
911
+ if (printHelpEntry(label, description, width, forceWrap) && index < entries.length - 1) printLn();
912
+ });
913
+ }
914
+ function printHelp(context, commands) {
770
915
  const app = context.app;
771
- const rootCommands = [
772
- ...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [],
773
- versionCommand,
774
- helpCommand
775
- ];
776
- if (commandPath.length) {
777
- const { command, path } = getCommand(commandPath, rootCommands);
778
- printCommandHelp(context, command, path);
779
- } else if (app.subCommands !== void 0) printHelp(context, rootCommands);
780
- else printCommandHelp(context, app, []);
781
- });
916
+ const chalk = context.chalk;
917
+ print(chalk.green(app.name));
918
+ const { _author: author, _version: version } = app;
919
+ if (version) print(` ver. ${chalk.yellow(version)}`);
920
+ if (author) print(` by ${chalk.cyan(author)}`);
921
+ printLn();
922
+ if (app.description) {
923
+ printLn();
924
+ printLn(app.description);
925
+ }
926
+ printLn();
927
+ printLn(chalk.yellow("Usage:"));
928
+ print(` ${chalk.cyan(getProcName(app))}`);
929
+ if (hasSubCommands(app)) print(` ${chalk.gray("[")}--GLOBAL-OPTIONS${chalk.gray("]")} ${chalk.gray("<")}COMMAND${chalk.gray(">")}`);
930
+ printLn("\n");
931
+ if (commands.length) printAvailableCommands(commands, "Commands:", chalk);
932
+ const globalOptions = getGlobalOptions(app);
933
+ if (globalOptions.length) {
934
+ printLn();
935
+ printLn(chalk.yellow("Global Options:"));
936
+ printOptionEntries(globalOptions.map((option) => formatOption(option, chalk)));
937
+ }
938
+ }
939
+ function printAvailableCommands(commands, title, chalk) {
940
+ if (!commands.length) return;
941
+ printLn(chalk.yellow(title));
942
+ const width = Math.max(...commands.map((c) => stringWidth(c.name))) + 2;
943
+ for (const cmd of commands) printHelpEntry(chalk.green(cmd.name), cmd.description, width);
944
+ }
782
945
 
783
946
  //#endregion
784
947
  //#region src/run.ts
@@ -818,29 +981,6 @@ function mergeCommandOptions(app, cmd) {
818
981
  options: [...getGlobalOptions(app), ...normalized.options ?? []]
819
982
  };
820
983
  }
821
- function isVersionFlag(arg) {
822
- return arg === "--version";
823
- }
824
- function getHelpValidationCommand(cmd) {
825
- return {
826
- ...cmd,
827
- options: cmd.options?.map((opt) => ({
828
- ...opt,
829
- required: false
830
- })),
831
- positonals: cmd.positonals?.map((arg) => ({
832
- ...arg,
833
- required: false
834
- }))
835
- };
836
- }
837
- function getRootCommands(app) {
838
- return [
839
- ...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [],
840
- versionCommand,
841
- helpCommand
842
- ];
843
- }
844
984
  function createExecutionContext(app, opts, path = []) {
845
985
  return new ExecutionContext(app, opts?.color ?? "auto", path);
846
986
  }
@@ -856,17 +996,58 @@ function createGlobalParseCommand(app) {
856
996
  execute() {}
857
997
  };
858
998
  }
999
+ function getRequestedColorMode(app, argv) {
1000
+ const colorOption = getColorOption(app);
1001
+ if (colorOption === void 0) return "auto";
1002
+ let mode = "auto";
1003
+ const shortNames = /* @__PURE__ */ new Set();
1004
+ const longNames = new Set([colorOption.name, ...Array.isArray(colorOption.alias) ? colorOption.alias : colorOption.alias !== void 0 ? [colorOption.alias] : []]);
1005
+ if (colorOption.name.length === 1) shortNames.add(colorOption.name);
1006
+ for (const alias of longNames) if (alias.length === 1) shortNames.add(alias);
1007
+ for (const token of argv) {
1008
+ if (token === `--no-${colorOption.name}`) {
1009
+ mode = "never";
1010
+ continue;
1011
+ }
1012
+ if (token.startsWith("--")) {
1013
+ const body = token.slice(2);
1014
+ const equalIndex = body.indexOf("=");
1015
+ const name = equalIndex === -1 ? body : body.slice(0, equalIndex);
1016
+ if (!longNames.has(name)) continue;
1017
+ if (equalIndex === -1) {
1018
+ mode = "always";
1019
+ continue;
1020
+ }
1021
+ const value = body.slice(equalIndex + 1);
1022
+ if (value === "always" || value === "never" || value === "auto") mode = value;
1023
+ else mode = "never";
1024
+ continue;
1025
+ }
1026
+ if (token.startsWith("-") && !token.startsWith("--")) {
1027
+ const clusterText = token.slice(1);
1028
+ if ([...clusterText.includes("=") ? clusterText.slice(0, clusterText.indexOf("=")) : clusterText].some((ch) => shortNames.has(ch))) mode = "always";
1029
+ }
1030
+ }
1031
+ return mode;
1032
+ }
859
1033
  function parseGlobalOptions(app, argv) {
1034
+ const colorMode = getRequestedColorMode(app, argv);
860
1035
  try {
861
- const [, opts] = parseArgs(getHelpValidationCommand(createGlobalParseCommand(app)), argv);
1036
+ const [, opts] = parseArgs(createGlobalParseCommand(app), extractGlobalOptionArgv(argv, getGlobalOptions(app)), createChalk(colorMode), {
1037
+ skipRequiredOptions: true,
1038
+ skipRequiredPositionals: true
1039
+ });
862
1040
  return { opts };
863
1041
  } catch (err) {
864
1042
  if (err instanceof UnknownOptionError) return {};
865
1043
  const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
866
- return { result: {
867
- code: getErrorExitCode(error),
868
- error
869
- } };
1044
+ return {
1045
+ opts: { color: colorMode },
1046
+ result: {
1047
+ code: getErrorExitCode(error),
1048
+ error
1049
+ }
1050
+ };
870
1051
  }
871
1052
  }
872
1053
  function getOptionTokenConsumption(argv, index, rawOptions) {
@@ -895,6 +1076,19 @@ function getOptionTokenConsumption(argv, index, rawOptions) {
895
1076
  }
896
1077
  return 1;
897
1078
  }
1079
+ function extractGlobalOptionArgv(argv, globalOptions) {
1080
+ const extracted = [];
1081
+ for (let index = 0; index < argv.length;) {
1082
+ const consumed = getOptionTokenConsumption(argv, index, globalOptions);
1083
+ if (consumed > 0) {
1084
+ extracted.push(...argv.slice(index, index + consumed));
1085
+ index += consumed;
1086
+ continue;
1087
+ }
1088
+ index += 1;
1089
+ }
1090
+ return extracted;
1091
+ }
898
1092
  function resolveCommandWithGlobalOptions(argv, subCommands, globalOptions) {
899
1093
  const path = [];
900
1094
  const commandIndexes = /* @__PURE__ */ new Set();
@@ -933,13 +1127,27 @@ function getFirstNonGlobalToken(argv, globalOptions) {
933
1127
  return argv[index];
934
1128
  }
935
1129
  }
936
- function unknownCommandResult(app, commandName) {
937
- const error = createError(`${getProcName(app)}: unknown command '${commandName}'`, ErrorCategory.InvalidArg);
1130
+ function unknownCommandResult(app, commandName, chalk) {
1131
+ const error = createError(`${getProcName(app)}: unknown command ${formatToken(commandName, chalk)}`, ErrorCategory.InvalidArg);
1132
+ return {
1133
+ code: getErrorExitCode(error),
1134
+ error
1135
+ };
1136
+ }
1137
+ function isOptionLikeToken(token) {
1138
+ return token.length >= 2 && token.startsWith("-");
1139
+ }
1140
+ function unknownOptionResult(app, optionToken, chalk) {
1141
+ const error = createError(`${getProcName(app)}: ${new UnknownOptionError(optionToken, formatToken(optionToken, chalk)).message}`, ErrorCategory.InvalidArg);
938
1142
  return {
939
1143
  code: getErrorExitCode(error),
940
1144
  error
941
1145
  };
942
1146
  }
1147
+ function unknownTokenResult(app, token, chalk) {
1148
+ if (isOptionLikeToken(token)) return unknownOptionResult(app, token, chalk);
1149
+ return unknownCommandResult(app, token, chalk);
1150
+ }
943
1151
  async function executeLeaf(app, cmd, rawArgs, path) {
944
1152
  try {
945
1153
  validateCommandConfig(mergeCommandOptions(app, cmd));
@@ -953,11 +1161,19 @@ async function executeLeaf(app, cmd, rawArgs, path) {
953
1161
  context: createExecutionContext(app, void 0, path)
954
1162
  };
955
1163
  }
956
- let args;
957
1164
  let opts;
1165
+ const globalParse = parseGlobalOptions(app, rawArgs);
1166
+ if (globalParse.result !== void 0) return {
1167
+ result: globalParse.result,
1168
+ context: createExecutionContext(app, globalParse.opts, path)
1169
+ };
1170
+ const parseChalk = createChalk(globalParse.opts?.color ?? "auto");
958
1171
  try {
959
1172
  const parseableCommand = mergeCommandOptions(app, cmd);
960
- const [, provisionalOpts] = parseArgs(getHelpValidationCommand(parseableCommand), rawArgs);
1173
+ const [, provisionalOpts] = parseArgs(parseableCommand, rawArgs, parseChalk, {
1174
+ skipRequiredOptions: true,
1175
+ skipRequiredPositionals: true
1176
+ });
961
1177
  const provisionalContext = createExecutionContext(app, provisionalOpts, path);
962
1178
  if (provisionalOpts.help) {
963
1179
  if (cmd === app && !hasSubCommands(app)) {
@@ -973,7 +1189,14 @@ async function executeLeaf(app, cmd, rawArgs, path) {
973
1189
  context: provisionalContext
974
1190
  };
975
1191
  }
976
- [args, opts] = parseArgs(parseableCommand, rawArgs);
1192
+ if (provisionalOpts.version) {
1193
+ printLn(app._version);
1194
+ return {
1195
+ result: { code: 0 },
1196
+ context: provisionalContext
1197
+ };
1198
+ }
1199
+ [, opts] = parseArgs(parseableCommand, rawArgs, parseChalk);
977
1200
  } catch (err) {
978
1201
  if (err instanceof UnknownOptionError) {
979
1202
  const error$1 = createError(`${getProcName(app)}: ${err.message}`, ErrorCategory.InvalidArg);
@@ -982,7 +1205,7 @@ async function executeLeaf(app, cmd, rawArgs, path) {
982
1205
  code: getErrorExitCode(error$1),
983
1206
  error: error$1
984
1207
  },
985
- context: createExecutionContext(app, void 0, path)
1208
+ context: createExecutionContext(app, globalParse.opts, path)
986
1209
  };
987
1210
  }
988
1211
  const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
@@ -991,7 +1214,7 @@ async function executeLeaf(app, cmd, rawArgs, path) {
991
1214
  code: getErrorExitCode(error),
992
1215
  error
993
1216
  },
994
- context: createExecutionContext(app, void 0, path)
1217
+ context: createExecutionContext(app, globalParse.opts, path)
995
1218
  };
996
1219
  }
997
1220
  const handler = getExecuteHandler(cmd);
@@ -1007,7 +1230,7 @@ async function executeLeaf(app, cmd, rawArgs, path) {
1007
1230
  }
1008
1231
  try {
1009
1232
  const context = createExecutionContext(app, opts, path);
1010
- const code = await Promise.resolve(handler(opts, args, context));
1233
+ const code = await Promise.resolve(handler(opts, context));
1011
1234
  if (code === void 0) return {
1012
1235
  result: { code: null },
1013
1236
  context
@@ -1040,18 +1263,18 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
1040
1263
  };
1041
1264
  }
1042
1265
  if (isExecutable(app) && !hasSubCommands(app)) {
1043
- if (isVersionFlag(argv[0])) {
1266
+ const globalParse = parseGlobalOptions(app, argv);
1267
+ if (globalParse.result !== void 0) return {
1268
+ result: globalParse.result,
1269
+ context: createExecutionContext(app, globalParse.opts)
1270
+ };
1271
+ if (globalParse.opts?.version) {
1044
1272
  printLn(app._version);
1045
1273
  return {
1046
1274
  result: { code: 0 },
1047
- context: rootContext
1275
+ context: createExecutionContext(app, globalParse.opts)
1048
1276
  };
1049
1277
  }
1050
- const globalParse = parseGlobalOptions(app, argv);
1051
- if (globalParse.result !== void 0) return {
1052
- result: globalParse.result,
1053
- context: rootContext
1054
- };
1055
1278
  if (globalParse.opts?.help) return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
1056
1279
  const builtin = findSubCommand(argv[0], rootCommands);
1057
1280
  if (builtin && isExecutable(builtin)) return executeLeaf(app, builtin, argv.slice(1), [builtin.name]);
@@ -1062,10 +1285,17 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
1062
1285
  const globalParse = parseGlobalOptions(app, argv);
1063
1286
  if (globalParse.result !== void 0) return {
1064
1287
  result: globalParse.result,
1065
- context: rootContext
1288
+ context: createExecutionContext(app, globalParse.opts)
1066
1289
  };
1067
1290
  const globalOpts = globalParse.opts ?? {};
1068
1291
  const context = createExecutionContext(app, globalOpts);
1292
+ if (globalOpts.version) {
1293
+ printLn(app._version);
1294
+ return {
1295
+ result: { code: 0 },
1296
+ context
1297
+ };
1298
+ }
1069
1299
  const firstNonGlobalToken = getFirstNonGlobalToken(argv, globalOptions);
1070
1300
  if (firstNonGlobalToken === void 0) {
1071
1301
  if (globalOpts.help || !hasSubCommands(app)) {
@@ -1083,7 +1313,7 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
1083
1313
  };
1084
1314
  }
1085
1315
  return {
1086
- result: unknownCommandResult(app, firstNonGlobalToken),
1316
+ result: unknownTokenResult(app, firstNonGlobalToken, context.chalk),
1087
1317
  context
1088
1318
  };
1089
1319
  }
@@ -1091,10 +1321,17 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
1091
1321
  const globalParse = parseGlobalOptions(app, resolved.remainingArgv);
1092
1322
  if (globalParse.result !== void 0) return {
1093
1323
  result: globalParse.result,
1094
- context: rootContext
1324
+ context: createExecutionContext(app, globalParse.opts, resolved.path)
1095
1325
  };
1096
1326
  const branchOpts = globalParse.opts ?? {};
1097
1327
  const context = createExecutionContext(app, branchOpts, resolved.path);
1328
+ if (branchOpts.version) {
1329
+ printLn(app._version);
1330
+ return {
1331
+ result: { code: 0 },
1332
+ context
1333
+ };
1334
+ }
1098
1335
  const firstNonGlobalToken = getFirstNonGlobalToken(resolved.remainingArgv, globalOptions);
1099
1336
  if (!resolved.remainingArgv.length || branchOpts.help && firstNonGlobalToken === void 0) {
1100
1337
  printCommandHelp(context, resolved.command, resolved.path);
@@ -1104,7 +1341,7 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
1104
1341
  };
1105
1342
  }
1106
1343
  return {
1107
- result: unknownCommandResult(app, firstNonGlobalToken ?? resolved.remainingArgv[0]),
1344
+ result: unknownTokenResult(app, firstNonGlobalToken ?? resolved.remainingArgv[0], context.chalk),
1108
1345
  context
1109
1346
  };
1110
1347
  }