@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.
- package/README.md +79 -0
- package/dist/chunk-BT2OCXCG.js +178 -0
- package/dist/chunk-BT2OCXCG.js.map +1 -0
- package/dist/{chunk-F2TZRBEH.js → chunk-DO2YPMQ5.js} +9 -181
- package/dist/chunk-DO2YPMQ5.js.map +1 -0
- package/dist/{chunk-YPXW2TN3.js → chunk-IP4NMDJK.js} +2 -2
- package/dist/{chunk-VN3GZDV6.js → chunk-IQN6TRMQ.js} +2 -2
- package/dist/{chunk-XNDHQN4S.js → chunk-LU3PPAOQ.js} +288 -54
- package/dist/chunk-LU3PPAOQ.js.map +1 -0
- package/dist/chunk-R4DAIPXD.js +4461 -0
- package/dist/chunk-R4DAIPXD.js.map +1 -0
- package/dist/{chunk-5N2IR4EO.js → chunk-TQ54WLCZ.js} +1 -1
- package/dist/{chunk-5N2IR4EO.js.map → chunk-TQ54WLCZ.js.map} +1 -1
- package/dist/chunk-Z2EGETTT.js +204 -0
- package/dist/chunk-Z2EGETTT.js.map +1 -0
- package/dist/cli/init.cjs +483 -152
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +48 -2
- package/dist/cli/init.d.ts +48 -2
- package/dist/cli/init.js +109 -7
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +2 -2
- package/dist/cli/uninit.cjs +174 -54
- package/dist/cli/uninit.cjs.map +1 -1
- package/dist/cli/uninit.d.cts +2 -0
- package/dist/cli/uninit.d.ts +2 -0
- package/dist/cli/uninit.js +2 -1
- package/dist/edge-entry-Ds2fNOeh.d.ts +157 -0
- package/dist/edge-entry-FJFKkeFF.d.cts +157 -0
- package/dist/edge-entry.cjs +14939 -0
- package/dist/edge-entry.cjs.map +1 -0
- package/dist/edge-entry.d.cts +6 -0
- package/dist/edge-entry.d.ts +6 -0
- package/dist/edge-entry.js +16 -0
- package/dist/index.cjs +13 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d-DgeH-pNJ.d.cts +191 -0
- package/dist/index.d-DgeH-pNJ.d.ts +191 -0
- package/dist/index.d.cts +9 -461
- package/dist/index.d.ts +9 -461
- package/dist/index.js +32 -4609
- package/dist/index.js.map +1 -1
- package/dist/node-entry.cjs +22492 -0
- package/dist/node-entry.cjs.map +1 -0
- package/dist/node-entry.d.cts +10 -0
- package/dist/node-entry.d.ts +10 -0
- package/dist/node-entry.js +101 -0
- package/dist/node-entry.js.map +1 -0
- package/dist/node-subpath.cjs +14506 -0
- package/dist/node-subpath.cjs.map +1 -0
- package/dist/node-subpath.d.cts +132 -0
- package/dist/node-subpath.d.ts +132 -0
- package/dist/node-subpath.js +30 -0
- package/dist/node-subpath.js.map +1 -0
- package/dist/{source-map-uploader-VPDZWWM2.js → source-map-uploader-YXWO6JLN.js} +3 -3
- package/dist/source-map-uploader-YXWO6JLN.js.map +1 -0
- package/package.json +4 -1
- package/dist/chunk-F2TZRBEH.js.map +0 -1
- package/dist/chunk-XNDHQN4S.js.map +0 -1
- /package/dist/{chunk-YPXW2TN3.js.map → chunk-IP4NMDJK.js.map} +0 -0
- /package/dist/{chunk-VN3GZDV6.js.map → chunk-IQN6TRMQ.js.map} +0 -0
- /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
|
-
|
|
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 [
|
|
307
|
+
const [fs, path] = await Promise.all([
|
|
309
308
|
import("node:fs/promises"),
|
|
310
309
|
import("node:path")
|
|
311
310
|
]);
|
|
312
|
-
fsPathAsyncCache = { fs
|
|
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
|
|
322
|
-
const
|
|
323
|
-
return { readFileSync:
|
|
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-
|
|
687
|
+
//# sourceMappingURL=chunk-DO2YPMQ5.js.map
|