@glasstrace/sdk 0.19.0 → 0.20.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 (63) hide show
  1. package/README.md +79 -0
  2. package/dist/chunk-BT2OCXCG.js +178 -0
  3. package/dist/chunk-BT2OCXCG.js.map +1 -0
  4. package/dist/{chunk-F2TZRBEH.js → chunk-DO2YPMQ5.js} +9 -181
  5. package/dist/chunk-DO2YPMQ5.js.map +1 -0
  6. package/dist/{chunk-YPXW2TN3.js → chunk-IP4NMDJK.js} +2 -2
  7. package/dist/{chunk-VN3GZDV6.js → chunk-IQN6TRMQ.js} +2 -2
  8. package/dist/{chunk-XNDHQN4S.js → chunk-LU3PPAOQ.js} +288 -54
  9. package/dist/chunk-LU3PPAOQ.js.map +1 -0
  10. package/dist/chunk-R4DAIPXD.js +4461 -0
  11. package/dist/chunk-R4DAIPXD.js.map +1 -0
  12. package/dist/{chunk-5N2IR4EO.js → chunk-TQ54WLCZ.js} +1 -1
  13. package/dist/{chunk-5N2IR4EO.js.map → chunk-TQ54WLCZ.js.map} +1 -1
  14. package/dist/chunk-Z2EGETTT.js +204 -0
  15. package/dist/chunk-Z2EGETTT.js.map +1 -0
  16. package/dist/cli/init.cjs +483 -152
  17. package/dist/cli/init.cjs.map +1 -1
  18. package/dist/cli/init.d.cts +48 -2
  19. package/dist/cli/init.d.ts +48 -2
  20. package/dist/cli/init.js +109 -7
  21. package/dist/cli/init.js.map +1 -1
  22. package/dist/cli/mcp-add.cjs.map +1 -1
  23. package/dist/cli/mcp-add.js +2 -2
  24. package/dist/cli/uninit.cjs +174 -54
  25. package/dist/cli/uninit.cjs.map +1 -1
  26. package/dist/cli/uninit.d.cts +2 -0
  27. package/dist/cli/uninit.d.ts +2 -0
  28. package/dist/cli/uninit.js +2 -1
  29. package/dist/edge-entry-Ds2fNOeh.d.ts +157 -0
  30. package/dist/edge-entry-FJFKkeFF.d.cts +157 -0
  31. package/dist/edge-entry.cjs +14939 -0
  32. package/dist/edge-entry.cjs.map +1 -0
  33. package/dist/edge-entry.d.cts +6 -0
  34. package/dist/edge-entry.d.ts +6 -0
  35. package/dist/edge-entry.js +16 -0
  36. package/dist/index.cjs +13 -4
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d-DgeH-pNJ.d.cts +191 -0
  39. package/dist/index.d-DgeH-pNJ.d.ts +191 -0
  40. package/dist/index.d.cts +9 -461
  41. package/dist/index.d.ts +9 -461
  42. package/dist/index.js +32 -4609
  43. package/dist/index.js.map +1 -1
  44. package/dist/node-entry.cjs +22492 -0
  45. package/dist/node-entry.cjs.map +1 -0
  46. package/dist/node-entry.d.cts +10 -0
  47. package/dist/node-entry.d.ts +10 -0
  48. package/dist/node-entry.js +101 -0
  49. package/dist/node-entry.js.map +1 -0
  50. package/dist/node-subpath.cjs +14506 -0
  51. package/dist/node-subpath.cjs.map +1 -0
  52. package/dist/node-subpath.d.cts +132 -0
  53. package/dist/node-subpath.d.ts +132 -0
  54. package/dist/node-subpath.js +30 -0
  55. package/dist/node-subpath.js.map +1 -0
  56. package/dist/{source-map-uploader-VPDZWWM2.js → source-map-uploader-YXWO6JLN.js} +3 -3
  57. package/dist/source-map-uploader-YXWO6JLN.js.map +1 -0
  58. package/package.json +4 -1
  59. package/dist/chunk-F2TZRBEH.js.map +0 -1
  60. package/dist/chunk-XNDHQN4S.js.map +0 -1
  61. /package/dist/{chunk-YPXW2TN3.js.map → chunk-IP4NMDJK.js.map} +0 -0
  62. /package/dist/{chunk-VN3GZDV6.js.map → chunk-IQN6TRMQ.js.map} +0 -0
  63. /package/dist/{source-map-uploader-VPDZWWM2.js.map → edge-entry.js.map} +0 -0
