sandboxedjs 0.1.26 → 0.1.27

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.d.cts CHANGED
@@ -196,7 +196,7 @@ interface DirEntry {
196
196
  /**
197
197
  * The container's virtual filesystem.
198
198
  *
199
- * Real file content lives in a Nodepod `MemoryVolume`, which is deliberately the
199
+ * Real file content lives in the RuntimePod's `MemoryVolume`, deliberately the
200
200
  * *same* volume the Node.js worker processes see — so a file written by `echo`
201
201
  * is readable by `require('fs')` inside a spawned script, and vice versa.
202
202
  *
@@ -746,7 +746,7 @@ declare function createContext(init: ContextInit): ExecContext;
746
746
  * The container's network stack.
747
747
  *
748
748
  * There is no real socket layer: HTTP servers started inside the container are
749
- * registered with Nodepod's request proxy, and this module is the routing and
749
+ * registered with the RuntimePod's request proxy, and this module is the routing and
750
750
  * name-resolution layer on top — interfaces for `ip`/`ifconfig`, a hosts file
751
751
  * resolver, a listening-port table for `ss`/`netstat`, and an outbound policy
752
752
  * that decides whether `curl https://example.com` is allowed to touch the real
@@ -805,7 +805,7 @@ declare class NetworkStack {
805
805
  registerListener(port: number, info: Omit<ListeningPort, "port" | "since">): void;
806
806
  unregisterListener(port: number): void;
807
807
  listening(): ListeningPort[];
808
- /** Ports Nodepod's proxy has registered for this instance. */
808
+ /** Ports the pod's proxy has registered for this instance. */
809
809
  private knownPodPorts;
810
810
  /** True when something inside the container answers on `port`. */
811
811
  isPortOpen(port: number, timeoutMs?: number): Promise<boolean>;
@@ -1732,7 +1732,7 @@ declare class Container {
1732
1732
  /**
1733
1733
  * Deliver a request whose body is bytes, without letting them become text.
1734
1734
  *
1735
- * Nodepod's public `request()` runs the body through `toString("utf8")` on
1735
+ * A RuntimePod's public `request()` may run the body through `toString("utf8")` on
1736
1736
  * its way in, so anything above `0x7f` is replaced: a five-byte payload
1737
1737
  * containing `0x89` and `0xff` arrives as nine. That silently destroys every
1738
1738
  * upload — an image or a video reaches the server the wrong size and no
@@ -1772,7 +1772,7 @@ declare class Container {
1772
1772
  get cwd(): string;
1773
1773
  get env(): Record<string, string>;
1774
1774
  private assertActive;
1775
- /** Tear down every process and release the Nodepod instance. */
1775
+ /** Tear down every process and release the runtime pod. */
1776
1776
  dispose(): void;
1777
1777
  get isDisposed(): boolean;
1778
1778
  }
@@ -2114,17 +2114,16 @@ interface RootfsOptions {
2114
2114
  declare function buildRootfs(vfs: Vfs, opts?: RootfsOptions): void;
2115
2115
 
2116
2116
  /**
2117
- * The Node.js runtime, backed by Nodepod.
2117
+ * The Node.js command adapter, backed by the clean-room RuntimePod.
2118
2118
  *
2119
- * Nodepod runs the script in an isolated worker over the *same* memory volume
2119
+ * The pod runs the script over the *same* memory volume
2120
2120
  * the container's filesystem uses, so `require('fs')` inside a script sees the
2121
2121
  * files `echo` and `tar` created, and anything the script writes is visible to
2122
2122
  * the shell afterwards.
2123
2123
  *
2124
- * Two gaps in the underlying `spawn` are papered over here:
2124
+ * Two details of the underlying `spawn` are handled here:
2125
2125
  * - only `node` resolves as a command, so everything else is dispatched by our
2126
- * own kernel rather than being handed to Nodepod's shell (which hangs on an
2127
- * unknown command);
2126
+ * own kernel rather than being handed to the Node process runner;
2128
2127
  * - the worker's stdin has no end-of-stream signal, so when a pipeline feeds
2129
2128
  * a script we materialise stdin as a file and install a real stdin stream
2130
2129
  * over it before the script loads.
@@ -2484,6 +2483,8 @@ declare function createCoreModules(options: CoreModulesOptions): {
2484
2483
  * script that has simply finished.
2485
2484
  */
2486
2485
  pendingHandles(): number;
2486
+ /** Active timers which called `unref()` and therefore only merit startup grace. */
2487
+ pendingUnrefed(): number;
2487
2488
  };
2488
2489
 
2489
2490
  interface EsmTransformResult {
@@ -2570,9 +2571,18 @@ declare class LocalRuntimePod implements RuntimePod {
2570
2571
  private readonly aliases;
2571
2572
  private readonly modules;
2572
2573
  private readonly esbuild;
2574
+ private rolldownBinding;
2573
2575
  private constructor();
2574
2576
  static boot(options?: LocalRuntimeOptions): Promise<LocalRuntimePod>;
2575
2577
  spawn(command: string, args?: string[], options?: Record<string, unknown>): Promise<RuntimeProcess>;
2578
+ /**
2579
+ * Rolldown's JavaScript API synchronously requires its compiled binding.
2580
+ * When a project contains Rolldown, preload the official WASI build in the
2581
+ * host and expose it through the module override table before evaluation.
2582
+ * Keeping this demand-driven avoids adding WASM startup cost to ordinary
2583
+ * shells and Node programs.
2584
+ */
2585
+ private prepareRolldown;
2576
2586
  /**
2577
2587
  * Wait until the process has either started serving or genuinely run out of
2578
2588
  * work.
@@ -2607,6 +2617,10 @@ interface RegistryManifest {
2607
2617
  };
2608
2618
  dependencies?: Record<string, string>;
2609
2619
  optionalDependencies?: Record<string, string>;
2620
+ os?: string[];
2621
+ cpu?: string[];
2622
+ libc?: string[];
2623
+ main?: string;
2610
2624
  bin?: string | Record<string, string>;
2611
2625
  }
2612
2626
  interface CleanInstallerOptions {
package/dist/index.d.ts CHANGED
@@ -196,7 +196,7 @@ interface DirEntry {
196
196
  /**
197
197
  * The container's virtual filesystem.
198
198
  *
199
- * Real file content lives in a Nodepod `MemoryVolume`, which is deliberately the
199
+ * Real file content lives in the RuntimePod's `MemoryVolume`, deliberately the
200
200
  * *same* volume the Node.js worker processes see — so a file written by `echo`
201
201
  * is readable by `require('fs')` inside a spawned script, and vice versa.
202
202
  *
@@ -746,7 +746,7 @@ declare function createContext(init: ContextInit): ExecContext;
746
746
  * The container's network stack.
747
747
  *
748
748
  * There is no real socket layer: HTTP servers started inside the container are
749
- * registered with Nodepod's request proxy, and this module is the routing and
749
+ * registered with the RuntimePod's request proxy, and this module is the routing and
750
750
  * name-resolution layer on top — interfaces for `ip`/`ifconfig`, a hosts file
751
751
  * resolver, a listening-port table for `ss`/`netstat`, and an outbound policy
752
752
  * that decides whether `curl https://example.com` is allowed to touch the real
@@ -805,7 +805,7 @@ declare class NetworkStack {
805
805
  registerListener(port: number, info: Omit<ListeningPort, "port" | "since">): void;
806
806
  unregisterListener(port: number): void;
807
807
  listening(): ListeningPort[];
808
- /** Ports Nodepod's proxy has registered for this instance. */
808
+ /** Ports the pod's proxy has registered for this instance. */
809
809
  private knownPodPorts;
810
810
  /** True when something inside the container answers on `port`. */
811
811
  isPortOpen(port: number, timeoutMs?: number): Promise<boolean>;
@@ -1732,7 +1732,7 @@ declare class Container {
1732
1732
  /**
1733
1733
  * Deliver a request whose body is bytes, without letting them become text.
1734
1734
  *
1735
- * Nodepod's public `request()` runs the body through `toString("utf8")` on
1735
+ * A RuntimePod's public `request()` may run the body through `toString("utf8")` on
1736
1736
  * its way in, so anything above `0x7f` is replaced: a five-byte payload
1737
1737
  * containing `0x89` and `0xff` arrives as nine. That silently destroys every
1738
1738
  * upload — an image or a video reaches the server the wrong size and no
@@ -1772,7 +1772,7 @@ declare class Container {
1772
1772
  get cwd(): string;
1773
1773
  get env(): Record<string, string>;
1774
1774
  private assertActive;
1775
- /** Tear down every process and release the Nodepod instance. */
1775
+ /** Tear down every process and release the runtime pod. */
1776
1776
  dispose(): void;
1777
1777
  get isDisposed(): boolean;
1778
1778
  }
@@ -2114,17 +2114,16 @@ interface RootfsOptions {
2114
2114
  declare function buildRootfs(vfs: Vfs, opts?: RootfsOptions): void;
2115
2115
 
2116
2116
  /**
2117
- * The Node.js runtime, backed by Nodepod.
2117
+ * The Node.js command adapter, backed by the clean-room RuntimePod.
2118
2118
  *
2119
- * Nodepod runs the script in an isolated worker over the *same* memory volume
2119
+ * The pod runs the script over the *same* memory volume
2120
2120
  * the container's filesystem uses, so `require('fs')` inside a script sees the
2121
2121
  * files `echo` and `tar` created, and anything the script writes is visible to
2122
2122
  * the shell afterwards.
2123
2123
  *
2124
- * Two gaps in the underlying `spawn` are papered over here:
2124
+ * Two details of the underlying `spawn` are handled here:
2125
2125
  * - only `node` resolves as a command, so everything else is dispatched by our
2126
- * own kernel rather than being handed to Nodepod's shell (which hangs on an
2127
- * unknown command);
2126
+ * own kernel rather than being handed to the Node process runner;
2128
2127
  * - the worker's stdin has no end-of-stream signal, so when a pipeline feeds
2129
2128
  * a script we materialise stdin as a file and install a real stdin stream
2130
2129
  * over it before the script loads.
@@ -2484,6 +2483,8 @@ declare function createCoreModules(options: CoreModulesOptions): {
2484
2483
  * script that has simply finished.
2485
2484
  */
2486
2485
  pendingHandles(): number;
2486
+ /** Active timers which called `unref()` and therefore only merit startup grace. */
2487
+ pendingUnrefed(): number;
2487
2488
  };
2488
2489
 
2489
2490
  interface EsmTransformResult {
@@ -2570,9 +2571,18 @@ declare class LocalRuntimePod implements RuntimePod {
2570
2571
  private readonly aliases;
2571
2572
  private readonly modules;
2572
2573
  private readonly esbuild;
2574
+ private rolldownBinding;
2573
2575
  private constructor();
2574
2576
  static boot(options?: LocalRuntimeOptions): Promise<LocalRuntimePod>;
2575
2577
  spawn(command: string, args?: string[], options?: Record<string, unknown>): Promise<RuntimeProcess>;
2578
+ /**
2579
+ * Rolldown's JavaScript API synchronously requires its compiled binding.
2580
+ * When a project contains Rolldown, preload the official WASI build in the
2581
+ * host and expose it through the module override table before evaluation.
2582
+ * Keeping this demand-driven avoids adding WASM startup cost to ordinary
2583
+ * shells and Node programs.
2584
+ */
2585
+ private prepareRolldown;
2576
2586
  /**
2577
2587
  * Wait until the process has either started serving or genuinely run out of
2578
2588
  * work.
@@ -2607,6 +2617,10 @@ interface RegistryManifest {
2607
2617
  };
2608
2618
  dependencies?: Record<string, string>;
2609
2619
  optionalDependencies?: Record<string, string>;
2620
+ os?: string[];
2621
+ cpu?: string[];
2622
+ libc?: string[];
2623
+ main?: string;
2610
2624
  bin?: string | Record<string, string>;
2611
2625
  }
2612
2626
  interface CleanInstallerOptions {
package/dist/index.js CHANGED
@@ -6806,7 +6806,7 @@ var NetworkStack = class {
6806
6806
  }
6807
6807
  return [...out.values()].sort((a, b) => a.port - b.port);
6808
6808
  }
6809
- /** Ports Nodepod's proxy has registered for this instance. */
6809
+ /** Ports the pod's proxy has registered for this instance. */
6810
6810
  knownPodPorts() {
6811
6811
  try {
6812
6812
  return this.pod.proxy.activePorts(this.pod.instanceId) ?? [];
@@ -18163,7 +18163,33 @@ function ffmpegCommands() {
18163
18163
  return [ffmpeg, ffprobe];
18164
18164
  }
18165
18165
  var platformBuffer = globalThis.Buffer;
18166
- var Buffer2 = platformBuffer ?? Buffer$1;
18166
+ var Buffer2 = platformBuffer ?? addBase64UrlSupport(Buffer$1);
18167
+ function addBase64UrlSupport(BufferClass) {
18168
+ const target = BufferClass;
18169
+ if (target.__sandboxedBase64Url) return BufferClass;
18170
+ Object.defineProperty(target, "__sandboxedBase64Url", { value: true });
18171
+ const from = target.from.bind(target);
18172
+ target.from = (value, encodingOrOffset, length) => from(value, normalizeEncoding(encodingOrOffset), length);
18173
+ const byteLength = target.byteLength.bind(target);
18174
+ target.byteLength = (value, encoding) => byteLength(value, normalizeEncoding(encoding));
18175
+ const isEncoding = target.isEncoding?.bind(target);
18176
+ if (isEncoding) target.isEncoding = (encoding) => encoding.toLowerCase() === "base64url" || isEncoding(encoding);
18177
+ const toString = target.prototype.toString;
18178
+ target.prototype.toString = function(encoding, start2, end) {
18179
+ if (encoding?.toLowerCase() !== "base64url") return toString.call(this, encoding, start2, end);
18180
+ return toString.call(this, "base64", start2, end).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
18181
+ };
18182
+ const write = target.prototype.write;
18183
+ target.prototype.write = function(...args) {
18184
+ const index = typeof args[1] === "string" ? 1 : typeof args[2] === "string" ? 2 : 3;
18185
+ if (typeof args[index] === "string") args[index] = normalizeEncoding(args[index]);
18186
+ return write.apply(this, args);
18187
+ };
18188
+ return BufferClass;
18189
+ }
18190
+ function normalizeEncoding(value) {
18191
+ return typeof value === "string" && value.toLowerCase() === "base64url" ? "base64" : value;
18192
+ }
18167
18193
 
18168
18194
  // src/pkg/clean-installer.ts
18169
18195
  init_path();
@@ -18205,6 +18231,9 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
18205
18231
  const version = resolveVersion(metadata, range);
18206
18232
  const manifest = metadata.versions[version];
18207
18233
  if (!manifest) throw new Error(`No matching version found for ${name}@${range}`);
18234
+ if (!supportsPlatform(manifest)) {
18235
+ throw new Error(`${name}@${version} is not compatible with linux/x64/glibc`);
18236
+ }
18208
18237
  const identity = `${name}@${version}`;
18209
18238
  const target = join(modulesRoot, name);
18210
18239
  const installed2 = this.tryReadJson(join(target, "package.json"));
@@ -18299,6 +18328,15 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
18299
18328
  }
18300
18329
  }
18301
18330
  };
18331
+ function supportsPlatform(manifest) {
18332
+ return !manifest.main?.endsWith(".node") && platformListAllows(manifest.os, "linux") && platformListAllows(manifest.cpu, "x64") && platformListAllows(manifest.libc, "glibc");
18333
+ }
18334
+ function platformListAllows(values, current) {
18335
+ if (!values?.length) return true;
18336
+ if (values.includes(`!${current}`)) return false;
18337
+ const positive = values.filter((value) => !value.startsWith("!"));
18338
+ return positive.length === 0 || positive.includes(current) || positive.includes("any");
18339
+ }
18302
18340
  function resolveVersion(metadata, range) {
18303
18341
  const tag2 = metadata["dist-tags"]?.[range];
18304
18342
  if (tag2) return tag2;
@@ -22259,6 +22297,7 @@ function createCoreModules(options) {
22259
22297
  globals,
22260
22298
  process: processObject,
22261
22299
  pendingHandles: timers.pending,
22300
+ pendingUnrefed: timers.pendingUnrefed,
22262
22301
  writeStdin: (data) => {
22263
22302
  if (options.interactiveStdin) stdin.write(data);
22264
22303
  },
@@ -22854,33 +22893,78 @@ var ERRNO_CONSTANTS = {
22854
22893
  };
22855
22894
  function createTrackedTimers() {
22856
22895
  const live = /* @__PURE__ */ new Set();
22857
- const track = (handle, repeating) => {
22896
+ const unrefed = /* @__PURE__ */ new Set();
22897
+ const states = /* @__PURE__ */ new WeakMap();
22898
+ const track = (native) => {
22899
+ const state = { native, active: true, referenced: true };
22900
+ const handle = {
22901
+ ref() {
22902
+ state.referenced = true;
22903
+ unrefed.delete(handle);
22904
+ if (state.active) live.add(handle);
22905
+ state.native?.ref?.();
22906
+ return handle;
22907
+ },
22908
+ unref() {
22909
+ state.referenced = false;
22910
+ live.delete(handle);
22911
+ if (state.active) unrefed.add(handle);
22912
+ state.native?.unref?.();
22913
+ return handle;
22914
+ },
22915
+ hasRef: () => state.referenced,
22916
+ refresh() {
22917
+ state.native?.refresh?.();
22918
+ return handle;
22919
+ },
22920
+ [Symbol.toPrimitive]: () => Number(state.native)
22921
+ };
22922
+ states.set(handle, state);
22858
22923
  live.add(handle);
22859
22924
  return handle;
22860
22925
  };
22926
+ const complete = (handle) => {
22927
+ const state = states.get(handle);
22928
+ if (state) state.active = false;
22929
+ live.delete(handle);
22930
+ unrefed.delete(handle);
22931
+ };
22861
22932
  const setTimeoutTracked = (fn, delay, ...args) => {
22862
- const handle = setTimeout(
22933
+ let handle;
22934
+ const native = setTimeout(
22863
22935
  (...inner) => {
22864
- live.delete(handle);
22936
+ complete(handle);
22865
22937
  fn(...inner);
22866
22938
  },
22867
22939
  delay,
22868
22940
  ...args
22869
22941
  );
22870
- live.add(handle);
22942
+ handle = track(native);
22871
22943
  return handle;
22872
22944
  };
22873
22945
  const setIntervalTracked = (fn, delay, ...args) => track(setInterval(fn, delay, ...args));
22874
22946
  const hostSetImmediate = globalThis.setImmediate;
22875
22947
  const setImmediateTracked = (fn, ...args) => {
22876
- const handle = hostSetImmediate ? hostSetImmediate((...inner) => {
22877
- live.delete(handle);
22948
+ if (!hostSetImmediate) return setTimeoutTracked(fn, 0, ...args);
22949
+ let handle;
22950
+ const native = hostSetImmediate((...inner) => {
22951
+ complete(handle);
22878
22952
  fn(...inner);
22879
- }, ...args) : setTimeoutTracked(fn, 0, ...args);
22880
- live.add(handle);
22953
+ }, ...args);
22954
+ handle = track(native);
22881
22955
  return handle;
22882
22956
  };
22883
22957
  const clear2 = (handle, native) => {
22958
+ if (typeof handle === "object" && handle !== null) {
22959
+ const state = states.get(handle);
22960
+ if (state) {
22961
+ state.active = false;
22962
+ live.delete(handle);
22963
+ unrefed.delete(handle);
22964
+ native(state.native);
22965
+ return;
22966
+ }
22967
+ }
22884
22968
  live.delete(handle);
22885
22969
  native(handle);
22886
22970
  };
@@ -22893,7 +22977,8 @@ function createTrackedTimers() {
22893
22977
  clearInterval: (handle) => clear2(handle, clearInterval),
22894
22978
  clearImmediate: (handle) => clear2(handle, globalThis.clearImmediate ?? clearTimeout)
22895
22979
  },
22896
- pending: () => live.size
22980
+ pending: () => live.size,
22981
+ pendingUnrefed: () => unrefed.size
22897
22982
  };
22898
22983
  }
22899
22984
  function createTimerPromises() {
@@ -23415,7 +23500,26 @@ function ensureProcessGlobal() {
23415
23500
  });
23416
23501
  }
23417
23502
 
23503
+ // src/runtime/host-rolldown.ts
23504
+ async function loadHostRolldownBinding() {
23505
+ if (typeof window !== "undefined" && globalThis.crossOriginIsolated !== true) {
23506
+ throw new Error(
23507
+ "Vite 8/Rolldown requires cross-origin isolation. Serve the app with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp headers."
23508
+ );
23509
+ }
23510
+ try {
23511
+ const loaded = await import('@rolldown/binding-wasm32-wasi');
23512
+ return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
23513
+ } catch (error) {
23514
+ if (typeof process !== "undefined" && process.env?.SANDBOXEDJS_DEBUG) {
23515
+ console.error("[sandboxedjs] Rolldown WASI binding unavailable:", error);
23516
+ }
23517
+ throw new Error("The optional Rolldown WASI binding could not be loaded.", { cause: error });
23518
+ }
23519
+ }
23520
+
23418
23521
  // src/runtime/local-runtime-pod.ts
23522
+ init_path();
23419
23523
  var WASM_ALIASES = {
23420
23524
  esbuild: "esbuild-wasm",
23421
23525
  rollup: "@rollup/wasm-node"
@@ -23666,6 +23770,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
23666
23770
  aliases;
23667
23771
  modules;
23668
23772
  esbuild;
23773
+ rolldownBinding;
23669
23774
  constructor(options) {
23670
23775
  ensureProcessGlobal();
23671
23776
  this.workdir = options.workdir ?? "/";
@@ -23699,6 +23804,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
23699
23804
  const env2 = { ...this.env, ...isRecord(options.env) ? options.env : {} };
23700
23805
  const owner = `${this.instanceId}:${Math.random().toString(36).slice(2)}`;
23701
23806
  return new LocalProcess(async (proc) => {
23807
+ await this.prepareRolldown(cwd, env2);
23702
23808
  const untrack = trackProcess(proc);
23703
23809
  let requestedExit = 0;
23704
23810
  let engine;
@@ -23735,7 +23841,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
23735
23841
  });
23736
23842
  try {
23737
23843
  await engine.run(script);
23738
- await this.settle(owner, core.pendingHandles, core.readingStdin);
23844
+ await this.settle(owner, core.pendingHandles, core.pendingUnrefed, core.readingStdin);
23739
23845
  if (this.router.activePorts(owner).length) {
23740
23846
  await proc.waitForKill();
23741
23847
  return 137;
@@ -23747,6 +23853,25 @@ var LocalRuntimePod = class _LocalRuntimePod {
23747
23853
  }
23748
23854
  });
23749
23855
  }
23856
+ /**
23857
+ * Rolldown's JavaScript API synchronously requires its compiled binding.
23858
+ * When a project contains Rolldown, preload the official WASI build in the
23859
+ * host and expose it through the module override table before evaluation.
23860
+ * Keeping this demand-driven avoids adding WASM startup cost to ordinary
23861
+ * shells and Node programs.
23862
+ */
23863
+ async prepareRolldown(cwd, env2) {
23864
+ const specifier = "@rolldown/binding-wasm32-wasi";
23865
+ if (!this.modules[specifier] && packageInstalled(this.volume, cwd, "rolldown")) {
23866
+ this.rolldownBinding ??= loadHostRolldownBinding();
23867
+ const binding = await this.rolldownBinding;
23868
+ if (binding) this.modules[specifier] = binding;
23869
+ }
23870
+ if (this.modules[specifier]) {
23871
+ syncRolldownFileSystem(this.modules[specifier], this.volume, cwd);
23872
+ env2.NAPI_RS_FORCE_WASI ??= "true";
23873
+ }
23874
+ }
23750
23875
  /**
23751
23876
  * Wait until the process has either started serving or genuinely run out of
23752
23877
  * work.
@@ -23762,11 +23887,17 @@ var LocalRuntimePod = class _LocalRuntimePod {
23762
23887
  * A plain script that has genuinely finished falls straight through both,
23763
23888
  * costing a handful of empty turns.
23764
23889
  */
23765
- async settle(owner, pendingHandles, readingStdin) {
23890
+ async settle(owner, pendingHandles, pendingUnrefed, readingStdin) {
23766
23891
  for (let turn = 0; turn < DRAIN_TURNS; turn++) {
23767
23892
  if (this.router.activePorts(owner).length) return;
23768
23893
  await new Promise((resolve2) => setTimeout(resolve2, 0));
23769
23894
  }
23895
+ if (pendingHandles() === 0 && pendingUnrefed() > 0 && !readingStdin()) {
23896
+ const deadline = Date.now() + 1e3;
23897
+ while (!this.router.activePorts(owner).length && Date.now() < deadline) {
23898
+ await new Promise((resolve2) => setTimeout(resolve2, 5));
23899
+ }
23900
+ }
23770
23901
  while (!this.router.activePorts(owner).length && (pendingHandles() > 0 || readingStdin())) {
23771
23902
  await new Promise((resolve2) => setTimeout(resolve2, 5));
23772
23903
  }
@@ -23819,6 +23950,60 @@ function formatError(error) {
23819
23950
  function isRecord(value) {
23820
23951
  return typeof value === "object" && value !== null && !Array.isArray(value);
23821
23952
  }
23953
+ function packageInstalled(volume, cwd, wanted) {
23954
+ for (let dir3 = clean(cwd); ; dir3 = dirname(dir3)) {
23955
+ if (packageTreeContains(volume, join(dir3, "node_modules"), wanted, /* @__PURE__ */ new Set())) return true;
23956
+ if (dir3 === "/") return false;
23957
+ }
23958
+ }
23959
+ function packageTreeContains(volume, modulesRoot, wanted, visited) {
23960
+ if (visited.has(modulesRoot) || !directory(volume, modulesRoot)) return false;
23961
+ visited.add(modulesRoot);
23962
+ if (directory(volume, join(modulesRoot, wanted))) return true;
23963
+ for (const entry of volume.readdirSync(modulesRoot)) {
23964
+ if (entry === ".bin") continue;
23965
+ const first = join(modulesRoot, entry);
23966
+ const packages = entry.startsWith("@") && directory(volume, first) ? volume.readdirSync(first).map((name) => join(first, name)) : [first];
23967
+ for (const root of packages) {
23968
+ if (directory(volume, root) && packageTreeContains(volume, join(root, "node_modules"), wanted, visited)) {
23969
+ return true;
23970
+ }
23971
+ }
23972
+ }
23973
+ return false;
23974
+ }
23975
+ function directory(volume, path) {
23976
+ try {
23977
+ return volume.lstatSync(path).isDirectory();
23978
+ } catch {
23979
+ return false;
23980
+ }
23981
+ }
23982
+ function syncRolldownFileSystem(binding, volume, root) {
23983
+ const fs = binding?.__fs;
23984
+ if (!fs?.mkdirSync || !fs?.writeFileSync) return;
23985
+ try {
23986
+ fs.rmSync?.(root, { recursive: true, force: true });
23987
+ } catch {
23988
+ }
23989
+ const copy = (path) => {
23990
+ const stat2 = volume.lstatSync(path);
23991
+ if (stat2.isDirectory()) {
23992
+ fs.mkdirSync(path, { recursive: true });
23993
+ for (const name of volume.readdirSync(path)) copy(join(path, name));
23994
+ } else if (stat2.isSymbolicLink()) {
23995
+ fs.mkdirSync(dirname(path), { recursive: true });
23996
+ try {
23997
+ fs.symlinkSync(volume.readlinkSync(path), path);
23998
+ } catch {
23999
+ }
24000
+ } else if (!path.endsWith(".node")) {
24001
+ fs.mkdirSync(dirname(path), { recursive: true });
24002
+ fs.writeFileSync(path, volume.readFileSync(path));
24003
+ }
24004
+ };
24005
+ copy(clean(root));
24006
+ }
23822
24007
 
23823
24008
  // src/container/container.ts
23824
24009
  var Container = class _Container {
@@ -24162,7 +24347,7 @@ var Container = class _Container {
24162
24347
  /**
24163
24348
  * Deliver a request whose body is bytes, without letting them become text.
24164
24349
  *
24165
- * Nodepod's public `request()` runs the body through `toString("utf8")` on
24350
+ * A RuntimePod's public `request()` may run the body through `toString("utf8")` on
24166
24351
  * its way in, so anything above `0x7f` is replaced: a five-byte payload
24167
24352
  * containing `0x89` and `0xff` arrives as nine. That silently destroys every
24168
24353
  * upload — an image or a video reaches the server the wrong size and no
@@ -24264,7 +24449,7 @@ var Container = class _Container {
24264
24449
  assertActive() {
24265
24450
  if (this.disposed) throw new Error("container has been disposed");
24266
24451
  }
24267
- /** Tear down every process and release the Nodepod instance. */
24452
+ /** Tear down every process and release the runtime pod. */
24268
24453
  dispose() {
24269
24454
  if (this.disposed) return;
24270
24455
  this.disposed = true;