sandboxedjs 0.1.35 → 0.1.36

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
@@ -2642,6 +2642,13 @@ interface MirrorFs {
2642
2642
  }): void;
2643
2643
  unlinkSync?(path: string): void;
2644
2644
  rmdirSync?(path: string): void;
2645
+ readdirSync?(path: string): string[];
2646
+ readFileSync?(path: string): Uint8Array;
2647
+ lstatSync?(path: string): {
2648
+ isDirectory(): boolean;
2649
+ isSymbolicLink(): boolean;
2650
+ size?: number;
2651
+ };
2645
2652
  }
2646
2653
  /**
2647
2654
  * A `RuntimeVolume` that forwards every mutation to an optional mirror.
@@ -2665,6 +2672,22 @@ declare class MirroringVolume implements RuntimeVolume {
2665
2672
  detach(): void;
2666
2673
  /** Copy the whole subtree across. The only bulk operation that remains. */
2667
2674
  private seed;
2675
+ /**
2676
+ * Bring back what the mirrored engine wrote.
2677
+ *
2678
+ * The mirror exists because Rolldown's resolver reads through WASI rather
2679
+ * than through anything JavaScript can hand it — but Rolldown also *writes*
2680
+ * through WASI, so `vite build` leaves its output in the mirror and the
2681
+ * container sees an empty `dist/`. This is the return leg, and it is why the
2682
+ * mirror is no longer strictly one-way.
2683
+ *
2684
+ * Called when a process ends rather than on a timer: that is the moment a
2685
+ * build's output is complete, and a dev server — which serves from memory
2686
+ * and writes nothing — pays for it only once, at exit.
2687
+ */
2688
+ absorb(): void;
2689
+ /** `mkdir -p`, which the volume does not offer directly. */
2690
+ private ensureDirectory;
2668
2691
  /** Is this path inside the mirrored subtree? */
2669
2692
  private mirrored;
2670
2693
  /**
package/dist/index.d.ts CHANGED
@@ -2642,6 +2642,13 @@ interface MirrorFs {
2642
2642
  }): void;
2643
2643
  unlinkSync?(path: string): void;
2644
2644
  rmdirSync?(path: string): void;
2645
+ readdirSync?(path: string): string[];
2646
+ readFileSync?(path: string): Uint8Array;
2647
+ lstatSync?(path: string): {
2648
+ isDirectory(): boolean;
2649
+ isSymbolicLink(): boolean;
2650
+ size?: number;
2651
+ };
2645
2652
  }
2646
2653
  /**
2647
2654
  * A `RuntimeVolume` that forwards every mutation to an optional mirror.
@@ -2665,6 +2672,22 @@ declare class MirroringVolume implements RuntimeVolume {
2665
2672
  detach(): void;
2666
2673
  /** Copy the whole subtree across. The only bulk operation that remains. */
2667
2674
  private seed;
2675
+ /**
2676
+ * Bring back what the mirrored engine wrote.
2677
+ *
2678
+ * The mirror exists because Rolldown's resolver reads through WASI rather
2679
+ * than through anything JavaScript can hand it — but Rolldown also *writes*
2680
+ * through WASI, so `vite build` leaves its output in the mirror and the
2681
+ * container sees an empty `dist/`. This is the return leg, and it is why the
2682
+ * mirror is no longer strictly one-way.
2683
+ *
2684
+ * Called when a process ends rather than on a timer: that is the moment a
2685
+ * build's output is complete, and a dev server — which serves from memory
2686
+ * and writes nothing — pays for it only once, at exit.
2687
+ */
2688
+ absorb(): void;
2689
+ /** `mkdir -p`, which the volume does not offer directly. */
2690
+ private ensureDirectory;
2668
2691
  /** Is this path inside the mirrored subtree? */
2669
2692
  private mirrored;
