sandboxedjs 0.1.50 → 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) => {
@@ -19107,7 +19196,7 @@ async function startNodeWorker(url, workerData) {
19107
19196
  /* webpackIgnore: true */
19108
19197
  specifier
19109
19198
  );
19110
- const worker = new NodeWorker(url, { workerData });
19199
+ const worker = new NodeWorker(url, { workerData, execArgv: [] });
19111
19200
  return {
19112
19201
  postMessage: (message) => worker.postMessage(message),
19113
19202
  onMessage: (listener) => worker.on("message", listener),
@@ -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,56 +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 ordered = (stable.length > 0 ? stable : candidates).reverse();
19384
- let sawSourceOnly = false;
19385
- const resolved = [];
19386
- for (const version of ordered) {
19387
- const files = (index.releases[version] ?? []).filter((file3) => !file3.yanked);
19388
- const wheel = pickWheel(files);
19389
- if (wheel) {
19390
- resolved.push({
19391
- 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),
19392
19628
  version,
19393
- url: wheel.url,
19394
- filename: wheel.filename,
19395
- sha256: wheel.digests?.sha256 ?? "",
19396
- requires: []
19629
+ kind,
19630
+ url: file3.url,
19631
+ filename: file3.filename,
19632
+ sha256: file3.digests?.sha256 ?? "",
19633
+ metadataUrl: metadataUrlFor(file3)
19397
19634
  });
19398
- continue;
19399
19635
  }
19400
- if (files.some((file3) => file3.packagetype === "sdist")) sawSourceOnly = true;
19401
19636
  }
19402
- if (resolved.length > 0) return resolved;
19403
- 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") {
19404
19648
  throw new Error(
19405
- `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`
19406
19650
  );
19407
19651
  }
19408
- throw new Error(
19409
- 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 ")
19410
- );
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()));
19411
19657
  }
19412
- function pickWheel(files) {
19413
- const wheels = files.filter((file3) => file3.packagetype === "bdist_wheel");
19414
- for (const tag2 of ACCEPTED_TAGS) {
19415
- const match2 = wheels.find((file3) => file3.filename.endsWith(`-${tag2}.whl`));
19416
- 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());
19417
19663
  }
19418
- return null;
19419
- }
19420
- function describe(requirement) {
19421
- return requirement.specifiers.length === 0 ? "any version" : requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ");
19422
- }
19423
- function splitOnce(text2, separator) {
19424
- const at = text2.indexOf(separator);
19425
- return at < 0 ? [text2] : [text2.slice(0, at), text2.slice(at + 1)];
19664
+ return result;
19426
19665
  }
19427
19666
 
19428
19667
  // src/runtime/python/install.ts
@@ -19442,123 +19681,45 @@ function markerEnvironment(pythonVersion) {
19442
19681
  }
19443
19682
  async function installRequirements(options) {
19444
19683
  const environment = markerEnvironment(options.pythonVersion);
19445
- const report = { installed: [], skipped: [] };
19446
- const queue = [];
19447
- for (const text2 of options.requirements) {
19448
- const requirement = parseRequirement(text2);
19449
- if (requirement) queue.push({ requirement, extras: requirement.extras });
19450
- }
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];
19471
- if (!markerApplies(requirement.marker, environment, extras)) {
19472
- return solve(rest, {
19473
- ...state,
19474
- skipped: [...state.skipped, { name: requirement.name, marker: requirement.marker ?? "" }]
19475
- });
19476
- }
19477
- const already = state.chosen.get(requirement.name);
19478
- if (already) {
19479
- const satisfied = requirement.specifiers.every(
19480
- (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`
19481
19700
  );
19482
- if (!satisfied) {
19483
- throw new Error(
19484
- `${requirement.name} ${already.version} is already selected but another dependency needs ` + requirement.specifiers.map((s) => `${s.op}${s.version}`).join(", ")
19485
- );
19486
- }
19487
- return solve(rest, state);
19488
- }
19489
- options.progress.collecting(requirement.name);
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
19701
  }
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);
19561
- 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
+ }
19562
19723
  }
19563
19724
  function stageWheel(entries, resolved) {
19564
19725
  const staged = [];
@@ -19781,34 +19942,52 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
19781
19942
  "outbound network access is disabled for this container (enable with network: { allowOutbound: true })"
19782
19943
  );
19783
19944
  }
19784
- const jsonCache = /* @__PURE__ */ new Map();
19785
- const bytesCache = /* @__PURE__ */ new Map();
19786
- const client = {
19787
- async json(url) {
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;
19797
- },
19798
- async bytes(url) {
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);
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`);
19806
19956
  }
19807
- return pending;
19957
+ throw error;
19958
+ } finally {
19959
+ clearTimeout(timer);
19808
19960
  }
