@gunshi/plugin-global 1.0.0-beta.2 → 1.0.0-rc.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/README.md CHANGED
@@ -127,9 +127,9 @@ Available extensions:
127
127
 
128
128
  - **`showHeader(): Awaitable<string | undefined>`**: Display the application header. Returns `undefined` if no `renderHeader` function is provided in the CLI configuration.
129
129
 
130
- - **`showUsage(): Awaitable<string | undefined>`**: Display the command usage information. This is automatically called when `--help` is used. Returns `undefined` if no `renderUsage` function is provided.
130
+ - **`showUsage(): Awaitable<string | undefined>`**: Display the command usage information, with the header as its title. This is automatically called when `--help` is used. Returns `undefined` if no `renderUsage` function is provided.
131
131
 
132
- - **`showValidationErrors(error: AggregateError): Awaitable<string | undefined>`**: Display validation errors when argument validation fails. Returns `undefined` if `renderValidationErrors` is null.
132
+ - **`showValidationErrors(error: AggregateError): Awaitable<string | undefined>`**: Display validation errors when argument validation fails. The rendered message is printed to stderr, unless it is empty or `usageSilent` is set. Returns `undefined` if `renderValidationErrors` is null.
133
133
 
134
134
  ### Usage Example
135
135
 
@@ -143,15 +143,12 @@ const command = {
143
143
  // Access global extensions
144
144
  const { showVersion, showHeader } = ctx.extensions[globalId]
145
145
 
146
+ // Show the custom header before the output of the command (it prints the header itself)
147
+ await showHeader()
148
+
146
149
  // Manually show version if needed
147
150
  console.log(`Deploying with CLI version: ${showVersion()}`)
148
151
 
149
- // Show custom header
150
- const header = await showHeader()
151
- if (header) {
152
- console.log(header)
153
- }
154
-
155
152
  // Your command logic here...
156
153
  }
157
154
  }
package/lib/index.d.ts CHANGED
@@ -18,13 +18,13 @@ interface GlobalExtension {
18
18
  */
19
19
  showHeader: () => Awaitable<string | undefined>;
20
20
  /**
21
- * Show the usage of the application. if `--help` option is specified, it will print the usage to the console.
21
+ * Show the usage of the application, with the header as its title. if `--help` option is specified, it will print the usage to the console.
22
22
  *
23
- * @returns The usage of the application, or `undefined` if the `renderUsage` is not specified.
23
+ * @returns The header and the usage of the application, or `undefined` if the `renderUsage` is not specified.
24
24
  */
25
25
  showUsage: () => Awaitable<string | undefined>;
26
26
  /**
27
- * Show validation errors. This is called when argument validation fails.
27
+ * Show validation errors. This is called when argument validation fails. The rendered message is printed to stderr, unless it is empty or `usageSilent` is set.
28
28
  *
29
29
  * @param error - The aggregate error containing validation failures
30
30
  * @returns The rendered error message, or `undefined` if `renderValidationErrors` is null
package/lib/index.js CHANGED
@@ -61,6 +61,36 @@ const pluginId = namespacedId("global");
61
61
  * @license MIT
62
62
  */
63
63
  /**
64
+ * The key of the registry in which the core keeps, for the command context of each run, the set of the
65
+ * errors that have been reported to the user. An error in the set tells `cli()` to end without
66
+ * rejecting. The errors are frozen with the command context, so they are not marked themselves. The key
67
+ * is a registered symbol, since `gunshi` and the plugins bundle their own copies.
68
+ */
69
+ const REPORTED = Symbol.for("gunshi.reported");
70
+ /**
71
+ * Mark an error as reported to the user, in the set that the core keeps for the run of the context.
72
+ *
73
+ * @param ctx - The command context of the run
74
+ * @param error - The error that has been reported
75
+ * @returns `true` if the error is marked, `false` if the core keeps no set for the run
76
+ */
77
+ function markReported(ctx, error) {
78
+ const reported = globalThis[REPORTED]?.get(ctx);
79
+ if (reported === void 0) return false;
80
+ reported.add(error);
81
+ return true;
82
+ }
83
+ /**
84
+ * Set the exit code of the process, where the runtime has one (Node.js, Bun and Deno 2 have `process`).
85
+ * The core does not call the APIs of a runtime, so the plugin, which reports the errors, sets it.
86
+ *
87
+ * @param code - The exit code
88
+ */
89
+ function setExitCode(code) {
90
+ const proc = globalThis.process;
91
+ if (proc != null && typeof proc === "object") proc.exitCode = code;
92
+ }
93
+ /**
64
94
  * Decorator function to extend the command with global options.
65
95
  *
66
96
  * @param baseRunner - The base command runner
@@ -72,27 +102,25 @@ const decorator = (baseRunner) => async (ctx) => {
72
102
  * declares the same name — even with an identical schema — is not in that map, so `--version` /
73
103
  * `--help` belong to the command.
74
104
  */
75
- const { values, validationError, env: { globalOptions }, extensions: { [pluginId]: { showVersion, showHeader, showUsage, showValidationErrors } } } = ctx;
76
- if (hasPriorityValidationError(validationError)) {
77
- await showValidationErrors(validationError);
78
- throw validationError;
79
- }
105
+ const { values, validationError, env: { globalOptions }, extensions: { [pluginId]: { showVersion, showUsage, showValidationErrors } } } = ctx;
106
+ /**
107
+ * NOTE(kazupon): when the plugin has shown the validation errors to the user, they have been
108
+ * reported: the exit code is 1, and `cli()` ends without rejecting, as the other CLI libraries end
109
+ * without a stack trace. They are still thrown, so that the `onErrorCommand` hook sees them. When
110
+ * nothing was shown (`renderValidationErrors: null`, an empty message, or `usageSilent`), `cli()`
111
+ * rejects with them, for the app to report them. So it does when the core keeps no set of reported
112
+ * errors for the run, and then the exit code is left alone.
113
+ *
114
+ * @param error - The aggregate error of the validation errors
115
+ */
116
+ const report = async (error) => {
117
+ if (await showValidationErrors(error) && !ctx.env.usageSilent && markReported(ctx, error)) setExitCode(1);
118
+ throw error;
119
+ };
120
+ if (hasPriorityValidationError(validationError)) return await report(validationError);
80
121
  if (values.version && globalOptions?.has("version")) return showVersion();
81
- const buf = [];
82
- const header = await showHeader();
83
- if (header) buf.push(header);
84
- if (values.help && globalOptions?.has("help")) {
85
- const usage = await showUsage();
86
- if (usage) {
87
- buf.push(usage);
88
- return buf.join("\n");
89
- }
90
- return;
91
- }
92
- if (validationError) {
93
- await showValidationErrors(validationError);
94
- throw validationError;
95
- }
122
+ if (values.help && globalOptions?.has("help")) return await showUsage();
123
+ if (validationError) return await report(validationError);
96
124
  return baseRunner(ctx);
97
125
  };
