@blaxel/core 0.2.55 → 0.2.56-dev.23

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.
Files changed (33) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/client/sdk.gen.js +2 -36
  3. package/dist/cjs/common/sentry.js +227 -131
  4. package/dist/cjs/common/settings.js +3 -3
  5. package/dist/cjs/common/version.js +6 -0
  6. package/dist/cjs/types/client/sdk.gen.d.ts +1 -11
  7. package/dist/cjs/types/client/types.gen.d.ts +0 -48
  8. package/dist/cjs/types/common/sentry.d.ts +1 -3
  9. package/dist/cjs/types/common/version.d.ts +2 -0
  10. package/dist/cjs/types/sandbox/client/sdk.gen.d.ts +3 -3
  11. package/dist/cjs-browser/.tsbuildinfo +1 -1
  12. package/dist/cjs-browser/client/sdk.gen.js +2 -36
  13. package/dist/cjs-browser/common/sentry-browser.js +27 -0
  14. package/dist/cjs-browser/common/sentry.js +306 -11
  15. package/dist/cjs-browser/common/settings.js +3 -3
  16. package/dist/cjs-browser/common/version.js +6 -0
  17. package/dist/cjs-browser/types/client/sdk.gen.d.ts +1 -11
  18. package/dist/cjs-browser/types/client/types.gen.d.ts +0 -48
  19. package/dist/cjs-browser/types/common/sentry.d.ts +1 -3
  20. package/dist/cjs-browser/types/common/version.d.ts +2 -0
  21. package/dist/cjs-browser/types/sandbox/client/sdk.gen.d.ts +3 -3
  22. package/dist/esm/.tsbuildinfo +1 -1
  23. package/dist/esm/client/sdk.gen.js +0 -32
  24. package/dist/esm/common/sentry.js +227 -98
  25. package/dist/esm/common/settings.js +3 -3
  26. package/dist/esm/common/version.js +3 -0
  27. package/dist/esm-browser/.tsbuildinfo +1 -1
  28. package/dist/esm-browser/client/sdk.gen.js +0 -32
  29. package/dist/esm-browser/common/sentry-browser.js +22 -0
  30. package/dist/esm-browser/common/sentry.js +306 -11
  31. package/dist/esm-browser/common/settings.js +3 -3
  32. package/dist/esm-browser/common/version.js +3 -0
  33. package/package.json +2 -4
@@ -1206,38 +1206,6 @@ export const deleteSandboxPreviewToken = (options) => {
1206
1206
  ...options
1207
1207
  });
1208
1208
  };
