@glasstrace/sdk 0.20.1 → 1.0.1

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.
Files changed (46) hide show
  1. package/README.md +76 -5
  2. package/dist/chunk-3TU62WD6.js +142 -0
  3. package/dist/chunk-3TU62WD6.js.map +1 -0
  4. package/dist/chunk-67RIOAXV.js +105 -0
  5. package/dist/chunk-67RIOAXV.js.map +1 -0
  6. package/dist/{chunk-IQN6TRMQ.js → chunk-KE7MCPO5.js} +3 -140
  7. package/dist/chunk-KE7MCPO5.js.map +1 -0
  8. package/dist/{chunk-R4DAIPXD.js → chunk-MV3F7HVX.js} +96 -10
  9. package/dist/chunk-MV3F7HVX.js.map +1 -0
  10. package/dist/{chunk-BT2OCXCG.js → chunk-UGJ3X4CT.js} +1 -1
  11. package/dist/chunk-UGJ3X4CT.js.map +1 -0
  12. package/dist/cli/init.cjs +1 -1
  13. package/dist/cli/init.cjs.map +1 -1
  14. package/dist/cli/init.js +2 -2
  15. package/dist/cli/init.js.map +1 -1
  16. package/dist/{edge-entry-Ds2fNOeh.d.ts → edge-entry-CFq085RZ.d.ts} +2 -29
  17. package/dist/{edge-entry-FJFKkeFF.d.cts → edge-entry-DYl05SJ-.d.cts} +2 -29
  18. package/dist/edge-entry.cjs +2 -102
  19. package/dist/edge-entry.cjs.map +1 -1
  20. package/dist/edge-entry.d.cts +2 -2
  21. package/dist/edge-entry.d.ts +2 -2
  22. package/dist/edge-entry.js +3 -5
  23. package/dist/index.cjs +38 -244
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/{index.d-DgeH-pNJ.d.cts → index.d-CYYe3PxB.d.cts} +1 -1
  26. package/dist/{index.d-DgeH-pNJ.d.ts → index.d-CYYe3PxB.d.ts} +1 -1
  27. package/dist/index.d.cts +4 -5
  28. package/dist/index.d.ts +4 -5
  29. package/dist/index.js +4 -29
  30. package/dist/node-entry.cjs +93 -104
  31. package/dist/node-entry.cjs.map +1 -1
  32. package/dist/node-entry.d.cts +2 -2
  33. package/dist/node-entry.d.ts +2 -2
  34. package/dist/node-entry.js +6 -7
  35. package/dist/node-subpath.cjs.map +1 -1
  36. package/dist/node-subpath.d.cts +110 -3
  37. package/dist/node-subpath.d.ts +110 -3
  38. package/dist/node-subpath.js +3 -2
  39. package/dist/{source-map-uploader-YXWO6JLN.js → source-map-uploader-BJIXRLJ6.js} +3 -2
  40. package/package.json +11 -2
  41. package/dist/chunk-BT2OCXCG.js.map +0 -1
  42. package/dist/chunk-IQN6TRMQ.js.map +0 -1
  43. package/dist/chunk-R4DAIPXD.js.map +0 -1
  44. package/dist/chunk-Z2EGETTT.js +0 -204
  45. package/dist/chunk-Z2EGETTT.js.map +0 -1
  46. /package/dist/{source-map-uploader-YXWO6JLN.js.map → source-map-uploader-BJIXRLJ6.js.map} +0 -0