98
126
  //#endregion
@@ -126,19 +154,31 @@ function extension(ctx) {
126
154
  return header;
127
155
  },
128
156
  showUsage: async () => {
129
- if (ctx.env.renderUsage != null) {
130
- const usage = await ctx.env.renderUsage(ctx);
131
- if (usage) {
132
- ctx.log(usage);
133
- return usage;
134
- }
157
+ if (ctx.env.renderUsage == null) return;
158
+ const usage = await ctx.env.renderUsage(ctx);
159
+ if (!usage) return;
160
+ /**
161
+ * NOTE(kazupon): the header is the title of the usage, so it comes with the usage, wherever the
162
+ * usage is shown: for `--help`, and when a command that takes `--help` itself shows it. It is not
163
+ * shown before a command runs, where it would come before what the command prints, such as JSON.
164
+ */
165
+ const header = ctx.env.renderHeader == null ? void 0 : await ctx.env.renderHeader(ctx);
166
+ if (header) {
167
+ ctx.log(header);
168
+ ctx.log();
135
169
  }
170
+ ctx.log(usage);
171
+ return header ? [header, usage].join("\n") : usage;
136
172
  },
137
173
  showValidationErrors: async (error) => {
138
174
  if (ctx.env.renderValidationErrors === null) return;
139
175
  if (ctx.env.renderValidationErrors !== void 0) {
140
176
  const message = await ctx.env.renderValidationErrors(ctx, error);
141
- ctx.log(message);
177
+ /**
178
+ * NOTE(kazupon): the validation errors are diagnostics, not the output of the command, so they
179
+ * go to stderr, as the other CLI libraries do. stdout stays for what a command prints, such as JSON.
180
+ */
181
+ if (message && !ctx.env.usageSilent) console.error(message);
142
182
  return message;
143
183
  }
144
184
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gunshi/plugin-global",
3
3
  "description": "global options plugin for gunshi",
4
- "version": "1.0.0-beta.2",
4
+ "version": "1.0.0-rc.2",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -51,10 +51,10 @@
51
51
  }
52
52
  },
53
53
  "dependencies": {
54
- "@gunshi/plugin": "1.0.0-beta.2"
54
+ "@gunshi/plugin": "1.0.0-rc.2"
55
55
  },
56
56
  "devDependencies": {
57
- "@gunshi/shared": "1.0.0-beta.2",
57
+ "@gunshi/shared": "1.0.0-rc.2",
58
58
  "deno": "^2.7.14",
59
59
  "jsr": "^0.14.3",
60
60
  "jsr-exports-lint": "^0.4.2",