package/README.md CHANGED
@@ -155,6 +155,85 @@ GLASSTRACE_SUPPRESS_ACTION_NUDGE=1
155
155
  The nudge never fires in production (detected via `NODE_ENV` or
156
156
  `VERCEL_ENV`) unless `GLASSTRACE_FORCE_ENABLE=true` is also set.
157
157
 
158
+ ## Browser-extension discovery
159
+
160
+ `glasstrace init` writes a small static file at
161
+ `public/.well-known/glasstrace.json` (or `static/.well-known/glasstrace.json`
162
+ on SvelteKit) so the Glasstrace browser extension can discover your
163
+ project's anonymous key without a runtime HTTP handler. The file
164
+ contains only a schema version and the project's anonymous key — it
165
+ is public metadata, not a secret, and should be committed to source
166
+ control alongside the rest of your project.
167
+
168
+ The SDK no longer requires `createDiscoveryHandler` to be wired into
169
+ your server. If you previously registered the handler (for example,
170
+ inside `middleware.ts` or `proxy.ts` on Next.js), you can remove the
171
+ handler code and the extension will read the static file instead.
172
+
173
+ ### Migration: removing the runtime discovery handler
174
+
175
+ **Next.js 15 and earlier (`middleware.ts`):**
176
+
177
+ ```ts
178
+ // Before: middleware.ts
179
+ import { createDiscoveryHandler } from "@glasstrace/sdk";
180
+ import { NextResponse } from "next/server";
181
+
182
+ const discoveryHandler = createDiscoveryHandler(/* getAnonKey */, /* getSessionId */);
183
+
184
+ export async function middleware(req: Request) {
185
+ const response = await discoveryHandler(req);
186
+ if (response !== null) return response;
187
+ return NextResponse.next();
188
+ }
189
+ ```
190
+
191
+ ```ts
192
+ // After: middleware.ts (only the non-Glasstrace logic remains)
193
+ import { NextResponse } from "next/server";
194
+
195
+ export function middleware(_req: Request) {
196
+ return NextResponse.next();
197
+ }
198
+ ```
199
+
200
+ **Next.js 16 and later (`proxy.ts`):**
201
+
202
+ Next.js 16 replaces `middleware.ts` with `proxy.ts`. If your project
203
+ invoked the discovery handler from `middleware.ts`, migrate it to the
204
+ new file convention and drop the handler in the same edit:
205
+
206
+ ```ts
207
+ // Before: proxy.ts (Next 16+)
208
+ import { createDiscoveryHandler } from "@glasstrace/sdk";
209
+ import { NextResponse } from "next/server";
210
+
211
+ const discoveryHandler = createDiscoveryHandler(/* getAnonKey */, /* getSessionId */);
212
+
213
+ export async function proxy(req: Request) {
214
+ const response = await discoveryHandler(req);
215
+ if (response !== null) return response;
216
+ return NextResponse.next();
217
+ }
218
+ ```
219
+
220
+ ```ts
221
+ // After: proxy.ts (Next 16+)
222
+ import { NextResponse } from "next/server";
223
+
224
+ export function proxy(_req: Request) {
225
+ return NextResponse.next();
226
+ }
227
+ ```
228
+
229
+ If `proxy.ts` no longer does anything else, you can delete it entirely.
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.
236
+
158
237
  ## Security
159
238
 
160
239
  The SDK transmits your API key exclusively via the `Authorization: Bearer`
