@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 +21 -0
- package/README.md +37 -0
- package/dist/index.cjs +96 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|