pyfig 0.2.0 → 0.3.1

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 (64) hide show
  1. package/README.md +144 -22
  2. package/SECURITY.md +5 -3
  3. package/dist/cjs/index.js +1502 -0
  4. package/dist/cjs/index.js.map +21 -0
  5. package/dist/cjs/package.json +3 -0
  6. package/dist/cjs/sync-resolver.js +1242 -0
  7. package/dist/cjs/sync-resolver.js.map +18 -0
  8. package/dist/cli.js +17 -6
  9. package/dist/cli.js.map +1 -1
  10. package/dist/client.d.ts +20 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +23 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +45 -2
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +130 -9
  17. package/dist/config.js.map +1 -1
  18. package/dist/coordination.d.ts +25 -0
  19. package/dist/coordination.d.ts.map +1 -0
  20. package/dist/coordination.js +187 -0
  21. package/dist/coordination.js.map +1 -0
  22. package/dist/index.d.ts +9 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +6 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/package-target.d.ts +12 -0
  27. package/dist/package-target.d.ts.map +1 -0
  28. package/dist/package-target.js +97 -0
  29. package/dist/package-target.js.map +1 -0
  30. package/dist/probes.d.ts +19 -0
  31. package/dist/probes.d.ts.map +1 -1
  32. package/dist/probes.js +115 -36
  33. package/dist/probes.js.map +1 -1
  34. package/dist/process.d.ts +2 -0
  35. package/dist/process.d.ts.map +1 -1
  36. package/dist/process.js +63 -1
  37. package/dist/process.js.map +1 -1
  38. package/dist/project-root.d.ts +3 -0
  39. package/dist/project-root.d.ts.map +1 -0
  40. package/dist/project-root.js +83 -0
  41. package/dist/project-root.js.map +1 -0
  42. package/dist/provision.d.ts +3 -1
  43. package/dist/provision.d.ts.map +1 -1
  44. package/dist/provision.js +375 -97
  45. package/dist/provision.js.map +1 -1
  46. package/dist/runners.d.ts +5 -2
  47. package/dist/runners.d.ts.map +1 -1
  48. package/dist/runners.js +94 -28
  49. package/dist/runners.js.map +1 -1
  50. package/dist/runtime-files.d.ts +3 -0
  51. package/dist/runtime-files.d.ts.map +1 -0
  52. package/dist/runtime-files.js +12 -0
  53. package/dist/runtime-files.js.map +1 -0
  54. package/dist/state.d.ts +10 -0
  55. package/dist/state.d.ts.map +1 -0
  56. package/dist/state.js +62 -0
  57. package/dist/state.js.map +1 -0
  58. package/dist/sync-resolver.js +11 -2
  59. package/dist/sync-resolver.js.map +1 -1
  60. package/dist/virtual-environment.d.ts +9 -0
  61. package/dist/virtual-environment.d.ts.map +1 -0
  62. package/dist/virtual-environment.js +42 -0
  63. package/dist/virtual-environment.js.map +1 -0
  64. package/package.json +18 -7