2670
2693
  /**
package/dist/index.js CHANGED
@@ -17,6 +17,10 @@ import timersModule from 'timers-browserify';
17
17
  import legacyUrl from 'url/url.js';
18
18
  import { sha1 as sha1$1, md5 } from '@noble/hashes/legacy';
19
19
  import { hmac } from '@noble/hashes/hmac';
20
+ import { createRequire } from 'module';
21
+ import { readFileSync, existsSync } from 'fs';
22
+ import { dirname as dirname$1, join as join$1 } from 'path';
23
+ import { pathToFileURL as pathToFileURL$1, fileURLToPath as fileURLToPath$1 } from 'url';
20
24
 
21
25
  var __defProp = Object.defineProperty;
22
26
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -18660,9 +18664,9 @@ async function resolveIndexUrl() {
18660
18664
  if (indexUrl) return indexUrl;
18661
18665
  if (!isNode2) return DEFAULT_BROWSER_INDEX_URL;
18662
18666
  try {
18663
- const { createRequire } = await nodeBuiltin("module");
18667
+ const { createRequire: createRequire2 } = await nodeBuiltin("module");
18664
18668
  const path = await nodeBuiltin("path");
18665
- const require2 = createRequire(path.join(process.cwd(), "index.js"));
18669
+ const require2 = createRequire2(path.join(process.cwd(), "index.js"));
18666
18670
  return `${path.dirname(require2.resolve("pyodide/package.json"))}/`;
18667
18671
  } catch {
18668
18672
  return void 0;
@@ -19127,15 +19131,15 @@ function pythonCommands() {
19127
19131
  var INSTALL_HINT = "ffmpeg is not installed in this container.\nThe FFmpeg runtime ships separately because it is a ~31MB WebAssembly build:\n npm install @ffmpeg/core";
19128
19132
  var compiled = null;
19129
19133
  async function loadFactory() {
19130
- const { createRequire } = await nodeBuiltin("module");
19134
+ const { createRequire: createRequire2 } = await nodeBuiltin("module");
19131
19135
  const fs = await nodeBuiltin("fs/promises");
19132
19136
  const path = await nodeBuiltin("path");
19133
- const packageRequire = createRequire(import.meta.url);
19137
+ const packageRequire = createRequire2(import.meta.url);
19134
19138
  let require2 = packageRequire;
19135
19139
  try {
19136
19140
  require2.resolve("@ffmpeg/core/wasm");
19137
19141
  } catch {
19138
- require2 = createRequire(path.join(process.cwd(), "index.js"));
19142
+ require2 = createRequire2(path.join(process.cwd(), "index.js"));
19139
19143
  }
19140
19144
  const wasmPath = require2.resolve("@ffmpeg/core/wasm");
19141
19145
  const wasm = await fs.readFile(wasmPath);
@@ -25005,6 +25009,7 @@ function createCoreModules(options) {
25005
25009
  zlib: zlib_module_default
25006
25010
  };
25007
25011
  for (const name of stubNames) builtins[name] = createUnsupportedModule(name);
25012
+ builtins.net = createNetModule();
25008
25013
  const globals = {
25009
25014
  Buffer: Buffer2,
25010
25015
  console: consoleObject,
@@ -25833,15 +25838,43 @@ function createStreamPromises() {
25833
25838
  })
25834
25839
  };
25835
25840
  }
25836
- function createUnsupportedModule(name) {
25841
+ function createUnsupportedModule(name, supported = {}) {
25837
25842
  const unsupported = () => {
25838
25843
  const error = new Error(`${name} is unavailable in the browser runtime`);
25839
25844
  error.code = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
25840
25845
  throw error;
25841
25846
  };
25842
- return new Proxy({ unsupported }, { get(target, key) {
25843
- return key in target ? target[key] : unsupported;
25844
- } });
25847
+ const target = { unsupported, ...supported };
25848
+ const module = new Proxy(target, {
25849
+ get(object, key) {
25850
+ if (key === "default") return module;
25851
+ return key in object ? object[key] : unsupported;
25852
+ }
25853
+ });
25854
+ return module;
25855
+ }
25856
+ function createNetModule() {
25857
+ const isIPv4 = (value) => {
25858
+ const parts = String(value).split(".");
25859
+ return parts.length === 4 && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255 && String(Number(part)) === part);
25860
+ };
25861
+ const isIPv6 = (value) => {
25862
+ const text2 = String(value);
25863
+ if (!text2.includes(":")) return false;
25864
+ const [head2, tail2] = [text2.slice(0, text2.lastIndexOf(":")), text2.slice(text2.lastIndexOf(":") + 1)];
25865
+ const normalised = isIPv4(tail2) ? `${head2}:0:0` : text2;
25866
+ const halves = normalised.split("::");
25867
+ if (halves.length > 2) return false;
25868
+ const groups = halves.map((half) => half === "" ? [] : half.split(":"));
25869
+ if (groups.some((half) => half.some((group) => !/^[0-9a-fA-F]{1,4}$/.test(group)))) return false;
25870
+ const count = groups.reduce((total, half) => total + half.length, 0);
25871
+ return halves.length === 2 ? count <= 7 : count === 8;
25872
+ };
25873
+ return createUnsupportedModule("net", {
25874
+ isIPv4,
25875
+ isIPv6,
25876
+ isIP: (value) => isIPv4(value) ? 4 : isIPv6(value) ? 6 : 0
25877
+ });
25845
25878
  }
25846
25879
  var stubNames = ["cluster", "dgram", "diagnostics_channel", "domain", "http2", "inspector", "net", "tls", "v8", "vm", "worker_threads"];
25847
25880
  var builtinNames2 = [
@@ -26250,6 +26283,77 @@ var MirroringVolume = class {
26250
26283
  };
26251
26284
  copy(this.root);
26252
26285
  }
26286
+ /**
26287
+ * Bring back what the mirrored engine wrote.
26288
+ *
26289
+ * The mirror exists because Rolldown's resolver reads through WASI rather
26290
+ * than through anything JavaScript can hand it — but Rolldown also *writes*
26291
+ * through WASI, so `vite build` leaves its output in the mirror and the
26292
+ * container sees an empty `dist/`. This is the return leg, and it is why the
26293
+ * mirror is no longer strictly one-way.
26294
+ *
26295
+ * Called when a process ends rather than on a timer: that is the moment a
26296
+ * build's output is complete, and a dev server — which serves from memory
26297
+ * and writes nothing — pays for it only once, at exit.
26298
+ */
26299
+ absorb() {
26300
+ const mirror = this.mirror;
26301
+ if (!mirror?.readdirSync || !mirror.readFileSync || !mirror.lstatSync) return;
26302
+ const visit = (path) => {
26303
+ if (basename(path) === "node_modules") return;
26304
+ let stat2;
26305
+ try {
26306
+ stat2 = mirror.lstatSync(path);
26307
+ } catch {
26308
+ return;
26309
+ }
26310
+ if (stat2.isSymbolicLink()) return;
26311
+ if (stat2.isDirectory()) {
26312
+ let names;
26313
+ try {
26314
+ names = mirror.readdirSync(path);
26315
+ } catch {
26316
+ return;
26317
+ }
26318
+ for (const name of names) visit(join(path, name));
26319
+ return;
26320
+ }
26321
+ let produced;
26322
+ try {
26323
+ produced = mirror.readFileSync(path);
26324
+ } catch {
26325
+ return;
26326
+ }
26327
+ try {
26328
+ const current = this.inner.readFileSync(path);
26329
+ if (sameBytes(current, produced)) return;
26330
+ } catch {
26331
+ }
26332
+ try {
26333
+ this.ensureDirectory(dirname(path));
26334
+ this.inner.writeFileSync(path, produced);
26335
+ } catch {
26336
+ }
26337
+ };
26338
+ visit(this.root);
26339
+ }
26340
+ /** `mkdir -p`, which the volume does not offer directly. */
26341
+ ensureDirectory(path) {
26342
+ const parts = clean(path).split("/").filter(Boolean);
26343
+ let current = "";
26344
+ for (const part of parts) {
26345
+ current += `/${part}`;
26346
+ try {
26347
+ if (this.inner.lstatSync(current).isDirectory()) continue;
26348
+ return;
26349
+ } catch {
26350
+ }
26351
+ try {
26352
+ this.inner.mkdirSync(current);
26353
+ } catch {
26354
+ }
26355
+ }
26356
+ }
26253
26357
  /** Is this path inside the mirrored subtree? */
