@ishlabs/cli 0.17.5 → 0.17.7

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.
@@ -10,7 +10,7 @@ import { outputError, setVerbose, setFields, setGetField } from "./output.js";
10
10
  import { setColorsEnabled, colorsEnabled } from "./colors.js";
11
11
  import { loadConfig } from "../config.js";
12
12
  import { resolveId } from "./alias-store.js";
13
- import { exitWithFlush } from "./observability.js";
13
+ import { exitWithFlush, withCliSpan } from "./observability.js";
14
14
  function isSimulatable(p) {
15
15
  return Boolean(p.simulation_config_id) || Boolean(p.simulation_config);
16
16
  }
@@ -406,16 +406,32 @@ function applyGlobals(globals) {
406
406
  setColorsEnabled(globals.color);
407
407
  _activeWorkspace = globals.workspace;
408
408
  }
409
+ /**
410
+ * Build the dotted command path (`workspace.list` rather than just `list`)
411
+ * for the root-span name. Mirrors the walk in the program's `preAction`
412
+ * baggage hook so span names line up with `client.surface` on the backend.
413
+ */
414
+ function commandPath(cmd) {
415
+ const parts = [];
416
+ let c = cmd;
417
+ while (c && c.parent) {
418
+ parts.unshift(c.name());
419
+ c = c.parent;
420
+ }
421
+ return parts.join(".") || cmd.name();
422
+ }
409
423
  export async function withClient(cmd, fn) {
410
424
  const globals = getGlobals(cmd);
411
- try {
412
- const client = await createClient(globals);
413
- await fn(client, globals);
414
- }
415
- catch (err) {
416
- outputError(err, globals.json);
417
- await exitWithFlush(exitCodeFromError(err));
418
- }
425
+ await withCliSpan(commandPath(cmd), async () => {
426
+ try {
427
+ const client = await createClient(globals);
428
+ await fn(client, globals);
429
+ }
430
+ catch (err) {
431
+ outputError(err, globals.json);
432
+ await exitWithFlush(exitCodeFromError(err));
433
+ }
434
+ });
419
435
  }
420
436
  /**
421
437
  * Wrap an inline (non-API) command action with the same uniform error
@@ -424,13 +440,15 @@ export async function withClient(cmd, fn) {
424
440
  */
425
441
  export async function runInline(cmd, fn) {
426
442
  const globals = getGlobals(cmd);
427
- try {
428
- await fn(globals);
429
- }
430
- catch (err) {
431
- outputError(err, globals.json);
432
- await exitWithFlush(exitCodeFromError(err));
433
- }
443
+ await withCliSpan(commandPath(cmd), async () => {
444
+ try {
445
+ await fn(globals);
446
+ }
447
+ catch (err) {
448
+ outputError(err, globals.json);
449
+ await exitWithFlush(exitCodeFromError(err));
450
+ }
451
+ });
434
452
  }
435
453
  export function getWebUrl(globals, path) {
436
454
  const base = globals.dev ? "http://localhost:3000" : getAppUrl();
@@ -43,6 +43,17 @@ export declare function initObservability(): Promise<void>;
43
43
  * CLI.
44
44
  */
45
45
  export declare function flushObservability(timeoutMs?: number): Promise<void>;
46
+ /**
47
+ * Open a root transaction for a CLI command. `@sentry/node` only auto-creates
48
+ * transactions for HTTP server frameworks; arbitrary code paths need an
49
+ * explicit `startSpan` to anchor child spans (the UndiciInstrumentation
50
+ * fetch spans) so the exporter ships them. Without this wrap, every CLI
51
+ * run produces zero spans in the ish-cli Sentry project.
52
+ *
53
+ * No-op (passthrough) when Sentry isn't initialised — DSN unset or init
54
+ * failed — so the helper is always safe to call from withClient / runInline.
55
+ */
56
+ export declare function withCliSpan<T>(commandName: string, fn: () => Promise<T>): Promise<T>;
46
57
  /**
47
58
  * Async-flush-then-exit helper. Use this in place of `process.exit(code)`
48
59
  * anywhere we want telemetry to ship before teardown.
@@ -24,9 +24,6 @@
24
24
  */
25
25
  import pkg from "../../package.json" with { type: "json" };
26
26
  import { beforeSend } from "./sentry-scrub.js";
27
- /** Module-local reference to the loaded Sentry module so `flushObservability`
28
- * can call `flush()` without re-importing (which would lose runtime-pinned
29
- * state and create a second Hub in some bundler configurations). */
30
27
  let _sentryModule = null;
31
28
  /** Bun-global probe. ``typeof Bun !== 'undefined'`` is the only reliable
32
29
  * runtime-detect: ``process.versions.bun`` exists but is set up well after
@@ -76,6 +73,15 @@ export async function initObservability() {
76
73
  setTag("client.name", "ish-cli");
77
74
  setTag("client.version", pkg.version);
78
75
  }
76
+ // Natural-exit flush hook. `process.exit()` paths are covered by
77
+ // `exitWithFlush`, but successful CLI commands return normally —
78
+ // Node tears the process down via `beforeExit` and there's no
79
+ // process.exit to wrap. This is the catch-all for that case.
80
+ // Use `once` so a command that explicitly calls exit doesn't
81
+ // double-flush.
82
+ process.once("beforeExit", () => {
83
+ void flushObservability();
84
+ });
79
85
  }
80
86
  catch (err) {
81
87
  // Don't crash the CLI on init failure. Print to stderr at verbose
@@ -151,6 +157,22 @@ export async function flushObservability(timeoutMs = 2000) {
151
157
  // Swallow — same rationale as init failure.
152
158
  }
153
159
  }
160
+ /**
161
+ * Open a root transaction for a CLI command. `@sentry/node` only auto-creates
162
+ * transactions for HTTP server frameworks; arbitrary code paths need an
163
+ * explicit `startSpan` to anchor child spans (the UndiciInstrumentation
164
+ * fetch spans) so the exporter ships them. Without this wrap, every CLI
165
+ * run produces zero spans in the ish-cli Sentry project.
166
+ *
167
+ * No-op (passthrough) when Sentry isn't initialised — DSN unset or init
168
+ * failed — so the helper is always safe to call from withClient / runInline.
169
+ */
170
+ export async function withCliSpan(commandName, fn) {
171
+ if (!_initialized || _sentryModule?.startSpan === undefined) {
172
+ return await fn();
173
+ }
174
+ return await _sentryModule.startSpan({ name: `cli.${commandName}`, op: "cli.command" }, async () => await fn());
175
+ }
154
176
  /**
155
177
  * Async-flush-then-exit helper. Use this in place of `process.exit(code)`
156
178
  * anywhere we want telemetry to ship before teardown.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ishlabs/cli",
3
- "version": "0.17.5",
3
+ "version": "0.17.7",
4
4
  "description": "The command-line interface for ish",
5
5
  "type": "module",
6
6
  "bin": {