@@ -0,0 +1,1502 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ if (mod && typeof mod === "object" || typeof mod === "function") {
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ }
30
+ if (canCache)
31
+ cache.set(mod, to);
32
+ return to;
33
+ };
34
+ var __toCommonJS = (from) => {
35
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
36
+ if (entry)
37
+ return entry;
38
+ entry = __defProp({}, "__esModule", { value: true });
39
+ if (from && typeof from === "object" || typeof from === "function") {
40
+ for (var key of __getOwnPropNames(from))
41
+ if (!__hasOwnProp.call(entry, key))
42
+ __defProp(entry, key, {
43
+ get: __accessProp.bind(from, key),
44
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
45
+ });
46
+ }
47
+ __moduleCache.set(from, entry);
48
+ return entry;
49
+ };
50
+ var __moduleCache;
51
+ var __returnValue = (v) => v;
52
+ function __exportSetter(name, newValue) {
53
+ this[name] = __returnValue.bind(null, newValue);
54
+ }
55
+ var __export = (target, all) => {
56
+ for (var name in all)
57
+ __defProp(target, name, {
58
+ get: all[name],
59
+ enumerable: true,
60
+ configurable: true,
61
+ set: __exportSetter.bind(all, name)
62
+ });
63
+ };
64
+
65
+ // src/index.ts
66
+ var exports_src = {};
67
+ __export(exports_src, {
68
+ DEFAULT_MANAGED_PYTHON: () => DEFAULT_MANAGED_PYTHON,
69
+ DEFAULT_PIP_RANGE: () => DEFAULT_PIP_RANGE,
70
+ DEFAULT_PYTHON_RANGE: () => DEFAULT_PYTHON_RANGE,
71
+ DEFAULT_UV_RANGE: () => DEFAULT_UV_RANGE,
72
+ DEFAULT_UV_VERSION: () => DEFAULT_UV_VERSION,
73
+ clearPyfigCaches: () => clearPyfigCaches,
74
+ createPyfig: () => createPyfig,
75
+ ensureEnvironment: () => ensureEnvironment,
76
+ ensureUv: () => ensureUv,
77
+ ensureVirtualEnvironment: () => ensureVirtualEnvironment,
78
+ pipSpawn: () => pipSpawn,
79
+ pipSpawnSync: () => pipSpawnSync,
80
+ pythonSpawn: () => pythonSpawn,
81
+ pythonSpawnSync: () => pythonSpawnSync,
82
+ resolveProjectRoot: () => resolveProjectRoot,
83
+ uvSpawn: () => uvSpawn,
84
+ uvSpawnSync: () => uvSpawnSync
85
+ });
86
+ module.exports = __toCommonJS(exports_src);
87
+
88
+ // src/config.ts
89
+ var import_node_crypto = require("node:crypto");
90
+ var import_node_path2 = __toESM(require("node:path"), 1);
91
+ var envPaths = __toESM(require("env-paths"), 1);
92
+ var import_semver = __toESM(require("semver"), 1);
93
+
94
+ // src/project-root.ts
95
+ var import_node_fs = __toESM(require("node:fs"), 1);
96
+ var import_node_path = __toESM(require("node:path"), 1);
97
+ var import_pacwich = require("pacwich");
98
+ var PACKAGE_MANAGER_LOCKFILES = [
99
+ "bun.lock",
100
+ "bun.lockb",
101
+ "npm-shrinkwrap.json",
102
+ "package-lock.json",
103
+ "pnpm-lock.yaml",
104
+ "yarn.lock"
105
+ ];
106
+ var PYTHON_PROJECT_MARKERS = [
107
+ "pyproject.toml",
108
+ "uv.lock"
109
+ ];
110
+ function resolveProjectRoot(startDirectory = process.cwd()) {
111
+ const start = import_node_path.default.resolve(startDirectory);
112
+ const workspaceRoot = findWorkspaceProjectRoot(start);
113
+ if (workspaceRoot) {
114
+ try {
115
+ return import_node_path.default.resolve(import_pacwich.createFileSystemProject({
116
+ rootDirectory: start,
117
+ disableExecutableConfigs: true
118
+ }).rootDirectory);
119
+ } catch {
120
+ return workspaceRoot;
121
+ }
122
+ }
123
+ return findConventionalProjectRoot(start);
124
+ }
125
+ function findWorkspaceProjectRoot(startDirectory) {
126
+ for (const directory of ancestors(startDirectory)) {
127
+ const packageJson = import_node_path.default.join(directory, "package.json");
128
+ if (!import_node_fs.default.existsSync(packageJson))
129
+ continue;
130
+ if (import_node_fs.default.existsSync(import_node_path.default.join(directory, "pnpm-workspace.yaml")))
131
+ return directory;
132
+ try {
133
+ const manifest = JSON.parse(import_node_fs.default.readFileSync(packageJson, "utf8"));
134
+ if (hasWorkspaceDeclaration(manifest.workspaces))
135
+ return directory;
136
+ } catch {}
137
+ }
138
+ return null;
139
+ }
140
+ function findConventionalProjectRoot(startDirectory) {
141
+ let nearestPackageDirectory = null;
142
+ let nearestPythonDirectory = null;
143
+ for (const directory of ancestors(startDirectory)) {
144
+ const hasPackage = import_node_fs.default.existsSync(import_node_path.default.join(directory, "package.json"));
145
+ if (hasPackage && !nearestPackageDirectory)
146
+ nearestPackageDirectory = directory;
147
+ if (!nearestPythonDirectory && PYTHON_PROJECT_MARKERS.some((file) => import_node_fs.default.existsSync(import_node_path.default.join(directory, file)))) {
148
+ nearestPythonDirectory = directory;
149
+ }
150
+ if (hasPackage && PACKAGE_MANAGER_LOCKFILES.some((file) => import_node_fs.default.existsSync(import_node_path.default.join(directory, file)))) {
151
+ return directory;
152
+ }
153
+ if (import_node_fs.default.existsSync(import_node_path.default.join(directory, ".git")))
154
+ return directory;
155
+ }
156
+ return nearestPackageDirectory ?? nearestPythonDirectory ?? startDirectory;
157
+ }
158
+ function hasWorkspaceDeclaration(value) {
159
+ return Array.isArray(value) || value !== null && typeof value === "object";
160
+ }
161
+ function* ancestors(startDirectory) {
162
+ let directory = startDirectory;
163
+ while (true) {
164
+ yield directory;
165
+ const parent = import_node_path.default.dirname(directory);
166
+ if (parent === directory)
167
+ return;
168
+ directory = parent;
169
+ }
170
+ }
171
+
172
+ // src/config.ts
173
+ var DEFAULT_PYTHON_RANGE = ">=3.10.0 <4.0.0";
174
+ var DEFAULT_PIP_RANGE = ">=20.3.0";
175
+ var DEFAULT_MANAGED_PYTHON = "3.12";
176
+ var DEFAULT_UV_VERSION = "0.13.6";
177
+ var DEFAULT_UV_RANGE = `>=${DEFAULT_UV_VERSION} <1.0.0`;
178
+ var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
179
+ var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60000;
180
+ var DEFAULT_DOWNLOAD_TIMEOUT_MS = 5 * 60000;
181
+ var DEFAULT_CACHE_TTL_MS = 30000;
182
+ var envPathsFunction = (() => {
183
+ const imported = envPaths.default;
184
+ const implementation = typeof imported === "function" ? imported : imported.default;
185
+ if (!implementation)
186
+ throw new TypeError("env-paths did not expose its default function.");
187
+ return implementation;
188
+ })();
189
+ function resolveOptions(options = {}) {
190
+ const env = { ...options.env ?? process.env };
191
+ const pythonRange = options.pythonRange ?? env["PYFIG_PYTHON_RANGE"] ?? DEFAULT_PYTHON_RANGE;
192
+ const pipRange = options.pipRange ?? env["PYFIG_PIP_RANGE"] ?? DEFAULT_PIP_RANGE;
193
+ const uvRange = options.uvRange ?? env["PYFIG_UV_RANGE"] ?? DEFAULT_UV_RANGE;
194
+ assertRange("Python", pythonRange);
195
+ assertRange("pip", pipRange);
196
+ assertRange("uv", uvRange);
197
+ const managedPython = options.managedPython ?? env["PYFIG_MANAGED_PYTHON"] ?? DEFAULT_MANAGED_PYTHON;
198
+ const uvVersion = options.uvVersion ?? env["PYFIG_UV_VERSION"] ?? DEFAULT_UV_VERSION;
199
+ assertManagedPythonRequest(managedPython, pythonRange);
200
+ assertUvVersion(uvVersion, uvRange);
201
+ const probeTimeoutMs = resolveDuration("probeTimeoutMs", options.probeTimeoutMs, env["PYFIG_PROBE_TIMEOUT_MS"], DEFAULT_PROBE_TIMEOUT_MS);
202
+ const installTimeoutMs = resolveDuration("installTimeoutMs", options.installTimeoutMs, env["PYFIG_INSTALL_TIMEOUT_MS"], DEFAULT_INSTALL_TIMEOUT_MS);
203
+ const downloadTimeoutMs = resolveDuration("downloadTimeoutMs", options.downloadTimeoutMs, env["PYFIG_DOWNLOAD_TIMEOUT_MS"], DEFAULT_DOWNLOAD_TIMEOUT_MS);
204
+ const coordinationTimeoutMs = resolveDuration("coordinationTimeoutMs", options.coordinationTimeoutMs, env["PYFIG_COORDINATION_TIMEOUT_MS"], installTimeoutMs + downloadTimeoutMs + 60000);
205
+ const cacheTtlMs = resolveDuration("cacheTtlMs", options.cacheTtlMs, env["PYFIG_CACHE_TTL_MS"], DEFAULT_CACHE_TTL_MS, true);
206
+ const configuredHome = options.cacheDir ?? env["PYFIG_HOME"];
207
+ const cacheDir = configuredHome ? import_node_path2.default.resolve(configuredHome) : envPathsFunction("pyfig", { suffix: "" }).cache;
208
+ const configuredProjectRoot = options.projectRoot ?? env["PYFIG_PROJECT_ROOT"];
209
+ const projectRoot = configuredProjectRoot ? import_node_path2.default.resolve(configuredProjectRoot) : resolveProjectRoot();
210
+ const autoInstall = options.autoInstall ?? parseBoolean(env["PYFIG_AUTO_INSTALL"], true);
211
+ return {
212
+ python: options.python ?? env["PYFIG_PYTHON"] ?? "",
213
+ pythonRange,
214
+ pipRange,
215
+ managedPython,
216
+ uvVersion,
217
+ uvRange,
218
+ requireEmbedding: options.requireEmbedding ?? parseBoolean(env["PYFIG_REQUIRE_EMBEDDING"], false),
219
+ requireGil: options.requireGil ?? parseBoolean(env["PYFIG_REQUIRE_GIL"], false),
220
+ autoInstall,
221
+ installPython: options.installPython ?? parseBoolean(env["PYFIG_INSTALL_PYTHON"], autoInstall),
222
+ installUv: options.installUv ?? parseBoolean(env["PYFIG_INSTALL_UV"], autoInstall),
223
+ createVirtualEnvironment: options.createVirtualEnvironment ?? parseBoolean(env["PYFIG_CREATE_VIRTUAL_ENVIRONMENT"], autoInstall),
224
+ cacheDir,
225
+ projectRoot,
226
+ probeTimeoutMs,
227
+ installTimeoutMs,
228
+ downloadTimeoutMs,
229
+ coordinationTimeoutMs,
230
+ cacheTtlMs,
231
+ env
232
+ };
233
+ }
234
+ function configurationFingerprint(options) {
235
+ const policy = [
236
+ normalizedPath(options.projectRoot),
237
+ normalizedPath(options.cacheDir),
238
+ options.python,
239
+ options.pythonRange,
240
+ options.pipRange,
241
+ options.managedPython,
242
+ options.uvVersion,
243
+ options.uvRange,
244
+ options.requireEmbedding,
245
+ options.requireGil,
246
+ options.autoInstall,
247
+ options.installPython,
248
+ options.installUv,
249
+ options.createVirtualEnvironment,
250
+ options.probeTimeoutMs,
251
+ options.installTimeoutMs,
252
+ options.downloadTimeoutMs,
253
+ options.coordinationTimeoutMs,
254
+ options.cacheTtlMs,
255
+ environmentFingerprint(options.env)
256
+ ];
257
+ return import_node_crypto.createHash("sha256").update(JSON.stringify(policy)).digest("hex");
258
+ }
259
+ function uvEnvironment(options) {
260
+ return {
261
+ ...options.env,
262
+ UV_PYTHON_INSTALL_DIR: import_node_path2.default.join(options.cacheDir, "python"),
263
+ UV_CACHE_DIR: import_node_path2.default.join(options.cacheDir, "uv-cache")
264
+ };
265
+ }
266
+ function assertRange(tool, range) {
267
+ if (!import_semver.default.validRange(range)) {
268
+ throw new TypeError(`Invalid ${tool} SemVer range: ${range}`);
269
+ }
270
+ }
271
+ function assertUvVersion(version, range) {
272
+ if (!import_semver.default.valid(version)) {
273
+ throw new TypeError(`Invalid exact uv version: ${version}`);
274
+ }
275
+ if (!import_semver.default.satisfies(version, range)) {
276
+ throw new TypeError(`Pinned uv ${version} does not satisfy supported range ${range}.`);
277
+ }
278
+ }
279
+ function assertManagedPythonRequest(request, pythonRange) {
280
+ if (!request || /[\\/\0\r\n]/u.test(request) || request.includes("..")) {
281
+ throw new TypeError(`Invalid managed Python request: ${request}`);
282
+ }
283
+ if (/^\d+\.\d+(?:\.\d+)?$/u.test(request)) {
284
+ const normalized = request.split(".").length === 2 ? `${request}.0` : request;
285
+ if (!import_semver.default.satisfies(normalized, pythonRange)) {
286
+ throw new TypeError(`Managed Python ${request} cannot satisfy configured range ${pythonRange}.`);
287
+ }
288
+ }
289
+ }
290
+ function resolveDuration(name, explicit, environment, fallback, allowZero = false) {
291
+ const value = explicit ?? (environment === undefined ? fallback : Number(environment));
292
+ const minimum = allowZero ? 0 : 1;
293
+ if (!Number.isSafeInteger(value) || value < minimum) {
294
+ throw new TypeError(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer.`);
295
+ }
296
+ return value;
297
+ }
298
+ function parseBoolean(value, fallback) {
299
+ if (value === undefined)
300
+ return fallback;
301
+ if (/^(1|true|yes|on)$/iu.test(value))
302
+ return true;
303
+ if (/^(0|false|no|off)$/iu.test(value))
304
+ return false;
305
+ throw new TypeError(`Invalid boolean value: ${value}`);
306
+ }
307
+ function environmentFingerprint(env) {
308
+ const entries = Object.entries(env).filter((entry) => entry[1] !== undefined).map(([key, value]) => [
309
+ process.platform === "win32" ? key.toUpperCase() : key,
310
+ value
311
+ ]).sort(([left], [right]) => left.localeCompare(right));
312
+ return import_node_crypto.createHash("sha256").update(JSON.stringify(entries)).digest("hex");
313
+ }
314
+ function normalizedPath(value) {
315
+ const resolved = import_node_path2.default.resolve(value);
316
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
317
+ }
318
+ // src/provision.ts
319
+ var import_node_crypto3 = require("node:crypto");
320
+ var import_node_fs3 = require("node:fs");
321
+ var import_promises2 = require("node:fs/promises");
322
+ var import_node_fs4 = __toESM(require("node:fs"), 1);
323
+ var import_node_os = __toESM(require("node:os"), 1);
324
+ var import_node_path7 = __toESM(require("node:path"), 1);
325
+ var import_node_stream = require("node:stream");
326
+ var import_promises3 = require("node:stream/promises");
327
+ var import_extract_zip = __toESM(require("extract-zip"), 1);
328
+ var tar = __toESM(require("tar"), 1);
329
+ var import_undici = require("undici");
330
+
331
+ // src/coordination.ts
332
+ var import_node_crypto2 = require("node:crypto");
333
+ var import_node_child_process2 = require("node:child_process");
334
+ var import_promises = require("node:fs/promises");
335
+ var import_node_path4 = __toESM(require("node:path"), 1);
336
+ var workerThreads = __toESM(require("node:worker_threads"), 1);
337
+ var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
338
+
339
+ // src/process.ts
340
+ var import_node_child_process = require("node:child_process");
341
+ var import_node_path3 = __toESM(require("node:path"), 1);
342
+ var import_which = __toESM(require("which"), 1);
343
+ async function resolveExecutable(value, env) {
344
+ if (!value)
345
+ return null;
346
+ if (import_node_path3.default.isAbsolute(value)) {
347
+ return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
348
+ }
349
+ if (!/^[A-Za-z0-9_.-]+$/u.test(value))
350
+ return null;
351
+ return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
352
+ }
353
+ function run(executable, args, env, timeoutMs) {
354
+ const result = import_node_child_process.spawnSync(executable, args, {
355
+ encoding: "utf8",
356
+ env,
357
+ timeout: timeoutMs,
358
+ maxBuffer: 4 * 1024 * 1024,
359
+ windowsHide: true
360
+ });
361
+ if (result.status !== 0 || result.error) {
362
+ const detail = result.stderr?.trim() || result.error?.message || `exit ${result.status}`;
363
+ throw new Error(`${import_node_path3.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`);
364
+ }
365
+ return { stdout: result.stdout, stderr: result.stderr };
366
+ }
367
+ async function runAsync(executable, args, env, timeoutMs, signal) {
368
+ return new Promise((resolve, reject) => {
369
+ const child = import_node_child_process.spawn(executable, args, {
370
+ env,
371
+ stdio: ["ignore", "pipe", "pipe"],
372
+ windowsHide: true
373
+ });
374
+ let stdout = "";
375
+ let stderr = "";
376
+ let settled = false;
377
+ const finish = (error, result) => {
378
+ if (settled)
379
+ return;
380
+ settled = true;
381
+ clearTimeout(timer);
382
+ signal?.removeEventListener("abort", abort);
383
+ if (error)
384
+ reject(error);
385
+ else if (result)
386
+ resolve(result);
387
+ };
388
+ const abort = () => {
389
+ child.kill();
390
+ const reason = signal?.reason;
391
+ finish(reason instanceof Error ? reason : new Error(`${import_node_path3.default.basename(executable)} was aborted.`));
392
+ };
393
+ const timer = setTimeout(() => {
394
+ child.kill();
395
+ finish(new Error(`${import_node_path3.default.basename(executable)} timed out after ${timeoutMs}ms.`));
396
+ }, timeoutMs);
397
+ child.stdout.setEncoding("utf8");
398
+ child.stderr.setEncoding("utf8");
399
+ child.stdout.on("data", (chunk) => {
400
+ stdout += chunk;
401
+ if (stdout.length > 4 * 1024 * 1024) {
402
+ child.kill();
403
+ finish(new Error(`${import_node_path3.default.basename(executable)} exceeded the output limit.`));
404
+ }
405
+ });
406
+ child.stderr.on("data", (chunk) => {
407
+ stderr += chunk;
408
+ if (stderr.length > 4 * 1024 * 1024) {
409
+ child.kill();
410
+ finish(new Error(`${import_node_path3.default.basename(executable)} exceeded the error-output limit.`));
411
+ }
412
+ });
413
+ child.once("error", (error) => finish(error));
414
+ child.once("close", (code, childSignal) => {
415
+ if (code !== 0) {
416
+ const detail = stderr.trim() || (childSignal ? `signal ${childSignal}` : `exit ${code}`);
417
+ finish(new Error(`${import_node_path3.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
418
+ return;
419
+ }
420
+ finish(undefined, { stdout, stderr });
421
+ });
422
+ if (signal?.aborted)
423
+ abort();
424
+ else
425
+ signal?.addEventListener("abort", abort, { once: true });
426
+ });
427
+ }
428
+
429
+ // src/coordination.ts
430
+ var realmMutexTails = new Map;
431
+ var flockLookups = new Map;
432
+ var FLOCK_READY = "PYFIG_FLOCK_READY";
433
+ var FLOCK_HOLDER = [
434
+ `process.stdout.write('${FLOCK_READY}\\n')`,
435
+ "process.stdin.resume()",
436
+ 'process.stdin.once("end",()=>process.exit(0))'
437
+ ].join(";");
438
+ async function withProvisioningLock(options) {
439
+ const existing = await options.check();
440
+ if (existing !== null)
441
+ return existing;
442
+ return withResourceLock(options, async (signal) => {
443
+ const rechecked = await options.check();
444
+ return rechecked ?? options.mutate(signal);
445
+ });
446
+ }
447
+ async function withResourceLock(options, operation) {
448
+ await import_promises.mkdir(options.target, { recursive: true });
449
+ return withRealmMutex(options.key, () => withWorkerLock(options, () => withNativeFlock(options, () => withPortableLease(options, operation))));
450
+ }
451
+ async function withRealmMutex(key, operation) {
452
+ const previous = realmMutexTails.get(key) ?? Promise.resolve();
453
+ let releaseGate;
454
+ const gate = new Promise((resolve) => {
455
+ releaseGate = resolve;
456
+ });
457
+ const tail = previous.catch(() => {
458
+ return;
459
+ }).then(() => gate);
460
+ realmMutexTails.set(key, tail);
461
+ await previous.catch(() => {
462
+ return;
463
+ });
464
+ try {
465
+ return await operation();
466
+ } finally {
467
+ releaseGate();
468
+ if (realmMutexTails.get(key) === tail)
469
+ realmMutexTails.delete(key);
470
+ }
471
+ }
472
+ async function withWorkerLock(options, operation) {
473
+ const manager = workerThreads.locks;
474
+ if (!manager?.request)
475
+ return operation();
476
+ const controller = new AbortController;
477
+ const timeout = setTimeout(() => controller.abort(new Error(`Timed out waiting for worker lock ${options.key}.`)), options.timeoutMs);
478
+ let entered = false;
479
+ try {
480
+ return await manager.request(workerLockName(options.key), { mode: "exclusive", signal: controller.signal }, async () => {
481
+ entered = true;
482
+ return operation();
483
+ });
484
+ } catch (error) {
485
+ if (entered)
486
+ throw error;
487
+ return operation();
488
+ } finally {
489
+ clearTimeout(timeout);
490
+ }
491
+ }
492
+ async function withNativeFlock(options, operation) {
493
+ if (process.platform === "win32")
494
+ return operation();
495
+ const executable = await findFlock(process.env);
496
+ if (!executable)
497
+ return operation();
498
+ const child = await acquireFlock(executable, `${options.target}.flock`, options.timeoutMs);
499
+ if (!child)
500
+ return operation();
501
+ try {
502
+ return await operation();
503
+ } finally {
504
+ await releaseFlock(child);
505
+ }
506
+ }
507
+ async function findFlock(env) {
508
+ const lookupKey = `${env["PATH"] ?? ""}\x00${env["PATHEXT"] ?? ""}`;
509
+ let lookup = flockLookups.get(lookupKey);
510
+ if (!lookup) {
511
+ lookup = resolveExecutable("flock", env);
512
+ flockLookups.set(lookupKey, lookup);
513
+ }
514
+ return lookup;
515
+ }
516
+ async function acquireFlock(executable, lockPath, timeoutMs) {
517
+ const waitSeconds = Math.max(1, Math.ceil(Math.min(timeoutMs, 30000) / 1000));
518
+ const child = import_node_child_process2.spawn(executable, ["-x", "-w", String(waitSeconds), lockPath, process.execPath, "-e", FLOCK_HOLDER], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
519
+ child.stderr.resume();
520
+ let output = "";
521
+ const acquired = await new Promise((resolve) => {
522
+ let settled = false;
523
+ const finish = (value) => {
524
+ if (settled)
525
+ return;
526
+ settled = true;
527
+ clearTimeout(timer);
528
+ resolve(value);
529
+ };
530
+ const timer = setTimeout(() => finish(false), Math.min(timeoutMs, 30000) + 5000);
531
+ child.once("error", () => finish(false));
532
+ child.once("exit", () => finish(false));
533
+ child.stdout.setEncoding("utf8");
534
+ child.stdout.on("data", (chunk) => {
535
+ output += chunk;
536
+ if (output.includes(FLOCK_READY))
537
+ finish(true);
538
+ });
539
+ });
540
+ if (acquired)
541
+ return child;
542
+ child.kill();
543
+ return null;
544
+ }
545
+ async function releaseFlock(child) {
546
+ if (child.exitCode !== null)
547
+ return;
548
+ child.stdin.end();
549
+ await new Promise((resolve) => {
550
+ const timer = setTimeout(() => {
551
+ child.kill();
552
+ resolve();
553
+ }, 5000);
554
+ child.once("exit", () => {
555
+ clearTimeout(timer);
556
+ resolve();
557
+ });
558
+ });
559
+ }
560
+ async function withPortableLease(options, operation) {
561
+ const controller = new AbortController;
562
+ let compromised = null;
563
+ const retryIntervalMs = 500;
564
+ const release = await import_proper_lockfile.default.lock(options.target, {
565
+ realpath: true,
566
+ stale: 120000,
567
+ update: 15000,
568
+ retries: {
569
+ retries: Math.ceil(options.timeoutMs / retryIntervalMs),
570
+ factor: 1,
571
+ minTimeout: retryIntervalMs,
572
+ maxTimeout: retryIntervalMs
573
+ },
574
+ onCompromised: (error) => {
575
+ compromised = error;
576
+ controller.abort(error);
577
+ }
578
+ });
579
+ try {
580
+ const result = await operation(controller.signal);
581
+ if (compromised)
582
+ throw compromised;
583
+ return result;
584
+ } finally {
585
+ controller.abort();
586
+ await release();
587
+ }
588
+ }
589
+ function workerLockName(key) {
590
+ return `pyfig:${import_node_crypto2.createHash("sha256").update(key).digest("hex")}`;
591
+ }
592
+ function cacheLockTarget(cacheDirectory, name) {
593
+ return import_node_path4.default.join(cacheDirectory, "locks", name);
594
+ }
595
+
596
+ // src/probes.ts
597
+ var import_node_path6 = __toESM(require("node:path"), 1);
598
+ var import_semver2 = __toESM(require("semver"), 1);
599
+
600
+ // src/virtual-environment.ts
601
+ var import_node_fs2 = __toESM(require("node:fs"), 1);
602
+ var import_node_path5 = __toESM(require("node:path"), 1);
603
+ var VIRTUAL_ENV_DIRECTORY_NAME = ".venv";
604
+ function virtualEnvironmentDirectory(projectRoot) {
605
+ return import_node_path5.default.join(projectRoot, VIRTUAL_ENV_DIRECTORY_NAME);
606
+ }
607
+ function virtualEnvironmentPythonExecutable(virtualEnvironment) {
608
+ return import_node_path5.default.join(virtualEnvironment, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
609
+ }
610
+ function activateVirtualEnvironment(env, virtualEnvironment, pythonExecutable) {
611
+ const activated = { ...env };
612
+ const pathEntry = Object.keys(activated).find((key) => key.toUpperCase() === "PATH");
613
+ const existingPath = pathEntry ? activated[pathEntry] : undefined;
614
+ for (const key of Object.keys(activated)) {
615
+ const normalized = key.toUpperCase();
616
+ if (normalized === "PATH" || normalized === "PYTHONHOME" || normalized === "VIRTUAL_ENV") {
617
+ delete activated[key];
618
+ }
619
+ }
620
+ activated["PATH"] = [import_node_path5.default.dirname(pythonExecutable), existingPath].filter((value) => Boolean(value)).join(import_node_path5.default.delimiter);
621
+ activated["VIRTUAL_ENV"] = virtualEnvironment;
622
+ return activated;
623
+ }
624
+ function pathsEqual(left, right) {
625
+ return canonicalPath(left) === canonicalPath(right);
626
+ }
627
+ function canonicalPath(value) {
628
+ const resolved = import_node_path5.default.resolve(value);
629
+ let canonical = resolved;
630
+ try {
631
+ canonical = import_node_fs2.default.realpathSync.native(resolved);
632
+ } catch {}
633
+ return process.platform === "win32" ? canonical.toLowerCase() : canonical;
634
+ }
635
+
636
+ // src/probes.ts
637
+ var PYTHON_PROBE = `
638
+ import json
639
+ import os
640
+ import platform
641
+ import struct
642
+ import sys
643
+ import sysconfig
644
+
645
+ base_prefix = getattr(sys, "base_prefix", sys.prefix)
646
+ include_directory = sysconfig.get_path("include") or ""
647
+ header_path = os.path.join(include_directory, "Python.h") if include_directory else ""
648
+ library_candidates = []
649
+ if os.name == "nt":
650
+ library_directory = sysconfig.get_config_var("LIBDIR") or os.path.join(base_prefix, "libs")
651
+ configured_library = sysconfig.get_config_var("LDLIBRARY") or ""
652
+ if configured_library:
653
+ library_candidates.append(os.path.join(library_directory, os.path.splitext(configured_library)[0] + ".lib"))
654
+ library_candidates.append(os.path.join(library_directory, f"python{sys.version_info.major}{sys.version_info.minor}.lib"))
655
+ else:
656
+ library_directories = [
657
+ sysconfig.get_config_var("LIBDIR"),
658
+ sysconfig.get_config_var("LIBPL"),
659
+ os.path.join(base_prefix, "lib"),
660
+ ]
661
+ library_names = [
662
+ sysconfig.get_config_var("LDLIBRARY"),
663
+ sysconfig.get_config_var("LIBRARY"),
664
+ ]
665
+ for directory in library_directories:
666
+ for name in library_names:
667
+ if directory and name:
668
+ library_candidates.append(os.path.join(directory, name))
669
+
670
+ library_path = next((candidate for candidate in library_candidates if os.path.isfile(candidate)), "")
671
+ embedding = None
672
+ if os.path.isfile(header_path) and library_path:
673
+ library_name = os.path.basename(library_path)
674
+ library_kind = "import" if os.name == "nt" else ("static" if library_name.endswith(".a") else "shared")
675
+ embedding = {
676
+ "includeDirectory": include_directory,
677
+ "headerPath": header_path,
678
+ "libraryPath": library_path,
679
+ "libraryDirectory": os.path.dirname(library_path),
680
+ "libraryName": library_name,
681
+ "libraryKind": library_kind,
682
+ }
683
+
684
+ release_level = sys.version_info.releaselevel
685
+ release_suffix = "" if release_level == "final" else f"-{release_level}.{sys.version_info.serial}"
686
+
687
+ print(json.dumps({
688
+ "executable": sys.executable,
689
+ "baseExecutable": getattr(sys, "_base_executable", sys.executable),
690
+ "version": ".".join(map(str, sys.version_info[:3])) + release_suffix,
691
+ "major": sys.version_info.major,
692
+ "minor": sys.version_info.minor,
693
+ "patch": sys.version_info.micro,
694
+ "releaseLevel": release_level,
695
+ "implementation": sys.implementation.name,
696
+ "architecture": platform.machine().lower(),
697
+ "pointerBits": struct.calcsize("P") * 8,
698
+ "prefix": sys.prefix,
699
+ "basePrefix": base_prefix,
700
+ "virtualEnvironment": sys.prefix if sys.prefix != base_prefix else None,
701
+ "gilDisabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
702
+ "embedding": embedding,
703
+ }))
704
+ `;
705
+ async function findPython(options) {
706
+ const candidates = [];
707
+ if (options.python)
708
+ candidates.push({ executable: options.python, args: [] });
709
+ if (process.platform === "win32") {
710
+ candidates.push({ executable: "python", args: [] }, { executable: "python3", args: [] }, { executable: "py", args: ["-3"] });
711
+ } else {
712
+ candidates.push(...["python3.15", "python3.14", "python3.13", "python3.12", "python3.11", "python3.10"].map((executable) => ({ executable, args: [] })), { executable: "python3", args: [] }, { executable: "python", args: [] });
713
+ }
714
+ for (const candidate of candidates) {
715
+ const info = await probePython(candidate, options);
716
+ if (info && pythonSatisfiesRequirements(info, options))
717
+ return info;
718
+ }
719
+ return null;
720
+ }
721
+ function pythonSatisfiesRequirements(python, options) {
722
+ return import_semver2.default.satisfies(python.version, options.pythonRange) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
723
+ }
724
+ async function probePython(candidate, options) {
725
+ const executable = await resolveExecutable(candidate.executable, options.env);
726
+ if (!executable)
727
+ return null;
728
+ try {
729
+ const { stdout } = run(executable, [...candidate.args, "-c", PYTHON_PROBE], options.env, options.probeTimeoutMs);
730
+ const info = JSON.parse(stdout.trim());
731
+ return import_semver2.default.valid(info.version) ? info : null;
732
+ } catch {
733
+ return null;
734
+ }
735
+ }
736
+ async function findPip(python, options) {
737
+ if (!python.virtualEnvironment) {
738
+ throw new Error("pip discovery requires a virtual-environment Python interpreter.");
739
+ }
740
+ const activeEnv = activateVirtualEnvironment(options.env, python.virtualEnvironment, python.executable);
741
+ try {
742
+ const { stdout } = run(python.executable, ["-m", "pip", "--version"], activeEnv, options.probeTimeoutMs);
743
+ const details = parsePipVersionOutput(stdout);
744
+ const version = normalizeVersion(details?.version);
745
+ if (!version || !import_semver2.default.satisfies(version, options.pipRange))
746
+ return null;
747
+ if (!details || !pipBelongsToPython(details, python))
748
+ return null;
749
+ return {
750
+ kind: "pip",
751
+ executable: python.executable,
752
+ prefixArgs: ["-m", "pip"],
753
+ version,
754
+ pythonExecutable: python.executable,
755
+ virtualEnvironment: python.virtualEnvironment
756
+ };
757
+ } catch {
758
+ return null;
759
+ }
760
+ }
761
+ function parsePipVersionOutput(output) {
762
+ const match = output.trim().match(/^pip\s+(\S+)\s+from\s+(.+?)\s+\(python\s+(\d+)\.(\d+)(?:\.\d+)?\)$/iu);
763
+ if (!match?.[1] || !match[2] || !match[3] || !match[4])
764
+ return null;
765
+ return {
766
+ version: match[1],
767
+ location: match[2],
768
+ pythonMajor: Number.parseInt(match[3], 10),
769
+ pythonMinor: Number.parseInt(match[4], 10)
770
+ };
771
+ }
772
+ function pipBelongsToPython(pip, python) {
773
+ if (pip.pythonMajor !== python.major || pip.pythonMinor !== python.minor)
774
+ return false;
775
+ const prefix = normalizePathForComparison(import_node_path6.default.resolve(python.prefix));
776
+ const location = normalizePathForComparison(import_node_path6.default.resolve(pip.location));
777
+ const relative = import_node_path6.default.relative(prefix, location);
778
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path6.default.sep}`) && !import_node_path6.default.isAbsolute(relative);
779
+ }
780
+ async function findUv(options) {
781
+ const configured = options.env["PYFIG_UV"] ?? "uv";
782
+ const executable = await resolveExecutable(configured, options.env);
783
+ const uv = executable ? probeUv(executable, options) : null;
784
+ return uv && import_semver2.default.satisfies(uv.version, options.uvRange) ? uv : null;
785
+ }
786
+ function probeUv(executable, options) {
787
+ try {
788
+ const { stdout } = run(executable, ["--version"], options.env, options.probeTimeoutMs);
789
+ const version = normalizeVersion(stdout.match(/^uv\s+(\S+)/iu)?.[1]);
790
+ return version ? { executable, version } : null;
791
+ } catch {
792
+ return null;
793
+ }
794
+ }
795
+ function normalizeVersion(value) {
796
+ if (!value)
797
+ return null;
798
+ return import_semver2.default.valid(value) ?? import_semver2.default.coerce(value)?.version ?? null;
799
+ }
800
+ function normalizePathForComparison(value) {
801
+ return process.platform === "win32" ? value.toLowerCase() : value;
802
+ }
803
+ function pythonArchitectureMatchesRuntime(python) {
804
+ const architecture = python.architecture.toLowerCase();
805
+ if (python.pointerBits !== (process.arch === "ia32" ? 32 : 64))
806
+ return false;
807
+ if (process.arch === "x64")
808
+ return architecture === "x86_64" || architecture === "amd64";
809
+ if (process.arch === "arm64")
810
+ return architecture === "aarch64" || architecture === "arm64";
811
+ if (process.arch === "ia32") {
812
+ return architecture === "x86" || architecture === "i386" || architecture === "i686";
813
+ }
814
+ return architecture === process.arch;
815
+ }
816
+
817
+ // src/state.ts
818
+ var completed = new Map;
819
+ var inFlight = new Map;
820
+ var cacheGeneration = 0;
821
+ async function memoizeAsync(namespace, key, ttlMs, validate, load) {
822
+ const cacheKey = `${namespace}\x00${key}`;
823
+ const cached = completed.get(cacheKey);
824
+ if (cached && cached.expiresAt >= Date.now()) {
825
+ try {
826
+ if (await validate(cached.value))
827
+ return cached.value;
828
+ } catch {}
829
+ completed.delete(cacheKey);
830
+ }
831
+ const pending = inFlight.get(cacheKey);
832
+ if (pending)
833
+ return pending;
834
+ const generation = cacheGeneration;
835
+ const loading = load().then((value) => {
836
+ if (ttlMs > 0 && generation === cacheGeneration) {
837
+ completed.set(cacheKey, { expiresAt: Date.now() + ttlMs, value });
838
+ }
839
+ return value;
840
+ }).finally(() => {
841
+ if (inFlight.get(cacheKey) === loading)
842
+ inFlight.delete(cacheKey);
843
+ });
844
+ inFlight.set(cacheKey, loading);
845
+ return loading;
846
+ }
847
+ function memoizeSync(namespace, key, ttlMs, validate, load) {
848
+ const cacheKey = `${namespace}\x00${key}`;
849
+ const cached = completed.get(cacheKey);
850
+ if (cached && cached.expiresAt >= Date.now()) {
851
+ try {
852
+ if (validate(cached.value))
853
+ return cached.value;
854
+ } catch {}
855
+ completed.delete(cacheKey);
856
+ }
857
+ const value = load();
858
+ if (ttlMs > 0)
859
+ completed.set(cacheKey, { expiresAt: Date.now() + ttlMs, value });
860
+ return value;
861
+ }
862
+ function clearPyfigCaches() {
863
+ cacheGeneration += 1;
864
+ completed.clear();
865
+ inFlight.clear();
866
+ }
867
+
868
+ // src/provision.ts
869
+ async function ensurePython(options = {}) {
870
+ const resolved = resolveOptions(options);
871
+ return memoizeAsync("python", configurationFingerprint(resolved), resolved.cacheTtlMs, pythonIsPresent, () => ensurePythonResolved(resolved));
872
+ }
873
+ async function ensureVirtualEnvironment(options = {}) {
874
+ const resolved = resolveOptions(options);
875
+ return memoizeAsync("venv", configurationFingerprint(resolved), resolved.cacheTtlMs, (python) => pythonIsPresent(python) && Boolean(python.virtualEnvironment) && pathsEqual(python.virtualEnvironment, virtualEnvironmentDirectory(resolved.projectRoot)), () => ensureVirtualEnvironmentResolved(resolved));
876
+ }
877
+ async function ensureUv(options = {}) {
878
+ const resolved = resolveOptions(options);
879
+ return memoizeAsync("uv", configurationFingerprint(resolved), resolved.cacheTtlMs, (uv) => import_node_fs4.default.existsSync(uv.executable), () => ensureUvResolved(resolved));
880
+ }
881
+ async function ensureEnvironment(options = {}) {
882
+ const resolved = resolveOptions(options);
883
+ return memoizeAsync("environment", configurationFingerprint(resolved), resolved.cacheTtlMs, environmentIsPresent, async () => {
884
+ const python = await ensureVirtualEnvironmentResolved(resolved);
885
+ const packageManager = await ensurePipResolved(python, resolved);
886
+ return { python, packageManager };
887
+ });
888
+ }
889
+ async function ensureVirtualEnvironmentResolved(options) {
890
+ const directory = virtualEnvironmentDirectory(options.projectRoot);
891
+ const check = async () => {
892
+ const existing = await inspectVirtualEnvironment(directory, options);
893
+ if (!existing)
894
+ return null;
895
+ if (pythonSatisfiesRequirements(existing, options) && await virtualEnvironmentMatchesConfiguredPython(existing, options)) {
896
+ return existing;
897
+ }
898
+ throw incompatibleVirtualEnvironmentError(directory, existing, options);
899
+ };
900
+ const existing = await check();
901
+ if (existing)
902
+ return existing;
903
+ if (!options.createVirtualEnvironment) {
904
+ throw new Error(`No virtual environment at ${directory} satisfies the Python requirements; ` + "virtual-environment creation is disabled.");
905
+ }
906
+ const basePython = await ensurePythonResolved(options);
907
+ const preferredUv = await findAvailableUv(options) ?? (options.installUv ? await ensureUvResolved(options) : null);
908
+ return withProvisioningLock({
909
+ key: `venv:${canonicalPath(directory)}`,
910
+ target: directory,
911
+ timeoutMs: options.coordinationTimeoutMs,
912
+ env: options.env,
913
+ check,
914
+ mutate: async (signal) => {
915
+ const entries = await import_promises2.readdir(directory);
916
+ const hasVenvConfiguration = import_node_fs4.default.existsSync(import_node_path7.default.join(directory, "pyvenv.cfg"));
917
+ if (entries.length > 0 && !hasVenvConfiguration) {
918
+ throw new Error(`Refusing to replace non-virtual-environment contents at ${directory}.`);
919
+ }
920
+ await createVirtualEnvironment(directory, basePython, preferredUv, options, signal);
921
+ const installed = await inspectVirtualEnvironment(directory, options);
922
+ if (!installed || !pythonSatisfiesRequirements(installed, options)) {
923
+ throw new Error(`Python created ${directory}, but it does not satisfy the requested runtime capabilities.`);
924
+ }
925
+ return installed;
926
+ }
927
+ });
928
+ }
929
+ async function createVirtualEnvironment(directory, basePython, preferredUv, options, signal) {
930
+ if (preferredUv) {
931
+ try {
932
+ await createVirtualEnvironmentWithUv(preferredUv, directory, basePython, options, signal);
933
+ return;
934
+ } catch {}
935
+ }
936
+ try {
937
+ await runAsync(basePython.executable, ["-m", "venv", directory], withoutActiveVirtualEnvironment(options.env), options.installTimeoutMs, signal);
938
+ return;
939
+ } catch (systemVenvError) {
940
+ if (preferredUv || !options.installUv)
941
+ throw systemVenvError;
942
+ }
943
+ throw new Error("Python could not create a virtual environment and uv installation is disabled.");
944
+ }
945
+ async function createVirtualEnvironmentWithUv(uv, directory, basePython, options, signal) {
946
+ await runAsync(uv.executable, [
947
+ "venv",
948
+ "--no-project",
949
+ "--no-config",
950
+ "--no-python-downloads",
951
+ "--allow-existing",
952
+ "--python",
953
+ basePython.executable,
954
+ directory
955
+ ], uvEnvironment(options), options.installTimeoutMs, signal);
956
+ }
957
+ async function inspectVirtualEnvironment(directory, options) {
958
+ const executable = virtualEnvironmentPythonExecutable(directory);
959
+ const info = await probePython({ executable, args: [] }, options);
960
+ return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
961
+ }
962
+ async function ensurePythonResolved(options) {
963
+ const existing = await discoverPython(options);
964
+ if (existing)
965
+ return existing;
966
+ if (!options.installPython) {
967
+ const capability = options.requireEmbedding ? " with compatible CPython development headers, architecture, and a linkable library" : "";
968
+ throw new Error(`No Python interpreter satisfies ${options.pythonRange}${capability}; ` + "managed Python installation is disabled.");
969
+ }
970
+ const lockTarget = cacheLockTarget(options.cacheDir, "python");
971
+ return withProvisioningLock({
972
+ key: `python:${canonicalPath(lockTarget)}`,
973
+ target: lockTarget,
974
+ timeoutMs: options.coordinationTimeoutMs,
975
+ env: options.env,
976
+ check: () => discoverPython(options),
977
+ mutate: async (signal) => {
978
+ const uv = await ensureUvResolved(options);
979
+ const uvEnv = uvEnvironment(options);
980
+ await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython], uvEnv, options.installTimeoutMs, signal);
981
+ const installed = await findPythonWithUv(uv, options, signal);
982
+ if (!installed) {
983
+ throw new Error(`uv installed Python ${options.managedPython}, but it does not satisfy the requested ` + "runtime capabilities.");
984
+ }
985
+ return installed;
986
+ }
987
+ });
988
+ }
989
+ async function discoverPython(options) {
990
+ if (options.python) {
991
+ const configured = await probePython({ executable: options.python, args: [] }, options);
992
+ if (configured && pythonSatisfiesRequirements(configured, options))
993
+ return configured;
994
+ throw new Error(`Configured Python ${options.python} is unavailable or does not satisfy ${options.pythonRange}.`);
995
+ }
996
+ const existingUv = await findAvailableUv(options);
997
+ if (existingUv) {
998
+ const uvPython = await findPythonWithUv(existingUv, options);
999
+ if (uvPython)
1000
+ return uvPython;
1001
+ }
1002
+ return findPython({ ...options, python: "" });
1003
+ }
1004
+ async function findPythonWithUv(uv, options, signal) {
1005
+ const requests = [undefined, options.managedPython];
1006
+ for (const requestedPython of requests) {
1007
+ const args = [
1008
+ "python",
1009
+ "find",
1010
+ "--no-project",
1011
+ "--no-config",
1012
+ "--managed-python",
1013
+ "--no-python-downloads"
1014
+ ];
1015
+ if (requestedPython)
1016
+ args.push(requestedPython);
1017
+ try {
1018
+ const { stdout } = await runAsync(uv.executable, args, uvEnvironment(options), options.probeTimeoutMs, signal);
1019
+ const info = await probePython({ executable: stdout.trim(), args: [] }, options);
1020
+ if (info && pythonSatisfiesRequirements(info, options))
1021
+ return info;
1022
+ } catch (error) {
1023
+ if (signal?.aborted)
1024
+ throw signal.reason ?? error;
1025
+ }
1026
+ }
1027
+ return null;
1028
+ }
1029
+ async function ensurePipResolved(python, options) {
1030
+ const existingUv = await findAvailableUv(options);
1031
+ if (existingUv)
1032
+ return uvPackageManager(existingUv, python);
1033
+ if (options.installUv)
1034
+ return uvPackageManager(await ensureUvResolved(options), python);
1035
+ const existingPip = await findPip(python, options);
1036
+ if (existingPip)
1037
+ return existingPip;
1038
+ throw new Error(`The virtual environment's python -m pip does not satisfy ${options.pipRange}; ` + "uv installation is disabled.");
1039
+ }
1040
+ async function ensureUvResolved(options) {
1041
+ const existing = await findAvailableUv(options);
1042
+ if (existing)
1043
+ return existing;
1044
+ if (!options.installUv) {
1045
+ throw new Error(`Compatible uv is unavailable (required ${options.uvRange}), and uv installation is disabled.`);
1046
+ }
1047
+ const installDir = uvInstallDirectory(options);
1048
+ const executable = uvExecutable(options);
1049
+ const lockTarget = cacheLockTarget(options.cacheDir, "uv");
1050
+ return withProvisioningLock({
1051
+ key: `uv:${canonicalPath(lockTarget)}`,
1052
+ target: lockTarget,
1053
+ timeoutMs: options.coordinationTimeoutMs,
1054
+ env: options.env,
1055
+ check: () => findAvailableUv(options),
1056
+ mutate: async (signal) => {
1057
+ await installUv(installDir, options, signal);
1058
+ const result = probeUv(executable, options);
1059
+ if (!result || result.version !== options.uvVersion) {
1060
+ throw new Error(`Expected managed uv ${options.uvVersion}, but the installed executable did not match.`);
1061
+ }
1062
+ return result;
1063
+ }
1064
+ });
1065
+ }
1066
+ async function findAvailableUv(options) {
1067
+ const system = await findUv(options);
1068
+ if (system)
1069
+ return system;
1070
+ const managed = probeUv(uvExecutable(options), options);
1071
+ return managed?.version === options.uvVersion ? managed : null;
1072
+ }
1073
+ function uvInstallDirectory(options) {
1074
+ return import_node_path7.default.join(options.cacheDir, "tools", "uv", options.uvVersion);
1075
+ }
1076
+ function uvExecutable(options) {
1077
+ return import_node_path7.default.join(uvInstallDirectory(options), process.platform === "win32" ? "uv.exe" : "uv");
1078
+ }
1079
+ function uvPackageManager(uv, python) {
1080
+ if (!python.virtualEnvironment) {
1081
+ throw new Error("uv pip commands require a virtual-environment Python interpreter.");
1082
+ }
1083
+ return {
1084
+ kind: "uv",
1085
+ executable: uv.executable,
1086
+ prefixArgs: ["pip"],
1087
+ version: uv.version,
1088
+ pythonExecutable: python.executable,
1089
+ virtualEnvironment: python.virtualEnvironment
1090
+ };
1091
+ }
1092
+ function withoutActiveVirtualEnvironment(env) {
1093
+ const result = { ...env };
1094
+ for (const key of Object.keys(result)) {
1095
+ const normalized = key.toUpperCase();
1096
+ if (normalized === "CONDA_PREFIX" || normalized === "VIRTUAL_ENV" || normalized === "PYTHONHOME") {
1097
+ delete result[key];
1098
+ }
1099
+ }
1100
+ return result;
1101
+ }
1102
+ async function installUv(installDir, options, signal) {
1103
+ const asset = uvAsset();
1104
+ const baseUrl = `https://github.com/astral-sh/uv/releases/download/${options.uvVersion}`;
1105
+ const temporary = await import_promises2.mkdtemp(import_node_path7.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
1106
+ const dispatcher = proxyDispatcher(options.env);
1107
+ try {
1108
+ const archive = import_node_path7.default.join(temporary, asset);
1109
+ await download(`${baseUrl}/${asset}`, archive, options.downloadTimeoutMs, signal, dispatcher);
1110
+ const expected = (await downloadText(`${baseUrl}/${asset}.sha256`, options.downloadTimeoutMs, signal, dispatcher)).trim().split(/\s+/u)[0];
1111
+ const actual = await sha256File(archive);
1112
+ if (!expected || expected.toLowerCase() !== actual.toLowerCase()) {
1113
+ throw new Error(`uv archive checksum mismatch for ${asset}`);
1114
+ }
1115
+ const extracted = import_node_path7.default.join(temporary, "extracted");
1116
+ await import_promises2.mkdir(extracted, { recursive: true });
1117
+ signal.throwIfAborted();
1118
+ if (asset.endsWith(".zip"))
1119
+ await import_extract_zip.default(archive, { dir: extracted });
1120
+ else
1121
+ await tar.x({ file: archive, cwd: extracted, strict: true });
1122
+ signal.throwIfAborted();
1123
+ const source = await findFile(extracted, process.platform === "win32" ? "uv.exe" : "uv");
1124
+ if (!source)
1125
+ throw new Error(`uv archive did not contain the expected executable: ${asset}`);
1126
+ await publishUvAtomically(source, installDir, options);
1127
+ } finally {
1128
+ await dispatcher?.close();
1129
+ await import_promises2.rm(temporary, { recursive: true, force: true });
1130
+ }
1131
+ }
1132
+ async function publishUvAtomically(source, installDir, options) {
1133
+ await import_promises2.mkdir(installDir, { recursive: true });
1134
+ const executable = uvExecutable(options);
1135
+ const staged = import_node_path7.default.join(installDir, `.${import_node_path7.default.basename(executable)}.${process.pid}.${import_node_crypto3.randomUUID()}.tmp`);
1136
+ try {
1137
+ await import_promises2.copyFile(source, staged);
1138
+ if (process.platform !== "win32")
1139
+ await import_promises2.chmod(staged, 493);
1140
+ const stagedUv = probeUv(staged, options);
1141
+ if (!stagedUv || stagedUv.version !== options.uvVersion) {
1142
+ throw new Error(`Downloaded uv executable is not version ${options.uvVersion}.`);
1143
+ }
1144
+ if (process.platform === "win32")
1145
+ await import_promises2.rm(executable, { force: true });
1146
+ await import_promises2.rename(staged, executable);
1147
+ } finally {
1148
+ await import_promises2.rm(staged, { force: true });
1149
+ }
1150
+ }
1151
+ function uvAsset() {
1152
+ const architecture = process.arch === "x64" ? "x86_64" : process.arch === "arm64" ? "aarch64" : null;
1153
+ if (!architecture)
1154
+ throw new Error(`uv bootstrap does not support architecture ${process.arch}.`);
1155
+ if (process.platform === "win32")
1156
+ return `uv-${architecture}-pc-windows-msvc.zip`;
1157
+ if (process.platform === "darwin")
1158
+ return `uv-${architecture}-apple-darwin.tar.gz`;
1159
+ if (process.platform === "linux")
1160
+ return `uv-${architecture}-unknown-linux-musl.tar.gz`;
1161
+ throw new Error(`uv bootstrap does not support platform ${process.platform}.`);
1162
+ }
1163
+ async function download(url, destination, timeoutMs, externalSignal, dispatcher) {
1164
+ const response = await import_undici.fetch(url, {
1165
+ dispatcher,
1166
+ redirect: "follow",
1167
+ signal: combinedSignal(timeoutMs, externalSignal)
1168
+ });
1169
+ if (!response.ok || !response.body) {
1170
+ await response.body?.cancel();
1171
+ throw new Error(`Could not download ${url}: HTTP ${response.status}`);
1172
+ }
1173
+ await import_promises3.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs3.createWriteStream(destination, { flags: "wx" }));
1174
+ }
1175
+ async function downloadText(url, timeoutMs, externalSignal, dispatcher) {
1176
+ const response = await import_undici.fetch(url, {
1177
+ dispatcher,
1178
+ redirect: "follow",
1179
+ signal: combinedSignal(timeoutMs, externalSignal)
1180
+ });
1181
+ if (!response.ok) {
1182
+ await response.body?.cancel();
1183
+ throw new Error(`Could not download ${url}: HTTP ${response.status}`);
1184
+ }
1185
+ return response.text();
1186
+ }
1187
+ function proxyDispatcher(env) {
1188
+ const httpProxy = environmentValue(env, "http_proxy", "HTTP_PROXY");
1189
+ const httpsProxy = environmentValue(env, "https_proxy", "HTTPS_PROXY");
1190
+ if (!httpProxy && !httpsProxy)
1191
+ return;
1192
+ return new import_undici.EnvHttpProxyAgent({
1193
+ httpProxy,
1194
+ httpsProxy,
1195
+ noProxy: environmentValue(env, "no_proxy", "NO_PROXY")
1196
+ });
1197
+ }
1198
+ function environmentValue(env, lowercase, uppercase) {
1199
+ return env[lowercase] ?? env[uppercase];
1200
+ }
1201
+ function combinedSignal(timeoutMs, externalSignal) {
1202
+ return AbortSignal.any([externalSignal, AbortSignal.timeout(timeoutMs)]);
1203
+ }
1204
+ async function sha256File(file) {
1205
+ const hash = import_node_crypto3.createHash("sha256");
1206
+ for await (const chunk of import_node_fs3.createReadStream(file))
1207
+ hash.update(chunk);
1208
+ return hash.digest("hex");
1209
+ }
1210
+ async function findFile(directory, name) {
1211
+ for (const entry of await import_promises2.readdir(directory, { withFileTypes: true })) {
1212
+ const target = import_node_path7.default.join(directory, entry.name);
1213
+ if (entry.isFile() && entry.name === name)
1214
+ return target;
1215
+ if (entry.isDirectory()) {
1216
+ const nested = await findFile(target, name);
1217
+ if (nested)
1218
+ return nested;
1219
+ }
1220
+ }
1221
+ return null;
1222
+ }
1223
+ function incompatibleVirtualEnvironmentError(directory, python, options) {
1224
+ const capabilities = options.requireEmbedding ? " or lacks compatible CPython embedding headers, library, or architecture" : "";
1225
+ return new Error(`The existing virtual environment at ${directory} uses Python ${python.version}, ` + `which does not satisfy ${options.pythonRange}${capabilities}.`);
1226
+ }
1227
+ async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, options) {
1228
+ if (!options.python)
1229
+ return true;
1230
+ const configured = await probePython({ executable: options.python, args: [] }, options);
1231
+ if (!configured || !pythonSatisfiesRequirements(configured, options))
1232
+ return false;
1233
+ if (configured.virtualEnvironment && pathsEqual(configured.executable, virtualEnvironment.executable)) {
1234
+ return true;
1235
+ }
1236
+ const configuredBase = configured.virtualEnvironment ? configured.baseExecutable : configured.executable;
1237
+ return pathsEqual(configuredBase, virtualEnvironment.baseExecutable);
1238
+ }
1239
+ function pythonIsPresent(python) {
1240
+ return import_node_fs4.default.existsSync(python.executable);
1241
+ }
1242
+ function environmentIsPresent(environment) {
1243
+ return pythonIsPresent(environment.python) && import_node_fs4.default.existsSync(environment.packageManager.executable);
1244
+ }
1245
+
1246
+ // src/runners.ts
1247
+ var import_node_child_process3 = require("node:child_process");
1248
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
1249
+
1250
+ // src/package-target.ts
1251
+ var import_node_os2 = __toESM(require("node:os"), 1);
1252
+ var TARGET_CHANGING_OPTIONS = new Set([
1253
+ "--config-file",
1254
+ "--prefix",
1255
+ "--python",
1256
+ "--root",
1257
+ "--system",
1258
+ "--target",
1259
+ "--user"
1260
+ ]);
1261
+ var TARGET_CHANGING_SHORT_OPTIONS = new Set(["-p", "-t"]);
1262
+ var TARGET_ENVIRONMENT_VARIABLES = new Set([
1263
+ "CONDA_PREFIX",
1264
+ "PIP_CONFIG_FILE",
1265
+ "PIP_PREFIX",
1266
+ "PIP_PYTHON",
1267
+ "PIP_ROOT",
1268
+ "PIP_TARGET",
1269
+ "PIP_USER",
1270
+ "PYTHONHOME",
1271
+ "PYTHONUSERBASE",
1272
+ "UV_CONFIG_FILE",
1273
+ "UV_PROJECT_ENVIRONMENT",
1274
+ "UV_PREFIX",
1275
+ "UV_PYTHON",
1276
+ "UV_ROOT",
1277
+ "UV_SYSTEM_PYTHON",
1278
+ "UV_TARGET"
1279
+ ]);
1280
+ function assertSafePackageArguments(args) {
1281
+ for (const argument of args) {
1282
+ if (argument === "--")
1283
+ break;
1284
+ const [name] = argument.split("=", 1);
1285
+ if (name && TARGET_CHANGING_OPTIONS.has(name)) {
1286
+ throw new Error(`${name} is not allowed because Pyfig package commands are pinned to the repository .venv.`);
1287
+ }
1288
+ const shortName = argument.slice(0, 2);
1289
+ if (TARGET_CHANGING_SHORT_OPTIONS.has(shortName)) {
1290
+ throw new Error(`${shortName} is not allowed because Pyfig package commands are pinned to the repository .venv.`);
1291
+ }
1292
+ }
1293
+ }
1294
+ function packageManagerEnvironment(env, packageManager) {
1295
+ const active = sanitizedTargetEnvironment(env);
1296
+ const activated = activateVirtualEnvironment(active, packageManager.virtualEnvironment, packageManager.pythonExecutable);
1297
+ return packageManager.kind === "uv" ? {
1298
+ ...activated,
1299
+ UV_NO_CONFIG: "1",
1300
+ UV_PYTHON: packageManager.pythonExecutable
1301
+ } : {
1302
+ ...activated,
1303
+ PIP_CONFIG_FILE: import_node_os2.default.devNull,
1304
+ PIP_REQUIRE_VIRTUALENV: "1",
1305
+ PYTHONNOUSERSITE: "1"
1306
+ };
1307
+ }
1308
+ function uvPipEnvironment(env, python) {
1309
+ if (!python.virtualEnvironment) {
1310
+ throw new Error("uv pip commands require the repository virtual environment.");
1311
+ }
1312
+ return {
1313
+ ...activateVirtualEnvironment(sanitizedTargetEnvironment(env), python.virtualEnvironment, python.executable),
1314
+ UV_NO_CONFIG: "1",
1315
+ UV_PYTHON: python.executable
1316
+ };
1317
+ }
1318
+ function assertMutationUsesUv(packageManager, args) {
1319
+ if (packageManager.kind === "pip" && isPackageMutation(args)) {
1320
+ throw new Error("Package mutation requires uv so the repository environment is locked across processes. " + "Enable uv installation or use a read-only pip command.");
1321
+ }
1322
+ }
1323
+ function isPackageMutation(args) {
1324
+ return args.some((argument) => argument === "install" || argument === "sync" || argument === "uninstall");
1325
+ }
1326
+ function isUvPipCommand(args) {
1327
+ const separator = args.indexOf("--");
1328
+ const commandArguments = separator === -1 ? args : args.slice(0, separator);
1329
+ return commandArguments.includes("pip");
1330
+ }
1331
+ function sanitizedTargetEnvironment(env) {
1332
+ const sanitized = { ...env };
1333
+ for (const key of Object.keys(sanitized)) {
1334
+ if (TARGET_ENVIRONMENT_VARIABLES.has(key.toUpperCase()))
1335
+ delete sanitized[key];
1336
+ }
1337
+ return sanitized;
1338
+ }
1339
+
1340
+ // src/runtime-files.ts
1341
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
1342
+ var import_node_path8 = __toESM(require("node:path"), 1);
1343
+ function runtimeFile(name) {
1344
+ const moduleDirectory = __dirname;
1345
+ const compiled = import_node_path8.default.join(moduleDirectory, `${name}.js`);
1346
+ return import_node_fs5.default.existsSync(compiled) ? compiled : import_node_path8.default.join(moduleDirectory, `${name}.ts`);
1347
+ }
1348
+
1349
+ // src/runners.ts
1350
+ async function pythonSpawn(args = [], spawnOptions = {}, pyfigOptions = {}) {
1351
+ const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
1352
+ const python = await ensureVirtualEnvironment(options);
1353
+ const command = buildPythonCommand(python, args, resolveOptions(options).env);
1354
+ return import_node_child_process3.spawn(command.executable, command.args, {
1355
+ ...spawnOptions,
1356
+ env: command.env
1357
+ });
1358
+ }
1359
+ function pythonSpawnSync(args = [], spawnOptions = {}, pyfigOptions = {}) {
1360
+ const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
1361
+ const python = resolveSynchronously("venv", options);
1362
+ const command = buildPythonCommand(python, args, resolveOptions(options).env);
1363
+ return import_node_child_process3.spawnSync(command.executable, command.args, {
1364
+ ...spawnOptions,
1365
+ env: command.env
1366
+ });
1367
+ }
1368
+ async function pipSpawn(args = [], spawnOptions = {}, pyfigOptions = {}) {
1369
+ const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
1370
+ const environment = await ensureEnvironment(options);
1371
+ const command = buildPipCommand(environment.packageManager, args, resolveOptions(options).env);
1372
+ return import_node_child_process3.spawn(command.executable, command.args, {
1373
+ ...spawnOptions,
1374
+ env: command.env
1375
+ });
1376
+ }
1377
+ function pipSpawnSync(args = [], spawnOptions = {}, pyfigOptions = {}) {
1378
+ const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
1379
+ const environment = resolveSynchronously("environment", options);
1380
+ const command = buildPipCommand(environment.packageManager, args, resolveOptions(options).env);
1381
+ return import_node_child_process3.spawnSync(command.executable, command.args, {
1382
+ ...spawnOptions,
1383
+ env: command.env
1384
+ });
1385
+ }
1386
+ async function uvSpawn(args = [], spawnOptions = {}, pyfigOptions = {}) {
1387
+ const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
1388
+ const resolved = resolveOptions(options);
1389
+ const uv = await ensureUv(resolved);
1390
+ const python = isUvPipCommand(args) ? await ensureVirtualEnvironment(resolved) : undefined;
1391
+ const command = buildUvCommand(uv, args, resolved, python);
1392
+ return import_node_child_process3.spawn(command.executable, command.args, {
1393
+ ...spawnOptions,
1394
+ env: command.env
1395
+ });
1396
+ }
1397
+ function uvSpawnSync(args = [], spawnOptions = {}, pyfigOptions = {}) {
1398
+ const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
1399
+ const resolved = resolveOptions(options);
1400
+ const resolution = isUvPipCommand(args) ? resolveSynchronously("uv-pip", resolved) : { uv: resolveSynchronously("uv", resolved), python: undefined };
1401
+ const command = buildUvCommand(resolution.uv, args, resolved, resolution.python);
1402
+ return import_node_child_process3.spawnSync(command.executable, command.args, {
1403
+ ...spawnOptions,
1404
+ env: command.env
1405
+ });
1406
+ }
1407
+ function buildPythonCommand(python, args, env) {
1408
+ const commandEnv = python.virtualEnvironment ? activateVirtualEnvironment(env, python.virtualEnvironment, python.executable) : env;
1409
+ return {
1410
+ executable: python.executable,
1411
+ args: [...args],
1412
+ env: commandEnv
1413
+ };
1414
+ }
1415
+ function buildPipCommand(packageManager, args, env) {
1416
+ if (!packageManager.virtualEnvironment) {
1417
+ throw new Error("pip commands require an active virtual environment.");
1418
+ }
1419
+ assertSafePackageArguments(args);
1420
+ assertMutationUsesUv(packageManager, args);
1421
+ return {
1422
+ executable: packageManager.executable,
1423
+ args: [...packageManager.prefixArgs, ...args],
1424
+ env: packageManagerEnvironment(env, packageManager)
1425
+ };
1426
+ }
1427
+ function buildUvCommand(uv, args, options, python) {
1428
+ const isPip = isUvPipCommand(args);
1429
+ if (isPip && !python?.virtualEnvironment) {
1430
+ throw new Error("uv pip commands require the repo virtual-environment Python interpreter.");
1431
+ }
1432
+ if (isPip)
1433
+ assertSafePackageArguments(args);
1434
+ const env = isPip && python ? uvPipEnvironment(uvEnvironment(options), python) : uvEnvironment(options);
1435
+ return {
1436
+ executable: uv.executable,
1437
+ args: [...args],
1438
+ env
1439
+ };
1440
+ }
1441
+ function optionsForInvocation(pyfigOptions, spawnEnv) {
1442
+ return spawnEnv === undefined ? pyfigOptions : { ...pyfigOptions, env: spawnEnv };
1443
+ }
1444
+ function resolveSynchronously(target, options) {
1445
+ const resolved = resolveOptions(options);
1446
+ const cacheKey = `${target}:${configurationFingerprint(resolved)}`;
1447
+ return memoizeSync("sync-resolution", cacheKey, resolved.cacheTtlMs, synchronousResolutionIsPresent, () => {
1448
+ const request = { target, options: resolved };
1449
+ const result = import_node_child_process3.spawnSync(process.execPath, [runtimeFile("sync-resolver")], {
1450
+ input: JSON.stringify(request),
1451
+ encoding: "utf8",
1452
+ env: resolved.env,
1453
+ timeout: synchronousResolutionTimeout(resolved),
1454
+ maxBuffer: 4 * 1024 * 1024,
1455
+ windowsHide: true
1456
+ });
1457
+ if (result.status !== 0 || result.error) {
1458
+ const detail = result.stderr.trim() || result.error?.message || `exit ${result.status}`;
1459
+ throw new Error(`Could not resolve ${target} synchronously: ${detail}`);
1460
+ }
1461
+ try {
1462
+ return JSON.parse(result.stdout);
1463
+ } catch (error) {
1464
+ const detail = error instanceof Error ? error.message : String(error);
1465
+ throw new Error(`Could not parse synchronous ${target} resolution: ${detail}`);
1466
+ }
1467
+ });
1468
+ }
1469
+ function synchronousResolutionTimeout(options) {
1470
+ return options.coordinationTimeoutMs + options.downloadTimeoutMs + 2 * options.installTimeoutMs + 4 * options.probeTimeoutMs + 60000;
1471
+ }
1472
+ function synchronousResolutionIsPresent(resolution) {
1473
+ if ("packageManager" in resolution) {
1474
+ return import_node_fs6.default.existsSync(resolution.python.executable) && import_node_fs6.default.existsSync(resolution.packageManager.executable);
1475
+ }
1476
+ if ("uv" in resolution) {
1477
+ return import_node_fs6.default.existsSync(resolution.uv.executable) && import_node_fs6.default.existsSync(resolution.python.executable);
1478
+ }
1479
+ return import_node_fs6.default.existsSync(resolution.executable);
1480
+ }
1481
+
1482
+ // src/client.ts
1483
+ function createPyfig(options = {}) {
1484
+ const scopedOptions = {
1485
+ ...options,
1486
+ env: options.env ? { ...options.env } : undefined
1487
+ };
1488
+ return {
1489
+ prepare: () => ensureEnvironment(scopedOptions),
1490
+ pythonInfo: () => ensureVirtualEnvironment(scopedOptions),
1491
+ uvInfo: () => ensureUv(scopedOptions),
1492
+ python: (args = [], spawnOptions = {}) => pythonSpawn(args, spawnOptions, scopedOptions),
1493
+ pythonSync: (args = [], spawnOptions = {}) => pythonSpawnSync(args, spawnOptions, scopedOptions),
1494
+ pip: (args = [], spawnOptions = {}) => pipSpawn(args, spawnOptions, scopedOptions),
1495
+ pipSync: (args = [], spawnOptions = {}) => pipSpawnSync(args, spawnOptions, scopedOptions),
1496
+ uv: (args = [], spawnOptions = {}) => uvSpawn(args, spawnOptions, scopedOptions),
1497
+ uvSync: (args = [], spawnOptions = {}) => uvSpawnSync(args, spawnOptions, scopedOptions),
1498
+ clearCache: clearPyfigCaches
1499
+ };
1500
+ }
1501
+
1502
+ //# debugId=940E4B4A4836D48C64756E2164756E21