@blaxel/core 0.3.7-preview.229 → 0.3.7-preview.230

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.
@@ -0,0 +1,189 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ const mockSettings = vi.hoisted(() => ({
3
+ tracking: true,
4
+ sentryDsn: "https://public-key@sentry.example/123",
5
+ env: "prod",
6
+ version: "9.9.9",
7
+ commit: "abcdef0",
8
+ workspace: "private-workspace",
9
+ }));
10
+ vi.mock("./settings.js", () => ({ settings: mockSettings }));
11
+ const appFrameUrl = "file:///Users/customer/private-project/app.ts";
12
+ function makeSdkError(message = "secret response body for resource customer-123") {
13
+ // This helper lives under the exact @blaxel/core source root, so its real
14
+ // runtime frame exercises package-root attribution without test hooks.
15
+ return new TypeError(message);
16
+ }
17
+ function makeApplicationError() {
18
+ const sdkFrame = makeSdkError().stack
19
+ ?.split("\n")
20
+ .find((line) => line.includes("sentry.test"));
21
+ if (!sdkFrame)
22
+ throw new Error("Expected a real SDK test frame");
23
+ const error = new Error("application secret");
24
+ error.stack = [
25
+ "Error: application secret",
26
+ ` at application (${appFrameUrl}:10:20)`,
27
+ sdkFrame,
28
+ ].join("\n");
29
+ return error;
30
+ }
31
+ function makeTraversalStackError() {
32
+ const sdkFrame = makeSdkError().stack
33
+ ?.split("\n")
34
+ .find((line) => line.includes("sentry.test"));
35
+ if (!sdkFrame)
36
+ throw new Error("Expected a real SDK test frame");
37
+ const forgedFrame = sdkFrame.replace(/([\\/])src\1common\1sentry\.test\.ts/, "$1src$1..$1private$1customer-secret.ts");
38
+ if (forgedFrame === sdkFrame)
39
+ throw new Error("Expected to forge the SDK frame path");
40
+ const error = new Error("application secret");
41
+ error.stack = ["Error: application secret", forgedFrame].join("\n");
42
+ return error;
43
+ }
44
+ function eventFromFetch(fetchMock) {
45
+ const request = fetchMock.mock.calls[0]?.[1];
46
+ if (typeof request?.body !== "string")
47
+ throw new Error("Expected a string Sentry envelope");
48
+ return JSON.parse(request.body.split("\n")[2]);
49
+ }
50
+ function emitUncaughtExceptionMonitor(error) {
51
+ const processWithStringEvents = process;
52
+ return processWithStringEvents.emit("uncaughtExceptionMonitor", error, "uncaughtException");
53
+ }
54
+ describe("SDK Sentry boundary", () => {
55
+ let originalMonitorListeners;
56
+ beforeEach(() => {
57
+ vi.resetModules();
58
+ mockSettings.tracking = true;
59
+ mockSettings.sentryDsn = "https://public-key@sentry.example/123";
60
+ mockSettings.env = "prod";
61
+ originalMonitorListeners = process.listeners("uncaughtExceptionMonitor");
62
+ });
63
+ afterEach(() => {
64
+ vi.unstubAllGlobals();
65
+ for (const listener of process.listeners("uncaughtExceptionMonitor")) {
66
+ if (!originalMonitorListeners.includes(listener)) {
67
+ process.removeListener("uncaughtExceptionMonitor", listener);
68
+ }
69
+ }
70
+ vi.restoreAllMocks();
71
+ });
72
+ it("does not replace console.error or report caught errors", async () => {
73
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
74
+ const hostConsoleError = vi.fn();
75
+ vi.stubGlobal("fetch", fetchMock);
76
+ vi.spyOn(console, "error").mockImplementation(hostConsoleError);
77
+ const { initSentry } = await import("./sentry.js");
78
+ initSentry();
79
+ const installedConsoleError = console.error;
80
+ console.error(makeSdkError());
81
+ expect(console.error).toBe(installedConsoleError);
82
+ expect(hostConsoleError).toHaveBeenCalledOnce();
83
+ expect(fetchMock).not.toHaveBeenCalled();
84
+ });
85
+ it("composes with host handlers and reports one sanitized SDK-owned event", async () => {
86
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
87
+ const hostMonitor = vi.fn();
88
+ const hostRejection = vi.fn();
89
+ vi.stubGlobal("fetch", fetchMock);
90
+ process.on("uncaughtExceptionMonitor", hostMonitor);
91
+ process.on("unhandledRejection", hostRejection);
92
+ const rejectionListeners = process.listeners("unhandledRejection");
93
+ try {
94
+ const { flushSentry, initSentry } = await import("./sentry.js");
95
+ initSentry();
96
+ const error = makeSdkError();
97
+ emitUncaughtExceptionMonitor(error);
98
+ emitUncaughtExceptionMonitor(error);
99
+ await flushSentry();
100
+ expect(hostMonitor).toHaveBeenCalledTimes(2);
101
+ expect(process.listeners("unhandledRejection")).toEqual(rejectionListeners);
102
+ expect(fetchMock).toHaveBeenCalledOnce();
103
+ const event = eventFromFetch(fetchMock);
104
+ expect(event.exception.values[0]).toMatchObject({
105
+ type: "TypeError",
106
+ value: "Unhandled SDK exception",
107
+ });
108
+ expect(event.tags).toEqual({
109
+ "blaxel.version": "9.9.9",
110
+ "blaxel.commit": "abcdef0",
111
+ "blaxel.error_source": "unhandled-sdk-exception",
112
+ });
113
+ expect(event.tags).not.toHaveProperty("blaxel.workspace");
114
+ const frames = event.exception.values[0].stacktrace.frames;
115
+ expect(frames.length).toBeGreaterThan(0);
116
+ for (const frame of frames) {
117
+ expect(frame.filename).toBe("@blaxel/core/src/common/sentry.test.ts");
118
+ }
119
+ const serialized = JSON.stringify(event);
120
+ expect(serialized).not.toContain("secret response body");
121
+ expect(serialized).not.toContain("customer-123");
122
+ expect(serialized).not.toContain("private-workspace");
123
+ expect(serialized).not.toContain("/Users/customer");
124
+ }
125
+ finally {
126
+ process.removeListener("uncaughtExceptionMonitor", hostMonitor);
127
+ process.removeListener("unhandledRejection", hostRejection);
128
+ }
129
+ });
130
+ it("does not attribute an application-owned exception with a later SDK frame", async () => {
131
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
132
+ vi.stubGlobal("fetch", fetchMock);
133
+ const { initSentry } = await import("./sentry.js");
134
+ initSentry();
135
+ emitUncaughtExceptionMonitor(makeApplicationError());
136
+ expect(fetchMock).not.toHaveBeenCalled();
137
+ });
138
+ it("rejects a forged owned path containing parent traversal", async () => {
139
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
140
+ vi.stubGlobal("fetch", fetchMock);
141
+ const { initSentry } = await import("./sentry.js");
142
+ initSentry();
143
+ emitUncaughtExceptionMonitor(makeTraversalStackError());
144
+ expect(fetchMock).not.toHaveBeenCalled();
145
+ });
146
+ it("contains delivery setup failures without creating an unhandled rejection", async () => {
147
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
148
+ vi.stubGlobal("fetch", fetchMock);
149
+ vi.stubGlobal("AbortController", class FailingAbortController {
150
+ constructor() {
151
+ throw new Error("host AbortController failure");
152
+ }
153
+ });
154
+ const { flushSentry, initSentry } = await import("./sentry.js");
155
+ initSentry();
156
+ emitUncaughtExceptionMonitor(makeSdkError());
157
+ await flushSentry();
158
+ expect(fetchMock).not.toHaveBeenCalled();
159
+ });
160
+ it("composes with browser handlers and ignores primitive rejections", async () => {
161
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
162
+ const listeners = new Map();
163
+ const addEventListener = vi.fn((type, listener) => {
164
+ listeners.set(type, listener);
165
+ });
166
+ vi.stubGlobal("fetch", fetchMock);
167
+ vi.stubGlobal("process", undefined);
168
+ vi.stubGlobal("addEventListener", addEventListener);
169
+ const { flushSentry, initSentry } = await import("./sentry.js");
170
+ initSentry();
171
+ expect(addEventListener).toHaveBeenCalledWith("error", expect.any(Function));
172
+ expect(addEventListener).toHaveBeenCalledWith("unhandledrejection", expect.any(Function));
173
+ listeners.get("unhandledrejection")?.({ reason: "raw rejection secret" });
174
+ listeners.get("error")?.({ error: makeSdkError() });
175
+ await flushSentry();
176
+ expect(fetchMock).toHaveBeenCalledOnce();
177
+ expect(JSON.stringify(eventFromFetch(fetchMock))).not.toContain("raw rejection secret");
178
+ });
179
+ it("does not initialize when tracking is disabled", async () => {
180
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true });
181
+ vi.stubGlobal("fetch", fetchMock);
182
+ mockSettings.tracking = false;
183
+ const { initSentry, isSentryInitialized } = await import("./sentry.js");
184
+ initSentry();
185
+ emitUncaughtExceptionMonitor(makeSdkError());
186
+ expect(isSentryInitialized()).toBe(false);
187
+ expect(fetchMock).not.toHaveBeenCalled();
188
+ });
189
+ });
@@ -24,8 +24,8 @@ function missingCredentialsMessage() {
24
24
  return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
25
25
  }
26
26
  // Build info - these placeholders are replaced at build time by build:replace-imports
27
- const BUILD_VERSION = "0.3.7-preview.229";
28
- const BUILD_COMMIT = "23d40862a7ff5f350d98700c4c2e47adb91eddc5";
27
+ const BUILD_VERSION = "0.3.7-preview.230";
28
+ const BUILD_COMMIT = "110dd5983dfa658153d44e36dc9b02ffe6a90d77";
29
29
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
30
30
  const BLAXEL_API_VERSION = "2026-04-28";
31
31
  // Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blaxel/core",
3
- "version": "0.3.7-preview.229",
3
+ "version": "0.3.7-preview.230",
4
4
  "description": "Blaxel Core SDK for TypeScript",
5
5
  "license": "MIT",
6
6
  "author": "Blaxel, INC (https://blaxel.ai)",