@getargvio/yargs 0.1.0 → 0.4.0

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/dist/index.cjs CHANGED
@@ -56,9 +56,21 @@ function instrumentYargs(yargsInstance, client, options = {}) {
56
56
  return function wrapped(...args) {
57
57
  const maybeArgs = args[0];
58
58
  rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2);
59
- if (captureHelp && helpFlags.some((flag) => rawArgs.includes(flag))) {
59
+ client.recordSessionStart();
60
+ const helpRequested = helpFlags.some((flag) => rawArgs.includes(flag));
61
+ if (captureHelp && helpRequested) {
60
62
  client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs));
61
63
  }
64
+ const recordCommand = (exitCode) => {
65
+ if (helpRequested) return;
66
+ client.recordCommand({
67
+ path: lastCommandPath,
68
+ flagNames: extractFlagNames(rawArgs),
69
+ startMs: start,
70
+ durationMs: Date.now() - start,
71
+ exitCode
72
+ });
73
+ };
62
74
  const start = Date.now();
63
75
  let resultPromise;
64
76
  try {
@@ -67,16 +79,13 @@ function instrumentYargs(yargsInstance, client, options = {}) {
67
79
  resultPromise = Promise.reject(err);
68
80
  }
69
81
  return resultPromise.then((result) => {
70
- client.recordExitCode(lastCommandPath, 0);
71
- client.recordLatency(lastCommandPath, Date.now() - start);
82
+ recordCommand(0);
72
83
  return result;
73
84
  }).catch((err) => {
74
- client.recordExitCode(lastCommandPath, 1);
75
- client.recordLatency(lastCommandPath, Date.now() - start);
76
- client.recordError(
77
- lastCommandPath,
78
- classify(err instanceof Error ? err : new Error(String(err)))
79
- );
85
+ recordCommand(1);
86
+ const classified = classify(err instanceof Error ? err : new Error(String(err)));
87
+ const { category, reason } = typeof classified === "string" ? { category: classified } : classified;
88
+ client.recordError(lastCommandPath, category, reason);
80
89
  throw err;
81
90
  });
82
91
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["ErrorCategory","createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Argv, Arguments } from 'yargs'\n\nimport type { Client, ClientOptions } from '@getargvio/argvio'\nimport { createClient, ErrorCategory } from '@getargvio/argvio'\nimport type { ErrorCategory as ErrorCategoryType } from '@getargvio/argvio'\n\nconst INVOCATION_MARKER = Symbol('argvio.recordedInvocation')\n\n/** Configures {@link instrumentYargs}. */\nexport interface YargsHookOptions {\n /** Maps a caught error to an {@link ErrorCategoryType}. Without it, every error is recorded as `ErrorCategory.Unknown`. */\n classify?: (err: Error) => ErrorCategoryType\n /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */\n captureHelp?: boolean\n /** Skip the `middleware`-based command-invocation capture. */\n captureInvocation?: boolean\n /**\n * Called from inside the installed `.fail()` handler instead of this\n * package's default print-and-rethrow behavior. Use this if you\n * already have your own `.fail()` logic you want preserved —\n * `instrumentYargs` cannot chain with a pre-existing `.fail()` handler\n * the way it can with `middleware` (see docs/yargs-integration.md).\n * Error/exit-code recording itself always happens uniformly in the\n * wrapped `parseAsync`, regardless of whether `onFail` is set. Note\n * `.fail()` only fires for validation errors and *rejected* async\n * handlers — for the async-rejection case specifically, yargs invokes\n * `.fail()` purely as a side effect and swallows anything it throws,\n * so `onFail` cannot change whether the original rejection propagates.\n */\n onFail?: (msg: string, err: Error | undefined, yargsInstance: Argv) => void\n /** Flag tokens treated as a help request for the best-effort help capture. Defaults to `['--help', '-h']`. */\n helpFlags?: readonly string[]\n}\n\nfunction extractFlagNames(rawArgs: readonly string[]): string[] {\n const names = new Set<string>()\n for (const token of rawArgs) {\n if (!token.startsWith('-') || token === '-' || token === '--') continue\n const name = token.replace(/^--?/, '').split('=')[0]\n if (name) names.add(name)\n }\n return [...names]\n}\n\nfunction commandPathFrom(argv: Arguments): string {\n const script = typeof argv['$0'] === 'string' ? argv['$0'] : ''\n const segments = argv._.map((segment) => String(segment))\n return [script, ...segments].filter(Boolean).join(' ')\n}\n\n/**\n * Best-effort command path guess before parsing has happened (used for\n * help capture, which fires before yargs has resolved a command path\n * of its own). No CLI name prefix — the builder-side `Argv` has no\n * public getter for `scriptName`, only a setter.\n */\nfunction guessCommandPathFromRawArgs(rawArgs: readonly string[]): string {\n const segments: string[] = []\n for (const token of rawArgs) {\n if (token.startsWith('-')) break\n segments.push(token)\n }\n return segments.join(' ')\n}\n\ntype ParseFn = Argv['parseAsync']\n\n/**\n * Wires automatic telemetry capture into a yargs instance:\n *\n * - Command path + flag names (see `Client.recordCommandInvocation`) are\n * captured via global `middleware(fn, true)` — applied before\n * validation, so it still runs even if validation later fails.\n * - Exit code, latency, and errors are captured by wrapping\n * `parseAsync`/`parse` — the one place guaranteed to run exactly once\n * per invocation, regardless of success or failure.\n * - Help usage is captured on a **best-effort** basis: yargs handles\n * `--help`/`-h` internally before middleware, `.fail()`, or any\n * command handler ever runs, so this package scans the raw args\n * for help flags *before* calling the real parse.\n *\n * Calls `yargsInstance.exitProcess(false)`: with the default\n * `exitProcess(true)`, yargs calls `process.exit()` itself on failure or\n * `--help`, before this package's wrapped `parseAsync`/`.fail()` ever\n * get a chance to record anything. After instrumenting, your own code\n * must await `parseAsync()` and decide the real process exit itself —\n * see docs/yargs-integration.md.\n */\nexport function instrumentYargs(\n yargsInstance: Argv,\n client: Client,\n options: YargsHookOptions = {},\n): Argv {\n const classify = options.classify ?? (() => ErrorCategory.Unknown)\n const captureHelp = options.captureHelp ?? true\n const captureInvocation = options.captureInvocation ?? true\n const helpFlags = options.helpFlags ?? ['--help', '-h']\n\n yargsInstance.exitProcess(false)\n\n let lastCommandPath = ''\n let rawArgs: readonly string[] = []\n\n if (captureInvocation) {\n yargsInstance.middleware((argv) => {\n const marker = argv as unknown as Record<symbol, boolean>\n if (marker[INVOCATION_MARKER]) return\n marker[INVOCATION_MARKER] = true\n\n lastCommandPath = commandPathFrom(argv)\n client.recordCommandInvocation(lastCommandPath, extractFlagNames(rawArgs))\n }, true)\n }\n\n // yargs only ever routes a failure through `.fail()` for validation\n // errors and *rejected* async handlers — a handler that throws\n // synchronously bypasses `.fail()` entirely and propagates straight\n // out of `parse()`/`parseAsync()`. So `.fail()` here does not itself\n // record anything (that would double-count the async-rejection case\n // and miss the synchronous-throw case); its only jobs are (a) letting\n // a vendor plug in `onFail`, and (b) — critically — converting a\n // validation failure into an actual thrown error. Without this, with\n // `exitProcess(false)`, yargs' default fail behavior swallows the\n // failure and `parseAsync()` would resolve as if nothing went wrong.\n // Error/exit-code/latency recording happens uniformly for every\n // failure mode in the `parseAsync` wrapper's `catch` below instead.\n yargsInstance.fail((msg, err, yi) => {\n if (options.onFail) {\n options.onFail(msg, err, yi)\n return\n }\n process.stderr.write(`${msg}\\n`)\n throw err ?? new Error(msg)\n })\n\n const wrapParse = (original: ParseFn): ParseFn => {\n return function wrapped(this: Argv, ...args: Parameters<ParseFn>) {\n const maybeArgs = args[0]\n rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2)\n\n if (captureHelp && helpFlags.some((flag) => rawArgs.includes(flag))) {\n client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs))\n }\n\n const start = Date.now()\n let resultPromise: Promise<unknown>\n try {\n // A command handler that throws synchronously (as opposed to\n // returning a rejected Promise) makes yargs' own `parse()`\n // throw synchronously too, bypassing `.fail()` entirely — this\n // try/catch normalizes that into a rejected promise so both\n // failure modes go through the same recording path below.\n resultPromise = (original as (...a: unknown[]) => Promise<unknown>).apply(this, args)\n } catch (err) {\n resultPromise = Promise.reject(err)\n }\n return resultPromise\n .then((result: unknown) => {\n client.recordExitCode(lastCommandPath, 0)\n client.recordLatency(lastCommandPath, Date.now() - start)\n return result\n })\n .catch((err: unknown) => {\n client.recordExitCode(lastCommandPath, 1)\n client.recordLatency(lastCommandPath, Date.now() - start)\n client.recordError(\n lastCommandPath,\n classify(err instanceof Error ? err : new Error(String(err))),\n )\n throw err\n })\n } as ParseFn\n }\n\n yargsInstance.parseAsync = wrapParse(yargsInstance.parseAsync.bind(yargsInstance))\n\n return yargsInstance\n}\n\n/**\n * Builds a `Client` (using `cliName`/`cliVersion` you provide — yargs\n * has no built-in fields to read them from) and calls\n * {@link instrumentYargs} to wire up capture.\n *\n * const { client } = instrument(yargsInstance, apiKey, 'mycli', '1.4.0')\n * const argv = await yargsInstance.parseAsync()\n * await client.shutdown()\n */\nexport function instrument(\n yargsInstance: Argv,\n apiKey: string,\n cliName: string,\n cliVersion: string,\n clientOptions: ClientOptions = {},\n hookOptions: YargsHookOptions = {},\n): { client: Client; yargs: Argv } {\n const client = createClient(apiKey, cliName, cliVersion, clientOptions)\n instrumentYargs(yargsInstance, client, hookOptions)\n return { client, yargs: yargsInstance }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["ErrorCategory","createClient"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Argv, Arguments } from 'yargs'\n\nimport type { Client, ClientOptions } from '@getargvio/argvio'\nimport { createClient, ErrorCategory } from '@getargvio/argvio'\nimport type { ErrorCategory as ErrorCategoryType, ErrorClassification } from '@getargvio/argvio'\n\nconst INVOCATION_MARKER = Symbol('argvio.recordedInvocation')\n\n/** Configures {@link instrumentYargs}. */\nexport interface YargsHookOptions {\n /**\n * Maps a caught error to an {@link ErrorCategoryType}, or to\n * `{ category, reason }` to also report a fixed-vocabulary reason (e.g.\n * `\"no-token\"`). The reason must be an identifier chosen in code\n * (`^[a-z0-9][a-z0-9._-]*$`, at most 64 bytes), never error text or user\n * input; one that doesn't match is dropped. Without `classify`, every\n * error is recorded as `ErrorCategory.Unknown` with no reason.\n */\n classify?: (err: Error) => ErrorCategoryType | ErrorClassification\n /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */\n captureHelp?: boolean\n /** Skip the `middleware`-based command-invocation capture. */\n captureInvocation?: boolean\n /**\n * Called from inside the installed `.fail()` handler instead of this\n * package's default print-and-rethrow behavior. Use this if you\n * already have your own `.fail()` logic you want preserved —\n * `instrumentYargs` cannot chain with a pre-existing `.fail()` handler\n * the way it can with `middleware` (see docs/yargs-integration.md).\n * Error/exit-code recording itself always happens uniformly in the\n * wrapped `parseAsync`, regardless of whether `onFail` is set. Note\n * `.fail()` only fires for validation errors and *rejected* async\n * handlers — for the async-rejection case specifically, yargs invokes\n * `.fail()` purely as a side effect and swallows anything it throws,\n * so `onFail` cannot change whether the original rejection propagates.\n */\n onFail?: (msg: string, err: Error | undefined, yargsInstance: Argv) => void\n /** Flag tokens treated as a help request for the best-effort help capture. Defaults to `['--help', '-h']`. */\n helpFlags?: readonly string[]\n}\n\nfunction extractFlagNames(rawArgs: readonly string[]): string[] {\n const names = new Set<string>()\n for (const token of rawArgs) {\n if (!token.startsWith('-') || token === '-' || token === '--') continue\n const name = token.replace(/^--?/, '').split('=')[0]\n if (name) names.add(name)\n }\n return [...names]\n}\n\nfunction commandPathFrom(argv: Arguments): string {\n const script = typeof argv['$0'] === 'string' ? argv['$0'] : ''\n const segments = argv._.map((segment) => String(segment))\n return [script, ...segments].filter(Boolean).join(' ')\n}\n\n/**\n * Best-effort command path guess before parsing has happened (used for\n * help capture, which fires before yargs has resolved a command path\n * of its own). No CLI name prefix — the builder-side `Argv` has no\n * public getter for `scriptName`, only a setter.\n */\nfunction guessCommandPathFromRawArgs(rawArgs: readonly string[]): string {\n const segments: string[] = []\n for (const token of rawArgs) {\n if (token.startsWith('-')) break\n segments.push(token)\n }\n return segments.join(' ')\n}\n\ntype ParseFn = Argv['parseAsync']\n\n/**\n * Wires automatic telemetry capture into a yargs instance:\n *\n * - Command path + flag names (see `Client.recordCommandInvocation`) are\n * captured via global `middleware(fn, true)` — applied before\n * validation, so it still runs even if validation later fails.\n * - The session start, and each completed command (`Client.recordCommand`:\n * exit code, latency) and its errors, are captured by wrapping\n * `parseAsync`/`parse` — the one place guaranteed to run exactly once\n * per invocation, regardless of success or failure.\n * - Help usage is captured on a **best-effort** basis: yargs handles\n * `--help`/`-h` internally before middleware, `.fail()`, or any\n * command handler ever runs, so this package scans the raw args\n * for help flags *before* calling the real parse.\n *\n * Calls `yargsInstance.exitProcess(false)`: with the default\n * `exitProcess(true)`, yargs calls `process.exit()` itself on failure or\n * `--help`, before this package's wrapped `parseAsync`/`.fail()` ever\n * get a chance to record anything. After instrumenting, your own code\n * must await `parseAsync()` and decide the real process exit itself —\n * see docs/yargs-integration.md.\n */\nexport function instrumentYargs(\n yargsInstance: Argv,\n client: Client,\n options: YargsHookOptions = {},\n): Argv {\n const classify = options.classify ?? (() => ErrorCategory.Unknown)\n const captureHelp = options.captureHelp ?? true\n const captureInvocation = options.captureInvocation ?? true\n const helpFlags = options.helpFlags ?? ['--help', '-h']\n\n yargsInstance.exitProcess(false)\n\n let lastCommandPath = ''\n let rawArgs: readonly string[] = []\n\n if (captureInvocation) {\n yargsInstance.middleware((argv) => {\n const marker = argv as unknown as Record<symbol, boolean>\n if (marker[INVOCATION_MARKER]) return\n marker[INVOCATION_MARKER] = true\n\n lastCommandPath = commandPathFrom(argv)\n client.recordCommandInvocation(lastCommandPath, extractFlagNames(rawArgs))\n }, true)\n }\n\n // yargs only ever routes a failure through `.fail()` for validation\n // errors and *rejected* async handlers — a handler that throws\n // synchronously bypasses `.fail()` entirely and propagates straight\n // out of `parse()`/`parseAsync()`. So `.fail()` here does not itself\n // record anything (that would double-count the async-rejection case\n // and miss the synchronous-throw case); its only jobs are (a) letting\n // a vendor plug in `onFail`, and (b) — critically — converting a\n // validation failure into an actual thrown error. Without this, with\n // `exitProcess(false)`, yargs' default fail behavior swallows the\n // failure and `parseAsync()` would resolve as if nothing went wrong.\n // Error/exit-code/latency recording happens uniformly for every\n // failure mode in the `parseAsync` wrapper's `catch` below instead.\n yargsInstance.fail((msg, err, yi) => {\n if (options.onFail) {\n options.onFail(msg, err, yi)\n return\n }\n process.stderr.write(`${msg}\\n`)\n throw err ?? new Error(msg)\n })\n\n const wrapParse = (original: ParseFn): ParseFn => {\n return function wrapped(this: Argv, ...args: Parameters<ParseFn>) {\n const maybeArgs = args[0]\n rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2)\n client.recordSessionStart()\n\n const helpRequested = helpFlags.some((flag) => rawArgs.includes(flag))\n if (captureHelp && helpRequested) {\n client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs))\n }\n // Help output isn't a command run, same as in the cobra integration.\n const recordCommand = (exitCode: number): void => {\n if (helpRequested) return\n client.recordCommand({\n path: lastCommandPath,\n flagNames: extractFlagNames(rawArgs),\n startMs: start,\n durationMs: Date.now() - start,\n exitCode,\n })\n }\n\n const start = Date.now()\n let resultPromise: Promise<unknown>\n try {\n // A command handler that throws synchronously (as opposed to\n // returning a rejected Promise) makes yargs' own `parse()`\n // throw synchronously too, bypassing `.fail()` entirely — this\n // try/catch normalizes that into a rejected promise so both\n // failure modes go through the same recording path below.\n resultPromise = (original as (...a: unknown[]) => Promise<unknown>).apply(this, args)\n } catch (err) {\n resultPromise = Promise.reject(err)\n }\n return resultPromise\n .then((result: unknown) => {\n recordCommand(0)\n return result\n })\n .catch((err: unknown) => {\n recordCommand(1)\n const classified = classify(err instanceof Error ? err : new Error(String(err)))\n const { category, reason } =\n typeof classified === 'string' ? { category: classified } : classified\n client.recordError(lastCommandPath, category, reason)\n throw err\n })\n } as ParseFn\n }\n\n yargsInstance.parseAsync = wrapParse(yargsInstance.parseAsync.bind(yargsInstance))\n\n return yargsInstance\n}\n\n/**\n * Builds a `Client` (using `cliName`/`cliVersion` you provide — yargs\n * has no built-in fields to read them from) and calls\n * {@link instrumentYargs} to wire up capture.\n *\n * const { client } = instrument(yargsInstance, apiKey, 'mycli', '1.4.0')\n * const argv = await yargsInstance.parseAsync()\n * await client.shutdown()\n */\nexport function instrument(\n yargsInstance: Argv,\n apiKey: string,\n cliName: string,\n cliVersion: string,\n clientOptions: ClientOptions = {},\n hookOptions: YargsHookOptions = {},\n): { client: Client; yargs: Argv } {\n const client = createClient(apiKey, cliName, cliVersion, clientOptions)\n instrumentYargs(yargsInstance, client, hookOptions)\n return { client, yargs: yargsInstance }\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,10 +1,17 @@
1
1
  import { Argv } from 'yargs';
2
- import { ErrorCategory, ClientOptions, Client } from '@getargvio/argvio';
2
+ import { ErrorCategory, ErrorClassification, ClientOptions, Client } from '@getargvio/argvio';
3
3
 
4
4
  /** Configures {@link instrumentYargs}. */
5
5
  interface YargsHookOptions {
6
- /** Maps a caught error to an {@link ErrorCategoryType}. Without it, every error is recorded as `ErrorCategory.Unknown`. */
7
- classify?: (err: Error) => ErrorCategory;
6
+ /**
7
+ * Maps a caught error to an {@link ErrorCategoryType}, or to
8
+ * `{ category, reason }` to also report a fixed-vocabulary reason (e.g.
9
+ * `"no-token"`). The reason must be an identifier chosen in code
10
+ * (`^[a-z0-9][a-z0-9._-]*$`, at most 64 bytes), never error text or user
11
+ * input; one that doesn't match is dropped. Without `classify`, every
12
+ * error is recorded as `ErrorCategory.Unknown` with no reason.
13
+ */
14
+ classify?: (err: Error) => ErrorCategory | ErrorClassification;
8
15
  /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */
9
16
  captureHelp?: boolean;
10
17
  /** Skip the `middleware`-based command-invocation capture. */
@@ -32,7 +39,8 @@ interface YargsHookOptions {
32
39
  * - Command path + flag names (see `Client.recordCommandInvocation`) are
33
40
  * captured via global `middleware(fn, true)` — applied before
34
41
  * validation, so it still runs even if validation later fails.
35
- * - Exit code, latency, and errors are captured by wrapping
42
+ * - The session start, and each completed command (`Client.recordCommand`:
43
+ * exit code, latency) and its errors, are captured by wrapping
36
44
  * `parseAsync`/`parse` — the one place guaranteed to run exactly once
37
45
  * per invocation, regardless of success or failure.
38
46
  * - Help usage is captured on a **best-effort** basis: yargs handles
package/dist/index.d.ts CHANGED
@@ -1,10 +1,17 @@
1
1
  import { Argv } from 'yargs';
2
- import { ErrorCategory, ClientOptions, Client } from '@getargvio/argvio';
2
+ import { ErrorCategory, ErrorClassification, ClientOptions, Client } from '@getargvio/argvio';
3
3
 
4
4
  /** Configures {@link instrumentYargs}. */
5
5
  interface YargsHookOptions {
6
- /** Maps a caught error to an {@link ErrorCategoryType}. Without it, every error is recorded as `ErrorCategory.Unknown`. */
7
- classify?: (err: Error) => ErrorCategory;
6
+ /**
7
+ * Maps a caught error to an {@link ErrorCategoryType}, or to
8
+ * `{ category, reason }` to also report a fixed-vocabulary reason (e.g.
9
+ * `"no-token"`). The reason must be an identifier chosen in code
10
+ * (`^[a-z0-9][a-z0-9._-]*$`, at most 64 bytes), never error text or user
11
+ * input; one that doesn't match is dropped. Without `classify`, every
12
+ * error is recorded as `ErrorCategory.Unknown` with no reason.
13
+ */
14
+ classify?: (err: Error) => ErrorCategory | ErrorClassification;
8
15
  /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */
9
16
  captureHelp?: boolean;
10
17
  /** Skip the `middleware`-based command-invocation capture. */
@@ -32,7 +39,8 @@ interface YargsHookOptions {
32
39
  * - Command path + flag names (see `Client.recordCommandInvocation`) are
33
40
  * captured via global `middleware(fn, true)` — applied before
34
41
  * validation, so it still runs even if validation later fails.
35
- * - Exit code, latency, and errors are captured by wrapping
42
+ * - The session start, and each completed command (`Client.recordCommand`:
43
+ * exit code, latency) and its errors, are captured by wrapping
36
44
  * `parseAsync`/`parse` — the one place guaranteed to run exactly once
37
45
  * per invocation, regardless of success or failure.
38
46
  * - Help usage is captured on a **best-effort** basis: yargs handles
package/dist/index.js CHANGED
@@ -54,9 +54,21 @@ function instrumentYargs(yargsInstance, client, options = {}) {
54
54
  return function wrapped(...args) {
55
55
  const maybeArgs = args[0];
56
56
  rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2);
57
- if (captureHelp && helpFlags.some((flag) => rawArgs.includes(flag))) {
57
+ client.recordSessionStart();
58
+ const helpRequested = helpFlags.some((flag) => rawArgs.includes(flag));
59
+ if (captureHelp && helpRequested) {
58
60
  client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs));
59
61
  }
62
+ const recordCommand = (exitCode) => {
63
+ if (helpRequested) return;
64
+ client.recordCommand({
65
+ path: lastCommandPath,
66
+ flagNames: extractFlagNames(rawArgs),
67
+ startMs: start,
68
+ durationMs: Date.now() - start,
69
+ exitCode
70
+ });
71
+ };
60
72
  const start = Date.now();
61
73
  let resultPromise;
62
74
  try {
@@ -65,16 +77,13 @@ function instrumentYargs(yargsInstance, client, options = {}) {
65
77
  resultPromise = Promise.reject(err);
66
78
  }
67
79
  return resultPromise.then((result) => {
68
- client.recordExitCode(lastCommandPath, 0);
69
- client.recordLatency(lastCommandPath, Date.now() - start);
80
+ recordCommand(0);
70
81
  return result;
71
82
  }).catch((err) => {
72
- client.recordExitCode(lastCommandPath, 1);
73
- client.recordLatency(lastCommandPath, Date.now() - start);
74
- client.recordError(
75
- lastCommandPath,
76
- classify(err instanceof Error ? err : new Error(String(err)))
77
- );
83
+ recordCommand(1);
84
+ const classified = classify(err instanceof Error ? err : new Error(String(err)));
85
+ const { category, reason } = typeof classified === "string" ? { category: classified } : classified;
86
+ client.recordError(lastCommandPath, category, reason);
78
87
  throw err;
79
88
  });
80
89
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Argv, Arguments } from 'yargs'\n\nimport type { Client, ClientOptions } from '@getargvio/argvio'\nimport { createClient, ErrorCategory } from '@getargvio/argvio'\nimport type { ErrorCategory as ErrorCategoryType } from '@getargvio/argvio'\n\nconst INVOCATION_MARKER = Symbol('argvio.recordedInvocation')\n\n/** Configures {@link instrumentYargs}. */\nexport interface YargsHookOptions {\n /** Maps a caught error to an {@link ErrorCategoryType}. Without it, every error is recorded as `ErrorCategory.Unknown`. */\n classify?: (err: Error) => ErrorCategoryType\n /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */\n captureHelp?: boolean\n /** Skip the `middleware`-based command-invocation capture. */\n captureInvocation?: boolean\n /**\n * Called from inside the installed `.fail()` handler instead of this\n * package's default print-and-rethrow behavior. Use this if you\n * already have your own `.fail()` logic you want preserved —\n * `instrumentYargs` cannot chain with a pre-existing `.fail()` handler\n * the way it can with `middleware` (see docs/yargs-integration.md).\n * Error/exit-code recording itself always happens uniformly in the\n * wrapped `parseAsync`, regardless of whether `onFail` is set. Note\n * `.fail()` only fires for validation errors and *rejected* async\n * handlers — for the async-rejection case specifically, yargs invokes\n * `.fail()` purely as a side effect and swallows anything it throws,\n * so `onFail` cannot change whether the original rejection propagates.\n */\n onFail?: (msg: string, err: Error | undefined, yargsInstance: Argv) => void\n /** Flag tokens treated as a help request for the best-effort help capture. Defaults to `['--help', '-h']`. */\n helpFlags?: readonly string[]\n}\n\nfunction extractFlagNames(rawArgs: readonly string[]): string[] {\n const names = new Set<string>()\n for (const token of rawArgs) {\n if (!token.startsWith('-') || token === '-' || token === '--') continue\n const name = token.replace(/^--?/, '').split('=')[0]\n if (name) names.add(name)\n }\n return [...names]\n}\n\nfunction commandPathFrom(argv: Arguments): string {\n const script = typeof argv['$0'] === 'string' ? argv['$0'] : ''\n const segments = argv._.map((segment) => String(segment))\n return [script, ...segments].filter(Boolean).join(' ')\n}\n\n/**\n * Best-effort command path guess before parsing has happened (used for\n * help capture, which fires before yargs has resolved a command path\n * of its own). No CLI name prefix — the builder-side `Argv` has no\n * public getter for `scriptName`, only a setter.\n */\nfunction guessCommandPathFromRawArgs(rawArgs: readonly string[]): string {\n const segments: string[] = []\n for (const token of rawArgs) {\n if (token.startsWith('-')) break\n segments.push(token)\n }\n return segments.join(' ')\n}\n\ntype ParseFn = Argv['parseAsync']\n\n/**\n * Wires automatic telemetry capture into a yargs instance:\n *\n * - Command path + flag names (see `Client.recordCommandInvocation`) are\n * captured via global `middleware(fn, true)` — applied before\n * validation, so it still runs even if validation later fails.\n * - Exit code, latency, and errors are captured by wrapping\n * `parseAsync`/`parse` — the one place guaranteed to run exactly once\n * per invocation, regardless of success or failure.\n * - Help usage is captured on a **best-effort** basis: yargs handles\n * `--help`/`-h` internally before middleware, `.fail()`, or any\n * command handler ever runs, so this package scans the raw args\n * for help flags *before* calling the real parse.\n *\n * Calls `yargsInstance.exitProcess(false)`: with the default\n * `exitProcess(true)`, yargs calls `process.exit()` itself on failure or\n * `--help`, before this package's wrapped `parseAsync`/`.fail()` ever\n * get a chance to record anything. After instrumenting, your own code\n * must await `parseAsync()` and decide the real process exit itself —\n * see docs/yargs-integration.md.\n */\nexport function instrumentYargs(\n yargsInstance: Argv,\n client: Client,\n options: YargsHookOptions = {},\n): Argv {\n const classify = options.classify ?? (() => ErrorCategory.Unknown)\n const captureHelp = options.captureHelp ?? true\n const captureInvocation = options.captureInvocation ?? true\n const helpFlags = options.helpFlags ?? ['--help', '-h']\n\n yargsInstance.exitProcess(false)\n\n let lastCommandPath = ''\n let rawArgs: readonly string[] = []\n\n if (captureInvocation) {\n yargsInstance.middleware((argv) => {\n const marker = argv as unknown as Record<symbol, boolean>\n if (marker[INVOCATION_MARKER]) return\n marker[INVOCATION_MARKER] = true\n\n lastCommandPath = commandPathFrom(argv)\n client.recordCommandInvocation(lastCommandPath, extractFlagNames(rawArgs))\n }, true)\n }\n\n // yargs only ever routes a failure through `.fail()` for validation\n // errors and *rejected* async handlers — a handler that throws\n // synchronously bypasses `.fail()` entirely and propagates straight\n // out of `parse()`/`parseAsync()`. So `.fail()` here does not itself\n // record anything (that would double-count the async-rejection case\n // and miss the synchronous-throw case); its only jobs are (a) letting\n // a vendor plug in `onFail`, and (b) — critically — converting a\n // validation failure into an actual thrown error. Without this, with\n // `exitProcess(false)`, yargs' default fail behavior swallows the\n // failure and `parseAsync()` would resolve as if nothing went wrong.\n // Error/exit-code/latency recording happens uniformly for every\n // failure mode in the `parseAsync` wrapper's `catch` below instead.\n yargsInstance.fail((msg, err, yi) => {\n if (options.onFail) {\n options.onFail(msg, err, yi)\n return\n }\n process.stderr.write(`${msg}\\n`)\n throw err ?? new Error(msg)\n })\n\n const wrapParse = (original: ParseFn): ParseFn => {\n return function wrapped(this: Argv, ...args: Parameters<ParseFn>) {\n const maybeArgs = args[0]\n rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2)\n\n if (captureHelp && helpFlags.some((flag) => rawArgs.includes(flag))) {\n client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs))\n }\n\n const start = Date.now()\n let resultPromise: Promise<unknown>\n try {\n // A command handler that throws synchronously (as opposed to\n // returning a rejected Promise) makes yargs' own `parse()`\n // throw synchronously too, bypassing `.fail()` entirely — this\n // try/catch normalizes that into a rejected promise so both\n // failure modes go through the same recording path below.\n resultPromise = (original as (...a: unknown[]) => Promise<unknown>).apply(this, args)\n } catch (err) {\n resultPromise = Promise.reject(err)\n }\n return resultPromise\n .then((result: unknown) => {\n client.recordExitCode(lastCommandPath, 0)\n client.recordLatency(lastCommandPath, Date.now() - start)\n return result\n })\n .catch((err: unknown) => {\n client.recordExitCode(lastCommandPath, 1)\n client.recordLatency(lastCommandPath, Date.now() - start)\n client.recordError(\n lastCommandPath,\n classify(err instanceof Error ? err : new Error(String(err))),\n )\n throw err\n })\n } as ParseFn\n }\n\n yargsInstance.parseAsync = wrapParse(yargsInstance.parseAsync.bind(yargsInstance))\n\n return yargsInstance\n}\n\n/**\n * Builds a `Client` (using `cliName`/`cliVersion` you provide — yargs\n * has no built-in fields to read them from) and calls\n * {@link instrumentYargs} to wire up capture.\n *\n * const { client } = instrument(yargsInstance, apiKey, 'mycli', '1.4.0')\n * const argv = await yargsInstance.parseAsync()\n * await client.shutdown()\n */\nexport function instrument(\n yargsInstance: Argv,\n apiKey: string,\n cliName: string,\n cliVersion: string,\n clientOptions: ClientOptions = {},\n hookOptions: YargsHookOptions = {},\n): { client: Client; yargs: Argv } {\n const client = createClient(apiKey, cliName, cliVersion, clientOptions)\n instrumentYargs(yargsInstance, client, hookOptions)\n return { client, yargs: yargsInstance }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Argv, Arguments } from 'yargs'\n\nimport type { Client, ClientOptions } from '@getargvio/argvio'\nimport { createClient, ErrorCategory } from '@getargvio/argvio'\nimport type { ErrorCategory as ErrorCategoryType, ErrorClassification } from '@getargvio/argvio'\n\nconst INVOCATION_MARKER = Symbol('argvio.recordedInvocation')\n\n/** Configures {@link instrumentYargs}. */\nexport interface YargsHookOptions {\n /**\n * Maps a caught error to an {@link ErrorCategoryType}, or to\n * `{ category, reason }` to also report a fixed-vocabulary reason (e.g.\n * `\"no-token\"`). The reason must be an identifier chosen in code\n * (`^[a-z0-9][a-z0-9._-]*$`, at most 64 bytes), never error text or user\n * input; one that doesn't match is dropped. Without `classify`, every\n * error is recorded as `ErrorCategory.Unknown` with no reason.\n */\n classify?: (err: Error) => ErrorCategoryType | ErrorClassification\n /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */\n captureHelp?: boolean\n /** Skip the `middleware`-based command-invocation capture. */\n captureInvocation?: boolean\n /**\n * Called from inside the installed `.fail()` handler instead of this\n * package's default print-and-rethrow behavior. Use this if you\n * already have your own `.fail()` logic you want preserved —\n * `instrumentYargs` cannot chain with a pre-existing `.fail()` handler\n * the way it can with `middleware` (see docs/yargs-integration.md).\n * Error/exit-code recording itself always happens uniformly in the\n * wrapped `parseAsync`, regardless of whether `onFail` is set. Note\n * `.fail()` only fires for validation errors and *rejected* async\n * handlers — for the async-rejection case specifically, yargs invokes\n * `.fail()` purely as a side effect and swallows anything it throws,\n * so `onFail` cannot change whether the original rejection propagates.\n */\n onFail?: (msg: string, err: Error | undefined, yargsInstance: Argv) => void\n /** Flag tokens treated as a help request for the best-effort help capture. Defaults to `['--help', '-h']`. */\n helpFlags?: readonly string[]\n}\n\nfunction extractFlagNames(rawArgs: readonly string[]): string[] {\n const names = new Set<string>()\n for (const token of rawArgs) {\n if (!token.startsWith('-') || token === '-' || token === '--') continue\n const name = token.replace(/^--?/, '').split('=')[0]\n if (name) names.add(name)\n }\n return [...names]\n}\n\nfunction commandPathFrom(argv: Arguments): string {\n const script = typeof argv['$0'] === 'string' ? argv['$0'] : ''\n const segments = argv._.map((segment) => String(segment))\n return [script, ...segments].filter(Boolean).join(' ')\n}\n\n/**\n * Best-effort command path guess before parsing has happened (used for\n * help capture, which fires before yargs has resolved a command path\n * of its own). No CLI name prefix — the builder-side `Argv` has no\n * public getter for `scriptName`, only a setter.\n */\nfunction guessCommandPathFromRawArgs(rawArgs: readonly string[]): string {\n const segments: string[] = []\n for (const token of rawArgs) {\n if (token.startsWith('-')) break\n segments.push(token)\n }\n return segments.join(' ')\n}\n\ntype ParseFn = Argv['parseAsync']\n\n/**\n * Wires automatic telemetry capture into a yargs instance:\n *\n * - Command path + flag names (see `Client.recordCommandInvocation`) are\n * captured via global `middleware(fn, true)` — applied before\n * validation, so it still runs even if validation later fails.\n * - The session start, and each completed command (`Client.recordCommand`:\n * exit code, latency) and its errors, are captured by wrapping\n * `parseAsync`/`parse` — the one place guaranteed to run exactly once\n * per invocation, regardless of success or failure.\n * - Help usage is captured on a **best-effort** basis: yargs handles\n * `--help`/`-h` internally before middleware, `.fail()`, or any\n * command handler ever runs, so this package scans the raw args\n * for help flags *before* calling the real parse.\n *\n * Calls `yargsInstance.exitProcess(false)`: with the default\n * `exitProcess(true)`, yargs calls `process.exit()` itself on failure or\n * `--help`, before this package's wrapped `parseAsync`/`.fail()` ever\n * get a chance to record anything. After instrumenting, your own code\n * must await `parseAsync()` and decide the real process exit itself —\n * see docs/yargs-integration.md.\n */\nexport function instrumentYargs(\n yargsInstance: Argv,\n client: Client,\n options: YargsHookOptions = {},\n): Argv {\n const classify = options.classify ?? (() => ErrorCategory.Unknown)\n const captureHelp = options.captureHelp ?? true\n const captureInvocation = options.captureInvocation ?? true\n const helpFlags = options.helpFlags ?? ['--help', '-h']\n\n yargsInstance.exitProcess(false)\n\n let lastCommandPath = ''\n let rawArgs: readonly string[] = []\n\n if (captureInvocation) {\n yargsInstance.middleware((argv) => {\n const marker = argv as unknown as Record<symbol, boolean>\n if (marker[INVOCATION_MARKER]) return\n marker[INVOCATION_MARKER] = true\n\n lastCommandPath = commandPathFrom(argv)\n client.recordCommandInvocation(lastCommandPath, extractFlagNames(rawArgs))\n }, true)\n }\n\n // yargs only ever routes a failure through `.fail()` for validation\n // errors and *rejected* async handlers — a handler that throws\n // synchronously bypasses `.fail()` entirely and propagates straight\n // out of `parse()`/`parseAsync()`. So `.fail()` here does not itself\n // record anything (that would double-count the async-rejection case\n // and miss the synchronous-throw case); its only jobs are (a) letting\n // a vendor plug in `onFail`, and (b) — critically — converting a\n // validation failure into an actual thrown error. Without this, with\n // `exitProcess(false)`, yargs' default fail behavior swallows the\n // failure and `parseAsync()` would resolve as if nothing went wrong.\n // Error/exit-code/latency recording happens uniformly for every\n // failure mode in the `parseAsync` wrapper's `catch` below instead.\n yargsInstance.fail((msg, err, yi) => {\n if (options.onFail) {\n options.onFail(msg, err, yi)\n return\n }\n process.stderr.write(`${msg}\\n`)\n throw err ?? new Error(msg)\n })\n\n const wrapParse = (original: ParseFn): ParseFn => {\n return function wrapped(this: Argv, ...args: Parameters<ParseFn>) {\n const maybeArgs = args[0]\n rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2)\n client.recordSessionStart()\n\n const helpRequested = helpFlags.some((flag) => rawArgs.includes(flag))\n if (captureHelp && helpRequested) {\n client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs))\n }\n // Help output isn't a command run, same as in the cobra integration.\n const recordCommand = (exitCode: number): void => {\n if (helpRequested) return\n client.recordCommand({\n path: lastCommandPath,\n flagNames: extractFlagNames(rawArgs),\n startMs: start,\n durationMs: Date.now() - start,\n exitCode,\n })\n }\n\n const start = Date.now()\n let resultPromise: Promise<unknown>\n try {\n // A command handler that throws synchronously (as opposed to\n // returning a rejected Promise) makes yargs' own `parse()`\n // throw synchronously too, bypassing `.fail()` entirely — this\n // try/catch normalizes that into a rejected promise so both\n // failure modes go through the same recording path below.\n resultPromise = (original as (...a: unknown[]) => Promise<unknown>).apply(this, args)\n } catch (err) {\n resultPromise = Promise.reject(err)\n }\n return resultPromise\n .then((result: unknown) => {\n recordCommand(0)\n return result\n })\n .catch((err: unknown) => {\n recordCommand(1)\n const classified = classify(err instanceof Error ? err : new Error(String(err)))\n const { category, reason } =\n typeof classified === 'string' ? { category: classified } : classified\n client.recordError(lastCommandPath, category, reason)\n throw err\n })\n } as ParseFn\n }\n\n yargsInstance.parseAsync = wrapParse(yargsInstance.parseAsync.bind(yargsInstance))\n\n return yargsInstance\n}\n\n/**\n * Builds a `Client` (using `cliName`/`cliVersion` you provide — yargs\n * has no built-in fields to read them from) and calls\n * {@link instrumentYargs} to wire up capture.\n *\n * const { client } = instrument(yargsInstance, apiKey, 'mycli', '1.4.0')\n * const argv = await yargsInstance.parseAsync()\n * await client.shutdown()\n */\nexport function instrument(\n yargsInstance: Argv,\n apiKey: string,\n cliName: string,\n cliVersion: string,\n clientOptions: ClientOptions = {},\n hookOptions: YargsHookOptions = {},\n): { client: Client; yargs: Argv } {\n const client = createClient(apiKey, cliName, cliVersion, clientOptions)\n instrumentYargs(yargsInstance, client, hookOptions)\n return { client, yargs: yargsInstance }\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@getargvio/yargs",
3
3
  "description": "Auto-instruments a yargs command tree with @getargvio/argvio CLI usage telemetry",
4
- "version": "0.1.0",
4
+ "version": "0.4.0",
5
5
  "author": "getargvio",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -42,17 +42,11 @@
42
42
  "engines": {
43
43
  "node": ">=22.0.0"
44
44
  },
45
- "scripts": {
46
- "build": "tsup",
47
- "typecheck": "tsc --noEmit",
48
- "test": "vitest run",
49
- "prepublishOnly": "npm run build"
50
- },
51
45
  "peerDependencies": {
52
46
  "yargs": ">=17.0.0"
53
47
  },
54
48
  "dependencies": {
55
- "@getargvio/argvio": "^0.1.0"
49
+ "@getargvio/argvio": "0.5.0"
56
50
  },
57
51
  "devDependencies": {
58
52
  "@types/node": "^24",
@@ -60,5 +54,10 @@
60
54
  "typescript": "^5",
61
55
  "vitest": "^4.1.11",
62
56
  "yargs": "^18"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup",
60
+ "typecheck": "tsc --noEmit",
61
+ "test": "vitest run"
63
62
  }
64
- }
63
+ }