@glasstrace/sdk 0.14.2 → 0.16.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.
Files changed (84) hide show
  1. package/README.md +84 -1
  2. package/dist/adapters/drizzle.js +2 -5
  3. package/dist/adapters/drizzle.js.map +1 -1
  4. package/dist/{chunk-PD2SKFQQ.js → chunk-55FBXXER.js} +4 -8
  5. package/dist/{chunk-PD2SKFQQ.js.map → chunk-55FBXXER.js.map} +1 -1
  6. package/dist/chunk-5C2TJFLB.js +851 -0
  7. package/dist/chunk-5C2TJFLB.js.map +1 -0
  8. package/dist/{chunk-YMEXDDTA.js → chunk-7JBKXSBU.js} +3 -99
  9. package/dist/chunk-7JBKXSBU.js.map +1 -0
  10. package/dist/{chunk-2LDBR3F3.js → chunk-BANTDXUT.js} +15 -74
  11. package/dist/chunk-BANTDXUT.js.map +1 -0
  12. package/dist/{chunk-WV3NIPWJ.js → chunk-CTJI2YKA.js} +23 -288
  13. package/dist/chunk-CTJI2YKA.js.map +1 -0
  14. package/dist/{chunk-WK7MPK2T.js → chunk-DQ25VOKK.js} +1 -89
  15. package/dist/chunk-DQ25VOKK.js.map +1 -0
  16. package/dist/{chunk-BL3YDC6V.js → chunk-DXRZKKSO.js} +1 -6
  17. package/dist/{chunk-BL3YDC6V.js.map → chunk-DXRZKKSO.js.map} +1 -1
  18. package/dist/{chunk-BGZ7J74D.js → chunk-NSBPE2FW.js} +2 -16
  19. package/dist/chunk-O63DJKIJ.js +460 -0
  20. package/dist/chunk-O63DJKIJ.js.map +1 -0
  21. package/dist/{chunk-ECEN724Y.js → chunk-TM5NKZTO.js} +4 -8
  22. package/dist/{chunk-ECEN724Y.js.map → chunk-TM5NKZTO.js.map} +1 -1
  23. package/dist/chunk-VUZCLMIX.js +57 -0
  24. package/dist/chunk-VUZCLMIX.js.map +1 -0
  25. package/dist/{chunk-OSXIUKD5.js → chunk-WZXVS2EO.js} +1 -6
  26. package/dist/{chunk-OSXIUKD5.js.map → chunk-WZXVS2EO.js.map} +1 -1
  27. package/dist/{chunk-ARAOZCZT.js → chunk-XNDHQN4S.js} +122 -24
  28. package/dist/chunk-XNDHQN4S.js.map +1 -0
  29. package/dist/cli/init.cjs +1110 -255
  30. package/dist/cli/init.cjs.map +1 -1
  31. package/dist/cli/init.d.cts +86 -1
  32. package/dist/cli/init.d.ts +86 -1
  33. package/dist/cli/init.js +277 -66
  34. package/dist/cli/init.js.map +1 -1
  35. package/dist/cli/mcp-add.cjs +16 -16
  36. package/dist/cli/mcp-add.cjs.map +1 -1
  37. package/dist/cli/mcp-add.js +12 -13
  38. package/dist/cli/mcp-add.js.map +1 -1
  39. package/dist/cli/status.cjs +2 -2
  40. package/dist/cli/status.js +4 -7
  41. package/dist/cli/status.js.map +1 -1
  42. package/dist/cli/uninit.cjs +138 -20
  43. package/dist/cli/uninit.cjs.map +1 -1
  44. package/dist/cli/uninit.d.cts +38 -8
  45. package/dist/cli/uninit.d.ts +38 -8
  46. package/dist/cli/uninit.js +8 -5
  47. package/dist/cli/validate.cjs +135 -0
  48. package/dist/cli/validate.cjs.map +1 -0
  49. package/dist/cli/validate.d.cts +60 -0
  50. package/dist/cli/validate.d.ts +60 -0
  51. package/dist/cli/validate.js +100 -0
  52. package/dist/cli/validate.js.map +1 -0
  53. package/dist/{esm-MDK7CZID.js → esm-KBPHCVB4.js} +3 -3
  54. package/dist/{getMachineId-bsd-4NIRBWME.js → getMachineId-bsd-345PYXFX.js} +4 -7
  55. package/dist/{getMachineId-bsd-4NIRBWME.js.map → getMachineId-bsd-345PYXFX.js.map} +1 -1
  56. package/dist/{getMachineId-darwin-2XNOCCJQ.js → getMachineId-darwin-5L2D25AD.js} +4 -7
  57. package/dist/{getMachineId-darwin-2XNOCCJQ.js.map → getMachineId-darwin-5L2D25AD.js.map} +1 -1
  58. package/dist/{getMachineId-linux-V6YSQEY7.js → getMachineId-linux-KJR4P5HN.js} +3 -6
  59. package/dist/{getMachineId-linux-V6YSQEY7.js.map → getMachineId-linux-KJR4P5HN.js.map} +1 -1
  60. package/dist/{getMachineId-unsupported-4FKBJNVO.js → getMachineId-unsupported-NDNXDYDY.js} +3 -6
  61. package/dist/{getMachineId-unsupported-4FKBJNVO.js.map → getMachineId-unsupported-NDNXDYDY.js.map} +1 -1
  62. package/dist/{getMachineId-win-WLRZBKVG.js → getMachineId-win-T7PJNJXG.js} +4 -7
  63. package/dist/{getMachineId-win-WLRZBKVG.js.map → getMachineId-win-T7PJNJXG.js.map} +1 -1
  64. package/dist/index.cjs +519 -494
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.d.cts +47 -6
  67. package/dist/index.d.ts +47 -6
  68. package/dist/index.js +250 -719
  69. package/dist/index.js.map +1 -1
  70. package/dist/{monorepo-YILKGQXQ.js → monorepo-N5Z63XP7.js} +4 -4
  71. package/dist/{source-map-uploader-3GWUQDTS.js → source-map-uploader-MUZPI2S5.js} +5 -4
  72. package/dist/source-map-uploader-MUZPI2S5.js.map +1 -0
  73. package/package.json +6 -4
  74. package/dist/chunk-2LDBR3F3.js.map +0 -1
  75. package/dist/chunk-ARAOZCZT.js.map +0 -1
  76. package/dist/chunk-BGZ7J74D.js.map +0 -1
  77. package/dist/chunk-UPS5BGER.js +0 -182
  78. package/dist/chunk-UPS5BGER.js.map +0 -1
  79. package/dist/chunk-WK7MPK2T.js.map +0 -1
  80. package/dist/chunk-WV3NIPWJ.js.map +0 -1
  81. package/dist/chunk-YMEXDDTA.js.map +0 -1
  82. /package/dist/{esm-MDK7CZID.js.map → chunk-NSBPE2FW.js.map} +0 -0
  83. /package/dist/{monorepo-YILKGQXQ.js.map → esm-KBPHCVB4.js.map} +0 -0
  84. /package/dist/{source-map-uploader-3GWUQDTS.js.map → monorepo-N5Z63XP7.js.map} +0 -0
