@gunshi/bone 0.35.0 → 0.35.2

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../node_modules/.pnpm/args-tokens@0.27.0/node_modules/args-tokens/lib/parser-DT7Ztcch.d.ts
1
+ //#region ../../node_modules/.pnpm/args-tokens@0.28.0/node_modules/args-tokens/lib/parser-DT7Ztcch.d.ts
2
2
  //#region src/parser.d.ts
3
3
  /**
4
4
  * Entry point of argument parser.
@@ -55,7 +55,7 @@ interface ArgToken {
55
55
  * Parser Options.
56
56
  */
57
57
  //#endregion
58
- //#region ../../node_modules/.pnpm/args-tokens@0.27.0/node_modules/args-tokens/lib/resolver.d.ts
58
+ //#region ../../node_modules/.pnpm/args-tokens@0.28.0/node_modules/args-tokens/lib/resolver.d.ts
59
59
  //#region src/resolver.d.ts
60
60
  /**
61
61
  * An argument schema definition for command-line argument parsing.
@@ -476,7 +476,7 @@ interface ArgSchema {
476
476
  *
477
477
  * @param value - Raw string value from command line
478
478
  * @returns Parsed value of any type
479
- * @throws Error or subclass when value is invalid
479
+ * @throws {Error} Error or subclass when value is invalid
480
480
  *
481
481
  * @example
482
482
  * Custom parsing functions:
@@ -508,6 +508,13 @@ interface ArgSchema {
508
508
  */
509
509
  parse?: (value: string) => any;
510
510
  }
511
+ /**
512
+ * Machine-readable error codes for {@link ArgsValidationError}.
513
+ *
514
+ * Each code identifies a validation failure category and is also suitable as an
515
+ * i18n resource key for localized rendering.
516
+ */
517
+
511
518
  /**
512
519
  * An object that contains {@link ArgSchema | argument schema}.
513
520
  *
@@ -922,6 +929,13 @@ interface CommandEnvironment<G extends GunshiParamsConstraint = DefaultGunshiPar
922
929
  * @see {@linkcode CliOptions.usageSilent}
923
930
  */
924
931
  usageSilent: boolean;
932
+ /**
933
+ * Whether to treat undefined options as argument validation errors.
934
+ *
935
+ * @default false
936
+ * @see {@linkcode CliOptions.strict}
937
+ */
938
+ strict: boolean;
925
939
  /**
926
940
  * Sub commands.
927
941
  *
@@ -1009,6 +1023,16 @@ interface CliOptions<G extends GunshiParamsConstraint = DefaultGunshiParams> {
1009
1023
  * Whether to display the command usage.
1010
1024
  */
1011
1025
  usageSilent?: boolean;
1026
+ /**
1027
+ * Whether to treat undefined options as argument validation errors.
1028
+ *
1029
+ * When enabled, option tokens that are not declared by the resolved command
1030
+ * arguments or installed global options are reported as validation errors
1031
+ * before the command runner is executed.
1032
+ *
1033
+ * @default false
1034
+ */
1035
+ strict?: boolean;
1012
1036
  /**
1013
1037
  * Render function the command usage.
1014
1038
  */
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../node_modules/.pnpm/args-tokens@0.27.0/node_modules/args-tokens/lib/parser.js
1
+ //#region ../../node_modules/.pnpm/args-tokens@0.28.0/node_modules/args-tokens/lib/parser.js
2
2
  /**
3
3
  * forked from `nodejs/node` (`pkgjs/parseargs`)
4
4
  * repository url: https://github.com/nodejs/node (https://github.com/pkgjs/parseargs)
@@ -203,7 +203,7 @@ function hasOptionValue(value) {
203
203
  return !(value == null) && value.codePointAt(0) !== HYPHEN_CODE;
204
204
  }
205
205
  //#endregion
206
- //#region ../../node_modules/.pnpm/args-tokens@0.27.0/node_modules/args-tokens/lib/utils.js
206
+ //#region ../../node_modules/.pnpm/args-tokens@0.28.0/node_modules/args-tokens/lib/utils.js
207
207
  /**
208
208
  * Entry point of utils.
209
209
  *
@@ -224,8 +224,17 @@ function hasOptionValue(value) {
224
224
  function kebabnize(str) {
225
225
  return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
226
226
  }
227
+ /**
228
+ * Format allowed argument choices for validation error values and fallback messages.
229
+ *
230
+ * @param choices - Choice values to format
231
+ * @returns JSON-quoted choices joined with `, `.
232
+ */
233
+ function formatChoices(choices) {
234
+ return choices.map((value) => JSON.stringify(value)).join(", ");
235
+ }
227
236
  //#endregion
