@glasstrace/sdk 1.0.0 → 1.1.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.
- package/README.md +42 -2
- package/dist/{chunk-7PDDBLST.js → chunk-KE7MCPO5.js} +1 -1
- package/dist/chunk-KE7MCPO5.js.map +1 -0
- package/dist/{chunk-BT2OCXCG.js → chunk-UGJ3X4CT.js} +1 -1
- package/dist/chunk-UGJ3X4CT.js.map +1 -0
- package/dist/{chunk-D3WYZBQA.js → chunk-ZBTC5QIQ.js} +46 -24
- package/dist/{chunk-D3WYZBQA.js.map → chunk-ZBTC5QIQ.js.map} +1 -1
- package/dist/cli/init.cjs +1 -1
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +2 -2
- package/dist/index.cjs +134 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/node-entry.cjs +134 -111
- package/dist/node-entry.cjs.map +1 -1
- package/dist/node-entry.js +3 -3
- package/dist/node-subpath.cjs.map +1 -1
- package/dist/node-subpath.d.cts +109 -2
- package/dist/node-subpath.d.ts +109 -2
- package/dist/node-subpath.js +2 -2
- package/dist/{source-map-uploader-ZHA3B4GE.js → source-map-uploader-BJIXRLJ6.js} +2 -2
- package/package.json +6 -6
- package/dist/chunk-7PDDBLST.js.map +0 -1
- package/dist/chunk-BT2OCXCG.js.map +0 -1
- /package/dist/{source-map-uploader-ZHA3B4GE.js.map → source-map-uploader-BJIXRLJ6.js.map} +0 -0
package/dist/node-subpath.d.cts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { f as SourceMapUploadResponse, g as SourceMapManifestResponse, I as ImportGraphPayload } from './index.d-CYYe3PxB.cjs';
|
|
2
2
|
import './v4/classic/external.cjs';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* In-memory source map entry: a file path paired with its full text content.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Node-only. Describes the legacy in-memory shape consumed by
|
|
9
|
+
* {@link uploadSourceMaps} and {@link uploadSourceMapsAuto}. The type
|
|
10
|
+
* itself erases at runtime and is safe to import from edge code, but
|
|
11
|
+
* every function that produces or consumes it depends on `node:fs`
|
|
12
|
+
* and cannot run at the edge.
|
|
13
|
+
*/
|
|
4
14
|
interface SourceMapEntry {
|
|
5
15
|
filePath: string;
|
|
6
16
|
content: string;
|
|
@@ -8,6 +18,14 @@ interface SourceMapEntry {
|
|
|
8
18
|
/**
|
|
9
19
|
* Metadata for a discovered source map file, without its content loaded.
|
|
10
20
|
* Used by the streaming upload flow to defer file reads until upload time.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* Node-only. Describes the shape of data produced by the Node-only
|
|
24
|
+
* source-map upload flow ({@link discoverSourceMapFiles},
|
|
25
|
+
* {@link uploadSourceMapsAuto}). The type itself erases at runtime and
|
|
26
|
+
* is safe to import from edge code, but every function that produces
|
|
27
|
+
* or consumes it depends on `node:fs`/`node:path` and cannot run at
|
|
28
|
+
* the edge.
|
|
11
29
|
*/
|
|
12
30
|
interface SourceMapFileInfo {
|
|
13
31
|
/** Relative path to the compiled JS file (`.map` extension stripped). */
|
|
@@ -20,6 +38,12 @@ interface SourceMapFileInfo {
|
|
|
20
38
|
/**
|
|
21
39
|
* Recursively discovers all `.map` files in the given build directory.
|
|
22
40
|
* Returns metadata only — file content is NOT read into memory.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Node-only. Walks the filesystem with `node:fs/promises` (`readdir`,
|
|
44
|
+
* `stat`) and resolves paths with `node:path`. No edge-safe
|
|
45
|
+
* alternative — call from a Node context (build script, Next.js
|
|
46
|
+
* `next.config.ts`, CI job).
|
|
23
47
|
*/
|
|
24
48
|
declare function discoverSourceMapFiles(buildDir: string): Promise<SourceMapFileInfo[]>;
|
|
25
49
|
/**
|
|
@@ -28,6 +52,11 @@ declare function discoverSourceMapFiles(buildDir: string): Promise<SourceMapFile
|
|
|
28
52
|
*
|
|
29
53
|
* @deprecated Prefer {@link discoverSourceMapFiles} to avoid loading all
|
|
30
54
|
* source maps into memory simultaneously.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* Node-only. Reads every discovered `.map` file into memory via
|
|
58
|
+
* `node:fs/promises` (`readFile`). No edge-safe alternative — call
|
|
59
|
+
* from a Node context (build script, Next.js `next.config.ts`, CI job).
|
|
31
60
|
*/
|
|
32
61
|
declare function collectSourceMaps(buildDir: string): Promise<SourceMapEntry[]>;
|
|
33
62
|
/**
|
|
@@ -40,6 +69,16 @@ declare function collectSourceMaps(buildDir: string): Promise<SourceMapEntry[]>;
|
|
|
40
69
|
*
|
|
41
70
|
* Accepts either `SourceMapEntry[]` (legacy, in-memory) or
|
|
42
71
|
* `SourceMapFileInfo[]` (streaming, reads on demand).
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Node-only. Spawns `git rev-parse HEAD` via `node:child_process`
|
|
75
|
+
* (`execFileSync`), hashes with `node:crypto` (`createHash("sha256")`),
|
|
76
|
+
* and reads map contents from disk with `node:fs/promises`. No
|
|
77
|
+
* edge-safe alternative — call from a Node context (build script,
|
|
78
|
+
* Next.js `next.config.ts`, CI job). If a pre-computed build hash is
|
|
79
|
+
* already known (e.g., provided as a CI environment variable), pass
|
|
80
|
+
* it directly to {@link uploadSourceMaps} instead of calling this
|
|
81
|
+
* helper.
|
|
43
82
|
*/
|
|
44
83
|
declare function computeBuildHash(maps?: SourceMapEntry[] | SourceMapFileInfo[]): Promise<string>;
|
|
45
84
|
/**
|
|
@@ -53,11 +92,37 @@ declare function computeBuildHash(maps?: SourceMapEntry[] | SourceMapFileInfo[])
|
|
|
53
92
|
* file content is read at upload time rather than at discovery time.
|
|
54
93
|
* Note: the legacy endpoint sends all files in a single JSON body, so
|
|
55
94
|
* peak memory is similar — the benefit is deferring reads past discovery.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* Node-only. Reads map contents from disk via `node:fs/promises` when
|
|
98
|
+
* invoked with `SourceMapFileInfo[]`. The network call uses the
|
|
99
|
+
* platform-standard `fetch` (edge-safe on its own), but the upstream
|
|
100
|
+
* discovery and read path is Node-only, so the function is only
|
|
101
|
+
* reachable from a Node context (build script, Next.js
|
|
102
|
+
* `next.config.ts`, CI job). No edge-safe alternative.
|
|
56
103
|
*/
|
|
57
104
|
declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[] | SourceMapFileInfo[]): Promise<SourceMapUploadResponse>;
|
|
58
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Builds at or above this byte size route to the presigned upload flow.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* Node-only. The numeric value itself is pure (a constant is evaluable
|
|
110
|
+
* anywhere), but it is meaningful only alongside
|
|
111
|
+
* {@link uploadSourceMapsPresigned} and {@link uploadSourceMapsAuto},
|
|
112
|
+
* both of which depend on `node:fs` and `@vercel/blob`. No edge-safe
|
|
113
|
+
* alternative — consume from a Node context.
|
|
114
|
+
*/
|
|
59
115
|
declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
|
|
60
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Signature for the blob upload function, injectable for testing.
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* Node-only. Describes the shape of the uploader consumed by
|
|
121
|
+
* {@link uploadSourceMapsPresigned} and {@link uploadSourceMapsAuto},
|
|
122
|
+
* both of which depend on `@vercel/blob` and `node:fs`. The type itself
|
|
123
|
+
* erases at runtime and is safe to import from edge code, but every
|
|
124
|
+
* producer and consumer is Node-only.
|
|
125
|
+
*/
|
|
61
126
|
type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
|
|
62
127
|
url: string;
|
|
63
128
|
size: number;
|
|
@@ -75,10 +140,22 @@ type BlobUploader = (clientToken: string, pathname: string, content: string) =>
|
|
|
75
140
|
*
|
|
76
141
|
* Accepts an optional `blobUploader` for test injection; defaults to
|
|
77
142
|
* {@link uploadToBlob}.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* Node-only. Streams map contents from disk via `node:fs/promises`
|
|
146
|
+
* and uploads through `@vercel/blob/client` (loaded lazily as an
|
|
147
|
+
* optional peer dependency). No edge-safe alternative — call from a
|
|
148
|
+
* Node context (build script, Next.js `next.config.ts`, CI job).
|
|
78
149
|
*/
|
|
79
150
|
declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[] | SourceMapFileInfo[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
|
|
80
151
|
/**
|
|
81
152
|
* Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
|
|
153
|
+
*
|
|
154
|
+
* @remarks
|
|
155
|
+
* Node-only. Describes the shape of overrides consumed by
|
|
156
|
+
* {@link uploadSourceMapsAuto}, which depends on `node:fs` and
|
|
157
|
+
* `@vercel/blob`. The type itself erases at runtime and is safe to
|
|
158
|
+
* import from edge code, but the surrounding function is Node-only.
|
|
82
159
|
*/
|
|
83
160
|
interface AutoUploadOptions {
|
|
84
161
|
/** Override blob availability check (for testing). */
|
|
@@ -97,6 +174,14 @@ interface AutoUploadOptions {
|
|
|
97
174
|
*
|
|
98
175
|
* Accepts either `SourceMapEntry[]` (legacy, in-memory) or
|
|
99
176
|
* `SourceMapFileInfo[]` (streaming, reads on demand).
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* Node-only. Reads source map sizes/contents via `node:fs/promises`
|
|
180
|
+
* and, above the threshold, dynamically loads `@vercel/blob/client`
|
|
181
|
+
* (optional peer dependency) for direct blob storage uploads. No
|
|
182
|
+
* edge-safe alternative — call from a Node context (build script,
|
|
183
|
+
* Next.js `next.config.ts`, CI job). This is the recommended entry
|
|
184
|
+
* point for source-map upload in most projects.
|
|
100
185
|
*/
|
|
101
186
|
declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[] | SourceMapFileInfo[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
|
|
102
187
|
|
|
@@ -107,6 +192,13 @@ declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHas
|
|
|
107
192
|
*
|
|
108
193
|
* @param projectRoot - Absolute path to the project root directory.
|
|
109
194
|
* @returns Relative POSIX paths from projectRoot, capped at {@link MAX_TEST_FILES}.
|
|
195
|
+
*
|
|
196
|
+
* @remarks
|
|
197
|
+
* Node-only. Walks the filesystem with `node:fs/promises`
|
|
198
|
+
* (`readdir`), reads vitest/jest config files with `node:fs`
|
|
199
|
+
* (`readFileSync`), and resolves paths with `node:path`. No edge-safe
|
|
200
|
+
* alternative — call from a Node context (build script, CI job,
|
|
201
|
+
* Next.js `next.config.ts`).
|
|
110
202
|
*/
|
|
111
203
|
declare function discoverTestFiles(projectRoot: string): Promise<string[]>;
|
|
112
204
|
/**
|
|
@@ -115,6 +207,14 @@ declare function discoverTestFiles(projectRoot: string): Promise<string[]>;
|
|
|
115
207
|
*
|
|
116
208
|
* @param fileContent - The full text content of a TypeScript/JavaScript file.
|
|
117
209
|
* @returns An array of import path strings as written in the source (e.g. "./foo", "react").
|
|
210
|
+
*
|
|
211
|
+
* @remarks
|
|
212
|
+
* Node-only. The function body itself is pure string processing and
|
|
213
|
+
* would run anywhere, but it is exported through `@glasstrace/sdk/node`
|
|
214
|
+
* alongside {@link discoverTestFiles} and {@link buildImportGraph} —
|
|
215
|
+
* the practical consumers all pair it with those Node-only helpers.
|
|
216
|
+
* Kept under the `/node` subpath for API cohesion; call from a Node
|
|
217
|
+
* context (build script, CI job, Next.js `next.config.ts`).
|
|
118
218
|
*/
|
|
119
219
|
declare function extractImports(fileContent: string): string[];
|
|
120
220
|
/**
|
|
@@ -126,6 +226,13 @@ declare function extractImports(fileContent: string): string[];
|
|
|
126
226
|
*
|
|
127
227
|
* @param projectRoot - Absolute path to the project root directory.
|
|
128
228
|
* @returns An {@link ImportGraphPayload} containing the graph and a deterministic buildHash.
|
|
229
|
+
*
|
|
230
|
+
* @remarks
|
|
231
|
+
* Node-only. Walks the project with `node:fs/promises`, reads each
|
|
232
|
+
* test file from disk, resolves paths with `node:path`, and hashes
|
|
233
|
+
* the serialized graph with `node:crypto` (`createHash("sha256")`).
|
|
234
|
+
* No edge-safe alternative — call from a Node context (build script,
|
|
235
|
+
* CI job, Next.js `next.config.ts`).
|
|
129
236
|
*/
|
|
130
237
|
declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
|
|
131
238
|
|
package/dist/node-subpath.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { f as SourceMapUploadResponse, g as SourceMapManifestResponse, I as ImportGraphPayload } from './index.d-CYYe3PxB.js';
|
|
2
2
|
import './v4/classic/external.cjs';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* In-memory source map entry: a file path paired with its full text content.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* Node-only. Describes the legacy in-memory shape consumed by
|
|
9
|
+
* {@link uploadSourceMaps} and {@link uploadSourceMapsAuto}. The type
|
|
10
|
+
* itself erases at runtime and is safe to import from edge code, but
|
|
11
|
+
* every function that produces or consumes it depends on `node:fs`
|
|
12
|
+
* and cannot run at the edge.
|
|
13
|
+
*/
|
|
4
14
|
interface SourceMapEntry {
|
|
5
15
|
filePath: string;
|
|
6
16
|
content: string;
|
|
@@ -8,6 +18,14 @@ interface SourceMapEntry {
|
|
|
8
18
|
/**
|
|
9
19
|
* Metadata for a discovered source map file, without its content loaded.
|
|
10
20
|
* Used by the streaming upload flow to defer file reads until upload time.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* Node-only. Describes the shape of data produced by the Node-only
|
|
24
|
+
* source-map upload flow ({@link discoverSourceMapFiles},
|
|
25
|
+
* {@link uploadSourceMapsAuto}). The type itself erases at runtime and
|
|
26
|
+
* is safe to import from edge code, but every function that produces
|
|
27
|
+
* or consumes it depends on `node:fs`/`node:path` and cannot run at
|
|
28
|
+
* the edge.
|
|
11
29
|
*/
|
|
12
30
|
interface SourceMapFileInfo {
|
|
13
31
|
/** Relative path to the compiled JS file (`.map` extension stripped). */
|
|
@@ -20,6 +38,12 @@ interface SourceMapFileInfo {
|
|
|
20
38
|
/**
|
|
21
39
|
* Recursively discovers all `.map` files in the given build directory.
|
|
22
40
|
* Returns metadata only — file content is NOT read into memory.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Node-only. Walks the filesystem with `node:fs/promises` (`readdir`,
|
|
44
|
+
* `stat`) and resolves paths with `node:path`. No edge-safe
|
|
45
|
+
* alternative — call from a Node context (build script, Next.js
|
|
46
|
+
* `next.config.ts`, CI job).
|
|
23
47
|
*/
|
|
24
48
|
declare function discoverSourceMapFiles(buildDir: string): Promise<SourceMapFileInfo[]>;
|
|
25
49
|
/**
|
|
@@ -28,6 +52,11 @@ declare function discoverSourceMapFiles(buildDir: string): Promise<SourceMapFile
|
|
|
28
52
|
*
|
|
29
53
|
* @deprecated Prefer {@link discoverSourceMapFiles} to avoid loading all
|
|
30
54
|
* source maps into memory simultaneously.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* Node-only. Reads every discovered `.map` file into memory via
|
|
58
|
+
* `node:fs/promises` (`readFile`). No edge-safe alternative — call
|
|
59
|
+
* from a Node context (build script, Next.js `next.config.ts`, CI job).
|
|
31
60
|
*/
|
|
32
61
|
declare function collectSourceMaps(buildDir: string): Promise<SourceMapEntry[]>;
|
|
33
62
|
/**
|
|
@@ -40,6 +69,16 @@ declare function collectSourceMaps(buildDir: string): Promise<SourceMapEntry[]>;
|
|
|
40
69
|
*
|
|
41
70
|
* Accepts either `SourceMapEntry[]` (legacy, in-memory) or
|
|
42
71
|
* `SourceMapFileInfo[]` (streaming, reads on demand).
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Node-only. Spawns `git rev-parse HEAD` via `node:child_process`
|
|
75
|
+
* (`execFileSync`), hashes with `node:crypto` (`createHash("sha256")`),
|
|
76
|
+
* and reads map contents from disk with `node:fs/promises`. No
|
|
77
|
+
* edge-safe alternative — call from a Node context (build script,
|
|
78
|
+
* Next.js `next.config.ts`, CI job). If a pre-computed build hash is
|
|
79
|
+
* already known (e.g., provided as a CI environment variable), pass
|
|
80
|
+
* it directly to {@link uploadSourceMaps} instead of calling this
|
|
81
|
+
* helper.
|
|
43
82
|
*/
|
|
44
83
|
declare function computeBuildHash(maps?: SourceMapEntry[] | SourceMapFileInfo[]): Promise<string>;
|
|
45
84
|
/**
|
|
@@ -53,11 +92,37 @@ declare function computeBuildHash(maps?: SourceMapEntry[] | SourceMapFileInfo[])
|
|
|
53
92
|
* file content is read at upload time rather than at discovery time.
|
|
54
93
|
* Note: the legacy endpoint sends all files in a single JSON body, so
|
|
55
94
|
* peak memory is similar — the benefit is deferring reads past discovery.
|
|
95
|
+
*
|
|
96
|
+
* @remarks
|
|
97
|
+
* Node-only. Reads map contents from disk via `node:fs/promises` when
|
|
98
|
+
* invoked with `SourceMapFileInfo[]`. The network call uses the
|
|
99
|
+
* platform-standard `fetch` (edge-safe on its own), but the upstream
|
|
100
|
+
* discovery and read path is Node-only, so the function is only
|
|
101
|
+
* reachable from a Node context (build script, Next.js
|
|
102
|
+
* `next.config.ts`, CI job). No edge-safe alternative.
|
|
56
103
|
*/
|
|
57
104
|
declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[] | SourceMapFileInfo[]): Promise<SourceMapUploadResponse>;
|
|
58
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Builds at or above this byte size route to the presigned upload flow.
|
|
107
|
+
*
|
|
108
|
+
* @remarks
|
|
109
|
+
* Node-only. The numeric value itself is pure (a constant is evaluable
|
|
110
|
+
* anywhere), but it is meaningful only alongside
|
|
111
|
+
* {@link uploadSourceMapsPresigned} and {@link uploadSourceMapsAuto},
|
|
112
|
+
* both of which depend on `node:fs` and `@vercel/blob`. No edge-safe
|
|
113
|
+
* alternative — consume from a Node context.
|
|
114
|
+
*/
|
|
59
115
|
declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
|
|
60
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* Signature for the blob upload function, injectable for testing.
|
|
118
|
+
*
|
|
119
|
+
* @remarks
|
|
120
|
+
* Node-only. Describes the shape of the uploader consumed by
|
|
121
|
+
* {@link uploadSourceMapsPresigned} and {@link uploadSourceMapsAuto},
|
|
122
|
+
* both of which depend on `@vercel/blob` and `node:fs`. The type itself
|
|
123
|
+
* erases at runtime and is safe to import from edge code, but every
|
|
124
|
+
* producer and consumer is Node-only.
|
|
125
|
+
*/
|
|
61
126
|
type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
|
|
62
127
|
url: string;
|
|
63
128
|
size: number;
|
|
@@ -75,10 +140,22 @@ type BlobUploader = (clientToken: string, pathname: string, content: string) =>
|
|
|
75
140
|
*
|
|
76
141
|
* Accepts an optional `blobUploader` for test injection; defaults to
|
|
77
142
|
* {@link uploadToBlob}.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* Node-only. Streams map contents from disk via `node:fs/promises`
|
|
146
|
+
* and uploads through `@vercel/blob/client` (loaded lazily as an
|
|
147
|
+
* optional peer dependency). No edge-safe alternative — call from a
|
|
148
|
+
* Node context (build script, Next.js `next.config.ts`, CI job).
|
|
78
149
|
*/
|
|
79
150
|
declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[] | SourceMapFileInfo[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
|
|
80
151
|
/**
|
|
81
152
|
* Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
|
|
153
|
+
*
|
|
154
|
+
* @remarks
|
|
155
|
+
* Node-only. Describes the shape of overrides consumed by
|
|
156
|
+
* {@link uploadSourceMapsAuto}, which depends on `node:fs` and
|
|
157
|
+
* `@vercel/blob`. The type itself erases at runtime and is safe to
|
|
158
|
+
* import from edge code, but the surrounding function is Node-only.
|
|
82
159
|
*/
|
|
83
160
|
interface AutoUploadOptions {
|
|
84
161
|
/** Override blob availability check (for testing). */
|
|
@@ -97,6 +174,14 @@ interface AutoUploadOptions {
|
|
|
97
174
|
*
|
|
98
175
|
* Accepts either `SourceMapEntry[]` (legacy, in-memory) or
|
|
99
176
|
* `SourceMapFileInfo[]` (streaming, reads on demand).
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* Node-only. Reads source map sizes/contents via `node:fs/promises`
|
|
180
|
+
* and, above the threshold, dynamically loads `@vercel/blob/client`
|
|
181
|
+
* (optional peer dependency) for direct blob storage uploads. No
|
|
182
|
+
* edge-safe alternative — call from a Node context (build script,
|
|
183
|
+
* Next.js `next.config.ts`, CI job). This is the recommended entry
|
|
184
|
+
* point for source-map upload in most projects.
|
|
100
185
|
*/
|
|
101
186
|
declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[] | SourceMapFileInfo[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
|
|
102
187
|
|
|
@@ -107,6 +192,13 @@ declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHas
|
|
|
107
192
|
*
|
|
108
193
|
* @param projectRoot - Absolute path to the project root directory.
|
|
109
194
|
* @returns Relative POSIX paths from projectRoot, capped at {@link MAX_TEST_FILES}.
|
|
195
|
+
*
|
|
196
|
+
* @remarks
|
|
197
|
+
* Node-only. Walks the filesystem with `node:fs/promises`
|
|
198
|
+
* (`readdir`), reads vitest/jest config files with `node:fs`
|
|
199
|
+
* (`readFileSync`), and resolves paths with `node:path`. No edge-safe
|
|
200
|
+
* alternative — call from a Node context (build script, CI job,
|
|
201
|
+
* Next.js `next.config.ts`).
|
|
110
202
|
*/
|
|
111
203
|
declare function discoverTestFiles(projectRoot: string): Promise<string[]>;
|
|
112
204
|
/**
|
|
@@ -115,6 +207,14 @@ declare function discoverTestFiles(projectRoot: string): Promise<string[]>;
|
|
|
115
207
|
*
|
|
116
208
|
* @param fileContent - The full text content of a TypeScript/JavaScript file.
|
|
117
209
|
* @returns An array of import path strings as written in the source (e.g. "./foo", "react").
|
|
210
|
+
*
|
|
211
|
+
* @remarks
|
|
212
|
+
* Node-only. The function body itself is pure string processing and
|
|
213
|
+
* would run anywhere, but it is exported through `@glasstrace/sdk/node`
|
|
214
|
+
* alongside {@link discoverTestFiles} and {@link buildImportGraph} —
|
|
215
|
+
* the practical consumers all pair it with those Node-only helpers.
|
|
216
|
+
* Kept under the `/node` subpath for API cohesion; call from a Node
|
|
217
|
+
* context (build script, CI job, Next.js `next.config.ts`).
|
|
118
218
|
*/
|
|
119
219
|
declare function extractImports(fileContent: string): string[];
|
|
120
220
|
/**
|
|
@@ -126,6 +226,13 @@ declare function extractImports(fileContent: string): string[];
|
|
|
126
226
|
*
|
|
127
227
|
* @param projectRoot - Absolute path to the project root directory.
|
|
128
228
|
* @returns An {@link ImportGraphPayload} containing the graph and a deterministic buildHash.
|
|
229
|
+
*
|
|
230
|
+
* @remarks
|
|
231
|
+
* Node-only. Walks the project with `node:fs/promises`, reads each
|
|
232
|
+
* test file from disk, resolves paths with `node:path`, and hashes
|
|
233
|
+
* the serialized graph with `node:crypto` (`createHash("sha256")`).
|
|
234
|
+
* No edge-safe alternative — call from a Node context (build script,
|
|
235
|
+
* CI job, Next.js `next.config.ts`).
|
|
129
236
|
*/
|
|
130
237
|
declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
|
|
131
238
|
|
package/dist/node-subpath.js
CHANGED
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
uploadSourceMaps,
|
|
7
7
|
uploadSourceMapsAuto,
|
|
8
8
|
uploadSourceMapsPresigned
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-KE7MCPO5.js";
|
|
10
10
|
import "./chunk-3TU62WD6.js";
|
|
11
11
|
import {
|
|
12
12
|
buildImportGraph,
|
|
13
13
|
discoverTestFiles,
|
|
14
14
|
extractImports
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-UGJ3X4CT.js";
|
|
16
16
|
import "./chunk-VUZCLMIX.js";
|
|
17
17
|
import "./chunk-TQ54WLCZ.js";
|
|
18
18
|
import "./chunk-NSBPE2FW.js";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
uploadSourceMapsAuto,
|
|
12
12
|
uploadSourceMapsPresigned,
|
|
13
13
|
uploadToBlob
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KE7MCPO5.js";
|
|
15
15
|
import "./chunk-3TU62WD6.js";
|
|
16
16
|
import "./chunk-VUZCLMIX.js";
|
|
17
17
|
import "./chunk-TQ54WLCZ.js";
|
|
@@ -30,4 +30,4 @@ export {
|
|
|
30
30
|
uploadSourceMapsPresigned,
|
|
31
31
|
uploadToBlob
|
|
32
32
|
};
|
|
33
|
-
//# sourceMappingURL=source-map-uploader-
|
|
33
|
+
//# sourceMappingURL=source-map-uploader-BJIXRLJ6.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glasstrace/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Glasstrace server-side debugging SDK for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"build": "tsup",
|
|
46
46
|
"postbuild": "npm run check:edge-bundle && npm run verify:subpath",
|
|
47
47
|
"check:edge-bundle": "node scripts/check-edge-bundle.mjs",
|
|
48
|
-
"verify:subpath": "
|
|
48
|
+
"verify:subpath": "node scripts/verify-subpath-resolution.mjs",
|
|
49
49
|
"preuninstall": "node -e \"process.stderr.write('\\n[@glasstrace/sdk] Package removal warning:\\n Before \\'npm uninstall @glasstrace/sdk\\' runs, Glasstrace recommends running \\'npx @glasstrace/sdk uninit\\' first to cleanly remove instrumentation files, MCP configuration, and the .glasstrace/ state directory. Without uninit, your next build may fail because instrumentation.ts and next.config still reference the removed package.\\n See: https://glasstrace.dev/docs/cli#uninit\\n\\n')\""
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@prisma/instrumentation": "^4.0.0 || ^5.0.0 || ^6.0.0",
|
|
52
|
+
"@prisma/instrumentation": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
53
53
|
"@vercel/blob": "^2.0.0",
|
|
54
54
|
"@vercel/otel": "^2.0.0",
|
|
55
55
|
"drizzle-orm": "^0.29.0 || ^0.30.0 || ^0.31.0 || ^0.32.0 || ^0.33.0 || ^0.34.0 || ^0.35.0 || ^0.36.0 || ^0.37.0"
|
|
@@ -71,12 +71,12 @@
|
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@glasstrace/protocol": "*",
|
|
73
73
|
"@opentelemetry/api": "^1.9.0",
|
|
74
|
-
"@opentelemetry/core": "^2.
|
|
75
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
74
|
+
"@opentelemetry/core": "^2.7.0",
|
|
75
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.215.0",
|
|
76
76
|
"@opentelemetry/sdk-trace-base": "^2.6.1",
|
|
77
77
|
"@vercel/blob": "^2.3.3",
|
|
78
78
|
"@vercel/otel": "^2.1.2",
|
|
79
|
-
"esbuild": "^0.
|
|
79
|
+
"esbuild": "^0.28.0",
|
|
80
80
|
"tsup": "^8.5.0"
|
|
81
81
|
},
|
|
82
82
|
"publishConfig": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/source-map-uploader.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { execFileSync } from \"node:child_process\";\nimport { sdkLog } from \"./console-capture.js\";\nimport {\n SourceMapUploadResponseSchema,\n type SourceMapUploadResponse,\n PresignedUploadResponseSchema,\n type PresignedUploadResponse,\n SourceMapManifestResponseSchema,\n type SourceMapManifestResponse,\n} from \"@glasstrace/protocol\";\n\nexport interface SourceMapEntry {\n filePath: string;\n content: string;\n}\n\n/**\n * Metadata for a discovered source map file, without its content loaded.\n * Used by the streaming upload flow to defer file reads until upload time.\n */\nexport interface SourceMapFileInfo {\n /** Relative path to the compiled JS file (`.map` extension stripped). */\n filePath: string;\n /** Absolute path on disk for reading the file content on demand. */\n absolutePath: string;\n /** File size in bytes. */\n sizeBytes: number;\n}\n\n/** Threshold (50 MB) above which a single source map triggers a warning. */\nconst LARGE_FILE_WARNING_BYTES = 50 * 1024 * 1024;\n\n/**\n * Recursively discovers all `.map` files in the given build directory.\n * Returns metadata only — file content is NOT read into memory.\n */\nexport async function discoverSourceMapFiles(\n buildDir: string,\n): Promise<SourceMapFileInfo[]> {\n // Resolve to absolute so absolutePath in results is always absolute,\n // even when buildDir is relative (e.g. \".next\").\n const resolvedDir = path.resolve(buildDir);\n const results: SourceMapFileInfo[] = [];\n\n try {\n await walkDirMetadata(resolvedDir, resolvedDir, results);\n } catch {\n // Directory doesn't exist or is unreadable — return empty\n return [];\n }\n\n // Warn about oversized source map files\n for (const file of results) {\n if (file.sizeBytes >= LARGE_FILE_WARNING_BYTES) {\n const sizeMB = (file.sizeBytes / (1024 * 1024)).toFixed(1);\n sdkLog(\n \"warn\",\n `[glasstrace] Large source map detected: ${file.filePath} (${sizeMB}MB). Consider enabling source map compression.`,\n );\n }\n }\n\n return results;\n}\n\nasync function walkDirMetadata(\n baseDir: string,\n currentDir: string,\n results: SourceMapFileInfo[],\n): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await walkDirMetadata(baseDir, fullPath, results);\n } else if (entry.isFile() && entry.name.endsWith(\".map\")) {\n try {\n const stat = await fs.stat(fullPath);\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n // Strip the trailing .map extension so the key matches the compiled\n // JS path that the runtime uses for stack-frame lookups (e.g.\n // \"static/chunks/main.js\" instead of \"static/chunks/main.js.map\").\n const compiledPath = relativePath.replace(/\\.map$/, \"\");\n results.push({\n filePath: compiledPath,\n absolutePath: fullPath,\n sizeBytes: stat.size,\n });\n } catch {\n // Skip unreadable files\n }\n }\n }\n}\n\n/**\n * Reads the content of a single source map file from disk.\n */\nasync function readSourceMapContent(absolutePath: string): Promise<string> {\n return fs.readFile(absolutePath, \"utf-8\");\n}\n\n/**\n * Recursively finds all .map files in the given build directory.\n * Returns relative paths and file contents.\n *\n * @deprecated Prefer {@link discoverSourceMapFiles} to avoid loading all\n * source maps into memory simultaneously.\n */\nexport async function collectSourceMaps(\n buildDir: string,\n): Promise<SourceMapEntry[]> {\n const fileInfos = await discoverSourceMapFiles(buildDir);\n const results: SourceMapEntry[] = [];\n\n for (const info of fileInfos) {\n try {\n const content = await readSourceMapContent(info.absolutePath);\n results.push({ filePath: info.filePath, content });\n } catch {\n // Skip unreadable files — consistent with previous behavior\n }\n }\n\n return results;\n}\n\n/**\n * Computes a build hash for source map uploads.\n *\n * First tries `git rev-parse HEAD` to get the git commit SHA.\n * On failure, falls back to a deterministic content hash:\n * sort source map file paths alphabetically, concatenate each as\n * `{relativePath}\\n{fileLength}\\n{fileContent}`, then SHA-256 the result.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n */\nexport async function computeBuildHash(\n maps?: SourceMapEntry[] | SourceMapFileInfo[],\n): Promise<string> {\n // Try git first\n try {\n const sha = execFileSync(\"git\", [\"rev-parse\", \"HEAD\"], { encoding: \"utf-8\" }).trim();\n if (sha) {\n return sha;\n }\n } catch {\n // Git not available, fall through to content hash\n }\n\n // Fallback: content-based hash\n const sortedMaps = [...(maps ?? [])].sort((a, b) =>\n a.filePath.localeCompare(b.filePath),\n );\n\n const hash = crypto.createHash(\"sha256\");\n\n for (const m of sortedMaps) {\n let content: string;\n if (\"content\" in m) {\n content = m.content;\n } else {\n try {\n content = await readSourceMapContent(m.absolutePath);\n } catch {\n // Skip unreadable files — consistent with collectSourceMaps behavior\n continue;\n }\n }\n hash.update(`${m.filePath}\\n${content.length}\\n${content}`);\n }\n\n return hash.digest(\"hex\");\n}\n\n/**\n * Uploads source maps to the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,\n * and file entries. Validates the response against SourceMapUploadResponseSchema.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (deferred reads). With `SourceMapFileInfo[]`,\n * file content is read at upload time rather than at discovery time.\n * Note: the legacy endpoint sends all files in a single JSON body, so\n * peak memory is similar — the benefit is deferring reads past discovery.\n */\nexport async function uploadSourceMaps(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n): Promise<SourceMapUploadResponse> {\n // Read files on demand — for SourceMapEntry[], content is already available;\n // for SourceMapFileInfo[], each file is read individually to limit memory.\n // Individual reads are guarded so one transient failure (deleted file,\n // permission change) does not abort the entire upload.\n const files: Array<{ filePath: string; sourceMap: string }> = [];\n for (const m of maps) {\n try {\n const content = \"content\" in m\n ? m.content\n : await readSourceMapContent(m.absolutePath);\n files.push({ filePath: m.filePath, sourceMap: content });\n } catch {\n sdkLog(\"warn\", `[glasstrace] Skipping unreadable source map: ${m.filePath}`);\n }\n }\n\n const body = {\n buildHash,\n files,\n };\n\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n // Consume the response body to release the connection back to the pool.\n // Without this, the underlying TCP socket stays allocated until GC, which\n // causes connection pool exhaustion under sustained error conditions.\n // Wrapped in try-catch so a stream error doesn't mask the HTTP status error.\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map upload failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapUploadResponseSchema.parse(json);\n}\n\n// ---------------------------------------------------------------------------\n// Presigned source map upload (3-phase flow for large builds)\n// ---------------------------------------------------------------------------\n\n/** Builds at or above this byte size route to the presigned upload flow. */\nexport const PRESIGNED_THRESHOLD_BYTES = 4_500_000;\n\n/** Signature for the blob upload function, injectable for testing. */\nexport type BlobUploader = (\n clientToken: string,\n pathname: string,\n content: string,\n) => Promise<{ url: string; size: number }>;\n\n/**\n * Strips trailing slashes from a URL string.\n * Uses an iterative approach to avoid regex (CodeQL js/polynomial-redos).\n */\nfunction stripTrailingSlashes(url: string): string {\n let result = url;\n while (result.endsWith(\"/\")) {\n result = result.slice(0, -1);\n }\n return result;\n}\n\n/**\n * Phase 1: Request presigned upload tokens from the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps/presign` with the build hash and\n * file metadata. Returns presigned tokens for each file that the client\n * uses to upload directly to blob storage.\n */\nexport async function requestPresignedTokens(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number }>,\n): Promise<PresignedUploadResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Presigned token request failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return PresignedUploadResponseSchema.parse(json);\n}\n\n/** Shape of the subset of `@vercel/blob/client` the SDK consumes. */\ntype BlobClientModule = {\n put: (\n pathname: string,\n body: Blob,\n options: { access: string; token: string },\n ) => Promise<{ url: string }>;\n};\n\n/**\n * Loads `@vercel/blob/client` at runtime using the `Function()`-based\n * dynamic-import evasion trick.\n *\n * The indirection hides the specifier from static bundler analysis — webpack,\n * tsup, esbuild, and rollup all resolve literal `await import(\"...\")` targets\n * at build time and would raise `Module not found` for consumers without\n * `@vercel/blob` installed (it is an optional peer dependency). See DISC-1255.\n *\n * **CSP note:** `Function()` is semantically equivalent to `eval()` and will\n * trigger `unsafe-eval` CSP violations in restricted environments. Source-map\n * upload only runs in Node.js at build time, so CSP does not apply here. The\n * same caveat documented on `tryImport` in `otel-config.ts` applies if this\n * code is ever re-used in a browser-equivalent runtime.\n *\n * Exported for internal test injection via {@link _setBlobClientLoaderForTesting}\n * only; not part of the public API.\n */\nasync function defaultBlobClientLoader(): Promise<BlobClientModule> {\n const dynamicImport = Function(\"id\", \"return import(id)\") as (\n id: string,\n ) => Promise<BlobClientModule>;\n return dynamicImport(\"@vercel/blob/client\");\n}\n\nlet _blobClientLoader: () => Promise<BlobClientModule> = defaultBlobClientLoader;\n\n/**\n * Replaces the blob client loader. For test use only.\n *\n * The production loader uses a `Function()`-based dynamic import to evade\n * static bundler analysis (DISC-1255), which in turn bypasses Vitest's\n * module-mock interceptor. Tests that need to stub `@vercel/blob/client`\n * call this helper to install a fake loader, then restore the default\n * with {@link _resetBlobClientLoaderForTesting}.\n *\n * @internal\n */\nexport function _setBlobClientLoaderForTesting(\n loader: () => Promise<BlobClientModule>,\n): void {\n _blobClientLoader = loader;\n}\n\n/**\n * Restores the default `@vercel/blob/client` loader. For test use only.\n *\n * @internal\n */\nexport function _resetBlobClientLoaderForTesting(): void {\n _blobClientLoader = defaultBlobClientLoader;\n}\n\n/**\n * Phase 2: Upload a single source map to blob storage using a presigned token.\n *\n * Dynamically imports `@vercel/blob/client` via {@link defaultBlobClientLoader}\n * to avoid bundling the optional peer dependency. Throws a descriptive error\n * if the package is not installed.\n */\nexport async function uploadToBlob(\n clientToken: string,\n pathname: string,\n content: string,\n): Promise<{ url: string; size: number }> {\n let mod: BlobClientModule;\n try {\n mod = await _blobClientLoader();\n } catch (err) {\n // Distinguish \"not installed\" from other import errors\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\") {\n throw new Error(\n \"Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob\",\n );\n }\n throw err;\n }\n\n const result = await mod.put(pathname, new Blob([content]), {\n access: \"public\",\n token: clientToken,\n });\n\n return { url: result.url, size: Buffer.byteLength(content, \"utf-8\") };\n}\n\n/**\n * Phase 3: Submit the upload manifest to finalize a presigned upload.\n *\n * POSTs to `{endpoint}/v1/source-maps/manifest` with the upload ID,\n * build hash, and blob URLs for each uploaded file. The backend activates\n * the source maps for stack trace resolution.\n */\nexport async function submitManifest(\n apiKey: string,\n endpoint: string,\n uploadId: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number; blobUrl: string }>,\n): Promise<SourceMapManifestResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ uploadId, buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapManifestResponseSchema.parse(json);\n}\n\n/**\n * Orchestrates the 3-phase presigned upload flow.\n *\n * 1. Requests presigned tokens for all source map files\n * 2. Uploads each file to blob storage with a concurrency limit of 5,\n * reading file content from disk just before each upload\n * 3. Submits the manifest to finalize the upload\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n *\n * Accepts an optional `blobUploader` for test injection; defaults to\n * {@link uploadToBlob}.\n */\nexport async function uploadSourceMapsPresigned(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n blobUploader: BlobUploader = uploadToBlob,\n): Promise<SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n // Determine file metadata for the presign request\n const fileMetadata = maps.map((m) => ({\n filePath: m.filePath,\n sizeBytes: \"content\" in m\n ? Buffer.byteLength(m.content, \"utf-8\")\n : m.sizeBytes,\n }));\n\n // Phase 1: request presigned tokens\n const presigned = await requestPresignedTokens(\n apiKey, endpoint, buildHash, fileMetadata,\n );\n\n // Build a lookup map for O(1) access by filePath\n const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));\n\n if (mapsByPath.size !== maps.length) {\n throw new Error(\"Duplicate filePath entries in source maps\");\n }\n\n // Validate all tokens have matching entries before starting any uploads.\n for (const token of presigned.files) {\n if (!mapsByPath.has(token.filePath)) {\n throw new Error(\n `Presigned token for \"${token.filePath}\" has no matching source map entry`,\n );\n }\n }\n\n // Phase 2: upload to blob storage in chunks of CONCURRENCY.\n // File content is read from disk just before each upload to avoid\n // holding all source maps in memory simultaneously.\n const CONCURRENCY = 5;\n const uploadResults: Array<{ filePath: string; sizeBytes: number; blobUrl: string }> = [];\n\n for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {\n const chunk = presigned.files.slice(i, i + CONCURRENCY);\n const chunkResults = await Promise.all(\n chunk.map(async (token) => {\n const entry = mapsByPath.get(token.filePath)!;\n let content: string;\n if (\"content\" in entry) {\n content = entry.content;\n } else {\n try {\n content = await readSourceMapContent(entry.absolutePath);\n } catch {\n sdkLog(\"warn\", `[glasstrace] Skipping unreadable source map: ${token.filePath}`);\n return null;\n }\n }\n const result = await blobUploader(token.clientToken, token.pathname, content);\n return {\n filePath: token.filePath,\n sizeBytes: result.size,\n blobUrl: result.url,\n };\n }),\n );\n for (const r of chunkResults) {\n if (r !== null) {\n uploadResults.push(r);\n }\n }\n }\n\n // Phase 3: submit manifest\n return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);\n}\n\n/**\n * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.\n */\nexport interface AutoUploadOptions {\n /** Override blob availability check (for testing). */\n checkBlobAvailable?: () => Promise<boolean>;\n /** Override blob uploader (for testing). */\n blobUploader?: BlobUploader;\n}\n\n/**\n * Automatically routes source map uploads based on total build size.\n *\n * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request\n * {@link uploadSourceMaps} endpoint.\n * - At or above the threshold: checks if `@vercel/blob` is available and\n * uses the presigned 3-phase flow. Falls back to legacy with a warning\n * if the package is not installed.\n *\n * Accepts either `SourceMapEntry[]` (legacy, in-memory) or\n * `SourceMapFileInfo[]` (streaming, reads on demand).\n */\nexport async function uploadSourceMapsAuto(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[] | SourceMapFileInfo[],\n options?: AutoUploadOptions,\n): Promise<SourceMapUploadResponse | SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n const totalBytes = maps.reduce(\n (sum, m) => {\n const bytes = \"content\" in m\n ? Buffer.byteLength(m.content, \"utf-8\")\n : m.sizeBytes;\n return sum + bytes;\n },\n 0,\n );\n\n if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n }\n\n // Check if @vercel/blob is available. Uses the shared blob client loader\n // (which goes through the `Function()` evasion trick) so webpack / tsup /\n // esbuild do not resolve `@vercel/blob/client` at build time (DISC-1255).\n const checkAvailable = options?.checkBlobAvailable ?? (async () => {\n try {\n await _blobClientLoader();\n return true;\n } catch {\n return false;\n }\n });\n\n const blobAvailable = await checkAvailable();\n\n if (blobAvailable) {\n return uploadSourceMapsPresigned(\n apiKey, endpoint, buildHash, maps, options?.blobUploader,\n );\n }\n\n // Fall back to legacy upload with a warning\n sdkLog(\"warn\",\n `[glasstrace] Build exceeds 4.5MB (${String(totalBytes)} bytes). Install @vercel/blob for ` +\n `presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`\n );\n\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n}\n"],"mappings":"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","names":["stat"]}
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
File without changes
|