sandboxedjs 0.1.46 → 0.1.48

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.cjs CHANGED
@@ -20318,6 +20318,8 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
20318
20318
  fetcher;
20319
20319
  metadata = /* @__PURE__ */ new Map();
20320
20320
  tarballs = /* @__PURE__ */ new Map();
20321
+ /** Single-version manifests, for the fields the packument omits. */
20322
+ details = /* @__PURE__ */ new Map();
20321
20323
  forCwd(cwd) {
20322
20324
  return new _CleanPackageInstaller(this.volume, { ...this.options, cwd });
20323
20325
  }
@@ -20343,10 +20345,17 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
20343
20345
  const version = resolveVersion(metadata, range);
20344
20346
  const manifest = metadata.versions[version];
20345
20347
  if (!manifest) throw new Error(`No matching version found for ${name}@${range}`);
20346
- if (!supportsPlatform(manifest)) {
20347
- throw new Error(`${name}@${version} is not compatible with linux/x64/glibc`);
20348
- }
20349
20348
  const identity = `${name}@${version}`;
20349
+ if (!supportsPlatform(manifest)) throw new IncompatiblePlatform(identity, "");
20350
+ if (manifest.os?.length || manifest.cpu?.length) {
20351
+ const detail = await this.platformDetail(name, version);
20352
+ if (!platformListAllows(detail.libc, PLATFORM.libc)) {
20353
+ throw new IncompatiblePlatform(identity, ` (needs ${detail.libc?.join(", ")})`);
20354
+ }
20355
+ if (detail.main?.endsWith(".node")) {
20356
+ throw new IncompatiblePlatform(identity, " (native addon; this runtime cannot dlopen)");
20357
+ }
20358
+ }
20350
20359
  const target = join(modulesRoot, name);
20351
20360
  const installed2 = this.tryReadJson(join(target, "package.json"));
20352
20361
  if (installed2?.version === version) return installed2;
@@ -20368,11 +20377,44 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
20368
20377
  try {
20369
20378
  await this.installAt(dependency, dependencyRange, childRoot, options, nextAncestry);
20370
20379
  } catch (error) {
20380
+ if (error instanceof IncompatiblePlatform) continue;
20371
20381
  options.onProgress?.(`Skipped optional ${dependency}: ${error instanceof Error ? error.message : String(error)}`);
20372
20382
  }
20373
20383
  }
20374
20384
  return manifest;
20375
20385
  }
20386
+ /**
20387
+ * The `libc` and `main` fields, which the abbreviated packument leaves out.
20388
+ *
20389
+ * Fetched per version rather than as a full packument: the single-version
20390
+ * document is a few kilobytes, while the full one for a popular package runs
20391
+ * to megabytes. Only packages that already declare `os` or `cpu` ask for it,
20392
+ * so an ordinary install of pure-JavaScript dependencies makes no extra
20393
+ * requests at all — it is the prebuilt binaries, a handful per project, that
20394
+ * need the answer.
20395
+ *
20396
+ * A failure here is not fatal. Being unable to read `libc` puts the check
20397
+ * back where it was before this existed, which is worth strictly less than
20398
+ * being right and strictly more than refusing to install.
20399
+ */
20400
+ async platformDetail(name, version) {
20401
+ const key = `${name}@${version}`;
20402
+ let request = this.details.get(key);
20403
+ if (!request) {
20404
+ request = (async () => {
20405
+ try {
20406
+ const encoded = name.startsWith("@") ? name.replace("/", "%2F") : encodeURIComponent(name);
20407
+ const response = await this.fetcher(`${this.registry}/${encoded}/${version}`);
20408
+ if (!response.ok) return {};
20409
+ return await response.json();
20410
+ } catch {
20411
+ return {};
20412
+ }
20413
+ })();
20414
+ this.details.set(key, request);
20415
+ }
20416
+ return request;
20417
+ }
20376
20418
  async getMetadata(name) {
20377
20419
  let request = this.metadata.get(name);
20378
20420
  if (!request) {
@@ -20440,8 +20482,15 @@ var CleanPackageInstaller = class _CleanPackageInstaller {
20440
20482
  }
20441
20483
  }
20442
20484
  };