1209
- /**
1210
- * Start Sandbox
1211
- * Starts a Sandbox by name.
1212
- */
1213
- export const startSandbox = (options) => {
1214
- return (options.client ?? _heyApiClient).put({
1215
- security: [
1216
- {
1217
- scheme: 'bearer',
1218
- type: 'http'
1219
- }
1220
- ],
1221
- url: '/sandboxes/{sandboxName}/start',
1222
- ...options
1223
- });
1224
- };
1225
- /**
1226
- * Stop Sandbox
1227
- * Stops a Sandbox by name.
1228
- */
1229
- export const stopSandbox = (options) => {
1230
- return (options.client ?? _heyApiClient).put({
1231
- security: [
1232
- {
1233
- scheme: 'bearer',
1234
- type: 'http'
1235
- }
1236
- ],
1237
- url: '/sandboxes/{sandboxName}/stop',
1238
- ...options
1239
- });
1240
- };
1241
1209
  /**
1242
1210
  * Get workspace service accounts
1243
1211
  * Returns a list of all service accounts in the workspace.
@@ -1,10 +1,10 @@
1
1
  import { settings } from "./settings.js";
2
- import * as Sentry from "@sentry/node";
3
- import { makeNodeTransport } from "@sentry/node";
4
- // Isolated Sentry client for SDK-only error tracking (doesn't interfere with user's Sentry)
5
- let sentryClient = null;
2
+ // Lightweight Sentry client using fetch - only captures SDK errors
3
+ let sentryInitialized = false;
6
4
  const capturedExceptions = new Set();
7
5
  let handlersRegistered = false;
6
+ // Parsed DSN components
7
+ let sentryConfig = null;
8
8
  // SDK path patterns to identify errors originating from our SDK
9
9
  const SDK_PATTERNS = [
10
10
  "@blaxel/",
@@ -22,9 +22,146 @@ function isFromSDK(error) {
22
22
  return SDK_PATTERNS.some((pattern) => stack.includes(pattern));
23
23
  }
24
24
  /**
25
- * Initialize an isolated Sentry client for SDK error tracking.
26
- * This creates a separate Sentry instance that won't interfere with any
27
- * Sentry configuration the user might have in their application.
25
+ * Parse a Sentry DSN into its components.
26
+ * DSN format: https://{public_key}@{host}/{project_id}
27
+ */
28
+ function parseDsn(dsn) {
29
+ try {
30
+ const url = new URL(dsn);
31
+ const publicKey = url.username;
32
+ const host = url.host;
33
+ const projectId = url.pathname.slice(1); // Remove leading slash
34
+ if (!publicKey || !host || !projectId) {
35
+ return null;
36
+ }
37
+ return { publicKey, host, projectId };
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Generate a UUID v4
45
+ */
46
+ function generateEventId() {
47
+ return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, (c) => {
48
+ const r = (Math.random() * 16) | 0;
49
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
50
+ return v.toString(16);
51
+ });
52
+ }
53
+ /**
54
+ * Convert an Error to a Sentry event payload.
55
+ */
56
+ function errorToSentryEvent(error) {
57
+ const frames = parseStackTrace(error.stack || "");
58
+ return {
59
+ event_id: generateEventId(),
60
+ timestamp: Date.now() / 1000,
61
+ platform: "javascript",
62
+ level: "error",
63
+ environment: settings.env,
64
+ release: `sdk-typescript@${settings.version}`,
65
+ tags: {
66
+ "blaxel.workspace": settings.workspace,
67
+ "blaxel.version": settings.version,
68
+ "blaxel.commit": settings.commit,
69
+ },
70
+ exception: {
71
+ values: [
72
+ {
73
+ type: error.name,
74
+ value: error.message,
75
+ stacktrace: {
76
+ frames,
77
+ },
78
+ },
79
+ ],
80
+ },
81
+ };
82
+ }
83
+ /**
84
+ * Parse a stack trace string into Sentry-compatible frames.
85
+ */
86
+ function parseStackTrace(stack) {
87
+ const lines = stack.split("\n").slice(1); // Skip first line (error message)
88
+ const frames = [];
89
+ for (const line of lines) {
90
+ // Match patterns like "at functionName (filename:line:col)" or "at filename:line:col"
91
+ const match = line.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?/);
92
+ if (match) {
93
+ frames.unshift({
94
+ function: match[1] || "<anonymous>",
95
+ filename: match[2],
96
+ lineno: parseInt(match[3], 10),
97
+ colno: parseInt(match[4], 10),
98
+ });
99
+ }
100
+ }
101
+ return frames;
102
+ }
103
+ /**
104
+ * Send an event to Sentry using fetch.
105
+ */
106
+ async function sendToSentry(event) {
107
+ if (!sentryConfig)
108
+ return;
109
+ const { publicKey, host, projectId } = sentryConfig;
110
+ const envelopeUrl = `https://${host}/api/${projectId}/envelope/`;
111
+ // Create envelope header
112
+ const envelopeHeader = JSON.stringify({
113
+ event_id: event.event_id,
114
+ sent_at: new Date().toISOString(),
115
+ dsn: `https://${publicKey}@${host}/${projectId}`,
116
+ });
117
+ // Create item header
118
+ const itemHeader = JSON.stringify({
119
+ type: "event",
120
+ content_type: "application/json",
121
+ });
122
+ // Create envelope body
123
+ const envelope = `${envelopeHeader}\n${itemHeader}\n${JSON.stringify(event)}`;
124
+ try {
125
+ await fetch(envelopeUrl, {
126
+ method: "POST",
127
+ headers: {
128
+ "Content-Type": "application/x-sentry-envelope",
129
+ "X-Sentry-Auth": `Sentry sentry_version=7, sentry_client=blaxel-sdk/${settings.version}, sentry_key=${publicKey}`,
130
+ },
131
+ body: envelope,
132
+ });
133
+ }
134
+ catch {
135
+ // Silently fail - error reporting should never break the SDK
136
+ }
137
+ }
138
+ // Queue for pending events
139
+ const pendingEvents = [];
140
+ let flushPromise = null;
141
+ /**
142
+ * Register browser/edge environment error handlers.
143
+ * Separated to isolate dynamic globalThis access.
144
+ */
145
+ function registerBrowserHandlers() {
146
+ const g = globalThis;
147
+ if (g && typeof g.addEventListener === "function") {
148
+ g.addEventListener("error", (event) => {
149
+ const e = event;
150
+ if (e.error instanceof Error && isFromSDK(e.error)) {
151
+ captureException(e.error);
152
+ }
153
+ });
154
+ g.addEventListener("unhandledrejection", (event) => {
155
+ const e = event;
156
+ const error = e.reason instanceof Error ? e.reason : new Error(String(e.reason));
157
+ if (isFromSDK(error)) {
158
+ captureException(error);
159
+ }
160
+ });
161
+ }
162
+ }
163
+ /**
164
+ * Initialize the lightweight Sentry client for SDK error tracking.
28
165
  */
