@glasstrace/sdk 0.9.1 → 0.10.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/dist/index.js CHANGED
@@ -1,20 +1,33 @@
1
+ import {
2
+ PRESIGNED_THRESHOLD_BYTES,
3
+ collectSourceMaps,
4
+ computeBuildHash,
5
+ installConsoleCapture,
6
+ isAnonymousMode,
7
+ isProductionDisabled,
8
+ maybeShowMcpNudge,
9
+ readEnvVars,
10
+ resolveConfig,
11
+ uploadSourceMaps,
12
+ uploadSourceMapsAuto,
13
+ uploadSourceMapsPresigned
14
+ } from "./chunk-KOYJ2UQE.js";
1
15
  import {
2
16
  buildImportGraph,
3
17
  discoverTestFiles,
4
18
  extractImports
5
- } from "./chunk-Y6V7BTF3.js";
19
+ } from "./chunk-5MAHIPFH.js";
20
+ import {
21
+ getOrCreateAnonKey,
22
+ readAnonKey
23
+ } from "./chunk-ZRNG36LU.js";
6
24
  import {
7
25
  DEFAULT_CAPTURE_CONFIG,
8
26
  GLASSTRACE_ATTRIBUTE_NAMES,
9
- PresignedUploadResponseSchema,
10
27
  SdkCachedConfigSchema,
11
28
  SdkInitResponseSchema,
12
- SessionIdSchema,
13
- SourceMapManifestResponseSchema,
14
- SourceMapUploadResponseSchema,
15
- getOrCreateAnonKey,
16
- readAnonKey
17
- } from "./chunk-PQWAKVQ5.js";
29
+ SessionIdSchema
30
+ } from "./chunk-O3Y45VGV.js";
18
31
  import {
19
32
  INVALID_SPAN_CONTEXT,
20
33
  SamplingDecision,
@@ -42,56 +55,6 @@ var SdkError = class extends Error {
42
55
  }
43
56
  };
44
57
 
45
- // src/env-detection.ts
46
- var DEFAULT_ENDPOINT = "https://api.glasstrace.dev";
47
- function readEnvVars() {
48
- return {
49
- GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY?.trim() || void 0,
50
- GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,
51
- GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,
52
- GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,
53
- NODE_ENV: process.env.NODE_ENV,
54
- VERCEL_ENV: process.env.VERCEL_ENV
55
- };
56
- }
57
- function resolveConfig(options) {
58
- const env = readEnvVars();
59
- return {
60
- apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,
61
- endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,
62
- forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === "true",
63
- verbose: options?.verbose ?? false,
64
- environment: env.GLASSTRACE_ENV,
65
- coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === "true",
66
- nodeEnv: env.NODE_ENV,
67
- vercelEnv: env.VERCEL_ENV
68
- };
69
- }
70
- function isProductionDisabled(config) {
71
- if (config.forceEnable) {
72
- return false;
73
- }
74
- if (config.nodeEnv === "production") {
75
- return true;
76
- }
77
- if (config.vercelEnv === "production") {
78
- return true;
79
- }
80
- return false;
81
- }
82
- function isAnonymousMode(config) {
83
- if (config.apiKey === void 0) {
84
- return true;
85
- }
86
- if (config.apiKey.trim() === "") {
87
- return true;
88
- }
89
- if (config.apiKey.startsWith("gt_anon_")) {
90
- return true;
91
- }
92
- return false;
93
- }
94
-
95
58
  // src/session.ts
96
59
  import { createHash } from "crypto";
97
60
  var FOUR_HOURS_MS = 4 * 60 * 60 * 1e3;
@@ -2230,7 +2193,7 @@ function appendRootPathToUrlIfNeeded(url) {
2230
2193
  return void 0;
2231
2194
  }
2232
2195
  }