228
- //#region ../../node_modules/.pnpm/args-tokens@0.27.0/node_modules/args-tokens/lib/resolver.js
237
+ //#region ../../node_modules/.pnpm/args-tokens@0.28.0/node_modules/args-tokens/lib/resolver.js
229
238
  /**
230
239
  * Entry point of argument options resolver.
231
240
  *
@@ -235,6 +244,60 @@ function kebabnize(str) {
235
244
  * @author kazuya kawaguchi (a.k.a. kazupon)
236
245
  * @license MIT
237
246
  */
247
+ /**
248
+ * Machine-readable error codes for {@link ArgsValidationError}.
249
+ *
250
+ * Each code identifies a validation failure category and is also suitable as an
251
+ * i18n resource key for localized rendering.
252
+ */
253
+ const ArgsValidationErrorKeys = {
254
+ requiredOption: "err:arg:required-option",
255
+ requiredPositional: "err:arg:required-positional",
256
+ invalidType: "err:arg:invalid-type",
257
+ invalidChoice: "err:arg:invalid-choice",
258
+ customParse: "err:arg:custom-parse",
259
+ unknownOption: "err:arg:unknown-option"
260
+ };
261
+ /**
262
+ * An error that contains structured metadata for argument validation failures.
263
+ *
264
+ * The `message` remains the English fallback message. Renderers can use `code`
265
+ * and `values` to localize the error, falling back to `message` when localization
266
+ * is unavailable.
267
+ */
268
+ var ArgsValidationError = class extends Error {
269
+ /**
270
+ * Machine-readable error code for this validation failure.
271
+ *
272
+ * This code can also be used as an i18n resource key.
273
+ */
274
+ code;
275
+ /**
276
+ * Interpolation values for `code`.
277
+ */
278
+ values;
279
+ /**
280
+ * Create an `ArgsValidationError` instance.
281
+ *
282
+ * @param message - fallback error message
283
+ * @param options - structured validation metadata
284
+ */
285
+ constructor(message, options = {}) {
286
+ super(message, { cause: options.cause });
287
+ this.name = "ArgsValidationError";
288
+ this.code = options.code;
289
+ this.values = options.values ?? {};
290
+ }
291
+ };
292
+ /**
293
+ * Check whether the given value is an {@link ArgsValidationError}.
294
+ *
295
+ * @param error - value to check
296
+ * @returns `true` when the value is an `ArgsValidationError`
297
+ */
298
+ function isArgsValidationError(error) {
299
+ return error instanceof ArgsValidationError;
300
+ }
238
301
  const SKIP_POSITIONAL_DEFAULT = -1;