29
166
  export function initSentry() {
30
167
  try {
@@ -36,87 +173,64 @@ export function initSentry() {
36
173
  if (!dsn) {
37
174
  return;
38
175
  }
39
- // Create an isolated Sentry client that doesn't touch the global scope
40
- // This allows users to have their own Sentry.init() without conflicts
41
- sentryClient = new Sentry.NodeClient({
42
- dsn,
43
- environment: settings.env,
44
- release: `sdk-typescript@${settings.version}`,
45
- transport: makeNodeTransport,
46
- stackParser: Sentry.defaultStackParser,
47
- // No integrations - we handle error capturing manually
48
- integrations: [],
49
- // Disable traces for the SDK client
50
- tracesSampleRate: 0,
51
- // Filter errors before sending - only send SDK errors
52
- beforeSend(event, hint) {
53
- if (event.environment !== 'dev' && event.environment !== 'prod') {
54
- return null;
55
- }
56
- const error = hint.originalException;
57
- if (error instanceof Error) {
58
- if (!isFromSDK(error)) {
59
- // Drop errors that don't originate from SDK
60
- return null;
61
- }
62
- }
63
- return event;
64
- },
65
- });
66
- sentryClient.init();
67
- // Set SDK-specific tags
68
- const scope = new Sentry.Scope();
69
- scope.setTag("blaxel.workspace", settings.workspace);
70
- scope.setTag("blaxel.version", settings.version);
71
- scope.setTag("blaxel.commit", settings.commit);
72
- scope.setClient(sentryClient);
73
- // Register process handlers for uncaught errors (Node.js only)
74
- // Only register once to prevent memory leaks
75
- if (typeof process !== "undefined" &&
76
- typeof process.on === "function" &&
77
- !handlersRegistered) {
176
+ // Parse DSN
177
+ sentryConfig = parseDsn(dsn);
178
+ if (!sentryConfig) {
179
+ return;
180
+ }
181
+ // Only allow dev/prod environments
182
+ if (settings.env !== "dev" && settings.env !== "prod") {
183
+ return;
184
+ }
185
+ sentryInitialized = true;
186
+ // Register error handlers only once
187
+ if (!handlersRegistered) {
78
188
  handlersRegistered = true;
79
- // For SIGTERM/SIGINT, flush before exit
80
- const signalHandler = (signal) => {
81
- flushSentry(500)
82
- .catch(() => {
83
- // Silently fail
84
- })
85
- .finally(() => {
86
- process.exit(signal === "SIGTERM" ? 143 : 130);
87
- });
88
- };
89
- // Uncaught exception handler - only capture SDK errors
90
- const uncaughtExceptionHandler = (error) => {
91
- if (isFromSDK(error)) {
92
- captureException(error);
93
- }
94
- // Let the default Node.js behavior handle the process exit
95
- };
96
- // Unhandled rejection handler - only capture SDK errors
97
- const unhandledRejectionHandler = (reason) => {
98
- const error = reason instanceof Error ? reason : new Error(String(reason));
99
- if (isFromSDK(error)) {
100
- captureException(error);
101
- }
102
- };
103
- process.on("SIGTERM", () => signalHandler("SIGTERM"));
104
- process.on("SIGINT", () => signalHandler("SIGINT"));
105
- process.on("uncaughtException", uncaughtExceptionHandler);
106
- process.on("unhandledRejection", unhandledRejectionHandler);
107
- // Intercept console.error to capture SDK errors that are caught and logged
108
- const originalConsoleError = console.error;
109
- console.error = function (...args) {
110
- // Call the original console.error first
111
- originalConsoleError.apply(console, args);
112
- // Check if any argument is an Error from SDK and capture it
113
- for (const arg of args) {
114
- if (arg instanceof Error && isFromSDK(arg)) {
115
- captureException(arg);
116
- break; // Only capture the first SDK error to avoid duplicates
189
+ // Node.js specific handlers
190
+ if (typeof process !== "undefined" && typeof process.on === "function") {
191
+ // For SIGTERM/SIGINT, flush before exit
192
+ const signalHandler = (signal) => {
193
+ flushSentry(500)
194
+ .catch(() => {
195
+ // Silently fail
196
+ })
197
+ .finally(() => {
198
+ process.exit(signal === "SIGTERM" ? 143 : 130);
199
+ });
200
+ };
201
+ // Uncaught exception handler - only capture SDK errors
202
+ const uncaughtExceptionHandler = (error) => {
203
+ if (isFromSDK(error)) {
204
+ captureException(error);
117
205
  }
118
- }
119
- };
206
+ };
207
+ // Unhandled rejection handler - only capture SDK errors
208
+ const unhandledRejectionHandler = (reason) => {
209
+ const error = reason instanceof Error ? reason : new Error(String(reason));
210
+ if (isFromSDK(error)) {
211
+ captureException(error);
212
+ }
213
+ };
214
+ process.on("SIGTERM", () => signalHandler("SIGTERM"));
215
+ process.on("SIGINT", () => signalHandler("SIGINT"));
216
+ process.on("uncaughtException", uncaughtExceptionHandler);
217
+ process.on("unhandledRejection", unhandledRejectionHandler);
218
+ // Intercept console.error to capture SDK errors that are caught and logged
219
+ const originalConsoleError = console.error;
220
+ console.error = function (...args) {
221
+ originalConsoleError.apply(console, args);
222
+ for (const arg of args) {
223
+ if (arg instanceof Error && isFromSDK(arg)) {
224
+ captureException(arg);
225
+ break;
226
+ }
227
+ }
228
+ };
229
+ }
230
+ else {
231
+ // Browser/Edge environment handlers
232
+ registerBrowserHandlers();
233
+ }
120
234
  }
121
235
  }
122
236
  catch (error) {
@@ -127,13 +241,13 @@ export function initSentry() {
127
241
  }
128
242
  }
129
243
  /**
130
- * Capture an exception to the SDK's isolated Sentry client.
244
+ * Capture an exception to Sentry.
131
245
  * Only errors originating from SDK code will be captured.
132
246
  *
133
247
  * @param error - The error to capture
134
248
  */
135
249
  function captureException(error) {
136
- if (sentryClient === null) {
250
+ if (!sentryInitialized || !sentryConfig) {
137
251
  return;
138
252
  }
139
253
  // Double-check that error is from SDK (defense in depth)
@@ -151,13 +265,13 @@ function captureException(error) {
151
265
  if (capturedExceptions.size > 1000) {
152
266
  capturedExceptions.clear();
153
267
  }
154
- // Create a scope with SDK tags and capture the exception
155
- const scope = new Sentry.Scope();
156
- scope.setTag("blaxel.workspace", settings.workspace);
157
- scope.setTag("blaxel.version", settings.version);
158
- scope.setTag("blaxel.commit", settings.commit);
159
- scope.setClient(sentryClient);
160
- scope.captureException(error);
268
+ // Convert error to Sentry event and queue it
269
+ const event = errorToSentryEvent(error);
270
+ pendingEvents.push(event);
271
+ // Send immediately (fire and forget)
272
+ sendToSentry(event).catch(() => {
273
+ // Silently fail
274
+ });
161
275
  }
162
276
  catch {
163
277
  // Silently fail - error capturing should never break the SDK
@@ -170,19 +284,34 @@ function captureException(error) {
170
284
  * @param timeout - Maximum time in milliseconds to wait for flush (default: 2000)
171
285
  */
172
286
  export async function flushSentry(timeout = 2000) {
173
- if (sentryClient === null) {
287
+ if (!sentryInitialized || pendingEvents.length === 0) {
288
+ return;
289
+ }
290
+ // If already flushing, wait for it
291
+ if (flushPromise) {
292
+ await flushPromise;
174
293
  return;
175
294
  }
176
295
  try {
177
- await sentryClient.flush(timeout);
296
+ // Send all pending events
297
+ const eventsToSend = [...pendingEvents];
298
+ pendingEvents.length = 0;
299
+ flushPromise = Promise.race([
300
+ Promise.all(eventsToSend.map((event) => sendToSentry(event))).then(() => { }),
301
+ new Promise((resolve) => setTimeout(resolve, timeout)),
302
+ ]);
303
+ await flushPromise;
178
304
  }
179
305
  catch {
180
306
  // Silently fail
181
307
  }
308
+ finally {
309
+ flushPromise = null;
310
+ }
182
311
  }
183
312
  /**
184
313
  * Check if Sentry is initialized and available.
185
314
  */
186
315
  export function isSentryInitialized() {
187
- return sentryClient !== null;
316
+ return sentryInitialized;
188
317
  }
@@ -3,9 +3,9 @@ import { authentication } from "../authentication/index.js";
3
3
  import { env } from "../common/env.js";
4
4
  import { fs, os, path } from "../common/node.js";
5
5
  // Build info - these placeholders are replaced at build time by build:replace-imports
6
- const BUILD_VERSION = "0.2.55";
7
- const BUILD_COMMIT = "1efcf3565af2e54805e7526888bebaf2347c5886";
8
- const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
6
+ const BUILD_VERSION = "0.2.56-dev.23";
7
+ const BUILD_COMMIT = "d54548dc9e764e57728f9eda7721d5e50f11573a";
8
+ const BUILD_SENTRY_DSN = "";
9
9
  // Cache for config.yaml tracking value
10
10
  let configTrackingValue = null;
11
11
  let configTrackingLoaded = false;
@@ -0,0 +1,3 @@
1
+ // This file is auto-generated during build. Do not edit manually.
2
+ export const PACKAGE_VERSION = "0.2.50";
3
+ export const PACKAGE_COMMIT = "4a74989dcb65a6ff8d826aed7976436c64554f6a";