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.cjs CHANGED
@@ -19376,27 +19376,30 @@ function compareRelease(left, right) {
19376
19376
  function isPreRelease(version) {
19377
19377
  return /(a|b|rc|dev|post)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
19378
19378
  }
19379
- async function resolvePackage(client, requirement, environment) {
19379
+ async function resolvePackageCandidates(client, requirement, environment) {
19380
19380
  const index = await client.json(`https://pypi.org/pypi/${requirement.name}/json`);
19381
19381
  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)));
19382
19382
  const stable = candidates.filter((version) => !isPreRelease(version));
19383
19383
  const ordered = (stable.length > 0 ? stable : candidates).reverse();
19384
19384
  let sawSourceOnly = false;
19385
+ const resolved = [];
19385
19386
  for (const version of ordered) {
19386
19387
  const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
19387
19388
  const wheel = pickWheel(files);
19388
19389
  if (wheel) {
19389
- return {
19390
+ resolved.push({
19390
19391
  name: requirement.name,
19391
19392
  version,
19392
19393
  url: wheel.url,
19393
19394
  filename: wheel.filename,
19394
19395
  sha256: wheel.digests?.sha256 ?? "",
19395
19396
  requires: []
19396
- };
19397
+ });
19398
+ continue;
19397
19399
  }
19398
19400
  if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
19399
19401
  }
