sandboxedjs 0.1.49 → 0.1.51

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.js CHANGED
@@ -19090,7 +19090,7 @@ async function startNodeWorker(url, workerData) {
19090
19090
  /* webpackIgnore: true */
19091
19091
  specifier
19092
19092
  );
19093
- const worker = new NodeWorker(url, { workerData });
19093
+ const worker = new NodeWorker(url, { workerData, execArgv: [] });
19094
19094
  return {
19095
19095
  postMessage: (message) => worker.postMessage(message),
19096
19096
  onMessage: (listener) => worker.on("message", listener),
@@ -19359,27 +19359,40 @@ function compareRelease(left, right) {
19359
19359
  function isPreRelease(version) {
19360
19360
  return /(a|b|rc|dev|post)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
19361
19361
  }
19362
- async function resolvePackage(client, requirement, environment) {
19362
+ async function resolvePackageCandidates(client, requirement, environment) {
19363
19363
  const index = await client.json(`https://pypi.org/pypi/${requirement.name}/json`);
19364
19364
  const candidates = Object.keys(index.releases).filter((version) => requirement.specifiers.every((s) => compareVersions(version, s.version, s.op))).sort((a, b) => compareRelease(releaseOf(a), releaseOf(b)));
19365
19365
  const stable = candidates.filter((version) => !isPreRelease(version));
19366
- const ordered = (stable.length > 0 ? stable : candidates).reverse();
19366
+ const newestFirst = (stable.length > 0 ? stable : candidates).reverse();
19367
+ const probeIndexes = [
19368
+ 0,
19369
+ Math.floor(newestFirst.length / 2),
19370
+ Math.floor(newestFirst.length / 4),
19371
+ Math.floor(newestFirst.length * 3 / 4),
19372
+ newestFirst.length - 1
19373
+ ];
19374
+ const probes = [...new Set(probeIndexes)].map((index2) => newestFirst[index2]).filter((v) => Boolean(v));
19375
+ const probeVersions = new Set(probes);
19376
+ const ordered = [...probes, ...newestFirst.filter((version) => !probeVersions.has(version))];
19367
19377
  let sawSourceOnly = false;
19378
+ const resolved = [];
19368
19379
  for (const version of ordered) {
19369
19380
  const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
19370
19381
  const wheel = pickWheel(files);
19371
19382
  if (wheel) {
19372
- return {
19383
+ resolved.push({
19373
19384
  name: requirement.name,
19374
19385
  version,
19375
19386
  url: wheel.url,
19376
19387
  filename: wheel.filename,
19377
19388
  sha256: wheel.digests?.sha256 ?? "",
19378
19389
  requires: []
19379
- };
19390
+ });
19391
+ continue;
19380
19392
  }
19381
19393
  if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
19382
19394
  }
19395
+ if (resolved.length > 0) return resolved;
19383
19396
  if (ordered.length === 0) {
19384
19397
  throw new Error(
19385
19398
  `no version of ${requirement.name} matches ${describe(requirement)}`
@@ -19407,6 +19420,7 @@ function splitOnce(text2, separator) {
19407
19420
 
19408
19421
  // src/runtime/python/install.ts
19409
19422
  var SITE_PACKAGES = "/usr/lib/python3.13/site-packages";
19423
+ var SCRIPTS = "/usr/local/bin";
19410
19424
  function markerEnvironment(pythonVersion) {
19411
19425
  const [major = "3", minor = "13"] = pythonVersion.split(".");
19412
19426
  return {
@@ -19427,18 +19441,39 @@ async function installRequirements(options) {
19427
19441
  const requirement = parseRequirement(text2);
19428
19442
  if (requirement) queue.push({ requirement, extras: requirement.extras });
19429
19443
  }
19430
- const chosen = /* @__PURE__ */ new Map();
19431
- const seen = /* @__PURE__ */ new Set();
19432
- while (queue.length > 0) {
19433
- const { requirement, extras } = queue.shift();
19444
+ const archives = /* @__PURE__ */ new Map();
19445
+ const unavailable2 = /* @__PURE__ */ new Set();
19446
+ const unsatisfiable = /* @__PURE__ */ new Map();
19447
+ const candidateCache = /* @__PURE__ */ new Map();
19448
+ let candidateAttempts = 0;
19449
+ const MAX_CANDIDATE_ATTEMPTS = 96;
19450
+ const solve = async (pending, state) => {
19451
+ if (pending.length === 0) return state;
19452
+ const [first, ...tail2] = pending;
19453
+ let { requirement, extras } = first;
19454
+ const rest = [];
19455
+ const mergedSpecifiers = [...requirement.specifiers];
19456
+ const mergedExtras = new Set(extras);
19457
+ for (const item of tail2) {
19458
+ if (item.requirement.name === requirement.name && markerApplies(item.requirement.marker, environment, item.extras)) {
19459
+ mergedSpecifiers.push(...item.requirement.specifiers);
19460
+ for (const extra of item.extras) mergedExtras.add(extra);
19461
+ } else {
19462
+ rest.push(item);
19463
+ }
19464
+ }
19465
+ requirement = { ...requirement, specifiers: mergedSpecifiers };
19466
+ extras = [...mergedExtras];
19434
19467
  if (!markerApplies(requirement.marker, environment, extras)) {
19435
- report.skipped.push({ name: requirement.name, marker: requirement.marker ?? "" });
19436
- continue;
19468
+ return solve(rest, {
19469
+ ...state,
19470
+ skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
19471
+ });
19437
19472
  }
19438
- const key = `${requirement.name}[${extras.join(",")}]`;
19439
- if (seen.has(key)) continue;
19440
- seen.add(key);
19441
- const already = chosen.get(requirement.name);
19473
+ const solveKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).sort().join(",")}:${[...extras].sort().join(",")}`;
19474
+ const knownFailure = unsatisfiable.get(solveKey);
19475
+ if (knownFailure) throw knownFailure;
19476
+ const already = state.chosen.get(requirement.name);
19442
19477
  if (already) {
19443
19478
  const satisfied = requirement.specifiers.every(
19444
19479
  (s) => compareVersions(already.version, s.version, s.op)
@@ -19448,38 +19483,152 @@ async function installRequirements(options) {
19448
19483
  `${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
19449
19484
  );
19450
19485
  }
19451
- continue;
19486
+ return solve(rest, state);
19452
19487
  }
19453
19488
  options.progress.collecting(requirement.name);
19454
- const resolved = await resolvePackage(options.client, requirement);
19455
- chosen.set(requirement.name, resolved);
19456
- options.progress.downloading(resolved.name, resolved.version);
19457
- const archive = await options.client.bytes(resolved.url);
19458
- const entries = readZip(archive);
19459
- const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19460
- if (metadata) {
19461
- for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19462
- if (!line.startsWith("Requires-Dist:")) continue;
19463
- const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19464
- if (dependency) queue.push({ requirement: dependency, extras: [] });
19465
- }
19466
- }
19467
- unpack(options.vfs, options.cred, entries);
19468
- report.installed.push({ name: resolved.name, version: resolved.version });
19469
- }
19489
+ if (unavailable2.has(requirement.name)) {
19490
+ throw new Error(`${requirement.name} has no wheel this runtime can use`);
19491
+ }
19492
+ const candidateKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).join(",")}`;
19493
+ let candidatePromise = candidateCache.get(candidateKey);
19494
+ if (!candidatePromise) {
19495
+ candidatePromise = resolvePackageCandidates(options.client, requirement);
19496
+ candidateCache.set(candidateKey, candidatePromise);
19497
+ }
19498
+ let candidates;
19499
+ try {
19500
+ candidates = await candidatePromise;
19501
+ } catch (error) {
19502
+ const anyKey = `${requirement.name}:`;
19503
+ let anyPromise = candidateCache.get(anyKey);
19504
+ if (!anyPromise) {
19505
+ anyPromise = resolvePackageCandidates(
19506
+ options.client,
19507
+ { ...requirement, specifiers: [] });
19508
+ candidateCache.set(anyKey, anyPromise);
19509
+ }
19510
+ try {
19511
+ await anyPromise;
19512
+ } catch {
19513
+ unavailable2.add(requirement.name);
19514
+ }
19515
+ throw error;
19516
+ }
19517
+ let failure2;
19518
+ for (const resolved of candidates) {
19519
+ try {
19520
+ candidateAttempts += 1;
19521
+ if (candidateAttempts > MAX_CANDIDATE_ATTEMPTS) {
19522
+ throw new Error(
19523
+ "dependency resolution exceeded 96 wheel candidates; add version constraints for packages whose newest generation requires unavailable native WASM extensions"
19524
+ );
19525
+ }
19526
+ let entries = archives.get(resolved.url);
19527
+ if (!entries) {
19528
+ options.progress.downloading(resolved.name, resolved.version);
19529
+ entries = readZip(await options.client.bytes(resolved.url));
19530
+ archives.set(resolved.url, entries);
19531
+ }
19532
+ const dependencies = [];
19533
+ const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19534
+ if (metadata) {
19535
+ for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19536
+ if (!line.startsWith("Requires-Dist:")) continue;
19537
+ const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19538
+ if (dependency && markerApplies(dependency.marker, environment, extras)) {
19539
+ dependencies.push({ requirement: dependency, extras: dependency.extras });
19540
+ }
19541
+ }
19542
+ }
19543
+ const chosen = new Map(state.chosen);
19544
+ chosen.set(requirement.name, resolved);
19545
+ return await solve([...dependencies, ...rest], {
19546
+ chosen,
19547
+ staged: [...state.staged, ...stageWheel(entries, resolved)],
19548
+ installed: [...state.installed, { name: resolved.name, version: resolved.version }],
19549
+ skipped: state.skipped
19550
+ });
19551
+ } catch (error) {
19552
+ if (error?.message?.startsWith("dependency resolution exceeded")) throw error;
19553
+ failure2 = error;
19554
+ }
19555
+ }
19556
+ const resolutionError = failure2 instanceof Error ? failure2 : new Error(`could not resolve ${requirement.name}`);
19557
+ unsatisfiable.set(solveKey, resolutionError);
19558
+ throw resolutionError;
19559
+ };
19560
+ const solved = await solve(queue, {
19561
+ chosen: /* @__PURE__ */ new Map(),
19562
+ staged: [],
19563
+ installed: [],
19564
+ skipped: []
19565
+ });
19566
+ report.installed = solved.installed;
19567
+ report.skipped = solved.skipped;
19568
+ commit(options.vfs, options.cred, solved.staged);
19470
19569
  return report;
19471
19570
  }
19472
- function unpack(vfs, cred, entries, resolved) {
19571
+ function stageWheel(entries, resolved) {
19473
19572
  const staged = [];
19474
19573
  for (const entry of entries) {
19475
19574
  if (entry.isDirectory) continue;
19575
+ const dataPath = /^[^/]+\.data\/(purelib|platlib|scripts)\/(.+)$/.exec(entry.name);
19576
+ if (dataPath) {
19577
+ const [, scheme, relative2] = dataPath;
19578
+ staged.push({
19579
+ path: scheme === "scripts" ? `${SCRIPTS}/${relative2}` : `${SITE_PACKAGES}/${relative2}`,
19580
+ data: entry.data(),
19581
+ ...scheme === "scripts" ? { mode: 493 } : {}
19582
+ });
19583
+ continue;
19584
+ }
19476
19585
  if (/^[^/]+\.data\//.test(entry.name)) continue;
19477
19586
  staged.push({ path: `${SITE_PACKAGES}/${entry.name}`, data: entry.data() });
19478
19587
  }
19588
+ const entryPoints = entries.find((entry) => /\.dist-info\/entry_points\.txt$/.test(entry.name));
19589
+ if (entryPoints) {
19590
+ for (const [name, target] of consoleScripts(new TextDecoder().decode(entryPoints.data()))) {
19591
+ staged.push({ path: `${SCRIPTS}/${name}`, data: new TextEncoder().encode(consoleLauncher(target)), mode: 493 });
19592
+ }
19593
+ }
19594
+ return staged;
19595
+ }
19596
+ function commit(vfs, cred, staged) {
19479
19597
  const directories = /* @__PURE__ */ new Set();
19480
19598
  for (const file3 of staged) directories.add(file3.path.slice(0, file3.path.lastIndexOf("/")));
19481
19599
  for (const directory2 of directories) vfs.mkdir(directory2, { recursive: true, cred });
19482
- for (const file3 of staged) vfs.writeFile(file3.path, file3.data, { cred });
19600
+ for (const file3 of staged) {
19601
+ vfs.writeFile(file3.path, file3.data, { cred, ...file3.mode === void 0 ? {} : { mode: file3.mode } });
19602
+ if (file3.mode !== void 0) vfs.chmod(file3.path, file3.mode, cred);
19603
+ }
19604
+ }
19605
+ function consoleScripts(text2) {
19606
+ const result = [];
19607
+ let section = "";
19608
+ for (const raw of text2.split(/\r?\n/)) {
19609
+ const line = raw.trim();
19610
+ const heading = /^\[([^\]]+)\]$/.exec(line);
19611
+ if (heading) {
19612
+ section = heading[1];
19613
+ continue;
19614
+ }
19615
+ if (section !== "console_scripts" || line === "" || line.startsWith("#")) continue;
19616
+ const assignment = /^([^=\s]+)\s*=\s*([^\s;]+)(?:\s*;.*)?$/.exec(line);
19617
+ if (assignment) result.push([assignment[1], assignment[2].replace(/\s*\[.*\]$/, "")]);
19618
+ }
19619
+ return result;
19620
+ }
19621
+ function consoleLauncher(target) {
19622
+ const [module, attribute = ""] = target.split(":", 2);
19623
+ return `#!/usr/bin/python3
19624
+ import sys
19625
+ from importlib import import_module
19626
+ entry = import_module(${JSON.stringify(module)})
19627
+ for part in ${JSON.stringify(attribute)}.split('.'):
19628
+ if part:
19629
+ entry = getattr(entry, part)
19630
+ raise SystemExit(entry())
19631
+ `;
19483
19632
  }
19484
19633
 
19485
19634
  // src/runtime/python/backend.ts
@@ -19487,7 +19636,11 @@ function withSitePackages(env2) {
19487
19636
  const existing = env2.PYTHONPATH;
19488
19637
  return {
19489
19638
  ...env2,
19490
- PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES
19639
+ PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES,
19640
+ /* Installed packages live on a host-mounted VFS. Rewriting hundreds of
19641
+ * cache files on every short-lived WASM process is wasted work and can
19642
+ * exhaust the synchronous host-call bridge during large import graphs. */
19643
+ PYTHONDONTWRITEBYTECODE: env2.PYTHONDONTWRITEBYTECODE ?? "1"
19491
19644
  };
19492
19645
  }
19493
19646
  var INTERPRETER_OWNED = /* @__PURE__ */ new Set(["lib", "dev", "proc", "usr"]);
@@ -19577,6 +19730,12 @@ function containerMounts(ctx) {
19577
19730
  }
19578
19731
  } catch {
19579
19732
  }
19733
+ try {
19734
+ if (ctx.vfs.stat(SCRIPTS, { cred: ctx.cred }).isDirectory()) {
19735
+ mounts.push({ guest: SCRIPTS, container: SCRIPTS });
19736
+ }
19737
+ } catch {
19738
+ }
19580
19739
  return mounts;
19581
19740
  }
19582
19741
 
@@ -19630,16 +19789,30 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
19630
19789
  "outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
19631
19790
  );
19632
19791
  }
19792
+ const jsonCache = /* @__PURE__ */ new Map();
19793
+ const bytesCache = /* @__PURE__ */ new Map();
19633
19794
  const client = {
19634
19795
  async json(url) {
19635
- const response = await fetch(url);
19636
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19637
- return response.json();
19796
+ let pending = jsonCache.get(url);
19797
+ if (!pending) {
19798
+ pending = fetch(url).then((response) => {
19799
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19800
+ return response.json();
19801
+ });
19802
+ jsonCache.set(url, pending);
19803
+ }
19804
+ return pending;
19638
19805
  },
19639
19806
  async bytes(url) {
19640
- const response = await fetch(url);
19641
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19642
- return new Uint8Array(await response.arrayBuffer());
19807
+ let pending = bytesCache.get(url);
19808
+ if (!pending) {
19809
+ pending = fetch(url).then(async (response) => {
19810
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19811
+ return new Uint8Array(await response.arrayBuffer());
19812
+ });
19813
+ bytesCache.set(url, pending);
19814
+ }
19815
+ return pending;
19643
19816
  }
19644
19817
  };
19645
19818
  try {