@gunshi/plugin-renderer 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.
package/README.md CHANGED
@@ -115,7 +115,7 @@ EXAMPLES:
115
115
 
116
116
  - **`renderHeader(ctx: CommandContext): Promise<string>`**: Renders the command header section
117
117
  - **`renderUsage(ctx: CommandContext): Promise<string>`**: Renders the complete usage/help information
118
- - **`renderValidationErrors(ctx: CommandContext, error: AggregateError): Promise<string>`**: Renders validation errors
118
+ - **`renderValidationErrors(ctx: CommandContext, error: AggregateError): Promise<string>`**: Renders validation errors. With `--json`, the global options plugin prints the validation errors as one line of JSON, and does not call the renderer of the validation errors
119
119
 
120
120
  ## 🧩 Context Extensions
121
121
 
package/lib/index.d.ts CHANGED
@@ -581,11 +581,12 @@ type CommonArgType = {
581
581
  };
582
582
  };
583
583
  declare const COMMON_ARGS: CommonArgType;
584
- declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "ARGUMENTS", "OPTIONS", "EXAMPLES", "FORMORE", "NEGATABLE", "DEFAULT", "CHOICES"];
584
+ declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "ARGUMENTS", "OPTIONS", "EXAMPLES", "FORMORE", "NEGATABLE", "DEFAULT", "CHOICES", "CAUSEDBY"];
585
585
  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"];
586
586
  declare const ARG_ERROR_HINT_RESOURCE_KEYS: readonly ["err:arg:missing-value:hint"];
587
587
  declare const COMMAND_ERROR_RESOURCE_KEYS: readonly ["err:cmd:not-found", "err:cmd:ambiguous", "err:cmd:inconsistent-options", "err:cmd:lazy-schema-mismatch"];
588
588
  declare const SUGGESTION_ERROR_RESOURCE_KEYS: readonly ["err:suggestion:did-you-mean"];
589
+ declare const JSON_ERROR_RESOURCE_KEYS: readonly ["err:json:not-supported", "err:json:invalid-result", "err:json:unserializable-result"];
589
590
  //#endregion
590
591
  //#region ../shared/src/types.d.ts
591
592
  type RemoveIndexSignature<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]; };
@@ -625,10 +626,14 @@ type CommandErrorResourceKeys = (typeof COMMAND_ERROR_RESOURCE_KEYS)[number];
625
626
  * Suggestion error resource keys.
626
627
  */
627
628
  type SuggestionErrorResourceKeys = (typeof SUGGESTION_ERROR_RESOURCE_KEYS)[number];
629
+ /**
630
+ * Resource keys of the errors of the JSON output.
631
+ */
632
+ type JsonErrorResourceKeys = (typeof JSON_ERROR_RESOURCE_KEYS)[number];
628
633
  /**
629
634
  * Error resource keys.
630
635
  */
631
- type ErrorResourceKeys = ArgErrorResourceKeys | ArgErrorHintResourceKeys | CommandErrorResourceKeys | SuggestionErrorResourceKeys;
636
+ type ErrorResourceKeys = ArgErrorResourceKeys | ArgErrorHintResourceKeys | CommandErrorResourceKeys | SuggestionErrorResourceKeys | JsonErrorResourceKeys;
632
637
  /**
633
638
  * Command built-in keys.
634
639
  */
package/lib/index.js CHANGED
@@ -150,8 +150,13 @@ var en_US_default = {
150
150
  NEGATABLE: "Negatable of",
151
151
  DEFAULT: "default",
152
152
  CHOICES: "choices",
153
+ CAUSEDBY: "Caused by",
153
154
  help: "Display this help message",
154
155
  version: "Display this version",
156
+ json: "Output the result as JSON",
157
+ "json:invalid-arguments": "{$count} arguments are invalid",
158
+ "json:next:help": "See the arguments of the command",
159
+ "json:next:help-placeholder": "Replace <COMMAND> with how you ran this CLI, to see the arguments of the command",
155
160
  "err:arg:required-option": "Option {$displayName} is required",
156
161
  "err:arg:required-positional": "Positional argument {$displayName} is required",
157
162
  "err:arg:invalid-type": "Invalid value for {$displayName}: expected {$expected}",
@@ -167,6 +172,9 @@ var en_US_default = {
167
172
  "err:cmd:ambiguous": "The command is ambiguous. Move options after the command name: {$candidatePaths}",
168
173
  "err:cmd:inconsistent-options": "The options do not identify a consistent command. Move options after the command name.",
169
174
  "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}",
175
+ "err:json:not-supported": "This command does not support --json",
176
+ "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",
177
+ "err:json:unserializable-result": "The command finished, but its result cannot be converted to JSON: {$message}",
170
178
  "err:suggestion:did-you-mean": "Did you mean {$name}?"
171
179
  };
172
180
  //#endregion
@@ -344,6 +352,84 @@ function formatResource(resource, values = Object.create(null)) {
344
352
  });