19402
+ if (resolved.length > 0) return resolved;
19400
19403
  if (ordered.length === 0) {
19401
19404
  throw new Error(
19402
19405
  `no version of ${requirement.name} matches ${describe(requirement)}`
@@ -19424,6 +19427,7 @@ function splitOnce(text2, separator) {
19424
19427
 
19425
19428
  // src/runtime/python/install.ts
19426
19429
  var SITE_PACKAGES = "/usr/lib/python3.13/site-packages";
19430
+ var SCRIPTS = "/usr/local/bin";
19427
19431
  function markerEnvironment(pythonVersion) {
19428
19432
  const [major = "3", minor = "13"] = pythonVersion.split(".");
19429
19433
  return {
@@ -19444,18 +19448,33 @@ async function installRequirements(options) {
19444
19448
  const requirement = parseRequirement(text2);
19445
19449
  if (requirement) queue.push({ requirement, extras: requirement.extras });
19446
19450
  }
19447
- const chosen = /* @__PURE__ */ new Map();
19448
- const seen = /* @__PURE__ */ new Set();
19449
- while (queue.length > 0) {
19450
- const { requirement, extras } = queue.shift();
19451
+ const archives = /* @__PURE__ */ new Map();
19452
+ const unavailable2 = /* @__PURE__ */ new Set();
19453
+ const candidateCache = /* @__PURE__ */ new Map();
19454
+ const solve = async (pending, state) => {
19455
+ if (pending.length === 0) return state;
19456
+ const [first, ...tail2] = pending;
19457
+ let { requirement, extras } = first;
19458
+ const rest = [];
19459
+ const mergedSpecifiers = [...requirement.specifiers];
19460
+ const mergedExtras = new Set(extras);
19461
+ for (const item of tail2) {
19462
+ if (item.requirement.name === requirement.name && markerApplies(item.requirement.marker, environment, item.extras)) {
19463
+ mergedSpecifiers.push(...item.requirement.specifiers);
19464
+ for (const extra of item.extras) mergedExtras.add(extra);
19465
+ } else {
19466
+ rest.push(item);
19467
+ }
19468
+ }
19469
+ requirement = { ...requirement, specifiers: mergedSpecifiers };
19470
+ extras = [...mergedExtras];
19451
19471
  if (!markerApplies(requirement.marker, environment, extras)) {
19452
- report.skipped.push({ name: requirement.name, marker: requirement.marker ?? "" });
19453
- continue;
19472
+ return solve(rest, {
19473
+ ...state,
19474
+ skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
19475
+ });
19454
19476
  }
19455
- const key = `${requirement.name}[${extras.join(",")}]`;
19456
- if (seen.has(key)) continue;
19457
- seen.add(key);
19458
- const already = chosen.get(requirement.name);
19477
+ const already = state.chosen.get(requirement.name);
19459
19478
  if (already) {
19460
19479
  const satisfied = requirement.specifiers.every(
19461
19480
  (s) => compareVersions(already.version, s.version, s.op)
@@ -19465,38 +19484,143 @@ async function installRequirements(options) {
19465
19484
  `${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
19466
19485
  );
19467
19486
  }
19468
- continue;
19487
+ return solve(rest, state);
19469
19488
  }
19470
19489
  options.progress.collecting(requirement.name);
19471
- const resolved = await resolvePackage(options.client, requirement);
19472
- chosen.set(requirement.name, resolved);
19473
- options.progress.downloading(resolved.name, resolved.version);
19474
- const archive = await options.client.bytes(resolved.url);
19475
- const entries = readZip(archive);
19476
- const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19477
- if (metadata) {
19478
- for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19479
- if (!line.startsWith("Requires-Dist:")) continue;
19480
- const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19481
- if (dependency) queue.push({ requirement: dependency, extras: [] });
19482
- }
19483
- }
19484
- unpack(options.vfs, options.cred, entries);
19485
- report.installed.push({ name: resolved.name, version: resolved.version });
19486
- }
19490
+ if (unavailable2.has(requirement.name)) {
19491
+ throw new Error(`${requirement.name} has no wheel this runtime can use`);
19492
+ }
19493
+ const candidateKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).join(",")}`;
19494
+ let candidatePromise = candidateCache.get(candidateKey);
19495
+ if (!candidatePromise) {
19496
+ candidatePromise = resolvePackageCandidates(options.client, requirement);
19497
+ candidateCache.set(candidateKey, candidatePromise);
19498
+ }
19499
+ let candidates;
19500
+ try {
19501
+ candidates = await candidatePromise;
19502
+ } catch (error) {
19503
+ const anyKey = `${requirement.name}:`;
19504
+ let anyPromise = candidateCache.get(anyKey);
19505
+ if (!anyPromise) {
19506
+ anyPromise = resolvePackageCandidates(
19507
+ options.client,
19508
+ { ...requirement, specifiers: [] });
19509
+ candidateCache.set(anyKey, anyPromise);
19510
+ }
19511
+ try {
19512
+ await anyPromise;
19513
+ } catch {
19514
+ unavailable2.add(requirement.name);
19515
+ }
19516
+ throw error;
19517
+ }
19518
+ let failure2;
19519
+ for (const resolved of candidates) {
19520
+ try {
19521
+ let entries = archives.get(resolved.url);
19522
+ if (!entries) {
19523
+ options.progress.downloading(resolved.name, resolved.version);
19524
+ entries = readZip(await options.client.bytes(resolved.url));
19525
+ archives.set(resolved.url, entries);
19526
+ }
19527
+ const dependencies = [];
19528
+ const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19529
+ if (metadata) {
19530
+ for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19531
+ if (!line.startsWith("Requires-Dist:")) continue;
19532
+ const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19533
+ if (dependency && markerApplies(dependency.marker, environment, extras)) {
19534
+ dependencies.push({ requirement: dependency, extras: dependency.extras });
19535
+ }
19536
+ }
19537
+ }
19538
+ const chosen = new Map(state.chosen);
19539
+ chosen.set(requirement.name, resolved);
19540
+ return await solve([...dependencies, ...rest], {
19541
+ chosen,
19542
+ staged: [...state.staged, ...stageWheel(entries, resolved)],
19543
+ installed: [...state.installed, { name: resolved.name, version: resolved.version }],
19544
+ skipped: state.skipped
19545
+ });
19546
+ } catch (error) {
19547
+ failure2 = error;
19548
+ }
19549
+ }
19550
+ throw failure2 instanceof Error ? failure2 : new Error(`could not resolve ${requirement.name}`);
19551
+ };
19552
+ const solved = await solve(queue, {
19553
+ chosen: /* @__PURE__ */ new Map(),
19554
+ staged: [],
19555
+ installed: [],
19556
+ skipped: []
19557
+ });
19558
+ report.installed = solved.installed;
19559
+ report.skipped = solved.skipped;
19560
+ commit(options.vfs, options.cred, solved.staged);
19487
19561
  return report;
19488
19562
  }
19489
- function unpack(vfs, cred, entries, resolved) {
19563
+ function stageWheel(entries, resolved) {
19490
19564
  const staged = [];
19491
19565
  for (const entry of entries) {
19492
19566
  if (entry.isDirectory) continue;
19567
+ const dataPath = /^[^/]+\.data\/(purelib|platlib|scripts)\/(.+)$/.exec(entry.name);
19568
+ if (dataPath) {
19569
+ const [, scheme, relative2] = dataPath;
19570
+ staged.push({
19571
+ path: scheme === "scripts" ? `${SCRIPTS}/${relative2}` : `${SITE_PACKAGES}/${relative2}`,
19572
+ data: entry.data(),
19573
+ ...scheme === "scripts" ? { mode: 493 } : {}
19574
+ });
19575
+ continue;
19576
+ }
19493
19577
  if (/^[^/]+\.data\//.test(entry.name)) continue;
19494
19578
  staged.push({ path: `${SITE_PACKAGES}/${entry.name}`, data: entry.data() });
19495
19579
  }
19580
+ const entryPoints = entries.find((entry) => /\.dist-info\/entry_points\.txt$/.test(entry.name));
19581
+ if (entryPoints) {
19582
+ for (const [name, target] of consoleScripts(new TextDecoder().decode(entryPoints.data()))) {
19583
+ staged.push({ path: `${SCRIPTS}/${name}`, data: new TextEncoder().encode(consoleLauncher(target)), mode: 493 });
19584
+ }
19585
+ }
19586
+ return staged;
19587
+ }
19588
+ function commit(vfs, cred, staged) {
19496
19589
  const directories = /* @__PURE__ */ new Set();
19497
19590
  for (const file3 of staged) directories.add(file3.path.slice(0, file3.path.lastIndexOf("/")));
19498
19591
  for (const directory2 of directories) vfs.mkdir(directory2, { recursive: true, cred });
19499
- for (const file3 of staged) vfs.writeFile(file3.path, file3.data, { cred });
19592
+ for (const file3 of staged) {
19593
+ vfs.writeFile(file3.path, file3.data, { cred, ...file3.mode === void 0 ? {} : { mode: file3.mode } });
19594
+ if (file3.mode !== void 0) vfs.chmod(file3.path, file3.mode, cred);
19595
+ }
19596
+ }
19597
+ function consoleScripts(text2) {
19598
+ const result = [];
19599
+ let section = "";
19600
+ for (const raw of text2.split(/\r?\n/)) {
19601
+ const line = raw.trim();
19602
+ const heading = /^\[([^\]]+)\]$/.exec(line);
19603
+ if (heading) {
19604
+ section = heading[1];
19605
+ continue;
19606
+ }
19607
+ if (section !== "console_scripts" || line === "" || line.startsWith("#")) continue;
19608
+ const assignment = /^([^=\s]+)\s*=\s*([^\s;]+)(?:\s*;.*)?$/.exec(line);
19609
+ if (assignment) result.push([assignment[1], assignment[2].replace(/\s*\[.*\]$/, "")]);
19610
+ }
19611
+ return result;
19612
+ }
19613
+ function consoleLauncher(target) {
19614
+ const [module, attribute = ""] = target.split(":", 2);
19615
+ return `#!/usr/bin/python3
19616
+ import sys
19617
+ from importlib import import_module
19618
+ entry = import_module(${JSON.stringify(module)})
19619
+ for part in ${JSON.stringify(attribute)}.split('.'):
19620
+ if part:
19621
+ entry = getattr(entry, part)
19622
+ raise SystemExit(entry())
19623
+ `;
19500
19624
  }
19501
19625
 
19502
19626
  // src/runtime/python/backend.ts
@@ -19504,7 +19628,11 @@ function withSitePackages(env2) {
19504
19628
  const existing = env2.PYTHONPATH;
19505
19629
  return {
19506
19630
  ...env2,
19507
- PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES
19631
+ PYTHONPATH: existing ? `${SITE_PACKAGES}:${existing}` : SITE_PACKAGES,
19632
+ /* Installed packages live on a host-mounted VFS. Rewriting hundreds of
19633
+ * cache files on every short-lived WASM process is wasted work and can
19634
+ * exhaust the synchronous host-call bridge during large import graphs. */
19635
+ PYTHONDONTWRITEBYTECODE: env2.PYTHONDONTWRITEBYTECODE ?? "1"
19508
19636
  };
19509
19637
  }
19510
19638
  var INTERPRETER_OWNED = /* @__PURE__ */ new Set(["lib", "dev", "proc", "usr"]);
@@ -19594,6 +19722,12 @@ function containerMounts(ctx) {
19594
19722
  }
19595
19723
  } catch {
19596
19724
  }
19725
+ try {
19726
+ if (ctx.vfs.stat(SCRIPTS, { cred: ctx.cred }).isDirectory()) {
19727
+ mounts.push({ guest: SCRIPTS, container: SCRIPTS });
19728
+ }
19729
+ } catch {
19730
+ }
19597
19731
  return mounts;
19598
19732
  }
19599
19733
 
@@ -19647,16 +19781,30 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
19647
19781
  "outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
19648
19782
  );
19649
19783
  }
19784
+ const jsonCache = /* @__PURE__ */ new Map();
19785
+ const bytesCache = /* @__PURE__ */ new Map();
19650
19786
  const client = {
19651
19787
  async json(url) {
19652
- const response = await fetch(url);
19653
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19654
- return response.json();
19788
+ let pending = jsonCache.get(url);
19789
+ if (!pending) {
19790
+ pending = fetch(url).then((response) => {
19791
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19792
+ return response.json();
19793
+ });
19794
+ jsonCache.set(url, pending);
19795
+ }
19796
+ return pending;
19655
19797
  },
19656
19798
  async bytes(url) {
19657
- const response = await fetch(url);
19658
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19659
- return new Uint8Array(await response.arrayBuffer());
19799
+ let pending = bytesCache.get(url);
19800
+ if (!pending) {
19801
+ pending = fetch(url).then(async (response) => {
19802
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19803
+ return new Uint8Array(await response.arrayBuffer());
19804
+ });
19805
+ bytesCache.set(url, pending);
19806
+ }
19807
+ return pending;
19660
19808
  }
19661
19809
  };
19662
19810
  try {