@gunshi/shared 1.0.0-rc.5 → 1.0.0-rc.6

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.
Files changed (3) hide show
  1. package/lib/index.d.ts +133 -10
  2. package/lib/index.js +468 -2
  3. package/package.json +4 -3
package/lib/index.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ //#region ../gunshi/src/constants.d.ts
2
+ /**
3
+ * The key of the mark on the `json` argument of `jsonArgs`. A string key, not a symbol: `gunshi` and the other
4
+ * packages bundle their own copies of the types, and each copy of a `unique symbol` is a different type.
5
+ *
6
+ * @internal
7
+ */
8
+ declare const JSON_ARG = "~gunshi.json-arg";
9
+ //#endregion
1
10
  //#region ../../node_modules/.pnpm/args-tokens@1.0.2/node_modules/args-tokens/lib/parser-DvPxBZG9.d.ts
2
11
  /**
3
12
  * Entry point of argument parser.
@@ -845,7 +854,11 @@ interface CommandEnvironment<G extends GunshiParamsConstraint = DefaultGunshiPar
845
854
  * (they are still returned as strings) and to silence {@linkcode CommandContext.log}.
846
855
  *
847
856
  * Output that must still appear — machine-readable results for an agent, warnings for the
848
- * CLI author — should use `console.log` / `console.warn`, not `ctx.log`.
857
+ * CLI author — should use `console.log` / `console.warn`, not `ctx.log`. A command that spreads
858
+ * `jsonArgs` and is given `--json` prints its result, and its errors, as JSON even with it.
859
+ *
860
+ * The validation errors, and a `CommandError` that the command throws, are not printed either, so
861
+ * `cli()` rejects with them, for the caller to report them.
849
862
  *
850
863
  * @default false
851
864
  * @see {@linkcode CliOptions.usageSilent}
@@ -905,7 +918,8 @@ interface CommandEnvironment<G extends GunshiParamsConstraint = DefaultGunshiPar
905
918
  */
906
919
  onBeforeCommand: ((ctx: Readonly<CommandContext<G>>) => Awaitable<void>) | undefined;
907
920
  /**
908
- * Hook that runs after successful command execution
921
+ * Hook that runs after successful command execution. It receives the result of the command only
922
+ * when it is a string.
909
923
  *
910
924
  * @see {@linkcode CliOptions.onAfterCommand}
911
925
  * @since v0.27.0
@@ -1010,9 +1024,11 @@ interface CommandContext<G extends GunshiParamsConstraint = DefaultGunshiParams>
1010
1024
  */
1011
1025
  toKebab?: boolean;
1012
1026
  /**
1013
- * Output a message.
1027
+ * Output a message for people.
1014
1028
  *
1015
- * If {@linkcode CommandEnvironment.usageSilent} is true, the message is not output.
1029
+ * If {@linkcode CommandEnvironment.usageSilent} is true, the message is not output. Nor is it when a
1030
+ * command that spreads `jsonArgs` is asked for JSON with `--json`, so that stdout has the JSON only:
1031
+ * the hooks and the plugins get the same function.
1016
1032
  *
1017
1033
  * @param message - an output message, see {@linkcode console.log}
1018
1034
  * @param optionalParams - an optional parameters, see {@linkcode console.log}
@@ -1233,9 +1249,40 @@ type CommandExamplesFetcher<G extends GunshiParamsConstraint = DefaultGunshiPara
1233
1249
  * @typeParam G - A type extending {@linkcode GunshiParams} to specify the shape of command context.
1234
1250
  *
1235
1251
  * @param ctx - A {@link CommandContext | command context}
1236
- * @returns void or string (for CLI output)
1252
+ * @returns The result of a command that spreads `jsonArgs`, an object keyed by a noun, which is printed
1253
+ * as JSON with `--json`. For any other command, a string, which `cli()` returns, or nothing. See
1254
+ * {@linkcode CommandRunnerResult}.
1255
+ */
1256
+ type CommandRunner<G extends GunshiParamsConstraint = DefaultGunshiParams> = (ctx: Readonly<CommandContext<G>>) => CommandRunnerResult<G>;
1257
+ /**
1258
+ * The result of a command that supports `--json`: an object keyed by a noun, such as
1259
+ * `{ project }` or `{ projects }`, so that fields can be added later. The global options plugin prints
1260
+ * it with `JSON.stringify`, so convert what JSON cannot hold, such as a `Map` or a `BigInt`, yourself.
1261
+ */
1262
+ type JsonResult = Record<string, unknown>;
1263
+ /**
1264
+ * Whether the arguments have the `json` of `jsonArgs`.
1265
+ *
1266
+ * @typeParam A - The arguments of a command
1237
1267
  */
1238
- type CommandRunner<G extends GunshiParamsConstraint = DefaultGunshiParams> = (ctx: Readonly<CommandContext<G>>) => Awaitable<string | void>;
1268
+ type HasJsonArgs<A> = [A] extends [{
1269
+ readonly json: {
1270
+ readonly [JSON_ARG]: true;
1271
+ };
1272
+ }] ? true : false;
1273
+ /**
1274
+ * What the runner of a command returns: its result for a command that spreads `jsonArgs`, otherwise a string or
1275
+ * nothing.
1276
+ *
1277
+ * @typeParam G - A type extending {@linkcode GunshiParams}
1278
+ */
1279
+ type CommandRunnerResult<G extends GunshiParamsConstraint = DefaultGunshiParams> = IsAnyArgs<ExtractArgs<G>> extends true ? Awaitable<string | void | JsonResult> : HasJsonArgs<ExtractArgs<G>> extends true ? Awaitable<JsonResult> : Awaitable<string | void>;
1280
+ /**
1281
+ * Whether the arguments are `any`, as in `CommandRunner<any>`. Then a runner may return anything it could before.
1282
+ *
1283
+ * @internal
1284
+ */
1285
+ type IsAnyArgs<A> = 0 extends 1 & A ? true : false;
1239
1286
  //#endregion