@@ -0,0 +1,851 @@
1
+ import {
2
+ DEFAULT_CAPTURE_CONFIG,
3
+ SdkCachedConfigSchema,
4
+ SdkInitResponseSchema,
5
+ createBuildHash
6
+ } from "./chunk-7JBKXSBU.js";
7
+ import {
8
+ __require
9
+ } from "./chunk-NSBPE2FW.js";
10
+
11
+ // src/health-collector.ts
12
+ var tracesExported = 0;
13
+ var tracesDropped = 0;
14
+ var initFailures = 0;
15
+ var lastConfigSyncAt = null;
16
+ function recordSpansExported(count) {
17
+ if (!Number.isFinite(count) || count < 0 || !Number.isInteger(count)) return;
18
+ tracesExported += count;
19
+ }
20
+ function recordSpansDropped(count) {
21
+ if (!Number.isFinite(count) || count < 0 || !Number.isInteger(count)) return;
22
+ tracesDropped += count;
23
+ }
24
+ function recordInitFailure() {
25
+ try {
26
+ initFailures += 1;
27
+ } catch {
28
+ }
29
+ }
30
+ function recordConfigSync(timestamp) {
31
+ try {
32
+ lastConfigSyncAt = timestamp;
33
+ } catch {
34
+ }
35
+ }
36
+ function collectHealthReport(sdkVersion) {
37
+ try {
38
+ const now = Date.now();
39
+ const configAge = lastConfigSyncAt !== null ? Math.max(0, now - lastConfigSyncAt) : 0;
40
+ return {
41
+ tracesExportedSinceLastInit: tracesExported,
42
+ tracesDropped,
43
+ initFailures,
44
+ configAge: Math.round(configAge),
45
+ sdkVersion
46
+ };
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function acknowledgeHealthReport(report) {
52
+ const exp = Math.max(0, report.tracesExportedSinceLastInit);
53
+ const expVal = tracesExported - exp;
54
+ tracesExported = Number.isFinite(expVal) ? Math.max(0, expVal) : tracesExported;
55
+ const drop = Math.max(0, report.tracesDropped);
56
+ const dropVal = tracesDropped - drop;
57
+ tracesDropped = Number.isFinite(dropVal) ? Math.max(0, dropVal) : tracesDropped;
58
+ const fail = Math.max(0, report.initFailures);
59
+ const failVal = initFailures - fail;
60
+ initFailures = Number.isFinite(failVal) ? Math.max(0, failVal) : initFailures;
61
+ }
62
+
63
+ // src/https-transport.ts
64
+ import {
65
+ request as httpsRequest
66
+ } from "node:https";
67
+ import {
68
+ request as httpRequest
69
+ } from "node:http";
70
+ import { URL } from "node:url";
71
+ var HttpsTransportError = class extends Error {
72
+ kind = "transport";
73
+ cause;
74
+ constructor(message, cause) {
75
+ super(message);
76
+ this.name = "HttpsTransportError";
77
+ this.cause = cause;
78
+ }
79
+ };
80
+ var HttpsStatusError = class extends Error {
81
+ kind = "status";
82
+ status;
83
+ /** Raw response body text (may be truncated by caller if large). */
84
+ body;
85
+ constructor(status, body) {
86
+ super(`Server returned HTTP ${status}`);
87
+ this.name = "HttpsStatusError";
88
+ this.status = status;
89
+ this.body = body;
90
+ }
91
+ };
92
+ var HttpsBodyParseError = class extends Error {
93
+ kind = "parse";
94
+ status;
95
+ cause;
96
+ constructor(status, cause) {
97
+ super(`Server returned malformed response (HTTP ${status})`);
98
+ this.name = "HttpsBodyParseError";
99
+ this.status = status;
100
+ this.cause = cause;
101
+ }
102
+ };
103
+ var DEFAULT_TIMEOUT_MS = 1e4;
104
+ var DEFAULT_RETRY_DELAYS_MS = [500, 1500];
105
+ var DEFAULT_TOTAL_DEADLINE_MS = 2e4;
106
+ async function httpsPostJson(url, jsonBody, options) {
107
+ const parsed = new URL(url);
108
+ const isHttps = parsed.protocol === "https:";
109
+ const isHttp = parsed.protocol === "http:";
110
+ if (!isHttps && !isHttp) {
111
+ throw new HttpsTransportError(
112
+ `Unsupported protocol: ${parsed.protocol} (expected http: or https:)`
113
+ );
114
+ }
115
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
116
+ const maxAttempts = options.maxAttempts ?? 3;
117
+ const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS;
118
+ const totalDeadlineMs = options.totalDeadlineMs ?? DEFAULT_TOTAL_DEADLINE_MS;
119
+ const scheduler = options.scheduler ?? ((fn, ms) => setTimeout(fn, ms));
120
+ const requestImpl = isHttps ? options.requestImpl ?? httpsRequest : options.httpRequestImpl ?? httpRequest;
121
+ let payload;
122
+ try {
123
+ payload = JSON.stringify(jsonBody);
124
+ } catch (err) {
125
+ throw new HttpsTransportError(
126
+ `Failed to serialize request body: ${err instanceof Error ? err.message : String(err)}`,
127
+ err
128
+ );
129
+ }
130
+ const payloadBuffer = Buffer.from(payload, "utf-8");
131
+ const startedAt = Date.now();
132
+ let lastError;
133
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
134
+ if (options.signal?.aborted) {
135
+ throw new HttpsTransportError("Request aborted");
136
+ }
137
+ const elapsed = Date.now() - startedAt;
138
+ if (elapsed >= totalDeadlineMs) {
139
+ break;
140
+ }
141
+ const remainingBudget = totalDeadlineMs - elapsed;
142
+ const attemptTimeoutMs = Math.min(timeoutMs, remainingBudget);
143
+ try {
144
+ return await sendSingleRequest(
145
+ parsed,
146
+ payloadBuffer,
147
+ options.headers,
148
+ attemptTimeoutMs,
149
+ options.signal,
150
+ requestImpl
151
+ );
152
+ } catch (err) {
153
+ lastError = err;
154
+ if (err instanceof HttpsStatusError || err instanceof HttpsBodyParseError) {
155
+ throw err;
156
+ }
157
+ const isLast = attempt === maxAttempts - 1;
158
+ if (isLast) break;
159
+ const delayMs = retryDelaysMs[attempt] ?? retryDelaysMs[retryDelaysMs.length - 1] ?? 0;
160
+ const elapsedBeforeSleep = Date.now() - startedAt;
161
+ const remaining = totalDeadlineMs - elapsedBeforeSleep;
162
+ if (remaining <= 0) break;
163
+ const actualDelayMs = Math.min(delayMs, remaining);
164
+ await sleep(actualDelayMs, scheduler, options.signal);
165
+ }
166
+ }
167
+ if (lastError instanceof HttpsTransportError) throw lastError;
168
+ throw new HttpsTransportError(
169
+ lastError instanceof Error ? lastError.message : "Request failed",
170
+ lastError
171
+ );
172
+ }
173
+ function sendSingleRequest(url, payload, headers, timeoutMs, signal, requestImpl) {
174
+ return new Promise((resolve, reject) => {
175
+ const finalHeaders = {
176
+ ...headers,
177
+ "Content-Length": payload.byteLength
178
+ };
179
+ const reqOptions = {
180
+ method: "POST",
181
+ hostname: url.hostname,
182
+ port: url.port === "" ? void 0 : Number(url.port),
183
+ path: `${url.pathname}${url.search}`,
184
+ headers: finalHeaders,
185
+ // Explicit timeout at the socket level. Still complemented by a
186
+ // manual timer below because `timeout` only fires when the socket
187
+ // is idle — it does not cover "TLS handshake hangs forever".
188
+ timeout: timeoutMs
189
+ };
190
+ let settled = false;
191
+ let cleanup = () => {
192
+ };
193
+ const settle = (fn) => {
194
+ if (settled) return;
195
+ settled = true;
196
+ cleanup();
197
+ fn();
198
+ };
199
+ const req = requestImpl(reqOptions, (res) => {
200
+ const chunks = [];
201
+ res.on("data", (chunk) => {
202
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf-8") : chunk);
203
+ });
204
+ res.on("end", () => {
205
+ const raw = Buffer.concat(chunks).toString("utf-8");
206
+ const status = res.statusCode ?? 0;
207
+ if (status < 200 || status >= 300) {
208
+ settle(() => reject(new HttpsStatusError(status, raw)));
209
+ return;
210
+ }
211
+ if (status === 204 || raw.length === 0) {
212
+ settle(() => resolve({ status, body: void 0, raw }));
213
+ return;
214
+ }
215
+ try {
216
+ const parsed = JSON.parse(raw);
217
+ settle(() => resolve({ status, body: parsed, raw }));
218
+ } catch (err) {
219
+ settle(() => reject(new HttpsBodyParseError(status, err)));
220
+ }
221
+ });
222
+ res.on("error", (err) => {
223
+ settle(() => reject(new HttpsTransportError(`Response stream error: ${err.message}`, err)));
224
+ });
225
+ });
226
+ const timer = setTimeout(() => {
227
+ settle(() => {
228
+ req.destroy(new Error("Request timed out"));
229
+ reject(new HttpsTransportError(`Request timed out after ${timeoutMs}ms`));
230
+ });
231
+ }, timeoutMs);
232
+ if (typeof timer.unref === "function") timer.unref();
233
+ const onAbort = () => {
234
+ settle(() => {
235
+ req.destroy(new Error("Aborted"));
236
+ reject(new HttpsTransportError("Request aborted"));
237
+ });
238
+ };
239
+ cleanup = () => {
240
+ clearTimeout(timer);
241
+ if (signal !== void 0) {
242
+ signal.removeEventListener("abort", onAbort);
243
+ }
244
+ };
245
+ req.on("error", (err) => {
246
+ settle(() => reject(new HttpsTransportError(`fetch failed: ${err.message}`, err)));
247
+ });
248
+ req.on("timeout", () => {
249
+ settle(() => {
250
+ req.destroy(new Error("Request timed out"));
251
+ reject(new HttpsTransportError(`Request timed out after ${timeoutMs}ms`));
252
+ });
253
+ });
254
+ if (signal !== void 0) {
255
+ if (signal.aborted) {
256
+ req.destroy(new Error("Aborted"));
257
+ settle(() => reject(new HttpsTransportError("Request aborted")));
258
+ return;
259
+ }
260
+ signal.addEventListener("abort", onAbort, { once: true });
261
+ }
262
+ req.end(payload);
263
+ });
264
+ }
265
+ function sleep(ms, scheduler, signal) {
266
+ return new Promise((resolve, reject) => {
267
+ let settled = false;
268
+ let cleanup = () => {
269
+ };
270
+ const settle = (fn) => {
271
+ if (settled) return;
272
+ settled = true;
273
+ cleanup();
274
+ fn();
275
+ };
276
+ const onAbort = () => {
277
+ settle(() => reject(new HttpsTransportError("Request aborted")));
278
+ };
279
+ const timer = scheduler(() => {
280
+ settle(resolve);
281
+ }, ms);
282
+ if (typeof timer.unref === "function") timer.unref();
283
+ cleanup = () => {
284
+ clearTimeout(timer);
285
+ if (signal !== void 0) {
286
+ signal.removeEventListener("abort", onAbort);
287
+ }
288
+ };
289
+ if (signal !== void 0) {
290
+ if (signal.aborted) {
291
+ onAbort();
292
+ return;
293
+ }
294
+ signal.addEventListener("abort", onAbort, { once: true });
295
+ }
296
+ });
297
+ }
298
+
299
+ // src/init-client.ts
300
+ var GLASSTRACE_DIR = ".glasstrace";
301
+ var CONFIG_FILE = "config";
302
+ var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
303
+ var INIT_TIMEOUT_MS = 1e4;
304
+ var fsPathAsyncCache;
305
+ async function loadFsPathAsync() {
306
+ if (fsPathAsyncCache !== void 0) return fsPathAsyncCache;
307
+ try {
308
+ const [fs2, path2] = await Promise.all([
309
+ import("node:fs/promises"),
310
+ import("node:path")
311
+ ]);
312
+ fsPathAsyncCache = { fs: fs2, path: path2 };
313
+ return fsPathAsyncCache;
314
+ } catch {
315
+ fsPathAsyncCache = null;
316
+ return null;
317
+ }
318
+ }
319
+ function loadFsSyncOrNull() {
320
+ try {
321
+ const fs2 = __require("node:fs");
322
+ const path2 = __require("node:path");
323
+ return { readFileSync: fs2.readFileSync, join: path2.join };
324
+ } catch {
325
+ return null;
326
+ }
327
+ }
328
+ var transportOverride = null;
329
+ var currentConfig = null;
330
+ var configCacheChecked = false;
331
+ var rateLimitBackoff = false;
332
+ var lastInitSucceeded = false;
333
+ function loadCachedConfig(projectRoot) {
334
+ const modules = loadFsSyncOrNull();
335
+ if (!modules) return null;
336
+ const root = projectRoot ?? process.cwd();
337
+ const configPath = modules.join(root, GLASSTRACE_DIR, CONFIG_FILE);
338
+ try {
339
+ const content = modules.readFileSync(configPath, "utf-8");
340
+ const parsed = JSON.parse(content);
341
+ const cached = SdkCachedConfigSchema.parse(parsed);
342
+ const age = Date.now() - cached.cachedAt;
343
+ if (age > TWENTY_FOUR_HOURS_MS) {
344
+ console.warn(
345
+ `[glasstrace] Cached config is ${Math.round(age / 36e5)}h old. Will refresh on next init.`
346
+ );
347
+ }
348
+ const result = SdkInitResponseSchema.safeParse(cached.response);
349
+ if (result.success) {
350
+ recordConfigSync(cached.cachedAt);
351
+ return result.data;
352
+ }
353
+ console.warn("[glasstrace] Cached config failed validation. Using defaults.");
354
+ return null;
355
+ } catch {
356
+ return null;
357
+ }
358
+ }
359
+ async function saveCachedConfig(response, projectRoot) {
360
+ const modules = await loadFsPathAsync();
361
+ if (!modules) return;
362
+ const root = projectRoot ?? process.cwd();
363
+ const dirPath = modules.path.join(root, GLASSTRACE_DIR);
364
+ const configPath = modules.path.join(dirPath, CONFIG_FILE);
365
+ const tmpPath = `${configPath}.tmp`;
366
+ try {
367
+ await modules.fs.mkdir(dirPath, { recursive: true, mode: 448 });
368
+ await modules.fs.chmod(dirPath, 448);
369
+ const cached = {
370
+ response,
371
+ cachedAt: Date.now()
372
+ };
373
+ await modules.fs.writeFile(tmpPath, JSON.stringify(cached), {
374
+ encoding: "utf-8",
375
+ mode: 384
376
+ });
377
+ try {
378
+ await modules.fs.chmod(tmpPath, 384);
379
+ await modules.fs.rename(tmpPath, configPath);
380
+ } catch (renameErr) {
381
+ try {
382
+ await modules.fs.unlink(tmpPath);
383
+ } catch {
384
+ }
385
+ throw renameErr;
386
+ }
387
+ await modules.fs.chmod(configPath, 384);
388
+ } catch (err) {
389
+ console.warn(
390
+ `[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`
391
+ );
392
+ }
393
+ }
394
+ async function sendInitRequest(config, anonKey, sdkVersion, importGraph, healthReport, diagnostics, signal) {
395
+ const effectiveKey = config.apiKey || anonKey;
396
+ if (!effectiveKey) {
397
+ throw new Error("No API key available for init request");
398
+ }
399
+ const payload = {
400
+ sdkVersion
401
+ };
402
+ if (config.apiKey && anonKey) {
403
+ payload.anonKey = anonKey;
404
+ }
405
+ if (config.environment) {
406
+ payload.environment = config.environment;
407
+ }
408
+ if (importGraph) {
409
+ payload.importGraph = importGraph;
410
+ }
411
+ if (healthReport) {
412
+ payload.healthReport = healthReport;
413
+ }
414
+ if (diagnostics) {
415
+ payload.diagnostics = diagnostics;
416
+ }
417
+ const url = `${config.endpoint}/v1/sdk/init`;
418
+ const transport = transportOverride ?? httpsPostJson;
419
+ let result;
420
+ try {
421
+ result = await transport(url, payload, {
422
+ headers: {
423
+ "Content-Type": "application/json",
424
+ Authorization: `Bearer ${effectiveKey}`
425
+ },
426
+ timeoutMs: INIT_TIMEOUT_MS,
427
+ signal
428
+ });
429
+ } catch (err) {
430
+ if (err instanceof HttpsStatusError) {
431
+ const error = new Error(`Init request failed with status ${err.status}`);
432
+ error.status = err.status;
433
+ throw error;
434
+ }
435
+ if (err instanceof HttpsBodyParseError) {
436
+ const cause = err.cause;
437
+ if (cause instanceof SyntaxError) throw cause;
438
+ throw err;
439
+ }
440
+ if (err instanceof HttpsTransportError) {
441
+ throw err;
442
+ }
443
+ throw err;
444
+ }
445
+ return SdkInitResponseSchema.parse(result.body);
446
+ }
447
+ async function writeClaimedKey(newApiKey, projectRoot) {
448
+ const modules = await loadFsPathAsync();
449
+ if (modules) {
450
+ const root = projectRoot ?? process.cwd();
451
+ const envLocalPath = modules.path.join(root, ".env.local");
452
+ let envLocalWritten = false;
453
+ try {
454
+ let content;
455
+ try {
456
+ content = await modules.fs.readFile(envLocalPath, "utf-8");
457
+ if (/^GLASSTRACE_API_KEY=.*/m.test(content)) {
458
+ content = content.replace(
459
+ /^GLASSTRACE_API_KEY=.*$/gm,
460
+ `GLASSTRACE_API_KEY=${newApiKey}`
461
+ );
462
+ } else {
463
+ if (content.length > 0 && !content.endsWith("\n")) {
464
+ content += "\n";
465
+ }
466
+ content += `GLASSTRACE_API_KEY=${newApiKey}
467
+ `;
468
+ }
469
+ } catch (readErr) {
470
+ const code = readErr instanceof Error ? readErr.code : void 0;
471
+ if (code !== "ENOENT") {
472
+ throw readErr;
473
+ }
474
+ content = `GLASSTRACE_API_KEY=${newApiKey}
475
+ `;
476
+ }
477
+ await modules.fs.writeFile(envLocalPath, content, { encoding: "utf-8", mode: 384 });
478
+ await modules.fs.chmod(envLocalPath, 384);
479
+ envLocalWritten = true;
480
+ } catch {
481
+ }
482
+ if (envLocalWritten) {
483
+ try {
484
+ process.stderr.write(
485
+ "[glasstrace] Account claimed! API key written to .env.local. Restart your dev server to use it.\n"
486
+ );
487
+ } catch {
488
+ }
489
+ return;
490
+ }
491
+ let claimedKeyWritten = false;
492
+ try {
493
+ const dirPath = modules.path.join(root, GLASSTRACE_DIR);
494
+ await modules.fs.mkdir(dirPath, { recursive: true, mode: 448 });
495
+ await modules.fs.chmod(dirPath, 448);
496
+ const claimedKeyPath = modules.path.join(dirPath, "claimed-key");
497
+ await modules.fs.writeFile(claimedKeyPath, newApiKey, {
498
+ encoding: "utf-8",
499
+ mode: 384
500
+ });
501
+ await modules.fs.chmod(claimedKeyPath, 384);
502
+ claimedKeyWritten = true;
503
+ } catch {
504
+ }
505
+ if (claimedKeyWritten) {
506
+ try {
507
+ process.stderr.write(
508
+ "[glasstrace] Account claimed! API key written to .glasstrace/claimed-key. Copy it to your .env.local file.\n"
509
+ );
510
+ } catch {
511
+ }
512
+ return;
513
+ }
514
+ }
515
+ try {
516
+ process.stderr.write(
517
+ "[glasstrace] Account claimed but could not write key to disk. Visit your dashboard settings to rotate and retrieve a new API key.\n"
518
+ );
519
+ } catch {
520
+ }
521
+ }
522
+ async function performInit(config, anonKey, sdkVersion, healthReport) {
523
+ lastInitSucceeded = false;
524
+ if (rateLimitBackoff) {
525
+ rateLimitBackoff = false;
526
+ return null;
527
+ }
528
+ try {
529
+ const effectiveKey = config.apiKey || anonKey;
530
+ if (!effectiveKey) {
531
+ console.warn("[glasstrace] No API key available for init request.");
532
+ return null;
533
+ }
534
+ try {
535
+ const result = await sendInitRequest(
536
+ config,
537
+ anonKey,
538
+ sdkVersion,
539
+ void 0,
540
+ healthReport ?? void 0,
541
+ void 0
542
+ );
543
+ currentConfig = result;
544
+ recordConfigSync(Date.now());
545
+ if (healthReport) {
546
+ acknowledgeHealthReport(healthReport);
547
+ }
548
+ lastInitSucceeded = true;
549
+ await saveCachedConfig(result);
550
+ if (result.claimResult) {
551
+ try {
552
+ await writeClaimedKey(result.claimResult.newApiKey);
553
+ } catch {
554
+ }
555
+ return { claimResult: result.claimResult };
556
+ }
557
+ return null;
558
+ } catch (err) {
559
+ recordInitFailure();
560
+ if (err instanceof HttpsTransportError) {
561
+ if (/timed out|aborted/i.test(err.message)) {
562
+ console.warn("[glasstrace] ingestion_unreachable: Init request timed out.");
563
+ } else {
564
+ console.warn(`[glasstrace] ingestion_unreachable: ${err.message}`);
565
+ }
566
+ return null;
567
+ }
568
+ const status = err.status;
569
+ if (status === 401) {
570
+ console.warn(
571
+ "[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY."
572
+ );
573
+ return null;
574
+ }
575
+ if (status === 429) {
576
+ console.warn("[glasstrace] ingestion_rate_limited: Backing off.");
577
+ rateLimitBackoff = true;
578
+ return null;
579
+ }
580
+ if (typeof status === "number" && status >= 400) {
581
+ console.warn(
582
+ `[glasstrace] Init request failed with status ${status}. Using cached config.`
583
+ );
584
+ return null;
585
+ }
586
+ if (err instanceof Error && err.name === "ZodError") {
587
+ console.warn(
588
+ "[glasstrace] Init response failed validation (schema version mismatch?). Using cached config."
589
+ );
590
+ return null;
591
+ }
592
+ console.warn(
593
+ `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`
594
+ );
595
+ return null;
596
+ }
597
+ } catch (err) {
598
+ console.warn(
599
+ `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`
600
+ );
601
+ }
602
+ return null;
603
+ }
604
+ function getActiveConfig() {
605
+ if (currentConfig) {
606
+ return currentConfig.config;
607
+ }
608
+ if (!configCacheChecked) {
609
+ configCacheChecked = true;
610
+ const cached = loadCachedConfig();
611
+ if (cached) {
612
+ currentConfig = cached;
613
+ return cached.config;
614
+ }
615
+ }
616
+ return { ...DEFAULT_CAPTURE_CONFIG };
617
+ }
618
+ function getLinkedAccountId() {
619
+ return currentConfig?.linkedAccountId;
620
+ }
621
+ function getClaimResult() {
622
+ return currentConfig?.claimResult;
623
+ }
624
+ function _setCurrentConfig(config) {
625
+ currentConfig = config;
626
+ }
627
+ function consumeRateLimitFlag() {
628
+ if (rateLimitBackoff) {
629
+ rateLimitBackoff = false;
630
+ return true;
631
+ }
632
+ return false;
633
+ }
634
+ function didLastInitSucceed() {
635
+ return lastInitSucceeded;
636
+ }
637
+ async function verifyInitReachable(config, anonKey, sdkVersion) {
638
+ try {
639
+ const response = await sendInitRequest(config, anonKey, sdkVersion);
640
+ return { ok: true, response };
641
+ } catch (err) {
642
+ const status = err.status;
643
+ if (typeof status === "number") {
644
+ return {
645
+ ok: false,
646
+ reason: "rejected",
647
+ status,
648
+ detail: `server returned HTTP ${status}`
649
+ };
650
+ }
651
+ if (err instanceof Error && (err.name === "ZodError" || err.name === "SyntaxError")) {
652
+ return {
653
+ ok: false,
654
+ reason: "malformed",
655
+ detail: "server returned malformed response"
656
+ };
657
+ }
658
+ const rawMessage = err instanceof Error ? err.message : String(err);
659
+ const detail = rawMessage.startsWith("fetch failed: ") ? rawMessage.slice("fetch failed: ".length) : rawMessage;
660
+ return { ok: false, reason: "transport", detail };
661
+ }
662
+ }
663
+
664
+ // src/import-graph.ts
665
+ import * as fs from "node:fs/promises";
666
+ import * as fsSync from "node:fs";
667
+ import * as path from "node:path";
668
+ import * as crypto from "node:crypto";
669
+ var MAX_TEST_FILES = 5e3;
670
+ var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", ".turbo"]);
671
+ var DEFAULT_TEST_PATTERNS = [
672
+ /\.test\.tsx?$/,
673
+ /\.spec\.tsx?$/
674
+ ];
675
+ function globToRegExp(glob) {
676
+ const DOUBLE_STAR_PLACEHOLDER = "\0DSTAR\0";
677
+ const regexStr = glob.replace(/\*\*\//g, DOUBLE_STAR_PLACEHOLDER).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]+").replace(new RegExp(DOUBLE_STAR_PLACEHOLDER.replace(/\0/g, "\\0"), "g"), "(?:.+/)?");
678
+ return new RegExp("^" + regexStr + "$");
679
+ }
680
+ function loadCustomTestPatterns(projectRoot) {
681
+ const configNames = [
682
+ "vitest.config.ts",
683
+ "vitest.config.js",
684
+ "vitest.config.mts",
685
+ "vitest.config.mjs",
686
+ "vite.config.ts",
687
+ "vite.config.js",
688
+ "vite.config.mts",
689
+ "vite.config.mjs",
690
+ "jest.config.ts",
691
+ "jest.config.js",
692
+ "jest.config.mts",
693
+ "jest.config.mjs"
694
+ ];
695
+ for (const name of configNames) {
696
+ const configPath = path.join(projectRoot, name);
697
+ let content;
698
+ try {
699
+ content = fsSync.readFileSync(configPath, "utf-8");
700
+ } catch {
701
+ continue;
702
+ }
703
+ try {
704
+ const isJest = name.startsWith("jest.");
705
+ let includeMatch = null;
706
+ if (isJest) {
707
+ includeMatch = /testMatch\s*:\s*\[([^\]]*)\]/s.exec(content);
708
+ } else {
709
+ const testBlockMatch = /\btest\s*[:{]\s*/s.exec(content);
710
+ if (testBlockMatch) {
711
+ const afterTest = content.slice(testBlockMatch.index, testBlockMatch.index + 500);
712
+ includeMatch = /include\s*:\s*\[([^\]]*)\]/s.exec(afterTest);
713
+ }
714
+ }
715
+ if (!includeMatch) {
716
+ continue;
717
+ }
718
+ const arrayContent = includeMatch[1];
719
+ const stringRegex = /['"]([^'"]+)['"]/g;
720
+ const patterns = [];
721
+ let match;
722
+ match = stringRegex.exec(arrayContent);
723
+ while (match !== null) {
724
+ patterns.push(globToRegExp(match[1]));
725
+ match = stringRegex.exec(arrayContent);
726
+ }
727
+ if (patterns.length > 0) {
728
+ return patterns;
729
+ }
730
+ } catch {
731
+ continue;
732
+ }
733
+ }
734
+ return [];
735
+ }
736
+ async function discoverTestFiles(projectRoot) {
737
+ const customPatterns = loadCustomTestPatterns(projectRoot);
738
+ const testPatterns = [...DEFAULT_TEST_PATTERNS, ...customPatterns];
739
+ const results = [];
740
+ try {
741
+ await walkForTests(projectRoot, projectRoot, results, testPatterns);
742
+ } catch {
743
+ return [];
744
+ }
745
+ return results.slice(0, MAX_TEST_FILES);
746
+ }
747
+ async function walkForTests(baseDir, currentDir, results, testPatterns) {
748
+ if (results.length >= MAX_TEST_FILES) {
749
+ return;
750
+ }
751
+ let entries;
752
+ try {
753
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
754
+ } catch {
755
+ return;
756
+ }
757
+ for (const entry of entries) {
758
+ if (results.length >= MAX_TEST_FILES) {
759
+ return;
760
+ }
761
+ const fullPath = path.join(currentDir, entry.name);
762
+ if (entry.isDirectory()) {
763
+ if (EXCLUDED_DIRS.has(entry.name)) {
764
+ continue;
765
+ }
766
+ await walkForTests(baseDir, fullPath, results, testPatterns);
767
+ } else if (entry.isFile()) {
768
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
769
+ const isTestFile = testPatterns.some((p) => p.test(entry.name) || p.test(relativePath)) || relativePath.includes("__tests__");
770
+ if (isTestFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))) {
771
+ results.push(relativePath);
772
+ }
773
+ }
774
+ }
775
+ }
776
+ function extractImports(fileContent) {
777
+ const seen = /* @__PURE__ */ new Set();
778
+ const imports = [];
779
+ const addUnique = (importPath) => {
780
+ if (!seen.has(importPath)) {
781
+ seen.add(importPath);
782
+ imports.push(importPath);
783
+ }
784
+ };
785
+ const esFromImportRegex = /\bimport\b[^'"]+\bfrom\s+['"]([^'"]+)['"]/g;
786
+ const esSideEffectRegex = /\bimport\s+['"]([^'"]+)['"]/g;
787
+ let match;
788
+ match = esFromImportRegex.exec(fileContent);
789
+ while (match !== null) {
790
+ addUnique(match[1]);
791
+ match = esFromImportRegex.exec(fileContent);
792
+ }
793
+ match = esSideEffectRegex.exec(fileContent);
794
+ while (match !== null) {
795
+ addUnique(match[1]);
796
+ match = esSideEffectRegex.exec(fileContent);
797
+ }
798
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
799
+ match = requireRegex.exec(fileContent);
800
+ while (match !== null) {
801
+ addUnique(match[1]);
802
+ match = requireRegex.exec(fileContent);
803
+ }
804
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
805
+ match = dynamicImportRegex.exec(fileContent);
806
+ while (match !== null) {
807
+ addUnique(match[1]);
808
+ match = dynamicImportRegex.exec(fileContent);
809
+ }
810
+ return imports;
811
+ }
812
+ async function buildImportGraph(projectRoot) {
813
+ const testFiles = await discoverTestFiles(projectRoot);
814
+ const graph = {};
815
+ for (const testFile of testFiles) {
816
+ const fullPath = path.join(projectRoot, testFile);
817
+ try {
818
+ const content = await fs.readFile(fullPath, "utf-8");
819
+ const imports = extractImports(content);
820
+ graph[testFile] = imports;
821
+ } catch {
822
+ continue;
823
+ }
824
+ }
825
+ const sortedKeys = Object.keys(graph).sort();
826
+ const serialized = sortedKeys.map((key) => `${key}:${JSON.stringify(graph[key])}`).join("\n");
827
+ const hashHex = crypto.createHash("sha256").update(serialized).digest("hex");
828
+ const buildHash = createBuildHash(hashHex);
829
+ return { buildHash, graph };
830
+ }
831
+
832
+ export {
833
+ recordSpansExported,
834
+ recordSpansDropped,
835
+ collectHealthReport,
836
+ loadCachedConfig,
837
+ saveCachedConfig,
838
+ sendInitRequest,
839
+ performInit,
840
+ getActiveConfig,
841
+ getLinkedAccountId,
842
+ getClaimResult,
843
+ _setCurrentConfig,
844
+ consumeRateLimitFlag,
845
+ didLastInitSucceed,
846
+ verifyInitReachable,
847
+ discoverTestFiles,
848
+ extractImports,
849
+ buildImportGraph
850
+ };
851
+ //# sourceMappingURL=chunk-5C2TJFLB.js.map