pyfig 0.6.2 → 0.7.0

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.
Files changed (44) hide show
  1. package/README.md +47 -34
  2. package/SECURITY.md +1 -1
  3. package/dist/cjs/index.js +409 -261
  4. package/dist/cjs/index.js.map +10 -9
  5. package/dist/cjs/sync-resolver.js +369 -236
  6. package/dist/cjs/sync-resolver.js.map +9 -8
  7. package/dist/cli.js +12 -3
  8. package/dist/cli.js.map +1 -1
  9. package/dist/client.d.ts +6 -6
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +31 -21
  12. package/dist/client.js.map +1 -1
  13. package/dist/config.d.ts +9 -2
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +48 -1
  16. package/dist/config.js.map +1 -1
  17. package/dist/index.d.ts +3 -4
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/package-target.d.ts +3 -3
  22. package/dist/package-target.d.ts.map +1 -1
  23. package/dist/package-target.js +6 -6
  24. package/dist/package-target.js.map +1 -1
  25. package/dist/provision.d.ts +8 -12
  26. package/dist/provision.d.ts.map +1 -1
  27. package/dist/provision.js +156 -158
  28. package/dist/provision.js.map +1 -1
  29. package/dist/python-venv.d.ts +12 -0
  30. package/dist/python-venv.d.ts.map +1 -0
  31. package/dist/{virtual-environment.js → python-venv.js} +12 -9
  32. package/dist/python-venv.js.map +1 -0
  33. package/dist/runners.js +12 -12
  34. package/dist/runners.js.map +1 -1
  35. package/dist/sync-resolver.js +2 -2
  36. package/dist/sync-resolver.js.map +1 -1
  37. package/dist/venv-identity.d.ts +36 -0
  38. package/dist/venv-identity.d.ts.map +1 -0
  39. package/dist/venv-identity.js +80 -0
  40. package/dist/venv-identity.js.map +1 -0
  41. package/package.json +2 -2
  42. package/dist/virtual-environment.d.ts +0 -11
  43. package/dist/virtual-environment.d.ts.map +0 -1
  44. package/dist/virtual-environment.js.map +0 -1
@@ -63,17 +63,16 @@ var __export = (target, all) => {
63
63
  };
64
64
 
65
65
  // src/sync-resolver.ts
66
- var import_node_fs5 = require("node:fs");
66
+ var import_node_fs4 = require("node:fs");
67
67
 
68
68
  // src/provision.ts
69
- var import_node_crypto = require("node:crypto");
69
+ var import_node_crypto2 = require("node:crypto");
70
70
  var import_node_fs3 = require("node:fs");
71
- var import_promises = require("node:fs/promises");
72
- var import_node_fs4 = __toESM(require("node:fs"), 1);
71
+ var import_promises2 = require("node:fs/promises");
73
72
  var import_node_os = __toESM(require("node:os"), 1);
74
- var import_node_path5 = __toESM(require("node:path"), 1);
73
+ var import_node_path6 = __toESM(require("node:path"), 1);
75
74
  var import_node_stream = require("node:stream");
76
- var import_promises2 = require("node:stream/promises");
75
+ var import_promises3 = require("node:stream/promises");
77
76
  var import_fflate = require("fflate");
78
77
  var tar = __toESM(require("tar"), 1);
79
78
  var import_undici = require("undici");
