@cloudflare/sandbox 0.9.1 → 0.9.2

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.
@@ -1,5 +1,5 @@
1
1
  import { _ as GitLogger, b as getEnvString, c as parseSSEFrames, d as createNoOpLogger, f as TraceContext, g as DEFAULT_GIT_CLONE_TIMEOUT_MS, h as ResultImpl, i as isWSStreamChunk, l as shellEscape, m as Execution, n as isWSError, p as logCanonicalEvent, r as isWSResponse, t as generateRequestId, u as createLogger, v as extractRepoName, x as partitionEnvVars, y as filterEnvVars } from "./dist-B_eXrP83.js";
2
- import { n as ErrorCode, t as getHttpStatus } from "./errors-LE3HHcRb.js";
2
+ import { n as getHttpStatus, r as ErrorCode, t as getSuggestion } from "./errors-CBi-O-pF.js";
3
3
  import { Container, getContainer, switchPort } from "@cloudflare/containers";
4
4
  import { AwsClient } from "aws4fetch";
5
5
  import { RpcSession } from "capnweb";
@@ -11,8 +11,8 @@ import path from "node:path/posix";
11
11
  * Preserves all error information from container
12
12
  */
13
13
  var SandboxError = class extends Error {
14
- constructor(errorResponse) {
15
- super(errorResponse.message);
14
+ constructor(errorResponse, options) {
15
+ super(errorResponse.message, options);
16
16
  this.errorResponse = errorResponse;
17
17
  this.name = "SandboxError";
18
18
  }
@@ -664,6 +664,30 @@ var DesktopInvalidCoordinatesError = class extends SandboxError {
664
664
  this.name = "DesktopInvalidCoordinatesError";
665
665
  }
666
666
  };
667
+ /**
668
+ * Raised when the capnweb WebSocket session itself fails on the SDK side.
669
+ * Unlike the rest of the SandboxError tree, the container never produces
670
+ * this error — it is synthesised by `translateRPCError` from the plain
671
+ * Errors capnweb / DeferredTransport raise when the connection dies.
672
+ *
673
+ * `kind` distinguishes the failure mode (peer close, upgrade failed, etc.)
674
+ * so callers can branch on a structured code instead of substring-matching
675
+ * on the message.
676
+ *
677
+ * Always retryable: the SDK opens a fresh connection on the next call.
678
+ */
679
+ var RPCTransportError = class extends SandboxError {
680
+ constructor(errorResponse, options) {
681
+ super(errorResponse, options);
682
+ this.name = "RPCTransportError";
683
+ }
684
+ get kind() {
685
+ return this.errorResponse.context.kind;
686
+ }
687
+ get originalMessage() {
688
+ return this.errorResponse.context.originalMessage;
689
+ }
690
+ };
667
691
 
668
692
  //#endregion
669
693
  //#region src/errors/adapter.ts
@@ -671,7 +695,7 @@ var DesktopInvalidCoordinatesError = class extends SandboxError {
671
695
  * Convert ErrorResponse to appropriate Error class
672
696
  * Simple switch statement - we trust the container sends correct context
673
697
  */
674
- function createErrorFromResponse(errorResponse) {
698
+ function createErrorFromResponse(errorResponse, options) {
675
699
  switch (errorResponse.code) {
676
700
  case ErrorCode.FILE_NOT_FOUND: return new FileNotFoundError(errorResponse);
677
701
  case ErrorCode.FILE_EXISTS: return new FileExistsError(errorResponse);
@@ -727,6 +751,7 @@ function createErrorFromResponse(errorResponse) {
727
751
  case ErrorCode.DESKTOP_PROCESS_CRASHED: return new DesktopProcessCrashedError(errorResponse);
728
752
  case ErrorCode.DESKTOP_INVALID_OPTIONS: return new DesktopInvalidOptionsError(errorResponse);
729
753
  case ErrorCode.DESKTOP_INVALID_COORDINATES: return new DesktopInvalidCoordinatesError(errorResponse);
754
+ case ErrorCode.RPC_TRANSPORT_ERROR: return new RPCTransportError(errorResponse, options);
730
755
  case ErrorCode.VALIDATION_FAILED: return new ValidationFailedError(errorResponse);
731
756
  case ErrorCode.INVALID_JSON_RESPONSE:
732
757
  case ErrorCode.UNKNOWN_ERROR:
@@ -2712,6 +2737,14 @@ const BACKUP_ALLOWED_PREFIXES = [
2712
2737
  "/var/tmp",
2713
2738
  "/app"
2714
2739
  ];
2740
+ function normalizeBackupExcludePattern(pattern) {
2741
+ let normalized = pattern;
2742
+ while (normalized.startsWith("**/")) normalized = normalized.slice(3);
2743
+ while (normalized.includes("/**/")) normalized = normalized.replace(/\/\*\*\//g, "/");
2744
+ if (normalized.endsWith("/**")) normalized = normalized.slice(0, -3);
2745
+ if (!normalized || normalized === "**") return null;
2746
+ return normalized;
2747
+ }
2715
2748
 
2716
2749
  //#endregion
2717
2750
  //#region src/container-connection.ts
@@ -2847,12 +2880,13 @@ var DeferredTransport = class {
2847
2880
  this.#receiveResolver = void 0;
2848
2881
  this.#receiveRejecter = void 0;
2849
2882
  } else this.#receiveQueue.push(event.data);
2883
+ else this.#fail(/* @__PURE__ */ new TypeError("Received non-string message from WebSocket."));
2850
2884
  });
2851
2885
  ws.addEventListener("close", (event) => {
2852
2886
  this.#fail(/* @__PURE__ */ new Error(`Peer closed WebSocket: ${event.code} ${event.reason}`));
2853
2887
  });
2854
2888
  ws.addEventListener("error", () => {
2855
- this.#fail(/* @__PURE__ */ new Error("WebSocket connection failed"));
2889
+ this.#fail(/* @__PURE__ */ new Error("WebSocket connection failed."));
2856
2890
  });
2857
2891
  for (const msg of this.#sendQueue) ws.send(msg);
2858
2892
  this.#sendQueue = [];
@@ -2924,19 +2958,65 @@ const IDLE_EXPORT_THRESHOLD = 1;
2924
2958
  * string: `{"code":"...","message":"...","context":{...}}`.
2925
2959
  */
2926
2960
  function translateRPCError(error) {
2927
- if (error instanceof Error) try {
2928
- const payload = JSON.parse(error.message);
2929
- if (typeof payload.code === "string" && typeof payload.message === "string") throw createErrorFromResponse({
2961
+ if (error instanceof Error) {
2962
+ let payload;
2963
+ try {
2964
+ payload = JSON.parse(error.message);
2965
+ } catch {}
2966
+ if (payload && typeof payload.code === "string" && typeof payload.message === "string") throw createErrorFromResponse({
2930
2967
  code: payload.code,
2931
2968
  message: payload.message,
2932
2969
  context: payload.context ?? {},
2933
2970
  httpStatus: getHttpStatus(payload.code),
2934
2971
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2935
2972
  });
2936
- } catch (e) {
2937
- if (e instanceof Error && e !== error) throw e;
2973
+ throw createErrorFromResponse(buildTransportErrorResponse(error), { cause: error });
2938
2974
  }
2939
- throw error;
2975
+ throw createErrorFromResponse(buildTransportErrorResponse(new Error(String(error))), { cause: error });
2976
+ }
2977
+ /**
2978
+ * Inspect a transport-level Error's message and produce the ErrorResponse
2979
+ * that becomes an RPCTransportError. Pattern strings are pinned to the exact
2980
+ * messages emitted by capnweb's WebSocketTransport (see capnweb's
2981
+ * src/websocket.ts) and our DeferredTransport in container-connection.ts —
2982
+ * notably the trailing period in `WebSocket connection failed.` matches
2983
+ * capnweb verbatim. The DeferredTransport tests in
2984
+ * tests/container-connection.test.ts pin the literal strings.
2985
+ */
2986
+ function buildTransportErrorResponse(error) {
2987
+ const message = error.message;
2988
+ const errorName = error.name;
2989
+ let kind = "unknown";
2990
+ let closeCode;
2991
+ let closeReason;
2992
+ if (errorName === "TypeError") kind = "invalid_frame";
2993
+ else if (errorName === "SyntaxError") kind = "protocol_error";
2994
+ else {
2995
+ const peerCloseMatch = message.match(/^Peer closed WebSocket: (\d+) ?(.*)$/);
2996
+ if (peerCloseMatch) {
2997
+ kind = "peer_closed";
2998
+ closeCode = Number(peerCloseMatch[1]);
2999
+ closeReason = peerCloseMatch[2] || void 0;
3000
+ } else if (message === "WebSocket connection failed.") kind = "connection_failed";
3001
+ else if (message.startsWith("WebSocket upgrade failed")) kind = "upgrade_failed";
3002
+ else if (message === "No WebSocket in upgrade response") kind = "upgrade_failed";
3003
+ else if (message === "RPC session was shut down by disposing the main stub" || message === "RPC was canceled because the RpcPromise was disposed.") kind = "session_disposed";
3004
+ }
3005
+ const context = {
3006
+ kind,
3007
+ originalMessage: message,
3008
+ errorName,
3009
+ ...closeCode !== void 0 ? { closeCode } : {},
3010
+ ...closeReason !== void 0 ? { closeReason } : {}
3011
+ };
3012
+ return {
3013
+ code: ErrorCode.RPC_TRANSPORT_ERROR,
3014
+ message,
3015
+ context,
3016
+ httpStatus: getHttpStatus(ErrorCode.RPC_TRANSPORT_ERROR),
3017
+ suggestion: getSuggestion(ErrorCode.RPC_TRANSPORT_ERROR, context),
3018
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3019
+ };
2940
3020
  }
2941
3021
  /**
2942
3022
  * Wrap a capnweb RPC stub so that every method call translates errors
@@ -4111,7 +4191,7 @@ function isLocalhostPattern(hostname) {
4111
4191
  * This file is auto-updated by .github/changeset-version.ts during releases
4112
4192
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
4113
4193
  */
4114
- const SDK_VERSION = "0.9.1";
4194
+ const SDK_VERSION = "0.9.2";
4115
4195
 
4116
4196
  //#endregion
4117
4197
  //#region src/sandbox.ts
@@ -6362,6 +6442,22 @@ var Sandbox = class Sandbox extends Container {
6362
6442
  });
6363
6443
  return this.backupBucket;
6364
6444
  }
6445
+ normalizeBackupExcludes(excludes) {
6446
+ const normalizedExcludes = [];
6447
+ for (const pattern of excludes) {
6448
+ const normalized = normalizeBackupExcludePattern(pattern);
6449
+ if (normalized === null) {
6450
+ this.logger.warn("Exclude pattern reduced to empty after globstar normalization; skipping", { original: pattern });
6451
+ continue;
6452
+ }
6453
+ if (normalized !== pattern) this.logger.warn("Exclude pattern contained ** (globstar) which mksquashfs does not support; normalized automatically", {
6454
+ original: pattern,
6455
+ normalized
6456
+ });
6457
+ normalizedExcludes.push(normalized);
6458
+ }
6459
+ return normalizedExcludes;
6460
+ }
6365
6461
  static PRESIGNED_URL_EXPIRY_SECONDS = 3600;
6366
6462
  /**
6367
6463
  * Create a unique, dedicated session for a single backup operation.
@@ -6592,12 +6688,13 @@ var Sandbox = class Sandbox extends Container {
6592
6688
  context: { reason: "excludes must be an array of strings" },
6593
6689
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6594
6690
  });
6691
+ const normalizedExcludes = this.normalizeBackupExcludes(excludes);
6595
6692
  backupSession = await this.ensureBackupSession();
6596
6693
  backupId = crypto.randomUUID();
6597
6694
  const archivePath = `${BACKUP_CONTAINER_DIR}/${backupId}.sqsh`;
6598
6695
  const createResult = await this.client.backup.createArchive(dir, archivePath, backupSession, {
6599
6696
  gitignore,
6600
- excludes
6697
+ excludes: normalizedExcludes
6601
6698
  });
6602
6699
  if (!createResult.success) throw new BackupCreateError({
6603
6700
  message: "Container failed to create backup archive",
@@ -6713,12 +6810,13 @@ var Sandbox = class Sandbox extends Container {
6713
6810
  context: { reason: "excludes must be an array of strings" },
6714
6811
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6715
6812
  });
6813
+ const normalizedExcludes = this.normalizeBackupExcludes(excludes);
6716
6814
  backupSession = await this.ensureBackupSession();
6717
6815
  backupId = crypto.randomUUID();
6718
6816
  const archivePath = `${BACKUP_CONTAINER_DIR}/${backupId}.sqsh`;
6719
6817
  const createResult = await this.client.backup.createArchive(dir, archivePath, backupSession, {
6720
6818
  gitignore,
6721
- excludes
6819
+ excludes: normalizedExcludes
6722
6820
  });
6723
6821
  if (!createResult.success) throw new BackupCreateError({
6724
6822
  message: "Container failed to create backup archive",
@@ -7070,5 +7168,5 @@ var Sandbox = class Sandbox extends Container {
7070
7168
  };
7071
7169
 
7072
7170
  //#endregion
7073
- export { DesktopInvalidOptionsError as A, CommandClient as C, BackupNotFoundError as D, BackupExpiredError as E, InvalidBackupConfigError as F, ProcessExitedBeforeReadyError as I, ProcessReadyTimeoutError as L, DesktopProcessCrashedError as M, DesktopStartFailedError as N, BackupRestoreError as O, DesktopUnavailableError as P, SessionTerminatedError as R, DesktopClient as S, BackupCreateError as T, UtilityClient as _, BucketMountError as a, GitClient as b, MissingCredentialsError as c, parseSSEStream as d, responseToAsyncIterable as f, SandboxClient as g, streamFile as h, proxyTerminal as i, DesktopNotStartedError as j, DesktopInvalidCoordinatesError as k, S3FSMountError as l, collectFile as m, getSandbox as n, BucketUnmountError as o, CodeInterpreter as p, proxyToSandbox as r, InvalidMountConfigError as s, Sandbox as t, asyncIterableToSSEStream as u, ProcessClient as v, BackupClient as w, FileClient as x, PortClient as y };
7074
- //# sourceMappingURL=sandbox-PAYx1CcU.js.map
7171
+ export { DesktopInvalidOptionsError as A, CommandClient as C, BackupNotFoundError as D, BackupExpiredError as E, InvalidBackupConfigError as F, ProcessExitedBeforeReadyError as I, ProcessReadyTimeoutError as L, DesktopProcessCrashedError as M, DesktopStartFailedError as N, BackupRestoreError as O, DesktopUnavailableError as P, RPCTransportError as R, DesktopClient as S, BackupCreateError as T, UtilityClient as _, BucketMountError as a, GitClient as b, MissingCredentialsError as c, parseSSEStream as d, responseToAsyncIterable as f, SandboxClient as g, streamFile as h, proxyTerminal as i, DesktopNotStartedError as j, DesktopInvalidCoordinatesError as k, S3FSMountError as l, collectFile as m, getSandbox as n, BucketUnmountError as o, CodeInterpreter as p, proxyToSandbox as r, InvalidMountConfigError as s, Sandbox as t, asyncIterableToSSEStream as u, ProcessClient as v, BackupClient as w, FileClient as x, PortClient as y, SessionTerminatedError as z };
7172
+ //# sourceMappingURL=sandbox-CReFGUtF.js.map