sandboxedjs 0.1.49 → 0.1.50

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
@@ -19359,27 +19359,30 @@ 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
19366
  const ordered = (stable.length > 0 ? stable : candidates).reverse();
19367
19367
  let sawSourceOnly = false;
19368
+ const resolved = [];
19368
19369
  for (const version of ordered) {
19369
19370
  const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
19370
19371
  const wheel = pickWheel(files);
19371
19372
  if (wheel) {
19372
- return {
19373
+ resolved.push({
19373
19374
  name: requirement.name,
19374
19375
  version,
19375
19376
  url: wheel.url,
19376
19377
  filename: wheel.filename,
19377
19378
  sha256: wheel.digests?.sha256 ?? "",
19378
19379
  requires: []
19379
- };
19380
+ });
19381
+ continue;
19380
19382
  }
19381
19383
  if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
19382
19384
  }
19385
+ if (resolved.length > 0) return resolved;
19383
19386
  if (ordered.length === 0) {
19384
19387
  throw new Error(
19385
19388
  `no version of ${requirement.name} matches ${describe(requirement)}`
@@ -19407,6 +19410,7 @@ function splitOnce(text2, separator) {
19407
19410
 
19408
19411
  // src/runtime/python/install.ts
19409
19412
  var SITE_PACKAGES = "/usr/lib/python3.13/site-packages";
19413
+ var SCRIPTS = "/usr/local/bin";
19410
19414
  function markerEnvironment(pythonVersion) {
19411
19415
  const [major = "3", minor = "13"] = pythonVersion.split(".");
19412
19416
  return {
@@ -19427,18 +19431,33 @@ async function installRequirements(options) {
19427
19431
  const requirement = parseRequirement(text2);
19428
19432
  if (requirement) queue.push({ requirement, extras: requirement.extras });
19429
19433
  }
19430
- const chosen = /* @__PURE__ */ new Map();
19431
- const seen = /* @__PURE__ */ new Set();
19432
- while (queue.length > 0) {
19433
- const { requirement, extras } = queue.shift();
19434
+ const archives = /* @__PURE__ */ new Map();
19435
+ const unavailable2 = /* @__PURE__ */ new Set();
19436
+ const candidateCache = /* @__PURE__ */ new Map();
19437
+ const solve = async (pending, state) => {
19438
+ if (pending.length === 0) return state;
19439
+ const [first, ...tail2] = pending;
19440
+ let { requirement, extras } = first;
19441
+ const rest = [];
19442
+ const mergedSpecifiers = [...requirement.specifiers];
19443
+ const mergedExtras = new Set(extras);
19444
+ for (const item of tail2) {
19445
+ if (item.requirement.name === requirement.name && markerApplies(item.requirement.marker, environment, item.extras)) {
19446
+ mergedSpecifiers.push(...item.requirement.specifiers);
19447
+ for (const extra of item.extras) mergedExtras.add(extra);
19448
+ } else {
19449
+ rest.push(item);
19450
+ }
19451
+ }
19452
+ requirement = { ...requirement, specifiers: mergedSpecifiers };
19453
+ extras = [...mergedExtras];
19434
19454
  if (!markerApplies(requirement.marker, environment, extras)) {
19435
- report.skipped.push({ name: requirement.name, marker: requirement.marker ?? "" });
19436
- continue;
19455
+ return solve(rest, {
19456
+ ...state,
19457
+ skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
19458
+ });
19437
19459
  }
19438
- const key = `${requirement.name}[${extras.join(",")}]`;
19439
- if (seen.has(key)) continue;
19440
- seen.add(key);
19441
- const already = chosen.get(requirement.name);
19460
+ const already = state.chosen.get(requirement.name);
19442
19461
  if (already) {
19443
19462
  const satisfied = requirement.specifiers.every(
19444
19463
  (s) => compareVersions(already.version, s.version, s.op)
@@ -19448,38 +19467,143 @@ async function installRequirements(options) {
19448
19467
  `${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
19449
19468
  );
19450
19469
  }
19451
- continue;
19470
+ return solve(rest, state);
19452
19471
  }
19453
19472
  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
- }
19473
+ if (unavailable2.has(requirement.name)) {
19474
+ throw new Error(`${requirement.name} has no wheel this runtime can use`);
19475
+ }
19476
+ const candidateKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).join(",")}`;
19477
+ let candidatePromise = candidateCache.get(candidateKey);
19478
+ if (!candidatePromise) {
19479
+ candidatePromise = resolvePackageCandidates(options.client, requirement);
19480
+ candidateCache.set(candidateKey, candidatePromise);
19481
+ }
19482
+ let candidates;
19483
+ try {
19484
+ candidates = await candidatePromise;
19485
+ } catch (error) {
19486
+ const anyKey = `${requirement.name}:`;
19487
+ let anyPromise = candidateCache.get(anyKey);
19488
+ if (!anyPromise) {
19489
+ anyPromise = resolvePackageCandidates(
19490
+ options.client,
19491
+ { ...requirement, specifiers: [] });
19492
+ candidateCache.set(anyKey, anyPromise);
19493
+ }
19494
+ try {
19495
+ await anyPromise;
19496
+ } catch {
19497
+ unavailable2.add(requirement.name);
19498
+ }
19499
+ throw error;
19500
+ }
19501
+ let failure2;
19502
+ for (const resolved of candidates) {
19503
+ try {
19504
+ let entries = archives.get(resolved.url);
19505
+ if (!entries) {
19506
+ options.progress.downloading(resolved.name, resolved.version);
19507
+ entries = readZip(await options.client.bytes(resolved.url));
19508
+ archives.set(resolved.url, entries);
19509
+ }
19510
+ const dependencies = [];
19511
+ const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19512
+ if (metadata) {
19513
+ for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19514
+ if (!line.startsWith("Requires-Dist:")) continue;
19515
+ const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19516
+ if (dependency && markerApplies(dependency.marker, environment, extras)) {
19517
+ dependencies.push({ requirement: dependency, extras: dependency.extras });
19518
+ }
19519
+ }
19520
+ }
19521
+ const chosen = new Map(state.chosen);
19522
+ chosen.set(requirement.name, resolved);
19523
+ return await solve([...dependencies, ...rest], {
19524
+ chosen,
19525
+ staged: [...state.staged, ...stageWheel(entries, resolved)],
19526
+ installed: [...state.installed, { name: resolved.name, version: resolved.version }],
19527
+ skipped: state.skipped
19528
+ });
19529
+ } catch (error) {
19530
+ failure2 = error;
19531
+ }
19532
+ }
19533
+ throw failure2 instanceof Error ? failure2 : new Error(`could not resolve ${requirement.name}`);
19534
+ };
19535
+ const solved = await solve(queue, {
19536
+ chosen: /* @__PURE__ */ new Map(),
19537
+ staged: [],
19538
+ installed: [],
19539
+ skipped: []
19540
+ });
19541
+ report.installed = solved.installed;
19542
+ report.skipped = solved.skipped;
19543
+ commit(options.vfs, options.cred, solved.staged);
19470
19544
  return report;
19471
19545
  }
19472
- function unpack(vfs, cred, entries, resolved) {
19546
+ function stageWheel(entries, resolved) {
19473
19547
  const staged = [];
19474
19548
  for (const entry of entries) {
19475
19549
  if (entry.isDirectory) continue;
19550
+ const dataPath = /^[^/]+\.data\/(purelib|platlib|scripts)\/(.+)$/.exec(entry.name);
19551
+ if (dataPath) {
19552
+ const [, scheme, relative2] = dataPath;
19553
+ staged.push({
19554
+ path: scheme === "scripts" ? `${SCRIPTS}/${relative2}` : `${SITE_PACKAGES}/${relative2}`,
19555
+ data: entry.data(),
19556
+ ...scheme === "scripts" ? { mode: 493 } : {}
19557
+ });
19558
+ continue;
19559
+ }
19476
19560
  if (/^[^/]+\.data\//.test(entry.name)) continue;
19477
19561
  staged.push({ path: `${SITE_PACKAGES}/${entry.name}`, data: entry.data() });
19478
19562
  }
19563
+ const entryPoints = entries.find((entry) => /\.dist-info\/entry_points\.txt$/.test(entry.name));
19564
+ if (entryPoints) {
19565
+ for (const [name, target] of consoleScripts(new TextDecoder().decode(entryPoints.data()))) {
19566
+ staged.push({ path: `${SCRIPTS}/${name}`, data: new TextEncoder().encode(consoleLauncher(target)), mode: 493 });
19567
+ }
19568
+ }
19569
+ return staged;
19570
+ }
19571
+ function commit(vfs, cred, staged) {
19479
19572
  const directories = /* @__PURE__ */ new Set();
19480
19573
  for (const file3 of staged) directories.add(file3.path.slice(0, file3.path.lastIndexOf("/")));
19481
19574
  for (const directory2 of directories) vfs.mkdir(directory2, { recursive: true, cred });
19482
- for (const file3 of staged) vfs.writeFile(file3.path, file3.data, { cred });
19575
+ for (const file3 of staged) {
19576
+ vfs.writeFile(file3.path, file3.data, { cred, ...file3.mode === void 0 ? {} : { mode: file3.mode } });
19577
+ if (file3.mode !== void 0) vfs.chmod(file3.path, file3.mode, cred);
19578
+ }
19579
+ }
19580
+ function consoleScripts(text2) {
19581
+ const result = [];
19582
+ let section = "";
19583
+ for (const raw of text2.split(/\r?\n/)) {
19584
+ const line = raw.trim();
19585
+ const heading = /^\[([^\]]+)\]$/.exec(line);
19586
+ if (heading) {
19587
+ section = heading[1];
19588
+ continue;
19589
+ }
19590
+ if (section !== "console_scripts" || line === "" || line.startsWith("#")) continue;
19591
+ const assignment = /^([^=\s]+)\s*=\s*([^\s;]+)(?:\s*;.*)?$/.exec(line);
19592
+ if (assignment) result.push([assignment[1], assignment[2].replace(/\s*\[.*\]$/, "")]);
19593
+ }
19594
+ return result;
19595
+ }
19596
+ function consoleLauncher(target) {
19597
+ const [module, attribute = ""] = target.split(":", 2);
19598
+ return `#!/usr/bin/python3
19599
+ import sys
19600
+ from importlib import import_module
19601
+ entry = import_module(${JSON.stringify(module)})
19602
+ for part in ${JSON.stringify(attribute)}.split('.'):
19603
+ if part:
19604
+ entry = getattr(entry, part)
19605
+ raise SystemExit(entry())
19606
+ `;
19483
19607
  }
19484
19608
 
19485
19609
  // src/runtime/python/backend.ts
@@ -19487,7 +19611,11 @@ function withSitePackages(env2) {
19487
19611
  const existing = env2.PYTHONPATH;
19488
19612
  return {
19489
19613
  ...env2,
19490
- PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES
19614
+ PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES,
19615
+ /* Installed packages live on a host-mounted VFS. Rewriting hundreds of
19616
+ * cache files on every short-lived WASM process is wasted work and can
19617
+ * exhaust the synchronous host-call bridge during large import graphs. */
19618
+ PYTHONDONTWRITEBYTECODE: env2.PYTHONDONTWRITEBYTECODE ?? "1"
19491
19619
  };
19492
19620
  }
19493
19621
  var INTERPRETER_OWNED = /* @__PURE__ */ new Set(["lib", "dev", "proc", "usr"]);
@@ -19577,6 +19705,12 @@ function containerMounts(ctx) {
19577
19705
  }
19578
19706
  } catch {
19579
19707
  }
19708
+ try {
19709
+ if (ctx.vfs.stat(SCRIPTS, { cred: ctx.cred }).isDirectory()) {
19710
+ mounts.push({ guest: SCRIPTS, container: SCRIPTS });
19711
+ }
19712
+ } catch {
19713
+ }
19580
19714
  return mounts;
19581
19715
  }
19582
19716
 
@@ -19630,16 +19764,30 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
19630
19764
  "outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
19631
19765
  );
19632
19766
  }
19767
+ const jsonCache = /* @__PURE__ */ new Map();
19768
+ const bytesCache = /* @__PURE__ */ new Map();
19633
19769
  const client = {
19634
19770
  async json(url) {
19635
- const response = await fetch(url);
19636
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19637
- return response.json();
19771
+ let pending = jsonCache.get(url);
19772
+ if (!pending) {
19773
+ pending = fetch(url).then((response) => {
19774
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19775
+ return response.json();
19776
+ });
19777
+ jsonCache.set(url, pending);
19778
+ }
19779
+ return pending;
19638
19780
  },
19639
19781
  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());
19782
+ let pending = bytesCache.get(url);
19783
+ if (!pending) {
19784
+ pending = fetch(url).then(async (response) => {
19785
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19786
+ return new Uint8Array(await response.arrayBuffer());
19787
+ });
19788
+ bytesCache.set(url, pending);
19789
+ }
19790
+ return pending;
19643
19791
  }
19644
19792
  };
19645
19793
  try {