239
302
  /**
240
303
  * Resolve command line arguments.
@@ -398,11 +461,9 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
398
461
  const parsed = [];
399
462
  for (let i = positionalsCount; i < endPositionals; i++) {
400
463
  const p = positionalTokens[i];
401
- try {
402
- parsed.push(schema.parse(p.value));
403
- } catch (error) {
404
- errors.push(error);
405
- }
464
+ const [parsedValue, error] = parseSchemaValue(p.value, rawArg, arg, schema);
465
+ if (error) errors.push(error);
466
+ else parsed.push(parsedValue);
406
467
  }
407
468
  values[rawArg] = parsed;
408
469
  } else {
@@ -412,22 +473,22 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
412
473
  }
413
474
  positionalsCount = endPositionals;
414
475
  explicit[rawArg] = true;
415
- } else if (schema.required) errors.push(createRequireError(arg, schema));
416
- } else if (schema.required) errors.push(createRequireError(arg, schema));
476
+ } else if (schema.required) errors.push(createRequireError(rawArg, arg, schema));
477
+ } else if (schema.required) errors.push(createRequireError(rawArg, arg, schema));
417
478
  } else {
418
479
  const positional = positionalTokens[positionalsCount];
419
480
  if (shouldRequireMissingSinglePositional(schema)) {
420
481
  if (positional != null) {
421
- resolveSinglePositionalValue(values, errors, rawArg, schema, positional);
482
+ resolveSinglePositionalValue(values, errors, rawArg, arg, schema, positional);
422
483
  explicit[rawArg] = true;
423
484
  positionalsCount++;
424
- } else errors.push(createRequireError(arg, schema));
485
+ } else errors.push(createRequireError(rawArg, arg, schema));
425
486
  continue;
426
487
  }
427
488
  if (positional != null) {
428
489
  const requiredPositionals = getRequiredPositionalsAfter(rawArg);
429
490
  if (Math.max(positionalTokens.length - positionalsCount, 0) > requiredPositionals) {
430
- resolveSinglePositionalValue(values, errors, rawArg, schema, positional);
491
+ resolveSinglePositionalValue(values, errors, rawArg, arg, schema, positional);
431
492
  explicit[rawArg] = true;
432
493
  positionalsCount++;
433
494
  continue;
@@ -441,14 +502,14 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
441
502
  if (!optionTokens.find((token) => {
442
503
  return schema.short && token.name === schema.short || token.rawName && hasLongOptionPrefix(token.rawName) && token.name === arg;
443
504
  })) {
444
- errors.push(createRequireError(arg, schema));
505
+ errors.push(createRequireError(rawArg, arg, schema));
445
506
  continue;
446
507
  }
447
508
  }
448
509
  for (let i = 0; i < optionTokens.length; i++) {
449
510
  const token = optionTokens[i];
450
511
  if (checkTokenName(arg, schema, token) && token.rawName != void 0 && hasLongOptionPrefix(token.rawName) || schema.short === token.name && token.rawName != void 0 && isShortOption(token.rawName)) {
451
- const invalid = validateRequire(token, arg, schema);
512
+ const invalid = validateRequire(token, rawArg, arg, schema);
452
513
  if (invalid) {
453
514
  errors.push(invalid);
454
515
  continue;
@@ -456,7 +517,7 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
456
517
  explicit[rawArg] = true;
457
518
  const actualInputName = isShortOption(token.rawName) ? `-${token.name}` : `--${arg}`;
458
519
  actualInputNames.set(rawArg, actualInputName);
459
- const [parsedValue, error] = parse(token, arg, schema);
520
+ const [parsedValue, error] = parse(token, rawArg, arg, schema);
460
521
  if (error) errors.push(error);
461
522
  else if (schema.multiple) {
462
523
  values[rawArg] ||= [];
@@ -476,39 +537,49 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
476
537
  explicit
477
538
  };
478
539
  }
479
- function parse(token, option, schema) {
480
- if (typeof schema.parse === "function") try {
540
+ function parse(token, rawArg, option, schema) {
541
+ if (typeof schema.parse === "function") {
481
542
  if (schema.type === "boolean") {
482
543
  const boolValue = !(schema.negatable && token.name.startsWith("no-"));
483
- return [schema.parse(String(boolValue)), void 0];
544
+ return parseSchemaValue(String(boolValue), rawArg, option, schema);
484
545
  }
485
- return [schema.parse(token.value ?? String(schema.default ?? "")), void 0];
486
- } catch (error) {
487
- return [void 0, error];
546
+ return parseSchemaValue(token.value ?? String(schema.default ?? ""), rawArg, option, schema);
488
547
  }
489
548
  switch (schema.type) {
490
- case "string": return typeof token.value === "string" ? [token.value || schema.default, void 0] : [void 0, createTypeError(option, schema)];
549
+ case "string": return typeof token.value === "string" ? [token.value || schema.default, void 0] : [void 0, createTypeError(rawArg, option, schema, token.value)];
491
550
  case "boolean": return [!(schema.negatable && token.name.startsWith("no-")), void 0];
492
551
  case "number":
493
- if (!isNumeric(token.value)) return [void 0, createTypeError(option, schema)];
552
+ if (!isNumeric(token.value)) return [void 0, createTypeError(rawArg, option, schema, token.value)];
494
553
  return token.value ? [+token.value, void 0] : [+(schema.default || ""), void 0];
495
554
  case "enum":
496
- if (schema.choices && !schema.choices.includes(token.value)) return [void 0, new ArgResolveError(`Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be chosen from '${schema.type}' [${schema.choices.map((c) => JSON.stringify(c)).join(", ")}] values`, option, "type", schema)];
555
+ if (schema.choices && !schema.choices.includes(token.value)) return [void 0, createChoiceError(rawArg, option, schema, token.value)];
497
556
  return [token.value || schema.default, void 0];
498
557
  case "custom": throw new TypeError(`argument '${option}' should have a 'parse' function`);
499
558
  default: throw new Error(`Unsupported argument type '${schema.type}' for option '${option}'`);
500
559
  }
501
560
  }
502
- function createRequireError(option, schema) {
503
- return new ArgResolveError(schema.type === "positional" ? `Positional argument '${option}' is required` : `Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}is required`, option, "required", schema);
504
- }
505
- function resolveSinglePositionalValue(values, errors, rawArg, schema, positional) {
506
- if (typeof schema.parse === "function") try {
507
- values[rawArg] = schema.parse(positional.value);
561
+ function parseSchemaValue(value, rawArg, option, schema) {
562
+ try {
563
+ return [schema.parse(value), void 0];
508
564
  } catch (error) {
509
- errors.push(error);
565
+ return [void 0, createCustomParseError(error, rawArg, option, schema, value)];
510
566
  }
511
- else values[rawArg] = positional.value;
567
+ }
568
+ function createRequireError(rawArg, option, schema) {
569
+ return new ArgResolveError(schema.type === "positional" ? `Positional argument '${option}' is required` : `Optional argument ${createOptionDisplayName(option, schema)} is required`, option, "required", schema, {
570
+ code: schema.type === "positional" ? ArgsValidationErrorKeys.requiredPositional : ArgsValidationErrorKeys.requiredOption,
571
+ values: schema.type === "positional" ? { name: rawArg } : {
572
+ displayName: createOptionDisplayName(option, schema),
573
+ name: rawArg
574
+ }
575
+ });
576
+ }
577
+ function resolveSinglePositionalValue(values, errors, rawArg, option, schema, positional) {
578
+ if (typeof schema.parse === "function") {
579
+ const [parsedValue, error] = parseSchemaValue(positional.value, rawArg, option, schema);
580
+ if (error) errors.push(error);
581
+ else values[rawArg] = parsedValue;
582
+ } else values[rawArg] = positional.value;
512
583
  }
513
584
  function hasDefault(schema) {
514
585
  return schema.default != null;
@@ -538,7 +609,7 @@ function createRequiredPositionalsAfter(argEntries) {
538
609
  * An error that occurs when resolving arguments.
539
610
  * This error is thrown when the argument is not valid.
540
611
  */