2233
- function appendResourcePathToUrl(url, path3) {
2196
+ function appendResourcePathToUrl(url, path2) {
2234
2197
  try {
2235
2198
  new URL(url);
2236
2199
  } catch {
@@ -2240,11 +2203,11 @@ function appendResourcePathToUrl(url, path3) {
2240
2203
  if (!url.endsWith("/")) {
2241
2204
  url = url + "/";
2242
2205
  }
2243
- url += path3;
2206
+ url += path2;
2244
2207
  try {
2245
2208
  new URL(url);
2246
2209
  } catch {
2247
- diag.warn(`Configuration: Provided URL appended with '${path3}' is not a valid URL, using 'undefined' instead of '${url}'`);
2210
+ diag.warn(`Configuration: Provided URL appended with '${path2}' is not a valid URL, using 'undefined' instead of '${url}'`);
2248
2211
  return void 0;
2249
2212
  }
2250
2213
  return url;
@@ -3488,112 +3451,6 @@ async function configureOtel(config, sessionManager) {
3488
3451
  registerShutdownHooks(provider);
3489
3452
  }
3490
3453
 
3491
- // src/nudge/error-nudge.ts
3492
- import { existsSync } from "fs";
3493
- import { join as join2 } from "path";
3494
- var hasFired = false;
3495
- function sanitize(input) {
3496
- return input.replace(/[\x00-\x1f\x7f]/g, "");
3497
- }
3498
- function maybeShowMcpNudge(errorSummary) {
3499
- if (hasFired) {
3500
- return;
3501
- }
3502
- const config = resolveConfig();
3503
- if (isProductionDisabled(config)) {
3504
- hasFired = true;
3505
- return;
3506
- }
3507
- let markerExists = false;
3508
- try {
3509
- const markerPath = join2(process.cwd(), ".glasstrace", "mcp-connected");
3510
- markerExists = existsSync(markerPath);
3511
- } catch {
3512
- markerExists = false;
3513
- }
3514
- if (markerExists) {
3515
- hasFired = true;
3516
- return;
3517
- }
3518
- hasFired = true;
3519
- const safe = sanitize(errorSummary);
3520
- process.stderr.write(
3521
- `[glasstrace] Error captured: ${safe}
3522
- Debug with AI: ask your agent "What's the latest Glasstrace error?"
3523
- Not connected? Run: npx glasstrace mcp add
3524
- `
3525
- );
3526
- }
3527
-
3528
- // src/console-capture.ts
3529
- var isGlasstraceLog = false;
3530
- var originalError = null;
3531
- var originalWarn = null;
3532
- var installed = false;
3533
- var otelApi = null;
3534
- function formatArgs(args) {
3535
- return args.map((arg) => {
3536
- if (typeof arg === "string") return arg;
3537
- if (arg instanceof Error) return arg.stack ?? arg.message;
3538
- try {
3539
- return JSON.stringify(arg);
3540
- } catch {
3541
- return String(arg);
3542
- }
3543
- }).join(" ");
3544
- }
3545
- function isSdkMessage(args) {
3546
- return typeof args[0] === "string" && args[0].startsWith("[glasstrace]");
3547
- }
3548
- async function installConsoleCapture() {
3549
- if (installed) return;
3550
- try {
3551
- otelApi = await import("./esm-POMEQPKL.js");
3552
- } catch {
3553
- otelApi = null;
3554
- }
3555
- originalError = console.error;
3556
- originalWarn = console.warn;
3557
- installed = true;
3558
- console.error = (...args) => {
3559
- originalError.apply(console, args);
3560
- if (isGlasstraceLog || isSdkMessage(args)) return;
3561
- if (otelApi) {
3562
- const span = otelApi.trace.getSpan(otelApi.context.active());
3563
- if (span) {
3564
- const formattedMessage = formatArgs(args);
3565
- span.addEvent("console.error", {
3566
- "console.message": formattedMessage
3567
- });
3568
- try {
3569
- maybeShowMcpNudge(formattedMessage);
3570
- } catch {
3571
- }
3572
- }
3573
- }
3574
- };
3575
- console.warn = (...args) => {
3576
- originalWarn.apply(console, args);
3577
- if (isGlasstraceLog || isSdkMessage(args)) return;
3578
- if (otelApi) {
3579
- const span = otelApi.trace.getSpan(otelApi.context.active());
3580
- if (span) {
3581
- span.addEvent("console.warn", {
3582
- "console.message": formatArgs(args)
3583
- });
3584
- }
3585
- }
3586
- };
3587
- }
3588
- function sdkLog(level, message) {
3589
- isGlasstraceLog = true;
3590
- try {
3591
- console[level](message);
3592
- } finally {
3593
- isGlasstraceLog = false;
3594
- }
3595
- }
3596
-
3597
3454
  // src/register.ts
3598
3455
  var consoleCaptureInstalled = false;
3599
3456
  var discoveryHandler = null;
@@ -3604,6 +3461,12 @@ function registerGlasstrace(options) {
3604
3461
  if (isRegistered) {
3605
3462
  return;
3606
3463
  }
3464
+ if (typeof process === "undefined" || typeof process.versions?.node !== "string") {
3465
+ console.warn(
3466
+ "[glasstrace] SDK requires a Node.js runtime. Edge Runtime, browser, and Deno without Node compat are not supported. Glasstrace is disabled in this environment."
3467
+ );
3468
+ return;
3469
+ }
3607
3470
  const config = resolveConfig(options);
3608
3471
  if (config.verbose) {
3609
3472
  console.info("[glasstrace] Config resolved.");
@@ -3734,7 +3597,7 @@ async function backgroundInit(config, anonKeyForInit, generation) {
3734
3597
  if (config.verbose) {
3735
3598
  console.info("[glasstrace] Background init firing.");
3736
3599
  }
3737
- const initResult = await performInit(config, anonKeyForInit, "0.9.1");
3600
+ const initResult = await performInit(config, anonKeyForInit, "0.10.0");
3738
3601
  if (generation !== registrationGeneration) return;
3739
3602
  if (initResult?.claimResult) {
3740
3603
  setResolvedApiKey(initResult.claimResult.newApiKey);
@@ -3761,241 +3624,11 @@ function isDiscoveryEnabled(config) {
3761
3624
  return false;
3762
3625
  }
3763
3626
 
3764
- // src/source-map-uploader.ts
3765
- import * as fs2 from "fs/promises";
3766
- import * as path2 from "path";
3767
- import * as crypto from "crypto";
3768
- import { execFileSync } from "child_process";
3769
- async function collectSourceMaps(buildDir) {
3770
- const results = [];
3771
- try {
3772
- await walkDir(buildDir, buildDir, results);
3773
- } catch {
3774
- return [];
3775
- }
3776
- return results;
3777
- }
3778
- async function walkDir(baseDir, currentDir, results) {
3779
- let entries;
3780
- try {
3781
- entries = await fs2.readdir(currentDir, { withFileTypes: true });
3782
- } catch {
3783
- return;
3784
- }
3785
- for (const entry of entries) {
3786
- const fullPath = path2.join(currentDir, entry.name);
3787
- if (entry.isDirectory()) {
3788
- await walkDir(baseDir, fullPath, results);
3789
- } else if (entry.isFile() && entry.name.endsWith(".map")) {
3790
- try {
3791
- const content = await fs2.readFile(fullPath, "utf-8");
3792
- const relativePath = path2.relative(baseDir, fullPath).replace(/\\/g, "/");
3793
- const compiledPath = relativePath.replace(/\.map$/, "");
3794
- results.push({ filePath: compiledPath, content });
3795
- } catch {
3796
- }
3797
- }
3798
- }
3799
- }
3800
- async function computeBuildHash(maps) {
3801
- try {
3802
- const sha = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf-8" }).trim();
3803
- if (sha) {
3804
- return sha;
3805
- }
3806
- } catch {
3807
- }
3808
- const sortedMaps = [...maps ?? []].sort(
3809
- (a, b) => a.filePath.localeCompare(b.filePath)
3810
- );
3811
- const hashInput = sortedMaps.map((m) => `${m.filePath}
3812
- ${m.content.length}
3813
- ${m.content}`).join("");
3814
- const hash = crypto.createHash("sha256").update(hashInput).digest("hex");
3815
- return hash;
3816
- }
3817
- async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3818
- const body = {
3819
- apiKey,
3820
- buildHash,
3821
- files: maps.map((m) => ({
3822
- filePath: m.filePath,
3823
- sourceMap: m.content
3824
- }))
3825
- };
3826
- const baseUrl = stripTrailingSlashes(endpoint);
3827
- const response = await fetch(`${baseUrl}/v1/source-maps`, {
3828
- method: "POST",
3829
- headers: {
3830
- "Content-Type": "application/json",
3831
- Authorization: `Bearer ${apiKey}`
3832
- },
3833
- body: JSON.stringify(body)
3834
- });
3835
- if (!response.ok) {
3836
- try {
3837
- await response.text();
3838
- } catch {
3839
- }
3840
- throw new Error(
3841
- `Source map upload failed: ${String(response.status)} ${response.statusText}`
3842
- );
3843
- }
3844
- const json = await response.json();
3845
- return SourceMapUploadResponseSchema.parse(json);
3846
- }
3847
- var PRESIGNED_THRESHOLD_BYTES = 45e5;
3848
- function stripTrailingSlashes(url) {
3849
- let result = url;
3850
- while (result.endsWith("/")) {
3851
- result = result.slice(0, -1);
3852
- }
3853
- return result;
3854
- }
3855
- async function requestPresignedTokens(apiKey, endpoint, buildHash, files) {
3856
- const baseUrl = stripTrailingSlashes(endpoint);
3857
- const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {
3858
- method: "POST",
3859
- headers: {
3860
- "Content-Type": "application/json",
3861
- Authorization: `Bearer ${apiKey}`
3862
- },
3863
- body: JSON.stringify({ buildHash, files })
3864
- });
3865
- if (!response.ok) {
3866
- try {
3867
- await response.text();
3868
- } catch {
3869
- }
3870
- throw new Error(
3871
- `Presigned token request failed: ${String(response.status)} ${response.statusText}`
3872
- );
3873
- }
3874
- const json = await response.json();
3875
- return PresignedUploadResponseSchema.parse(json);
3876
- }
3877
- async function uploadToBlob(clientToken, pathname, content) {
3878
- let mod;
3879
- try {
3880
- mod = await import("@vercel/blob/client");
3881
- } catch (err) {
3882
- const code = err.code;
3883
- if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
3884
- throw new Error(
3885
- "Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob"
3886
- );
3887
- }
3888
- throw err;
3889
- }
3890
- const result = await mod.put(pathname, new Blob([content]), {
3891
- access: "public",
3892
- token: clientToken
3893
- });
3894
- return { url: result.url, size: Buffer.byteLength(content, "utf-8") };
3895
- }
3896
- async function submitManifest(apiKey, endpoint, uploadId, buildHash, files) {
3897
- const baseUrl = stripTrailingSlashes(endpoint);
3898
- const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {
3899
- method: "POST",
3900
- headers: {
3901
- "Content-Type": "application/json",
3902
- Authorization: `Bearer ${apiKey}`
3903
- },
3904
- body: JSON.stringify({ uploadId, buildHash, files })
3905
- });
3906
- if (!response.ok) {
3907
- try {
3908
- await response.text();
3909
- } catch {
3910
- }
3911
- throw new Error(
3912
- `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`
3913
- );
3914
- }
3915
- const json = await response.json();
3916
- return SourceMapManifestResponseSchema.parse(json);
3917
- }
3918
- async function uploadSourceMapsPresigned(apiKey, endpoint, buildHash, maps, blobUploader = uploadToBlob) {
3919
- if (maps.length === 0) {
3920
- throw new Error("No source maps to upload");
3921
- }
3922
- const presigned = await requestPresignedTokens(
3923
- apiKey,
3924
- endpoint,
3925
- buildHash,
3926
- maps.map((m) => ({
3927
- filePath: m.filePath,
3928
- sizeBytes: Buffer.byteLength(m.content, "utf-8")
3929
- }))
3930
- );
3931
- const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));
3932
- if (mapsByPath.size !== maps.length) {
3933
- throw new Error("Duplicate filePath entries in source maps");
3934
- }
3935
- for (const token of presigned.files) {
3936
- if (!mapsByPath.has(token.filePath)) {
3937
- throw new Error(
3938
- `Presigned token for "${token.filePath}" has no matching source map entry`
3939
- );
3940
- }
3941
- }
3942
- const CONCURRENCY = 5;
3943
- const uploadResults = [];
3944
- for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {
3945
- const chunk = presigned.files.slice(i, i + CONCURRENCY);
3946
- const chunkResults = await Promise.all(
3947
- chunk.map(async (token) => {
3948
- const entry = mapsByPath.get(token.filePath);
3949
- const result = await blobUploader(token.clientToken, token.pathname, entry.content);
3950
- return {
3951
- filePath: token.filePath,
3952
- sizeBytes: result.size,
3953
- blobUrl: result.url
3954
- };
3955
- })
3956
- );
3957
- uploadResults.push(...chunkResults);
3958
- }
3959
- return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);
3960
- }
3961
- async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options) {
3962
- if (maps.length === 0) {
3963
- throw new Error("No source maps to upload");
3964
- }
3965
- const totalBytes = maps.reduce(
3966
- (sum, m) => sum + Buffer.byteLength(m.content, "utf-8"),
3967
- 0
3968
- );
3969
- if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {
3970
- return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3971
- }
3972
- const checkAvailable = options?.checkBlobAvailable ?? (async () => {
3973
- try {
3974
- await import("@vercel/blob/client");
3975
- return true;
3976
- } catch {
3977
- return false;
3978
- }
3979
- });
3980
- const blobAvailable = await checkAvailable();
3981
- if (blobAvailable) {
3982
- return uploadSourceMapsPresigned(
3983
- apiKey,
3984
- endpoint,
3985
- buildHash,
3986
- maps,
3987
- options?.blobUploader
3988
- );
3989
- }
3990
- sdkLog(
3991
- "warn",
3992
- `[glasstrace] Build exceeds 4.5MB (${totalBytes} bytes). Install @vercel/blob for presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`
3993
- );
3994
- return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3995
- }
3996
-
3997
3627
  // src/config-wrapper.ts