@@ -150,7 +149,11 @@ function resolveOptions(options = {}) {
150
149
  const configuredProjectRoot = options.projectRoot ?? env["PYFIG_PROJECT_ROOT"];
151
150
  const projectRoot = configuredProjectRoot ? import_node_path2.default.resolve(configuredProjectRoot) : resolveProjectRoot(env["INIT_CWD"] ?? process.cwd());
152
151
  const autoInstall = options.autoInstall ?? parseBoolean(env["PYFIG_AUTO_INSTALL"], true);
152
+ const dependencies = normalizeDependencies(options.dependencies ?? []);
153
+ const requirements = normalizeRequirements(options.requirements ?? [], projectRoot);
153
154
  return {
155
+ dependencies,
156
+ requirements,
154
157
  python: options.python ?? env["PYFIG_PYTHON"] ?? "",
155
158
  requiresPython,
156
159
  managedPython,
@@ -170,7 +173,10 @@ function resolveOptions(options = {}) {
170
173
  env
171
174
  };
172
175
  }
173
- function uvEnvironment(options) {
176
+ function hasRuntimeRequirements(options) {
177
+ return options.dependencies.length > 0 || options.requirements.length > 0;
178
+ }
179
+ function uvProcessEnvironment(options) {
174
180
  return {
175
181
  ...options.env,
176
182
  UV_PYTHON_INSTALL_DIR: import_node_path2.default.join(options.cacheDir, "python"),
@@ -212,46 +218,68 @@ function parseBoolean(value, fallback) {
212
218
  return false;
213
219
  throw new TypeError(`Invalid boolean value: ${value}`);
214
220
  }
215
-
216
- // src/provision-lock.ts
217
- var import_loqkit = require("loqkit");
218
- async function withProvisioningLock(options) {
219
- let existing;
220
- const result = await import_loqkit.withLock({ resource: options.key }, options.mutate, {
221
- key: options.key,
222
- name: "Pyfig provisioning",
223
- namespace: "pyfig",
224
- timeout: options.timeoutMs,
225
- predicate: async () => {
226
- const value = await options.check();
227
- if (value === null)
228
- return true;
229
- existing = { value };
230
- return false;
221
+ function normalizeDependencies(dependencies) {
222
+ const normalized = dependencies.map((dependency) => dependency.trim());
223
+ for (const dependency of normalized) {
224
+ if (!dependency || /[\0\r\n]/u.test(dependency)) {
225
+ throw new TypeError(`Invalid runtime dependency: ${JSON.stringify(dependency)}`);
231
226
  }
232
- });
233
- if (result.context.state === "executed")
234
- return result.value;
235
- if (existing)
236
- return existing.value;
237
- throw new Error(`Provisioning check for ${options.key} did not produce a resource.`);
227
+ }
228
+ return normalized;
229
+ }
230
+ function normalizeRequirements(requirements, projectRoot) {
231
+ const normalized = [];
232
+ for (const requirement of requirements) {
233
+ const candidate = requirement.trim();
234
+ if (!candidate || /[\0\r\n]/u.test(candidate)) {
235
+ throw new TypeError(`Invalid requirements file path: ${JSON.stringify(candidate)}`);
236
+ }
237
+ const absolute = import_node_path2.default.resolve(projectRoot, candidate);
238
+ const basename = import_node_path2.default.basename(absolute).toLowerCase();
239
+ if (isPythonProjectFile(basename)) {
240
+ throw new TypeError(`Expected a requirements file, not Python project metadata: ${absolute}`);
241
+ }
242
+ normalized.push(absolute);
243
+ }
244
+ return normalized;
245
+ }
246
+ var PYTHON_PROJECT_FILES = new Set([
247
+ "pdm.lock",
248
+ "pipfile",
249
+ "pipfile.lock",
250
+ "poetry.lock",
251
+ "pyproject.toml",
252
+ "setup.cfg",
253
+ "setup.py",
254
+ "uv.lock"
255
+ ]);
256
+ function isPythonProjectFile(basename) {
257
+ return PYTHON_PROJECT_FILES.has(basename) || /^pylock(?:\.[^.]+)?\.toml$/u.test(basename);
238
258
  }
239
259
 
240
- // src/virtual-environment.ts
260
+ // src/venv-identity.ts
261
+ var import_node_crypto = require("node:crypto");
262
+ var import_promises = require("node:fs/promises");
263
+ var import_node_path4 = __toESM(require("node:path"), 1);
264
+
265
+ // src/python-venv.ts
241
266
  var import_node_fs2 = __toESM(require("node:fs"), 1);
242
267
  var import_node_path3 = __toESM(require("node:path"), 1);
243
- var VIRTUAL_ENV_DIRECTORY_NAME = import_node_path3.default.join("node_modules", ".pyfig", "venv");
244
- function virtualEnvironmentDirectory(projectRoot) {
245
- return import_node_path3.default.join(projectRoot, VIRTUAL_ENV_DIRECTORY_NAME);
268
+ var VIRTUAL_ENV_ROOT_NAME = import_node_path3.default.join("node_modules", ".pyfig", "venvs");
269
+ function pythonVenvRoot(projectRoot) {
270
+ return import_node_path3.default.join(projectRoot, VIRTUAL_ENV_ROOT_NAME);
246
271
  }
247
- function externalVirtualEnvironmentDirectories(projectRoot, env) {
272
+ function pythonVenvDirectory(projectRoot, venvDirectory) {
273
+ return import_node_path3.default.join(pythonVenvRoot(projectRoot), venvDirectory);
274
+ }
275
+ function externalPythonVenvDirectories(projectRoot, env) {
248
276
  const candidates = [env["VIRTUAL_ENV"], import_node_path3.default.join(projectRoot, ".venv")].filter((value) => Boolean(value)).map((value) => import_node_path3.default.resolve(value));
249
277
  return [...new Map(candidates.map((value) => [canonicalPath(value), value])).values()];
250
278
  }
251
- function virtualEnvironmentPythonExecutable(virtualEnvironment) {
252
- return import_node_path3.default.join(virtualEnvironment, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
279
+ function pythonVenvExecutable(pythonVenv) {
280
+ return import_node_path3.default.join(pythonVenv, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
253
281
  }
254
- function activateVirtualEnvironment(env, virtualEnvironment, pythonExecutable) {
282
+ function activatePythonVenv(env, pythonVenv, pythonExecutable) {
255
283
  const activated = { ...env };
256
284
  const pathEntry = Object.keys(activated).find((key) => key.toUpperCase() === "PATH");
257
285
  const existingPath = pathEntry ? activated[pathEntry] : undefined;
@@ -262,7 +290,7 @@ function activateVirtualEnvironment(env, virtualEnvironment, pythonExecutable) {
262
290
  }
263
291
  }
264
292
  activated["PATH"] = [import_node_path3.default.dirname(pythonExecutable), existingPath].filter((value) => Boolean(value)).join(import_node_path3.default.delimiter);
265
- activated["VIRTUAL_ENV"] = virtualEnvironment;
293
+ activated["VIRTUAL_ENV"] = pythonVenv;
266
294
  return activated;
267
295
  }
268
296
  function pathsEqual(left, right) {
@@ -277,6 +305,102 @@ function canonicalPath(value) {
277
305
  return process.platform === "win32" ? canonical.toLowerCase() : canonical;
278
306
  }
279
307
 
308
+ // src/venv-identity.ts
309
+ var VENV_SCHEMA = 1;
310
+ var VENV_METADATA = ".pyfig-venv.json";
311
+ function createVenvIdentity(python, requirementsSha256) {
312
+ const contract = {
313
+ schema: VENV_SCHEMA,
314
+ runtime: {
315
+ architecture: process.arch,
316
+ platform: process.platform
317
+ },
318
+ python: {
319
+ architecture: python.architecture,
320
+ baseExecutable: canonicalPath(python.executable),
321
+ basePrefix: canonicalPath(python.basePrefix),
322
+ embeddingHeader: python.embedding ? canonicalPath(python.embedding.headerPath) : null,
323
+ embeddingLibrary: python.embedding ? canonicalPath(python.embedding.libraryPath) : null,
324
+ gilDisabled: python.gilDisabled,
325
+ implementation: python.implementation,
326
+ pointerBits: python.pointerBits,
327
+ version: python.version
328
+ },
329
+ requirementsSha256
330
+ };
331
+ const id = sha256(JSON.stringify(contract));
332
+ const implementation = safePathSegment(python.implementation);
333
+ const version = safePathSegment(python.version);
334
+ return {
335
+ contract,
336
+ directoryName: `${implementation}-${version}-${id.slice(0, 20)}`,
337
+ id
338
+ };
339
+ }
340
+ function requirementsDigest(content) {
341
+ return sha256(content);
342
+ }
343
+ function venvMetadataFile(directory) {
344
+ return import_node_path4.default.join(directory, VENV_METADATA);
345
+ }
346
+ async function venvMatches(directory, venv) {
347
+ try {
348
+ const parsed = JSON.parse(await import_promises.readFile(venvMetadataFile(directory), "utf8"));
349
+ return parsed.venvId === venv.id && JSON.stringify(parsed.contract) === JSON.stringify(venv.contract);
350
+ } catch {
351
+ return false;
352
+ }
353
+ }
354
+ async function writeVenvMetadata(directory, venv) {
355
+ await import_promises.mkdir(directory, { recursive: true });
356
+ const file = venvMetadataFile(directory);
357
+ const staged = `${file}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`;
358
+ const metadata = {
359
+ contract: venv.contract,
360
+ venvId: venv.id
361
+ };
362
+ try {
363
+ await import_promises.writeFile(staged, `${JSON.stringify(metadata, null, 2)}
364
+ `, {
365
+ encoding: "utf8",
366
+ flag: "wx"
367
+ });
368
+ await import_promises.rename(staged, file);
369
+ } finally {
370
+ await import_promises.rm(staged, { force: true });
371
+ }
372
+ }
373
+ function safePathSegment(value) {
374
+ return value.toLowerCase().replace(/[^a-z0-9.-]+/gu, "-").replace(/^-+|-+$/gu, "") || "python";
375
+ }
376
+ function sha256(value) {
377
+ return import_node_crypto.createHash("sha256").update(value).digest("hex");
378
+ }
379
+
380
+ // src/provision-lock.ts
381
+ var import_loqkit = require("loqkit");
382
+ async function withProvisioningLock(options) {
383
+ let existing;
384
+ const result = await import_loqkit.withLock({ resource: options.key }, options.mutate, {
385
+ key: options.key,
386
+ name: "Pyfig provisioning",
387
+ namespace: "pyfig",
388
+ timeout: options.timeoutMs,
389
+ predicate: async () => {
390
+ const value = await options.check();
391
+ if (value === null)
392
+ return true;
393
+ existing = { value };
394
+ return false;
395
+ }
396
+ });
397
+ if (result.context.state === "executed")
398
+ return result.value;
399
+ if (existing)
400
+ return existing.value;
401
+ throw new Error(`Provisioning check for ${options.key} did not produce a resource.`);
402
+ }
403
+
280
404
  // src/package-target.ts
281
405
  var TARGET_OPTIONS = new Set([
282
406
  "--active",
@@ -309,7 +433,7 @@ var TARGET_ENVIRONMENT_VARIABLES = new Set([
309
433
  "UV_SYSTEM_PYTHON",
310
434
  "UV_TARGET"
311
435
  ]);
312
- function uvCommandNeedsEnvironment(args) {
436
+ function uvCommandNeedsPython(args) {
313
437
  return args[0] === "pip" || args[0] === "run";
314
438
  }
315
439
  function targetUvArguments(args, python) {
@@ -325,16 +449,16 @@ function targetUvArguments(args, python) {
325
449
  }
326
450
  return [...args];
327
451
  }
328
- function uvCommandEnvironment(env, python) {
329
- const sanitized = sanitizeUvEnvironment(env);
452
+ function uvCommandProcessEnvironment(env, python) {
453
+ const sanitized = sanitizeUvProcessEnvironment(env);
330
454
  if (!python?.virtualEnvironment)
331
455
  return sanitized;
332
456
  return {
333
- ...activateVirtualEnvironment(sanitized, python.virtualEnvironment, python.executable),
457
+ ...activatePythonVenv(sanitized, python.virtualEnvironment, python.executable),
334
458
  UV_PYTHON: python.executable
335
459
  };
336
460
  }
337
- function sanitizeUvEnvironment(env) {
461
+ function sanitizeUvProcessEnvironment(env) {
338
462
  const sanitized = { ...env };
339
463
  for (const key of Object.keys(sanitized)) {
340
464
  if (TARGET_ENVIRONMENT_VARIABLES.has(key.toUpperCase()))
@@ -358,12 +482,12 @@ function assertNoTargetOverride(args) {
358
482
 
359
483
  // src/process.ts
360
484
  var import_node_child_process = require("node:child_process");
361
- var import_node_path4 = __toESM(require("node:path"), 1);
485
+ var import_node_path5 = __toESM(require("node:path"), 1);
362
486
  var import_which = __toESM(require("which"), 1);
363
487
  async function resolveExecutable(value, env) {
364
488
  if (!value)
365
489
  return null;
366
- if (import_node_path4.default.isAbsolute(value)) {
490
+ if (import_node_path5.default.isAbsolute(value)) {
367
491
  return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
368
492
  }
369
493
  if (!/^[A-Za-z0-9_.-]+$/u.test(value))
@@ -380,7 +504,7 @@ function run(executable, args, env, timeoutMs) {
380
504
  });
381
505
  if (result.status !== 0 || result.error) {
382
506
  const detail = result.stderr?.trim() || result.error?.message || `exit ${result.status}`;
383
- throw new Error(`${import_node_path4.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`);
507
+ throw new Error(`${import_node_path5.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`);
384
508
  }
385
509
  return { stdout: result.stdout, stderr: result.stderr };
386
510
  }
@@ -409,11 +533,11 @@ async function runAsync(executable, args, env, timeoutMs, signal, cwd) {
409
533
  const abort = () => {
410
534
  child.kill();
411
535
  const reason = signal?.reason;
412
- finish(reason instanceof Error ? reason : new Error(`${import_node_path4.default.basename(executable)} was aborted.`));
536
+ finish(reason instanceof Error ? reason : new Error(`${import_node_path5.default.basename(executable)} was aborted.`));
413
537
  };
414
538
  const timer = setTimeout(() => {
415
539
  child.kill();
416
- finish(new Error(`${import_node_path4.default.basename(executable)} timed out after ${timeoutMs}ms.`));
540
+ finish(new Error(`${import_node_path5.default.basename(executable)} timed out after ${timeoutMs}ms.`));
417
541
  }, timeoutMs);
418
542
  child.stdout.setEncoding("utf8");
419
543
  child.stderr.setEncoding("utf8");
@@ -421,21 +545,21 @@ async function runAsync(executable, args, env, timeoutMs, signal, cwd) {
421
545
  stdout += chunk;
422
546
  if (stdout.length > 4 * 1024 * 1024) {
423
547
  child.kill();
424
- finish(new Error(`${import_node_path4.default.basename(executable)} exceeded the output limit.`));
548
+ finish(new Error(`${import_node_path5.default.basename(executable)} exceeded the output limit.`));
425
549
  }
426
550
  });
427
551
  child.stderr.on("data", (chunk) => {
428
552
  stderr += chunk;
429
553
  if (stderr.length > 4 * 1024 * 1024) {
430
554
  child.kill();
431
- finish(new Error(`${import_node_path4.default.basename(executable)} exceeded the error-output limit.`));
555
+ finish(new Error(`${import_node_path5.default.basename(executable)} exceeded the error-output limit.`));
432
556
  }
433
557
  });
434
558
  child.once("error", (error) => finish(error));
435
559
  child.once("close", (code, childSignal) => {
436
560
  if (code !== 0) {
437
561
  const detail = stderr.trim() || (childSignal ? `signal ${childSignal}` : `exit ${code}`);
438
- finish(new Error(`${import_node_path4.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
562
+ finish(new Error(`${import_node_path5.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
439
563
  return;
440
564
  }
441
565
  finish(undefined, { stdout, stderr });
@@ -589,59 +713,71 @@ function pythonArchitectureMatchesRuntime(python) {
589
713
  async function ensureUv(options = {}) {
590
714
  return ensureUvResolved(resolveOptions(options));
591
715
  }
592
- async function ensureEnvironment(options = {}, dependencies = [], dependencyOptions = {}) {
716
+ async function ensurePython(options = {}) {
593
717
  const resolved = resolveOptions(options);
594
- const normalized = normalizeDependencies(dependencies);
595
- const selections = normalizeDependencyOptions(dependencyOptions);
718
+ await validateRequirementsFiles(resolved.requirements);
596
719
  const uv = await ensureUvResolved(resolved);
597
- const python = await ensureVirtualEnvironmentResolved(resolved, uv);
598
- if (normalized.length > 0 || selections.extras.length > 0 || selections.groups.length > 0 || selections.allExtras) {
599
- await installRuntimeDependencies(uv, python, normalized, selections, resolved);
720
+ if (!hasRuntimeRequirements(resolved)) {
721
+ const external = await findReusableExternalPythonVenv(resolved);
722
+ if (external) {
723
+ return { managed: false, python: external, uv, venvId: null };
724
+ }
600
725
  }
601
- return {
602
- python,
603
- uv,
604
- managed: isManagedEnvironment(python, resolved)
726
+ const basePython = await ensureBasePythonResolved(uv, resolved);
727
+ const resolution = await resolveRuntimeDependencies(uv, basePython, resolved);
728
+ const venv = createVenvIdentity(basePython, resolution.sha256);
729
+ const python = await ensureManagedPythonVenv(uv, basePython, venv, resolution, resolved);
730
+ return { managed: true, python, uv, venvId: venv.id };
731
+ }
732
+ async function ensureManagedPythonVenv(uv, basePython, venv, resolution, options) {
733
+ const directory = pythonVenvDirectory(options.projectRoot, venv.directoryName);
734
+ const check = async () => {
735
+ if (!await venvMatches(directory, venv))
736
+ return null;
737
+ const candidate = await inspectPythonVenv(directory, options);
738
+ return candidate && pythonSatisfiesPolicy(candidate, options) ? candidate : null;
605
739
  };
606
- }
607
- async function ensureVirtualEnvironmentResolved(options, uv) {
608
- const existing = await findReusableEnvironment(options);
740
+ const existing = await check();
609
741
  if (existing)
610
742
  return existing;
611
- const directory = virtualEnvironmentDirectory(options.projectRoot);
612
- const check = async () => {
613
- const candidate = await inspectVirtualEnvironment(directory, options);
614
- return candidate && pythonSatisfiesPolicy(candidate, options) && await virtualEnvironmentMatchesConfiguredPython(candidate, options) ? candidate : null;
615
- };
616
743
  if (!options.createVirtualEnvironment) {
617
744
  throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
618
745
  }
619
- const basePython = await ensureBasePythonResolved(uv, options);
620
746
  return withProvisioningLock({
621
747
  key: `venv:${canonicalPath(directory)}`,
622
748
  timeoutMs: options.coordinationTimeoutMs,
623
749
  check,
624
750
  mutate: async () => {
625
- await import_promises.rm(directory, { recursive: true, force: true });
626
- await import_promises.mkdir(import_node_path5.default.dirname(directory), { recursive: true });
627
- await runAsync(uv.executable, [
628
- "venv",
629
- "--no-project",
630
- "--no-config",
631
- "--no-python-downloads",
632
- "--python",
633
- basePython.executable,
634
- directory
635
- ], isolatedUvEnvironment(options), options.installTimeoutMs);
636
- const installed = await check();
637
- if (!installed) {
638
- throw new Error(`uv created ${directory}, but its Python does not satisfy the requested capabilities.`);
751
+ await import_promises2.rm(directory, { recursive: true, force: true });
752
+ await import_promises2.mkdir(import_node_path6.default.dirname(directory), { recursive: true });
753
+ try {
754
+ await runAsync(uv.executable, [
755
+ "venv",
756
+ "--no-project",
757
+ "--no-config",
758
+ "--no-python-downloads",
759
+ "--python",
760
+ basePython.executable,
761
+ directory
762
+ ], isolatedUvProcessEnvironment(options), options.installTimeoutMs);
763
+ const created = await inspectPythonVenv(directory, options);
764
+ if (!created || !pythonSatisfiesPolicy(created, options)) {
765
+ throw new Error(`uv created ${directory}, but its Python does not satisfy the requested capabilities.`);
766
+ }
767
+ await installResolvedDependencies(uv, created, resolution, options);
768
+ await writeVenvMetadata(directory, venv);
769
+ const installed = await check();
770
+ if (!installed)
771
+ throw new Error(`Pyfig could not validate environment ${directory}.`);
772
+ return installed;
773
+ } catch (error) {
774
+ await import_promises2.rm(directory, { recursive: true, force: true });
775
+ throw error;
639
776
  }
640
- return installed;
641
777
  }
642
778
  });
643
779
  }
644
- async function findReusableEnvironment(options) {
780
+ async function findReusableExternalPythonVenv(options) {
645
781
  if (options.python) {
646
782
  const configured = await probePython({ executable: options.python, args: [] }, options);
647
783
  if (!configured || !pythonSatisfiesPolicy(configured, options)) {
@@ -650,14 +786,10 @@ async function findReusableEnvironment(options) {
650
786
  if (configured.virtualEnvironment)
651
787
  return configured;
652
788
  }
653
- for (const directory of [
654
- ...externalVirtualEnvironmentDirectories(options.projectRoot, options.env),
655
- virtualEnvironmentDirectory(options.projectRoot)
656
- ]) {
657
- const info = await inspectVirtualEnvironment(directory, options);
658
- if (info && pythonSatisfiesPolicy(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
789
+ for (const directory of externalPythonVenvDirectories(options.projectRoot, options.env)) {
790
+ const info = await inspectPythonVenv(directory, options);
791
+ if (info && pythonSatisfiesPolicy(info, options))
659
792
  return info;
660
- }
661
793
  }
662
794
  return null;
663
795
  }
@@ -674,6 +806,9 @@ async function ensureBasePythonResolved(uv, options) {
674
806
  return base;
675
807
  throw incompatibleConfiguredPython(options);
676
808
  }
809
+ const externalBase = await findExternalBasePython(options);
810
+ if (externalBase)
811
+ return externalBase;
677
812
  const system = await findPythonWithUv(uv, options, "system");
678
813
  if (system)
679
814
  return system;
@@ -684,11 +819,11 @@ async function ensureBasePythonResolved(uv, options) {
684
819
  throw new Error(missingPythonMessage(options, "managed Python installation is disabled"));
685
820
  }
686
821
  return withProvisioningLock({
687
- key: `python:${canonicalPath(import_node_path5.default.join(options.cacheDir, "python"))}`,
822
+ key: `python:${canonicalPath(import_node_path6.default.join(options.cacheDir, "python"))}`,
688
823
  timeoutMs: options.coordinationTimeoutMs,
689
824
  check: () => findPythonWithUv(uv, options, "managed"),
690
825
  mutate: async () => {
691
- await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython], isolatedUvEnvironment(options), options.installTimeoutMs);
826
+ await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython], isolatedUvProcessEnvironment(options), options.installTimeoutMs);
692
827
  const installed = await findPythonWithUv(uv, options, "managed");
693
828
  if (!installed) {
694
829
  throw new Error(missingPythonMessage(options, `uv installed ${options.managedPython}, but it remains incompatible`));
@@ -697,6 +832,17 @@ async function ensureBasePythonResolved(uv, options) {
697
832
  }
698
833
  });
699
834
  }
835
+ async function findExternalBasePython(options) {
836
+ for (const directory of externalPythonVenvDirectories(options.projectRoot, options.env)) {
837
+ const environment = await inspectPythonVenv(directory, options);
838
+ if (!environment || !pythonSatisfiesPolicy(environment, options))
839
+ continue;
840
+ const base = await probePython({ executable: environment.baseExecutable, args: [] }, options);
841
+ if (base && !base.virtualEnvironment && pythonSatisfiesPolicy(base, options))
842
+ return base;
843
+ }
844
+ return null;
845
+ }
700
846
  async function findPythonWithUv(uv, options, source) {
701
847
  for (const request of ["", options.managedPython]) {
702
848
  const args = [
@@ -710,7 +856,7 @@ async function findPythonWithUv(uv, options, source) {
710
856
  if (request)
711
857
  args.push(request);
712
858
  try {
713
- const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
859
+ const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceProcessEnvironment(options), options.probeTimeoutMs);
714
860
  const info = await probePython({ executable: stdout.trim(), args: [] }, options);
715
861
  if (info && pythonSatisfiesPolicy(info, options))
716
862
  return info;
@@ -726,7 +872,7 @@ async function findPythonWithUv(uv, options, source) {
726
872
  "--output-format",
727
873
  "json",
728
874
  source === "system" ? "--no-managed-python" : "--managed-python"
729
- ], explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
875
+ ], explicitPythonSourceProcessEnvironment(options), options.probeTimeoutMs);
730
876
  const candidates = installedPythonPaths(stdout);
731
877
  for (const executable of candidates) {
732
878
  const info = await probePython({ executable, args: [] }, options);
@@ -749,59 +895,79 @@ function installedPythonPaths(output) {
749
895
  });
750
896
  return [...new Map(paths.map((candidate) => [canonicalPath(candidate), candidate])).values()];
751
897
  }
752
- async function inspectVirtualEnvironment(directory, options) {
753
- const executable = virtualEnvironmentPythonExecutable(directory);
898
+ async function inspectPythonVenv(directory, options) {
899
+ const executable = pythonVenvExecutable(directory);
754
900
  const info = await probePython({ executable, args: [] }, options);
755
901
  return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
756
902
  }
757
- async function installRuntimeDependencies(uv, python, dependencies, dependencyOptions, options) {
758
- if (!python.virtualEnvironment) {
759
- throw new Error("Runtime dependency installation requires a virtual environment.");
903
+ async function resolveRuntimeDependencies(uv, python, options) {
904
+ if (!hasRuntimeRequirements(options)) {
905
+ return { content: "", sha256: requirementsDigest("") };
760
906
  }
761
- await withProvisioningLock({
762
- key: `dependencies:${canonicalPath(python.virtualEnvironment)}`,
763
- timeoutMs: options.coordinationTimeoutMs,
764
- check: async () => null,
765
- mutate: async () => {
766
- const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-dependencies-"));
767
- try {
768
- const additions = import_node_path5.default.join(temporary, "runtime-dependencies.txt");
769
- const resolved = import_node_path5.default.join(temporary, "resolved-dependencies.txt");
770
- const pyproject = import_node_path5.default.join(options.projectRoot, "pyproject.toml");
771
- const hasPyproject = import_node_fs4.default.existsSync(pyproject);
772
- if (!hasPyproject && (dependencyOptions.extras.length > 0 || dependencyOptions.groups.length > 0 || dependencyOptions.allExtras)) {
773
- throw new Error("Runtime extras and groups require an existing pyproject.toml.");
774
- }
775
- const sources = hasPyproject ? [pyproject] : [];
776
- if (dependencies.length > 0) {
777
- await import_promises.writeFile(additions, `${dependencies.join(`
907
+ const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-resolution-"));
908
+ try {
909
+ const additions = import_node_path6.default.join(temporary, "runtime-dependencies.txt");
910
+ const output = import_node_path6.default.join(temporary, "resolved-dependencies.txt");
911
+ const sources = [...options.requirements];
912
+ if (options.dependencies.length > 0) {
913
+ await import_promises2.writeFile(additions, `${options.dependencies.join(`
778
914
  `)}
779
915
  `, "utf8");
780
- sources.push(additions);
781
- }
782
- const selectionArgs = [
783
- ...dependencyOptions.extras.flatMap((extra) => ["--extra", extra]),
784
- ...dependencyOptions.groups.flatMap((group) => ["--group", group]),
785
- ...dependencyOptions.allExtras ? ["--all-extras"] : []
786
- ];
787
- await runAsync(uv.executable, [
788
- "pip",
789
- "compile",
790
- "--no-config",
791
- "--python",
792
- python.executable,
793
- ...selectionArgs,
794
- ...sources,
795
- "--output-file",
796
- resolved
797
- ], targetUvEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
798
- await runAsync(uv.executable, ["pip", "install", "--no-config", "--python", python.executable, "-r", resolved], targetUvEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
799
- } finally {
800
- await import_promises.rm(temporary, { recursive: true, force: true });
801
- }
802
- return true;
916
+ sources.push(additions);
803
917
  }
804
- });
918
+ await runAsync(uv.executable, [
919
+ "pip",
920
+ "compile",
921
+ "--no-config",
922
+ "--no-header",
923
+ "--no-annotate",
924
+ "--generate-hashes",
925
+ "--python",
926
+ python.executable,
927
+ ...sources,
928
+ "--output-file",
929
+ output
930
+ ], isolatedUvProcessEnvironment(options), options.installTimeoutMs, undefined, options.projectRoot);
931
+ const content = normalizeResolution(await import_promises2.readFile(output, "utf8"));
932
+ return { content, sha256: requirementsDigest(content) };
933
+ } finally {
934
+ await import_promises2.rm(temporary, { recursive: true, force: true });
935
+ }
936
+ }
937
+ async function validateRequirementsFiles(requirements) {
938
+ for (const requirement of requirements) {
939
+ let information;
940
+ try {
941
+ information = await import_promises2.stat(requirement);
942
+ } catch (error) {
943
+ throw new Error(`Requirements file is unavailable: ${requirement}`, { cause: error });
944
+ }
945
+ if (!information.isFile()) {
946
+ throw new Error(`Requirements path is not a file: ${requirement}`);
947
+ }
948
+ }
949
+ }
950
+ async function installResolvedDependencies(uv, python, resolution, options) {
951
+ if (!resolution.content)
952
+ return;
953
+ if (!python.virtualEnvironment) {
954
+ throw new Error("Runtime dependency installation requires a virtual environment.");
955
+ }
956
+ const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-install-"));
957
+ try {
958
+ const requirements = import_node_path6.default.join(temporary, "resolved-dependencies.txt");
959
+ await import_promises2.writeFile(requirements, resolution.content, { encoding: "utf8", flag: "wx" });
960
+ await runAsync(uv.executable, ["pip", "install", "--no-config", "--python", python.executable, "-r", requirements], targetUvProcessEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
961
+ await runAsync(uv.executable, ["pip", "check", "--no-config", "--python", python.executable], targetUvProcessEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
962
+ } finally {
963
+ await import_promises2.rm(temporary, { recursive: true, force: true });
964
+ }
965
+ }
966
+ function normalizeResolution(content) {
967
+ const normalized = content.replace(/\r\n/gu, `
968
+ `).trimEnd();
969
+ return normalized ? `${normalized}
970
+ ` : "";
805
971
  }
806
972
  async function ensureUvResolved(options) {
807
973
  const existing = await findAvailableUv(options);
@@ -810,7 +976,7 @@ async function ensureUvResolved(options) {
810
976
  if (!options.installUv) {
811
977
  throw new Error(`Compatible uv is unavailable (requires-uv ${options.requiresUv}), and uv installation is disabled.`);
812
978
  }
813
- const policyKey = import_node_crypto.createHash("sha256").update(options.requiresUv).digest("hex");
979
+ const policyKey = import_node_crypto2.createHash("sha256").update(options.requiresUv).digest("hex");
814
980
  return withProvisioningLock({
815
981
  key: `uv-policy:${canonicalPath(uvInstallRoot(options))}:${policyKey}`,
816
982
  timeoutMs: options.coordinationTimeoutMs,
@@ -842,7 +1008,7 @@ async function findAvailableUv(options) {
842
1008
  return findManagedUv(options);
843
1009
  }
844
1010
  async function findManagedUv(options) {
845
- const entries = await import_promises.readdir(uvInstallRoot(options), { withFileTypes: true }).catch(() => []);
1011
+ const entries = await import_promises2.readdir(uvInstallRoot(options), { withFileTypes: true }).catch(() => []);
846
1012
  let versions = entries.filter((entry) => entry.isDirectory() && normalizePep440Version(entry.name) === entry.name).map((entry) => entry.name);
847
1013
  while (versions.length > 0) {
848
1014
  const selected = maxPep440Version(versions, options.requiresUv);
@@ -855,45 +1021,16 @@ async function findManagedUv(options) {
855
1021
  }
856
1022
  return null;
857
1023
  }
858
- function normalizeDependencies(dependencies) {
859
- const normalized = dependencies.map((dependency) => dependency.trim());
860
- for (const dependency of normalized) {
861
- if (!dependency || /[\0\r\n]/u.test(dependency)) {
862
- throw new TypeError(`Invalid runtime dependency: ${JSON.stringify(dependency)}`);
863
- }
864
- }
865
- return [...new Set(normalized)];
1024
+ function isolatedUvProcessEnvironment(options) {
1025
+ return sanitizeUvProcessEnvironment(uvProcessEnvironment(options));
866
1026
  }
867
- function normalizeDependencyOptions(options) {
868
- const extras = normalizeNames("extra", options.extras ?? []);
869
- const groups = normalizeNames("dependency group", options.groups ?? []);
870
- if (options.allExtras && extras.length > 0) {
871
- throw new TypeError("allExtras cannot be combined with explicit extras.");
872
- }
873
- return { extras, groups, allExtras: options.allExtras ?? false };
874
- }
875
- function normalizeNames(kind, names) {
876
- const normalized = names.map((name) => name.trim());
877
- for (const name of normalized) {
878
- if (!name || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(name)) {
879
- throw new TypeError(`Invalid ${kind}: ${JSON.stringify(name)}`);
880
- }
881
- }
882
- return [...new Set(normalized)];
883
- }
884
- function isManagedEnvironment(python, options) {
885
- return Boolean(python.virtualEnvironment) && pathsEqual(python.virtualEnvironment, virtualEnvironmentDirectory(options.projectRoot));
886
- }
887
- function isolatedUvEnvironment(options) {
888
- return sanitizeUvEnvironment(uvEnvironment(options));
889
- }
890
- function explicitPythonSourceEnvironment(options) {
891
- const env = isolatedUvEnvironment(options);
1027
+ function explicitPythonSourceProcessEnvironment(options) {
1028
+ const env = isolatedUvProcessEnvironment(options);
892
1029
  delete env["UV_PYTHON_PREFERENCE"];
893
1030
  return env;
894
1031
  }
895
- function targetUvEnvironment(options, python) {
896
- return uvCommandEnvironment(uvEnvironment(options), python);
1032
+ function targetUvProcessEnvironment(options, python) {
1033
+ return uvCommandProcessEnvironment(uvProcessEnvironment(options), python);
897
1034
  }
898
1035
  function incompatibleConfiguredPython(options) {
899
1036
  return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
@@ -902,18 +1039,6 @@ function missingPythonMessage(options, reason) {
902
1039
  const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
903
1040
  return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
904
1041
  }
905
- async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, options) {
906
- if (!options.python)
907
- return true;
908
- const configured = await probePython({ executable: options.python, args: [] }, options);
909
- if (!configured || !pythonSatisfiesPolicy(configured, options))
910
- return false;
911
- if (configured.virtualEnvironment && pathsEqual(configured.executable, virtualEnvironment.executable)) {
912
- return true;
913
- }
914
- const configuredBase = configured.virtualEnvironment ? configured.baseExecutable : configured.executable;
915
- return pathsEqual(configuredBase, virtualEnvironment.baseExecutable);
916
- }
917
1042
  var UV_RELEASE_PAGE_SIZE = 100;
918
1043
  var UV_RELEASE_MAX_PAGES = 20;
919
1044
  var UV_RELEASE_METADATA_TTL_MS = 24 * 60 * 60000;
@@ -955,11 +1080,7 @@ async function fetchUvReleaseCatalog(options) {
955
1080
  let exhausted = false;
956
1081
  try {
957
1082
  for (let page = 1;page <= UV_RELEASE_MAX_PAGES; page += 1) {
958
- const text = await downloadText(`https://api.github.com/repos/astral-sh/uv/releases?per_page=${UV_RELEASE_PAGE_SIZE}&page=${page}`, options.downloadTimeoutMs, dispatcher, {
959
- Accept: "application/vnd.github+json",
960
- "User-Agent": "pyfig",
961
- "X-GitHub-Api-Version": "2022-11-28"
962
- });
1083
+ const text = await downloadText(`https://api.github.com/repos/astral-sh/uv/releases?per_page=${UV_RELEASE_PAGE_SIZE}&page=${page}`, options.downloadTimeoutMs, dispatcher, githubApiHeaders(options.env));
963
1084
  const payload = JSON.parse(text);
964
1085
  if (!Array.isArray(payload)) {
965
1086
  throw new Error("GitHub returned invalid uv release metadata.");
@@ -992,6 +1113,18 @@ async function fetchUvReleaseCatalog(options) {
992
1113
  }
993
1114
  return { fetchedAt: Date.now(), releases: [...releases.values()] };
994
1115
  }
1116
+ function githubApiHeaders(env) {
1117
+ const token = [env["GITHUB_TOKEN"], env["GH_TOKEN"]].find((value) => typeof value === "string" && value.trim().length > 0);
1118
+ if (token && /[\r\n]/u.test(token)) {
1119
+ throw new TypeError("GitHub API token must not contain line breaks.");
1120
+ }
1121
+ return {
1122
+ Accept: "application/vnd.github+json",
1123
+ "User-Agent": "pyfig",
1124
+ "X-GitHub-Api-Version": "2022-11-28",
1125
+ ...token ? { Authorization: `Bearer ${token}` } : {}
1126
+ };
1127
+ }
995
1128
  async function readFreshUvReleaseCatalog(file) {
996
1129
  const catalog = await readUvReleaseCatalog(file);
997
1130
  if (!catalog)
@@ -1001,7 +1134,7 @@ async function readFreshUvReleaseCatalog(file) {
1001
1134
  }
1002
1135
  async function readUvReleaseCatalog(file) {
1003
1136
  try {
1004
- return parseUvReleaseCatalog(JSON.parse(await import_promises.readFile(file, "utf8")));
1137
+ return parseUvReleaseCatalog(JSON.parse(await import_promises2.readFile(file, "utf8")));
1005
1138
  } catch {
1006
1139
  return null;
1007
1140
  }
@@ -1030,20 +1163,20 @@ function parseUvReleaseCatalog(value) {
1030
1163
  return { fetchedAt: catalog.fetchedAt, releases };
1031
1164
  }
1032
1165
  async function writeUvReleaseCatalog(file, catalog) {
1033
- await import_promises.mkdir(import_node_path5.default.dirname(file), { recursive: true });
1034
- const staged = import_node_path5.default.join(import_node_path5.default.dirname(file), `.${import_node_path5.default.basename(file)}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`);
1166
+ await import_promises2.mkdir(import_node_path6.default.dirname(file), { recursive: true });
1167
+ const staged = import_node_path6.default.join(import_node_path6.default.dirname(file), `.${import_node_path6.default.basename(file)}.${process.pid}.${import_node_crypto2.randomUUID()}.tmp`);
1035
1168
  try {
1036
- await import_promises.writeFile(staged, `${JSON.stringify(catalog)}
1169
+ await import_promises2.writeFile(staged, `${JSON.stringify(catalog)}
1037
1170
  `, { encoding: "utf8", flag: "wx" });
1038
1171
  if (process.platform === "win32")
1039
- await import_promises.rm(file, { force: true });
1040
- await import_promises.rename(staged, file);
1172
+ await import_promises2.rm(file, { force: true });
1173
+ await import_promises2.rename(staged, file);
1041
1174
  } finally {
1042
- await import_promises.rm(staged, { force: true });
1175
+ await import_promises2.rm(staged, { force: true });
1043
1176
  }
1044
1177
  }
1045
1178
  function uvReleaseMetadataFile(options) {
1046
- return import_node_path5.default.join(options.cacheDir, "metadata", "uv-releases.json");
1179
+ return import_node_path6.default.join(options.cacheDir, "metadata", "uv-releases.json");
1047
1180
  }
1048
1181
  function releaseAssetNames(value) {
1049
1182
  if (!Array.isArray(value))
@@ -1056,36 +1189,36 @@ function releaseAssetNames(value) {
1056
1189
  });
1057
1190
  }
1058
1191
  function uvInstallRoot(options) {
1059
- return import_node_path5.default.join(options.cacheDir, "tools", "uv");
1192
+ return import_node_path6.default.join(options.cacheDir, "tools", "uv");
1060
1193
  }
1061
1194
  function uvInstallDirectory(options, version) {
1062
- return import_node_path5.default.join(uvInstallRoot(options), version);
1195
+ return import_node_path6.default.join(uvInstallRoot(options), version);
1063
1196
  }
1064
1197
  function uvExecutable(options, version) {
1065
- return import_node_path5.default.join(uvInstallDirectory(options, version), process.platform === "win32" ? "uv.exe" : "uv");
1198
+ return import_node_path6.default.join(uvInstallDirectory(options, version), process.platform === "win32" ? "uv.exe" : "uv");
1066
1199
  }
1067
1200
  async function installUv(installDir, release, options) {
1068
1201
  const asset = uvAsset();
1069
1202
  const baseUrl = `https://github.com/astral-sh/uv/releases/download/${release.tag}`;
1070
- const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
1203
+ const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
1071
1204
  const dispatcher = proxyDispatcher(options.env);
1072
1205
  try {
1073
- const archive = import_node_path5.default.join(temporary, asset);
1206
+ const archive = import_node_path6.default.join(temporary, asset);
1074
1207
  await download(`${baseUrl}/${asset}`, archive, options.downloadTimeoutMs, dispatcher);
1075
1208
  const expected = (await downloadText(`${baseUrl}/${asset}.sha256`, options.downloadTimeoutMs, dispatcher)).trim().split(/\s+/u)[0];
1076
1209
  const actual = await sha256File(archive);
1077
1210
  if (!expected || expected.toLowerCase() !== actual.toLowerCase()) {
1078
1211
  throw new Error(`uv archive checksum mismatch for ${asset}`);
1079
1212
  }
1080
- const extracted = import_node_path5.default.join(temporary, "extracted");
1081
- await import_promises.mkdir(extracted, { recursive: true });
1213
+ const extracted = import_node_path6.default.join(temporary, "extracted");
1214
+ await import_promises2.mkdir(extracted, { recursive: true });
1082
1215
  const source = await extractUvExecutable(archive, asset, extracted);
1083
1216
  if (!source)
1084
1217
  throw new Error(`uv archive did not contain the expected executable: ${asset}`);
1085
1218
  await publishUvAtomically(source, installDir, release.version, options);
1086
1219
  } finally {
1087
1220
  await dispatcher?.close();
1088
- await import_promises.rm(temporary, { recursive: true, force: true });
1221
+ await import_promises2.rm(temporary, { recursive: true, force: true });
1089
1222
  }
1090
1223
  }
1091
1224
  async function extractUvExecutable(archive, asset, destination) {
@@ -1094,33 +1227,33 @@ async function extractUvExecutable(archive, asset, destination) {
1094
1227
  await tar.x({ file: archive, cwd: destination, strict: true });
1095
1228
  return findFile(destination, executableName);
1096
1229
  }
1097
- const entries = import_fflate.unzipSync(await import_promises.readFile(archive), {
1098
- filter: ({ name }) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName
1230
+ const entries = import_fflate.unzipSync(await import_promises2.readFile(archive), {
1231
+ filter: ({ name }) => import_node_path6.default.posix.basename(name.replaceAll("\\", "/")) === executableName
1099
1232
  });
1100
- const matches = Object.entries(entries).filter(([name]) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
1233
+ const matches = Object.entries(entries).filter(([name]) => import_node_path6.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
1101
1234
  if (matches.length !== 1 || !matches[0])
1102
1235
  return null;
1103
- const source = import_node_path5.default.join(destination, executableName);
1104
- await import_promises.writeFile(source, matches[0][1], { flag: "wx" });
1236
+ const source = import_node_path6.default.join(destination, executableName);
1237
+ await import_promises2.writeFile(source, matches[0][1], { flag: "wx" });
1105
1238
  return source;
1106
1239
  }
1107
1240
  async function publishUvAtomically(source, installDir, version, options) {
1108
- await import_promises.mkdir(installDir, { recursive: true });
1241
+ await import_promises2.mkdir(installDir, { recursive: true });
1109
1242
  const executable = uvExecutable(options, version);
1110
- const staged = import_node_path5.default.join(installDir, `.${import_node_path5.default.basename(executable)}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`);
1243
+ const staged = import_node_path6.default.join(installDir, `.${import_node_path6.default.basename(executable)}.${process.pid}.${import_node_crypto2.randomUUID()}.tmp`);
1111
1244
  try {
1112
- await import_promises.copyFile(source, staged);
1245
+ await import_promises2.copyFile(source, staged);
1113
1246
  if (process.platform !== "win32")
1114
- await import_promises.chmod(staged, 493);
1247
+ await import_promises2.chmod(staged, 493);
1115
1248
  const stagedUv = probeUv(staged, options);
1116
1249
  if (!stagedUv || stagedUv.version !== version) {
1117
1250
  throw new Error(`Downloaded uv executable is not version ${version}.`);
1118
1251
  }
1119
1252
  if (process.platform === "win32")
1120
- await import_promises.rm(executable, { force: true });
1121
- await import_promises.rename(staged, executable);
1253
+ await import_promises2.rm(executable, { force: true });
1254
+ await import_promises2.rename(staged, executable);
1122
1255
  } finally {
1123
- await import_promises.rm(staged, { force: true });
1256
+ await import_promises2.rm(staged, { force: true });
1124
1257
  }
1125
1258
  }
1126
1259
  function uvAsset() {
@@ -1145,7 +1278,7 @@ async function download(url, destination, timeoutMs, dispatcher) {
1145
1278
  await response.body?.cancel();
1146
1279
  throw new Error(`Could not download ${url}: HTTP ${response.status}`);
1147
1280
  }
1148
- await import_promises2.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs3.createWriteStream(destination, { flags: "wx" }));
1281
+ await import_promises3.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs3.createWriteStream(destination, { flags: "wx" }));
1149
1282
  }
1150
1283
  async function downloadText(url, timeoutMs, dispatcher, headers) {
1151
1284
  const response = await import_undici.fetch(url, {
@@ -1161,28 +1294,28 @@ async function downloadText(url, timeoutMs, dispatcher, headers) {
1161
1294
  return response.text();
1162
1295
  }
1163
1296
  function proxyDispatcher(env) {
1164
- const httpProxy = environmentValue(env, "http_proxy", "HTTP_PROXY");
1165
- const httpsProxy = environmentValue(env, "https_proxy", "HTTPS_PROXY");
1297
+ const httpProxy = processEnvironmentValue(env, "http_proxy", "HTTP_PROXY");
1298
+ const httpsProxy = processEnvironmentValue(env, "https_proxy", "HTTPS_PROXY");
1166
1299
  if (!httpProxy && !httpsProxy)
1167
1300
  return;
1168
1301
  return new import_undici.EnvHttpProxyAgent({
1169
1302
  httpProxy,
1170
1303
  httpsProxy,
1171
- noProxy: environmentValue(env, "no_proxy", "NO_PROXY")
1304
+ noProxy: processEnvironmentValue(env, "no_proxy", "NO_PROXY")
1172
1305
  });
1173
1306
  }
1174
- function environmentValue(env, lowercase, uppercase) {
1307
+ function processEnvironmentValue(env, lowercase, uppercase) {
1175
1308
  return env[lowercase] ?? env[uppercase];
1176
1309
  }
1177
1310
  async function sha256File(file) {
1178
- const hash = import_node_crypto.createHash("sha256");
1311
+ const hash = import_node_crypto2.createHash("sha256");
1179
1312
  for await (const chunk of import_node_fs3.createReadStream(file))
1180
1313
  hash.update(chunk);
1181
1314
  return hash.digest("hex");
1182
1315
  }
1183
1316
  async function findFile(directory, name) {
1184
- for (const entry of await import_promises.readdir(directory, { withFileTypes: true })) {
1185
- const target = import_node_path5.default.join(directory, entry.name);
1317
+ for (const entry of await import_promises2.readdir(directory, { withFileTypes: true })) {
1318
+ const target = import_node_path6.default.join(directory, entry.name);
1186
1319
  if (entry.isFile() && entry.name === name)
1187
1320
  return target;
1188
1321
  if (entry.isDirectory()) {
@@ -1196,8 +1329,8 @@ async function findFile(directory, name) {
1196
1329
 
1197
1330
  // src/sync-resolver.ts
1198
1331
  async function main() {
1199
- const request = JSON.parse(import_node_fs5.readFileSync(0, "utf8"));
1200
- const result = request.target === "uv" ? await ensureUv(request.options) : await ensureEnvironment(request.options);
1332
+ const request = JSON.parse(import_node_fs4.readFileSync(0, "utf8"));
1333
+ const result = request.target === "uv" ? await ensureUv(request.options) : await ensurePython(request.options);
1201
1334
  process.stdout.write(JSON.stringify(result));
1202
1335
  }
1203
1336
  main().catch((error) => {
@@ -1206,4 +1339,4 @@ main().catch((error) => {
1206
1339
  process.exitCode = 1;
1207
1340
  });
1208
1341
 
1209
- //# debugId=DD5BE52110A7FDFA64756E2164756E21
1342
+ //# debugId=0726BCDBF58AF5C064756E2164756E21