541
- var ArgResolveError = class extends Error {
612
+ var ArgResolveError = class extends ArgsValidationError {
542
613
  name;
543
614
  schema;
544
615
  type;
@@ -547,24 +618,78 @@ var ArgResolveError = class extends Error {
547
618
  *
548
619
  * @param message - the error message
549
620
  * @param name - the name of the argument
550
- * @param type - the type of the error, either 'type' or 'required'
621
+ * @param type - the type of the error: 'type', 'required', or 'conflict'
551
622
  * @param schema - the argument schema that caused the error
623
+ * @param options - structured validation metadata
552
624
  */
553
- constructor(message, name, type, schema) {
554
- super(message);
625
+ constructor(message, name, type, schema, options = {}) {
626
+ super(message, options);
555
627
  this.name = name;
556
628
  this.type = type;
557
629
  this.schema = schema;
558
630
  }
559
631
  };
560
- function validateRequire(token, option, schema) {
561
- if (schema.required && schema.type !== "boolean" && !token.value) return createRequireError(option, schema);
632
+ function validateRequire(token, rawArg, option, schema) {
633
+ if (schema.required && schema.type !== "boolean" && !token.value) return createRequireError(rawArg, option, schema);
562
634
  }
563
635
  function isNumeric(str) {
564
636
  return str.trim() !== "" && !isNaN(str);
565
637
  }
566
- function createTypeError(option, schema) {
567
- return new ArgResolveError(`Optional argument '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be '${schema.type}'`, option, "type", schema);
638
+ function createTypeError(rawArg, option, schema, actual) {
639
+ return new ArgResolveError(`Optional argument ${createOptionDisplayName(option, schema)} should be '${schema.type}'`, option, "type", schema, {
640
+ code: ArgsValidationErrorKeys.invalidType,
641
+ values: {
642
+ displayName: createOptionDisplayName(option, schema),
643
+ name: rawArg,
644
+ expected: schema.type,
645
+ ...actual != null ? { actual } : {}
646
+ }
647
+ });
648
+ }
649
+ function createChoiceError(rawArg, option, schema, actual) {
650
+ const choices = schema.choices ?? [];
651
+ return new ArgResolveError(`Optional argument ${createOptionDisplayName(option, schema)} should be chosen from '${schema.type}' [${formatChoices(choices)}] values`, option, "type", schema, {
652
+ code: ArgsValidationErrorKeys.invalidChoice,
653
+ values: {
654
+ displayName: createOptionDisplayName(option, schema),
655
+ name: rawArg,
656
+ expected: schema.type,
657
+ choices: formatChoices(choices),
658
+ choiceValues: [...choices],
659
+ ...actual != null ? { actual } : {}
660
+ }
661
+ });
662
+ }
663
+ function createCustomParseError(error, rawArg, option, schema, value) {
664
+ if (isArgsValidationError(error)) {
665
+ augmentValidationError(error, rawArg, option, schema, value);
666
+ return error;
667
+ }
668
+ const reason = getErrorReason(error);
669
+ return new ArgsValidationError(reason, {
670
+ code: ArgsValidationErrorKeys.customParse,
671
+ values: {
672
+ displayName: createArgumentDisplayName(option, schema),
673
+ name: rawArg,
674
+ reason
675
+ },
676
+ cause: error
677
+ });
678
+ }
679
+ function augmentValidationError(error, rawArg, option, schema, value) {
680
+ const values = error.values;
681
+ values.name ??= rawArg;
682
+ values.displayName ??= createArgumentDisplayName(option, schema);
683
+ if ((error.code === ArgsValidationErrorKeys.invalidType || error.code === ArgsValidationErrorKeys.invalidChoice) && values.actual == null) values.actual = value;
684
+ }
685
+ function getErrorReason(error) {
686
+ return error instanceof Error ? error.message : String(error);
687
+ }
688
+ function createArgumentDisplayName(option, schema) {
689
+ return schema.type === "positional" ? `'${option}'` : createOptionDisplayName(option, schema);
690
+ }
691
+ function createOptionDisplayName(option, schema) {
692
+ return `'--${option}'${schema.short ? ` or '-${schema.short}'` : ""}`;
568
693
  }
569
694
  function checkConflicts(args, explicit, toKebab, actualInputNames) {
570
695
  for (const rawArg in args) {
@@ -583,7 +708,7 @@ function checkConflicts(args, explicit, toKebab, actualInputNames) {
583
708
  return [];
584
709
  }
585
710
  //#endregion
586
- //#region ../../node_modules/.pnpm/args-tokens@0.27.0/node_modules/args-tokens/lib/index.js
711
+ //#region ../../node_modules/.pnpm/args-tokens@0.28.0/node_modules/args-tokens/lib/index.js
587
712
  /**
588
713
  * @author kazuya kawaguchi (a.k.a. kazupon)
589
714
  * @license MIT
@@ -614,6 +739,7 @@ const CLI_OPTIONS_DEFAULT = {
614
739
  middleMargin: 10,
615
740
  usageOptionType: false,
616
741
  usageOptionValue: true,
742
+ strict: false,
617
743
  renderHeader: void 0,
618
744
  renderUsage: void 0,
619
745
  renderValidationErrors: void 0,
@@ -885,6 +1011,59 @@ function createDecorators() {
885
1011
  });
886
1012
  }
887
1013
  //#endregion
1014
+ //#region ../gunshi/src/error.ts
1015
+ /**
1016
+ * @author kazuya kawaguchi (a.k.a. kazupon)
1017
+ * @license MIT
1018
+ */
1019
+ /**
1020
+ * Command not found error resource keys.
1021
+ */
1022
+ const CommandNotFoundErrorKeys = { notFound: "err:cmd:not-found" };
1023
+ /**
1024
+ * Error raised when a command cannot be resolved.
1025
+ */
1026
+ var CommandNotFoundError = class extends Error {
1027
+ code;
1028
+ values;
1029
+ commandName;
1030
+ candidates;
1031
+ commandPath;
1032
+ /**
1033
+ * Create a command-not-found error.
1034
+ *
1035
+ * @param message - Fallback error message
1036
+ * @param options - Command-not-found metadata
1037
+ */
1038
+ constructor(message, options) {
1039
+ super(message, { cause: options.cause });
1040
+ this.name = "CommandNotFoundError";
1041
+ this.code = options.code;
1042
+ this.values = options.values || {};
1043
+ this.commandName = options.commandName;
1044
+ this.candidates = options.candidates || [];
1045
+ this.commandPath = options.commandPath || [];
1046
+ }
1047
+ };
1048
+ /**
1049
+ * Check whether an error is a {@link CommandNotFoundError}.
1050
+ *
1051
+ * @param error - An unknown error
1052
+ * @returns `true` if the error is a {@link CommandNotFoundError}
1053
+ */
1054
+ function isCommandNotFoundError(error) {
1055
+ return error instanceof CommandNotFoundError;
1056
+ }
1057
+ /**
1058
+ * Check whether validation errors should be handled before version, help, or command execution.
1059
+ *
1060
+ * @param error - An aggregate validation error
1061
+ * @returns `true` if the validation error must be handled with priority
1062
+ */
1063
+ function hasPriorityValidationError(error) {
1064
+ return error?.errors.some((error) => isCommandNotFoundError(error) || isArgsValidationError(error) && error.code === ArgsValidationErrorKeys.unknownOption) ?? false;
1065
+ }
1066
+ //#endregion
888
1067
  //#region ../gunshi/src/plugin/context.ts
889
1068
  /**
890
1069
  * @author kazuya kawaguchi (a.k.a. kazupon)
@@ -1008,17 +1187,35 @@ async function cliCore(argv, entry, options, plugins) {
1008
1187
  const tokens = parseArgs(argv);
1009
1188
  const resolved = resolveCommandTree(tokens, entry, cliOptions);
1010
1189
  const { commandName: name, command, callMode, commandPath, depth, levelSubCommands } = resolved;
1011
- if (!command) throw new Error(`Command not found: ${name || ""}`);
1012
- if (levelSubCommands) cliOptions.subCommands = levelSubCommands;
1013
- const resolvedCommand = isLazyCommand(command) ? await resolveLazyCommand(command, name, true) : command;
1190
+ let targetCommand = command;
1191
+ let targetCommandName = name;
1192
+ let targetCallMode = callMode;
1193
+ let targetCommandPath = commandPath;
1194
+ let targetDepth = depth;
1195
+ let targetOmitted = resolved.omitted;
1196
+ let targetLevelSubCommands = levelSubCommands;
1197
+ const additionalValidationErrors = [];
1198
+ if (!targetCommand) {
1199
+ if (!resolved.parentCommand || !resolved.unresolvedCommandName) throw new Error(`Command not found: ${name || ""}`);
1200
+ targetCommand = resolved.parentCommand;
1201
+ targetCommandName = resolved.parentCommandName;
1202
+ targetCommandPath = resolved.parentCommandPath || [];
1203
+ targetDepth = targetCommandPath.length;
1204
+ targetCallMode = targetDepth > 0 ? "subCommand" : "entry";
1205
+ targetOmitted = false;
1206
+ targetLevelSubCommands = resolved.parentSubCommands;
1207
+ additionalValidationErrors.push(createCommandNotFoundError(resolved));
1208
+ }
1209
+ if (targetLevelSubCommands) cliOptions.subCommands = targetLevelSubCommands;
1210
+ const resolvedCommand = isLazyCommand(targetCommand) ? await resolveLazyCommand(targetCommand, targetCommandName, true) : targetCommand;
1014
1211
  const args = resolveArguments(pluginContext, getCommandArgs(resolvedCommand));
1015
- const skipPositional = depth > 0 ? depth - 1 : -1;
1212
+ const skipPositional = targetDepth > 0 ? targetDepth - 1 : -1;
1016
1213
  const { explicit, values, positionals, rest, error } = resolveArgs(args, tokens, {
1017
1214
  shortGrouping: true,
1018
1215
  toKebab: resolvedCommand.toKebab,
1019
1216
  skipPositional
1020
1217
  });
1021
- const omitted = resolved.omitted;
1218
+ const validationError = mergeValidationErrors(error, [...additionalValidationErrors, ...cliOptions.strict ? createUnknownOptionErrors(findUnknownOptions(args, tokens, { toKebab: resolvedCommand.toKebab })) : []]);
1022
1219
  return await executeCommand(resolvedCommand, await createCommandContext({
1023
1220
  args,
1024
1221
  explicit,
@@ -1027,12 +1224,12 @@ async function cliCore(argv, entry, options, plugins) {
1027
1224
  rest,
1028
1225
  argv,
1029
1226
  tokens,
1030
- omitted,
1031
- callMode,
1032
- commandPath,
1227
+ omitted: targetOmitted,
1228
+ callMode: targetCallMode,
1229
+ commandPath: targetCommandPath,
1033
1230
  command: resolvedCommand,
1034
1231
  extensions: getPluginExtensions(resolvedPlugins),
1035
- validationError: error,
1232
+ validationError,
1036
1233
  cliOptions
1037
1234
  }), decorators.commandDecorators);
1038
1235
  }
@@ -1060,6 +1257,66 @@ function getCommandArgs(cmd) {
1060
1257
  function resolveArguments(pluginContext, args) {
1061
1258
  return Object.assign(create(), Object.fromEntries(pluginContext.globalOptions), args);
1062
1259
  }
1260
+ const NEGATABLE_OPTION_PREFIX = "no-";
1261
+ function findUnknownOptions(args, tokens, options) {
1262
+ const knownLongOptions = /* @__PURE__ */ new Set();
1263
+ const knownShortOptions = /* @__PURE__ */ new Set();
1264
+ const knownNegatableOptions = /* @__PURE__ */ new Set();
1265
+ const knownLongOptionCandidates = /* @__PURE__ */ new Set();
1266
+ const longOptionCandidates = [];
1267
+ function addLongOptionCandidate(name) {
1268
+ const candidate = `--${name}`;
1269
+ if (knownLongOptionCandidates.has(candidate)) return;
1270
+ knownLongOptionCandidates.add(candidate);
1271
+ longOptionCandidates.push(candidate);
1272
+ }
1273
+ for (const [name, schema] of Object.entries(args)) {
1274
+ if (schema.type === "positional") continue;
1275
+ const optionName = resolveOptionName(name, schema, options);
1276
+ knownLongOptions.add(optionName);
1277
+ addLongOptionCandidate(optionName);
1278
+ if (schema.short) knownShortOptions.add(schema.short);
1279
+ if (schema.type === "boolean" && schema.negatable) {
1280
+ const negatableOptionName = `${NEGATABLE_OPTION_PREFIX}${optionName}`;
1281
+ knownNegatableOptions.add(negatableOptionName);
1282
+ addLongOptionCandidate(negatableOptionName);
1283
+ }
1284
+ }
1285
+ const unknownOptions = [];
1286
+ for (const token of tokens) {
1287
+ if (token.kind === "option-terminator") break;
1288
+ if (token.kind !== "option" || !token.name || !token.rawName) continue;
1289
+ const isLongOption = token.rawName.startsWith("--");
1290
+ if (isLongOption) {
1291
+ if (knownLongOptions.has(token.name) || knownNegatableOptions.has(token.name)) continue;
1292
+ } else if (knownShortOptions.has(token.name)) continue;
1293
+ unknownOptions.push({
1294
+ rawName: token.rawName,
1295
+ name: token.name,
1296
+ candidates: isLongOption ? longOptionCandidates : []
1297
+ });
1298
+ }
1299
+ return unknownOptions;
1300
+ }
1301
+ function resolveOptionName(name, schema, options) {
1302
+ return options.toKebab || schema.toKebab ? kebabnize(name) : name;
1303
+ }
1304
+ function createUnknownOptionErrors(unknownOptions) {
1305
+ return unknownOptions.map(({ rawName, name, candidates }) => {
1306
+ return new ArgsValidationError(`Unknown option: ${rawName}`, {
1307
+ code: ArgsValidationErrorKeys.unknownOption,
1308
+ values: {
1309
+ rawName,
1310
+ name,
1311
+ candidates
1312
+ }
1313
+ });
1314
+ });
1315
+ }
1316
+ function mergeValidationErrors(error, additionalErrors) {
1317
+ if (additionalErrors.length === 0) return error;
1318
+ return new AggregateError(error ? [...error.errors, ...additionalErrors] : additionalErrors, error?.message || additionalErrors[0]?.message);
1319
+ }
1063
1320
  const isObject = (val) => val !== null && typeof val === "object";
1064
1321
  function createInitialSubCommands(options, entryCmd) {
1065
1322
  const hasSubCommands = options.subCommands ? options.subCommands instanceof Map ? options.subCommands.size > 0 : isObject(options.subCommands) && Object.keys(options.subCommands).length > 0 : false;
@@ -1146,16 +1403,34 @@ function resolveCommandTree(tokens, entry, options) {
1146
1403
  if (cmd == null) {
1147
1404
  if (depth === 0) {
1148
1405
  if (options.fallbackToEntry) return resolveAsEntry();
1406
+ const parent = resolveAsEntry();
1149
1407
  return {
1150
1408
  commandName: token,
1151
1409
  callMode: "unexpected",
1152
1410
  commandPath: [],
1153
1411
  depth: 0,
1154
1412
  omitted: false,
1155
- levelSubCommands: void 0
1413
+ levelSubCommands: void 0,
1414
+ unresolvedCommandName: token,
1415
+ parentCommand: parent.command,
1416
+ parentCommandName: parent.commandName,
1417
+ parentCommandPath: [],
1418
+ parentSubCommands: options.subCommands
1156
1419
  };
1157
1420
  }
1158
- break;
1421
+ return {
1422
+ commandName: token,
1423
+ callMode: "unexpected",
1424
+ commandPath: [...commandPath],
1425
+ depth,
1426
+ omitted: false,
1427
+ levelSubCommands: void 0,
1428
+ unresolvedCommandName: token,
1429
+ parentCommand: resolvedCommand,
1430
+ parentCommandName: resolvedName,
1431
+ parentCommandPath: [...commandPath],
1432
+ parentSubCommands: currentSubCommands
1433
+ };
1159
1434
  }
1160
1435
  let resolved = cmd;
1161
1436
  if (typeof cmd === "function" && cmd.commandName == null) resolved = Object.assign((...args) => cmd(...args), cmd, { commandName: token });
@@ -1189,6 +1464,16 @@ function resolveCommandTree(tokens, entry, options) {
1189
1464
  levelSubCommands
1190
1465
  };
1191
1466
  }
1467
+ function createCommandNotFoundError(resolved) {
1468
+ const commandName = resolved.unresolvedCommandName || resolved.commandName || "";
1469
+ return new CommandNotFoundError(`Command not found: ${commandName}`, {
1470
+ code: CommandNotFoundErrorKeys.notFound,
1471
+ values: { commandName },
1472
+ commandName,
1473
+ candidates: [...resolved.parentSubCommands?.keys() || []],
1474
+ commandPath: resolved.parentCommandPath || []
1475
+ });
1476
+ }
1192
1477
  function resolveEntryName(entry) {
1193
1478
  return isLazyCommand(entry) ? entry.commandName || "(anonymous)" : entry.name || "(anonymous)";
1194
1479
  }
@@ -1202,7 +1487,11 @@ function getPluginExtensions(plugins) {
1202
1487
  return extensions;
1203
1488
  }
1204
1489
  async function executeCommand(cmd, ctx, decorators) {
1205
- const baseRunner = cmd.run || NOOP;
1490
+ const commandRunner = cmd.run || NOOP;
1491
+ const baseRunner = (ctx) => {
1492
+ if (hasPriorityValidationError(ctx.validationError)) throw ctx.validationError;
1493
+ return commandRunner(ctx);
1494
+ };
1206
1495
  const decoratedRunner = decorators.reduceRight((runner, decorator) => decorator(runner), baseRunner);
1207
1496
  try {
1208
1497
  if (ctx.env.onBeforeCommand) await ctx.env.onBeforeCommand(ctx);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gunshi/bone",
3
3
  "description": "gunshi minimum",
4
- "version": "0.35.0",
4
+ "version": "0.35.2",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -56,10 +56,10 @@
56
56
  "jsr-exports-lint": "^0.4.2",
57
57
  "publint": "^0.3.20",
58
58
  "tsdown": "0.21.0",
59
- "@gunshi/definition": "0.35.0",
60
- "@gunshi/plugin-global": "0.35.0",
61
- "gunshi": "0.35.0",
62
- "@gunshi/plugin-renderer": "0.35.0"
59
+ "@gunshi/definition": "0.35.2",
60
+ "@gunshi/plugin-global": "0.35.2",
61
+ "@gunshi/plugin-renderer": "0.35.2",
62
+ "gunshi": "0.35.2"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsdown",