sandboxedjs 0.1.51 → 0.1.52

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
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var pako = require('pako');
6
- var index_js = require('buffer/index.js');
7
6
  var sha256 = require('@noble/hashes/sha256');
7
+ var index_js = require('buffer/index.js');
8
8
  var sha512 = require('@noble/hashes/sha512');
9
9
  var sha1 = require('@noble/hashes/sha1');
10
10
  var semver = require('semver');
@@ -3348,7 +3348,7 @@ async function builtinRead({ shell, argv, io }) {
3348
3348
  if (timeoutSeconds !== void 0 && Number.isFinite(timeoutSeconds)) {
3349
3349
  line = await Promise.race([
3350
3350
  readOnce(),
3351
- new Promise((resolve2) => setTimeout(() => resolve2(null), timeoutSeconds * 1e3))
3351
+ new Promise((resolve3) => setTimeout(() => resolve3(null), timeoutSeconds * 1e3))
3352
3352
  ]);
3353
3353
  } else {
3354
3354
  line = await readOnce();
@@ -3607,10 +3607,10 @@ function builtinType({ shell, argv, io }) {
3607
3607
  }
3608
3608
  async function builtinCommand({ shell, argv, io }) {
3609
3609
  const args = argv.slice(1);
3610
- const describe3 = args.includes("-v") || args.includes("-V");
3610
+ const describe2 = args.includes("-v") || args.includes("-V");
3611
3611
  const verbose = args.includes("-V");
3612
3612
  const rest = args.filter((a) => a !== "-v" && a !== "-V" && a !== "-p");
3613
- if (describe3) {
3613
+ if (describe2) {
3614
3614
  let status = 0;
3615
3615
  for (const name of rest) {
3616
3616
  if (isBuiltinName(name)) {
@@ -4074,6 +4074,94 @@ var init_builtins = __esm({
4074
4074
  }
4075
4075
  });
4076
4076
 
4077
+ // src/pkg/zip.ts
4078
+ var zip_exports = {};
4079
+ __export(zip_exports, {
4080
+ ZipError: () => ZipError,
4081
+ readZip: () => readZip
4082
+ });
4083
+ function readZip(archive) {
4084
+ const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
4085
+ const end = findEndOfCentralDirectory(archive);
4086
+ let entryCount = view.getUint16(end + 10, true);
4087
+ let directoryOffset = view.getUint32(end + 16, true);
4088
+ if (entryCount === 65535 || directoryOffset === 4294967295) {
4089
+ const locator = findSignature(archive, ZIP64_END_LOCATOR, end);
4090
+ if (locator < 0) throw new ZipError("zip64 archive without an end locator");
4091
+ const zip64End = Number(view.getBigUint64(locator + 8, true));
4092
+ if (view.getUint32(zip64End, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY) {
4093
+ throw new ZipError("zip64 end record not found where the locator says");
4094
+ }
4095
+ entryCount = Number(view.getBigUint64(zip64End + 32, true));
4096
+ directoryOffset = Number(view.getBigUint64(zip64End + 48, true));
4097
+ }
4098
+ const entries = [];
4099
+ let at = directoryOffset;
4100
+ for (let i = 0; i < entryCount; i += 1) {
4101
+ if (view.getUint32(at, true) !== CENTRAL_FILE_HEADER) {
4102
+ throw new ZipError(`central directory entry ${i} has a bad signature`);
4103
+ }
4104
+ const flags = view.getUint16(at + 8, true);
4105
+ if (flags & 1) throw new ZipError("encrypted archives are not supported");
4106
+ const method = view.getUint16(at + 10, true);
4107
+ const compressedSize = view.getUint32(at + 20, true);
4108
+ const uncompressedSize = view.getUint32(at + 24, true);
4109
+ const nameLength = view.getUint16(at + 28, true);
4110
+ const extraLength = view.getUint16(at + 30, true);
4111
+ const commentLength = view.getUint16(at + 32, true);
4112
+ const externalAttributes = view.getUint32(at + 38, true);
4113
+ const localOffset = view.getUint32(at + 42, true);
4114
+ const name = new TextDecoder().decode(archive.subarray(at + 46, at + 46 + nameLength));
4115
+ entries.push({
4116
+ name,
4117
+ /* The high 16 bits are the Unix mode when the archive was made on Unix.
4118
+ * pip relies on it for console scripts, which have to stay executable. */
4119
+ mode: externalAttributes >>> 16,
4120
+ isDirectory: name.endsWith("/"),
4121
+ data: () => extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name)
4122
+ });
4123
+ at += 46 + nameLength + extraLength + commentLength;
4124
+ }
4125
+ return entries;
4126
+ }
4127
+ function extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name) {
4128
+ const nameLength = view.getUint16(localOffset + 26, true);
4129
+ const extraLength = view.getUint16(localOffset + 28, true);
4130
+ const start2 = localOffset + 30 + nameLength + extraLength;
4131
+ const body = archive.subarray(start2, start2 + compressedSize);
4132
+ if (method === 0) return body.slice();
4133
+ if (method !== 8) throw new ZipError(`${name}: unsupported compression method ${method}`);
4134
+ const inflated = pako.inflateRaw(body);
4135
+ if (inflated.length !== uncompressedSize) {
4136
+ throw new ZipError(`${name}: inflated to ${inflated.length} bytes, the directory says ${uncompressedSize}`);
4137
+ }
4138
+ return inflated;
4139
+ }
4140
+ function findEndOfCentralDirectory(archive, view) {
4141
+ const at = findSignature(archive, END_OF_CENTRAL_DIRECTORY, archive.length);
4142
+ if (at < 0) throw new ZipError("not a zip archive: no end-of-central-directory record");
4143
+ return at;
4144
+ }
4145
+ function findSignature(archive, signature, before) {
4146
+ const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
4147
+ for (let at = Math.min(before, archive.length) - 4; at >= 0; at -= 1) {
4148
+ if (view.getUint32(at, true) === signature) return at;
4149
+ }
4150
+ return -1;
4151
+ }
4152
+ var END_OF_CENTRAL_DIRECTORY, CENTRAL_FILE_HEADER, ZIP64_END_LOCATOR, ZIP64_END_OF_CENTRAL_DIRECTORY, ZipError;
4153
+ var init_zip = __esm({
4154
+ "src/pkg/zip.ts"() {
4155
+ END_OF_CENTRAL_DIRECTORY = 101010256;
4156
+ CENTRAL_FILE_HEADER = 33639248;
4157
+ ZIP64_END_LOCATOR = 117853008;
4158
+ ZIP64_END_OF_CENTRAL_DIRECTORY = 101075792;
4159
+ ZipError = class extends Error {
4160
+ code = "ERR_ZIP";
4161
+ };
4162
+ }
4163
+ });
4164
+
4077
4165
  // src/fs/vfs.ts
4078
4166
  init_errno();
4079
4167
  init_path();
@@ -4859,7 +4947,7 @@ var Pipe = class _Pipe {
4859
4947
  }
4860
4948
  waitForData() {
4861
4949
  if (this.buffered > 0 || this.writerClosed || this.readerClosed) return Promise.resolve();
4862
- return new Promise((resolve2) => this.wakers.push(resolve2));
4950
+ return new Promise((resolve3) => this.wakers.push(resolve3));
4863
4951
  }
4864
4952
  async read(size = Infinity) {
4865
4953
  await this.waitForData();
@@ -5218,7 +5306,7 @@ var Process = class {
5218
5306
  }
5219
5307
  wait() {
5220
5308
  if (this.exitCode !== null) return Promise.resolve(this.exitCode);
5221
- return new Promise((resolve2) => this.exitWaiters.push(resolve2));
5309
+ return new Promise((resolve3) => this.exitWaiters.push(resolve3));
5222
5310
  }
5223
5311
  };
5224
5312
  var ProcessTable = class {
@@ -6985,7 +7073,7 @@ var NetworkStack = class {
6985
7073
  try {
6986
7074
  const res = await Promise.race([
6987
7075
  this.pod.request(port, { path: "/", method: "GET" }),
6988
- new Promise((resolve2) => setTimeout(() => resolve2(null), timeoutMs))
7076
+ new Promise((resolve3) => setTimeout(() => resolve3(null), timeoutMs))
6989
7077
  ]);
6990
7078
  if (res === null) return false;
6991
7079
  return res.statusCode !== 503;
@@ -10449,11 +10537,11 @@ var sleepCmd = defineCommand({
10449
10537
  const multiplier = { "": 1e3, s: 1e3, m: 6e4, h: 36e5, d: 864e5 }[m[2] ?? ""];
10450
10538
  totalMs += value * multiplier;
10451
10539
  }
10452
- await new Promise((resolve2) => {
10453
- const timer = setTimeout(resolve2, totalMs);
10540
+ await new Promise((resolve3) => {
10541
+ const timer = setTimeout(resolve3, totalMs);
10454
10542
  ctx.signal.addEventListener("abort", () => {
10455
10543
  clearTimeout(timer);
10456
- resolve2();
10544
+ resolve3();
10457
10545
  });
10458
10546
  });
10459
10547
  return ctx.signal.aborted ? 130 : 0;
@@ -17391,13 +17479,13 @@ async function execute(ctx, invocation) {
17391
17479
  })();
17392
17480
  }
17393
17481
  let onAbort;
17394
- const aborted = new Promise((resolve2) => {
17482
+ const aborted = new Promise((resolve3) => {
17395
17483
  onAbort = () => {
17396
17484
  try {
17397
17485
  proc.kill();
17398
17486
  } catch {
17399
17487
  }
17400
- resolve2({ exitCode: 137 });
17488
+ resolve3({ exitCode: 137 });
17401
17489
  };
17402
17490
  if (ctx.signal.aborted) onAbort();
17403
17491
  else ctx.signal.addEventListener("abort", onAbort, { once: true });
@@ -17806,10 +17894,10 @@ function validateManifest(value) {
17806
17894
  var bundledManifest = {
17807
17895
  format: "sandboxedjs-python-runtime",
17808
17896
  schemaVersion: 1,
17809
- runtimeId: "sbx-cpython-3.13.5-threaded-fixed",
17897
+ runtimeId: "sbx-cpython-3.13.5-dynamic",
17810
17898
  engine: "cpython-wasm",
17811
17899
  pythonVersion: "3.13.5",
17812
- profile: "threaded-fixed",
17900
+ profile: "dynamic",
17813
17901
  hostAbi: { name: "sbx_host_v1", version: 1 },
17814
17902
  artifacts: {
17815
17903
  moduleUrl: new URL(
@@ -17819,7 +17907,7 @@ var bundledManifest = {
17819
17907
  },
17820
17908
  capabilities: {
17821
17909
  threads: true,
17822
- nativeExtensions: "fixed",
17910
+ nativeExtensions: "dynamic",
17823
17911
  networking: "none",
17824
17912
  processes: "none",
17825
17913
  persistence: "memory"
@@ -17827,6 +17915,7 @@ var bundledManifest = {
17827
17915
  };
17828
17916
  var config = { backend: "sbx-cpython-wasm", manifest: bundledManifest };
17829
17917
  function setPythonBackend(options) {
17918
+ if (options.wheelIndex !== void 0) config.wheelIndex = options.wheelIndex;
17830
17919
  if (options.manifest !== void 0) config.manifest = validateManifest(options.manifest);
17831
17920
  if (options.workerUrl !== void 0) config.workerUrl = options.workerUrl;
17832
17921
  if (options.backend !== void 0) {
@@ -18134,10 +18223,10 @@ var PipeBuffer = class {
18134
18223
  wake() {
18135
18224
  const waiting = this.waiters;
18136
18225
  this.waiters = [];
18137
- for (const resolve2 of waiting) resolve2();
18226
+ for (const resolve3 of waiting) resolve3();
18138
18227
  }
18139
18228
  whenReady() {
18140
- return new Promise((resolve2) => this.waiters.push(resolve2));
18229
+ return new Promise((resolve3) => this.waiters.push(resolve3));
18141
18230
  }
18142
18231
  };
18143
18232
  var PipeReadEnd = class {
@@ -18291,7 +18380,7 @@ var StreamDescription = class {
18291
18380
  return true;
18292
18381
  }
18293
18382
  whenReady() {
18294
- return new Promise((resolve2) => this.waiters.push(resolve2));
18383
+ return new Promise((resolve3) => this.waiters.push(resolve3));
18295
18384
  }
18296
18385
  close() {
18297
18386
  this.wake();
@@ -18299,7 +18388,7 @@ var StreamDescription = class {
18299
18388
  wake() {
18300
18389
  const waiting = this.waiters;
18301
18390
  this.waiters = [];
18302
- for (const resolve2 of waiting) resolve2();
18391
+ for (const resolve3 of waiting) resolve3();
18303
18392
  }
18304
18393
  };
18305
18394
  function toPosixError(error) {
@@ -18650,7 +18739,7 @@ function createHostAbiServer(proc) {
18650
18739
  }
18651
18740
  /* ------------------------------------------------------------ files */
18652
18741
  case Op.openat: {
18653
- const path = resolve2(r.string());
18742
+ const path = resolve3(r.string());
18654
18743
  const flags = r.u32();
18655
18744
  const mode = r.u32();
18656
18745
  const description = files.open(path, flags, mode);
@@ -18698,7 +18787,7 @@ function createHostAbiServer(proc) {
18698
18787
  return { status: 0, payload: encodeStat(proc.table.get(r.i32()).stat()) };
18699
18788
  }
18700
18789
  case Op.statat: {
18701
- const path = resolve2(r.string());
18790
+ const path = resolve3(r.string());
18702
18791
  const followLinks = r.u32() !== 0;
18703
18792
  try {
18704
18793
  const st = followLinks ? proc.vfs.stat(path, { cred: proc.cred }) : proc.vfs.lstat(path);
@@ -18728,11 +18817,11 @@ function createHostAbiServer(proc) {
18728
18817
  return { status: 0 };
18729
18818
  }
18730
18819
  case Op.renameat: {
18731
- files.rename(resolve2(r.string()), resolve2(r.string()));
18820
+ files.rename(resolve3(r.string()), resolve3(r.string()));
18732
18821
  return { status: 0 };
18733
18822
  }
18734
18823
  case Op.unlinkat: {
18735
- const path = resolve2(r.string());
18824
+ const path = resolve3(r.string());
18736
18825
  const removeDirectory = r.u32() !== 0;
18737
18826
  if (removeDirectory) {
18738
18827
  try {
@@ -18746,7 +18835,7 @@ function createHostAbiServer(proc) {
18746
18835
  return { status: 0 };
18747
18836
  }
18748
18837
  case Op.mkdirat: {
18749
- const path = resolve2(r.string());
18838
+ const path = resolve3(r.string());
18750
18839
  const mode = r.u32();
18751
18840
  try {
18752
18841
  proc.vfs.mkdir(path, { mode, cred: proc.cred });
@@ -18757,7 +18846,7 @@ function createHostAbiServer(proc) {
18757
18846
  }
18758
18847
  case Op.readlinkat: {
18759
18848
  try {
18760
- const target = proc.vfs.readlink(resolve2(r.string()), proc.cred);
18849
+ const target = proc.vfs.readlink(resolve3(r.string()), proc.cred);
18761
18850
  return { status: 0, payload: new Writer().string(target).finish() };
18762
18851
  } catch (error) {
18763
18852
  throw toPosixError(error);
@@ -18766,7 +18855,7 @@ function createHostAbiServer(proc) {
18766
18855
  case Op.symlinkat: {
18767
18856
  const target = r.string();
18768
18857
  try {
18769
- proc.vfs.symlink(target, resolve2(r.string()), proc.cred);
18858
+ proc.vfs.symlink(target, resolve3(r.string()), proc.cred);
18770
18859
  } catch (error) {
18771
18860
  throw toPosixError(error);
18772
18861
  }
@@ -18841,7 +18930,7 @@ function createHostAbiServer(proc) {
18841
18930
  case Op.getcwd:
18842
18931
  return { status: 0, payload: new Writer().string(proc.cwd).finish() };
18843
18932
  case Op.chdir: {
18844
- const path = resolve2(r.string());
18933
+ const path = resolve3(r.string());
18845
18934
  let st;
18846
18935
  try {
18847
18936
  st = proc.vfs.stat(path, { cred: proc.cred });
@@ -18870,7 +18959,7 @@ function createHostAbiServer(proc) {
18870
18959
  function implemented(capability) {
18871
18960
  return ["files", "descriptors", "pipes", "readiness", "time", "identity", "entropy"].includes(capability);
18872
18961
  }
18873
- function resolve2(path) {
18962
+ function resolve3(path) {
18874
18963
  if (path.startsWith("/")) return proc.vfs.resolvePath(path);
18875
18964
  return proc.vfs.resolvePath(`${proc.cwd.replace(/\/$/, "")}/${path}`);
18876
18965
  }
@@ -18932,11 +19021,11 @@ function createHostAbiServer(proc) {
18932
19021
  }
18933
19022
  function aborted() {
18934
19023
  if (proc.signal.aborted) return Promise.resolve();
18935
- return new Promise((resolve3) => proc.signal.addEventListener("abort", () => resolve3(), { once: true }));
19024
+ return new Promise((resolve4) => proc.signal.addEventListener("abort", () => resolve4(), { once: true }));
18936
19025
  }
18937
19026
  }
18938
19027
  function sleep(ms) {
18939
- return new Promise((resolve2) => setTimeout(resolve2, Math.max(0, ms)));
19028
+ return new Promise((resolve3) => setTimeout(resolve3, Math.max(0, ms)));
18940
19029
  }
18941
19030
  function encodeStat(st) {
18942
19031
  return new Writer().u64(st.ino).u32(st.mode).u64(st.size).u32(st.uid).u32(st.gid).u32(st.nlink).i64(Math.round(st.atimeMs * 1e6)).i64(Math.round(st.mtimeMs * 1e6)).i64(Math.round(st.ctimeMs * 1e6)).finish();
@@ -19056,12 +19145,12 @@ async function startRuntimeWorker(options = {}) {
19056
19145
  const url = options.url ?? defaultWorkerUrl();
19057
19146
  const worker = hasDomWorker() ? await startDomWorker(url) : await startNodeWorker(url, options.workerData ?? {});
19058
19147
  try {
19059
- await new Promise((resolve2, reject) => {
19148
+ await new Promise((resolve3, reject) => {
19060
19149
  const timer = setTimeout(() => reject(new Error("the runtime worker did not start in time")), options.timeoutMs ?? 1e4);
19061
19150
  worker.onMessage((message) => {
19062
19151
  if (message?.type === "sandboxedjs:ready") {
19063
19152
  clearTimeout(timer);
19064
- resolve2();
19153
+ resolve3();
19065
19154
  }
19066
19155
  });
19067
19156
  worker.onError((error) => {
@@ -19155,8 +19244,8 @@ async function startPythonProcess(options) {
19155
19244
  };
19156
19245
  let blame = () => {
19157
19246
  };
19158
- const finished = new Promise((resolve2, reject) => {
19159
- settle = resolve2;
19247
+ const finished = new Promise((resolve3, reject) => {
19248
+ settle = resolve3;
19160
19249
  blame = reject;
19161
19250
  });
19162
19251
  let done = false;
@@ -19221,85 +19310,11 @@ async function startPythonProcess(options) {
19221
19310
  }
19222
19311
  };
19223
19312
  }
19224
- var END_OF_CENTRAL_DIRECTORY = 101010256;
19225
- var CENTRAL_FILE_HEADER = 33639248;
19226
- var ZIP64_END_LOCATOR = 117853008;
19227
- var ZIP64_END_OF_CENTRAL_DIRECTORY = 101075792;
19228
- var ZipError = class extends Error {
19229
- code = "ERR_ZIP";
19230
- };
19231
- function readZip(archive) {
19232
- const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
19233
- const end = findEndOfCentralDirectory(archive);
19234
- let entryCount = view.getUint16(end + 10, true);
19235
- let directoryOffset = view.getUint32(end + 16, true);
19236
- if (entryCount === 65535 || directoryOffset === 4294967295) {
19237
- const locator = findSignature(archive, ZIP64_END_LOCATOR, end);
19238
- if (locator < 0) throw new ZipError("zip64 archive without an end locator");
19239
- const zip64End = Number(view.getBigUint64(locator + 8, true));
19240
- if (view.getUint32(zip64End, true) !== ZIP64_END_OF_CENTRAL_DIRECTORY) {
19241
- throw new ZipError("zip64 end record not found where the locator says");
19242
- }
19243
- entryCount = Number(view.getBigUint64(zip64End + 32, true));
19244
- directoryOffset = Number(view.getBigUint64(zip64End + 48, true));
19245
- }
19246
- const entries = [];
19247
- let at = directoryOffset;
19248
- for (let i = 0; i < entryCount; i += 1) {
19249
- if (view.getUint32(at, true) !== CENTRAL_FILE_HEADER) {
19250
- throw new ZipError(`central directory entry ${i} has a bad signature`);
19251
- }
19252
- const flags = view.getUint16(at + 8, true);
19253
- if (flags & 1) throw new ZipError("encrypted archives are not supported");
19254
- const method = view.getUint16(at + 10, true);
19255
- const compressedSize = view.getUint32(at + 20, true);
19256
- const uncompressedSize = view.getUint32(at + 24, true);
19257
- const nameLength = view.getUint16(at + 28, true);
19258
- const extraLength = view.getUint16(at + 30, true);
19259
- const commentLength = view.getUint16(at + 32, true);
19260
- const externalAttributes = view.getUint32(at + 38, true);
19261
- const localOffset = view.getUint32(at + 42, true);
19262
- const name = new TextDecoder().decode(archive.subarray(at + 46, at + 46 + nameLength));
19263
- entries.push({
19264
- name,
19265
- /* The high 16 bits are the Unix mode when the archive was made on Unix.
19266
- * pip relies on it for console scripts, which have to stay executable. */
19267
- mode: externalAttributes >>> 16,
19268
- isDirectory: name.endsWith("/"),
19269
- data: () => extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name)
19270
- });
19271
- at += 46 + nameLength + extraLength + commentLength;
19272
- }
19273
- return entries;
19274
- }
19275
- function extract(archive, view, localOffset, method, compressedSize, uncompressedSize, name) {
19276
- const nameLength = view.getUint16(localOffset + 26, true);
19277
- const extraLength = view.getUint16(localOffset + 28, true);
19278
- const start2 = localOffset + 30 + nameLength + extraLength;
19279
- const body = archive.subarray(start2, start2 + compressedSize);
19280
- if (method === 0) return body.slice();
19281
- if (method !== 8) throw new ZipError(`${name}: unsupported compression method ${method}`);
19282
- const inflated = pako.inflateRaw(body);
19283
- if (inflated.length !== uncompressedSize) {
19284
- throw new ZipError(`${name}: inflated to ${inflated.length} bytes, the directory says ${uncompressedSize}`);
19285
- }
19286
- return inflated;
19287
- }
19288
- function findEndOfCentralDirectory(archive, view) {
19289
- const at = findSignature(archive, END_OF_CENTRAL_DIRECTORY, archive.length);
19290
- if (at < 0) throw new ZipError("not a zip archive: no end-of-central-directory record");
19291
- return at;
19292
- }
19293
- function findSignature(archive, signature, before) {
19294
- const view = new DataView(archive.buffer, archive.byteOffset, archive.byteLength);
19295
- for (let at = Math.min(before, archive.length) - 4; at >= 0; at -= 1) {
19296
- if (view.getUint32(at, true) === signature) return at;
19297
- }
19298
- return -1;
19299
- }
19313
+
19314
+ // src/runtime/python/install.ts
19315
+ init_zip();
19300
19316
 
19301
19317
  // src/runtime/python/pypi.ts
19302
- var ACCEPTED_TAGS = ["py3-none-any", "py2.py3-none-any", "py3-none-emscripten"];
19303
19318
  function parseRequirement(text2) {
19304
19319
  const cleaned = text2.replace(/#.*$/, "").trim();
19305
19320
  if (!cleaned) return null;
@@ -19373,66 +19388,280 @@ function compareRelease(left, right) {
19373
19388
  }
19374
19389
  return 0;
19375
19390
  }
19376
- function isPreRelease(version) {
19377
- return /(a|b|rc|dev|post)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
19378
- }
19379
- async function resolvePackageCandidates(client, requirement, environment) {
19380
- const index = await client.json(`https://pypi.org/pypi/${requirement.name}/json`);
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
- const stable = candidates.filter((version) => !isPreRelease(version));
19383
- const newestFirst = (stable.length > 0 ? stable : candidates).reverse();
19384
- const probeIndexes = [
19385
- 0,
19386
- Math.floor(newestFirst.length / 2),
19387
- Math.floor(newestFirst.length / 4),
19388
- Math.floor(newestFirst.length * 3 / 4),
19389
- newestFirst.length - 1
19390
- ];
19391
- const probes = [...new Set(probeIndexes)].map((index2) => newestFirst[index2]).filter((v) => Boolean(v));
19392
- const probeVersions = new Set(probes);
19393
- const ordered = [...probes, ...newestFirst.filter((version) => !probeVersions.has(version))];
19394
- let sawSourceOnly = false;
19395
- const resolved = [];
19396
- for (const version of ordered) {
19397
- const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
19398
- const wheel = pickWheel(files);
19399
- if (wheel) {
19400
- resolved.push({
19401
- name: requirement.name,
19391
+ function splitOnce(text2, separator) {
19392
+ const at = text2.indexOf(separator);
19393
+ return at < 0 ? [text2] : [text2.slice(0, at), text2.slice(at + 1)];
19394
+ }
19395
+
19396
+ // src/runtime/python/extension-abi.ts
19397
+ var EXTENSION_ABI = {
19398
+ "abiId": "sbxabi1-c2637d04695ad927",
19399
+ "wheelTag": "cp313-cp313-emscripten_5_0_6_wasm32",
19400
+ "wheel": {
19401
+ "acceptedPurePythonTags": [
19402
+ "py3-none-any",
19403
+ "py2.py3-none-any",
19404
+ "cp313-none-any"
19405
+ ]
19406
+ }};
19407
+ var WHEEL_TAG = EXTENSION_ABI.wheelTag;
19408
+ var PURE_PYTHON_TAGS = EXTENSION_ABI.wheel.acceptedPurePythonTags;
19409
+
19410
+ // src/runtime/python/resolver.ts
19411
+ var ResolutionError = class extends Error {
19412
+ constructor(message, explanation = []) {
19413
+ super(explanation.length > 0 ? `${message}
19414
+ ${explanation.join("\n ")}` : message);
19415
+ this.explanation = explanation;
19416
+ this.name = "ResolutionError";
19417
+ }
19418
+ explanation;
19419
+ };
19420
+ function classifyFile(file3, allowSourceBuilds) {
19421
+ if (file3.packagetype === "sdist") return allowSourceBuilds ? "sdist" : null;
19422
+ if (file3.packagetype !== "bdist_wheel") return null;
19423
+ const stem = file3.filename.replace(/\.whl$/, "");
19424
+ const parts = stem.split("-");
19425
+ const tags = parts.slice(-3).join("-");
19426
+ if (tags === WHEEL_TAG) return "sbx-wasm";
19427
+ if (PURE_PYTHON_TAGS.includes(tags)) return "pure";
19428
+ const [pythonTags = "", abiTags = "", platformTags = ""] = parts.slice(-3);
19429
+ for (const python2 of pythonTags.split(".")) {
19430
+ for (const abi of abiTags.split(".")) {
19431
+ for (const platform of platformTags.split(".")) {
19432
+ const tag2 = `${python2}-${abi}-${platform}`;
19433
+ if (tag2 === WHEEL_TAG) return "sbx-wasm";
19434
+ if (PURE_PYTHON_TAGS.includes(tag2)) return "pure";
19435
+ }
19436
+ }
19437
+ }
19438
+ return null;
19439
+ }
19440
+ function metadataUrlFor(file3) {
19441
+ const declared = file3["core-metadata"] ?? file3.core_metadata;
19442
+ return declared ? `${file3.url}.metadata` : null;
19443
+ }
19444
+ var KIND_RANK = { pure: 0, "sbx-wasm": 1, sdist: 2 };
19445
+ async function resolve2(options) {
19446
+ const {
19447
+ client,
19448
+ environment,
19449
+ signal,
19450
+ deadlineMs = 18e4,
19451
+ allowSourceBuilds = false,
19452
+ progress
19453
+ } = options;
19454
+ const started = Date.now();
19455
+ const checkBudget = () => {
19456
+ if (signal?.aborted) throw new ResolutionError("dependency resolution was cancelled");
19457
+ if (Date.now() - started > deadlineMs) {
19458
+ throw new ResolutionError(
19459
+ `dependency resolution exceeded its ${Math.round(deadlineMs / 1e3)}s budget. The graph explored so far is consistent but incomplete; constrain the requirements or raise the deadline.`
19460
+ );
19461
+ }
19462
+ };
19463
+ const releases = /* @__PURE__ */ new Map();
19464
+ const dependencies = /* @__PURE__ */ new Map();
19465
+ const nogoods = /* @__PURE__ */ new Map();
19466
+ const candidatesFor = (name) => {
19467
+ let pending = releases.get(name);
19468
+ if (!pending) {
19469
+ pending = fetchCandidates(client, name, allowSourceBuilds, options.index ?? null);
19470
+ releases.set(name, pending);
19471
+ }
19472
+ return pending;
19473
+ };
19474
+ const requiresFor = (candidate) => {
19475
+ const key = `${candidate.name}==${candidate.version}`;
19476
+ let pending = dependencies.get(key);
19477
+ if (!pending) {
19478
+ pending = candidate.indexedRequires ? Promise.resolve(candidate.indexedRequires) : fetchRequires(client, candidate);
19479
+ dependencies.set(key, pending);
19480
+ }
19481
+ return pending;
19482
+ };
19483
+ const roots = [];
19484
+ for (const text2 of options.requirements) {
19485
+ const requirement = parseRequirement(text2);
19486
+ if (requirement) roots.push({ requirement, extras: requirement.extras, origin: "<root>" });
19487
+ }
19488
+ const skipped = [];
19489
+ const signatureOf = (name, state) => {
19490
+ const clauses = (state.constraints.get(name) ?? []).flatMap((entry) => entry.spec.map((s) => `${s.op}${s.version}`)).sort();
19491
+ return `${name}|${[...new Set(clauses)].join(",")}`;
19492
+ };
19493
+ const search = async (queue, state) => {
19494
+ checkBudget();
19495
+ if (queue.length === 0) return state;
19496
+ const [edge, ...rest] = queue;
19497
+ const { requirement, extras, origin } = edge;
19498
+ const name = requirement.name;
19499
+ if (!markerApplies(requirement.marker, environment, extras)) {
19500
+ if (requirement.marker) skipped.push({ name, marker: requirement.marker });
19501
+ return search(rest, state);
19502
+ }
19503
+ const previous = state.constraints.get(name) ?? [];
19504
+ const constraints = new Map(state.constraints);
19505
+ constraints.set(name, [...previous, { spec: requirement.specifiers, origin }]);
19506
+ const next = { chosen: state.chosen, constraints };
19507
+ const already = next.chosen.get(name);
19508
+ if (already) {
19509
+ const satisfied = requirement.specifiers.every((s) => compareVersions(already.version, s.version, s.op));
19510
+ if (!satisfied) {
19511
+ throw new ResolutionError(
19512
+ `cannot satisfy ${name}`,
19513
+ explain(name, next.constraints, already.version)
19514
+ );
19515
+ }
19516
+ return search(rest, next);
19517
+ }
19518
+ const signature = signatureOf(name, next);
19519
+ const learned = nogoods.get(signature);
19520
+ if (learned) throw learned;
19521
+ progress?.collecting(name);
19522
+ const all = await candidatesFor(name);
19523
+ checkBudget();
19524
+ const clauses = (next.constraints.get(name) ?? []).flatMap((entry) => entry.spec);
19525
+ const viable = all.filter((candidate) => clauses.every((s) => compareVersions(candidate.version, s.version, s.op))).sort((a, b) => compareVersions(a.version, b.version, ">") ? -1 : compareVersions(b.version, a.version, ">") ? 1 : KIND_RANK[a.kind] - KIND_RANK[b.kind]);
19526
+ if (viable.length === 0) {
19527
+ const error2 = new ResolutionError(
19528
+ all.length === 0 ? `no distribution of ${name} is usable by this runtime (it needs a pure-Python wheel or one tagged ${WHEEL_TAG})` : `no version of ${name} satisfies every requirement`,
19529
+ explain(name, next.constraints, null, all)
19530
+ );
19531
+ nogoods.set(signature, error2);
19532
+ throw error2;
19533
+ }
19534
+ let failure2 = null;
19535
+ const seenVersions = /* @__PURE__ */ new Set();
19536
+ for (const candidate of viable) {
19537
+ if (seenVersions.has(candidate.version)) continue;
19538
+ seenVersions.add(candidate.version);
19539
+ checkBudget();
19540
+ progress?.examining?.(candidate.name, candidate.version);
19541
+ let requires;
19542
+ try {
19543
+ requires = await requiresFor(candidate);
19544
+ } catch (error2) {
19545
+ failure2 = new ResolutionError(
19546
+ `could not read the dependencies of ${name} ${candidate.version}: ${error2.message}`
19547
+ );
19548
+ continue;
19549
+ }
19550
+ const edges = [];
19551
+ for (const line of requires) {
19552
+ const dependency = parseRequirement(line);
19553
+ if (!dependency) continue;
19554
+ if (!markerApplies(dependency.marker, environment, extras)) continue;
19555
+ edges.push({
19556
+ requirement: dependency,
19557
+ extras: dependency.extras,
19558
+ origin: `${name}==${candidate.version}`
19559
+ });
19560
+ }
19561
+ const chosen = new Map(next.chosen);
19562
+ chosen.set(name, { ...candidate, requires });
19563
+ try {
19564
+ return await search([...edges, ...rest], { chosen, constraints: next.constraints });
19565
+ } catch (error2) {
19566
+ if (!(error2 instanceof ResolutionError)) throw error2;
19567
+ if (/cancelled|budget/.test(error2.message)) throw error2;
19568
+ failure2 = error2;
19569
+ }
19570
+ }
19571
+ const error = failure2 ?? new ResolutionError(`could not resolve ${name}`);
19572
+ nogoods.set(signature, error);
19573
+ throw error;
19574
+ };
19575
+ const solved = await search(roots, { chosen: /* @__PURE__ */ new Map(), constraints: /* @__PURE__ */ new Map() });
19576
+ const distributions = [...solved.chosen.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
19577
+ return { distributions, skipped };
19578
+ }
19579
+ function explain(name, constraints, chosenVersion, available = []) {
19580
+ const lines = [];
19581
+ for (const entry of constraints.get(name) ?? []) {
19582
+ if (entry.spec.length === 0) continue;
19583
+ const text2 = entry.spec.map((s) => `${s.op}${s.version}`).join(", ");
19584
+ lines.push(`${entry.origin} requires ${name} ${text2}`);
19585
+ }
19586
+ if (chosenVersion) lines.push(`but ${name} ${chosenVersion} is already selected`);
19587
+ if (available.length > 0) {
19588
+ const versions = [...new Set(available.map((c) => c.version))].slice(0, 8);
19589
+ lines.push(`usable versions: ${versions.join(", ")}${available.length > 8 ? ", ..." : ""}`);
19590
+ }
19591
+ return lines;
19592
+ }
19593
+ async function fetchCandidates(client, name, allowSourceBuilds, prebuilt) {
19594
+ const candidates = [];
19595
+ for (const wheel of prebuilt?.wheels ?? []) {
19596
+ if (normalize2(wheel.name) !== normalize2(name)) continue;
19597
+ if (wheel.abiId !== EXTENSION_ABI.abiId) {
19598
+ throw new ResolutionError(
19599
+ `${wheel.filename} in the wheel index was built for ABI ${wheel.abiId}, but this runtime implements ${EXTENSION_ABI.abiId}; rebuild it`
19600
+ );
19601
+ }
19602
+ candidates.push({
19603
+ name: normalize2(wheel.name),
19604
+ version: wheel.version,
19605
+ kind: "sbx-wasm",
19606
+ url: `${prebuilt.baseUrl.replace(/\/$/, "")}/${wheel.filename}`,
19607
+ filename: wheel.filename,
19608
+ sha256: wheel.sha256,
19609
+ metadataUrl: null,
19610
+ indexedRequires: wheel.requires
19611
+ });
19612
+ }
19613
+ let index;
19614
+ try {
19615
+ index = await client.json(`https://pypi.org/pypi/${name}/json`, { timeoutMs: 3e4 });
19616
+ } catch (error) {
19617
+ if (candidates.length > 0) return candidates;
19618
+ throw error;
19619
+ }
19620
+ for (const [version, files] of Object.entries(index.releases ?? {})) {
19621
+ if (isPreRelease(version)) continue;
19622
+ for (const file3 of files) {
19623
+ if (file3.yanked) continue;
19624
+ const kind = classifyFile(file3, allowSourceBuilds);
19625
+ if (!kind) continue;
19626
+ candidates.push({
19627
+ name: normalize2(name),
19402
19628
  version,
19403
- url: wheel.url,
19404
- filename: wheel.filename,
19405
- sha256: wheel.digests?.sha256 ?? "",
19406
- requires: []
19629
+ kind,
19630
+ url: file3.url,
19631
+ filename: file3.filename,
19632
+ sha256: file3.digests?.sha256 ?? "",
19633
+ metadataUrl: metadataUrlFor(file3)
19407
19634
  });
19408
- continue;
19409
19635
  }
19410
- if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
19411
19636
  }
19412
- if (resolved.length > 0) return resolved;
19413
- if (ordered.length === 0) {
19637
+ return candidates;
19638
+ }
19639
+ function isPreRelease(version) {
19640
+ return /(a|b|rc|dev)\d*$/i.test(version.replace(/^\d+(\.\d+)*/, ""));
19641
+ }
19642
+ async function fetchRequires(client, candidate) {
19643
+ if (candidate.metadataUrl) {
19644
+ const text2 = await client.text(candidate.metadataUrl, { timeoutMs: 3e4 });
19645
+ return requiresFromMetadata(text2);
19646
+ }
19647
+ if (candidate.kind === "sdist") {
19414
19648
  throw new Error(
19415
- `no version of ${requirement.name} matches ${describe(requirement)}`
19649
+ `${candidate.name} ${candidate.version} is a source distribution and publishes no metadata sidecar, so its dependencies cannot be read without building it`
19416
19650
  );
19417
19651
  }
19418
- throw new Error(
19419
- sawSourceOnly ? `${requirement.name} ${ordered[0]} publishes only a source distribution, and this runtime cannot build one yet (source builds are a later milestone)` : `${requirement.name} ${ordered[0]} has no wheel this runtime can use; it needs one tagged ` + ACCEPTED_TAGS.join(" or ")
19420
- );
19652
+ const { readZip: readZip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
19653
+ const entries = readZip2(await client.bytes(candidate.url, { timeoutMs: 12e4 }));
19654
+ const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19655
+ if (!metadata) return [];
19656
+ return requiresFromMetadata(new TextDecoder().decode(metadata.data()));
19421
19657
  }
19422
- function pickWheel(files) {
19423
- const wheels = files.filter((file3) => file3.packagetype === "bdist_wheel");
19424
- for (const tag2 of ACCEPTED_TAGS) {
19425
- const match2 = wheels.find((file3) => file3.filename.endsWith(`-${tag2}.whl`));
19426
- if (match2) return match2;
19658
+ function requiresFromMetadata(text2) {
19659
+ const result = [];
19660
+ for (const line of text2.split(/\r?\n/)) {
19661
+ if (line === "") break;
19662
+ if (line.startsWith("Requires-Dist:")) result.push(line.slice("Requires-Dist:".length).trim());
19427
19663
  }
19428
- return null;
19429
- }
19430
- function describe(requirement) {
19431
- return requirement.specifiers.length === 0 ? "any version" : requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ");
19432
- }
19433
- function splitOnce(text2, separator) {
19434
- const at = text2.indexOf(separator);
19435
- return at < 0 ? [text2] : [text2.slice(0, at), text2.slice(at + 1)];
19664
+ return result;
19436
19665
  }
19437
19666
 
19438
19667
  // src/runtime/python/install.ts
@@ -19452,138 +19681,45 @@ function markerEnvironment(pythonVersion) {
19452
19681
  }
19453
19682
  async function installRequirements(options) {
19454
19683
  const environment = markerEnvironment(options.pythonVersion);
19455
- const report = { installed: [], skipped: [] };
19456
- const queue = [];
19457
- for (const text2 of options.requirements) {
19458
- const requirement = parseRequirement(text2);
19459
- if (requirement) queue.push({ requirement, extras: requirement.extras });
19460
- }
19461
- const archives = /* @__PURE__ */ new Map();
19462
- const unavailable2 = /* @__PURE__ */ new Set();
19463
- const unsatisfiable = /* @__PURE__ */ new Map();
19464
- const candidateCache = /* @__PURE__ */ new Map();
19465
- let candidateAttempts = 0;
19466
- const MAX_CANDIDATE_ATTEMPTS = 96;
19467
- const solve = async (pending, state) => {
19468
- if (pending.length === 0) return state;
19469
- const [first, ...tail2] = pending;
19470
- let { requirement, extras } = first;
19471
- const rest = [];
19472
- const mergedSpecifiers = [...requirement.specifiers];
19473
- const mergedExtras = new Set(extras);
19474
- for (const item of tail2) {
19475
- if (item.requirement.name === requirement.name && markerApplies(item.requirement.marker, environment, item.extras)) {
19476
- mergedSpecifiers.push(...item.requirement.specifiers);
19477
- for (const extra of item.extras) mergedExtras.add(extra);
19478
- } else {
19479
- rest.push(item);
19480
- }
19481
- }
19482
- requirement = { ...requirement, specifiers: mergedSpecifiers };
19483
- extras = [...mergedExtras];
19484
- if (!markerApplies(requirement.marker, environment, extras)) {
19485
- return solve(rest, {
19486
- ...state,
19487
- skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
19488
- });
19489
- }
19490
- const solveKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).sort().join(",")}:${[...extras].sort().join(",")}`;
19491
- const knownFailure = unsatisfiable.get(solveKey);
19492
- if (knownFailure) throw knownFailure;
19493
- const already = state.chosen.get(requirement.name);
19494
- if (already) {
19495
- const satisfied = requirement.specifiers.every(
19496
- (s) => compareVersions(already.version, s.version, s.op)
19684
+ const solved = await resolve2({
19685
+ client: options.client,
19686
+ requirements: options.requirements,
19687
+ environment,
19688
+ index: options.index ?? null,
19689
+ ...options.deadlineMs === void 0 ? {} : { deadlineMs: options.deadlineMs },
19690
+ ...options.signal === void 0 ? {} : { signal: options.signal },
19691
+ allowSourceBuilds: options.allowSourceBuilds ?? false,
19692
+ progress: { collecting: (name) => options.progress.collecting(name) }
19693
+ });
19694
+ const staged = [];
19695
+ const installed2 = [];
19696
+ for (const distribution of solved.distributions) {
19697
+ if (distribution.kind === "sdist") {
19698
+ throw new ResolutionError(
19699
+ `${distribution.name} ${distribution.version} resolved to a source distribution, and no local builder is configured to build it for this runtime`
19497
19700
  );
19498
- if (!satisfied) {
19499
- throw new Error(
19500
- `${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
19501
- );
19502
- }
19503
- return solve(rest, state);
19504
- }
19505
- options.progress.collecting(requirement.name);
19506
- if (unavailable2.has(requirement.name)) {
19507
- throw new Error(`${requirement.name} has no wheel this runtime can use`);
19508
- }
19509
- const candidateKey = `${requirement.name}:${requirement.specifiers.map((s) => `${s.op}${s.version}`).join(",")}`;
19510
- let candidatePromise = candidateCache.get(candidateKey);
19511
- if (!candidatePromise) {
19512
- candidatePromise = resolvePackageCandidates(options.client, requirement);
19513
- candidateCache.set(candidateKey, candidatePromise);
19514
- }
19515
- let candidates;
19516
- try {
19517
- candidates = await candidatePromise;
19518
- } catch (error) {
19519
- const anyKey = `${requirement.name}:`;
19520
- let anyPromise = candidateCache.get(anyKey);
19521
- if (!anyPromise) {
19522
- anyPromise = resolvePackageCandidates(
19523
- options.client,
19524
- { ...requirement, specifiers: [] });
19525
- candidateCache.set(anyKey, anyPromise);
19526
- }
19527
- try {
19528
- await anyPromise;
19529
- } catch {
19530
- unavailable2.add(requirement.name);
19531
- }
19532
- throw error;
19533
- }
19534
- let failure2;
19535
- for (const resolved of candidates) {
19536
- try {
19537
- candidateAttempts += 1;
19538
- if (candidateAttempts > MAX_CANDIDATE_ATTEMPTS) {
19539
- throw new Error(
19540
- "dependency resolution exceeded 96 wheel candidates; add version constraints for packages whose newest generation requires unavailable native WASM extensions"
19541
- );
19542
- }
19543
- let entries = archives.get(resolved.url);
19544
- if (!entries) {
19545
- options.progress.downloading(resolved.name, resolved.version);
19546
- entries = readZip(await options.client.bytes(resolved.url));
19547
- archives.set(resolved.url, entries);
19548
- }
19549
- const dependencies = [];
19550
- const metadata = entries.find((entry) => /\.dist-info\/METADATA$/.test(entry.name));
19551
- if (metadata) {
19552
- for (const line of new TextDecoder().decode(metadata.data()).split(/\r?\n/)) {
19553
- if (!line.startsWith("Requires-Dist:")) continue;
19554
- const dependency = parseRequirement(line.slice("Requires-Dist:".length));
19555
- if (dependency && markerApplies(dependency.marker, environment, extras)) {
19556
- dependencies.push({ requirement: dependency, extras: dependency.extras });
19557
- }
19558
- }
19559
- }
19560
- const chosen = new Map(state.chosen);
19561
- chosen.set(requirement.name, resolved);
19562
- return await solve([...dependencies, ...rest], {
19563
- chosen,
19564
- staged: [...state.staged, ...stageWheel(entries, resolved)],
19565
- installed: [...state.installed, { name: resolved.name, version: resolved.version }],
19566
- skipped: state.skipped
19567
- });
19568
- } catch (error) {
19569
- if (error?.message?.startsWith("dependency resolution exceeded")) throw error;
19570
- failure2 = error;
19571
- }
19572
19701
  }
19573
- const resolutionError = failure2 instanceof Error ? failure2 : new Error(`could not resolve ${requirement.name}`);
19574
- unsatisfiable.set(solveKey, resolutionError);
19575
- throw resolutionError;
19576
- };
19577
- const solved = await solve(queue, {
19578
- chosen: /* @__PURE__ */ new Map(),
19579
- staged: [],
19580
- installed: [],
19581
- skipped: []
19582
- });
19583
- report.installed = solved.installed;
19584
- report.skipped = solved.skipped;
19585
- commit(options.vfs, options.cred, solved.staged);
19586
- return report;
19702
+ options.progress.downloading(distribution.name, distribution.version);
19703
+ const bytes2 = await options.client.bytes(distribution.url, { timeoutMs: 12e4 });
19704
+ verifyDigest(distribution, bytes2);
19705
+ staged.push(...stageWheel(readZip(bytes2)));
19706
+ installed2.push({ name: distribution.name, version: distribution.version });
19707
+ }
19708
+ commit(options.vfs, options.cred, staged);
19709
+ return { installed: installed2, skipped: solved.skipped };
19710
+ }
19711
+ function verifyDigest(distribution, bytes2) {
19712
+ if (!distribution.sha256) {
19713
+ throw new Error(
19714
+ `${distribution.filename} was published without a sha256 digest, so it cannot be verified`
19715
+ );
19716
+ }
19717
+ const actual = [...sha256.sha256(bytes2)].map((b) => b.toString(16).padStart(2, "0")).join("");
19718
+ if (actual !== distribution.sha256) {
19719
+ throw new Error(
19720
+ `${distribution.filename} failed verification: the index published sha256 ${distribution.sha256} but the download hashes to ${actual}`
19721
+ );
19722
+ }
19587
19723
  }
19588
19724
  function stageWheel(entries, resolved) {
19589
19725
  const staged = [];
@@ -19806,34 +19942,52 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
19806
19942
  "outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
19807
19943
  );
19808
19944
  }
19809
- const jsonCache = /* @__PURE__ */ new Map();
19810
- const bytesCache = /* @__PURE__ */ new Map();
19811
- const client = {
19812
- async json(url) {
19813
- let pending = jsonCache.get(url);
19814
- if (!pending) {
19815
- pending = fetch(url).then((response) => {
19816
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19817
- return response.json();
19818
- });
19819
- jsonCache.set(url, pending);
19820
- }
19821
- return pending;
19822
- },
19823
- async bytes(url) {
19824
- let pending = bytesCache.get(url);
19825
- if (!pending) {
19826
- pending = fetch(url).then(async (response) => {
19827
- if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19828
- return new Uint8Array(await response.arrayBuffer());
19829
- });
19830
- bytesCache.set(url, pending);
19945
+ const cache = /* @__PURE__ */ new Map();
19946
+ const request = async (url, timeoutMs, read) => {
19947
+ const controller = new AbortController();
19948
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
19949
+ try {
19950
+ const response = await fetch(url, { signal: controller.signal });
19951
+ if (!response.ok) throw new Error(`${url} answered ${response.status}`);
19952
+ return await read(response);
19953
+ } catch (error) {
19954
+ if (controller.signal.aborted) {
19955
+ throw new Error(`${url} did not answer within ${Math.round(timeoutMs / 1e3)}s`);
19831
19956
  }
19832
- return pending;
19957
+ throw error;
19958
+ } finally {
19959
+ clearTimeout(timer);
19833
19960
  }
19834
19961
  };
19962
+ const once = (url, produce) => {
19963
+ let pending = cache.get(url);
19964
+ if (!pending) {
19965
+ pending = produce();
19966
+ cache.set(url, pending);
19967
+ }
19968
+ return pending;
19969
+ };
19970
+ const client = {
19971
+ json: (url, o) => once(`json:${url}`, () => request(url, o?.timeoutMs ?? 3e4, (r) => r.json())),
19972
+ text: (url, o) => once(`text:${url}`, () => request(url, o?.timeoutMs ?? 3e4, (r) => r.text())),
19973
+ bytes: (url, o) => once(`bytes:${url}`, () => request(url, o?.timeoutMs ?? 12e4, async (r) => new Uint8Array(await r.arrayBuffer())))
19974
+ };
19975
+ let index = null;
19976
+ const configured = pythonBackend().wheelIndex ?? null;
19977
+ if (typeof configured === "string") {
19978
+ try {
19979
+ const url = configured.replace(/\/$/, "");
19980
+ const loaded = await client.json(`${url}/index.json`);
19981
+ index = { baseUrl: url, wheels: loaded.wheels ?? [] };
19982
+ } catch (error) {
19983
+ return ctx.fail(`could not read the wheel index at ${configured}: ${error.message}`);
19984
+ }
19985
+ } else if (configured) {
19986
+ index = configured;
19987
+ }
19835
19988
  try {
19836
19989
  const report = await installRequirements({
19990
+ index,
19837
19991
  client,
19838
19992
  vfs: ctx.vfs,
19839
19993
  cred: ctx.cred,
@@ -21278,7 +21432,8 @@ function configurePython(options = {}) {
21278
21432
  setPythonBackend({
21279
21433
  ...options.backend !== void 0 ? { backend: options.backend } : {},
21280
21434
  ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
21281
- ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {}
21435
+ ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
21436
+ ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {}
21282
21437
  });
21283
21438
  }
21284
21439
  var isPythonAvailable = isCPythonAvailable;
@@ -25208,10 +25363,10 @@ function promisify(original) {
25208
25363
  const override = original[promisifyCustom];
25209
25364
  if (typeof override === "function") return override;
25210
25365
  const wrapped = function(...args) {
25211
- return new Promise((resolve2, reject) => {
25366
+ return new Promise((resolve3, reject) => {
25212
25367
  original.call(this, ...args, (error, ...values) => {
25213
25368
  if (error) reject(error);
25214
- else resolve2(values.length > 1 ? values : values[0]);
25369
+ else resolve3(values.length > 1 ? values : values[0]);
25215
25370
  });
25216
25371
  });
25217
25372
  };
@@ -25409,14 +25564,14 @@ var AssertionError = class extends Error {
25409
25564
  code = "ERR_ASSERTION";
25410
25565
  constructor(options) {
25411
25566
  const generated = options.message === void 0;
25412
- super(options.message ?? describe2(options.actual, options.expected, options.operator));
25567
+ super(options.message ?? describe(options.actual, options.expected, options.operator));
25413
25568
  this.actual = options.actual;
25414
25569
  this.expected = options.expected;
25415
25570
  this.operator = options.operator;
25416
25571
  this.generatedMessage = generated;
25417
25572
  }
25418
25573
  };
25419
- function describe2(actual, expected, operator) {
25574
+ function describe(actual, expected, operator) {
25420
25575
  if (operator === "fail") return "Failed";
25421
25576
  const rendered = (value) => inspect(value, { depth: 2 });
25422
25577
  return `${rendered(actual)} ${operator} ${rendered(expected)}`;
@@ -25790,8 +25945,8 @@ var VirtualServerResponse = class extends streamModule4__default.default.Writabl
25790
25945
  constructor(request) {
25791
25946
  super();
25792
25947
  this.req = request;
25793
- this.completed = new Promise((resolve2) => {
25794
- this.resolve = resolve2;
25948
+ this.completed = new Promise((resolve3) => {
25949
+ this.resolve = resolve3;
25795
25950
  });
25796
25951
  }
25797
25952
  _write(chunk, encoding, callback) {
@@ -26968,7 +27123,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
26968
27123
  this.pending.push(callback);
26969
27124
  return;
26970
27125
  }
26971
- return new Promise((resolve2) => this.pending.push(resolve2));
27126
+ return new Promise((resolve3) => this.pending.push(resolve3));
26972
27127
  }
26973
27128
  prompt() {
26974
27129
  this.output?.write?.(this.promptText);
@@ -27125,14 +27280,14 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
27125
27280
  yield line;
27126
27281
  continue;
27127
27282
  }
27128
- const next = await new Promise((resolve2) => {
27283
+ const next = await new Promise((resolve3) => {
27129
27284
  const onLine = (value) => {
27130
27285
  cleanup();
27131
- resolve2(value);
27286
+ resolve3(value);
27132
27287
  };
27133
27288
  const onClose = () => {
27134
27289
  cleanup();
27135
- resolve2(null);
27290
+ resolve3(null);
27136
27291
  };
27137
27292
  const cleanup = () => {
27138
27293
  this.off("line", onLine);
@@ -27509,13 +27664,13 @@ var ASYNC_FS_METHODS = [
27509
27664
  "exists"
27510
27665
  ];
27511
27666
  function createPathModule(cwd) {
27512
- const resolve2 = (...segments2) => pathModule__default.default.resolve(cwd(), ...segments2);
27667
+ const resolve3 = (...segments2) => pathModule__default.default.resolve(cwd(), ...segments2);
27513
27668
  const path = {
27514
27669
  ...pathModule__default.default,
27515
- resolve: resolve2,
27670
+ resolve: resolve3,
27516
27671
  /* `relative` resolves both operands, and the implementation would do so
27517
27672
  * against the host cwd, so they are resolved here first. */
27518
- relative: (from, to) => pathModule__default.default.relative(resolve2(from), resolve2(to))
27673
+ relative: (from, to) => pathModule__default.default.relative(resolve3(from), resolve3(to))
27519
27674
  };
27520
27675
  path.posix = path;
27521
27676
  path.win32 = pathModule__default.default.win32 ?? path;
@@ -28179,8 +28334,8 @@ function createTrackedTimers() {
28179
28334
  }
28180
28335
  function createTimerPromises(timers) {
28181
28336
  return {
28182
- setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
28183
- setImmediate: (value) => new Promise((resolve2) => timers.setImmediate(resolve2, value))
28337
+ setTimeout: (delay, value) => new Promise((resolve3) => timers.setTimeout(resolve3, delay, value)),
28338
+ setImmediate: (value) => new Promise((resolve3) => timers.setImmediate(resolve3, value))
28184
28339
  };
28185
28340
  }
28186
28341
  function createAsyncHooksModule() {
@@ -28259,12 +28414,12 @@ function createAsyncHooksModule() {
28259
28414
  }
28260
28415
  function createStreamPromises() {
28261
28416
  return {
28262
- pipeline: (...streams) => new Promise((resolve2, reject) => {
28263
- const callback = (error) => error ? reject(error) : resolve2();
28417
+ pipeline: (...streams) => new Promise((resolve3, reject) => {
28418
+ const callback = (error) => error ? reject(error) : resolve3();
28264
28419
  streamModule4__default.default.pipeline(...streams, callback);
28265
28420
  }),
28266
- finished: (stream) => new Promise((resolve2, reject) => {
28267
- streamModule4__default.default.finished(stream, (error) => error ? reject(error) : resolve2());
28421
+ finished: (stream) => new Promise((resolve3, reject) => {
28422
+ streamModule4__default.default.finished(stream, (error) => error ? reject(error) : resolve3());
28268
28423
  })
28269
28424
  };
28270
28425
  }
@@ -29328,8 +29483,8 @@ var LocalProcess = class extends EventEmitter4__default.default {
29328
29483
  this.task = task;
29329
29484
  super.on("error", () => {
29330
29485
  });
29331
- this.completion = new Promise((resolve2) => {
29332
- this.resolveCompletion = resolve2;
29486
+ this.completion = new Promise((resolve3) => {
29487
+ this.resolveCompletion = resolve3;
29333
29488
  });
29334
29489
  setTimeout(() => void this.start(), 0);
29335
29490
  }
@@ -29346,8 +29501,8 @@ var LocalProcess = class extends EventEmitter4__default.default {
29346
29501
  pendingInput = [];
29347
29502
  resolveKilled;
29348
29503
  cleanup;
29349
- killedPromise = new Promise((resolve2) => {
29350
- this.resolveKilled = resolve2;
29504
+ killedPromise = new Promise((resolve3) => {
29505
+ this.resolveKilled = resolve3;
29351
29506
  });
29352
29507
  on(event, listener) {
29353
29508
  return super.on(event, listener);
@@ -29777,7 +29932,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
29777
29932
  const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
29778
29933
  idleTurns = busy ? 0 : idleTurns + 1;
29779
29934
  if (idleTurns >= DRAIN_TURNS) return;
29780
- await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
29935
+ await new Promise((resolve3) => setTimeout(resolve3, busy ? 5 : 0));
29781
29936
  }
29782
29937
  }
29783
29938
  async request(_port, _init = {}) {
@@ -29963,8 +30118,8 @@ var WorkerProcess = class extends EventEmitter4__default.default {
29963
30118
  this.release = release;
29964
30119
  super.on("error", () => {
29965
30120
  });
29966
- this.completion = new Promise((resolve2) => {
29967
- this.resolveCompletion = resolve2;
30121
+ this.completion = new Promise((resolve3) => {
30122
+ this.resolveCompletion = resolve3;
29968
30123
  });
29969
30124
  }
29970
30125
  worker;
@@ -30206,7 +30361,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30206
30361
  }
30207
30362
  /** Run a child to completion and collect it, for the guest's `spawnSync`. */
30208
30363
  runChildToCompletion(request, streamTo, owned) {
30209
- return new Promise((resolve2) => {
30364
+ return new Promise((resolve3) => {
30210
30365
  let handle;
30211
30366
  try {
30212
30367
  handle = this.processManager.spawn({
@@ -30218,7 +30373,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30218
30373
  });
30219
30374
  } catch (error) {
30220
30375
  const failure2 = error;
30221
- resolve2({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure2.code ? { code: failure2.code } : {}, message: failure2.message } });
30376
+ resolve3({ status: null, stdout: "", stderr: "", signal: null, error: { ...failure2.code ? { code: failure2.code } : {}, message: failure2.message } });
30222
30377
  return;
30223
30378
  }
30224
30379
  owned.add(handle);
@@ -30236,7 +30391,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30236
30391
  stderr += text2;
30237
30392
  live?.error(text2);
30238
30393
  });
30239
- handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
30394
+ handle.on("exit", (code) => resolve3({ status: code, stdout, stderr, signal: null }));
30240
30395
  handle.exec();
30241
30396
  if (request.input !== void 0) {
30242
30397
  handle.sendStdin?.(request.input);
@@ -30275,7 +30430,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30275
30430
  for await (const chunk of request) chunks.push(chunk);
30276
30431
  const body = concat5(chunks);
30277
30432
  const id = this.nextRequestId++;
30278
- const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
30433
+ const answered = new Promise((resolve3) => this.waiting.set(id, resolve3));
30279
30434
  worker.postMessage({
30280
30435
  type: "http-request",
30281
30436
  id,
@@ -30322,10 +30477,10 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30322
30477
  });
30323
30478
  }
30324
30479
  settleProxied(id, response) {
30325
- const resolve2 = this.waiting.get(id);
30326
- if (!resolve2) return;
30480
+ const resolve3 = this.waiting.get(id);
30481
+ if (!resolve3) return;
30327
30482
  this.waiting.delete(id);
30328
- resolve2(response);
30483
+ resolve3(response);
30329
30484
  }
30330
30485
  closeProxies(owner) {
30331
30486
  for (const [port, server] of [...this.proxies]) {
@@ -30537,7 +30692,7 @@ var Container = class _Container {
30537
30692
  try {
30538
30693
  exitCode = timeoutMs === void 0 ? await shell.execute(command, io) : await Promise.race([
30539
30694
  shell.execute(command, io),
30540
- new Promise((resolve2) => setTimeout(() => resolve2(137), timeoutMs + 500))
30695
+ new Promise((resolve3) => setTimeout(() => resolve3(137), timeoutMs + 500))
30541
30696
  ]);
30542
30697
  } finally {
30543
30698
  if (timer) clearTimeout(timer);
@@ -30800,16 +30955,16 @@ var Container = class _Container {
30800
30955
  });
30801
30956
  const hostPort = opts.hostPort ?? 0;
30802
30957
  const hostname = opts.hostname ?? "127.0.0.1";
30803
- await new Promise((resolve2, reject) => {
30958
+ await new Promise((resolve3, reject) => {
30804
30959
  server.once("error", reject);
30805
- server.listen(hostPort, hostname, () => resolve2());
30960
+ server.listen(hostPort, hostname, () => resolve3());
30806
30961
  });
30807
30962
  const address = server.address();
30808
30963
  const actualPort = typeof address === "object" && address ? address.port : hostPort;
30809
30964
  return {
30810
30965
  url: `http://${hostname}:${actualPort}`,
30811
30966
  port: actualPort,
30812
- close: () => new Promise((resolve2) => server.close(() => resolve2()))
30967
+ close: () => new Promise((resolve3) => server.close(() => resolve3()))
30813
30968
  };
30814
30969
  }
30815
30970
  // ── persistence ───────────────────────────────────────────────────────────
@@ -31373,18 +31528,18 @@ async function createPreview(box, options = {}) {
31373
31528
  const worker = registration.active ?? registration.waiting ?? registration.installing;
31374
31529
  if (!worker) return null;
31375
31530
  if (worker.state !== "activated") {
31376
- const activated = await new Promise((resolve2) => {
31531
+ const activated = await new Promise((resolve3) => {
31377
31532
  const check = () => {
31378
31533
  if (worker.state === "activated") {
31379
31534
  worker.removeEventListener("statechange", check);
31380
- resolve2(true);
31535
+ resolve3(true);
31381
31536
  } else if (worker.state === "redundant") {
31382
31537
  worker.removeEventListener("statechange", check);
31383
- resolve2(false);
31538
+ resolve3(false);
31384
31539
  }
31385
31540
  };
31386
31541
  worker.addEventListener("statechange", check);
31387
- setTimeout(() => resolve2(worker.state === "activated"), 1e4);
31542
+ setTimeout(() => resolve3(worker.state === "activated"), 1e4);
31388
31543
  check();
31389
31544
  });
31390
31545
  if (!activated) return null;