@@ -0,0 +1,178 @@
1
+ import {
2
+ createBuildHash
3
+ } from "./chunk-TQ54WLCZ.js";
4
+
5
+ // src/import-graph.ts
6
+ import * as fs from "node:fs/promises";
7
+ import * as fsSync from "node:fs";
8
+ import * as path from "node:path";
9
+ import * as crypto from "node:crypto";
10
+ var MAX_TEST_FILES = 5e3;
11
+ var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", ".turbo"]);
12
+ var DEFAULT_TEST_PATTERNS = [
13
+ /\.test\.tsx?$/,
14
+ /\.spec\.tsx?$/
15
+ ];
16
+ function globToRegExp(glob) {
17
+ const DOUBLE_STAR_PLACEHOLDER = "\0DSTAR\0";
18
+ const regexStr = glob.replace(/\*\*\//g, DOUBLE_STAR_PLACEHOLDER).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]+").replace(new RegExp(DOUBLE_STAR_PLACEHOLDER.replace(/\0/g, "\\0"), "g"), "(?:.+/)?");
19
+ return new RegExp("^" + regexStr + "$");
20
+ }
21
+ function loadCustomTestPatterns(projectRoot) {
22
+ const configNames = [
23
+ "vitest.config.ts",
24
+ "vitest.config.js",
25
+ "vitest.config.mts",
26
+ "vitest.config.mjs",
27
+ "vite.config.ts",
28
+ "vite.config.js",
29
+ "vite.config.mts",
30
+ "vite.config.mjs",
31
+ "jest.config.ts",
32
+ "jest.config.js",
33
+ "jest.config.mts",
34
+ "jest.config.mjs"
35
+ ];
36
+ for (const name of configNames) {
37
+ const configPath = path.join(projectRoot, name);
38
+ let content;
39
+ try {
40
+ content = fsSync.readFileSync(configPath, "utf-8");
41
+ } catch {
42
+ continue;
43
+ }
44
+ try {
45
+ const isJest = name.startsWith("jest.");
46
+ let includeMatch = null;
47
+ if (isJest) {
48
+ includeMatch = /testMatch\s*:\s*\[([^\]]*)\]/s.exec(content);
49
+ } else {
50
+ const testBlockMatch = /\btest\s*[:{]\s*/s.exec(content);
51
+ if (testBlockMatch) {
52
+ const afterTest = content.slice(testBlockMatch.index, testBlockMatch.index + 500);
53
+ includeMatch = /include\s*:\s*\[([^\]]*)\]/s.exec(afterTest);
54
+ }
55
+ }
56
+ if (!includeMatch) {
57
+ continue;
58
+ }
59
+ const arrayContent = includeMatch[1];
60
+ const stringRegex = /['"]([^'"]+)['"]/g;
61
+ const patterns = [];
62
+ let match;
63
+ match = stringRegex.exec(arrayContent);
64
+ while (match !== null) {
65
+ patterns.push(globToRegExp(match[1]));
66
+ match = stringRegex.exec(arrayContent);
67
+ }
68
+ if (patterns.length > 0) {
69
+ return patterns;
70
+ }
71
+ } catch {
72
+ continue;
73
+ }
74
+ }
75
+ return [];
76
+ }
77
+ async function discoverTestFiles(projectRoot) {
78
+ const customPatterns = loadCustomTestPatterns(projectRoot);
79
+ const testPatterns = [...DEFAULT_TEST_PATTERNS, ...customPatterns];
80
+ const results = [];
81
+ try {
82
+ await walkForTests(projectRoot, projectRoot, results, testPatterns);
83
+ } catch {
84
+ return [];
85
+ }
86
+ return results.slice(0, MAX_TEST_FILES);
87
+ }
88
+ async function walkForTests(baseDir, currentDir, results, testPatterns) {
89
+ if (results.length >= MAX_TEST_FILES) {
90
+ return;
91
+ }
92
+ let entries;
93
+ try {
94
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
95
+ } catch {
96
+ return;
97
+ }
98
+ for (const entry of entries) {
99
+ if (results.length >= MAX_TEST_FILES) {
100
+ return;
101
+ }
102
+ const fullPath = path.join(currentDir, entry.name);
103
+ if (entry.isDirectory()) {
104
+ if (EXCLUDED_DIRS.has(entry.name)) {
105
+ continue;
106
+ }
107
+ await walkForTests(baseDir, fullPath, results, testPatterns);
108
+ } else if (entry.isFile()) {
109
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
110
+ const isTestFile = testPatterns.some((p) => p.test(entry.name) || p.test(relativePath)) || relativePath.includes("__tests__");
111
+ if (isTestFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))) {
112
+ results.push(relativePath);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ function extractImports(fileContent) {
118
+ const seen = /* @__PURE__ */ new Set();
119
+ const imports = [];
120
+ const addUnique = (importPath) => {
121
+ if (!seen.has(importPath)) {
122
+ seen.add(importPath);
123
+ imports.push(importPath);
124
+ }
125
+ };
126
+ const esFromImportRegex = /\bimport\b[^'"]+\bfrom\s+['"]([^'"]+)['"]/g;
127
+ const esSideEffectRegex = /\bimport\s+['"]([^'"]+)['"]/g;
128
+ let match;
129
+ match = esFromImportRegex.exec(fileContent);
130
+ while (match !== null) {
131
+ addUnique(match[1]);
132
+ match = esFromImportRegex.exec(fileContent);
133
+ }
134
+ match = esSideEffectRegex.exec(fileContent);
135
+ while (match !== null) {
136
+ addUnique(match[1]);
137
+ match = esSideEffectRegex.exec(fileContent);
138
+ }
139
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
140
+ match = requireRegex.exec(fileContent);
141
+ while (match !== null) {
142
+ addUnique(match[1]);
143
+ match = requireRegex.exec(fileContent);
144
+ }
145
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
146
+ match = dynamicImportRegex.exec(fileContent);
147
+ while (match !== null) {
148
+ addUnique(match[1]);
149
+ match = dynamicImportRegex.exec(fileContent);
150
+ }
151
+ return imports;
152
+ }
153
+ async function buildImportGraph(projectRoot) {
154
+ const testFiles = await discoverTestFiles(projectRoot);
155
+ const graph = {};
156
+ for (const testFile of testFiles) {
157
+ const fullPath = path.join(projectRoot, testFile);
158
+ try {
159
+ const content = await fs.readFile(fullPath, "utf-8");
160
+ const imports = extractImports(content);
161
+ graph[testFile] = imports;
162
+ } catch {
163
+ continue;
164
+ }
165
+ }
166
+ const sortedKeys = Object.keys(graph).sort();
167
+ const serialized = sortedKeys.map((key) => `${key}:${JSON.stringify(graph[key])}`).join("\n");
168
+ const hashHex = crypto.createHash("sha256").update(serialized).digest("hex");
169
+ const buildHash = createBuildHash(hashHex);
170
+ return { buildHash, graph };
171
+ }
172
+
173
+ export {
174
+ discoverTestFiles,
175
+ extractImports,
176
+ buildImportGraph
177
+ };
178
+ //# sourceMappingURL=chunk-BT2OCXCG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/import-graph.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as fsSync from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { createBuildHash, type ImportGraphPayload } from \"@glasstrace/protocol\";\n\n/** Maximum number of test files to process to prevent runaway in large projects */\nconst MAX_TEST_FILES = 5000;\n\n/** Directories to exclude from test file discovery */\nconst EXCLUDED_DIRS = new Set([\"node_modules\", \".next\", \".git\", \"dist\", \".turbo\"]);\n\n/** Conventional test file patterns */\nconst DEFAULT_TEST_PATTERNS = [\n /\\.test\\.tsx?$/,\n /\\.spec\\.tsx?$/,\n];\n\n/**\n * Converts a glob pattern (e.g. \"e2e/**\\/*.ts\") to an anchored RegExp.\n * Uses a placeholder to avoid `*` replacement corrupting the `**\\/` output.\n *\n * @param glob - A file glob pattern such as \"src/**\\/*.test.ts\".\n * @returns A RegExp that matches paths against the glob from start to end.\n */\nfunction globToRegExp(glob: string): RegExp {\n const DOUBLE_STAR_PLACEHOLDER = \"\\0DSTAR\\0\";\n const regexStr = glob\n .replace(/\\*\\*\\//g, DOUBLE_STAR_PLACEHOLDER) // protect **/ first\n .replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\") // escape all regex metacharacters (except *)\n .replace(/\\*/g, \"[^/]+\")\n .replace(new RegExp(DOUBLE_STAR_PLACEHOLDER.replace(/\\0/g, \"\\\\0\"), \"g\"), \"(?:.+/)?\");\n return new RegExp(\"^\" + regexStr + \"$\");\n}\n\n/**\n * Attempts to read include patterns from vitest.config.*, vite.config.*,\n * or jest.config.* files. Returns additional RegExp patterns extracted\n * from the config, or an empty array if no config is found or parsing fails.\n * This is best-effort — it reads the config as text and extracts patterns\n * via regex, without evaluating the JS.\n *\n * For Vitest/Vite configs, looks for `test.include` arrays.\n * For Jest configs, looks for `testMatch` arrays.\n * Does not support `testRegex` (string-based Jest pattern) — that is\n * left as future work.\n */\nfunction loadCustomTestPatterns(projectRoot: string): RegExp[] {\n const configNames = [\n \"vitest.config.ts\",\n \"vitest.config.js\",\n \"vitest.config.mts\",\n \"vitest.config.mjs\",\n \"vite.config.ts\",\n \"vite.config.js\",\n \"vite.config.mts\",\n \"vite.config.mjs\",\n \"jest.config.ts\",\n \"jest.config.js\",\n \"jest.config.mts\",\n \"jest.config.mjs\",\n ];\n\n for (const name of configNames) {\n const configPath = path.join(projectRoot, name);\n let content: string;\n try {\n content = fsSync.readFileSync(configPath, \"utf-8\");\n } catch {\n // Config file does not exist at this path — try next candidate\n continue;\n }\n\n try {\n const isJest = name.startsWith(\"jest.\");\n let includeMatch: RegExpExecArray | null = null;\n\n if (isJest) {\n // Jest: look for testMatch: [...]\n includeMatch = /testMatch\\s*:\\s*\\[([^\\]]*)\\]/s.exec(content);\n } else {\n // Vitest/Vite: look for `test` block's `include` to avoid\n // matching `coverage.include` or other unrelated arrays.\n // Strategy: find `test` property, then look for `include` within\n // the next ~500 chars (heuristic to stay within the test block).\n const testBlockMatch = /\\btest\\s*[:{]\\s*/s.exec(content);\n if (testBlockMatch) {\n const afterTest = content.slice(testBlockMatch.index, testBlockMatch.index + 500);\n includeMatch = /include\\s*:\\s*\\[([^\\]]*)\\]/s.exec(afterTest);\n }\n }\n\n if (!includeMatch) {\n continue;\n }\n\n const arrayContent = includeMatch[1];\n const stringRegex = /['\"]([^'\"]+)['\"]/g;\n const patterns: RegExp[] = [];\n let match: RegExpExecArray | null;\n match = stringRegex.exec(arrayContent);\n while (match !== null) {\n patterns.push(globToRegExp(match[1]));\n match = stringRegex.exec(arrayContent);\n }\n\n if (patterns.length > 0) {\n return patterns;\n }\n } catch {\n // Regex-based config parsing failed — fall through to next config file\n continue;\n }\n }\n\n return [];\n}\n\n/**\n * Discovers test files by scanning the project directory for conventional\n * test file patterns. Also reads vitest/jest config files for custom include\n * patterns and merges them with the defaults. Excludes node_modules/ and .next/.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns Relative POSIX paths from projectRoot, capped at {@link MAX_TEST_FILES}.\n */\nexport async function discoverTestFiles(\n projectRoot: string,\n): Promise<string[]> {\n const customPatterns = loadCustomTestPatterns(projectRoot);\n const testPatterns = [...DEFAULT_TEST_PATTERNS, ...customPatterns];\n const results: string[] = [];\n\n try {\n await walkForTests(projectRoot, projectRoot, results, testPatterns);\n } catch {\n // Project root directory does not exist or is unreadable — return empty\n return [];\n }\n\n return results.slice(0, MAX_TEST_FILES);\n}\n\n/** Recursively walks directories, collecting test file paths into `results`. */\nasync function walkForTests(\n baseDir: string,\n currentDir: string,\n results: string[],\n testPatterns: RegExp[],\n): Promise<void> {\n if (results.length >= MAX_TEST_FILES) {\n return;\n }\n\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n // Directory is unreadable (permissions, broken symlink) — skip subtree\n return;\n }\n\n for (const entry of entries) {\n if (results.length >= MAX_TEST_FILES) {\n return;\n }\n\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n if (EXCLUDED_DIRS.has(entry.name)) {\n continue;\n }\n await walkForTests(baseDir, fullPath, results, testPatterns);\n } else if (entry.isFile()) {\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n\n // Check if it matches test patterns or is in __tests__\n const isTestFile =\n testPatterns.some((p) => p.test(entry.name) || p.test(relativePath)) ||\n relativePath.includes(\"__tests__\");\n\n if (isTestFile && (entry.name.endsWith(\".ts\") || entry.name.endsWith(\".tsx\"))) {\n results.push(relativePath);\n }\n }\n }\n}\n\n/**\n * Extracts import paths from file content using regex.\n * Handles ES module imports, CommonJS requires, and dynamic imports.\n *\n * @param fileContent - The full text content of a TypeScript/JavaScript file.\n * @returns An array of import path strings as written in the source (e.g. \"./foo\", \"react\").\n */\nexport function extractImports(fileContent: string): string[] {\n const seen = new Set<string>();\n const imports: string[] = [];\n\n /** Adds a path to the result if not already present. */\n const addUnique = (importPath: string): void => {\n if (!seen.has(importPath)) {\n seen.add(importPath);\n imports.push(importPath);\n }\n };\n\n // ES module imports — split into two simple patterns to avoid\n // catastrophic backtracking (CodeQL ReDoS). The original single regex\n // used [\\w*{}\\s,]+ which overlapped with the surrounding \\s+, causing\n // polynomial backtracking. These replacements use [^'\"]+ which has\n // only one quantifier before the anchor, ensuring linear-time matching.\n // The [^'\"]+ class supports multiline destructured imports (e.g.,\n // import {\\n foo,\\n bar\\n} from 'path') since it does not exclude \\n.\n //\n // 1. Named/default/namespace: import { x } from 'path'\n const esFromImportRegex = /\\bimport\\b[^'\"]+\\bfrom\\s+['\"]([^'\"]+)['\"]/g;\n // 2. Side-effect: import 'path'\n const esSideEffectRegex = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\n\n let match: RegExpExecArray | null;\n\n match = esFromImportRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = esFromImportRegex.exec(fileContent);\n }\n\n match = esSideEffectRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = esSideEffectRegex.exec(fileContent);\n }\n\n // CommonJS: require('path')\n const requireRegex = /require\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n match = requireRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = requireRegex.exec(fileContent);\n }\n\n // Dynamic import: import('path')\n const dynamicImportRegex = /import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n match = dynamicImportRegex.exec(fileContent);\n while (match !== null) {\n addUnique(match[1]);\n match = dynamicImportRegex.exec(fileContent);\n }\n\n return imports;\n}\n\n/**\n * Builds an import graph mapping test file paths to their imported module paths.\n *\n * Discovers test files, reads each, extracts imports, and builds a graph.\n * Computes a deterministic buildHash from the serialized graph content.\n * Individual file read failures are silently skipped.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @returns An {@link ImportGraphPayload} containing the graph and a deterministic buildHash.\n */\nexport async function buildImportGraph(\n projectRoot: string,\n): Promise<ImportGraphPayload> {\n const testFiles = await discoverTestFiles(projectRoot);\n const graph: Record<string, string[]> = {};\n\n for (const testFile of testFiles) {\n const fullPath = path.join(projectRoot, testFile);\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n const imports = extractImports(content);\n graph[testFile] = imports;\n } catch {\n // File is unreadable (permissions, deleted between discovery and read) — skip\n continue;\n }\n }\n\n // Compute deterministic build hash from graph content\n const sortedKeys = Object.keys(graph).sort();\n const serialized = sortedKeys\n .map((key) => `${key}:${JSON.stringify(graph[key])}`)\n .join(\"\\n\");\n const hashHex = crypto\n .createHash(\"sha256\")\n .update(serialized)\n .digest(\"hex\");\n const buildHash = createBuildHash(hashHex);\n\n return { buildHash, graph };\n}\n"],"mappings":"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","names":[]}
@@ -1,9 +1,8 @@
1
1
  import {
2
2
  DEFAULT_CAPTURE_CONFIG,
3
3
  SdkCachedConfigSchema,
4
- SdkInitResponseSchema,
5
- createBuildHash
6
- } from "./chunk-5N2IR4EO.js";
4
+ SdkInitResponseSchema
5
+ } from "./chunk-TQ54WLCZ.js";
7
6
  import {
8
7
  __require
9
8
  } from "./chunk-NSBPE2FW.js";
@@ -305,11 +304,11 @@ var fsPathAsyncCache;
305
304
  async function loadFsPathAsync() {
306
305
  if (fsPathAsyncCache !== void 0) return fsPathAsyncCache;
307
306
  try {
308
- const [fs2, path2] = await Promise.all([
307
+ const [fs, path] = await Promise.all([
309
308
  import("node:fs/promises"),
310
309
  import("node:path")
311
310
  ]);
312
- fsPathAsyncCache = { fs: fs2, path: path2 };
311
+ fsPathAsyncCache = { fs, path };
313
312
  return fsPathAsyncCache;
314
313
  } catch {
315
314
  fsPathAsyncCache = null;
@@ -318,9 +317,9 @@ async function loadFsPathAsync() {
318
317
  }
319
318
  function loadFsSyncOrNull() {
320
319
  try {
321
- const fs2 = __require("node:fs");
322
- const path2 = __require("node:path");
323
- return { readFileSync: fs2.readFileSync, join: path2.join };
320
+ const fs = __require("node:fs");
321
+ const path = __require("node:path");
322
+ return { readFileSync: fs.readFileSync, join: path.join };
324
323
  } catch {
325
324
  return null;
326
325
  }
@@ -669,174 +668,6 @@ async function verifyInitReachable(config, anonKey, sdkVersion) {
669
668
  }
670
669
  }
671
670
 
672
- // src/import-graph.ts
673
- import * as fs from "node:fs/promises";
674
- import * as fsSync from "node:fs";
675
- import * as path from "node:path";
676
- import * as crypto from "node:crypto";
677
- var MAX_TEST_FILES = 5e3;
678
- var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", ".turbo"]);
679
- var DEFAULT_TEST_PATTERNS = [
680
- /\.test\.tsx?$/,
681
- /\.spec\.tsx?$/
682
- ];
683
- function globToRegExp(glob) {
684
- const DOUBLE_STAR_PLACEHOLDER = "\0DSTAR\0";
685
- const regexStr = glob.replace(/\*\*\//g, DOUBLE_STAR_PLACEHOLDER).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]+").replace(new RegExp(DOUBLE_STAR_PLACEHOLDER.replace(/\0/g, "\\0"), "g"), "(?:.+/)?");
686
- return new RegExp("^" + regexStr + "$");
687
- }
688
- function loadCustomTestPatterns(projectRoot) {
689
- const configNames = [
690
- "vitest.config.ts",
691
- "vitest.config.js",
692
- "vitest.config.mts",
693
- "vitest.config.mjs",
694
- "vite.config.ts",
695
- "vite.config.js",
696
- "vite.config.mts",
697
- "vite.config.mjs",
698
- "jest.config.ts",
699
- "jest.config.js",
700
- "jest.config.mts",
701
- "jest.config.mjs"
702
- ];
703
- for (const name of configNames) {
704
- const configPath = path.join(projectRoot, name);
705
- let content;
706
- try {
707
- content = fsSync.readFileSync(configPath, "utf-8");
708
- } catch {
709
- continue;
710
- }
711
- try {
712
- const isJest = name.startsWith("jest.");
713
- let includeMatch = null;
714
- if (isJest) {
715
- includeMatch = /testMatch\s*:\s*\[([^\]]*)\]/s.exec(content);
716
- } else {
717
- const testBlockMatch = /\btest\s*[:{]\s*/s.exec(content);
718
- if (testBlockMatch) {
719
- const afterTest = content.slice(testBlockMatch.index, testBlockMatch.index + 500);
720
- includeMatch = /include\s*:\s*\[([^\]]*)\]/s.exec(afterTest);
721
- }
722
- }
723
- if (!includeMatch) {
724
- continue;
725
- }
726
- const arrayContent = includeMatch[1];
727
- const stringRegex = /['"]([^'"]+)['"]/g;
728
- const patterns = [];
729
- let match;
730
- match = stringRegex.exec(arrayContent);
731
- while (match !== null) {
732
- patterns.push(globToRegExp(match[1]));
733
- match = stringRegex.exec(arrayContent);
734
- }
735
- if (patterns.length > 0) {
736
- return patterns;
737
- }
738
- } catch {
739
- continue;
740
- }
741
- }
742
- return [];
743
- }
744
- async function discoverTestFiles(projectRoot) {
745
- const customPatterns = loadCustomTestPatterns(projectRoot);
746
- const testPatterns = [...DEFAULT_TEST_PATTERNS, ...customPatterns];
747
- const results = [];
748
- try {
749
- await walkForTests(projectRoot, projectRoot, results, testPatterns);
750
- } catch {
751
- return [];
752
- }
753
- return results.slice(0, MAX_TEST_FILES);
754
- }
755
- async function walkForTests(baseDir, currentDir, results, testPatterns) {
756
- if (results.length >= MAX_TEST_FILES) {
757
- return;
758
- }
759
- let entries;
760
- try {
761
- entries = await fs.readdir(currentDir, { withFileTypes: true });
762
- } catch {
763
- return;
764
- }
765
- for (const entry of entries) {
766
- if (results.length >= MAX_TEST_FILES) {
767
- return;
768
- }
769
- const fullPath = path.join(currentDir, entry.name);
770
- if (entry.isDirectory()) {
771
- if (EXCLUDED_DIRS.has(entry.name)) {
772
- continue;
773
- }
774
- await walkForTests(baseDir, fullPath, results, testPatterns);
775
- } else if (entry.isFile()) {
776
- const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
777
- const isTestFile = testPatterns.some((p) => p.test(entry.name) || p.test(relativePath)) || relativePath.includes("__tests__");
778
- if (isTestFile && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))) {
779
- results.push(relativePath);
780
- }
781
- }
782
- }
783
- }
784
- function extractImports(fileContent) {
785
- const seen = /* @__PURE__ */ new Set();
786
- const imports = [];
787
- const addUnique = (importPath) => {
788
- if (!seen.has(importPath)) {
789
- seen.add(importPath);
790
- imports.push(importPath);
791
- }
792
- };
793
- const esFromImportRegex = /\bimport\b[^'"]+\bfrom\s+['"]([^'"]+)['"]/g;
794
- const esSideEffectRegex = /\bimport\s+['"]([^'"]+)['"]/g;
795
- let match;
796
- match = esFromImportRegex.exec(fileContent);
797
- while (match !== null) {
798
- addUnique(match[1]);
799
- match = esFromImportRegex.exec(fileContent);
800
- }
801
- match = esSideEffectRegex.exec(fileContent);
802
- while (match !== null) {
803
- addUnique(match[1]);
804
- match = esSideEffectRegex.exec(fileContent);
805
- }
806
- const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
807
- match = requireRegex.exec(fileContent);
808
- while (match !== null) {
809
- addUnique(match[1]);
810
- match = requireRegex.exec(fileContent);
811
- }
812
- const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
813
- match = dynamicImportRegex.exec(fileContent);
814
- while (match !== null) {
815
- addUnique(match[1]);
816
- match = dynamicImportRegex.exec(fileContent);
817
- }
818
- return imports;
819
- }
820
- async function buildImportGraph(projectRoot) {
821
- const testFiles = await discoverTestFiles(projectRoot);
822
- const graph = {};
823
- for (const testFile of testFiles) {
824
- const fullPath = path.join(projectRoot, testFile);
825
- try {
826
- const content = await fs.readFile(fullPath, "utf-8");
827
- const imports = extractImports(content);
828
- graph[testFile] = imports;
829
- } catch {
830
- continue;
831
- }
832
- }
833
- const sortedKeys = Object.keys(graph).sort();
834
- const serialized = sortedKeys.map((key) => `${key}:${JSON.stringify(graph[key])}`).join("\n");
835
- const hashHex = crypto.createHash("sha256").update(serialized).digest("hex");
836
- const buildHash = createBuildHash(hashHex);
837
- return { buildHash, graph };
838
- }
839
-
840
671
  export {
841
672
  recordSpansExported,
842
673
  recordSpansDropped,
@@ -851,9 +682,6 @@ export {
851
682
  _setCurrentConfig,
852
683
  consumeRateLimitFlag,
853
684
  didLastInitSucceed,
854
- verifyInitReachable,
855
- discoverTestFiles,
856
- extractImports,
857
- buildImportGraph
685
+ verifyInitReachable
858
686
  };
859
- //# sourceMappingURL=chunk-F2TZRBEH.js.map
687
+ //# sourceMappingURL=chunk-DO2YPMQ5.js.map