1240
1287
  //#region ../../node_modules/.pnpm/args-tokens@1.0.2/node_modules/args-tokens/lib/utils.d.ts
1241
1288
  /**
@@ -1258,6 +1305,36 @@ type CommandRunner<G extends GunshiParamsConstraint = DefaultGunshiParams> = (ct
1258
1305
  declare function kebabnize(str: string): string;
1259
1306
  //#endregion
1260
1307
  //#region ../gunshi/src/utils.d.ts
1308
+ /**
1309
+ * How `--json` asks a command for JSON.
1310
+ *
1311
+ * - `'declared'`: the command spreads `jsonArgs` into its arguments, and `--json` asks it for JSON
1312
+ * - `'undeclared'`: the command has no `json` argument, and `--json` is given to it
1313
+ */
1314
+ type JsonRequest = 'declared' | 'undeclared';
1315
+ /**
1316
+ * Check whether an argument schema is the `json` argument of `jsonArgs`, by its mark.
1317
+ *
1318
+ * @param schema - An argument schema
1319
+ * @returns `true` if the schema has the mark of `jsonArgs`
1320
+ */
1321
+ declare function isJsonArg(schema: unknown): boolean;
1322
+ /**
1323
+ * Resolve whether `--json` asks a command for JSON.
1324
+ *
1325
+ * A command that spreads `jsonArgs` is asked for JSON when the resolved value of its `json` is `true`:
1326
+ * with `--json`, or with a short name that the command gives it, but not with `--json=false`, nor with
1327
+ * `--json` after `--`. The raw tokens are read only when they are given, and only when there is no
1328
+ * value to read: when the command has no `json` argument, and when the core could not resolve the
1329
+ * command and left the values empty. A `json` argument without the mark belongs to the command, and
1330
+ * `--json` is never taken for a request then.
1331
+ *
1332
+ * @param args - The arguments of the command, the global options included
1333
+ * @param values - The resolved values of the arguments
1334
+ * @param tokens - The raw tokens of the arguments, to find a `--json` that no argument declares
1335
+ * @returns How the command is asked for JSON, or `undefined` when it is not
1336
+ */
1337
+ declare function resolveJsonRequest(args: Readonly<Args>, values: Readonly<Record<string, unknown>>, tokens?: readonly ArgToken[]): JsonRequest | undefined;
1261
1338
  /**
1262
1339
  * Check if the given command is a {@link LazyCommand}.
1263
1340
  *
@@ -1339,11 +1416,13 @@ type CommonArgType = {
1339
1416
  };
1340
1417
  };
1341
1418
  declare const COMMON_ARGS: CommonArgType;
1342
- declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "ARGUMENTS", "OPTIONS", "EXAMPLES", "FORMORE", "NEGATABLE", "DEFAULT", "CHOICES"];
1419
+ declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "ARGUMENTS", "OPTIONS", "EXAMPLES", "FORMORE", "NEGATABLE", "DEFAULT", "CHOICES", "CAUSEDBY"];
1343
1420
  declare const ARG_ERROR_RESOURCE_KEYS: readonly ["err:arg:required-option", "err:arg:required-positional", "err:arg:invalid-type", "err:arg:invalid-choice", "err:arg:custom-parse", "err:arg:unknown-option", "err:arg:unexpected-value", "err:arg:missing-value", "err:arg:conflict", "err:arg:invalid-default"];
1344
1421
  declare const ARG_ERROR_HINT_RESOURCE_KEYS: readonly ["err:arg:missing-value:hint"];
1345
1422
  declare const COMMAND_ERROR_RESOURCE_KEYS: readonly ["err:cmd:not-found", "err:cmd:ambiguous", "err:cmd:inconsistent-options", "err:cmd:lazy-schema-mismatch"];
1346
1423
  declare const SUGGESTION_ERROR_RESOURCE_KEYS: readonly ["err:suggestion:did-you-mean"];
1424
+ declare const JSON_RESOURCE_KEYS: readonly ["json", "json:invalid-arguments", "json:next:help", "json:next:help-placeholder"];
1425
+ declare const JSON_ERROR_RESOURCE_KEYS: readonly ["err:json:not-supported", "err:json:invalid-result", "err:json:unserializable-result"];
1347
1426
  //#endregion
1348
1427
  //#region src/types.d.ts
1349
1428
  type RemoveIndexSignature<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; };
@@ -1387,14 +1466,23 @@ type CommandErrorResourceKeys = (typeof COMMAND_ERROR_RESOURCE_KEYS)[number];
1387
1466
  * Suggestion error resource keys.
1388
1467
  */
1389
1468
  type SuggestionErrorResourceKeys = (typeof SUGGESTION_ERROR_RESOURCE_KEYS)[number];
1469
+ /**
1470
+ * Resource keys of the errors of the JSON output.
1471
+ */
1472
+ type JsonErrorResourceKeys = (typeof JSON_ERROR_RESOURCE_KEYS)[number];
1473
+ /**
1474
+ * Resource keys of the description of the `json` argument of `jsonArgs`, and of the texts that only
1475
+ * the JSON errors print.
1476
+ */
1477
+ type JsonResourceKeys = (typeof JSON_RESOURCE_KEYS)[number];
1390
1478
  /**
1391
1479
  * Error resource keys.
1392
1480
  */
1393
- type ErrorResourceKeys = ArgErrorResourceKeys | ArgErrorHintResourceKeys | CommandErrorResourceKeys | SuggestionErrorResourceKeys;
1481
+ type ErrorResourceKeys = ArgErrorResourceKeys | ArgErrorHintResourceKeys | CommandErrorResourceKeys | SuggestionErrorResourceKeys | JsonErrorResourceKeys;
1394
1482
  /**
1395
1483
  * Built-in resource keys.
1396
1484
  */