26254
26358
  mirrored(path) {
26255
26359
  if (!this.mirror) return false;
@@ -26381,6 +26485,11 @@ var MirroringVolume = class {
26381
26485
  this.seed();
26382
26486
  }
26383
26487
  };
26488
+ function sameBytes(a, b) {
26489
+ if (a.length !== b.length) return false;
26490
+ for (let index = 0; index < a.length; index++) if (a[index] !== b[index]) return false;
26491
+ return true;
26492
+ }
26384
26493
 
26385
26494
  // src/runtime/host-esbuild.ts
26386
26495
  var ASYNC_API = ["transform", "build", "context", "formatMessages", "analyzeMetafile"];
@@ -26449,12 +26558,142 @@ function ensureProcessGlobal() {
26449
26558
  configurable: true
26450
26559
  });
26451
26560
  }
26561
+ var BINDING_PACKAGE = "@rolldown/binding-wasm32-wasi";
26562
+ var ASYNC_WORK_POOL_SIZE = 4;
26563
+ var WORKER_FILE = "rolldown-wasi-worker.js";
26564
+ async function loadNodeRolldownMemfsBinding() {
26565
+ const require2 = createRequire(import.meta.url);
26566
+ let packageDir;
26567
+ try {
26568
+ packageDir = dirname$1(require2.resolve(`${BINDING_PACKAGE}/package.json`));
26569
+ } catch {
26570
+ return null;
26571
+ }
26572
+ const fromBinding = createRequire(join$1(packageDir, "package.json"));
26573
+ let runtime;
26574
+ let fsModuleUrl;
26575
+ let createContext2;
26576
+ try {
26577
+ runtime = fromBinding("@napi-rs/wasm-runtime");
26578
+ fsModuleUrl = resolveSubpath(fromBinding, "@napi-rs/wasm-runtime", "./fs");
26579
+ ({ createContext: createContext2 } = fromBinding("@emnapi/runtime"));
26580
+ } catch {
26581
+ return null;
26582
+ }
26583
+ const { memfs } = await import(fsModuleUrl);
26584
+ const { Worker: Worker2 } = await import('worker_threads');
26585
+ const { fs: memoryFs, vol } = memfs();
26586
+ const wasi2 = new runtime.WASI({
26587
+ version: "preview1",
26588
+ fs: memoryFs,
26589
+ preopens: { "/": "/" }
26590
+ });
26591
+ const wasmFile = readFileSync(join$1(packageDir, "rolldown-binding.wasm32-wasi.wasm"));
26592
+ const sharedMemory = new WebAssembly.Memory({ initial: 16384, maximum: 65536, shared: true });
26593
+ const workerPoolSize = Math.max(2, cpuCount());
26594
+ const context = createContext2({ autoDestroy: false });
26595
+ context.suppressDestroy();
26596
+ const workerPath = resolveWorkerPath();
26597
+ if (!workerPath) return null;
26598
+ const runtimeAnchor = pathToFileURL$1(join$1(packageDir, "package.json")).href;
26599
+ const workers = /* @__PURE__ */ new Set();
26600
+ let started = false;
26601
+ const { napiModule } = await runtime.instantiateNapiModule(wasmFile, {
26602
+ context,
26603
+ asyncWorkPoolSize: ASYNC_WORK_POOL_SIZE,
26604
+ reuseWorker: { size: ASYNC_WORK_POOL_SIZE + workerPoolSize },
26605
+ plugins: [runtime.emnapiAsyncWorkPlugin, runtime.emnapiTSFNPlugin],
26606
+ wasi: wasi2,
26607
+ onCreateWorker() {
26608
+ const worker = new Worker2(workerPath, {
26609
+ env: process.env,
26610
+ workerData: { runtimeAnchor, fsModuleUrl },
26611
+ /*
26612
+ * Deliberately no `execArgv`.
26613
+ *
26614
+ * A worker inherits the parent's flags by default, and this worker is
26615
+ * a plain JavaScript file we ship — it needs none of them. Inheriting
26616
+ * them is actively harmful: under a host started with a module loader
26617
+ * (`tsx`, `ts-node`, `--import`), the worker's module resolution then
26618
+ * needs the main thread, which is at that moment blocked waiting for
26619
+ * this very worker to report itself ready. The result is a deadlock at
26620
+ * startup with no error, and it took a while to find.
26621
+ */
26622
+ execArgv: []
26623
+ });
26624
+ workers.add(worker);
26625
+ const serveFsRequest = runtime.createOnMessage(memoryFs);
26626
+ worker.on("message", (data) => serveFsRequest({ data }));
26627
+ worker.on("exit", () => workers.delete(worker));
26628
+ if (started) unreferenceWorker(worker);
26629
+ return worker;
26630
+ },
26631
+ overwriteImports(importObject) {
26632
+ importObject.env = {
26633
+ ...importObject.env,
26634
+ ...importObject.napi,
26635
+ ...importObject.emnapi,
26636
+ memory: sharedMemory
26637
+ };
26638
+ return importObject;
26639
+ },
26640
+ beforeInit({ instance }) {
26641
+ for (const name of Object.keys(instance.exports)) {
26642
+ if (name.startsWith("__napi_register__")) instance.exports[name]();
26643
+ }
26644
+ }
26645
+ });
26646
+ started = true;
26647
+ for (const worker of workers) unreferenceWorker(worker);
26648
+ return { ...napiModule.exports, __fs: memoryFs, __volume: vol };
26649
+ }
26650
+ function resolveWorkerPath() {
26651
+ const here = dirname$1(fileURLToPath$1(import.meta.url));
26652
+ const candidates = [
26653
+ join$1(here, WORKER_FILE),
26654
+ /* src/runtime → dist, for a checkout that has been built. */
26655
+ join$1(here, "..", "..", "dist", WORKER_FILE)
26656
+ ];
26657
+ return candidates.find((candidate) => existsSync(candidate)) ?? null;
26658
+ }
26659
+ function resolveSubpath(require2, packageName, subpath) {
26660
+ const packageDir = dirname$1(require2.resolve(packageName));
26661
+ const manifest = JSON.parse(readFileSync(join$1(packageDir, "package.json"), "utf8"));
26662
+ const entry = manifest?.exports?.[subpath];
26663
+ const target = typeof entry === "string" ? entry : entry?.import ?? entry?.default;
26664
+ if (typeof target !== "string") {
26665
+ throw new Error(`${packageName} does not export ${subpath}`);
26666
+ }
26667
+ return pathToFileURL$1(join$1(packageDir, target)).href;
26668
+ }
26669
+ function cpuCount() {
26670
+ try {
26671
+ return process.availableParallelism?.() ?? 4;
26672
+ } catch {
26673
+ return 4;
26674
+ }
26675
+ }
26676
+ function unreferenceWorker(worker) {
26677
+ for (const key of Object.getOwnPropertySymbols(worker)) {
26678
+ if (key.toString().includes("kPublicPort") || key.toString().includes("kHandle")) {
26679
+ const handle = worker[key];
26680
+ if (handle && typeof handle === "object") handle.ref = () => {
26681
+ };
26682
+ }
26683
+ }
26684
+ worker.unref();
26685
+ }
26452
26686
 