20485
+ var PLATFORM = { os: "linux", cpu: "x64", libc: "glibc" };
20486
+ var IncompatiblePlatform = class extends Error {
20487
+ constructor(identity, detail) {
20488
+ super(`${identity} is not compatible with ${PLATFORM.os}/${PLATFORM.cpu}/${PLATFORM.libc}${detail}`);
20489
+ this.name = "IncompatiblePlatform";
20490
+ }
20491
+ };
20443
20492
  function supportsPlatform(manifest) {
20444
- return !manifest.main?.endsWith(".node") && platformListAllows(manifest.os, "linux") && platformListAllows(manifest.cpu, "x64") && platformListAllows(manifest.libc, "glibc");
20493
+ return platformListAllows(manifest.os, PLATFORM.os) && platformListAllows(manifest.cpu, PLATFORM.cpu) && platformListAllows(manifest.libc, PLATFORM.libc);
20445
20494
  }
20446
20495
  function platformListAllows(values, current) {
20447
20496
  if (!values?.length) return true;
@@ -20533,7 +20582,9 @@ function safeTarget(destination, name) {
20533
20582
  return join(destination, clean2);
20534
20583
  }
20535
20584
  function stripPackageRoot(name) {
20536
- return name.replace(/^\.\//, "").replace(/^package\//, "").replace(/\/$/, "");
20585
+ const clean2 = name.replace(/^\.\//, "").replace(/\/$/, "");
20586
+ const slash = clean2.indexOf("/");
20587
+ return slash === -1 ? "" : clean2.slice(slash + 1);
20537
20588
  }
20538
20589
  function text(bytes2, start2, length) {
20539
20590
  return new TextDecoder().decode(bytes2.subarray(start2, start2 + length)).replace(/\0.*$/, "").trim();
@@ -26579,15 +26630,91 @@ var MirroringVolume = class {
26579
26630
  mirror.writeFileSync(path, this.inner.readFileSync(path));
26580
26631
  });
26581
26632
  }
26582
- // ── reads: straight through ───────────────────────────────────────────────
26633
+ // ── reads: straight through, then the mirror ──────────────────────────────
26634
+ /*
26635
+ * A file the bundler produced exists only in the mirror until something
26636
+ * asks for it here.
26637
+ *
26638
+ * {@link absorb} was the original answer and is not enough on its own. It
26639
+ * runs when a process ends — a dev server never ends — and it skips
26640
+ * `node_modules`, where Vite keeps the one output that a dev server depends
26641
+ * on: `node_modules/.vite/deps`. Between those two, the entire pre-bundled
26642
+ * dependency set stayed invisible to the container that had to serve it.
26643
+ *
26644
+ * The symptom was almost perfectly disguised. Vite writes `_metadata.json`
26645
+ * itself, through this volume, so the directory looked half-populated and
26646
+ * the metadata listed every dependency as optimized. Rolldown wrote the
26647
+ * actual bundles through WASI into the mirror, where nothing brought them
26648
+ * back. Vite then read a file that was not there — and reports a failed read
26649
+ * of an optimized dependency as `504 Outdated Optimize Dep`, which describes
26650
+ * a stale hash and sends everyone looking at hashes. The hash was never
26651
+ * wrong. The file was missing.
26652
+ *
26653
+ * Falling back on read fixes it wherever it happens, for any path, without
26654
+ * polling and without depending on how the engine chose to write.
26655
+ */
26583
26656
  readFileSync(path) {
26584
- return this.inner.readFileSync(path);
26657
+ try {
26658
+ return this.inner.readFileSync(path);
26659
+ } catch (error) {
26660
+ const produced = this.fromMirror(path);
26661
+ if (!produced) throw error;
26662
+ try {
26663
+ this.ensureDirectory(dirname(path));
26664
+ this.inner.writeFileSync(path, produced);
26665
+ } catch {
26666
+ }
26667
+ return produced;
26668
+ }
26585
26669
  }
26586
26670
  readdirSync(path) {
26587
- return this.inner.readdirSync(path);
26671
+ let here = [];
26672
+ let missing;
26673
+ try {
26674
+ here = this.inner.readdirSync(path);
26675
+ } catch (error) {
26676
+ missing = error;
26677
+ }
26678
+ const mirrored = this.mirrorNames(path);
26679
+ if (!mirrored) {
26680
+ if (missing) throw missing;
26681
+ return here;
26682
+ }
26683
+ return [.../* @__PURE__ */ new Set([...here, ...mirrored])].sort();
26588
26684
  }
26589
26685
  lstatSync(path) {
26590
- return this.inner.lstatSync(path);
26686
+ try {
26687
+ return this.inner.lstatSync(path);
26688
+ } catch (error) {
26689
+ if (this.fromMirror(path)) {
26690
+ try {
26691
+ this.readFileSync(path);
26692
+ return this.inner.lstatSync(path);
26693
+ } catch {
26694
+ }
26695
+ }
26696
+ throw error;
26697
+ }
26698
+ }
26699
+ /** The mirror's copy of a file, if it has one and we are allowed to look. */
26700
+ fromMirror(path) {
26701
+ const mirror = this.mirror;
26702
+ if (!mirror?.readFileSync || !mirror.lstatSync || !this.mirrored(path)) return void 0;
26703
+ try {
26704
+ if (!mirror.lstatSync(path).isDirectory()) return mirror.readFileSync(path);
26705
+ } catch {
26706
+ }
26707
+ return void 0;
26708
+ }
26709
+ /** The mirror's entries for a directory, for merging into a listing. */
26710
+ mirrorNames(path) {
26711
+ const mirror = this.mirror;
26712
+ if (!mirror?.readdirSync || !this.mirrored(path)) return void 0;
26713
+ try {
26714
+ return mirror.readdirSync(path);
26715
+ } catch {
26716
+ return void 0;
26717
+ }
26591
26718
  }
26592
26719
  readlinkSync(path) {
26593
26720
  return this.inner.readlinkSync(path);
@@ -26616,11 +26743,76 @@ var MirroringVolume = class {
26616
26743
  this.inner.unlinkSync(path);
26617
26744
  this.sync(path);
26618
26745
  }
26746
+ /**
26747
+ * Rename, carrying anything the mirrored engine put there.
26748
+ *
26749
+ * This is the operation the dependency optimizer is built on, and the one
26750
+ * that used to destroy its output. Vite bundles into a temporary directory
26751
+ * and renames it into place when it is complete — but only half of that
26752
+ * directory is ever on this volume. Vite writes `_metadata.json` and
26753
+ * `package.json` through here; Rolldown writes the actual bundles through
26754
+ * WASI into the mirror. Renaming moved the half that was here and then
26755
+ * `sync(from)` deleted the half that was not, because from this side the
26756
+ * source directory had simply gone.
26757
+ *
26758
+ * What survived was a `deps` directory holding metadata that described five
26759
+ * optimized dependencies and contained none of them — which Vite reports, on
26760
+ * the failed read, as `504 Outdated Optimize Dep`. Nothing in that message is
26761
+ * true, and following it leads to hashes rather than to a missing file.
26762
+ *
26763
+ * So the mirror's side of the move is collected first, and lands here as
26764
+ * part of the rename.
26765
+ */
26619
26766
  renameSync(from, to) {
26620
26767
  this.inner.renameSync(from, to);
26768
+ this.collect(from, to);
26621
26769
  this.sync(from);
26622
26770
  this.sync(to);
26623
26771
  }
26772
+ /**
26773
+ * Copy whatever the mirror holds under `from` into this volume under `to`.
26774
+ *
26775
+ * Deliberately not filtered the way {@link absorb} is: this walks one
26776
+ * directory that is being renamed, not the whole project, so the subtree is
26777
+ * small and `node_modules` is exactly where the interesting case lives.
26778
+ */
26779
+ collect(from, to) {
26780
+ const mirror = this.mirror;
26781
+ if (!mirror?.readdirSync || !mirror.readFileSync || !mirror.lstatSync) return;
26782
+ if (!this.mirrored(from)) return;
26783
+ const walk = (source, destination) => {
26784
+ let stat2;
26785
+ try {
26786
+ stat2 = mirror.lstatSync(source);
26787
+ } catch {
26788
+ return;
26789
+ }
26790
+ if (stat2.isSymbolicLink()) return;
26791
+ if (stat2.isDirectory()) {
26792
+ let names;
26793
+ try {
26794
+ names = mirror.readdirSync(source);
26795
+ } catch {
26796
+ return;
26797
+ }
26798
+ this.ensureDirectory(destination);
26799
+ for (const name of names) walk(join(source, name), join(destination, name));
26800
+ return;
26801
+ }
26802
+ try {
26803
+ this.inner.lstatSync(destination);
26804
+ return;
26805
+ } catch {
26806
+ }
26807
+ try {
26808
+ const bytes2 = mirror.readFileSync(source);
26809
+ this.ensureDirectory(dirname(destination));
26810
+ this.inner.writeFileSync(destination, bytes2);
26811
+ } catch {
26812
+ }
26813
+ };
26814
+ this.safely(() => walk(from, to));
26815
+ }
26624
26816
  symlinkSync(target, path) {
26625
26817
  this.inner.symlinkSync(target, path);
26626
26818
  this.sync(path);