3998
3628
  function withGlasstraceConfig(nextConfig) {
3629
+ if (typeof process === "undefined" || typeof process.versions?.node !== "string") {
3630
+ return nextConfig != null ? { ...nextConfig } : {};
3631
+ }
3999
3632
  const config = nextConfig != null ? { ...nextConfig } : {};
4000
3633
  const existingExperimental = config.experimental ?? {};
4001
3634
  config.experimental = { ...existingExperimental, serverSourceMaps: true };
@@ -4038,13 +3671,14 @@ async function handleSourceMapUpload(distDir) {
4038
3671
  );
4039
3672
  return;
4040
3673
  }
4041
- const maps = await collectSourceMaps(distDir);
3674
+ const { collectSourceMaps: collectSourceMaps2, computeBuildHash: computeBuildHash2, uploadSourceMaps: uploadSourceMaps2 } = await import("./source-map-uploader-OA5NCDOK.js");
3675
+ const maps = await collectSourceMaps2(distDir);
4042
3676
  if (maps.length === 0) {
4043
3677
  console.info("[glasstrace] No source map files found. Skipping upload.");
4044
3678
  return;
4045
3679
  }
4046
- const buildHash = await computeBuildHash(maps);
4047
- await uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3680
+ const buildHash = await computeBuildHash2(maps);
3681
+ await uploadSourceMaps2(apiKey, endpoint, buildHash, maps);
4048
3682
  console.info(
4049
3683
  `[glasstrace] Uploaded ${String(maps.length)} source map(s) for build ${buildHash}.`
4050
3684
  );