26453
26687
  // src/runtime/host-rolldown.ts
26454
26688
  var BUNDLER_HINT = "If this is a bundler pre-bundling the worker away, exclude the binding from dependency optimisation \u2014 in Vite:\n\n optimizeDeps: { exclude: ['@rolldown/binding-wasm32-wasi'] }";
26689
+ var isNodeHost = typeof process !== "undefined" && Boolean(process.versions?.node);
26455
26690
  async function loadHostRolldownBinding() {
26456
26691
  const reason = syncChannelUnavailableReason();
26457
26692
  if (reason) throw new Error(`SandboxedJs threaded WASM unavailable: ${reason}`);
26693
+ if (isNodeHost) {
26694
+ const binding = await loadNodeRolldownMemfsBinding();
26695
+ if (binding) return binding;
26696
+ }
26458
26697
  try {
26459
26698
  const loaded = await import('@rolldown/binding-wasm32-wasi');
26460
26699
  return "__fs" in loaded ? { ...loaded } : loaded.default ?? loaded;
@@ -26854,6 +27093,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
26854
27093
  core.cancelTimers();
26855
27094
  untrack();
26856
27095
  this.router.closeOwner(owner);
27096
+ this.volume.absorb();
26857
27097
  }
26858
27098
  });
26859
27099
  this.running.add(process2);