@ishlabs/cli 0.17.4 → 0.17.5

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.js CHANGED
@@ -26,7 +26,7 @@ import { tagAlias, ALIAS_PREFIX } from "./lib/alias-store.js";
26
26
  import { output } from "./lib/output.js";
27
27
  import { ishDir } from "./lib/paths.js";
28
28
  import { findInstalledSkill } from "./lib/skill-content.js";
29
- import { initObservability } from "./lib/observability.js";
29
+ import { exitWithFlush, initObservability } from "./lib/observability.js";
30
30
  import { setSurfaceBaggage } from "./lib/baggage.js";
31
31
  import pkg from "../package.json" with { type: "json" };
32
32
  const { version } = pkg;
@@ -62,7 +62,8 @@ program.exitOverride((err) => {
62
62
  if (err.code === "commander.helpDisplayed"
63
63
  || err.code === "commander.version"
64
64
  || err.code === "commander.help") {
65
- process.exit(0);
65
+ void exitWithFlush(0);
66
+ return;
66
67
  }
67
68
  // Detect --json without relying on parsed opts (parse may have failed).
68
69
  const useJson = process.argv.includes("--json") || !process.stdout.isTTY;
@@ -80,7 +81,7 @@ program.exitOverride((err) => {
80
81
  console.error(`Error: ${err.message}`);
81
82
  console.error(" → Run `ish <command> --help` for usage");
82
83
  }
83
- process.exit(EXIT_USAGE);
84
+ void exitWithFlush(EXIT_USAGE);
84
85
  });
85
86
  // Global options
86
87
  program
@@ -10,6 +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
14
  function isSimulatable(p) {
14
15
  return Boolean(p.simulation_config_id) || Boolean(p.simulation_config);
15
16
  }
@@ -277,6 +278,10 @@ export function getGlobals(cmd) {
277
278
  || process.argv.includes("--get")
278
279
  || !process.stdout.isTTY;
279
280
  outputError(err, useJson);
281
+ // Sync exit path: getGlobals is called from non-async Commander hooks,
282
+ // so we can't await exitWithFlush here. Usage errors are typed early-
283
+ // exits without rich Sentry signal; accept losing the flush on this
284
+ // one site rather than cascading async through every getGlobals caller.
280
285
  process.exit(exitCodeFromError(err));
281
286
  }
282
287
  // Apply side effects (verbose, fields, colors, get-field, active workspace)
@@ -409,7 +414,7 @@ export async function withClient(cmd, fn) {
409
414
  }
410
415
  catch (err) {
411
416
  outputError(err, globals.json);
412
- process.exit(exitCodeFromError(err));
417
+ await exitWithFlush(exitCodeFromError(err));
413
418
  }
414
419
  }
415
420
  /**
@@ -424,7 +429,7 @@ export async function runInline(cmd, fn) {
424
429
  }
425
430
  catch (err) {
426
431
  outputError(err, globals.json);
427
- process.exit(exitCodeFromError(err));
432
+ await exitWithFlush(exitCodeFromError(err));
428
433
  }
429
434
  }
430
435
  export function getWebUrl(globals, path) {
@@ -27,3 +27,28 @@
27
27
  * import time on some Bun-compile builds. */
28
28
  export declare function isBunRuntime(): boolean;
29
29
  export declare function initObservability(): Promise<void>;
30
+ /**
31
+ * Drain the Sentry buffer before exiting the process.
32
+ *
33
+ * Why this exists: Sentry's transport buffers events asynchronously. `ish`
34
+ * is a short-lived process — most commands call `process.exit(code)` within
35
+ * milliseconds of init, so the buffered envelope is dropped before the HTTP
36
+ * batch fires. Net effect: zero telemetry from real CLI invocations.
37
+ *
38
+ * Wrap every `process.exit(code)` callsite with `exitWithFlush(code)` instead,
39
+ * OR await `flushObservability()` directly before exit.
40
+ *
41
+ * Always safe to call: no-op when Sentry wasn't initialised (no DSN, or
42
+ * init failed). Never throws — an observability outage must not crash the
43
+ * CLI.
44
+ */
45
+ export declare function flushObservability(timeoutMs?: number): Promise<void>;
46
+ /**
47
+ * Async-flush-then-exit helper. Use this in place of `process.exit(code)`
48
+ * anywhere we want telemetry to ship before teardown.
49
+ *
50
+ * The Node `beforeExit` event would fire automatically on natural process
51
+ * end, but it does NOT fire when `process.exit()` is called explicitly —
52
+ * which the CLI does from at least 5 sites. This helper closes that gap.
53
+ */
54
+ export declare function exitWithFlush(code: number): Promise<never>;
@@ -24,6 +24,10 @@
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
+ let _sentryModule = null;
27
31
  /** Bun-global probe. ``typeof Bun !== 'undefined'`` is the only reliable
28
32
  * runtime-detect: ``process.versions.bun`` exists but is set up well after
29
33
  * import time on some Bun-compile builds. */
@@ -44,6 +48,7 @@ export async function initObservability() {
44
48
  const sentryModule = bun
45
49
  ? await import("@sentry/bun")
46
50
  : await import("@sentry/node");
51
+ _sentryModule = sentryModule;
47
52
  const init = sentryModule.init;
48
53
  init({
49
54
  dsn,
@@ -121,3 +126,40 @@ async function initOtelNode() {
121
126
  instrumentations: [new UndiciInstrumentation()],
122
127
  });
123
128
  }
129
+ /**
130
+ * Drain the Sentry buffer before exiting the process.
131
+ *
132
+ * Why this exists: Sentry's transport buffers events asynchronously. `ish`
133
+ * is a short-lived process — most commands call `process.exit(code)` within
134
+ * milliseconds of init, so the buffered envelope is dropped before the HTTP
135
+ * batch fires. Net effect: zero telemetry from real CLI invocations.
136
+ *
137
+ * Wrap every `process.exit(code)` callsite with `exitWithFlush(code)` instead,
138
+ * OR await `flushObservability()` directly before exit.
139
+ *
140
+ * Always safe to call: no-op when Sentry wasn't initialised (no DSN, or
141
+ * init failed). Never throws — an observability outage must not crash the
142
+ * CLI.
143
+ */
144
+ export async function flushObservability(timeoutMs = 2000) {
145
+ if (!_initialized || _sentryModule?.flush === undefined)
146
+ return;
147
+ try {
148
+ await _sentryModule.flush(timeoutMs);
149
+ }
150
+ catch {
151
+ // Swallow — same rationale as init failure.
152
+ }
153
+ }
154
+ /**
155
+ * Async-flush-then-exit helper. Use this in place of `process.exit(code)`
156
+ * anywhere we want telemetry to ship before teardown.
157
+ *
158
+ * The Node `beforeExit` event would fire automatically on natural process
159
+ * end, but it does NOT fire when `process.exit()` is called explicitly —
160
+ * which the CLI does from at least 5 sites. This helper closes that gap.
161
+ */
162
+ export async function exitWithFlush(code) {
163
+ await flushObservability();
164
+ process.exit(code);
165
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ishlabs/cli",
3
- "version": "0.17.4",
3
+ "version": "0.17.5",
4
4
  "description": "The command-line interface for ish",
5
5
  "type": "module",
6
6
  "bin": {