345
353
  }
346
354
  //#endregion
355
+ //#region ../shared/src/validation.ts
356
+ /**
357
+ * @author kazuya kawaguchi (a.k.a. kazupon)
358
+ * @license MIT
359
+ */
360
+ const i18nPluginId$1 = namespacedId("i18n");
361
+ const missingValueHintKey = "err:arg:missing-value:hint";
362
+ /**
363
+ * Localize the message of a validation error, as gunshi shows it to the user: with the i18n plugin when
364
+ * it is installed, and otherwise the message of the error.
365
+ *
366
+ * NOTE(kazupon): the renderer plugin renders the validation errors for people, and the global options
367
+ * plugin prints them as JSON with `--json`. Both take the messages from here, so that a message is the
368
+ * same in both.
369
+ *
370
+ * @param ctx - A {@link CommandContext | command context}
371
+ * @param error - A validation error: an {@link ArgsValidationError}, a command-not-found error, or a
372
+ * command-resolution error
373
+ * @returns The localized message
374
+ */
375
+ async function localizeValidationError(ctx, error) {
376
+ if (isCommandNotFoundError(error) && error.code) {
377
+ const message = await localize(ctx, error.code, error.values);
378
+ if (message && message !== error.code) return message;
379
+ }
380
+ if (isCommandResolutionError(error) && error.code) {
381
+ const message = await localize(ctx, error.code, error.values);
382
+ if (message && message !== error.code) return message;
383
+ }
384
+ if (isArgsValidationError(error) && error.code) {
385
+ const values = await resolveValidationValues(ctx, error);
386
+ const message = await localize(ctx, error.code, values);
387
+ if (message && message !== error.code) {
388
+ const hint = await localizeHint(ctx, error, values);
389
+ return hint ? `${message} ${hint}` : message;
390
+ }
391
+ }
392
+ return error.message;
393
+ }
394
+ /**
395
+ * Localize the hint that args-tokens adds to the message of an error.
396
+ *
397
+ * A translation replaces the whole message, hint included, so the hint is translated by a key of
398
+ * its own and follows the translated error. args-tokens gives one today: for an option given without
399
+ * a value, when the argument after it may be the value, the long form with `=` (`--port=-5` for
400
+ * `--port -5`), in `values.next` and `values.suggestion`.
401
+ *
402
+ * @param ctx - A {@link CommandContext | command context}
403
+ * @param error - An args validation error
404
+ * @param values - The values of the error, as given to its translation
405
+ * @returns The localized hint, or `undefined` when the error has none.
406
+ */
407
+ async function localizeHint(ctx, error, values) {
408
+ if (error.code !== ArgsValidationErrorKeys.missingValue || typeof values.next !== "string" || typeof values.suggestion !== "string") return;
409
+ const hint = await localize(ctx, missingValueHintKey, values);
410
+ return hint && hint !== missingValueHintKey ? hint : void 0;
411
+ }
412
+ async function resolveValidationValues(ctx, error) {
413
+ if (error.code !== ArgsValidationErrorKeys.customParse) return error.values;
414
+ const { reasonKey, reasonValues } = error.values;
415
+ if (typeof reasonKey !== "string") return error.values;
416
+ const key = resolveKey(reasonKey, ctx.name);
417
+ const localizedReason = await localize(ctx, key, toRecord(reasonValues));
418
+ if (!localizedReason || localizedReason === key) return error.values;
419
+ return {
420
+ ...error.values,
421
+ reason: localizedReason
422
+ };
423
+ }
424
+ async function localize(ctx, key, values) {
425
+ const i18n = ctx.extensions?.[i18nPluginId$1];
426
+ if (i18n?.translate) return i18n.translate(key, values);
427
+ return key;
428
+ }
429
+ function toRecord(value) {
430
+ return value != null && typeof value === "object" ? value : Object.create(null);
431
+ }
432
+ //#endregion
347
433
  //#region ../shared/src/index.ts
