@getargvio/yargs 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 getargvio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @getargvio/yargs
2
+
3
+ Auto-instruments a [yargs](https://github.com/yargs/yargs) command tree
4
+ with [`@getargvio/argvio`](https://www.npmjs.com/package/@getargvio/argvio)
5
+ CLI usage telemetry.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @getargvio/yargs
11
+ ```
12
+
13
+ `yargs` is a peer dependency — bring your own (`>=17.0.0`).
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import yargs from 'yargs'
19
+ import { hideBin } from 'yargs/helpers'
20
+ import { instrument } from '@getargvio/yargs'
21
+
22
+ const y = yargs(hideBin(process.argv)).scriptName('mycli')
23
+ // ... add commands as usual ...
24
+
25
+ const { client } = instrument(y, process.env.MYCLI_ARGVIO_KEY ?? '', 'mycli', '1.4.0')
26
+
27
+ const argv = await y.parseAsync()
28
+ await client.shutdown(3000)
29
+ ```
30
+
31
+ See [docs/yargs-integration.md](docs/yargs-integration.md) for exactly
32
+ what's captured automatically, the `exitProcess(false)` requirement,
33
+ and why `.fail()` behaves differently here than you might expect.
34
+
35
+ ## License
36
+
37
+ MIT — see [LICENSE](../../LICENSE).
package/dist/index.cjs ADDED
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ var argvio = require('@getargvio/argvio');
4
+
5
+ // src/index.ts
6
+ var INVOCATION_MARKER = /* @__PURE__ */ Symbol("argvio.recordedInvocation");
7
+ function extractFlagNames(rawArgs) {
8
+ const names = /* @__PURE__ */ new Set();
9
+ for (const token of rawArgs) {
10
+ if (!token.startsWith("-") || token === "-" || token === "--") continue;
11
+ const name = token.replace(/^--?/, "").split("=")[0];
12
+ if (name) names.add(name);
13
+ }
14
+ return [...names];
15
+ }
16
+ function commandPathFrom(argv) {
17
+ const script = typeof argv["$0"] === "string" ? argv["$0"] : "";
18
+ const segments = argv._.map((segment) => String(segment));
19
+ return [script, ...segments].filter(Boolean).join(" ");
20
+ }
21
+ function guessCommandPathFromRawArgs(rawArgs) {
22
+ const segments = [];
23
+ for (const token of rawArgs) {
24
+ if (token.startsWith("-")) break;
25
+ segments.push(token);
26
+ }
27
+ return segments.join(" ");
28
+ }
29
+ function instrumentYargs(yargsInstance, client, options = {}) {
30
+ const classify = options.classify ?? (() => argvio.ErrorCategory.Unknown);
31
+ const captureHelp = options.captureHelp ?? true;
32
+ const captureInvocation = options.captureInvocation ?? true;
33
+ const helpFlags = options.helpFlags ?? ["--help", "-h"];
34
+ yargsInstance.exitProcess(false);
35
+ let lastCommandPath = "";
36
+ let rawArgs = [];
37
+ if (captureInvocation) {
38
+ yargsInstance.middleware((argv) => {
39
+ const marker = argv;
40
+ if (marker[INVOCATION_MARKER]) return;
41
+ marker[INVOCATION_MARKER] = true;
42
+ lastCommandPath = commandPathFrom(argv);
43
+ client.recordCommandInvocation(lastCommandPath, extractFlagNames(rawArgs));
44
+ }, true);
45
+ }
46
+ yargsInstance.fail((msg, err, yi) => {
47
+ if (options.onFail) {
48
+ options.onFail(msg, err, yi);
49
+ return;
50
+ }
51
+ process.stderr.write(`${msg}
52
+ `);
53
+ throw err ?? new Error(msg);
54
+ });
55
+ const wrapParse = (original) => {
56
+ return function wrapped(...args) {
57
+ const maybeArgs = args[0];
58
+ rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2);
59
+ if (captureHelp && helpFlags.some((flag) => rawArgs.includes(flag))) {
60
+ client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs));
61
+ }
62
+ const start = Date.now();
63
+ let resultPromise;
64
+ try {
65
+ resultPromise = original.apply(this, args);
66
+ } catch (err) {
67
+ resultPromise = Promise.reject(err);
68
+ }
69
+ return resultPromise.then((result) => {
70
+ client.recordExitCode(lastCommandPath, 0);
71
+ client.recordLatency(lastCommandPath, Date.now() - start);
72
+ return result;
73
+ }).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
+ );
80
+ throw err;
81
+ });
82
+ };
83
+ };
84
+ yargsInstance.parseAsync = wrapParse(yargsInstance.parseAsync.bind(yargsInstance));
85
+ return yargsInstance;
86
+ }
87
+ function instrument(yargsInstance, apiKey, cliName, cliVersion, clientOptions = {}, hookOptions = {}) {
88
+ const client = argvio.createClient(apiKey, cliName, cliVersion, clientOptions);
89
+ instrumentYargs(yargsInstance, client, hookOptions);
90
+ return { client, yargs: yargsInstance };
91
+ }
92
+
93
+ exports.instrument = instrument;
94
+ exports.instrumentYargs = instrumentYargs;
95
+ //# sourceMappingURL=index.cjs.map
96
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,65 @@
1
+ import { Argv } from 'yargs';
2
+ import { ErrorCategory, ClientOptions, Client } from '@getargvio/argvio';
3
+
4
+ /** Configures {@link instrumentYargs}. */
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;
8
+ /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */
9
+ captureHelp?: boolean;
10
+ /** Skip the `middleware`-based command-invocation capture. */
11
+ captureInvocation?: boolean;
12
+ /**
13
+ * Called from inside the installed `.fail()` handler instead of this
14
+ * package's default print-and-rethrow behavior. Use this if you
15
+ * already have your own `.fail()` logic you want preserved —
16
+ * `instrumentYargs` cannot chain with a pre-existing `.fail()` handler
17
+ * the way it can with `middleware` (see docs/yargs-integration.md).
18
+ * Error/exit-code recording itself always happens uniformly in the
19
+ * wrapped `parseAsync`, regardless of whether `onFail` is set. Note
20
+ * `.fail()` only fires for validation errors and *rejected* async
21
+ * handlers — for the async-rejection case specifically, yargs invokes
22
+ * `.fail()` purely as a side effect and swallows anything it throws,
23
+ * so `onFail` cannot change whether the original rejection propagates.
24
+ */
25
+ onFail?: (msg: string, err: Error | undefined, yargsInstance: Argv) => void;
26
+ /** Flag tokens treated as a help request for the best-effort help capture. Defaults to `['--help', '-h']`. */
27
+ helpFlags?: readonly string[];
28
+ }
29
+ /**
30
+ * Wires automatic telemetry capture into a yargs instance:
31
+ *
32
+ * - Command path + flag names (see `Client.recordCommandInvocation`) are
33
+ * captured via global `middleware(fn, true)` — applied before
34
+ * validation, so it still runs even if validation later fails.
35
+ * - Exit code, latency, and errors are captured by wrapping
36
+ * `parseAsync`/`parse` — the one place guaranteed to run exactly once
37
+ * per invocation, regardless of success or failure.
38
+ * - Help usage is captured on a **best-effort** basis: yargs handles
39
+ * `--help`/`-h` internally before middleware, `.fail()`, or any
40
+ * command handler ever runs, so this package scans the raw args
41
+ * for help flags *before* calling the real parse.
42
+ *
43
+ * Calls `yargsInstance.exitProcess(false)`: with the default
44
+ * `exitProcess(true)`, yargs calls `process.exit()` itself on failure or
45
+ * `--help`, before this package's wrapped `parseAsync`/`.fail()` ever
46
+ * get a chance to record anything. After instrumenting, your own code
47
+ * must await `parseAsync()` and decide the real process exit itself —
48
+ * see docs/yargs-integration.md.
49
+ */
50
+ declare function instrumentYargs(yargsInstance: Argv, client: Client, options?: YargsHookOptions): Argv;
51
+ /**
52
+ * Builds a `Client` (using `cliName`/`cliVersion` you provide — yargs
53
+ * has no built-in fields to read them from) and calls
54
+ * {@link instrumentYargs} to wire up capture.
55
+ *
56
+ * const { client } = instrument(yargsInstance, apiKey, 'mycli', '1.4.0')
57
+ * const argv = await yargsInstance.parseAsync()
58
+ * await client.shutdown()
59
+ */
60
+ declare function instrument(yargsInstance: Argv, apiKey: string, cliName: string, cliVersion: string, clientOptions?: ClientOptions, hookOptions?: YargsHookOptions): {
61
+ client: Client;
62
+ yargs: Argv;
63
+ };
64
+
65
+ export { type YargsHookOptions, instrument, instrumentYargs };
@@ -0,0 +1,65 @@
1
+ import { Argv } from 'yargs';
2
+ import { ErrorCategory, ClientOptions, Client } from '@getargvio/argvio';
3
+
4
+ /** Configures {@link instrumentYargs}. */
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;
8
+ /** Skip the best-effort help-flag capture (see docs/yargs-integration.md for why it's best-effort). */
9
+ captureHelp?: boolean;
10
+ /** Skip the `middleware`-based command-invocation capture. */
11
+ captureInvocation?: boolean;
12
+ /**
13
+ * Called from inside the installed `.fail()` handler instead of this
14
+ * package's default print-and-rethrow behavior. Use this if you
15
+ * already have your own `.fail()` logic you want preserved —
16
+ * `instrumentYargs` cannot chain with a pre-existing `.fail()` handler
17
+ * the way it can with `middleware` (see docs/yargs-integration.md).
18
+ * Error/exit-code recording itself always happens uniformly in the
19
+ * wrapped `parseAsync`, regardless of whether `onFail` is set. Note
20
+ * `.fail()` only fires for validation errors and *rejected* async
21
+ * handlers — for the async-rejection case specifically, yargs invokes
22
+ * `.fail()` purely as a side effect and swallows anything it throws,
23
+ * so `onFail` cannot change whether the original rejection propagates.
24
+ */
25
+ onFail?: (msg: string, err: Error | undefined, yargsInstance: Argv) => void;
26
+ /** Flag tokens treated as a help request for the best-effort help capture. Defaults to `['--help', '-h']`. */
27
+ helpFlags?: readonly string[];
28
+ }
29
+ /**
30
+ * Wires automatic telemetry capture into a yargs instance:
31
+ *
32
+ * - Command path + flag names (see `Client.recordCommandInvocation`) are
33
+ * captured via global `middleware(fn, true)` — applied before
34
+ * validation, so it still runs even if validation later fails.
35
+ * - Exit code, latency, and errors are captured by wrapping
36
+ * `parseAsync`/`parse` — the one place guaranteed to run exactly once
37
+ * per invocation, regardless of success or failure.
38
+ * - Help usage is captured on a **best-effort** basis: yargs handles
39
+ * `--help`/`-h` internally before middleware, `.fail()`, or any
40
+ * command handler ever runs, so this package scans the raw args
41
+ * for help flags *before* calling the real parse.
42
+ *
43
+ * Calls `yargsInstance.exitProcess(false)`: with the default
44
+ * `exitProcess(true)`, yargs calls `process.exit()` itself on failure or
45
+ * `--help`, before this package's wrapped `parseAsync`/`.fail()` ever
46
+ * get a chance to record anything. After instrumenting, your own code
47
+ * must await `parseAsync()` and decide the real process exit itself —
48
+ * see docs/yargs-integration.md.
49
+ */
50
+ declare function instrumentYargs(yargsInstance: Argv, client: Client, options?: YargsHookOptions): Argv;
51
+ /**
52
+ * Builds a `Client` (using `cliName`/`cliVersion` you provide — yargs
53
+ * has no built-in fields to read them from) and calls
54
+ * {@link instrumentYargs} to wire up capture.
55
+ *
56
+ * const { client } = instrument(yargsInstance, apiKey, 'mycli', '1.4.0')
57
+ * const argv = await yargsInstance.parseAsync()
58
+ * await client.shutdown()
59
+ */
60
+ declare function instrument(yargsInstance: Argv, apiKey: string, cliName: string, cliVersion: string, clientOptions?: ClientOptions, hookOptions?: YargsHookOptions): {
61
+ client: Client;
62
+ yargs: Argv;
63
+ };
64
+
65
+ export { type YargsHookOptions, instrument, instrumentYargs };
package/dist/index.js ADDED
@@ -0,0 +1,93 @@
1
+ import { ErrorCategory, createClient } from '@getargvio/argvio';
2
+
3
+ // src/index.ts
4
+ var INVOCATION_MARKER = /* @__PURE__ */ Symbol("argvio.recordedInvocation");
5
+ function extractFlagNames(rawArgs) {
6
+ const names = /* @__PURE__ */ new Set();
7
+ for (const token of rawArgs) {
8
+ if (!token.startsWith("-") || token === "-" || token === "--") continue;
9
+ const name = token.replace(/^--?/, "").split("=")[0];
10
+ if (name) names.add(name);
11
+ }
12
+ return [...names];
13
+ }
14
+ function commandPathFrom(argv) {
15
+ const script = typeof argv["$0"] === "string" ? argv["$0"] : "";
16
+ const segments = argv._.map((segment) => String(segment));
17
+ return [script, ...segments].filter(Boolean).join(" ");
18
+ }
19
+ function guessCommandPathFromRawArgs(rawArgs) {
20
+ const segments = [];
21
+ for (const token of rawArgs) {
22
+ if (token.startsWith("-")) break;
23
+ segments.push(token);
24
+ }
25
+ return segments.join(" ");
26
+ }
27
+ function instrumentYargs(yargsInstance, client, options = {}) {
28
+ const classify = options.classify ?? (() => ErrorCategory.Unknown);
29
+ const captureHelp = options.captureHelp ?? true;
30
+ const captureInvocation = options.captureInvocation ?? true;
31
+ const helpFlags = options.helpFlags ?? ["--help", "-h"];
32
+ yargsInstance.exitProcess(false);
33
+ let lastCommandPath = "";
34
+ let rawArgs = [];
35
+ if (captureInvocation) {
36
+ yargsInstance.middleware((argv) => {
37
+ const marker = argv;
38
+ if (marker[INVOCATION_MARKER]) return;
39
+ marker[INVOCATION_MARKER] = true;
40
+ lastCommandPath = commandPathFrom(argv);
41
+ client.recordCommandInvocation(lastCommandPath, extractFlagNames(rawArgs));
42
+ }, true);
43
+ }
44
+ yargsInstance.fail((msg, err, yi) => {
45
+ if (options.onFail) {
46
+ options.onFail(msg, err, yi);
47
+ return;
48
+ }
49
+ process.stderr.write(`${msg}
50
+ `);
51
+ throw err ?? new Error(msg);
52
+ });
53
+ const wrapParse = (original) => {
54
+ return function wrapped(...args) {
55
+ const maybeArgs = args[0];
56
+ rawArgs = Array.isArray(maybeArgs) ? maybeArgs.map(String) : process.argv.slice(2);
57
+ if (captureHelp && helpFlags.some((flag) => rawArgs.includes(flag))) {
58
+ client.recordHelpFlagUsage(guessCommandPathFromRawArgs(rawArgs));
59
+ }
60
+ const start = Date.now();
61
+ let resultPromise;
62
+ try {
63
+ resultPromise = original.apply(this, args);
64
+ } catch (err) {
65
+ resultPromise = Promise.reject(err);
66
+ }
67
+ return resultPromise.then((result) => {
68
+ client.recordExitCode(lastCommandPath, 0);
69
+ client.recordLatency(lastCommandPath, Date.now() - start);
70
+ return result;
71
+ }).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
+ );
78
+ throw err;
79
+ });
80
+ };
81
+ };
82
+ yargsInstance.parseAsync = wrapParse(yargsInstance.parseAsync.bind(yargsInstance));
83
+ return yargsInstance;
84
+ }
85
+ function instrument(yargsInstance, apiKey, cliName, cliVersion, clientOptions = {}, hookOptions = {}) {
86
+ const client = createClient(apiKey, cliName, cliVersion, clientOptions);
87
+ instrumentYargs(yargsInstance, client, hookOptions);
88
+ return { client, yargs: yargsInstance };
89
+ }
90
+
91
+ export { instrument, instrumentYargs };
92
+ //# sourceMappingURL=index.js.map
93
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@getargvio/yargs",
3
+ "description": "Auto-instruments a yargs command tree with @getargvio/argvio CLI usage telemetry",
4
+ "version": "0.1.0",
5
+ "author": "getargvio",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/getargvio/argvio-node.git",
11
+ "directory": "packages/yargs"
12
+ },
13
+ "homepage": "https://github.com/getargvio/argvio-node/tree/main/packages/yargs",
14
+ "bugs": {
15
+ "url": "https://github.com/getargvio/argvio-node/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "yargs",
22
+ "cli",
23
+ "telemetry",
24
+ "opentelemetry",
25
+ "analytics"
26
+ ],
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "types": "./dist/index.d.ts",
37
+ "files": [
38
+ "dist/",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "engines": {
43
+ "node": ">=22.0.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "typecheck": "tsc --noEmit",
48
+ "test": "vitest run",
49
+ "prepublishOnly": "npm run build"
50
+ },
51
+ "peerDependencies": {
52
+ "yargs": ">=17.0.0"
53
+ },
54
+ "dependencies": {
55
+ "@getargvio/argvio": "^0.1.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^24",
59
+ "@types/yargs": "^17",
60
+ "typescript": "^5",
61
+ "vitest": "^4.1.11",
62
+ "yargs": "^18"
63
+ }
64
+ }