@glasstrace/sdk 0.4.2 → 0.7.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/dist/{chunk-TVOYTJ7I.js → chunk-QW6W4CSA.js} +48 -2
- package/dist/{chunk-TVOYTJ7I.js.map → chunk-QW6W4CSA.js.map} +1 -1
- package/dist/{chunk-67H2JEI4.js → chunk-SALPGSWK.js} +2 -2
- package/dist/cli/init.cjs +46 -2
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +2 -2
- package/dist/cli/mcp-add.cjs +45 -1
- package/dist/cli/mcp-add.cjs.map +1 -1
- package/dist/cli/mcp-add.js +1 -1
- package/dist/index.cjs +313 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +270 -19
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- /package/dist/{chunk-67H2JEI4.js.map → chunk-SALPGSWK.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -67,6 +67,7 @@ type GlasstraceEnvVars = z.infer<typeof GlasstraceEnvVarsSchema>;
|
|
|
67
67
|
* - SDK health diagnostics (embedded in init requests)
|
|
68
68
|
* - Discovery endpoint (GET /__glasstrace/config)
|
|
69
69
|
* - Source map upload (POST /v1/source-maps)
|
|
70
|
+
* - Presigned source map upload (POST /v1/source-maps/presign, POST /v1/source-maps/manifest)
|
|
70
71
|
*/
|
|
71
72
|
|
|
72
73
|
/** Test file import relationships, embedded in SDK init request. */
|
|
@@ -112,6 +113,11 @@ declare const SdkInitResponseSchema: z.ZodObject<{
|
|
|
112
113
|
maxTraceSizeBytes: z.ZodNumber;
|
|
113
114
|
maxConcurrentSessions: z.ZodNumber;
|
|
114
115
|
}, z.core.$strip>;
|
|
116
|
+
claimResult: z.ZodOptional<z.ZodObject<{
|
|
117
|
+
newApiKey: z.core.$ZodBranded<z.ZodString, "DevApiKey", "out">;
|
|
118
|
+
accountId: z.ZodString;
|
|
119
|
+
graceExpiresAt: z.ZodNumber;
|
|
120
|
+
}, z.core.$strip>>;
|
|
115
121
|
}, z.core.$strip>;
|
|
116
122
|
type SdkInitResponse = z.infer<typeof SdkInitResponseSchema>;
|
|
117
123
|
/** Response from POST /v1/source-maps. */
|
|
@@ -122,6 +128,15 @@ declare const SourceMapUploadResponseSchema: z.ZodObject<{
|
|
|
122
128
|
totalSizeBytes: z.ZodNumber;
|
|
123
129
|
}, z.core.$strip>;
|
|
124
130
|
type SourceMapUploadResponse = z.infer<typeof SourceMapUploadResponseSchema>;
|
|
131
|
+
/** Response confirming source map manifest activation. */
|
|
132
|
+
declare const SourceMapManifestResponseSchema: z.ZodObject<{
|
|
133
|
+
success: z.ZodLiteral<true>;
|
|
134
|
+
buildHash: z.core.$ZodBranded<z.ZodString, "BuildHash", "out">;
|
|
135
|
+
fileCount: z.ZodNumber;
|
|
136
|
+
totalSizeBytes: z.ZodNumber;
|
|
137
|
+
activatedAt: z.ZodNumber;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
type SourceMapManifestResponse = z.infer<typeof SourceMapManifestResponseSchema>;
|
|
125
140
|
|
|
126
141
|
/**
|
|
127
142
|
* Internal SDK error class with a typed diagnostic code.
|
|
@@ -270,11 +285,24 @@ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | n
|
|
|
270
285
|
message: string;
|
|
271
286
|
timestamp: number;
|
|
272
287
|
}>, signal?: AbortSignal): Promise<SdkInitResponse>;
|
|
288
|
+
/**
|
|
289
|
+
* Result returned by {@link performInit} when the backend reports an
|
|
290
|
+
* account claim transition. `null` means no claim was present.
|
|
291
|
+
*/
|
|
292
|
+
interface InitClaimResult {
|
|
293
|
+
claimResult: NonNullable<SdkInitResponse["claimResult"]>;
|
|
294
|
+
}
|
|
273
295
|
/**
|
|
274
296
|
* Orchestrates the full init flow: send request, update config, cache result.
|
|
275
297
|
* This function MUST NOT throw.
|
|
298
|
+
*
|
|
299
|
+
* Returns the claim result when the backend reports an account claim
|
|
300
|
+
* transition, or `null` when no claim result is available (including
|
|
301
|
+
* when init is skipped due to rate-limit backoff, missing API key,
|
|
302
|
+
* or request failure). Callers that do not need claim information
|
|
303
|
+
* can safely ignore the return value.
|
|
276
304
|
*/
|
|
277
|
-
declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<
|
|
305
|
+
declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<InitClaimResult | null>;
|
|
278
306
|
/**
|
|
279
307
|
* Returns the current capture config from the three-tier fallback chain:
|
|
280
308
|
* 1. In-memory config from latest init response
|
|
@@ -457,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
|
|
|
457
485
|
* and file entries. Validates the response against SourceMapUploadResponseSchema.
|
|
458
486
|
*/
|
|
459
487
|
declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
|
|
488
|
+
/** Builds at or above this byte size route to the presigned upload flow. */
|
|
489
|
+
declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
|
|
490
|
+
/** Signature for the blob upload function, injectable for testing. */
|
|
491
|
+
type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
|
|
492
|
+
url: string;
|
|
493
|
+
size: number;
|
|
494
|
+
}>;
|
|
495
|
+
/**
|
|
496
|
+
* Orchestrates the 3-phase presigned upload flow.
|
|
497
|
+
*
|
|
498
|
+
* 1. Requests presigned tokens for all source map files
|
|
499
|
+
* 2. Uploads each file to blob storage with a concurrency limit of 5
|
|
500
|
+
* 3. Submits the manifest to finalize the upload
|
|
501
|
+
*
|
|
502
|
+
* Accepts an optional `blobUploader` for test injection; defaults to
|
|
503
|
+
* {@link uploadToBlob}.
|
|
504
|
+
*/
|
|
505
|
+
declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
|
|
506
|
+
/**
|
|
507
|
+
* Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
|
|
508
|
+
*/
|
|
509
|
+
interface AutoUploadOptions {
|
|
510
|
+
/** Override blob availability check (for testing). */
|
|
511
|
+
checkBlobAvailable?: () => Promise<boolean>;
|
|
512
|
+
/** Override blob uploader (for testing). */
|
|
513
|
+
blobUploader?: BlobUploader;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Automatically routes source map uploads based on total build size.
|
|
517
|
+
*
|
|
518
|
+
* - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request
|
|
519
|
+
* {@link uploadSourceMaps} endpoint.
|
|
520
|
+
* - At or above the threshold: checks if `@vercel/blob` is available and
|
|
521
|
+
* uses the presigned 3-phase flow. Falls back to legacy with a warning
|
|
522
|
+
* if the package is not installed.
|
|
523
|
+
*/
|
|
524
|
+
declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
|
|
460
525
|
|
|
461
526
|
/**
|
|
462
527
|
* Manual error capture API.
|
|
@@ -519,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
|
|
|
519
584
|
*/
|
|
520
585
|
declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
|
|
521
586
|
|
|
522
|
-
export { type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, withGlasstraceConfig };
|
|
587
|
+
export { type AutoUploadOptions, type BlobUploader, type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type InitClaimResult, PRESIGNED_THRESHOLD_BYTES, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, uploadSourceMapsAuto, uploadSourceMapsPresigned, withGlasstraceConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ type GlasstraceEnvVars = z.infer<typeof GlasstraceEnvVarsSchema>;
|
|
|
67
67
|
* - SDK health diagnostics (embedded in init requests)
|
|
68
68
|
* - Discovery endpoint (GET /__glasstrace/config)
|
|
69
69
|
* - Source map upload (POST /v1/source-maps)
|
|
70
|
+
* - Presigned source map upload (POST /v1/source-maps/presign, POST /v1/source-maps/manifest)
|
|
70
71
|
*/
|
|
71
72
|
|
|
72
73
|
/** Test file import relationships, embedded in SDK init request. */
|
|
@@ -112,6 +113,11 @@ declare const SdkInitResponseSchema: z.ZodObject<{
|
|
|
112
113
|
maxTraceSizeBytes: z.ZodNumber;
|
|
113
114
|
maxConcurrentSessions: z.ZodNumber;
|
|
114
115
|
}, z.core.$strip>;
|
|
116
|
+
claimResult: z.ZodOptional<z.ZodObject<{
|
|
117
|
+
newApiKey: z.core.$ZodBranded<z.ZodString, "DevApiKey", "out">;
|
|
118
|
+
accountId: z.ZodString;
|
|
119
|
+
graceExpiresAt: z.ZodNumber;
|
|
120
|
+
}, z.core.$strip>>;
|
|
115
121
|
}, z.core.$strip>;
|
|
116
122
|
type SdkInitResponse = z.infer<typeof SdkInitResponseSchema>;
|
|
117
123
|
/** Response from POST /v1/source-maps. */
|
|
@@ -122,6 +128,15 @@ declare const SourceMapUploadResponseSchema: z.ZodObject<{
|
|
|
122
128
|
totalSizeBytes: z.ZodNumber;
|
|
123
129
|
}, z.core.$strip>;
|
|
124
130
|
type SourceMapUploadResponse = z.infer<typeof SourceMapUploadResponseSchema>;
|
|
131
|
+
/** Response confirming source map manifest activation. */
|
|
132
|
+
declare const SourceMapManifestResponseSchema: z.ZodObject<{
|
|
133
|
+
success: z.ZodLiteral<true>;
|
|
134
|
+
buildHash: z.core.$ZodBranded<z.ZodString, "BuildHash", "out">;
|
|
135
|
+
fileCount: z.ZodNumber;
|
|
136
|
+
totalSizeBytes: z.ZodNumber;
|
|
137
|
+
activatedAt: z.ZodNumber;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
type SourceMapManifestResponse = z.infer<typeof SourceMapManifestResponseSchema>;
|
|
125
140
|
|
|
126
141
|
/**
|
|
127
142
|
* Internal SDK error class with a typed diagnostic code.
|
|
@@ -270,11 +285,24 @@ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | n
|
|
|
270
285
|
message: string;
|
|
271
286
|
timestamp: number;
|
|
272
287
|
}>, signal?: AbortSignal): Promise<SdkInitResponse>;
|
|
288
|
+
/**
|
|
289
|
+
* Result returned by {@link performInit} when the backend reports an
|
|
290
|
+
* account claim transition. `null` means no claim was present.
|
|
291
|
+
*/
|
|
292
|
+
interface InitClaimResult {
|
|
293
|
+
claimResult: NonNullable<SdkInitResponse["claimResult"]>;
|
|
294
|
+
}
|
|
273
295
|
/**
|
|
274
296
|
* Orchestrates the full init flow: send request, update config, cache result.
|
|
275
297
|
* This function MUST NOT throw.
|
|
298
|
+
*
|
|
299
|
+
* Returns the claim result when the backend reports an account claim
|
|
300
|
+
* transition, or `null` when no claim result is available (including
|
|
301
|
+
* when init is skipped due to rate-limit backoff, missing API key,
|
|
302
|
+
* or request failure). Callers that do not need claim information
|
|
303
|
+
* can safely ignore the return value.
|
|
276
304
|
*/
|
|
277
|
-
declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<
|
|
305
|
+
declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<InitClaimResult | null>;
|
|
278
306
|
/**
|
|
279
307
|
* Returns the current capture config from the three-tier fallback chain:
|
|
280
308
|
* 1. In-memory config from latest init response
|
|
@@ -457,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
|
|
|
457
485
|
* and file entries. Validates the response against SourceMapUploadResponseSchema.
|
|
458
486
|
*/
|
|
459
487
|
declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
|
|
488
|
+
/** Builds at or above this byte size route to the presigned upload flow. */
|
|
489
|
+
declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
|
|
490
|
+
/** Signature for the blob upload function, injectable for testing. */
|
|
491
|
+
type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
|
|
492
|
+
url: string;
|
|
493
|
+
size: number;
|
|
494
|
+
}>;
|
|
495
|
+
/**
|
|
496
|
+
* Orchestrates the 3-phase presigned upload flow.
|
|
497
|
+
*
|
|
498
|
+
* 1. Requests presigned tokens for all source map files
|
|
499
|
+
* 2. Uploads each file to blob storage with a concurrency limit of 5
|
|
500
|
+
* 3. Submits the manifest to finalize the upload
|
|
501
|
+
*
|
|
502
|
+
* Accepts an optional `blobUploader` for test injection; defaults to
|
|
503
|
+
* {@link uploadToBlob}.
|
|
504
|
+
*/
|
|
505
|
+
declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
|
|
506
|
+
/**
|
|
507
|
+
* Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
|
|
508
|
+
*/
|
|
509
|
+
interface AutoUploadOptions {
|
|
510
|
+
/** Override blob availability check (for testing). */
|
|
511
|
+
checkBlobAvailable?: () => Promise<boolean>;
|
|
512
|
+
/** Override blob uploader (for testing). */
|
|
513
|
+
blobUploader?: BlobUploader;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Automatically routes source map uploads based on total build size.
|
|
517
|
+
*
|
|
518
|
+
* - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request
|
|
519
|
+
* {@link uploadSourceMaps} endpoint.
|
|
520
|
+
* - At or above the threshold: checks if `@vercel/blob` is available and
|
|
521
|
+
* uses the presigned 3-phase flow. Falls back to legacy with a warning
|
|
522
|
+
* if the package is not installed.
|
|
523
|
+
*/
|
|
524
|
+
declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
|
|
460
525
|
|
|
461
526
|
/**
|
|
462
527
|
* Manual error capture API.
|
|
@@ -519,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
|
|
|
519
584
|
*/
|
|
520
585
|
declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
|
|
521
586
|
|
|
522
|
-
export { type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, withGlasstraceConfig };
|
|
587
|
+
export { type AutoUploadOptions, type BlobUploader, type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type InitClaimResult, PRESIGNED_THRESHOLD_BYTES, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, uploadSourceMapsAuto, uploadSourceMapsPresigned, withGlasstraceConfig };
|
package/dist/index.js
CHANGED
|
@@ -2,17 +2,19 @@ import {
|
|
|
2
2
|
buildImportGraph,
|
|
3
3
|
discoverTestFiles,
|
|
4
4
|
extractImports
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SALPGSWK.js";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_CAPTURE_CONFIG,
|
|
8
8
|
GLASSTRACE_ATTRIBUTE_NAMES,
|
|
9
|
+
PresignedUploadResponseSchema,
|
|
9
10
|
SdkCachedConfigSchema,
|
|
10
11
|
SdkInitResponseSchema,
|
|
11
12
|
SessionIdSchema,
|
|
13
|
+
SourceMapManifestResponseSchema,
|
|
12
14
|
SourceMapUploadResponseSchema,
|
|
13
15
|
getOrCreateAnonKey,
|
|
14
16
|
readAnonKey
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-QW6W4CSA.js";
|
|
16
18
|
import {
|
|
17
19
|
INVALID_SPAN_CONTEXT,
|
|
18
20
|
SamplingDecision,
|
|
@@ -184,7 +186,7 @@ function classifyFetchTarget(url) {
|
|
|
184
186
|
|
|
185
187
|
// src/init-client.ts
|
|
186
188
|
import { readFileSync } from "fs";
|
|
187
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
189
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
188
190
|
import { join } from "path";
|
|
189
191
|
var GLASSTRACE_DIR = ".glasstrace";
|
|
190
192
|
var CONFIG_FILE = "config";
|
|
@@ -220,12 +222,14 @@ async function saveCachedConfig(response, projectRoot) {
|
|
|
220
222
|
const dirPath = join(root, GLASSTRACE_DIR);
|
|
221
223
|
const configPath = join(dirPath, CONFIG_FILE);
|
|
222
224
|
try {
|
|
223
|
-
await mkdir(dirPath, { recursive: true });
|
|
225
|
+
await mkdir(dirPath, { recursive: true, mode: 448 });
|
|
226
|
+
await chmod(dirPath, 448);
|
|
224
227
|
const cached = {
|
|
225
228
|
response,
|
|
226
229
|
cachedAt: Date.now()
|
|
227
230
|
};
|
|
228
|
-
await writeFile(configPath, JSON.stringify(cached), "utf-8");
|
|
231
|
+
await writeFile(configPath, JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
|
|
232
|
+
await chmod(configPath, 384);
|
|
229
233
|
} catch (err) {
|
|
230
234
|
console.warn(
|
|
231
235
|
`[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -278,16 +282,88 @@ async function sendInitRequest(config, anonKey, sdkVersion, importGraph, healthR
|
|
|
278
282
|
const body = await response.json();
|
|
279
283
|
return SdkInitResponseSchema.parse(body);
|
|
280
284
|
}
|
|
285
|
+
async function writeClaimedKey(newApiKey, projectRoot) {
|
|
286
|
+
const root = projectRoot ?? process.cwd();
|
|
287
|
+
const envLocalPath = join(root, ".env.local");
|
|
288
|
+
let envLocalWritten = false;
|
|
289
|
+
try {
|
|
290
|
+
let content;
|
|
291
|
+
try {
|
|
292
|
+
content = await readFile(envLocalPath, "utf-8");
|
|
293
|
+
if (/^GLASSTRACE_API_KEY=.*/m.test(content)) {
|
|
294
|
+
content = content.replace(
|
|
295
|
+
/^GLASSTRACE_API_KEY=.*$/gm,
|
|
296
|
+
`GLASSTRACE_API_KEY=${newApiKey}`
|
|
297
|
+
);
|
|
298
|
+
} else {
|
|
299
|
+
if (content.length > 0 && !content.endsWith("\n")) {
|
|
300
|
+
content += "\n";
|
|
301
|
+
}
|
|
302
|
+
content += `GLASSTRACE_API_KEY=${newApiKey}
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
} catch (readErr) {
|
|
306
|
+
const code = readErr instanceof Error ? readErr.code : void 0;
|
|
307
|
+
if (code !== "ENOENT") {
|
|
308
|
+
throw readErr;
|
|
309
|
+
}
|
|
310
|
+
content = `GLASSTRACE_API_KEY=${newApiKey}
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
await writeFile(envLocalPath, content, { encoding: "utf-8", mode: 384 });
|
|
314
|
+
await chmod(envLocalPath, 384);
|
|
315
|
+
envLocalWritten = true;
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
if (envLocalWritten) {
|
|
319
|
+
try {
|
|
320
|
+
process.stderr.write(
|
|
321
|
+
"[glasstrace] Account claimed! API key written to .env.local. Restart your dev server to use it.\n"
|
|
322
|
+
);
|
|
323
|
+
} catch {
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
let claimedKeyWritten = false;
|
|
328
|
+
try {
|
|
329
|
+
const dirPath = join(root, GLASSTRACE_DIR);
|
|
330
|
+
await mkdir(dirPath, { recursive: true, mode: 448 });
|
|
331
|
+
await chmod(dirPath, 448);
|
|
332
|
+
const claimedKeyPath = join(dirPath, "claimed-key");
|
|
333
|
+
await writeFile(claimedKeyPath, newApiKey, {
|
|
334
|
+
encoding: "utf-8",
|
|
335
|
+
mode: 384
|
|
336
|
+
});
|
|
337
|
+
await chmod(claimedKeyPath, 384);
|
|
338
|
+
claimedKeyWritten = true;
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
if (claimedKeyWritten) {
|
|
342
|
+
try {
|
|
343
|
+
process.stderr.write(
|
|
344
|
+
"[glasstrace] Account claimed! API key written to .glasstrace/claimed-key. Copy it to your .env.local file.\n"
|
|
345
|
+
);
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
process.stderr.write(
|
|
352
|
+
"[glasstrace] Account claimed but could not write key to disk. Visit your dashboard settings to rotate and retrieve a new API key.\n"
|
|
353
|
+
);
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
}
|
|
281
357
|
async function performInit(config, anonKey, sdkVersion) {
|
|
282
358
|
if (rateLimitBackoff) {
|
|
283
359
|
rateLimitBackoff = false;
|
|
284
|
-
return;
|
|
360
|
+
return null;
|
|
285
361
|
}
|
|
286
362
|
try {
|
|
287
363
|
const effectiveKey = config.apiKey || anonKey;
|
|
288
364
|
if (!effectiveKey) {
|
|
289
365
|
console.warn("[glasstrace] No API key available for init request.");
|
|
290
|
-
return;
|
|
366
|
+
return null;
|
|
291
367
|
}
|
|
292
368
|
const controller = new AbortController();
|
|
293
369
|
const timeoutId = setTimeout(() => controller.abort(), INIT_TIMEOUT_MS);
|
|
@@ -304,45 +380,55 @@ async function performInit(config, anonKey, sdkVersion) {
|
|
|
304
380
|
clearTimeout(timeoutId);
|
|
305
381
|
currentConfig = result;
|
|
306
382
|
await saveCachedConfig(result);
|
|
383
|
+
if (result.claimResult) {
|
|
384
|
+
try {
|
|
385
|
+
await writeClaimedKey(result.claimResult.newApiKey);
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
return { claimResult: result.claimResult };
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
307
391
|
} catch (err) {
|
|
308
392
|
clearTimeout(timeoutId);
|
|
309
393
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
310
394
|
console.warn("[glasstrace] ingestion_unreachable: Init request timed out.");
|
|
311
|
-
return;
|
|
395
|
+
return null;
|
|
312
396
|
}
|
|
313
397
|
const status = err.status;
|
|
314
398
|
if (status === 401) {
|
|
315
399
|
console.warn(
|
|
316
400
|
"[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY."
|
|
317
401
|
);
|
|
318
|
-
return;
|
|
402
|
+
return null;
|
|
319
403
|
}
|
|
320
404
|
if (status === 429) {
|
|
321
405
|
console.warn("[glasstrace] ingestion_rate_limited: Backing off.");
|
|
322
406
|
rateLimitBackoff = true;
|
|
323
|
-
return;
|
|
407
|
+
return null;
|
|
324
408
|
}
|
|
325
409
|
if (typeof status === "number" && status >= 400) {
|
|
326
410
|
console.warn(
|
|
327
411
|
`[glasstrace] Init request failed with status ${status}. Using cached config.`
|
|
328
412
|
);
|
|
329
|
-
return;
|
|
413
|
+
return null;
|
|
330
414
|
}
|
|
331
415
|
if (err instanceof Error && err.name === "ZodError") {
|
|
332
416
|
console.warn(
|
|
333
417
|
"[glasstrace] Init response failed validation (schema version mismatch?). Using cached config."
|
|
334
418
|
);
|
|
335
|
-
return;
|
|
419
|
+
return null;
|
|
336
420
|
}
|
|
337
421
|
console.warn(
|
|
338
422
|
`[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`
|
|
339
423
|
);
|
|
424
|
+
return null;
|
|
340
425
|
}
|
|
341
426
|
} catch (err) {
|
|
342
427
|
console.warn(
|
|
343
428
|
`[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`
|
|
344
429
|
);
|
|
345
430
|
}
|
|
431
|
+
return null;
|
|
346
432
|
}
|
|
347
433
|
function getActiveConfig() {
|
|
348
434
|
if (currentConfig) {
|
|
@@ -3452,6 +3538,14 @@ async function installConsoleCapture() {
|
|
|
3452
3538
|
}
|
|
3453
3539
|
};
|
|
3454
3540
|
}
|
|
3541
|
+
function sdkLog(level, message) {
|
|
3542
|
+
isGlasstraceLog = true;
|
|
3543
|
+
try {
|
|
3544
|
+
console[level](message);
|
|
3545
|
+
} finally {
|
|
3546
|
+
isGlasstraceLog = false;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3455
3549
|
|
|
3456
3550
|
// src/register.ts
|
|
3457
3551
|
var consoleCaptureInstalled = false;
|
|
@@ -3541,7 +3635,11 @@ function registerGlasstrace(options) {
|
|
|
3541
3635
|
if (config.verbose) {
|
|
3542
3636
|
console.info("[glasstrace] Background init firing.");
|
|
3543
3637
|
}
|
|
3544
|
-
await performInit(config, anonKey, "0.
|
|
3638
|
+
const initResult = await performInit(config, anonKey, "0.7.1");
|
|
3639
|
+
if (initResult?.claimResult) {
|
|
3640
|
+
setResolvedApiKey(initResult.claimResult.newApiKey);
|
|
3641
|
+
notifyApiKeyResolved();
|
|
3642
|
+
}
|
|
3545
3643
|
maybeInstallConsoleCapture();
|
|
3546
3644
|
} catch (err) {
|
|
3547
3645
|
console.warn(
|
|
@@ -3561,7 +3659,11 @@ function registerGlasstrace(options) {
|
|
|
3561
3659
|
if (config.verbose) {
|
|
3562
3660
|
console.info("[glasstrace] Background init firing.");
|
|
3563
3661
|
}
|
|
3564
|
-
await performInit(config, anonKey, "0.
|
|
3662
|
+
const initResult = await performInit(config, anonKey, "0.7.1");
|
|
3663
|
+
if (initResult?.claimResult) {
|
|
3664
|
+
setResolvedApiKey(initResult.claimResult.newApiKey);
|
|
3665
|
+
notifyApiKeyResolved();
|
|
3666
|
+
}
|
|
3565
3667
|
maybeInstallConsoleCapture();
|
|
3566
3668
|
} catch (err) {
|
|
3567
3669
|
console.warn(
|
|
@@ -3583,7 +3685,7 @@ function registerGlasstrace(options) {
|
|
|
3583
3685
|
if (config.verbose) {
|
|
3584
3686
|
console.info("[glasstrace] Background init firing.");
|
|
3585
3687
|
}
|
|
3586
|
-
await performInit(config, anonKeyForInit, "0.
|
|
3688
|
+
await performInit(config, anonKeyForInit, "0.7.1");
|
|
3587
3689
|
maybeInstallConsoleCapture();
|
|
3588
3690
|
} catch (err) {
|
|
3589
3691
|
console.warn(
|
|
@@ -3682,10 +3784,7 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
|
|
|
3682
3784
|
sourceMap: m.content
|
|
3683
3785
|
}))
|
|
3684
3786
|
};
|
|
3685
|
-
|
|
3686
|
-
while (baseUrl.endsWith("/")) {
|
|
3687
|
-
baseUrl = baseUrl.slice(0, -1);
|
|
3688
|
-
}
|
|
3787
|
+
const baseUrl = stripTrailingSlashes(endpoint);
|
|
3689
3788
|
const response = await fetch(`${baseUrl}/v1/source-maps`, {
|
|
3690
3789
|
method: "POST",
|
|
3691
3790
|
headers: {
|
|
@@ -3706,6 +3805,155 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
|
|
|
3706
3805
|
const json = await response.json();
|
|
3707
3806
|
return SourceMapUploadResponseSchema.parse(json);
|
|
3708
3807
|
}
|
|
3808
|
+
var PRESIGNED_THRESHOLD_BYTES = 45e5;
|
|
3809
|
+
function stripTrailingSlashes(url) {
|
|
3810
|
+
let result = url;
|
|
3811
|
+
while (result.endsWith("/")) {
|
|
3812
|
+
result = result.slice(0, -1);
|
|
3813
|
+
}
|
|
3814
|
+
return result;
|
|
3815
|
+
}
|
|
3816
|
+
async function requestPresignedTokens(apiKey, endpoint, buildHash, files) {
|
|
3817
|
+
const baseUrl = stripTrailingSlashes(endpoint);
|
|
3818
|
+
const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {
|
|
3819
|
+
method: "POST",
|
|
3820
|
+
headers: {
|
|
3821
|
+
"Content-Type": "application/json",
|
|
3822
|
+
Authorization: `Bearer ${apiKey}`
|
|
3823
|
+
},
|
|
3824
|
+
body: JSON.stringify({ buildHash, files })
|
|
3825
|
+
});
|
|
3826
|
+
if (!response.ok) {
|
|
3827
|
+
try {
|
|
3828
|
+
await response.text();
|
|
3829
|
+
} catch {
|
|
3830
|
+
}
|
|
3831
|
+
throw new Error(
|
|
3832
|
+
`Presigned token request failed: ${String(response.status)} ${response.statusText}`
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
const json = await response.json();
|
|
3836
|
+
return PresignedUploadResponseSchema.parse(json);
|
|
3837
|
+
}
|
|
3838
|
+
async function uploadToBlob(clientToken, pathname, content) {
|
|
3839
|
+
let mod;
|
|
3840
|
+
try {
|
|
3841
|
+
mod = await import("@vercel/blob/client");
|
|
3842
|
+
} catch (err) {
|
|
3843
|
+
const code = err.code;
|
|
3844
|
+
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
3845
|
+
throw new Error(
|
|
3846
|
+
"Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob"
|
|
3847
|
+
);
|
|
3848
|
+
}
|
|
3849
|
+
throw err;
|
|
3850
|
+
}
|
|
3851
|
+
const result = await mod.put(pathname, new Blob([content]), {
|
|
3852
|
+
access: "public",
|
|
3853
|
+
token: clientToken
|
|
3854
|
+
});
|
|
3855
|
+
return { url: result.url, size: Buffer.byteLength(content, "utf-8") };
|
|
3856
|
+
}
|
|
3857
|
+
async function submitManifest(apiKey, endpoint, uploadId, buildHash, files) {
|
|
3858
|
+
const baseUrl = stripTrailingSlashes(endpoint);
|
|
3859
|
+
const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {
|
|
3860
|
+
method: "POST",
|
|
3861
|
+
headers: {
|
|
3862
|
+
"Content-Type": "application/json",
|
|
3863
|
+
Authorization: `Bearer ${apiKey}`
|
|
3864
|
+
},
|
|
3865
|
+
body: JSON.stringify({ uploadId, buildHash, files })
|
|
3866
|
+
});
|
|
3867
|
+
if (!response.ok) {
|
|
3868
|
+
try {
|
|
3869
|
+
await response.text();
|
|
3870
|
+
} catch {
|
|
3871
|
+
}
|
|
3872
|
+
throw new Error(
|
|
3873
|
+
`Source map manifest submission failed: ${String(response.status)} ${response.statusText}`
|
|
3874
|
+
);
|
|
3875
|
+
}
|
|
3876
|
+
const json = await response.json();
|
|
3877
|
+
return SourceMapManifestResponseSchema.parse(json);
|
|
3878
|
+
}
|
|
3879
|
+
async function uploadSourceMapsPresigned(apiKey, endpoint, buildHash, maps, blobUploader = uploadToBlob) {
|
|
3880
|
+
if (maps.length === 0) {
|
|
3881
|
+
throw new Error("No source maps to upload");
|
|
3882
|
+
}
|
|
3883
|
+
const presigned = await requestPresignedTokens(
|
|
3884
|
+
apiKey,
|
|
3885
|
+
endpoint,
|
|
3886
|
+
buildHash,
|
|
3887
|
+
maps.map((m) => ({
|
|
3888
|
+
filePath: m.filePath,
|
|
3889
|
+
sizeBytes: Buffer.byteLength(m.content, "utf-8")
|
|
3890
|
+
}))
|
|
3891
|
+
);
|
|
3892
|
+
const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));
|
|
3893
|
+
if (mapsByPath.size !== maps.length) {
|
|
3894
|
+
throw new Error("Duplicate filePath entries in source maps");
|
|
3895
|
+
}
|
|
3896
|
+
for (const token of presigned.files) {
|
|
3897
|
+
if (!mapsByPath.has(token.filePath)) {
|
|
3898
|
+
throw new Error(
|
|
3899
|
+
`Presigned token for "${token.filePath}" has no matching source map entry`
|
|
3900
|
+
);
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
const CONCURRENCY = 5;
|
|
3904
|
+
const uploadResults = [];
|
|
3905
|
+
for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {
|
|
3906
|
+
const chunk = presigned.files.slice(i, i + CONCURRENCY);
|
|
3907
|
+
const chunkResults = await Promise.all(
|
|
3908
|
+
chunk.map(async (token) => {
|
|
3909
|
+
const entry = mapsByPath.get(token.filePath);
|
|
3910
|
+
const result = await blobUploader(token.clientToken, token.pathname, entry.content);
|
|
3911
|
+
return {
|
|
3912
|
+
filePath: token.filePath,
|
|
3913
|
+
sizeBytes: result.size,
|
|
3914
|
+
blobUrl: result.url
|
|
3915
|
+
};
|
|
3916
|
+
})
|
|
3917
|
+
);
|
|
3918
|
+
uploadResults.push(...chunkResults);
|
|
3919
|
+
}
|
|
3920
|
+
return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);
|
|
3921
|
+
}
|
|
3922
|
+
async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options) {
|
|
3923
|
+
if (maps.length === 0) {
|
|
3924
|
+
throw new Error("No source maps to upload");
|
|
3925
|
+
}
|
|
3926
|
+
const totalBytes = maps.reduce(
|
|
3927
|
+
(sum, m) => sum + Buffer.byteLength(m.content, "utf-8"),
|
|
3928
|
+
0
|
|
3929
|
+
);
|
|
3930
|
+
if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {
|
|
3931
|
+
return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
|
|
3932
|
+
}
|
|
3933
|
+
const checkAvailable = options?.checkBlobAvailable ?? (async () => {
|
|
3934
|
+
try {
|
|
3935
|
+
await import("@vercel/blob/client");
|
|
3936
|
+
return true;
|
|
3937
|
+
} catch {
|
|
3938
|
+
return false;
|
|
3939
|
+
}
|
|
3940
|
+
});
|
|
3941
|
+
const blobAvailable = await checkAvailable();
|
|
3942
|
+
if (blobAvailable) {
|
|
3943
|
+
return uploadSourceMapsPresigned(
|
|
3944
|
+
apiKey,
|
|
3945
|
+
endpoint,
|
|
3946
|
+
buildHash,
|
|
3947
|
+
maps,
|
|
3948
|
+
options?.blobUploader
|
|
3949
|
+
);
|
|
3950
|
+
}
|
|
3951
|
+
sdkLog(
|
|
3952
|
+
"warn",
|
|
3953
|
+
`[glasstrace] Build exceeds 4.5MB (${totalBytes} bytes). Install @vercel/blob for presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`
|
|
3954
|
+
);
|
|
3955
|
+
return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
|
|
3956
|
+
}
|
|
3709
3957
|
|
|
3710
3958
|
// src/config-wrapper.ts
|
|
3711
3959
|
function withGlasstraceConfig(nextConfig) {
|
|
@@ -3823,6 +4071,7 @@ function captureError(error) {
|
|
|
3823
4071
|
export {
|
|
3824
4072
|
GlasstraceExporter,
|
|
3825
4073
|
GlasstraceSpanProcessor,
|
|
4074
|
+
PRESIGNED_THRESHOLD_BYTES,
|
|
3826
4075
|
SdkError,
|
|
3827
4076
|
SessionManager,
|
|
3828
4077
|
buildImportGraph,
|
|
@@ -3850,6 +4099,8 @@ export {
|
|
|
3850
4099
|
saveCachedConfig,
|
|
3851
4100
|
sendInitRequest,
|
|
3852
4101
|
uploadSourceMaps,
|
|
4102
|
+
uploadSourceMapsAuto,
|
|
4103
|
+
uploadSourceMapsPresigned,
|
|
3853
4104
|
withGlasstraceConfig
|
|
3854
4105
|
};
|
|
3855
4106
|
//# sourceMappingURL=index.js.map
|