348
434
  /**
349
435
  * @author kazuya kawaguchi (a.k.a. kazupon)
@@ -795,8 +881,6 @@ function isRequiredSinglePositionalArg(arg) {
795
881
  * @author kazuya kawaguchi (a.k.a. kazupon)
796
882
  * @license MIT
797
883
  */
798
- const i18nPluginId$1 = namespacedId("i18n");
799
- const missingValueHintKey = "err:arg:missing-value:hint";
800
884
  /**
801
885
  * Render the validation errors.
802
886
  *
@@ -806,66 +890,9 @@ const missingValueHintKey = "err:arg:missing-value:hint";
806
890
  */
807
891
  async function renderValidationErrors(ctx, error) {
808
892
  const messages = [];
809
- for (const err of error.errors) messages.push(await renderValidationError(ctx, err));
893
+ for (const err of error.errors) messages.push(await localizeValidationError(ctx, err));
810
894
  return messages.join("\n");
811
895
  }
812
- async function renderValidationError(ctx, error) {
813
- if (isCommandNotFoundError(error) && error.code) {
814
- const message = await localize(ctx, error.code, error.values);
815
- if (message && message !== error.code) return message;
816
- }
817
- if (isCommandResolutionError(error) && error.code) {
818
- const message = await localize(ctx, error.code, error.values);
819
- if (message && message !== error.code) return message;
820
- }
821
- if (isArgsValidationError(error) && error.code) {
822
- const values = await resolveValidationValues(ctx, error);
823
- const message = await localize(ctx, error.code, values);
824
- if (message && message !== error.code) {
825
- const hint = await localizeHint(ctx, error, values);
826
- return hint ? `${message} ${hint}` : message;
827
- }
828
- }
829
- return error.message;
830
- }
831
- /**
832
- * Localize the hint that args-tokens adds to the message of an error.
833
- *
834
- * A translation replaces the whole message, hint included, so the hint is translated by a key of
835
- * its own and follows the translated error. args-tokens gives one today: for an option given without
836
- * a value, when the argument after it may be the value, the long form with `=` (`--port=-5` for
837
- * `--port -5`), in `values.next` and `values.suggestion`.
838
- *
839
- * @param ctx - A {@link CommandContext | command context}
840
- * @param error - An args validation error
841
- * @param values - The values of the error, as given to its translation
842
- * @returns The localized hint, or `undefined` when the error has none.
843
- */
844
- async function localizeHint(ctx, error, values) {
845
- if (error.code !== ArgsValidationErrorKeys.missingValue || typeof values.next !== "string" || typeof values.suggestion !== "string") return;
846
- const hint = await localize(ctx, missingValueHintKey, values);
847
- return hint && hint !== missingValueHintKey ? hint : void 0;
848
- }
849
- async function resolveValidationValues(ctx, error) {
850
- if (error.code !== ArgsValidationErrorKeys.customParse) return error.values;
851
- const { reasonKey, reasonValues } = error.values;
852
- if (typeof reasonKey !== "string") return error.values;
853
- const key = resolveKey(reasonKey, ctx.name);
854
- const localizedReason = await localize(ctx, key, toRecord(reasonValues));
855
- if (!localizedReason || localizedReason === key) return error.values;
856
- return {
857
- ...error.values,
858
- reason: localizedReason
859
- };
860
- }
861
- async function localize(ctx, key, values) {
862
- const i18n = ctx.extensions?.[i18nPluginId$1];
863
- if (i18n?.translate) return i18n.translate(key, values);
864
- return key;
865
- }
866
- function toRecord(value) {
867
- return value != null && typeof value === "object" ? value : Object.create(null);
868
- }
869
896
  //#endregion
870
897
  //#region src/index.ts
871
898
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gunshi/plugin-renderer",
3
3
  "description": "usage renderer plugin 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"
@@ -53,13 +53,13 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "@gunshi/plugin": "1.0.0-rc.5"
56
+ "@gunshi/plugin": "1.0.0-rc.6"
57
57
  },
58
58
  "peerDependencies": {
59
- "@gunshi/plugin-i18n": "1.0.0-rc.5"
59
+ "@gunshi/plugin-i18n": "1.0.0-rc.6"
60
60
  },
61
61
  "devDependencies": {
62
- "@gunshi/shared": "1.0.0-rc.5",
62
+ "@gunshi/shared": "1.0.0-rc.6",
63
63
  "publint": "^0.3.20",
64
64
  "tsdown": "0.22.14"
65
65
  },