@f5-sales-demo/pi-utils 20.2.2 → 20.2.3

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/cli.ts +32 -12
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-utils",
4
- "version": "20.2.2",
4
+ "version": "20.2.3",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/bun": "^1.3",
42
- "@f5-sales-demo/pi-natives": "20.2.2"
42
+ "@f5-sales-demo/pi-natives": "20.2.3"
43
43
  },
44
44
  "engines": {
45
45
  "bun": ">=1.3.7"
package/src/cli.ts CHANGED
@@ -98,6 +98,9 @@ export interface ParseOutput<
98
98
  argv: string[];
99
99
  }
100
100
 
101
+ /** Invalid command-line syntax that callers should report as a usage error. */
102
+ export class CliUsageError extends Error {}
103
+
101
104
  // ---------------------------------------------------------------------------
102
105
  // Command base class
103
106
  // ---------------------------------------------------------------------------
@@ -173,12 +176,19 @@ export abstract class Command {
173
176
 
174
177
  // strict=false when command declares args (positionals must pass through)
175
178
  // or when the command itself opts out
176
- const { values: rawValues, positionals } = nodeParseArgs({
177
- args: this.argv,
178
- options,
179
- allowPositionals: true,
180
- strict,
181
- });
179
+ const { values: rawValues, positionals } = (() => {
180
+ try {
181
+ return nodeParseArgs({
182
+ args: this.argv,
183
+ options,
184
+ allowPositionals: true,
185
+ strict,
186
+ });
187
+ } catch (error) {
188
+ if (error instanceof TypeError) throw new CliUsageError(error.message);
189
+ throw error;
190
+ }
191
+ })();
182
192
 
183
193
  // Convert raw values to proper types and validate
184
194
  const flags: Record<string, unknown> = {};
@@ -190,7 +200,7 @@ export abstract class Command {
190
200
  } else {
191
201
  const n = Number.parseInt(raw as string, 10);
192
202
  if (Number.isNaN(n)) {
193
- throw new Error(`Expected integer for --${name}, got "${raw}"`);
203
+ throw new CliUsageError(`Expected integer for --${name}, got "${raw}"`);
194
204
  }
195
205
  flags[name] = n;
196
206
  }
@@ -203,14 +213,16 @@ export abstract class Command {
203
213
  // Validate options constraint
204
214
  if (val !== undefined && desc.options && !Array.isArray(val)) {
205
215
  if (!desc.options.includes(val as string)) {
206
- throw new Error(`Expected --${name} to be one of: ${[...desc.options].join(", ")}; got "${val}"`);
216
+ throw new CliUsageError(
217
+ `Expected --${name} to be one of: ${[...desc.options].join(", ")}; got "${val}"`,
218
+ );
207
219
  }
208
220
  }
209
221
  flags[name] = val;
210
222
  }
211
223
  // Validate required
212
224
  if (desc.required && flags[name] === undefined) {
213
- throw new Error(`Missing required flag: --${name}`);
225
+ throw new CliUsageError(`Missing required flag: --${name}`);
214
226
  }
215
227
  }
216
228
 
@@ -229,13 +241,15 @@ export abstract class Command {
229
241
  }
230
242
  // Validate required
231
243
  if (desc.required && args[argName] === undefined) {
232
- throw new Error(`Missing required argument: ${argName}`);
244
+ throw new CliUsageError(`Missing required argument: ${argName}`);
233
245
  }
234
246
  // Validate options constraint
235
247
  const argVal = args[argName];
236
248
  if (argVal !== undefined && desc.options && typeof argVal === "string") {
237
249
  if (!desc.options.includes(argVal)) {
238
- throw new Error(`Expected ${argName} to be one of: ${[...desc.options].join(", ")}; got "${argVal}"`);
250
+ throw new CliUsageError(
251
+ `Expected ${argName} to be one of: ${[...desc.options].join(", ")}; got "${argVal}"`,
252
+ );
239
253
  }
240
254
  }
241
255
  }
@@ -418,7 +432,13 @@ export async function run(opts: RunOptions): Promise<void> {
418
432
  const Cmd = await entry.load();
419
433
  const config: CliConfig = { bin, version, commands: new Map([[entry.name, Cmd]]) };
420
434
  const instance = new Cmd(commandArgv, config);
421
- await instance.run();
435
+ try {
436
+ await instance.run();
437
+ } catch (error) {
438
+ if (!(error instanceof CliUsageError)) throw error;
439
+ process.stderr.write(`Error: ${error.message}\n`);
440
+ process.exitCode = 2;
441
+ }
422
442
  }
423
443
 
424
444
  /** Resolve all command loaders for help/alias display. */