package/README.md CHANGED
@@ -228,11 +228,82 @@ export function proxy(_req: Request) {
228
228
 
229
229
  If `proxy.ts` no longer does anything else, you can delete it entirely.
230
230
 
231
- `createDiscoveryHandler` remains available for one more major version
232
- to avoid breaking integrations that depend on it, but it now prints a
233
- one-time deprecation warning on first use and will be removed in
234
- `v1.0.0`. Run `npx glasstrace init` after upgrading to generate the
235
- static file.
231
+ `createDiscoveryHandler` was removed from the public API in `v1.0.0`.
232
+ The runtime handler is installed automatically in anonymous + development
233
+ mode there is nothing to wire up yourself. Run `npx glasstrace init`
234
+ after upgrading to generate the static file; the extension reads the
235
+ file directly and no longer needs the runtime handler.
236
+
237
+ ## Subpath exports
238
+
239
+ `@glasstrace/sdk` ships three public entries:
240
+
241
+ - **`@glasstrace/sdk`** — primary import site. Use from
242
+ `instrumentation.ts` (runtime instrumentation) and `next.config.ts`
243
+ (via `withGlasstraceConfig`). The Node-only build-time helpers that
244
+ previously lived here (source-map upload, import-graph construction)
245
+ were moved to `@glasstrace/sdk/node` in this release so the root
246
+ specifier no longer drags `fs` / `path` / `@vercel/blob` into the
247
+ closure. The remaining root surface is intended for Node / serverful
248
+ runtimes; workloads running strictly on workerd or Vercel Edge
249
+ should import from the internal edge-entry bundle — not currently
250
+ exposed as a public entry — or ask for a public `/edge` subpath.
251
+ - **`@glasstrace/sdk/node`** — Node-only build-time tooling
252
+ (source-map uploading, import-graph construction). Use from
253
+ `next.config.ts` / build scripts. Resolves only under the Node
254
+ condition; non-Node runtimes (workerd, edge-light) fail cleanly at
255
+ module resolution rather than at evaluation.
256
+ - **`@glasstrace/sdk/drizzle`** — Drizzle ORM adapter.
257
+
258
+ The source-map and import-graph helpers previously reachable from the
259
+ `@glasstrace/sdk` root specifier have moved to `@glasstrace/sdk/node`
260
+ to narrow the root surface. Update imports:
261
+
262
+ ```ts
263
+ // Before
264
+ import { uploadSourceMapsAuto } from "@glasstrace/sdk";
265
+ // After
266
+ import { uploadSourceMapsAuto } from "@glasstrace/sdk/node";
267
+ ```
268
+
269
+ ### `/node` surface by symbol
270
+
271
+ The `@glasstrace/sdk/node` subpath is Node-only by design: the
272
+ package's conditional exports resolve `./node` under the Node
273
+ condition only, so any non-Node runtime (workerd, Vercel Edge, the
274
+ browser) fails at module resolution rather than at evaluation. Most
275
+ symbols additionally depend on a Node built-in module (`node:fs`,
276
+ `node:path`, `node:crypto`, `node:child_process`) or on the
277
+ `@vercel/blob` optional peer dependency. A handful — the pure
278
+ constant `PRESIGNED_THRESHOLD_BYTES`, the type-only exports, and the
279
+ pure string helper `extractImports` — have no direct Node dependency
280
+ of their own; they live under `/node` for API cohesion with the
281
+ upload and import-graph flows they belong to. The source-file JSDoc
282
+ on each symbol names its specific dependency (or notes "pure" /
283
+ "erases at runtime"); the table below summarizes the `/node` surface
284
+ and the recommended call site.
285
+
286
+ | Symbol | Kind | Node dependency | Edge-safe alternative |
287
+ |---|---|---|---|
288
+ | `discoverSourceMapFiles` | function | `node:fs`, `node:path` | — (call from a build script / `next.config.ts`) |
289
+ | `collectSourceMaps` | function | `node:fs`, `node:path` | — (call from a build script / `next.config.ts`) |
290
+ | `computeBuildHash` | function | `node:child_process` (git), `node:crypto`, `node:fs` | Pass a pre-computed build hash directly to `uploadSourceMaps` |
291
+ | `uploadSourceMaps` | function | `node:fs` (when given `SourceMapFileInfo[]`) | — (upstream discovery is Node-only) |
292
+ | `PRESIGNED_THRESHOLD_BYTES` | constant | — (pure value) | — (consume alongside the Node-only upload helpers) |
293
+ | `uploadSourceMapsPresigned` | function | `node:fs`, `@vercel/blob` | — (call from a build script / `next.config.ts`) |
294
+ | `uploadSourceMapsAuto` | function | `node:fs`, `@vercel/blob` (optional) | — (call from a build script / `next.config.ts`) |
295
+ | `SourceMapFileInfo` | type | — (erases at runtime) | — (produced/consumed by Node-only functions) |
296
+ | `SourceMapEntry` | type | — (erases at runtime) | — (produced/consumed by Node-only functions) |
297
+ | `BlobUploader` | type | — (erases at runtime) | — (produced/consumed by Node-only functions) |
298
+ | `AutoUploadOptions` | type | — (erases at runtime) | — (produced/consumed by Node-only functions) |
299
+ | `discoverTestFiles` | function | `node:fs`, `node:path` | — (call from a build script / CI job) |
300
+ | `extractImports` | function | — (pure string processing) | — (kept under `/node` for API cohesion with `buildImportGraph`) |
301
+ | `buildImportGraph` | function | `node:fs`, `node:path`, `node:crypto` | — (call from a build script / CI job) |
302
+
303
+ Type exports erase at runtime and are technically safe to import from
304
+ edge code, but every runtime function that produces or consumes them is
305
+ Node-only, so the practical signal is the same: reach for these from
306
+ your build pipeline, not from a request handler.
236
307
 
237
308
  ## Security
238
309
 
@@ -0,0 +1,142 @@
1
+ import {
2
+ isProductionDisabled,
3
+ resolveConfig
4
+ } from "./chunk-VUZCLMIX.js";
5
+ import {
6
+ __require
7
+ } from "./chunk-NSBPE2FW.js";
8
+
9
+ // src/nudge/error-nudge.ts
10
+ var hasFired = false;
11
+ var hasFiredServerAction = false;
12
+ function sanitize(input) {
13
+ return input.replace(/[\x00-\x1f\x7f]/g, "");
14
+ }
15
+ function markerFileExists() {
16
+ try {
17
+ const fs = __require("node:fs");
18
+ const path = __require("node:path");
19
+ const markerPath = path.join(process.cwd(), ".glasstrace", "mcp-connected");
20
+ return fs.existsSync(markerPath);
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ function maybeShowMcpNudge(errorSummary) {
26
+ if (hasFired) {
27
+ return;
28
+ }
29
+ const config = resolveConfig();
30
+ if (isProductionDisabled(config)) {
31
+ hasFired = true;
32
+ return;
33
+ }
34
+ if (markerFileExists()) {
35
+ hasFired = true;
36
+ return;
37
+ }
38
+ hasFired = true;
39
+ const safe = sanitize(errorSummary);
40
+ process.stderr.write(
41
+ `[glasstrace] Error captured: ${safe}
42
+ Debug with AI: ask your agent "What's the latest Glasstrace error?"
43
+ Not connected? Run: npx glasstrace mcp add
44
+ `
45
+ );
46
+ }
47
+ function maybeShowServerActionNudge() {
48
+ if (hasFiredServerAction) {
49
+ return;
50
+ }
51
+ if (process.env.GLASSTRACE_SUPPRESS_ACTION_NUDGE === "1") {
52
+ hasFiredServerAction = true;
53
+ return;
54
+ }
55
+ const config = resolveConfig();
56
+ if (isProductionDisabled(config)) {
57
+ hasFiredServerAction = true;
58
+ return;
59
+ }
60
+ hasFiredServerAction = true;
61
+ process.stderr.write(
62
+ `[glasstrace] Detected a Next.js Server Action trace. Install the Glasstrace browser extension to capture the Server Action identifier for precise action-level debugging. https://glasstrace.dev/ext
63
+ `
64
+ );
65
+ }
66
+
67
+ // src/console-capture.ts
68
+ var isGlasstraceLog = false;
69
+ var originalError = null;
70
+ var originalWarn = null;
71
+ var installed = false;
72
+ var otelApi = null;
73
+ function formatArgs(args) {
74
+ return args.map((arg) => {
75
+ if (typeof arg === "string") return arg;
76
+ if (arg instanceof Error) return arg.stack ?? arg.message;
77
+ try {
78
+ return JSON.stringify(arg);
79
+ } catch {
80
+ return String(arg);
81
+ }
82
+ }).join(" ");
83
+ }
84
+ function isSdkMessage(args) {
85
+ return typeof args[0] === "string" && args[0].startsWith("[glasstrace]");
86
+ }
87
+ async function installConsoleCapture() {
88
+ if (installed) return;
89
+ try {
90
+ otelApi = await import("./esm-KBPHCVB4.js");
91
+ } catch {
92
+ otelApi = null;
93
+ }
94
+ originalError = console.error;
95
+ originalWarn = console.warn;
96
+ installed = true;
97
+ console.error = (...args) => {
98
+ originalError.apply(console, args);
99
+ if (isGlasstraceLog || isSdkMessage(args)) return;
100
+ if (otelApi) {
101
+ const span = otelApi.trace.getSpan(otelApi.context.active());
102
+ if (span) {
103
+ const formattedMessage = formatArgs(args);
104
+ span.addEvent("console.error", {
105
+ "console.message": formattedMessage
106
+ });
107
+ try {
108
+ maybeShowMcpNudge(formattedMessage);
109
+ } catch {
110
+ }
111
+ }
112
+ }
113
+ };
114
+ console.warn = (...args) => {
115
+ originalWarn.apply(console, args);
116
+ if (isGlasstraceLog || isSdkMessage(args)) return;
117
+ if (otelApi) {
118
+ const span = otelApi.trace.getSpan(otelApi.context.active());
119
+ if (span) {
120
+ span.addEvent("console.warn", {
121
+ "console.message": formatArgs(args)
122
+ });
123
+ }
124
+ }
125
+ };
126
+ }
127
+ function sdkLog(level, message) {
128
+ isGlasstraceLog = true;
129
+ try {
130
+ console[level](message);
131
+ } finally {
132
+ isGlasstraceLog = false;
133
+ }
134
+ }
135
+
136
+ export {
137
+ maybeShowMcpNudge,
138
+ maybeShowServerActionNudge,
139
+ installConsoleCapture,
140
+ sdkLog
141
+ };
142
+ //# sourceMappingURL=chunk-3TU62WD6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nudge/error-nudge.ts","../src/console-capture.ts"],"sourcesContent":["import { resolveConfig, isProductionDisabled } from \"../env-detection.js\";\n\n/**\n * Module-level flag ensuring the MCP-connection nudge fires at most once\n * per process.\n */\nlet hasFired = false;\n\n/**\n * Module-level flag ensuring the Server Action nudge fires at most once\n * per process (DISC-1253).\n */\nlet hasFiredServerAction = false;\n\n/**\n * Strips control characters (except space) from a string to prevent\n * terminal escape sequence injection via error summaries written to stderr.\n */\nfunction sanitize(input: string): string {\n // eslint-disable-next-line no-control-regex\n return input.replace(/[\\x00-\\x1f\\x7f]/g, \"\");\n}\n\n/**\n * Checks whether the MCP marker file exists using synchronous filesystem\n * APIs. Returns `false` when `node:fs` or `node:path` cannot be resolved\n * (non-Node environments) or on any I/O error.\n */\nfunction markerFileExists(): boolean {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const fs = require(\"node:fs\") as typeof import(\"node:fs\");\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const path = require(\"node:path\") as typeof import(\"node:path\");\n const markerPath = path.join(process.cwd(), \".glasstrace\", \"mcp-connected\");\n return fs.existsSync(markerPath);\n } catch {\n // node:fs/node:path unavailable, permission denied, ENOENT from\n // cwd(), or other error — treat as not connected\n return false;\n }\n}\n\n/**\n * Shows a one-time stderr nudge when the SDK captures its first error\n * and the MCP connection marker file is absent.\n *\n * The nudge is suppressed when:\n * - It has already fired in this process\n * - The `.glasstrace/mcp-connected` marker file exists at the project root\n * - The environment is detected as production (and force-enable is off)\n *\n * Uses `process.stderr.write()` instead of `console.error()` to avoid\n * being captured by OpenTelemetry console instrumentation.\n */\nexport function maybeShowMcpNudge(errorSummary: string): void {\n if (hasFired) {\n return;\n }\n\n // Production check — suppress silently, but remember the decision\n // so subsequent calls fast-exit via hasFired without re-running I/O.\n const config = resolveConfig();\n if (isProductionDisabled(config)) {\n hasFired = true;\n return;\n }\n\n // Check for MCP connection marker file.\n if (markerFileExists()) {\n hasFired = true;\n return;\n }\n\n // Fire the nudge exactly once\n hasFired = true;\n\n const safe = sanitize(errorSummary);\n process.stderr.write(\n `[glasstrace] Error captured: ${safe}\\n` +\n ` Debug with AI: ask your agent \"What's the latest Glasstrace error?\"\\n` +\n ` Not connected? Run: npx glasstrace mcp add\\n`,\n );\n}\n\n/**\n * Shows a one-time stderr nudge when the SDK detects a Next.js Server\n * Action trace whose originating request had no Glasstrace browser\n * extension correlation header (`x-gt-cid`) — meaning the extension was\n * not active for that request and the specific Server Action identifier\n * could not be captured (DISC-1253).\n *\n * The nudge is suppressed when:\n * - It has already fired in this process\n * - The environment is detected as production (and force-enable is off)\n * - `GLASSTRACE_SUPPRESS_ACTION_NUDGE=1` is set\n *\n * Routes through `process.stderr.write()` — identical transport to the\n * existing MCP nudge — so it is not captured by OpenTelemetry console\n * instrumentation and plays nicely with existing error-nudge tests.\n */\nexport function maybeShowServerActionNudge(): void {\n if (hasFiredServerAction) {\n return;\n }\n\n // User opt-out takes precedence over every other check so we never\n // re-run I/O when silenced.\n if (process.env.GLASSTRACE_SUPPRESS_ACTION_NUDGE === \"1\") {\n hasFiredServerAction = true;\n return;\n }\n\n // Production check — suppress silently, but remember the decision so\n // subsequent calls fast-exit via hasFiredServerAction without re-running\n // resolveConfig.\n const config = resolveConfig();\n if (isProductionDisabled(config)) {\n hasFiredServerAction = true;\n return;\n }\n\n hasFiredServerAction = true;\n\n process.stderr.write(\n `[glasstrace] Detected a Next.js Server Action trace. Install the ` +\n `Glasstrace browser extension to capture the Server Action identifier ` +\n `for precise action-level debugging. https://glasstrace.dev/ext\\n`,\n );\n}\n\n/**\n * Test-only hook: resets both nudge guards so independent tests can\n * reload module state without relying on `vi.resetModules()` side effects.\n * Kept internal — not exported from the SDK barrel.\n */\nexport function __resetNudgeStateForTests(): void {\n hasFired = false;\n hasFiredServerAction = false;\n}\n","/**\n * Console error/warn capture module.\n *\n * When enabled, monkey-patches `console.error` and `console.warn` to record\n * their output as OTel span events on the currently active span. SDK-internal\n * log messages (prefixed with \"[glasstrace]\") are never captured.\n */\n\nimport { maybeShowMcpNudge } from \"./nudge/error-nudge.js\";\n\n/**\n * Module-level flag to suppress capture of SDK-internal log messages.\n * Set to `true` before calling `console.warn`/`console.error` from SDK code,\n * then reset to `false` immediately after.\n */\nexport let isGlasstraceLog = false;\n\n/** Saved reference to the original `console.error`. */\nlet originalError: typeof console.error | null = null;\n\n/** Saved reference to the original `console.warn`. */\nlet originalWarn: typeof console.warn | null = null;\n\n/** Whether the console capture is currently installed. */\nlet installed = false;\n\n/** Cached OTel API module reference, resolved at install time. */\nlet otelApi: typeof import(\"@opentelemetry/api\") | null = null;\n\n/**\n * Formats console arguments into a single string for span event attributes.\n * Uses best-effort serialization: strings pass through, Errors preserve their\n * stack trace, and other values are JSON-stringified with a String() fallback.\n */\nfunction formatArgs(args: unknown[]): string {\n return args\n .map((arg) => {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return arg.stack ?? arg.message;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n })\n .join(\" \");\n}\n\n/**\n * Returns `true` if the first argument is a string starting with \"[glasstrace]\".\n * Used to skip capture of SDK-internal log messages without requiring every\n * call site to set the `isGlasstraceLog` flag.\n */\nfunction isSdkMessage(args: unknown[]): boolean {\n return typeof args[0] === \"string\" && args[0].startsWith(\"[glasstrace]\");\n}\n\n/**\n * Installs console capture by replacing `console.error` and `console.warn`\n * with wrappers that record span events on the active OTel span.\n *\n * Must be called after OTel is configured so the API module is available.\n * Safe to call multiple times; subsequent calls are no-ops.\n */\nexport async function installConsoleCapture(): Promise<void> {\n if (installed) return;\n\n // Resolve OTel API at install time via dynamic import so that:\n // 1. tsup inlines @opentelemetry/api into the bundle (it's in noExternal)\n // 2. vitest's vi.doMock can intercept this import for testing\n try {\n otelApi = await import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n\n originalError = console.error;\n originalWarn = console.warn;\n installed = true;\n\n console.error = (...args: unknown[]) => {\n // Always call the original first to preserve developer experience\n originalError!.apply(console, args);\n\n // Skip SDK-internal messages and flagged messages\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n const formattedMessage = formatArgs(args);\n span.addEvent(\"console.error\", {\n \"console.message\": formattedMessage,\n });\n // Show one-time MCP connection nudge on first captured error\n try {\n maybeShowMcpNudge(formattedMessage);\n } catch {\n // Never allow the monkey-patched console.error wrapper to throw\n }\n }\n }\n };\n\n console.warn = (...args: unknown[]) => {\n originalWarn!.apply(console, args);\n\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n span.addEvent(\"console.warn\", {\n \"console.message\": formatArgs(args),\n });\n }\n }\n };\n}\n\n/**\n * Restores the original `console.error` and `console.warn` methods.\n * Primarily intended for use in tests.\n */\nexport function uninstallConsoleCapture(): void {\n if (!installed) return;\n\n if (originalError) console.error = originalError;\n if (originalWarn) console.warn = originalWarn;\n\n originalError = null;\n originalWarn = null;\n otelApi = null;\n installed = false;\n}\n\n/**\n * Logs a message from SDK-internal code without triggering console capture.\n *\n * Use this helper in new SDK code instead of bare `console.warn(...)` calls\n * to prevent SDK log messages from being recorded as user-facing span events.\n *\n * @param level - The console log level to use.\n * @param message - The message to log.\n */\nexport function sdkLog(level: \"warn\" | \"info\" | \"error\", message: string): void {\n isGlasstraceLog = true;\n try {\n console[level](message);\n } finally {\n isGlasstraceLog = false;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,105 @@
1
+ import {
2
+ trace
3
+ } from "./chunk-DQ25VOKK.js";
4
+ import {
5
+ GLASSTRACE_ATTRIBUTE_NAMES
6
+ } from "./chunk-TQ54WLCZ.js";
7
+
8
+ // src/errors.ts
9
+ var SdkError = class extends Error {
10
+ code;
11
+ constructor(code, message, cause) {
12
+ super(message, { cause });
13
+ this.name = "SdkError";
14
+ this.code = code;
15
+ }
16
+ };
17
+
18
+ // src/span-processor.ts
19
+ var GlasstraceSpanProcessor = class {
20
+ wrappedProcessor;
21
+ /* eslint-disable @typescript-eslint/no-unused-vars -- backward compat signature */
22
+ constructor(wrappedProcessor, _sessionManager, _apiKey, _getConfig, _environment) {
23
+ this.wrappedProcessor = wrappedProcessor;
24
+ }
25
+ onStart(span, parentContext) {
26
+ this.wrappedProcessor.onStart(span, parentContext);
27
+ }
28
+ onEnd(readableSpan) {
29
+ this.wrappedProcessor.onEnd(readableSpan);
30
+ }
31
+ async shutdown() {
32
+ return this.wrappedProcessor.shutdown();
33
+ }
34
+ async forceFlush() {
35
+ return this.wrappedProcessor.forceFlush();
36
+ }
37
+ };
38
+
39
+ // src/correlation-id.ts
40
+ var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
41
+ var HEADER_NAME = "x-gt-cid";
42
+ var MAX_CID_LENGTH = 128;
43
+ function captureCorrelationId(req) {
44
+ try {
45
+ if (!req || !req.headers) {
46
+ return;
47
+ }
48
+ const value = readHeader(req.headers);
49
+ if (!value) {
50
+ return;
51
+ }
52
+ const span = trace.getActiveSpan();
53
+ if (!span) {
54
+ return;
55
+ }
56
+ span.setAttribute(ATTR.CORRELATION_ID, value);
57
+ } catch {
58
+ }
59
+ }
60
+ function readHeader(headers) {
61
+ const asFetch = headers;
62
+ if (typeof asFetch.get === "function") {
63
+ const raw = asFetch.get(HEADER_NAME);
64
+ return firstToken(raw);
65
+ }
66
+ const dict = headers;
67
+ const direct = dict[HEADER_NAME];
68
+ if (direct !== void 0) {
69
+ return firstValue(direct);
70
+ }
71
+ for (const key of Object.keys(dict)) {
72
+ if (key.toLowerCase() === HEADER_NAME) {
73
+ return firstValue(dict[key]);
74
+ }
75
+ }
76
+ return void 0;
77
+ }
78
+ function firstValue(value) {
79
+ if (Array.isArray(value)) {
80
+ for (const entry of value) {
81
+ const token = firstToken(entry);
82
+ if (token) return token;
83
+ }
84
+ return void 0;
85
+ }
86
+ return firstToken(value);
87
+ }
88
+ function firstToken(value) {
89
+ if (typeof value !== "string") return void 0;
90
+ const parts = value.split(",");
91
+ for (const part of parts) {
92
+ const trimmed = part.trim();
93
+ if (trimmed.length === 0) continue;
94
+ if (trimmed.length > MAX_CID_LENGTH) return void 0;
95
+ return trimmed;
96
+ }
97
+ return void 0;
98
+ }
99
+
100
+ export {
101
+ SdkError,
102
+ GlasstraceSpanProcessor,
103
+ captureCorrelationId
104
+ };
105
+ //# sourceMappingURL=chunk-67RIOAXV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/span-processor.ts","../src/correlation-id.ts"],"sourcesContent":["import type { SdkDiagnosticCode } from \"@glasstrace/protocol\";\n\n/**\n * Internal SDK error class with a typed diagnostic code.\n * Caught at the boundary and converted to a log message + diagnostic entry.\n * Never thrown to the developer.\n */\nexport class SdkError extends Error {\n readonly code: SdkDiagnosticCode;\n\n constructor(code: SdkDiagnosticCode, message: string, cause?: Error) {\n super(message, { cause });\n this.name = \"SdkError\";\n this.code = code;\n }\n}\n","import type { SpanProcessor, ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport type { Span } from \"@opentelemetry/sdk-trace-base\";\nimport type { CaptureConfig } from \"@glasstrace/protocol\";\nimport type { SessionManager } from \"./session.js\";\n\n/**\n * Lightweight SpanProcessor that delegates to a wrapped processor.\n *\n * All glasstrace.* attribute enrichment has been moved to {@link GlasstraceExporter}\n * (see enriching-exporter.ts), which enriches spans at export time. This resolves:\n * - Cold-start spans are buffered in the exporter, not dropped\n * - Vercel's CompositeSpanProcessor skips onEnding(); the exporter doesn't need it\n * - Session ID is computed at export time with the resolved API key\n *\n * This class is retained for backward compatibility. New code should use\n * GlasstraceExporter directly.\n *\n * @deprecated Use GlasstraceExporter for span enrichment. This processor is now a pass-through.\n */\nexport class GlasstraceSpanProcessor implements SpanProcessor {\n private readonly wrappedProcessor: SpanProcessor;\n\n /* eslint-disable @typescript-eslint/no-unused-vars -- backward compat signature */\n constructor(\n wrappedProcessor: SpanProcessor,\n _sessionManager?: SessionManager,\n _apiKey?: string | (() => string),\n _getConfig?: () => CaptureConfig,\n _environment?: string,\n ) {\n /* eslint-enable @typescript-eslint/no-unused-vars */\n this.wrappedProcessor = wrappedProcessor;\n }\n\n onStart(span: Span, parentContext: Parameters<SpanProcessor[\"onStart\"]>[1]): void {\n this.wrappedProcessor.onStart(span, parentContext);\n }\n\n onEnd(readableSpan: ReadableSpan): void {\n this.wrappedProcessor.onEnd(readableSpan);\n }\n\n async shutdown(): Promise<void> {\n return this.wrappedProcessor.shutdown();\n }\n\n async forceFlush(): Promise<void> {\n return this.wrappedProcessor.forceFlush();\n }\n}\n","import { trace } from \"@opentelemetry/api\";\nimport { GLASSTRACE_ATTRIBUTE_NAMES } from \"@glasstrace/protocol\";\n\nconst ATTR = GLASSTRACE_ATTRIBUTE_NAMES;\nconst HEADER_NAME = \"x-gt-cid\";\n\n/**\n * Hard cap on the correlation ID length we accept from the wire. Our\n * own extension emits ULIDs (26 characters); 128 is a generous ceiling\n * that still prevents a hostile client from ballooning span payloads.\n */\nconst MAX_CID_LENGTH = 128;\n\n/**\n * Minimal Fetch-API `Headers`-like interface supporting case-insensitive\n * single-value lookup. Matches `Headers` from `undici` / the Web Fetch API.\n */\ninterface FetchHeadersLike {\n get(name: string): string | null;\n}\n\n/**\n * Minimal Node `IncomingMessage.headers`-like shape: a dictionary mapping\n * (typically lower-cased) header names to a value, a list of values, or\n * `undefined`.\n */\ntype NodeHeadersLike = Record<\n string,\n string | string[] | undefined\n>;\n\n/**\n * Accepted request shape for {@link captureCorrelationId}. Intentionally\n * loose so callers can pass either a Fetch `Request` (or `NextRequest`)\n * or a Node `IncomingMessage` without adapting the type.\n */\nexport interface CorrelationIdRequest {\n headers: FetchHeadersLike | NodeHeadersLike | undefined;\n}\n\n/**\n * Captures the Glasstrace correlation ID header (`x-gt-cid`) from an\n * incoming request and materializes it as the\n * `glasstrace.correlation.id` attribute on the currently active OTel span\n * (DISC-1253).\n *\n * The SDK does not own any HTTP instrumentation, so it cannot read this\n * header itself. Users opt in by calling this helper from a hook that\n * runs inside the request's OTel context — typically a Next.js\n * `middleware.ts` or a custom server request handler.\n *\n * The function is intentionally forgiving:\n * - No active span → no-op.\n * - Missing / empty header → no-op.\n * - Array header values (Node IncomingMessage) → the first non-empty\n * value is used; subsequent values are ignored because a correlation\n * ID is a single logical value.\n * - Malformed or unexpected `headers` shapes → caught and ignored; the\n * helper never throws.\n *\n * @example\n * ```ts\n * // Next.js middleware.ts\n * import { captureCorrelationId } from \"@glasstrace/sdk\";\n *\n * export function middleware(req: Request) {\n * captureCorrelationId(req);\n * return NextResponse.next();\n * }\n * ```\n */\nexport function captureCorrelationId(req: CorrelationIdRequest | null | undefined): void {\n try {\n if (!req || !req.headers) {\n return;\n }\n\n const value = readHeader(req.headers);\n if (!value) {\n return;\n }\n\n const span = trace.getActiveSpan();\n if (!span) {\n return;\n }\n\n span.setAttribute(ATTR.CORRELATION_ID, value);\n } catch {\n // Never throw from a request hook — correlation is a best-effort\n // enrichment and must not break the user's request pipeline.\n }\n}\n\n/**\n * Reads the `x-gt-cid` header from either a Fetch-API `Headers` object\n * or a Node-style dictionary. Returns a trimmed single value, or\n * `undefined` if the header is missing or empty.\n */\nfunction readHeader(\n headers: FetchHeadersLike | NodeHeadersLike,\n): string | undefined {\n // Fetch-API Headers: duck-type on `.get(name)` being a function.\n const asFetch = headers as FetchHeadersLike;\n if (typeof asFetch.get === \"function\") {\n const raw = asFetch.get(HEADER_NAME);\n return firstToken(raw);\n }\n\n // Node IncomingMessage headers: case-insensitive dictionary lookup.\n // Node normalizes to lower-case but some frameworks preserve case, so\n // scan keys defensively.\n const dict = headers as NodeHeadersLike;\n const direct = dict[HEADER_NAME];\n if (direct !== undefined) {\n return firstValue(direct);\n }\n\n for (const key of Object.keys(dict)) {\n if (key.toLowerCase() === HEADER_NAME) {\n return firstValue(dict[key]);\n }\n }\n\n return undefined;\n}\n\n/**\n * Picks the first value from a possibly-array header and trims it.\n * Correlation IDs are logically single-valued; when duplicated we keep\n * the first occurrence and drop the rest.\n *\n * Also handles the comma-joined form that intermediaries (and some\n * Node.js HTTP stacks) produce when the same header is sent multiple\n * times — `x-gt-cid: cid1, x-gt-cid: cid2` may surface as the single\n * string `\"cid1, cid2\"` via `Headers.get()` or `IncomingMessage.headers`.\n * Storing that raw merged value would both fail correlation and\n * silently suppress the DISC-1253 Server Action nudge (which only\n * checks attribute presence, not validity). We split on commas and\n * keep the first non-empty token.\n */\nfunction firstValue(value: string | string[] | undefined): string | undefined {\n if (Array.isArray(value)) {\n for (const entry of value) {\n const token = firstToken(entry);\n if (token) return token;\n }\n return undefined;\n }\n return firstToken(value);\n}\n\n/**\n * Extracts the first comma-separated token from a header-like string\n * and normalizes it. Returns undefined when no non-empty token exists\n * within the length bound.\n */\nfunction firstToken(value: string | null | undefined): string | undefined {\n if (typeof value !== \"string\") return undefined;\n // Split on commas (HTTP list-header separator). Trim each token and\n // return the first non-empty one that fits within MAX_CID_LENGTH.\n // A single un-merged header has no commas and this reduces to the\n // previous normalize() behavior.\n const parts = value.split(\",\");\n for (const part of parts) {\n const trimmed = part.trim();\n if (trimmed.length === 0) continue;\n if (trimmed.length > MAX_CID_LENGTH) return undefined;\n return trimmed;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;AAOO,IAAM,WAAN,cAAuB,MAAM;AAAA,EACzB;AAAA,EAET,YAAY,MAAyB,SAAiB,OAAe;AACnE,UAAM,SAAS,EAAE,MAAM,CAAC;AACxB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;;;ACIO,IAAM,0BAAN,MAAuD;AAAA,EAC3C;AAAA;AAAA,EAGjB,YACE,kBACA,iBACA,SACA,YACA,cACA;AAEA,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,QAAQ,MAAY,eAA8D;AAChF,SAAK,iBAAiB,QAAQ,MAAM,aAAa;AAAA,EACnD;AAAA,EAEA,MAAM,cAAkC;AACtC,SAAK,iBAAiB,MAAM,YAAY;AAAA,EAC1C;AAAA,EAEA,MAAM,WAA0B;AAC9B,WAAO,KAAK,iBAAiB,SAAS;AAAA,EACxC;AAAA,EAEA,MAAM,aAA4B;AAChC,WAAO,KAAK,iBAAiB,WAAW;AAAA,EAC1C;AACF;;;AC9CA,IAAM,OAAO;AACb,IAAM,cAAc;AAOpB,IAAM,iBAAiB;AA4DhB,SAAS,qBAAqB,KAAoD;AACvF,MAAI;AACF,QAAI,CAAC,OAAO,CAAC,IAAI,SAAS;AACxB;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,IAAI,OAAO;AACpC,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,cAAc;AACjC,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AAEA,SAAK,aAAa,KAAK,gBAAgB,KAAK;AAAA,EAC9C,QAAQ;AAAA,EAGR;AACF;AAOA,SAAS,WACP,SACoB;AAEpB,QAAM,UAAU;AAChB,MAAI,OAAO,QAAQ,QAAQ,YAAY;AACrC,UAAM,MAAM,QAAQ,IAAI,WAAW;AACnC,WAAO,WAAW,GAAG;AAAA,EACvB;AAKA,QAAM,OAAO;AACb,QAAM,SAAS,KAAK,WAAW;AAC/B,MAAI,WAAW,QAAW;AACxB,WAAO,WAAW,MAAM;AAAA,EAC1B;AAEA,aAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,QAAI,IAAI,YAAY,MAAM,aAAa;AACrC,aAAO,WAAW,KAAK,GAAG,CAAC;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AACT;AAgBA,SAAS,WAAW,OAA0D;AAC5E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,SAAS,OAAO;AACzB,YAAM,QAAQ,WAAW,KAAK;AAC9B,UAAI,MAAO,QAAO;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACA,SAAO,WAAW,KAAK;AACzB;AAOA,SAAS,WAAW,OAAsD;AACxE,MAAI,OAAO,UAAU,SAAU,QAAO;AAKtC,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,EAAG;AAC1B,QAAI,QAAQ,SAAS,eAAgB,QAAO;AAC5C,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
@@ -1,150 +1,17 @@
1
1
  import {
2
- isProductionDisabled,
3
- resolveConfig
4
- } from "./chunk-VUZCLMIX.js";
2
+ sdkLog
3
+ } from "./chunk-3TU62WD6.js";
5
4
  import {
6
5
  PresignedUploadResponseSchema,
7
6
  SourceMapManifestResponseSchema,
8
7
  SourceMapUploadResponseSchema
9
8
  } from "./chunk-TQ54WLCZ.js";
10
- import {
11
- __require
12
- } from "./chunk-NSBPE2FW.js";
13
9
 
14
10
  // src/source-map-uploader.ts
15
11
  import * as fs from "node:fs/promises";
16
12
  import * as path from "node:path";
17
13
  import * as crypto from "node:crypto";
18
14
  import { execFileSync } from "node:child_process";
19
-
20
- // src/nudge/error-nudge.ts
21
- var hasFired = false;
22
- var hasFiredServerAction = false;
23
- function sanitize(input) {
24
- return input.replace(/[\x00-\x1f\x7f]/g, "");
25
- }
26
- function markerFileExists() {
27
- try {
28
- const fs2 = __require("node:fs");
29
- const path2 = __require("node:path");
30
- const markerPath = path2.join(process.cwd(), ".glasstrace", "mcp-connected");
31
- return fs2.existsSync(markerPath);
32
- } catch {
33
- return false;
34
- }
35
- }
36
- function maybeShowMcpNudge(errorSummary) {
37
- if (hasFired) {
38
- return;
39
- }
40
- const config = resolveConfig();
41
- if (isProductionDisabled(config)) {
42
- hasFired = true;
43
- return;
44
- }
45
- if (markerFileExists()) {
46
- hasFired = true;
47
- return;
48
- }
49
- hasFired = true;
50
- const safe = sanitize(errorSummary);
51
- process.stderr.write(
52
- `[glasstrace] Error captured: ${safe}
53
- Debug with AI: ask your agent "What's the latest Glasstrace error?"
54
- Not connected? Run: npx glasstrace mcp add
55
- `
56
- );
57
- }
58
- function maybeShowServerActionNudge() {
59
- if (hasFiredServerAction) {
60
- return;
61
- }
62
- if (process.env.GLASSTRACE_SUPPRESS_ACTION_NUDGE === "1") {
63
- hasFiredServerAction = true;
64
- return;
65
- }
66
- const config = resolveConfig();
67
- if (isProductionDisabled(config)) {
68
- hasFiredServerAction = true;
69
- return;
70
- }
71
- hasFiredServerAction = true;
72
- process.stderr.write(
73
- `[glasstrace] Detected a Next.js Server Action trace. Install the Glasstrace browser extension to capture the Server Action identifier for precise action-level debugging. https://glasstrace.dev/ext
74
- `
75
- );
76
- }
77
-
78
- // src/console-capture.ts
79
- var isGlasstraceLog = false;
80
- var originalError = null;
81
- var originalWarn = null;
82
- var installed = false;
83
- var otelApi = null;
84
- function formatArgs(args) {
85
- return args.map((arg) => {
86
- if (typeof arg === "string") return arg;
87
- if (arg instanceof Error) return arg.stack ?? arg.message;
88
- try {
89
- return JSON.stringify(arg);
90
- } catch {
91
- return String(arg);
92
- }
93
- }).join(" ");
94
- }
95
- function isSdkMessage(args) {
96
- return typeof args[0] === "string" && args[0].startsWith("[glasstrace]");
97
- }
98
- async function installConsoleCapture() {
99
- if (installed) return;
100
- try {
101
- otelApi = await import("./esm-KBPHCVB4.js");
102
- } catch {
103
- otelApi = null;
104
- }
105
- originalError = console.error;
106
- originalWarn = console.warn;
107
- installed = true;
108
- console.error = (...args) => {
109
- originalError.apply(console, args);
110
- if (isGlasstraceLog || isSdkMessage(args)) return;
111
- if (otelApi) {
112
- const span = otelApi.trace.getSpan(otelApi.context.active());
113
- if (span) {
114
- const formattedMessage = formatArgs(args);
115
- span.addEvent("console.error", {
116
- "console.message": formattedMessage
117
- });
118
- try {
119
- maybeShowMcpNudge(formattedMessage);
120
- } catch {
121
- }
122
- }
123
- }
124
- };
125
- console.warn = (...args) => {
126
- originalWarn.apply(console, args);
127
- if (isGlasstraceLog || isSdkMessage(args)) return;
128
- if (otelApi) {
129
- const span = otelApi.trace.getSpan(otelApi.context.active());
130
- if (span) {
131
- span.addEvent("console.warn", {
132
- "console.message": formatArgs(args)
133
- });
134
- }
135
- }
136
- };
137
- }
138
- function sdkLog(level, message) {
139
- isGlasstraceLog = true;
140
- try {
141
- console[level](message);
142
- } finally {
143
- isGlasstraceLog = false;
144
- }
145
- }
146
-
147
- // src/source-map-uploader.ts
148
15
  var LARGE_FILE_WARNING_BYTES = 50 * 1024 * 1024;
149
16
  async function discoverSourceMapFiles(buildDir) {
150
17
  const resolvedDir = path.resolve(buildDir);
@@ -451,10 +318,6 @@ async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options)
451
318
  }
452
319
 
453
320
  export {
454
- maybeShowMcpNudge,
455
- maybeShowServerActionNudge,
456
- installConsoleCapture,
457
- sdkLog,
458
321
  discoverSourceMapFiles,
459
322
  collectSourceMaps,
460
323
  computeBuildHash,
@@ -468,4 +331,4 @@ export {
468
331
  uploadSourceMapsPresigned,
469
332
  uploadSourceMapsAuto
470
333
  };
471
- //# sourceMappingURL=chunk-IQN6TRMQ.js.map
334
+ //# sourceMappingURL=chunk-KE7MCPO5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/source-map-uploader.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { execFileSync } from \"node:child_process\";\nimport { sdkLog } from \"./console-capture.js\";\nimport {\n SourceMapUploadResponseSchema,\n type SourceMapUploadResponse,\n PresignedUploadResponseSchema,\n type PresignedUploadResponse,\n SourceMapManifestResponseSchema,\n type SourceMapManifestResponse,\n} from \"@glasstrace/protocol\";\n\n/**\n * In-memory source map entry: a file path paired with its full text content.\n *\n * @remarks\n * Node-only. Describes the legacy in-memory shape consumed by\n * {@link uploadSourceMaps} and {@link uploadSourceMapsAuto}. The type\n * itself erases at runtime and is safe to import from edge code, but\n * every function that produces or consumes it depends on `node:fs`\n * and cannot run at the edge.\n */\nexport interface SourceMapEntry {\n filePath: string;\n content: string;\n}\n\n/**\n * Metadata for a discovered source map file, without its content loaded.\n * Used by the streaming upload flow to defer file reads until upload time.\n *\n * @remarks\n * Node-only. Describes the shape of data produced by the Node-only\n * source-map upload flow ({@link discoverSourceMapFiles},\n * {@link uploadSourceMapsAuto}). The type itself erases at runtime and\n * is safe to import from edge code, but every function that produces\n * or consumes it depends on `node:fs`/`node:path` and cannot run at\n * the edge.\n */\nexport interface SourceMapFileInfo {\n /** Relative path to the compiled JS file (`.map` extension stripped). */\n filePath: string;\n /** Absolute path on disk for reading the file content on demand. */\n absolutePath: string;\n /** File size in bytes. */\n sizeBytes: number;\n}\n\n/** Threshold (50 MB) above which a single source map triggers a warning. */\nconst LARGE_FILE_WARNING_BYTES = 50 * 1024 * 1024;\n\n/**\n * Recursively discovers all `.map` files in the given build directory.\n * Returns metadata only — file content is NOT read into memory.\n *\n * @remarks\n * Node-only. Walks the filesystem with `node:fs/promises` (`readdir`,\n * `stat`) and resolves paths with `node:path`. No edge-safe\n * alternative — call from a Node context (build script, Next.js\n * `next.config.ts`, CI job).\n */\nexport async function discoverSourceMapFiles(\n buildDir: string,\n): Promise<SourceMapFileInfo[]> {\n // Resolve to absolute so absolutePath in results is always absolute,\n // even when buildDir is relative (e.g. \".next\").\n const resolvedDir = path.resolve(buildDir);\n const results: SourceMapFileInfo[] = [];\n\n try {\n await walkDirMetadata(resolvedDir, resolvedDir, results);\n } catch {\n // Directory doesn't exist or is unreadable — return empty\n return [];\n }\n\n // Warn about oversized source map files\n for (const file of results) {\n if (file.sizeBytes >= LARGE_FILE_WARNING_BYTES) {\n const sizeMB = (file.sizeBytes / (1024 * 1024)).toFixed(1);\n sdkLog(\n \"warn\",\n `[glasstrace] Large source map detected: ${file.filePath} (${sizeMB}MB). Consider enabling source map compression.`,\n );\n }\n }\n\n return results;\n}\n\nasync function walkDirMetadata(\n baseDir: string,\n currentDir: string,\n results: SourceMapFileInfo[],\n): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await walkDirMetadata(baseDir, fullPath, results);\n } else if (entry.isFile() && entry.name.endsWith(\".map\")) {\n try {\n const stat = await fs.stat(fullPath);\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n // Strip the trailing .map extension so the key matches the compiled\n // JS path that the runtime uses for stack-frame lookups (e.g.\n // \"static/chunks/main.js\" instead of \"static/chunks/main.js.map\").\n const compiledPath = relativePath.replace(/\\.map$/, \"\");\n results.push({\n filePath: compiledPath,\n absolutePath: fullPath,\n sizeBytes: stat.size,\n });\n } catch {\n // Skip unreadable files\n }\n }\n }\n}\n\n/**\n * Reads the content of a single source map file from disk.\n */\nasync function readSourceMapContent(absolutePath: string): Promise<string> {\n return fs.readFile(absolutePath, \"utf-8\");\n}\n\n/**\n * Recursively finds all .map files in the given build directory.\n * Returns relative paths and file contents.\n *\n * @deprecated Prefer {@link discoverSourceMapFiles} to avoid loading all\n * source maps into memory simultaneously.\n *\n * @remarks\n * Node-only. Reads every discovered `.map` file into memory via\n * `node:fs/promises` (`readFile`). No edge-safe alternative — call\n * from a Node context (build script, Next.js `next.config.ts`, CI job).\n */\nexport async function collectSourceMaps(\n buildDir: string,\n): Promise<SourceMapEntry[]> {\n const fileInfos = await discoverSourceMapFiles(buildDir);\n const results: SourceMapEntry[] = [];\n\n for (const info of fileInfos) {\n try {\n const content = await readSourceMapContent(info.absolutePath);\n results.push({ filePath: info.filePath, content });\n } catch {\n // Skip unreadable files — consistent with previous behavior\n }\n }\n\n return results;\n}\n\n/**\n * Computes a build hash for source map uploads.\n *\n * First tries `git rev-parse HEAD` to get the git commit SHA.\n * On failure, falls back to a deterministic content hash:\n * sort source map file paths alphabetically, concatenate each as\n * `{relativePath}\\n{fileLength}\\n{fileContent}`, then SHA-256 the result.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n *\n * @remarks\n * Node-only. Spawns `git rev-parse HEAD` via `node:child_process`\n * (`execFileSync`), hashes with `node:crypto` (`createHash(\"sha256\")`),\n * and reads map contents from disk with `node:fs/promises`. No\n * edge-safe alternative — call from a Node context (build script,\n * Next.js `next.config.ts`, CI job). If a pre-computed build hash is\n * already known (e.g., provided as a CI environment variable), pass\n * it directly to {@link uploadSourceMaps} instead of calling this\n * helper.\n */\nexport async function computeBuildHash(\n maps?: SourceMapEntry[] | SourceMapFileInfo[],\n): Promise<string> {\n // Try git first\n try {\n const sha = execFileSync(\"git\", [\"rev-parse\", \"HEAD\"], { encoding: \"utf-8\" }).trim();\n if (sha) {\n return sha;\n }\n } catch {\n // Git not available, fall through to content hash\n }\n\n // Fallback: content-based hash\n const sortedMaps = [...(maps ?? [])].sort((a, b) =>\n a.filePath.localeCompare(b.filePath),\n );\n\n const hash = crypto.createHash(\"sha256\");\n\n for (const m of sortedMaps) {\n let content: string;\n if (\"content\" in m) {\n content = m.content;\n } else {\n try {\n content = await readSourceMapContent(m.absolutePath);\n } catch {\n // Skip unreadable files — consistent with collectSourceMaps behavior\n continue;\n }\n }\n hash.update(`${m.filePath}\\n${content.length}\\n${content}`);\n }\n\n return hash.digest(\"hex\");\n}\n\n/**\n * Uploads source maps to the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,\n * and file entries. Validates the response against SourceMapUploadResponseSchema.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (deferred reads). With `SourceMapFileInfo[]`,\n * file content is read at upload time rather than at discovery time.\n * Note: the legacy endpoint sends all files in a single JSON body, so\n * peak memory is similar — the benefit is deferring reads past discovery.\n *\n * @remarks\n * Node-only. Reads map contents from disk via `node:fs/promises` when\n * invoked with `SourceMapFileInfo[]`. The network call uses the\n * platform-standard `fetch` (edge-safe on its own), but the upstream\n * discovery and read path is Node-only, so the function is only\n * reachable from a Node context (build script, Next.js\n * `next.config.ts`, CI job). No edge-safe alternative.\n */\nexport async function uploadSourceMaps(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n): Promise<SourceMapUploadResponse> {\n // Read files on demand — for SourceMapEntry[], content is already available;\n // for SourceMapFileInfo[], each file is read individually to limit memory.\n // Individual reads are guarded so one transient failure (deleted file,\n // permission change) does not abort the entire upload.\n const files: Array<{ filePath: string; sourceMap: string }> = [];\n for (const m of maps) {\n try {\n const content = \"content\" in m\n ? m.content\n : await readSourceMapContent(m.absolutePath);\n files.push({ filePath: m.filePath, sourceMap: content });\n } catch {\n sdkLog(\"warn\", `[glasstrace] Skipping unreadable source map: ${m.filePath}`);\n }\n }\n\n const body = {\n buildHash,\n files,\n };\n\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n // Consume the response body to release the connection back to the pool.\n // Without this, the underlying TCP socket stays allocated until GC, which\n // causes connection pool exhaustion under sustained error conditions.\n // Wrapped in try-catch so a stream error doesn't mask the HTTP status error.\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map upload failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapUploadResponseSchema.parse(json);\n}\n\n// ---------------------------------------------------------------------------\n// Presigned source map upload (3-phase flow for large builds)\n// ---------------------------------------------------------------------------\n\n/**\n * Builds at or above this byte size route to the presigned upload flow.\n *\n * @remarks\n * Node-only. The numeric value itself is pure (a constant is evaluable\n * anywhere), but it is meaningful only alongside\n * {@link uploadSourceMapsPresigned} and {@link uploadSourceMapsAuto},\n * both of which depend on `node:fs` and `@vercel/blob`. No edge-safe\n * alternative — consume from a Node context.\n */\nexport const PRESIGNED_THRESHOLD_BYTES = 4_500_000;\n\n/**\n * Signature for the blob upload function, injectable for testing.\n *\n * @remarks\n * Node-only. Describes the shape of the uploader consumed by\n * {@link uploadSourceMapsPresigned} and {@link uploadSourceMapsAuto},\n * both of which depend on `@vercel/blob` and `node:fs`. The type itself\n * erases at runtime and is safe to import from edge code, but every\n * producer and consumer is Node-only.\n */\nexport type BlobUploader = (\n clientToken: string,\n pathname: string,\n content: string,\n) => Promise<{ url: string; size: number }>;\n\n/**\n * Strips trailing slashes from a URL string.\n * Uses an iterative approach to avoid regex (CodeQL js/polynomial-redos).\n */\nfunction stripTrailingSlashes(url: string): string {\n let result = url;\n while (result.endsWith(\"/\")) {\n result = result.slice(0, -1);\n }\n return result;\n}\n\n/**\n * Phase 1: Request presigned upload tokens from the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps/presign` with the build hash and\n * file metadata. Returns presigned tokens for each file that the client\n * uses to upload directly to blob storage.\n */\nexport async function requestPresignedTokens(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number }>,\n): Promise<PresignedUploadResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Presigned token request failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return PresignedUploadResponseSchema.parse(json);\n}\n\n/** Shape of the subset of `@vercel/blob/client` the SDK consumes. */\ntype BlobClientModule = {\n put: (\n pathname: string,\n body: Blob,\n options: { access: string; token: string },\n ) => Promise<{ url: string }>;\n};\n\n/**\n * Loads `@vercel/blob/client` at runtime using the `Function()`-based\n * dynamic-import evasion trick.\n *\n * The indirection hides the specifier from static bundler analysis — webpack,\n * tsup, esbuild, and rollup all resolve literal `await import(\"...\")` targets\n * at build time and would raise `Module not found` for consumers without\n * `@vercel/blob` installed (it is an optional peer dependency). See DISC-1255.\n *\n * **CSP note:** `Function()` is semantically equivalent to `eval()` and will\n * trigger `unsafe-eval` CSP violations in restricted environments. Source-map\n * upload only runs in Node.js at build time, so CSP does not apply here. The\n * same caveat documented on `tryImport` in `otel-config.ts` applies if this\n * code is ever re-used in a browser-equivalent runtime.\n *\n * Exported for internal test injection via {@link _setBlobClientLoaderForTesting}\n * only; not part of the public API.\n */\nasync function defaultBlobClientLoader(): Promise<BlobClientModule> {\n const dynamicImport = Function(\"id\", \"return import(id)\") as (\n id: string,\n ) => Promise<BlobClientModule>;\n return dynamicImport(\"@vercel/blob/client\");\n}\n\nlet _blobClientLoader: () => Promise<BlobClientModule> = defaultBlobClientLoader;\n\n/**\n * Replaces the blob client loader. For test use only.\n *\n * The production loader uses a `Function()`-based dynamic import to evade\n * static bundler analysis (DISC-1255), which in turn bypasses Vitest's\n * module-mock interceptor. Tests that need to stub `@vercel/blob/client`\n * call this helper to install a fake loader, then restore the default\n * with {@link _resetBlobClientLoaderForTesting}.\n *\n * @internal\n */\nexport function _setBlobClientLoaderForTesting(\n loader: () => Promise<BlobClientModule>,\n): void {\n _blobClientLoader = loader;\n}\n\n/**\n * Restores the default `@vercel/blob/client` loader. For test use only.\n *\n * @internal\n */\nexport function _resetBlobClientLoaderForTesting(): void {\n _blobClientLoader = defaultBlobClientLoader;\n}\n\n/**\n * Phase 2: Upload a single source map to blob storage using a presigned token.\n *\n * Dynamically imports `@vercel/blob/client` via {@link defaultBlobClientLoader}\n * to avoid bundling the optional peer dependency. Throws a descriptive error\n * if the package is not installed.\n */\nexport async function uploadToBlob(\n clientToken: string,\n pathname: string,\n content: string,\n): Promise<{ url: string; size: number }> {\n let mod: BlobClientModule;\n try {\n mod = await _blobClientLoader();\n } catch (err) {\n // Distinguish \"not installed\" from other import errors\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\") {\n throw new Error(\n \"Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob\",\n );\n }\n throw err;\n }\n\n const result = await mod.put(pathname, new Blob([content]), {\n access: \"public\",\n token: clientToken,\n });\n\n return { url: result.url, size: Buffer.byteLength(content, \"utf-8\") };\n}\n\n/**\n * Phase 3: Submit the upload manifest to finalize a presigned upload.\n *\n * POSTs to `{endpoint}/v1/source-maps/manifest` with the upload ID,\n * build hash, and blob URLs for each uploaded file. The backend activates\n * the source maps for stack trace resolution.\n */\nexport async function submitManifest(\n apiKey: string,\n endpoint: string,\n uploadId: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number; blobUrl: string }>,\n): Promise<SourceMapManifestResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ uploadId, buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapManifestResponseSchema.parse(json);\n}\n\n/**\n * Orchestrates the 3-phase presigned upload flow.\n *\n * 1. Requests presigned tokens for all source map files\n * 2. Uploads each file to blob storage with a concurrency limit of 5,\n * reading file content from disk just before each upload\n * 3. Submits the manifest to finalize the upload\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n *\n * Accepts an optional `blobUploader` for test injection; defaults to\n * {@link uploadToBlob}.\n *\n * @remarks\n * Node-only. Streams map contents from disk via `node:fs/promises`\n * and uploads through `@vercel/blob/client` (loaded lazily as an\n * optional peer dependency). No edge-safe alternative — call from a\n * Node context (build script, Next.js `next.config.ts`, CI job).\n */\nexport async function uploadSourceMapsPresigned(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n blobUploader: BlobUploader = uploadToBlob,\n): Promise<SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n // Determine file metadata for the presign request\n const fileMetadata = maps.map((m) => ({\n filePath: m.filePath,\n sizeBytes: \"content\" in m\n ? Buffer.byteLength(m.content, \"utf-8\")\n : m.sizeBytes,\n }));\n\n // Phase 1: request presigned tokens\n const presigned = await requestPresignedTokens(\n apiKey, endpoint, buildHash, fileMetadata,\n );\n\n // Build a lookup map for O(1) access by filePath\n const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));\n\n if (mapsByPath.size !== maps.length) {\n throw new Error(\"Duplicate filePath entries in source maps\");\n }\n\n // Validate all tokens have matching entries before starting any uploads.\n for (const token of presigned.files) {\n if (!mapsByPath.has(token.filePath)) {\n throw new Error(\n `Presigned token for \"${token.filePath}\" has no matching source map entry`,\n );\n }\n }\n\n // Phase 2: upload to blob storage in chunks of CONCURRENCY.\n // File content is read from disk just before each upload to avoid\n // holding all source maps in memory simultaneously.\n const CONCURRENCY = 5;\n const uploadResults: Array<{ filePath: string; sizeBytes: number; blobUrl: string }> = [];\n\n for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {\n const chunk = presigned.files.slice(i, i + CONCURRENCY);\n const chunkResults = await Promise.all(\n chunk.map(async (token) => {\n const entry = mapsByPath.get(token.filePath)!;\n let content: string;\n if (\"content\" in entry) {\n content = entry.content;\n } else {\n try {\n content = await readSourceMapContent(entry.absolutePath);\n } catch {\n sdkLog(\"warn\", `[glasstrace] Skipping unreadable source map: ${token.filePath}`);\n return null;\n }\n }\n const result = await blobUploader(token.clientToken, token.pathname, content);\n return {\n filePath: token.filePath,\n sizeBytes: result.size,\n blobUrl: result.url,\n };\n }),\n );\n for (const r of chunkResults) {\n if (r !== null) {\n uploadResults.push(r);\n }\n }\n }\n\n // Phase 3: submit manifest\n return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);\n}\n\n/**\n * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.\n *\n * @remarks\n * Node-only. Describes the shape of overrides consumed by\n * {@link uploadSourceMapsAuto}, which depends on `node:fs` and\n * `@vercel/blob`. The type itself erases at runtime and is safe to\n * import from edge code, but the surrounding function is Node-only.\n */\nexport interface AutoUploadOptions {\n /** Override blob availability check (for testing). */\n checkBlobAvailable?: () => Promise<boolean>;\n /** Override blob uploader (for testing). */\n blobUploader?: BlobUploader;\n}\n\n/**\n * Automatically routes source map uploads based on total build size.\n *\n * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request\n * {@link uploadSourceMaps} endpoint.\n * - At or above the threshold: checks if `@vercel/blob` is available and\n * uses the presigned 3-phase flow. Falls back to legacy with a warning\n * if the package is not installed.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n *\n * @remarks\n * Node-only. Reads source map sizes/contents via `node:fs/promises`\n * and, above the threshold, dynamically loads `@vercel/blob/client`\n * (optional peer dependency) for direct blob storage uploads. No\n * edge-safe alternative — call from a Node context (build script,\n * Next.js `next.config.ts`, CI job). This is the recommended entry\n * point for source-map upload in most projects.\n */\nexport async function uploadSourceMapsAuto(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n options?: AutoUploadOptions,\n): Promise<SourceMapUploadResponse | SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n const totalBytes = maps.reduce(\n (sum, m) => {\n const bytes = \"content\" in m\n ? Buffer.byteLength(m.content, \"utf-8\")\n : m.sizeBytes;\n return sum + bytes;\n },\n 0,\n );\n\n if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n }\n\n // Check if @vercel/blob is available. Uses the shared blob client loader\n // (which goes through the `Function()` evasion trick) so webpack / tsup /\n // esbuild do not resolve `@vercel/blob/client` at build time (DISC-1255).\n const checkAvailable = options?.checkBlobAvailable ?? (async () => {\n try {\n await _blobClientLoader();\n return true;\n } catch {\n return false;\n }\n });\n\n const blobAvailable = await checkAvailable();\n\n if (blobAvailable) {\n return uploadSourceMapsPresigned(\n apiKey, endpoint, buildHash, maps, options?.blobUploader,\n );\n }\n\n // Fall back to legacy upload with a warning\n sdkLog(\"warn\",\n `[glasstrace] Build exceeds 4.5MB (${String(totalBytes)} bytes). Install @vercel/blob for ` +\n `presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`\n );\n\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n}\n"],"mappings":"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","names":["stat"]}