pyfig 0.6.3 → 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.
- package/README.md +46 -33
- package/SECURITY.md +1 -1
- package/dist/cjs/index.js +396 -256
- package/dist/cjs/index.js.map +10 -9
- package/dist/cjs/sync-resolver.js +356 -231
- package/dist/cjs/sync-resolver.js.map +9 -8
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +6 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +31 -21
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +9 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/package-target.d.ts +3 -3
- package/dist/package-target.d.ts.map +1 -1
- package/dist/package-target.js +6 -6
- package/dist/package-target.js.map +1 -1
- package/dist/provision.d.ts +6 -12
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +141 -153
- package/dist/provision.js.map +1 -1
- package/dist/python-venv.d.ts +12 -0
- package/dist/python-venv.d.ts.map +1 -0
- package/dist/{virtual-environment.js → python-venv.js} +12 -9
- package/dist/python-venv.js.map +1 -0
- package/dist/runners.js +12 -12
- package/dist/runners.js.map +1 -1
- package/dist/sync-resolver.js +2 -2
- package/dist/sync-resolver.js.map +1 -1
- package/dist/venv-identity.d.ts +36 -0
- package/dist/venv-identity.d.ts.map +1 -0
- package/dist/venv-identity.js +80 -0
- package/dist/venv-identity.js.map +1 -0
- package/package.json +2 -2
- package/dist/virtual-environment.d.ts +0 -11
- package/dist/virtual-environment.d.ts.map +0 -1
- package/dist/virtual-environment.js.map +0 -1
package/dist/cjs/index.js
CHANGED
|
@@ -68,8 +68,8 @@ __export(exports_src, {
|
|
|
68
68
|
DEFAULT_MANAGED_PYTHON: () => DEFAULT_MANAGED_PYTHON,
|
|
69
69
|
DEFAULT_REQUIRES_PYTHON: () => DEFAULT_REQUIRES_PYTHON,
|
|
70
70
|
DEFAULT_REQUIRES_UV: () => DEFAULT_REQUIRES_UV,
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
Python: () => Python,
|
|
72
|
+
ensurePython: () => ensurePython,
|
|
73
73
|
ensureUv: () => ensureUv,
|
|
74
74
|
resolveProjectRoot: () => resolveProjectRoot,
|
|
75
75
|
uvSpawn: () => uvSpawn,
|
|
@@ -149,7 +149,11 @@ function resolveOptions(options = {}) {
|
|
|
149
149
|
const configuredProjectRoot = options.projectRoot ?? env["PYFIG_PROJECT_ROOT"];
|
|
150
150
|
const projectRoot = configuredProjectRoot ? import_node_path2.default.resolve(configuredProjectRoot) : resolveProjectRoot(env["INIT_CWD"] ?? process.cwd());
|
|
151
151
|
const autoInstall = options.autoInstall ?? parseBoolean(env["PYFIG_AUTO_INSTALL"], true);
|
|
152
|
+
const dependencies = normalizeDependencies(options.dependencies ?? []);
|
|
153
|
+
const requirements = normalizeRequirements(options.requirements ?? [], projectRoot);
|
|
152
154
|
return {
|
|
155
|
+
dependencies,
|
|
156
|
+
requirements,
|
|
153
157
|
python: options.python ?? env["PYFIG_PYTHON"] ?? "",
|
|
154
158
|
requiresPython,
|
|
155
159
|
managedPython,
|
|
@@ -169,7 +173,10 @@ function resolveOptions(options = {}) {
|
|
|
169
173
|
env
|
|
170
174
|
};
|
|
171
175
|
}
|
|
172
|
-
function
|
|
176
|
+
function hasRuntimeRequirements(options) {
|
|
177
|
+
return options.dependencies.length > 0 || options.requirements.length > 0;
|
|
178
|
+
}
|
|
179
|
+
function uvProcessEnvironment(options) {
|
|
173
180
|
return {
|
|
174
181
|
...options.env,
|
|
175
182
|
UV_PYTHON_INSTALL_DIR: import_node_path2.default.join(options.cacheDir, "python"),
|
|
@@ -211,61 +218,82 @@ function parseBoolean(value, fallback) {
|
|
|
211
218
|
return false;
|
|
212
219
|
throw new TypeError(`Invalid boolean value: ${value}`);
|
|
213
220
|
}
|
|
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)}`);
|
|
226
|
+
}
|
|
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);
|
|
258
|
+
}
|
|
214
259
|
// src/client.ts
|
|
215
260
|
var import_loqkit2 = require("loqkit");
|
|
216
261
|
|
|
217
262
|
// src/provision.ts
|
|
218
|
-
var
|
|
263
|
+
var import_node_crypto2 = require("node:crypto");
|
|
219
264
|
var import_node_fs3 = require("node:fs");
|
|
220
|
-
var
|
|
221
|
-
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
265
|
+
var import_promises2 = require("node:fs/promises");
|
|
222
266
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
223
|
-
var
|
|
267
|
+
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
224
268
|
var import_node_stream = require("node:stream");
|
|
225
|
-
var
|
|
269
|
+
var import_promises3 = require("node:stream/promises");
|
|
226
270
|
var import_fflate = require("fflate");
|
|
227
271
|
var tar = __toESM(require("tar"), 1);
|
|
228
272
|
var import_undici = require("undici");
|
|
229
273
|
|
|
230
|
-
// src/
|
|
231
|
-
var
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const result = await import_loqkit.withLock({ resource: options.key }, options.mutate, {
|
|
235
|
-
key: options.key,
|
|
236
|
-
name: "Pyfig provisioning",
|
|
237
|
-
namespace: "pyfig",
|
|
238
|
-
timeout: options.timeoutMs,
|
|
239
|
-
predicate: async () => {
|
|
240
|
-
const value = await options.check();
|
|
241
|
-
if (value === null)
|
|
242
|
-
return true;
|
|
243
|
-
existing = { value };
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
if (result.context.state === "executed")
|
|
248
|
-
return result.value;
|
|
249
|
-
if (existing)
|
|
250
|
-
return existing.value;
|
|
251
|
-
throw new Error(`Provisioning check for ${options.key} did not produce a resource.`);
|
|
252
|
-
}
|
|
274
|
+
// src/venv-identity.ts
|
|
275
|
+
var import_node_crypto = require("node:crypto");
|
|
276
|
+
var import_promises = require("node:fs/promises");
|
|
277
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
253
278
|
|
|
254
|
-
// src/
|
|
279
|
+
// src/python-venv.ts
|
|
255
280
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
256
281
|
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
257
|
-
var
|
|
258
|
-
function
|
|
259
|
-
return import_node_path3.default.join(projectRoot,
|
|
282
|
+
var VIRTUAL_ENV_ROOT_NAME = import_node_path3.default.join("node_modules", ".pyfig", "venvs");
|
|
283
|
+
function pythonVenvRoot(projectRoot) {
|
|
284
|
+
return import_node_path3.default.join(projectRoot, VIRTUAL_ENV_ROOT_NAME);
|
|
260
285
|
}
|
|
261
|
-
function
|
|
286
|
+
function pythonVenvDirectory(projectRoot, venvDirectory) {
|
|
287
|
+
return import_node_path3.default.join(pythonVenvRoot(projectRoot), venvDirectory);
|
|
288
|
+
}
|
|
289
|
+
function externalPythonVenvDirectories(projectRoot, env) {
|
|
262
290
|
const candidates = [env["VIRTUAL_ENV"], import_node_path3.default.join(projectRoot, ".venv")].filter((value) => Boolean(value)).map((value) => import_node_path3.default.resolve(value));
|
|
263
291
|
return [...new Map(candidates.map((value) => [canonicalPath(value), value])).values()];
|
|
264
292
|
}
|
|
265
|
-
function
|
|
266
|
-
return import_node_path3.default.join(
|
|
293
|
+
function pythonVenvExecutable(pythonVenv) {
|
|
294
|
+
return import_node_path3.default.join(pythonVenv, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
|
|
267
295
|
}
|
|
268
|
-
function
|
|
296
|
+
function activatePythonVenv(env, pythonVenv, pythonExecutable) {
|
|
269
297
|
const activated = { ...env };
|
|
270
298
|
const pathEntry = Object.keys(activated).find((key) => key.toUpperCase() === "PATH");
|
|
271
299
|
const existingPath = pathEntry ? activated[pathEntry] : undefined;
|
|
@@ -276,7 +304,7 @@ function activateVirtualEnvironment(env, virtualEnvironment, pythonExecutable) {
|
|
|
276
304
|
}
|
|
277
305
|
}
|
|
278
306
|
activated["PATH"] = [import_node_path3.default.dirname(pythonExecutable), existingPath].filter((value) => Boolean(value)).join(import_node_path3.default.delimiter);
|
|
279
|
-
activated["VIRTUAL_ENV"] =
|
|
307
|
+
activated["VIRTUAL_ENV"] = pythonVenv;
|
|
280
308
|
return activated;
|
|
281
309
|
}
|
|
282
310
|
function pathsEqual(left, right) {
|
|
@@ -291,6 +319,102 @@ function canonicalPath(value) {
|
|
|
291
319
|
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
292
320
|
}
|
|
293
321
|
|
|
322
|
+
// src/venv-identity.ts
|
|
323
|
+
var VENV_SCHEMA = 1;
|
|
324
|
+
var VENV_METADATA = ".pyfig-venv.json";
|
|
325
|
+
function createVenvIdentity(python, requirementsSha256) {
|
|
326
|
+
const contract = {
|
|
327
|
+
schema: VENV_SCHEMA,
|
|
328
|
+
runtime: {
|
|
329
|
+
architecture: process.arch,
|
|
330
|
+
platform: process.platform
|
|
331
|
+
},
|
|
332
|
+
python: {
|
|
333
|
+
architecture: python.architecture,
|
|
334
|
+
baseExecutable: canonicalPath(python.executable),
|
|
335
|
+
basePrefix: canonicalPath(python.basePrefix),
|
|
336
|
+
embeddingHeader: python.embedding ? canonicalPath(python.embedding.headerPath) : null,
|
|
337
|
+
embeddingLibrary: python.embedding ? canonicalPath(python.embedding.libraryPath) : null,
|
|
338
|
+
gilDisabled: python.gilDisabled,
|
|
339
|
+
implementation: python.implementation,
|
|
340
|
+
pointerBits: python.pointerBits,
|
|
341
|
+
version: python.version
|
|
342
|
+
},
|
|
343
|
+
requirementsSha256
|
|
344
|
+
};
|
|
345
|
+
const id = sha256(JSON.stringify(contract));
|
|
346
|
+
const implementation = safePathSegment(python.implementation);
|
|
347
|
+
const version = safePathSegment(python.version);
|
|
348
|
+
return {
|
|
349
|
+
contract,
|
|
350
|
+
directoryName: `${implementation}-${version}-${id.slice(0, 20)}`,
|
|
351
|
+
id
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function requirementsDigest(content) {
|
|
355
|
+
return sha256(content);
|
|
356
|
+
}
|
|
357
|
+
function venvMetadataFile(directory) {
|
|
358
|
+
return import_node_path4.default.join(directory, VENV_METADATA);
|
|
359
|
+
}
|
|
360
|
+
async function venvMatches(directory, venv) {
|
|
361
|
+
try {
|
|
362
|
+
const parsed = JSON.parse(await import_promises.readFile(venvMetadataFile(directory), "utf8"));
|
|
363
|
+
return parsed.venvId === venv.id && JSON.stringify(parsed.contract) === JSON.stringify(venv.contract);
|
|
364
|
+
} catch {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function writeVenvMetadata(directory, venv) {
|
|
369
|
+
await import_promises.mkdir(directory, { recursive: true });
|
|
370
|
+
const file = venvMetadataFile(directory);
|
|
371
|
+
const staged = `${file}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`;
|
|
372
|
+
const metadata = {
|
|
373
|
+
contract: venv.contract,
|
|
374
|
+
venvId: venv.id
|
|
375
|
+
};
|
|
376
|
+
try {
|
|
377
|
+
await import_promises.writeFile(staged, `${JSON.stringify(metadata, null, 2)}
|
|
378
|
+
`, {
|
|
379
|
+
encoding: "utf8",
|
|
380
|
+
flag: "wx"
|
|
381
|
+
});
|
|
382
|
+
await import_promises.rename(staged, file);
|
|
383
|
+
} finally {
|
|
384
|
+
await import_promises.rm(staged, { force: true });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function safePathSegment(value) {
|
|
388
|
+
return value.toLowerCase().replace(/[^a-z0-9.-]+/gu, "-").replace(/^-+|-+$/gu, "") || "python";
|
|
389
|
+
}
|
|
390
|
+
function sha256(value) {
|
|
391
|
+
return import_node_crypto.createHash("sha256").update(value).digest("hex");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/provision-lock.ts
|
|
395
|
+
var import_loqkit = require("loqkit");
|
|
396
|
+
async function withProvisioningLock(options) {
|
|
397
|
+
let existing;
|
|
398
|
+
const result = await import_loqkit.withLock({ resource: options.key }, options.mutate, {
|
|
399
|
+
key: options.key,
|
|
400
|
+
name: "Pyfig provisioning",
|
|
401
|
+
namespace: "pyfig",
|
|
402
|
+
timeout: options.timeoutMs,
|
|
403
|
+
predicate: async () => {
|
|
404
|
+
const value = await options.check();
|
|
405
|
+
if (value === null)
|
|
406
|
+
return true;
|
|
407
|
+
existing = { value };
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
if (result.context.state === "executed")
|
|
412
|
+
return result.value;
|
|
413
|
+
if (existing)
|
|
414
|
+
return existing.value;
|
|
415
|
+
throw new Error(`Provisioning check for ${options.key} did not produce a resource.`);
|
|
416
|
+
}
|
|
417
|
+
|
|
294
418
|
// src/package-target.ts
|
|
295
419
|
var TARGET_OPTIONS = new Set([
|
|
296
420
|
"--active",
|
|
@@ -323,7 +447,7 @@ var TARGET_ENVIRONMENT_VARIABLES = new Set([
|
|
|
323
447
|
"UV_SYSTEM_PYTHON",
|
|
324
448
|
"UV_TARGET"
|
|
325
449
|
]);
|
|
326
|
-
function
|
|
450
|
+
function uvCommandNeedsPython(args) {
|
|
327
451
|
return args[0] === "pip" || args[0] === "run";
|
|
328
452
|
}
|
|
329
453
|
function targetUvArguments(args, python) {
|
|
@@ -339,16 +463,16 @@ function targetUvArguments(args, python) {
|
|
|
339
463
|
}
|
|
340
464
|
return [...args];
|
|
341
465
|
}
|
|
342
|
-
function
|
|
343
|
-
const sanitized =
|
|
466
|
+
function uvCommandProcessEnvironment(env, python) {
|
|
467
|
+
const sanitized = sanitizeUvProcessEnvironment(env);
|
|
344
468
|
if (!python?.virtualEnvironment)
|
|
345
469
|
return sanitized;
|
|
346
470
|
return {
|
|
347
|
-
...
|
|
471
|
+
...activatePythonVenv(sanitized, python.virtualEnvironment, python.executable),
|
|
348
472
|
UV_PYTHON: python.executable
|
|
349
473
|
};
|
|
350
474
|
}
|
|
351
|
-
function
|
|
475
|
+
function sanitizeUvProcessEnvironment(env) {
|
|
352
476
|
const sanitized = { ...env };
|
|
353
477
|
for (const key of Object.keys(sanitized)) {
|
|
354
478
|
if (TARGET_ENVIRONMENT_VARIABLES.has(key.toUpperCase()))
|
|
@@ -372,12 +496,12 @@ function assertNoTargetOverride(args) {
|
|
|
372
496
|
|
|
373
497
|
// src/process.ts
|
|
374
498
|
var import_node_child_process = require("node:child_process");
|
|
375
|
-
var
|
|
499
|
+
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
376
500
|
var import_which = __toESM(require("which"), 1);
|
|
377
501
|
async function resolveExecutable(value, env) {
|
|
378
502
|
if (!value)
|
|
379
503
|
return null;
|
|
380
|
-
if (
|
|
504
|
+
if (import_node_path5.default.isAbsolute(value)) {
|
|
381
505
|
return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
|
|
382
506
|
}
|
|
383
507
|
if (!/^[A-Za-z0-9_.-]+$/u.test(value))
|
|
@@ -394,7 +518,7 @@ function run(executable, args, env, timeoutMs) {
|
|
|
394
518
|
});
|
|
395
519
|
if (result.status !== 0 || result.error) {
|
|
396
520
|
const detail = result.stderr?.trim() || result.error?.message || `exit ${result.status}`;
|
|
397
|
-
throw new Error(`${
|
|
521
|
+
throw new Error(`${import_node_path5.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`);
|
|
398
522
|
}
|
|
399
523
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
400
524
|
}
|
|
@@ -423,11 +547,11 @@ async function runAsync(executable, args, env, timeoutMs, signal, cwd) {
|
|
|
423
547
|
const abort = () => {
|
|
424
548
|
child.kill();
|
|
425
549
|
const reason = signal?.reason;
|
|
426
|
-
finish(reason instanceof Error ? reason : new Error(`${
|
|
550
|
+
finish(reason instanceof Error ? reason : new Error(`${import_node_path5.default.basename(executable)} was aborted.`));
|
|
427
551
|
};
|
|
428
552
|
const timer = setTimeout(() => {
|
|
429
553
|
child.kill();
|
|
430
|
-
finish(new Error(`${
|
|
554
|
+
finish(new Error(`${import_node_path5.default.basename(executable)} timed out after ${timeoutMs}ms.`));
|
|
431
555
|
}, timeoutMs);
|
|
432
556
|
child.stdout.setEncoding("utf8");
|
|
433
557
|
child.stderr.setEncoding("utf8");
|
|
@@ -435,21 +559,21 @@ async function runAsync(executable, args, env, timeoutMs, signal, cwd) {
|
|
|
435
559
|
stdout += chunk;
|
|
436
560
|
if (stdout.length > 4 * 1024 * 1024) {
|
|
437
561
|
child.kill();
|
|
438
|
-
finish(new Error(`${
|
|
562
|
+
finish(new Error(`${import_node_path5.default.basename(executable)} exceeded the output limit.`));
|
|
439
563
|
}
|
|
440
564
|
});
|
|
441
565
|
child.stderr.on("data", (chunk) => {
|
|
442
566
|
stderr += chunk;
|
|
443
567
|
if (stderr.length > 4 * 1024 * 1024) {
|
|
444
568
|
child.kill();
|
|
445
|
-
finish(new Error(`${
|
|
569
|
+
finish(new Error(`${import_node_path5.default.basename(executable)} exceeded the error-output limit.`));
|
|
446
570
|
}
|
|
447
571
|
});
|
|
448
572
|
child.once("error", (error) => finish(error));
|
|
449
573
|
child.once("close", (code, childSignal) => {
|
|
450
574
|
if (code !== 0) {
|
|
451
575
|
const detail = stderr.trim() || (childSignal ? `signal ${childSignal}` : `exit ${code}`);
|
|
452
|
-
finish(new Error(`${
|
|
576
|
+
finish(new Error(`${import_node_path5.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
|
|
453
577
|
return;
|
|
454
578
|
}
|
|
455
579
|
finish(undefined, { stdout, stderr });
|
|
@@ -603,59 +727,71 @@ function pythonArchitectureMatchesRuntime(python) {
|
|
|
603
727
|
async function ensureUv(options = {}) {
|
|
604
728
|
return ensureUvResolved(resolveOptions(options));
|
|
605
729
|
}
|
|
606
|
-
async function
|
|
730
|
+
async function ensurePython(options = {}) {
|
|
607
731
|
const resolved = resolveOptions(options);
|
|
608
|
-
|
|
609
|
-
const selections = normalizeDependencyOptions(dependencyOptions);
|
|
732
|
+
await validateRequirementsFiles(resolved.requirements);
|
|
610
733
|
const uv = await ensureUvResolved(resolved);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
734
|
+
if (!hasRuntimeRequirements(resolved)) {
|
|
735
|
+
const external = await findReusableExternalPythonVenv(resolved);
|
|
736
|
+
if (external) {
|
|
737
|
+
return { managed: false, python: external, uv, venvId: null };
|
|
738
|
+
}
|
|
614
739
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
740
|
+
const basePython = await ensureBasePythonResolved(uv, resolved);
|
|
741
|
+
const resolution = await resolveRuntimeDependencies(uv, basePython, resolved);
|
|
742
|
+
const venv = createVenvIdentity(basePython, resolution.sha256);
|
|
743
|
+
const python = await ensureManagedPythonVenv(uv, basePython, venv, resolution, resolved);
|
|
744
|
+
return { managed: true, python, uv, venvId: venv.id };
|
|
745
|
+
}
|
|
746
|
+
async function ensureManagedPythonVenv(uv, basePython, venv, resolution, options) {
|
|
747
|
+
const directory = pythonVenvDirectory(options.projectRoot, venv.directoryName);
|
|
748
|
+
const check = async () => {
|
|
749
|
+
if (!await venvMatches(directory, venv))
|
|
750
|
+
return null;
|
|
751
|
+
const candidate = await inspectPythonVenv(directory, options);
|
|
752
|
+
return candidate && pythonSatisfiesPolicy(candidate, options) ? candidate : null;
|
|
619
753
|
};
|
|
620
|
-
|
|
621
|
-
async function ensureVirtualEnvironmentResolved(options, uv) {
|
|
622
|
-
const existing = await findReusableEnvironment(options);
|
|
754
|
+
const existing = await check();
|
|
623
755
|
if (existing)
|
|
624
756
|
return existing;
|
|
625
|
-
const directory = virtualEnvironmentDirectory(options.projectRoot);
|
|
626
|
-
const check = async () => {
|
|
627
|
-
const candidate = await inspectVirtualEnvironment(directory, options);
|
|
628
|
-
return candidate && pythonSatisfiesPolicy(candidate, options) && await virtualEnvironmentMatchesConfiguredPython(candidate, options) ? candidate : null;
|
|
629
|
-
};
|
|
630
757
|
if (!options.createVirtualEnvironment) {
|
|
631
758
|
throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
|
|
632
759
|
}
|
|
633
|
-
const basePython = await ensureBasePythonResolved(uv, options);
|
|
634
760
|
return withProvisioningLock({
|
|
635
761
|
key: `venv:${canonicalPath(directory)}`,
|
|
636
762
|
timeoutMs: options.coordinationTimeoutMs,
|
|
637
763
|
check,
|
|
638
764
|
mutate: async () => {
|
|
639
|
-
await
|
|
640
|
-
await
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
765
|
+
await import_promises2.rm(directory, { recursive: true, force: true });
|
|
766
|
+
await import_promises2.mkdir(import_node_path6.default.dirname(directory), { recursive: true });
|
|
767
|
+
try {
|
|
768
|
+
await runAsync(uv.executable, [
|
|
769
|
+
"venv",
|
|
770
|
+
"--no-project",
|
|
771
|
+
"--no-config",
|
|
772
|
+
"--no-python-downloads",
|
|
773
|
+
"--python",
|
|
774
|
+
basePython.executable,
|
|
775
|
+
directory
|
|
776
|
+
], isolatedUvProcessEnvironment(options), options.installTimeoutMs);
|
|
777
|
+
const created = await inspectPythonVenv(directory, options);
|
|
778
|
+
if (!created || !pythonSatisfiesPolicy(created, options)) {
|
|
779
|
+
throw new Error(`uv created ${directory}, but its Python does not satisfy the requested capabilities.`);
|
|
780
|
+
}
|
|
781
|
+
await installResolvedDependencies(uv, created, resolution, options);
|
|
782
|
+
await writeVenvMetadata(directory, venv);
|
|
783
|
+
const installed = await check();
|
|
784
|
+
if (!installed)
|
|
785
|
+
throw new Error(`Pyfig could not validate environment ${directory}.`);
|
|
786
|
+
return installed;
|
|
787
|
+
} catch (error) {
|
|
788
|
+
await import_promises2.rm(directory, { recursive: true, force: true });
|
|
789
|
+
throw error;
|
|
653
790
|
}
|
|
654
|
-
return installed;
|
|
655
791
|
}
|
|
656
792
|
});
|
|
657
793
|
}
|
|
658
|
-
async function
|
|
794
|
+
async function findReusableExternalPythonVenv(options) {
|
|
659
795
|
if (options.python) {
|
|
660
796
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
661
797
|
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
@@ -664,14 +800,10 @@ async function findReusableEnvironment(options) {
|
|
|
664
800
|
if (configured.virtualEnvironment)
|
|
665
801
|
return configured;
|
|
666
802
|
}
|
|
667
|
-
for (const directory of
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
]) {
|
|
671
|
-
const info = await inspectVirtualEnvironment(directory, options);
|
|
672
|
-
if (info && pythonSatisfiesPolicy(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
|
|
803
|
+
for (const directory of externalPythonVenvDirectories(options.projectRoot, options.env)) {
|
|
804
|
+
const info = await inspectPythonVenv(directory, options);
|
|
805
|
+
if (info && pythonSatisfiesPolicy(info, options))
|
|
673
806
|
return info;
|
|
674
|
-
}
|
|
675
807
|
}
|
|
676
808
|
return null;
|
|
677
809
|
}
|
|
@@ -688,6 +820,9 @@ async function ensureBasePythonResolved(uv, options) {
|
|
|
688
820
|
return base;
|
|
689
821
|
throw incompatibleConfiguredPython(options);
|
|
690
822
|
}
|
|
823
|
+
const externalBase = await findExternalBasePython(options);
|
|
824
|
+
if (externalBase)
|
|
825
|
+
return externalBase;
|
|
691
826
|
const system = await findPythonWithUv(uv, options, "system");
|
|
692
827
|
if (system)
|
|
693
828
|
return system;
|
|
@@ -698,11 +833,11 @@ async function ensureBasePythonResolved(uv, options) {
|
|
|
698
833
|
throw new Error(missingPythonMessage(options, "managed Python installation is disabled"));
|
|
699
834
|
}
|
|
700
835
|
return withProvisioningLock({
|
|
701
|
-
key: `python:${canonicalPath(
|
|
836
|
+
key: `python:${canonicalPath(import_node_path6.default.join(options.cacheDir, "python"))}`,
|
|
702
837
|
timeoutMs: options.coordinationTimeoutMs,
|
|
703
838
|
check: () => findPythonWithUv(uv, options, "managed"),
|
|
704
839
|
mutate: async () => {
|
|
705
|
-
await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython],
|
|
840
|
+
await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython], isolatedUvProcessEnvironment(options), options.installTimeoutMs);
|
|
706
841
|
const installed = await findPythonWithUv(uv, options, "managed");
|
|
707
842
|
if (!installed) {
|
|
708
843
|
throw new Error(missingPythonMessage(options, `uv installed ${options.managedPython}, but it remains incompatible`));
|
|
@@ -711,6 +846,17 @@ async function ensureBasePythonResolved(uv, options) {
|
|
|
711
846
|
}
|
|
712
847
|
});
|
|
713
848
|
}
|
|
849
|
+
async function findExternalBasePython(options) {
|
|
850
|
+
for (const directory of externalPythonVenvDirectories(options.projectRoot, options.env)) {
|
|
851
|
+
const environment = await inspectPythonVenv(directory, options);
|
|
852
|
+
if (!environment || !pythonSatisfiesPolicy(environment, options))
|
|
853
|
+
continue;
|
|
854
|
+
const base = await probePython({ executable: environment.baseExecutable, args: [] }, options);
|
|
855
|
+
if (base && !base.virtualEnvironment && pythonSatisfiesPolicy(base, options))
|
|
856
|
+
return base;
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
714
860
|
async function findPythonWithUv(uv, options, source) {
|
|
715
861
|
for (const request of ["", options.managedPython]) {
|
|
716
862
|
const args = [
|
|
@@ -724,7 +870,7 @@ async function findPythonWithUv(uv, options, source) {
|
|
|
724
870
|
if (request)
|
|
725
871
|
args.push(request);
|
|
726
872
|
try {
|
|
727
|
-
const { stdout } = await runAsync(uv.executable, args,
|
|
873
|
+
const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceProcessEnvironment(options), options.probeTimeoutMs);
|
|
728
874
|
const info = await probePython({ executable: stdout.trim(), args: [] }, options);
|
|
729
875
|
if (info && pythonSatisfiesPolicy(info, options))
|
|
730
876
|
return info;
|
|
@@ -740,7 +886,7 @@ async function findPythonWithUv(uv, options, source) {
|
|
|
740
886
|
"--output-format",
|
|
741
887
|
"json",
|
|
742
888
|
source === "system" ? "--no-managed-python" : "--managed-python"
|
|
743
|
-
],
|
|
889
|
+
], explicitPythonSourceProcessEnvironment(options), options.probeTimeoutMs);
|
|
744
890
|
const candidates = installedPythonPaths(stdout);
|
|
745
891
|
for (const executable of candidates) {
|
|
746
892
|
const info = await probePython({ executable, args: [] }, options);
|
|
@@ -763,59 +909,79 @@ function installedPythonPaths(output) {
|
|
|
763
909
|
});
|
|
764
910
|
return [...new Map(paths.map((candidate) => [canonicalPath(candidate), candidate])).values()];
|
|
765
911
|
}
|
|
766
|
-
async function
|
|
767
|
-
const executable =
|
|
912
|
+
async function inspectPythonVenv(directory, options) {
|
|
913
|
+
const executable = pythonVenvExecutable(directory);
|
|
768
914
|
const info = await probePython({ executable, args: [] }, options);
|
|
769
915
|
return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
|
|
770
916
|
}
|
|
771
|
-
async function
|
|
772
|
-
if (!
|
|
773
|
-
|
|
917
|
+
async function resolveRuntimeDependencies(uv, python, options) {
|
|
918
|
+
if (!hasRuntimeRequirements(options)) {
|
|
919
|
+
return { content: "", sha256: requirementsDigest("") };
|
|
774
920
|
}
|
|
775
|
-
await
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const additions = import_node_path5.default.join(temporary, "runtime-dependencies.txt");
|
|
783
|
-
const resolved = import_node_path5.default.join(temporary, "resolved-dependencies.txt");
|
|
784
|
-
const pyproject = import_node_path5.default.join(options.projectRoot, "pyproject.toml");
|
|
785
|
-
const hasPyproject = import_node_fs4.default.existsSync(pyproject);
|
|
786
|
-
if (!hasPyproject && (dependencyOptions.extras.length > 0 || dependencyOptions.groups.length > 0 || dependencyOptions.allExtras)) {
|
|
787
|
-
throw new Error("Runtime extras and groups require an existing pyproject.toml.");
|
|
788
|
-
}
|
|
789
|
-
const sources = hasPyproject ? [pyproject] : [];
|
|
790
|
-
if (dependencies.length > 0) {
|
|
791
|
-
await import_promises.writeFile(additions, `${dependencies.join(`
|
|
921
|
+
const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-resolution-"));
|
|
922
|
+
try {
|
|
923
|
+
const additions = import_node_path6.default.join(temporary, "runtime-dependencies.txt");
|
|
924
|
+
const output = import_node_path6.default.join(temporary, "resolved-dependencies.txt");
|
|
925
|
+
const sources = [...options.requirements];
|
|
926
|
+
if (options.dependencies.length > 0) {
|
|
927
|
+
await import_promises2.writeFile(additions, `${options.dependencies.join(`
|
|
792
928
|
`)}
|
|
793
929
|
`, "utf8");
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
const selectionArgs = [
|
|
797
|
-
...dependencyOptions.extras.flatMap((extra) => ["--extra", extra]),
|
|
798
|
-
...dependencyOptions.groups.flatMap((group) => ["--group", group]),
|
|
799
|
-
...dependencyOptions.allExtras ? ["--all-extras"] : []
|
|
800
|
-
];
|
|
801
|
-
await runAsync(uv.executable, [
|
|
802
|
-
"pip",
|
|
803
|
-
"compile",
|
|
804
|
-
"--no-config",
|
|
805
|
-
"--python",
|
|
806
|
-
python.executable,
|
|
807
|
-
...selectionArgs,
|
|
808
|
-
...sources,
|
|
809
|
-
"--output-file",
|
|
810
|
-
resolved
|
|
811
|
-
], targetUvEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
|
|
812
|
-
await runAsync(uv.executable, ["pip", "install", "--no-config", "--python", python.executable, "-r", resolved], targetUvEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
|
|
813
|
-
} finally {
|
|
814
|
-
await import_promises.rm(temporary, { recursive: true, force: true });
|
|
815
|
-
}
|
|
816
|
-
return true;
|
|
930
|
+
sources.push(additions);
|
|
817
931
|
}
|
|
818
|
-
|
|
932
|
+
await runAsync(uv.executable, [
|
|
933
|
+
"pip",
|
|
934
|
+
"compile",
|
|
935
|
+
"--no-config",
|
|
936
|
+
"--no-header",
|
|
937
|
+
"--no-annotate",
|
|
938
|
+
"--generate-hashes",
|
|
939
|
+
"--python",
|
|
940
|
+
python.executable,
|
|
941
|
+
...sources,
|
|
942
|
+
"--output-file",
|
|
943
|
+
output
|
|
944
|
+
], isolatedUvProcessEnvironment(options), options.installTimeoutMs, undefined, options.projectRoot);
|
|
945
|
+
const content = normalizeResolution(await import_promises2.readFile(output, "utf8"));
|
|
946
|
+
return { content, sha256: requirementsDigest(content) };
|
|
947
|
+
} finally {
|
|
948
|
+
await import_promises2.rm(temporary, { recursive: true, force: true });
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
async function validateRequirementsFiles(requirements) {
|
|
952
|
+
for (const requirement of requirements) {
|
|
953
|
+
let information;
|
|
954
|
+
try {
|
|
955
|
+
information = await import_promises2.stat(requirement);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
throw new Error(`Requirements file is unavailable: ${requirement}`, { cause: error });
|
|
958
|
+
}
|
|
959
|
+
if (!information.isFile()) {
|
|
960
|
+
throw new Error(`Requirements path is not a file: ${requirement}`);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async function installResolvedDependencies(uv, python, resolution, options) {
|
|
965
|
+
if (!resolution.content)
|
|
966
|
+
return;
|
|
967
|
+
if (!python.virtualEnvironment) {
|
|
968
|
+
throw new Error("Runtime dependency installation requires a virtual environment.");
|
|
969
|
+
}
|
|
970
|
+
const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-install-"));
|
|
971
|
+
try {
|
|
972
|
+
const requirements = import_node_path6.default.join(temporary, "resolved-dependencies.txt");
|
|
973
|
+
await import_promises2.writeFile(requirements, resolution.content, { encoding: "utf8", flag: "wx" });
|
|
974
|
+
await runAsync(uv.executable, ["pip", "install", "--no-config", "--python", python.executable, "-r", requirements], targetUvProcessEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
|
|
975
|
+
await runAsync(uv.executable, ["pip", "check", "--no-config", "--python", python.executable], targetUvProcessEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
|
|
976
|
+
} finally {
|
|
977
|
+
await import_promises2.rm(temporary, { recursive: true, force: true });
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function normalizeResolution(content) {
|
|
981
|
+
const normalized = content.replace(/\r\n/gu, `
|
|
982
|
+
`).trimEnd();
|
|
983
|
+
return normalized ? `${normalized}
|
|
984
|
+
` : "";
|
|
819
985
|
}
|
|
820
986
|
async function ensureUvResolved(options) {
|
|
821
987
|
const existing = await findAvailableUv(options);
|
|
@@ -824,7 +990,7 @@ async function ensureUvResolved(options) {
|
|
|
824
990
|
if (!options.installUv) {
|
|
825
991
|
throw new Error(`Compatible uv is unavailable (requires-uv ${options.requiresUv}), and uv installation is disabled.`);
|
|
826
992
|
}
|
|
827
|
-
const policyKey =
|
|
993
|
+
const policyKey = import_node_crypto2.createHash("sha256").update(options.requiresUv).digest("hex");
|
|
828
994
|
return withProvisioningLock({
|
|
829
995
|
key: `uv-policy:${canonicalPath(uvInstallRoot(options))}:${policyKey}`,
|
|
830
996
|
timeoutMs: options.coordinationTimeoutMs,
|
|
@@ -856,7 +1022,7 @@ async function findAvailableUv(options) {
|
|
|
856
1022
|
return findManagedUv(options);
|
|
857
1023
|
}
|
|
858
1024
|
async function findManagedUv(options) {
|
|
859
|
-
const entries = await
|
|
1025
|
+
const entries = await import_promises2.readdir(uvInstallRoot(options), { withFileTypes: true }).catch(() => []);
|
|
860
1026
|
let versions = entries.filter((entry) => entry.isDirectory() && normalizePep440Version(entry.name) === entry.name).map((entry) => entry.name);
|
|
861
1027
|
while (versions.length > 0) {
|
|
862
1028
|
const selected = maxPep440Version(versions, options.requiresUv);
|
|
@@ -869,45 +1035,16 @@ async function findManagedUv(options) {
|
|
|
869
1035
|
}
|
|
870
1036
|
return null;
|
|
871
1037
|
}
|
|
872
|
-
function
|
|
873
|
-
|
|
874
|
-
for (const dependency of normalized) {
|
|
875
|
-
if (!dependency || /[\0\r\n]/u.test(dependency)) {
|
|
876
|
-
throw new TypeError(`Invalid runtime dependency: ${JSON.stringify(dependency)}`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
return [...new Set(normalized)];
|
|
880
|
-
}
|
|
881
|
-
function normalizeDependencyOptions(options) {
|
|
882
|
-
const extras = normalizeNames("extra", options.extras ?? []);
|
|
883
|
-
const groups = normalizeNames("dependency group", options.groups ?? []);
|
|
884
|
-
if (options.allExtras && extras.length > 0) {
|
|
885
|
-
throw new TypeError("allExtras cannot be combined with explicit extras.");
|
|
886
|
-
}
|
|
887
|
-
return { extras, groups, allExtras: options.allExtras ?? false };
|
|
888
|
-
}
|
|
889
|
-
function normalizeNames(kind, names) {
|
|
890
|
-
const normalized = names.map((name) => name.trim());
|
|
891
|
-
for (const name of normalized) {
|
|
892
|
-
if (!name || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(name)) {
|
|
893
|
-
throw new TypeError(`Invalid ${kind}: ${JSON.stringify(name)}`);
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
return [...new Set(normalized)];
|
|
897
|
-
}
|
|
898
|
-
function isManagedEnvironment(python, options) {
|
|
899
|
-
return Boolean(python.virtualEnvironment) && pathsEqual(python.virtualEnvironment, virtualEnvironmentDirectory(options.projectRoot));
|
|
900
|
-
}
|
|
901
|
-
function isolatedUvEnvironment(options) {
|
|
902
|
-
return sanitizeUvEnvironment(uvEnvironment(options));
|
|
1038
|
+
function isolatedUvProcessEnvironment(options) {
|
|
1039
|
+
return sanitizeUvProcessEnvironment(uvProcessEnvironment(options));
|
|
903
1040
|
}
|
|
904
|
-
function
|
|
905
|
-
const env =
|
|
1041
|
+
function explicitPythonSourceProcessEnvironment(options) {
|
|
1042
|
+
const env = isolatedUvProcessEnvironment(options);
|
|
906
1043
|
delete env["UV_PYTHON_PREFERENCE"];
|
|
907
1044
|
return env;
|
|
908
1045
|
}
|
|
909
|
-
function
|
|
910
|
-
return
|
|
1046
|
+
function targetUvProcessEnvironment(options, python) {
|
|
1047
|
+
return uvCommandProcessEnvironment(uvProcessEnvironment(options), python);
|
|
911
1048
|
}
|
|
912
1049
|
function incompatibleConfiguredPython(options) {
|
|
913
1050
|
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
|
|
@@ -916,18 +1053,6 @@ function missingPythonMessage(options, reason) {
|
|
|
916
1053
|
const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
|
|
917
1054
|
return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
|
|
918
1055
|
}
|
|
919
|
-
async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, options) {
|
|
920
|
-
if (!options.python)
|
|
921
|
-
return true;
|
|
922
|
-
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
923
|
-
if (!configured || !pythonSatisfiesPolicy(configured, options))
|
|
924
|
-
return false;
|
|
925
|
-
if (configured.virtualEnvironment && pathsEqual(configured.executable, virtualEnvironment.executable)) {
|
|
926
|
-
return true;
|
|
927
|
-
}
|
|
928
|
-
const configuredBase = configured.virtualEnvironment ? configured.baseExecutable : configured.executable;
|
|
929
|
-
return pathsEqual(configuredBase, virtualEnvironment.baseExecutable);
|
|
930
|
-
}
|
|
931
1056
|
var UV_RELEASE_PAGE_SIZE = 100;
|
|
932
1057
|
var UV_RELEASE_MAX_PAGES = 20;
|
|
933
1058
|
var UV_RELEASE_METADATA_TTL_MS = 24 * 60 * 60000;
|
|
@@ -1023,7 +1148,7 @@ async function readFreshUvReleaseCatalog(file) {
|
|
|
1023
1148
|
}
|
|
1024
1149
|
async function readUvReleaseCatalog(file) {
|
|
1025
1150
|
try {
|
|
1026
|
-
return parseUvReleaseCatalog(JSON.parse(await
|
|
1151
|
+
return parseUvReleaseCatalog(JSON.parse(await import_promises2.readFile(file, "utf8")));
|
|
1027
1152
|
} catch {
|
|
1028
1153
|
return null;
|
|
1029
1154
|
}
|
|
@@ -1052,20 +1177,20 @@ function parseUvReleaseCatalog(value) {
|
|
|
1052
1177
|
return { fetchedAt: catalog.fetchedAt, releases };
|
|
1053
1178
|
}
|
|
1054
1179
|
async function writeUvReleaseCatalog(file, catalog) {
|
|
1055
|
-
await
|
|
1056
|
-
const staged =
|
|
1180
|
+
await import_promises2.mkdir(import_node_path6.default.dirname(file), { recursive: true });
|
|
1181
|
+
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`);
|
|
1057
1182
|
try {
|
|
1058
|
-
await
|
|
1183
|
+
await import_promises2.writeFile(staged, `${JSON.stringify(catalog)}
|
|
1059
1184
|
`, { encoding: "utf8", flag: "wx" });
|
|
1060
1185
|
if (process.platform === "win32")
|
|
1061
|
-
await
|
|
1062
|
-
await
|
|
1186
|
+
await import_promises2.rm(file, { force: true });
|
|
1187
|
+
await import_promises2.rename(staged, file);
|
|
1063
1188
|
} finally {
|
|
1064
|
-
await
|
|
1189
|
+
await import_promises2.rm(staged, { force: true });
|
|
1065
1190
|
}
|
|
1066
1191
|
}
|
|
1067
1192
|
function uvReleaseMetadataFile(options) {
|
|
1068
|
-
return
|
|
1193
|
+
return import_node_path6.default.join(options.cacheDir, "metadata", "uv-releases.json");
|
|
1069
1194
|
}
|
|
1070
1195
|
function releaseAssetNames(value) {
|
|
1071
1196
|
if (!Array.isArray(value))
|
|
@@ -1078,36 +1203,36 @@ function releaseAssetNames(value) {
|
|
|
1078
1203
|
});
|
|
1079
1204
|
}
|
|
1080
1205
|
function uvInstallRoot(options) {
|
|
1081
|
-
return
|
|
1206
|
+
return import_node_path6.default.join(options.cacheDir, "tools", "uv");
|
|
1082
1207
|
}
|
|
1083
1208
|
function uvInstallDirectory(options, version) {
|
|
1084
|
-
return
|
|
1209
|
+
return import_node_path6.default.join(uvInstallRoot(options), version);
|
|
1085
1210
|
}
|
|
1086
1211
|
function uvExecutable(options, version) {
|
|
1087
|
-
return
|
|
1212
|
+
return import_node_path6.default.join(uvInstallDirectory(options, version), process.platform === "win32" ? "uv.exe" : "uv");
|
|
1088
1213
|
}
|
|
1089
1214
|
async function installUv(installDir, release, options) {
|
|
1090
1215
|
const asset = uvAsset();
|
|
1091
1216
|
const baseUrl = `https://github.com/astral-sh/uv/releases/download/${release.tag}`;
|
|
1092
|
-
const temporary = await
|
|
1217
|
+
const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
|
|
1093
1218
|
const dispatcher = proxyDispatcher(options.env);
|
|
1094
1219
|
try {
|
|
1095
|
-
const archive =
|
|
1220
|
+
const archive = import_node_path6.default.join(temporary, asset);
|
|
1096
1221
|
await download(`${baseUrl}/${asset}`, archive, options.downloadTimeoutMs, dispatcher);
|
|
1097
1222
|
const expected = (await downloadText(`${baseUrl}/${asset}.sha256`, options.downloadTimeoutMs, dispatcher)).trim().split(/\s+/u)[0];
|
|
1098
1223
|
const actual = await sha256File(archive);
|
|
1099
1224
|
if (!expected || expected.toLowerCase() !== actual.toLowerCase()) {
|
|
1100
1225
|
throw new Error(`uv archive checksum mismatch for ${asset}`);
|
|
1101
1226
|
}
|
|
1102
|
-
const extracted =
|
|
1103
|
-
await
|
|
1227
|
+
const extracted = import_node_path6.default.join(temporary, "extracted");
|
|
1228
|
+
await import_promises2.mkdir(extracted, { recursive: true });
|
|
1104
1229
|
const source = await extractUvExecutable(archive, asset, extracted);
|
|
1105
1230
|
if (!source)
|
|
1106
1231
|
throw new Error(`uv archive did not contain the expected executable: ${asset}`);
|
|
1107
1232
|
await publishUvAtomically(source, installDir, release.version, options);
|
|
1108
1233
|
} finally {
|
|
1109
1234
|
await dispatcher?.close();
|
|
1110
|
-
await
|
|
1235
|
+
await import_promises2.rm(temporary, { recursive: true, force: true });
|
|
1111
1236
|
}
|
|
1112
1237
|
}
|
|
1113
1238
|
async function extractUvExecutable(archive, asset, destination) {
|
|
@@ -1116,33 +1241,33 @@ async function extractUvExecutable(archive, asset, destination) {
|
|
|
1116
1241
|
await tar.x({ file: archive, cwd: destination, strict: true });
|
|
1117
1242
|
return findFile(destination, executableName);
|
|
1118
1243
|
}
|
|
1119
|
-
const entries = import_fflate.unzipSync(await
|
|
1120
|
-
filter: ({ name }) =>
|
|
1244
|
+
const entries = import_fflate.unzipSync(await import_promises2.readFile(archive), {
|
|
1245
|
+
filter: ({ name }) => import_node_path6.default.posix.basename(name.replaceAll("\\", "/")) === executableName
|
|
1121
1246
|
});
|
|
1122
|
-
const matches = Object.entries(entries).filter(([name]) =>
|
|
1247
|
+
const matches = Object.entries(entries).filter(([name]) => import_node_path6.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
|
|
1123
1248
|
if (matches.length !== 1 || !matches[0])
|
|
1124
1249
|
return null;
|
|
1125
|
-
const source =
|
|
1126
|
-
await
|
|
1250
|
+
const source = import_node_path6.default.join(destination, executableName);
|
|
1251
|
+
await import_promises2.writeFile(source, matches[0][1], { flag: "wx" });
|
|
1127
1252
|
return source;
|
|
1128
1253
|
}
|
|
1129
1254
|
async function publishUvAtomically(source, installDir, version, options) {
|
|
1130
|
-
await
|
|
1255
|
+
await import_promises2.mkdir(installDir, { recursive: true });
|
|
1131
1256
|
const executable = uvExecutable(options, version);
|
|
1132
|
-
const staged =
|
|
1257
|
+
const staged = import_node_path6.default.join(installDir, `.${import_node_path6.default.basename(executable)}.${process.pid}.${import_node_crypto2.randomUUID()}.tmp`);
|
|
1133
1258
|
try {
|
|
1134
|
-
await
|
|
1259
|
+
await import_promises2.copyFile(source, staged);
|
|
1135
1260
|
if (process.platform !== "win32")
|
|
1136
|
-
await
|
|
1261
|
+
await import_promises2.chmod(staged, 493);
|
|
1137
1262
|
const stagedUv = probeUv(staged, options);
|
|
1138
1263
|
if (!stagedUv || stagedUv.version !== version) {
|
|
1139
1264
|
throw new Error(`Downloaded uv executable is not version ${version}.`);
|
|
1140
1265
|
}
|
|
1141
1266
|
if (process.platform === "win32")
|
|
1142
|
-
await
|
|
1143
|
-
await
|
|
1267
|
+
await import_promises2.rm(executable, { force: true });
|
|
1268
|
+
await import_promises2.rename(staged, executable);
|
|
1144
1269
|
} finally {
|
|
1145
|
-
await
|
|
1270
|
+
await import_promises2.rm(staged, { force: true });
|
|
1146
1271
|
}
|
|
1147
1272
|
}
|
|
1148
1273
|
function uvAsset() {
|
|
@@ -1167,7 +1292,7 @@ async function download(url, destination, timeoutMs, dispatcher) {
|
|
|
1167
1292
|
await response.body?.cancel();
|
|
1168
1293
|
throw new Error(`Could not download ${url}: HTTP ${response.status}`);
|
|
1169
1294
|
}
|
|
1170
|
-
await
|
|
1295
|
+
await import_promises3.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs3.createWriteStream(destination, { flags: "wx" }));
|
|
1171
1296
|
}
|
|
1172
1297
|
async function downloadText(url, timeoutMs, dispatcher, headers) {
|
|
1173
1298
|
const response = await import_undici.fetch(url, {
|
|
@@ -1183,28 +1308,28 @@ async function downloadText(url, timeoutMs, dispatcher, headers) {
|
|
|
1183
1308
|
return response.text();
|
|
1184
1309
|
}
|
|
1185
1310
|
function proxyDispatcher(env) {
|
|
1186
|
-
const httpProxy =
|
|
1187
|
-
const httpsProxy =
|
|
1311
|
+
const httpProxy = processEnvironmentValue(env, "http_proxy", "HTTP_PROXY");
|
|
1312
|
+
const httpsProxy = processEnvironmentValue(env, "https_proxy", "HTTPS_PROXY");
|
|
1188
1313
|
if (!httpProxy && !httpsProxy)
|
|
1189
1314
|
return;
|
|
1190
1315
|
return new import_undici.EnvHttpProxyAgent({
|
|
1191
1316
|
httpProxy,
|
|
1192
1317
|
httpsProxy,
|
|
1193
|
-
noProxy:
|
|
1318
|
+
noProxy: processEnvironmentValue(env, "no_proxy", "NO_PROXY")
|
|
1194
1319
|
});
|
|
1195
1320
|
}
|
|
1196
|
-
function
|
|
1321
|
+
function processEnvironmentValue(env, lowercase, uppercase) {
|
|
1197
1322
|
return env[lowercase] ?? env[uppercase];
|
|
1198
1323
|
}
|
|
1199
1324
|
async function sha256File(file) {
|
|
1200
|
-
const hash =
|
|
1325
|
+
const hash = import_node_crypto2.createHash("sha256");
|
|
1201
1326
|
for await (const chunk of import_node_fs3.createReadStream(file))
|
|
1202
1327
|
hash.update(chunk);
|
|
1203
1328
|
return hash.digest("hex");
|
|
1204
1329
|
}
|
|
1205
1330
|
async function findFile(directory, name) {
|
|
1206
|
-
for (const entry of await
|
|
1207
|
-
const target =
|
|
1331
|
+
for (const entry of await import_promises2.readdir(directory, { withFileTypes: true })) {
|
|
1332
|
+
const target = import_node_path6.default.join(directory, entry.name);
|
|
1208
1333
|
if (entry.isFile() && entry.name === name)
|
|
1209
1334
|
return target;
|
|
1210
1335
|
if (entry.isDirectory()) {
|
|
@@ -1220,21 +1345,21 @@ async function findFile(directory, name) {
|
|
|
1220
1345
|
var import_node_child_process2 = require("node:child_process");
|
|
1221
1346
|
|
|
1222
1347
|
// src/runtime-files.ts
|
|
1223
|
-
var
|
|
1224
|
-
var
|
|
1348
|
+
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
1349
|
+
var import_node_path7 = __toESM(require("node:path"), 1);
|
|
1225
1350
|
function runtimeFile(name) {
|
|
1226
1351
|
const moduleDirectory = __dirname;
|
|
1227
|
-
const compiled =
|
|
1228
|
-
return
|
|
1352
|
+
const compiled = import_node_path7.default.join(moduleDirectory, `${name}.js`);
|
|
1353
|
+
return import_node_fs4.default.existsSync(compiled) ? compiled : import_node_path7.default.join(moduleDirectory, `${name}.ts`);
|
|
1229
1354
|
}
|
|
1230
1355
|
|
|
1231
1356
|
// src/runners.ts
|
|
1232
1357
|
async function uvSpawn(args = [], spawnOptions = {}, pyfigOptions = {}) {
|
|
1233
1358
|
const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
|
|
1234
1359
|
const resolved = resolveOptions(options);
|
|
1235
|
-
const
|
|
1236
|
-
const uv =
|
|
1237
|
-
const command = buildUvCommand(uv, args, resolved,
|
|
1360
|
+
const python = uvCommandNeedsPython(args) ? await ensurePython(resolved) : undefined;
|
|
1361
|
+
const uv = python?.uv ?? await ensureUv(resolved);
|
|
1362
|
+
const command = buildUvCommand(uv, args, resolved, python?.python);
|
|
1238
1363
|
return import_node_child_process2.spawn(command.executable, command.args, {
|
|
1239
1364
|
...spawnOptions,
|
|
1240
1365
|
env: command.env
|
|
@@ -1243,9 +1368,9 @@ async function uvSpawn(args = [], spawnOptions = {}, pyfigOptions = {}) {
|
|
|
1243
1368
|
function uvSpawnSync(args = [], spawnOptions = {}, pyfigOptions = {}) {
|
|
1244
1369
|
const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
|
|
1245
1370
|
const resolved = resolveOptions(options);
|
|
1246
|
-
const
|
|
1247
|
-
const uv =
|
|
1248
|
-
const command = buildUvCommand(uv, args, resolved,
|
|
1371
|
+
const python = uvCommandNeedsPython(args) ? resolveSynchronously("python", resolved) : undefined;
|
|
1372
|
+
const uv = python?.uv ?? resolveSynchronously("uv", resolved);
|
|
1373
|
+
const command = buildUvCommand(uv, args, resolved, python?.python);
|
|
1249
1374
|
return import_node_child_process2.spawnSync(command.executable, command.args, {
|
|
1250
1375
|
...spawnOptions,
|
|
1251
1376
|
env: command.env
|
|
@@ -1255,7 +1380,7 @@ function buildUvCommand(uv, args, options, python) {
|
|
|
1255
1380
|
return {
|
|
1256
1381
|
executable: uv.executable,
|
|
1257
1382
|
args: python ? targetUvArguments(args, python) : [...args],
|
|
1258
|
-
env:
|
|
1383
|
+
env: uvCommandProcessEnvironment(uvProcessEnvironment(options), python)
|
|
1259
1384
|
};
|
|
1260
1385
|
}
|
|
1261
1386
|
function optionsForInvocation(pyfigOptions, spawnEnv) {
|
|
@@ -1288,20 +1413,35 @@ function synchronousResolutionTimeout(options) {
|
|
|
1288
1413
|
}
|
|
1289
1414
|
|
|
1290
1415
|
// src/client.ts
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1416
|
+
class Python {
|
|
1417
|
+
#options;
|
|
1418
|
+
#python;
|
|
1419
|
+
#uvInfo;
|
|
1420
|
+
constructor(options = {}) {
|
|
1421
|
+
this.#options = {
|
|
1422
|
+
...options,
|
|
1423
|
+
dependencies: options.dependencies ? [...options.dependencies] : undefined,
|
|
1424
|
+
requirements: options.requirements ? [...options.requirements] : undefined,
|
|
1425
|
+
env: options.env ? { ...options.env } : undefined
|
|
1426
|
+
};
|
|
1427
|
+
this.#python = import_loqkit2.memoize(() => ensurePython(this.#options));
|
|
1428
|
+
this.#uvInfo = import_loqkit2.memoize(() => ensureUv(this.#options));
|
|
1429
|
+
}
|
|
1430
|
+
prepare() {
|
|
1431
|
+
return this.#python();
|
|
1432
|
+
}
|
|
1433
|
+
async pythonInfo() {
|
|
1434
|
+
return (await this.#python()).python;
|
|
1435
|
+
}
|
|
1436
|
+
uvInfo() {
|
|
1437
|
+
return this.#uvInfo();
|
|
1438
|
+
}
|
|
1439
|
+
uv(args = [], options = {}) {
|
|
1440
|
+
return uvSpawn(args, options, this.#options);
|
|
1441
|
+
}
|
|
1442
|
+
uvSync(args, options) {
|
|
1443
|
+
return uvSpawnSync(args, options, this.#options);
|
|
1444
|
+
}
|
|
1305
1445
|
}
|
|
1306
1446
|
|
|
1307
|
-
//# debugId=
|
|
1447
|
+
//# debugId=653756BC4A56AC4F64756E2164756E21
|