1397
- type BuiltinResourceKeys = ErrorResourceKeys | CommandBuiltinArgsKeys | CommandBuiltinResourceKeys;
1485
+ type BuiltinResourceKeys = ErrorResourceKeys | CommandBuiltinArgsKeys | CommandBuiltinResourceKeys | JsonResourceKeys;
1398
1486
  /**
1399
1487
  * Built-in resource.
1400
1488
  */
@@ -1478,8 +1566,13 @@ declare const _exports: {
1478
1566
  NEGATABLE: string;
1479
1567
  DEFAULT: string;
1480
1568
  CHOICES: string;
1569
+ CAUSEDBY: string;
1481
1570
  help: string;
1482
1571
  version: string;
1572
+ json: string;
1573
+ "json:invalid-arguments": string;
1574
+ "json:next:help": string;
1575
+ "json:next:help-placeholder": string;
1483
1576
  "err:arg:required-option": string;
1484
1577
  "err:arg:required-positional": string;
1485
1578
  "err:arg:invalid-type": string;
@@ -1495,6 +1588,9 @@ declare const _exports: {
1495
1588
  "err:cmd:ambiguous": string;
1496
1589
  "err:cmd:inconsistent-options": string;
1497
1590
  "err:cmd:lazy-schema-mismatch": string;
1591
+ "err:json:not-supported": string;
1592
+ "err:json:invalid-result": string;
1593
+ "err:json:unserializable-result": string;
1498
1594
  "err:suggestion:did-you-mean": string;
1499
1595
  };
1500
1596
  //#endregion
@@ -1601,4 +1697,31 @@ declare function resolveOptionNames(args: Args, toKebab?: boolean): Set<string>;
1601
1697
  */
1602
1698
  declare function resolveCommandArgs<A extends Args = Args>(globalOptions?: ReadonlyMap<string, ArgSchema>, args?: A): A;
1603
1699
  //#endregion
1604
- export { ARG_ERROR_HINT_RESOURCE_KEYS, ARG_ERROR_RESOURCE_KEYS, ARG_NEGATABLE_PREFIX, ARG_PREFIX, ARG_PREFIX_AND_KEY_SEPARATOR, type ArgErrorHintResourceKeys, type ArgErrorResourceKeys, BUILD_IN_PREFIX_AND_KEY_SEPARATOR, BUILT_IN_KEY_SEPARATOR, BUILT_IN_PREFIX, type BuiltinResource, type BuiltinResourceKeys, COMMAND_BUILTIN_RESOURCE_KEYS, COMMAND_ERROR_RESOURCE_KEYS, COMMON_ARGS, type CommandArgKeys, type CommandBuiltinArgsKeys, type CommandBuiltinKeys, type CommandBuiltinResourceKeys, type CommandErrorResourceKeys, type DeepWriteable, _exports as DefaultResource, ERROR_PREFIX, ERROR_PREFIX_AND_KEY_SEPARATOR, type ErrorResourceKeys, type GenerateNamespacedKey, type KeyOfArgs, Localization, PLUGIN_PREFIX, type RemovedIndex, type ResolveTranslationKeys, SUGGESTION_ERROR_RESOURCE_KEYS, type SuggestionErrorResourceKeys, type Translation, create, deepFreeze, formatResource, getCommandSubCommands, isLazyCommand, kebabnize, localizable, log, makeShortLongOptionPair, namespacedId, resolveArgKey, resolveBuiltInKey, resolveCommandArgs, resolveDisplayName, resolveExamples, resolveKey, resolveLazyCommand, resolveOptionNames };
1700
+ //#region src/validation.d.ts
1701
+ /**
1702
+ * Localize the message of a validation error, as gunshi shows it to the user: with the i18n plugin when
1703
+ * it is installed, and otherwise the message of the error.
1704
+ *
1705
+ * NOTE(kazupon): the renderer plugin renders the validation errors for people, and the global options
1706
+ * plugin prints them as JSON with `--json`. Both take the messages from here, so that a message is the
1707
+ * same in both.
1708
+ *
1709
+ * @param ctx - A {@link CommandContext | command context}
1710
+ * @param error - A validation error: an {@link ArgsValidationError}, a command-not-found error, or a
1711
+ * command-resolution error
1712
+ * @returns The localized message
1713
+ */
1714
+ declare function localizeValidationError<G extends GunshiParams = DefaultGunshiParams>(ctx: CommandContext<G>, error: Error): Promise<string>;
1715
+ /**
1716
+ * Localize a text of the built-in resources: with the i18n plugin when it is installed and knows the key,
1717
+ * and otherwise the English one.
1718
+ *
1719
+ * @param ctx - A {@link CommandContext | command context}
1720
+ * @param key - The key of the text: `_:<key>` for a text that is not an error, and the key itself for an
1721
+ * error, such as `err:json:not-supported`
1722
+ * @param values - The values of the placeholders of the text
1723
+ * @returns The localized text, or the key when no resource has it
1724
+ */
1725
+ declare function localizeBuiltinResource<G extends GunshiParams = DefaultGunshiParams>(ctx: CommandContext<G>, key: string, values?: Record<string, unknown>): Promise<string>;
1726
+ //#endregion
1727
+ export { ARG_ERROR_HINT_RESOURCE_KEYS, ARG_ERROR_RESOURCE_KEYS, ARG_NEGATABLE_PREFIX, ARG_PREFIX, ARG_PREFIX_AND_KEY_SEPARATOR, type ArgErrorHintResourceKeys, type ArgErrorResourceKeys, BUILD_IN_PREFIX_AND_KEY_SEPARATOR, BUILT_IN_KEY_SEPARATOR, BUILT_IN_PREFIX, type BuiltinResource, type BuiltinResourceKeys, COMMAND_BUILTIN_RESOURCE_KEYS, COMMAND_ERROR_RESOURCE_KEYS, COMMON_ARGS, type CommandArgKeys, type CommandBuiltinArgsKeys, type CommandBuiltinKeys, type CommandBuiltinResourceKeys, type CommandErrorResourceKeys, type DeepWriteable, _exports as DefaultResource, ERROR_PREFIX, ERROR_PREFIX_AND_KEY_SEPARATOR, type ErrorResourceKeys, type GenerateNamespacedKey, JSON_ERROR_RESOURCE_KEYS, JSON_RESOURCE_KEYS, type JsonErrorResourceKeys, JsonRequest, type JsonResourceKeys, type KeyOfArgs, Localization, PLUGIN_PREFIX, type RemovedIndex, type ResolveTranslationKeys, SUGGESTION_ERROR_RESOURCE_KEYS, type SuggestionErrorResourceKeys, type Translation, create, deepFreeze, formatResource, getCommandSubCommands, isJsonArg, isLazyCommand, kebabnize, localizable, localizeBuiltinResource, localizeValidationError, log, makeShortLongOptionPair, namespacedId, resolveArgKey, resolveBuiltInKey, resolveCommandArgs, resolveDisplayName, resolveExamples, resolveJsonRequest, resolveKey, resolveLazyCommand, resolveOptionNames };
package/lib/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @author kazuya kawaguchi (a.k.a. kazupon)
3
+ * @license MIT
4
+ */
5
+ //#endregion
1
6
  //#region ../../node_modules/.pnpm/args-tokens@1.0.2/node_modules/args-tokens/lib/utils.js
2
7
  /**
3
8
  * Entry point of utils.
@@ -26,6 +31,51 @@ function kebabnize(str) {
26
31
  * @license MIT
27
32
  */
28
33
  /**
34
+ * Check whether an argument schema is the `json` argument of `jsonArgs`, by its mark.
35
+ *
36
+ * @param schema - An argument schema
37
+ * @returns `true` if the schema has the mark of `jsonArgs`
38
+ */
39
+ function isJsonArg(schema) {
40
+ return typeof schema === "object" && schema !== null && schema["~gunshi.json-arg"] === true;
41
+ }
42
+ /**
43
+ * Resolve whether `--json` asks a command for JSON.
44
+ *
45
+ * A command that spreads `jsonArgs` is asked for JSON when the resolved value of its `json` is `true`:
46
+ * with `--json`, or with a short name that the command gives it, but not with `--json=false`, nor with
47
+ * `--json` after `--`. The raw tokens are read only when they are given, and only when there is no
48
+ * value to read: when the command has no `json` argument, and when the core could not resolve the
49
+ * command and left the values empty. A `json` argument without the mark belongs to the command, and
50
+ * `--json` is never taken for a request then.
51
+ *
52
+ * @param args - The arguments of the command, the global options included
53
+ * @param values - The resolved values of the arguments
54
+ * @param tokens - The raw tokens of the arguments, to find a `--json` that no argument declares
55
+ * @returns How the command is asked for JSON, or `undefined` when it is not
56
+ */
57
+ function resolveJsonRequest(args, values, tokens) {
58
+ const schema = Object.hasOwn(args, "json") ? args.json : void 0;
59
+ if (schema === void 0) return tokens !== void 0 && hasJsonOption(tokens) ? "undeclared" : void 0;
60
+ if (!isJsonArg(schema)) return;
61
+ if (values.json !== void 0) return values.json === true ? "declared" : void 0;
62
+ return tokens !== void 0 && hasJsonOption(tokens) ? "declared" : void 0;
63
+ }
64
+ /**
65
+ * Check whether the raw tokens have a `--json` that asks for JSON: `--json` or `--json=true`, before
66
+ * `--`. Without a declaration, a short name of `--json` is not known.
67
+ *
68
+ * @param tokens - The raw tokens of the arguments
69
+ * @returns `true` if a `--json` asks for JSON
70
+ */
71
+ function hasJsonOption(tokens) {
72
+ for (const token of tokens) {
73
+ if (token.kind === "option-terminator") return false;
74
+ if (token.kind === "option" && token.rawName === "--json" && (token.value === void 0 || token.value === "true")) return true;
75
+ }
76
+ return false;
77
+ }
78
+ /**
29
79
  * Check if the given command is a {@link LazyCommand}.
30
80
  *
31
81
  * @param cmd - A command to check
@@ -194,7 +244,8 @@ const COMMAND_BUILTIN_RESOURCE_KEYS = [
194
244
  "FORMORE",
195
245
  "NEGATABLE",
196
246
  "DEFAULT",
197
- "CHOICES"
247
+ "CHOICES",
248
+ "CAUSEDBY"
198
249
  ];
199
250
  const ARG_ERROR_RESOURCE_KEYS = [
200
251
  "err:arg:required-option",
@@ -216,6 +267,17 @@ const COMMAND_ERROR_RESOURCE_KEYS = [
216
267
  "err:cmd:lazy-schema-mismatch"
217
268
  ];
218
269
  const SUGGESTION_ERROR_RESOURCE_KEYS = ["err:suggestion:did-you-mean"];
270
+ const JSON_RESOURCE_KEYS = [
271
+ "json",
272
+ "json:invalid-arguments",
273
+ "json:next:help",
274
+ "json:next:help-placeholder"
275
+ ];
276
+ const JSON_ERROR_RESOURCE_KEYS = [
277
+ "err:json:not-supported",
278
+ "err:json:invalid-result",
279
+ "err:json:unserializable-result"
280
+ ];
219
281
  //#endregion
220
282
  //#region ../resources/locales/en-US.json
221
283
  var en_US_default = {
@@ -230,8 +292,13 @@ var en_US_default = {
230
292
  NEGATABLE: "Negatable of",
231
293
  DEFAULT: "default",
232
294
  CHOICES: "choices",
295
+ CAUSEDBY: "Caused by",
233
296
  help: "Display this help message",
234
297
  version: "Display this version",
298
+ json: "Output the result as JSON",
299
+ "json:invalid-arguments": "{$count} arguments are invalid",
300
+ "json:next:help": "See the arguments of the command",
301
+ "json:next:help-placeholder": "Replace <COMMAND> with how you ran this CLI, to see the arguments of the command",
235
302
  "err:arg:required-option": "Option {$displayName} is required",
236
303
  "err:arg:required-positional": "Positional argument {$displayName} is required",
237
304
  "err:arg:invalid-type": "Invalid value for {$displayName}: expected {$expected}",
@@ -247,6 +314,9 @@ var en_US_default = {
247
314
  "err:cmd:ambiguous": "The command is ambiguous. Move options after the command name: {$candidatePaths}",
248
315
  "err:cmd:inconsistent-options": "The options do not identify a consistent command. Move options after the command name.",
249
316
  "err:cmd:lazy-schema-mismatch": "The lazy command definition does not match its loaded options. Declare leading options in the lazy definition or move options after the command name: {$commandPath}",
317
+ "err:json:not-supported": "This command does not support --json",
318
+ "err:json:invalid-result": "The command finished, but did not return an object to output as JSON. A plugin that decorates the command may have dropped its result",
319
+ "err:json:unserializable-result": "The command finished, but its result cannot be converted to JSON: {$message}",
250
320
  "err:suggestion:did-you-mean": "Did you mean {$name}?"
251
321
  };
252
322
  //#endregion
@@ -458,6 +528,402 @@ function formatResource(resource, values = Object.create(null)) {
458
528
  return values[name] == null ? "" : String(values[name]);
459
529
  });
460
530
  }
531
+ /**
532
+ * forked from `nodejs/node` (`pkgjs/parseargs`)
533
+ * repository url: https://github.com/nodejs/node (https://github.com/pkgjs/parseargs)
534
+ * code url: https://github.com/nodejs/node/blob/main/lib/internal/util/parse_args/parse_args.js
535
+ *
536
+ * @author kazuya kawaguchi (a.k.a. kazupon)
537
+ * @license MIT
538
+ */
539
+ "-".codePointAt(0);
540
+ "=".codePointAt(0);
541
+ //#endregion
542
+ //#region ../../node_modules/.pnpm/args-tokens@1.0.2/node_modules/args-tokens/lib/resolver-Cx5Pw6-S.js
543
+ /**
544
+ * @author kazuya kawaguchi (a.k.a. kazupon)
545
+ * @license MIT
546
+ */
547
+ /**
548
+ * Entry point of argument options resolver.
549
+ *
550
+ * @module
551
+ */
552
+ /**
553
+ * @author kazuya kawaguchi (a.k.a. kazupon)
554
+ * @license MIT
555
+ */
556
+ /**
557
+ * Machine-readable error codes for {@link ArgsValidationError}.
558
+ *
559
+ * Each code identifies a validation failure category and is also suitable as an
560
+ * i18n resource key for localized rendering.
561
+ *
562
+ * {@link resolveArgs} and `parse()` do not report `unknownOption`. An option that is not in the
563
+ * schema takes the argument after it as its value, unless that argument is an option, and the value
564
+ * is dropped: with no `foo` in the schema, `--foo bar` gives no positional argument.
565
+ */
566
+ const ArgsValidationErrorKeys = {
567
+ requiredOption: "err:arg:required-option",
568
+ requiredPositional: "err:arg:required-positional",
569
+ invalidType: "err:arg:invalid-type",
570
+ invalidChoice: "err:arg:invalid-choice",
571
+ customParse: "err:arg:custom-parse",
572
+ unknownOption: "err:arg:unknown-option",
573
+ unexpectedValue: "err:arg:unexpected-value",
574
+ missingValue: "err:arg:missing-value",
575
+ conflict: "err:arg:conflict",
576
+ invalidDefault: "err:arg:invalid-default"
577
+ };
578
+ /**
579
+ * Brand that marks {@link ArgsValidationError} instances.
580
+ *
581
+ * The brand is looked up in the global symbol registry with `Symbol.for`, so it stays
582
+ * identical across bundled copies of this module and across realms. It lets
583
+ * {@link isArgsValidationError} recognize errors created by another copy of `args-tokens`,
584
+ * where `instanceof` cannot match.
585
+ */
586
+ const ARGS_VALIDATION_ERROR_BRAND = Symbol.for("args-tokens.ArgsValidationError");
587
+ /**
588
+ * An error that contains structured metadata for argument validation failures.
589
+ *
590
+ * The `message` remains the English fallback message. Renderers can use `code`
591
+ * and `values` to localize the error, falling back to `message` when localization
592
+ * is unavailable.
593
+ *
594
+ * Each instance carries a non-enumerable brand keyed by
595
+ * `Symbol.for('args-tokens.ArgsValidationError')`, which {@link isArgsValidationError}
596
+ * uses to recognize instances created by another bundled copy of `args-tokens`.
597
+ */
598
+ var ArgsValidationError = class extends Error {
599
+ /**
600
+ * Machine-readable error code for this validation failure.
601
+ *
602
+ * This code can also be used as an i18n resource key.
603
+ */
604
+ code;
605
+ /**
606
+ * Interpolation values for `code`.
607
+ */
608
+ values;
609
+ /**
610
+ * Create an `ArgsValidationError` instance.
611
+ *
612
+ * @param message - fallback error message
613
+ * @param options - structured validation metadata
614
+ */
615
+ constructor(message, options = {}) {
616
+ super(message, { cause: options.cause });
617
+ this.name = "ArgsValidationError";
618
+ this.code = options.code;
619
+ this.values = options.values ?? {};
620
+ Object.defineProperty(this, ARGS_VALIDATION_ERROR_BRAND, {
621
+ value: true,
622
+ enumerable: false,
623
+ writable: false,
624
+ configurable: false
625
+ });
626
+ }
627
+ };
628
+ /**
629
+ * Check whether the given value is an {@link ArgsValidationError}.
630
+ *
631
+ * This guard also recognizes errors created by another bundled copy of `args-tokens`
632
+ * (0.29.0 or later), where `instanceof` does not match. Such an error must have an own brand
633
+ * keyed by `Symbol.for('args-tokens.ArgsValidationError')` set to `true`, and a `values` object.
634
+ * The guard does not rely on `error.name`, so subclasses such as {@link ArgResolveError} that
635
+ * override `name` are still recognized.
636
+ *
637
+ * The guard narrows to `ArgsValidationError` only. Across bundled copies,
638
+ * `instanceof ArgResolveError` still does not match.
639
+ *
640
+ * @param error - value to check
641
+ * @returns `true` when the value is an `ArgsValidationError`
642
+ */
643
+ function isArgsValidationError$1(error) {
644
+ if (error instanceof ArgsValidationError) return true;
645
+ if (typeof error !== "object" || error === null) return false;
646
+ if (!Object.hasOwn(error, ARGS_VALIDATION_ERROR_BRAND) || error[ARGS_VALIDATION_ERROR_BRAND] !== true) return false;
647
+ const values = error.values;
648
+ return typeof values === "object" && values !== null;
649
+ }
650
+ //#endregion
651
+ //#region ../../node_modules/.pnpm/args-tokens@1.0.2/node_modules/args-tokens/lib/index.js
652
+ /**
653
+ * @author kazuya kawaguchi (a.k.a. kazupon)
654
+ * @license MIT
655
+ */
656
+ /**
657
+ * @author kazuya kawaguchi (a.k.a. kazupon)
658
+ * @license MIT
659
+ */
660
+ //#endregion
661
+ //#region ../gunshi/src/error.ts
662
+ /**
663
+ * @author kazuya kawaguchi (a.k.a. kazupon)
664
+ * @license MIT
665
+ */
666
+ /**
667
+ * Brand that marks {@link CommandNotFoundError} instances.
668
+ *
669
+ * The brand is looked up in the global symbol registry with `Symbol.for`, so it stays
670
+ * identical across bundled copies of gunshi (`gunshi`, `@gunshi/plugin`, `@gunshi/bone`)
671
+ * and across realms. It lets {@link isCommandNotFoundError} recognize errors created by
672
+ * another copy, where `instanceof` cannot match.
673
+ */
674
+ const COMMAND_NOT_FOUND_ERROR_BRAND = Symbol.for("gunshi.CommandNotFoundError");
675
+ const COMMAND_RESOLUTION_ERROR_BRAND = Symbol.for("gunshi.CommandResolutionError");
676
+ /**
677
+ * Error raised when a command cannot be resolved.
678
+ *
679
+ * Each instance carries a non-enumerable brand keyed by
680
+ * `Symbol.for('gunshi.CommandNotFoundError')`, so that {@link isCommandNotFoundError}
681
+ * recognizes it even when it was created by another bundled copy of gunshi.
682
+ */
683
+ var CommandNotFoundError = class extends Error {
684
+ code;
685
+ values;
686
+ commandName;
687
+ candidates;
688
+ commandPath;
689
+ /**
690
+ * Create a command-not-found error.
691
+ *
692
+ * @param message - Fallback error message
693
+ * @param options - Command-not-found metadata
694
+ */
695
+ constructor(message, options) {
696
+ super(message, { cause: options.cause });
697
+ this.name = "CommandNotFoundError";
698
+ this.code = options.code;
699
+ this.values = options.values || {};
700
+ this.commandName = options.commandName;
701
+ this.candidates = options.candidates || [];
702
+ this.commandPath = options.commandPath || [];
703
+ Object.defineProperty(this, COMMAND_NOT_FOUND_ERROR_BRAND, {
704
+ value: true,
705
+ enumerable: false,
706
+ writable: false,
707
+ configurable: false
708
+ });
709
+ }
710
+ };
711
+ /**
712
+ * Error raised when command routing cannot produce one consistent command.
713
+ *
714
+ * The own registry brand is the cross-bundle contract. Unlike the legacy command-not-found
715
+ * guard, the new guard deliberately has no name-based fallback because this error type is new and
716
+ * must not classify arbitrary errors from older versions.
717
+ */
718
+ var CommandResolutionError = class extends Error {
719
+ code;
720
+ values;
721
+ commandPath;
722
+ candidatePaths;
723
+ /**
724
+ * Create a command resolution error with structured routing metadata.
725
+ *
726
+ * @param message - Human-readable fallback message
727
+ * @param options - Error code and command-routing metadata
728
+ */
729
+ constructor(message, options = {}) {
730
+ super(message, { cause: options.cause });
731
+ this.name = "CommandResolutionError";
732
+ this.code = options.code;
733
+ this.values = options.values || {};
734
+ this.commandPath = options.commandPath || [];
735
+ this.candidatePaths = options.candidatePaths || [];
736
+ Object.defineProperty(this, COMMAND_RESOLUTION_ERROR_BRAND, {
737
+ value: true,
738
+ enumerable: false,
739
+ writable: false,
740
+ configurable: false
741
+ });
742
+ }
743
+ };
744
+ /**
745
+ * Check whether an error is a {@link CommandNotFoundError}.
746
+ *
747
+ * `instanceof` alone is not enough: `@gunshi/plugin` is bundled with its own copy of this
748
+ * class (`noExternal: ['gunshi/plugin']`), so an error thrown by `gunshi` is never an instance
749
+ * of the class a plugin imports. Errors from another copy are recognized through the
750
+ * `Symbol.for('gunshi.CommandNotFoundError')` brand, which does not depend on `error.name`.
751
+ *
752
+ * @param error - An unknown error
753
+ * @returns `true` if the error is a {@link CommandNotFoundError}
754
+ */
755
+ function isCommandNotFoundError(error) {
756
+ if (error instanceof CommandNotFoundError) return true;
757
+ if (!isRecord(error) || !hasCommandNotFoundErrorShape(error)) return false;
758
+ if (Object.hasOwn(error, COMMAND_NOT_FOUND_ERROR_BRAND) && error[COMMAND_NOT_FOUND_ERROR_BRAND] === true) return true;
759
+ return error instanceof Error && error.name === "CommandNotFoundError";
760
+ }
761
+ /**
762
+ * Check whether an error is a {@link CommandResolutionError}.
763
+ *
764
+ * @param error - An unknown error
765
+ * @returns `true` when the error has the command-resolution brand and shape
766
+ */
767
+ function isCommandResolutionError(error) {
768
+ if (error instanceof CommandResolutionError) return true;
769
+ if (!isRecord(error) || !hasCommandResolutionErrorShape(error)) return false;
770
+ return Object.hasOwn(error, COMMAND_RESOLUTION_ERROR_BRAND) && error[COMMAND_RESOLUTION_ERROR_BRAND] === true;
771
+ }
772
+ /**
773
+ * Check whether an error is an {@link ArgsValidationError}.
774
+ *
775
+ * Prefer this over the `args-tokens` guard of the same name. Both recognize errors from another
776
+ * bundled copy through the `Symbol.for('args-tokens.ArgsValidationError')` brand that
777
+ * `args-tokens` 0.29.0 or later sets, including subclasses such as `ArgResolveError` that
778
+ * override `name` with the argument name. This guard additionally:
779
+ *
780
+ * - recognizes direct `ArgsValidationError` instances from copies bundling `args-tokens`
781
+ * older than 0.29.0, which do not set the brand
782
+ * - checks that `code` and `values` of an error from another copy have the expected types
783
+ *
784
+ * The guard narrows only to {@link ArgsValidationError}: across copies,
785
+ * `instanceof ArgResolveError` still fails, so do not rely on `type` or `schema` for such errors.
786
+ *
787
+ * @param error - An unknown error
788
+ * @returns `true` if the error is an {@link ArgsValidationError}
789
+ */
790
+ function isArgsValidationError(error) {
791
+ if (error instanceof ArgsValidationError) return true;
792
+ if (!isRecord(error) || !hasArgsValidationErrorShape(error)) return false;
793
+ if (isArgsValidationError$1(error)) return true;
794
+ return error instanceof Error && error.name === "ArgsValidationError";
795
+ }
796
+ function isRecord(value) {
797
+ return typeof value === "object" && value !== null;
798
+ }
799
+ function isStringArray(value) {
800
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
801
+ }
802
+ /**
803
+ * Check whether `code` is an own or inherited property that is a string or `undefined`.
804
+ *
805
+ * Constructors always assign `code`, even when the option is omitted.
806
+ *
807
+ * @param error - An error from another copy
808
+ * @returns `true` if `code` has the expected type
809
+ */
810
+ function hasOptionalStringCode(error) {
811
+ return "code" in error && (error.code === void 0 || typeof error.code === "string");
812
+ }
813
+ function hasCommandNotFoundErrorShape(error) {
814
+ return hasOptionalStringCode(error) && isRecord(error.values) && typeof error.commandName === "string" && isStringArray(error.candidates) && isStringArray(error.commandPath);
815
+ }
816
+ function hasCommandResolutionErrorShape(error) {
817
+ return hasOptionalStringCode(error) && isRecord(error.values) && isStringArray(error.commandPath) && Array.isArray(error.candidatePaths) && error.candidatePaths.every((path) => isStringArray(path));
818
+ }
819
+ function hasArgsValidationErrorShape(error) {
820
+ return hasOptionalStringCode(error) && isRecord(error.values);
821
+ }
822
+ //#endregion
823
+ //#region ../gunshi/src/plugin.ts
824
+ /**
825
+ * @author kazuya kawaguchi (a.k.a. kazupon)
826
+ * @license MIT
827
+ */
828
+ //#endregion
829
+ //#region ../plugin/src/index.ts
830
+ /**
831
+ * @author kazuya kawaguchi (a.k.a. kazupon)
832
+ * @license MIT
833
+ */
834
+ //#endregion
835
+ //#region src/validation.ts
836
+ /**
837
+ * @author kazuya kawaguchi (a.k.a. kazupon)
838
+ * @license MIT
839
+ */
840
+ const i18nPluginId = namespacedId("i18n");
841
+ const missingValueHintKey = "err:arg:missing-value:hint";
842
+ /**
843
+ * Localize the message of a validation error, as gunshi shows it to the user: with the i18n plugin when
844
+ * it is installed, and otherwise the message of the error.
845
+ *
846
+ * NOTE(kazupon): the renderer plugin renders the validation errors for people, and the global options
847
+ * plugin prints them as JSON with `--json`. Both take the messages from here, so that a message is the
848
+ * same in both.
849
+ *
850
+ * @param ctx - A {@link CommandContext | command context}
851
+ * @param error - A validation error: an {@link ArgsValidationError}, a command-not-found error, or a
852
+ * command-resolution error
853
+ * @returns The localized message
854
+ */
855
+ async function localizeValidationError(ctx, error) {
856
+ if (isCommandNotFoundError(error) && error.code) {
857
+ const message = await localize(ctx, error.code, error.values);
858
+ if (message && message !== error.code) return message;
859
+ }
860
+ if (isCommandResolutionError(error) && error.code) {
861
+ const message = await localize(ctx, error.code, error.values);
862
+ if (message && message !== error.code) return message;
863
+ }
864
+ if (isArgsValidationError(error) && error.code) {
865
+ const values = await resolveValidationValues(ctx, error);
866
+ const message = await localize(ctx, error.code, values);
867
+ if (message && message !== error.code) {
868
+ const hint = await localizeHint(ctx, error, values);
869
+ return hint ? `${message} ${hint}` : message;
870
+ }
871
+ }
872
+ return error.message;
873
+ }
874
+ /**
875
+ * Localize a text of the built-in resources: with the i18n plugin when it is installed and knows the key,
876
+ * and otherwise the English one.
877
+ *
878
+ * @param ctx - A {@link CommandContext | command context}
879
+ * @param key - The key of the text: `_:<key>` for a text that is not an error, and the key itself for an
880
+ * error, such as `err:json:not-supported`
881
+ * @param values - The values of the placeholders of the text
882
+ * @returns The localized text, or the key when no resource has it
883
+ */
884
+ async function localizeBuiltinResource(ctx, key, values) {
885
+ const message = await localize(ctx, key, values);
886
+ if (message && message !== key) return message;
887
+ return formatResource(en_US_default[key.startsWith(BUILD_IN_PREFIX_AND_KEY_SEPARATOR) ? key.slice(BUILD_IN_PREFIX_AND_KEY_SEPARATOR.length) : key], values) || key;
888
+ }
889
+ /**
890
+ * Localize the hint that args-tokens adds to the message of an error.
891
+ *
892
+ * A translation replaces the whole message, hint included, so the hint is translated by a key of
893
+ * its own and follows the translated error. args-tokens gives one today: for an option given without
894
+ * a value, when the argument after it may be the value, the long form with `=` (`--port=-5` for
895
+ * `--port -5`), in `values.next` and `values.suggestion`.
896
+ *
897
+ * @param ctx - A {@link CommandContext | command context}
898
+ * @param error - An args validation error
899
+ * @param values - The values of the error, as given to its translation
900
+ * @returns The localized hint, or `undefined` when the error has none.
901
+ */
902
+ async function localizeHint(ctx, error, values) {
903
+ if (error.code !== ArgsValidationErrorKeys.missingValue || typeof values.next !== "string" || typeof values.suggestion !== "string") return;
904
+ const hint = await localize(ctx, missingValueHintKey, values);
905
+ return hint && hint !== missingValueHintKey ? hint : void 0;
906
+ }
907
+ async function resolveValidationValues(ctx, error) {
908
+ if (error.code !== ArgsValidationErrorKeys.customParse) return error.values;
909
+ const { reasonKey, reasonValues } = error.values;
910
+ if (typeof reasonKey !== "string") return error.values;
911
+ const key = resolveKey(reasonKey, ctx.name);
912
+ const localizedReason = await localize(ctx, key, toRecord(reasonValues));
913
+ if (!localizedReason || localizedReason === key) return error.values;
914
+ return {
915
+ ...error.values,
916
+ reason: localizedReason
917
+ };
918
+ }
919
+ async function localize(ctx, key, values) {
920
+ const i18n = ctx.extensions?.[i18nPluginId];
921
+ if (i18n?.translate) return i18n.translate(key, values);
922
+ return key;
923
+ }
924
+ function toRecord(value) {
925
+ return value != null && typeof value === "object" ? value : Object.create(null);
926
+ }
461
927
  //#endregion
462
928
  //#region src/index.ts
463
929
  /**
@@ -465,4 +931,4 @@ function formatResource(resource, values = Object.create(null)) {
465
931
  * @license MIT
466
932
  */
467
933
  //#endregion
468
- export { ARG_ERROR_HINT_RESOURCE_KEYS, ARG_ERROR_RESOURCE_KEYS, ARG_NEGATABLE_PREFIX, ARG_PREFIX, ARG_PREFIX_AND_KEY_SEPARATOR, BUILD_IN_PREFIX_AND_KEY_SEPARATOR, BUILT_IN_KEY_SEPARATOR, BUILT_IN_PREFIX, COMMAND_BUILTIN_RESOURCE_KEYS, COMMAND_ERROR_RESOURCE_KEYS, COMMON_ARGS, en_US_default as DefaultResource, ERROR_PREFIX, ERROR_PREFIX_AND_KEY_SEPARATOR, PLUGIN_PREFIX, SUGGESTION_ERROR_RESOURCE_KEYS, create, deepFreeze, formatResource, getCommandSubCommands, isLazyCommand, kebabnize, localizable, log, makeShortLongOptionPair, namespacedId, resolveArgKey, resolveBuiltInKey, resolveCommandArgs, resolveDisplayName, resolveExamples, resolveKey, resolveLazyCommand, resolveOptionNames };
934
+ export { ARG_ERROR_HINT_RESOURCE_KEYS, ARG_ERROR_RESOURCE_KEYS, ARG_NEGATABLE_PREFIX, ARG_PREFIX, ARG_PREFIX_AND_KEY_SEPARATOR, BUILD_IN_PREFIX_AND_KEY_SEPARATOR, BUILT_IN_KEY_SEPARATOR, BUILT_IN_PREFIX, COMMAND_BUILTIN_RESOURCE_KEYS, COMMAND_ERROR_RESOURCE_KEYS, COMMON_ARGS, en_US_default as DefaultResource, ERROR_PREFIX, ERROR_PREFIX_AND_KEY_SEPARATOR, JSON_ERROR_RESOURCE_KEYS, JSON_RESOURCE_KEYS, PLUGIN_PREFIX, SUGGESTION_ERROR_RESOURCE_KEYS, create, deepFreeze, formatResource, getCommandSubCommands, isJsonArg, isLazyCommand, kebabnize, localizable, localizeBuiltinResource, localizeValidationError, log, makeShortLongOptionPair, namespacedId, resolveArgKey, resolveBuiltInKey, resolveCommandArgs, resolveDisplayName, resolveExamples, resolveJsonRequest, resolveKey, resolveLazyCommand, resolveOptionNames };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gunshi/shared",
3
3
  "description": "shared utils for gunshi",
4
- "version": "1.0.0-rc.5",
4
+ "version": "1.0.0-rc.6",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -50,8 +50,9 @@
50
50
  }
51
51
  },
52
52
  "devDependencies": {
53
- "@gunshi/resources": "1.0.0-rc.5",
54
- "gunshi": "1.0.0-rc.5",
53
+ "@gunshi/plugin": "1.0.0-rc.6",
54
+ "@gunshi/resources": "1.0.0-rc.6",
55
+ "gunshi": "1.0.0-rc.6",
55
56
  "publint": "^0.3.20",
56
57
  "tsdown": "0.22.14"
57
58
  },