19809
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
+ }
19810
19988
  try {
19811
19989
  const report = await installRequirements({
19990
+ index,
19812
19991
  client,
19813
19992
  vfs: ctx.vfs,
19814
19993
  cred: ctx.cred,
@@ -21253,7 +21432,8 @@ function configurePython(options = {}) {
21253
21432
  setPythonBackend({
21254
21433
  ...options.backend !== void 0 ? { backend: options.backend } : {},
21255
21434
  ...options.manifest !== void 0 ? { manifest: options.manifest } : {},
21256
- ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {}
21435
+ ...options.workerUrl !== void 0 ? { workerUrl: options.workerUrl } : {},
21436
+ ...options.wheelIndex !== void 0 ? { wheelIndex: options.wheelIndex } : {}
21257
21437
  });
21258
21438
  }
21259
21439
  var isPythonAvailable = isCPythonAvailable;
@@ -25183,10 +25363,10 @@ function promisify(original) {
25183
25363
  const override = original[promisifyCustom];
25184
25364
  if (typeof override === "function") return override;
25185
25365
  const wrapped = function(...args) {
25186
- return new Promise((resolve2, reject) => {
25366
+ return new Promise((resolve3, reject) => {
25187
25367
  original.call(this, ...args, (error, ...values) => {
25188
25368
  if (error) reject(error);
25189
- else resolve2(values.length > 1 ? values : values[0]);
25369
+ else resolve3(values.length > 1 ? values : values[0]);
25190
25370
  });
25191
25371
  });
25192
25372
  };
@@ -25384,14 +25564,14 @@ var AssertionError = class extends Error {
25384
25564
  code = "ERR_ASSERTION";
25385
25565
  constructor(options) {
25386
25566
  const generated = options.message === void 0;
25387
- super(options.message ?? describe2(options.actual, options.expected, options.operator));
25567
+ super(options.message ?? describe(options.actual, options.expected, options.operator));
25388
25568
  this.actual = options.actual;
25389
25569
  this.expected = options.expected;
25390
25570
  this.operator = options.operator;
25391
25571
  this.generatedMessage = generated;
25392
25572
  }
25393
25573
  };
25394
- function describe2(actual, expected, operator) {
25574
+ function describe(actual, expected, operator) {
25395
25575
  if (operator === "fail") return "Failed";
25396
25576
  const rendered = (value) => inspect(value, { depth: 2 });
25397
25577
  return `${rendered(actual)} ${operator} ${rendered(expected)}`;
@@ -25765,8 +25945,8 @@ var VirtualServerResponse = class extends streamModule4__default.default.Writabl
25765
25945
  constructor(request) {
25766
25946
  super();
25767
25947
  this.req = request;
25768
- this.completed = new Promise((resolve2) => {
25769
- this.resolve = resolve2;
25948
+ this.completed = new Promise((resolve3) => {
25949
+ this.resolve = resolve3;
25770
25950
  });
25771
25951
  }
25772
25952
  _write(chunk, encoding, callback) {
@@ -26943,7 +27123,7 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
26943
27123
  this.pending.push(callback);
26944
27124
  return;
26945
27125
  }
26946
- return new Promise((resolve2) => this.pending.push(resolve2));
27126
+ return new Promise((resolve3) => this.pending.push(resolve3));
26947
27127
  }
26948
27128
  prompt() {
26949
27129
  this.output?.write?.(this.promptText);
@@ -27100,14 +27280,14 @@ function createReadlineModule(defaultInput, defaultOutput = () => void 0) {
27100
27280
  yield line;
27101
27281
  continue;
27102
27282
  }
27103
- const next = await new Promise((resolve2) => {
27283
+ const next = await new Promise((resolve3) => {
27104
27284
  const onLine = (value) => {
27105
27285
  cleanup();
27106
- resolve2(value);
27286
+ resolve3(value);
27107
27287
  };
27108
27288
  const onClose = () => {
27109
27289
  cleanup();
27110
- resolve2(null);
27290
+ resolve3(null);
27111
27291
  };
27112
27292
  const cleanup = () => {
27113
27293
  this.off("line", onLine);
@@ -27484,13 +27664,13 @@ var ASYNC_FS_METHODS = [
27484
27664
  "exists"
27485
27665
  ];
27486
27666
  function createPathModule(cwd) {
27487
- const resolve2 = (...segments2) => pathModule__default.default.resolve(cwd(), ...segments2);
27667
+ const resolve3 = (...segments2) => pathModule__default.default.resolve(cwd(), ...segments2);
27488
27668
  const path = {
27489
27669
  ...pathModule__default.default,
27490
- resolve: resolve2,
27670
+ resolve: resolve3,
27491
27671
  /* `relative` resolves both operands, and the implementation would do so
27492
27672
  * against the host cwd, so they are resolved here first. */
27493
- relative: (from, to) => pathModule__default.default.relative(resolve2(from), resolve2(to))
27673
+ relative: (from, to) => pathModule__default.default.relative(resolve3(from), resolve3(to))
27494
27674
  };
27495
27675
  path.posix = path;
27496
27676
  path.win32 = pathModule__default.default.win32 ?? path;
@@ -28154,8 +28334,8 @@ function createTrackedTimers() {
28154
28334
  }
28155
28335
  function createTimerPromises(timers) {
28156
28336
  return {
28157
- setTimeout: (delay, value) => new Promise((resolve2) => timers.setTimeout(resolve2, delay, value)),
28158
- 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))
28159
28339
  };
28160
28340
  }
28161
28341
  function createAsyncHooksModule() {
@@ -28234,12 +28414,12 @@ function createAsyncHooksModule() {
28234
28414
  }
28235
28415
  function createStreamPromises() {
28236
28416
  return {
28237
- pipeline: (...streams) => new Promise((resolve2, reject) => {
28238
- const callback = (error) => error ? reject(error) : resolve2();
28417
+ pipeline: (...streams) => new Promise((resolve3, reject) => {
28418
+ const callback = (error) => error ? reject(error) : resolve3();
28239
28419
  streamModule4__default.default.pipeline(...streams, callback);
28240
28420
  }),
28241
- finished: (stream) => new Promise((resolve2, reject) => {
28242
- 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());
28243
28423
  })
28244
28424
  };
28245
28425
  }
@@ -29303,8 +29483,8 @@ var LocalProcess = class extends EventEmitter4__default.default {
29303
29483
  this.task = task;
29304
29484
  super.on("error", () => {
29305
29485
  });
29306
- this.completion = new Promise((resolve2) => {
29307
- this.resolveCompletion = resolve2;
29486
+ this.completion = new Promise((resolve3) => {
29487
+ this.resolveCompletion = resolve3;
29308
29488
  });
29309
29489
  setTimeout(() => void this.start(), 0);
29310
29490
  }
@@ -29321,8 +29501,8 @@ var LocalProcess = class extends EventEmitter4__default.default {
29321
29501
  pendingInput = [];
29322
29502
  resolveKilled;
29323
29503
  cleanup;
29324
- killedPromise = new Promise((resolve2) => {
29325
- this.resolveKilled = resolve2;
29504
+ killedPromise = new Promise((resolve3) => {
29505
+ this.resolveKilled = resolve3;
29326
29506
  });
29327
29507
  on(event, listener) {
29328
29508
  return super.on(event, listener);
@@ -29752,7 +29932,7 @@ var LocalRuntimePod = class _LocalRuntimePod {
29752
29932
  const busy = this.router.referencedPorts(owner).length > 0 || pendingHandles() > 0 || pendingRequests() > 0 || readingStdin();
29753
29933
  idleTurns = busy ? 0 : idleTurns + 1;
29754
29934
  if (idleTurns >= DRAIN_TURNS) return;
29755
- await new Promise((resolve2) => setTimeout(resolve2, busy ? 5 : 0));
29935
+ await new Promise((resolve3) => setTimeout(resolve3, busy ? 5 : 0));
29756
29936
  }
29757
29937
  }
29758
29938
  async request(_port, _init = {}) {
@@ -29938,8 +30118,8 @@ var WorkerProcess = class extends EventEmitter4__default.default {
29938
30118
  this.release = release;
29939
30119
  super.on("error", () => {
29940
30120
  });
29941
- this.completion = new Promise((resolve2) => {
29942
- this.resolveCompletion = resolve2;
30121
+ this.completion = new Promise((resolve3) => {
30122
+ this.resolveCompletion = resolve3;
29943
30123
  });
29944
30124
  }
29945
30125
  worker;
@@ -30181,7 +30361,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30181
30361
  }
30182
30362
  /** Run a child to completion and collect it, for the guest's `spawnSync`. */
30183
30363
  runChildToCompletion(request, streamTo, owned) {
30184
- return new Promise((resolve2) => {
30364
+ return new Promise((resolve3) => {
30185
30365
  let handle;
30186
30366
  try {
30187
30367
  handle = this.processManager.spawn({
@@ -30193,7 +30373,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30193
30373
  });
30194
30374
  } catch (error) {
30195
30375
  const failure2 = error;
30196
- 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 } });
30197
30377
  return;
30198
30378
  }
30199
30379
  owned.add(handle);
@@ -30211,7 +30391,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30211
30391
  stderr += text2;
30212
30392
  live?.error(text2);
30213
30393
  });
30214
- handle.on("exit", (code) => resolve2({ status: code, stdout, stderr, signal: null }));
30394
+ handle.on("exit", (code) => resolve3({ status: code, stdout, stderr, signal: null }));
30215
30395
  handle.exec();
30216
30396
  if (request.input !== void 0) {
30217
30397
  handle.sendStdin?.(request.input);
@@ -30250,7 +30430,7 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30250
30430
  for await (const chunk of request) chunks.push(chunk);
30251
30431
  const body = concat5(chunks);
30252
30432
  const id = this.nextRequestId++;
30253
- const answered = new Promise((resolve2) => this.waiting.set(id, resolve2));
30433
+ const answered = new Promise((resolve3) => this.waiting.set(id, resolve3));
30254
30434
  worker.postMessage({
30255
30435
  type: "http-request",
30256
30436
  id,
@@ -30297,10 +30477,10 @@ var WorkerRuntimePod = class _WorkerRuntimePod extends LocalRuntimePod {
30297
30477
  });
30298
30478
  }
30299
30479
  settleProxied(id, response) {
30300
- const resolve2 = this.waiting.get(id);
30301
- if (!resolve2) return;
30480
+ const resolve3 = this.waiting.get(id);
30481
+ if (!resolve3) return;
30302
30482
  this.waiting.delete(id);
30303
- resolve2(response);
30483
+ resolve3(response);
30304
30484
  }
30305
30485
  closeProxies(owner) {
30306
30486
  for (const [port, server] of [...this.proxies]) {
@@ -30512,7 +30692,7 @@ var Container = class _Container {
30512
30692
  try {
30513
30693
  exitCode = timeoutMs === void 0 ? await shell.execute(command, io) : await Promise.race([
30514
30694
  shell.execute(command, io),
30515
- new Promise((resolve2) => setTimeout(() => resolve2(137), timeoutMs + 500))
30695
+ new Promise((resolve3) => setTimeout(() => resolve3(137), timeoutMs + 500))
30516
30696
  ]);
30517
30697
  } finally {
30518
30698
  if (timer) clearTimeout(timer);
@@ -30775,16 +30955,16 @@ var Container = class _Container {
30775
30955
  });
30776
30956
  const hostPort = opts.hostPort ?? 0;
30777
30957
  const hostname = opts.hostname ?? "127.0.0.1";
30778
- await new Promise((resolve2, reject) => {
30958
+ await new Promise((resolve3, reject) => {
30779
30959
  server.once("error", reject);
30780
- server.listen(hostPort, hostname, () => resolve2());
30960
+ server.listen(hostPort, hostname, () => resolve3());
30781
30961
  });
30782
30962
  const address = server.address();
30783
30963
  const actualPort = typeof address === "object" && address ? address.port : hostPort;
30784
30964
  return {
30785
30965
  url: `http://${hostname}:${actualPort}`,
30786
30966
  port: actualPort,
30787
- close: () => new Promise((resolve2) => server.close(() => resolve2()))
30967
+ close: () => new Promise((resolve3) => server.close(() => resolve3()))
30788
30968
  };
30789
30969
  }
30790
30970
  // ── persistence ───────────────────────────────────────────────────────────
@@ -31348,18 +31528,18 @@ async function createPreview(box, options = {}) {
31348
31528
  const worker = registration.active ?? registration.waiting ?? registration.installing;
31349
31529
  if (!worker) return null;
31350
31530
  if (worker.state !== "activated") {
31351
- const activated = await new Promise((resolve2) => {
31531
+ const activated = await new Promise((resolve3) => {
31352
31532
  const check = () => {
31353
31533
  if (worker.state === "activated") {
31354
31534
  worker.removeEventListener("statechange", check);
31355
- resolve2(true);
31535
+ resolve3(true);
31356
31536
  } else if (worker.state === "redundant") {
31357
31537
  worker.removeEventListener("statechange", check);
31358
- resolve2(false);
31538
+ resolve3(false);
31359
31539
  }
31360
31540
  };
31361
31541
  worker.addEventListener("statechange", check);
31362
- setTimeout(() => resolve2(worker.state === "activated"), 1e4);
31542
+ setTimeout(() => resolve3(worker.state === "activated"), 1e4);
31363
31543
  check();
31364
31544
  });
31365
31545
  if (!activated) return null;