pyfig 0.6.3 → 0.7.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.
- package/README.md +77 -166
- package/SECURITY.md +16 -5
- package/dist/cjs/index.js +471 -980
- package/dist/cjs/index.js.map +6 -15
- package/dist/index.d.ts +2 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/process.d.ts +6 -12
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +35 -54
- package/dist/process.js.map +1 -1
- package/dist/pyfig.d.ts +31 -0
- package/dist/pyfig.d.ts.map +1 -0
- package/dist/pyfig.js +275 -0
- package/dist/pyfig.js.map +1 -0
- package/dist/uv.d.ts +3 -0
- package/dist/uv.d.ts.map +1 -0
- package/dist/uv.js +423 -0
- package/dist/uv.js.map +1 -0
- package/package.json +6 -12
- package/dist/cjs/sync-resolver.js +0 -1217
- package/dist/cjs/sync-resolver.js.map +0 -19
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -48
- package/dist/cli.js.map +0 -1
- package/dist/client.d.ts +0 -15
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -25
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -62
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -112
- package/dist/config.js.map +0 -1
- package/dist/package-target.d.ts +0 -8
- package/dist/package-target.d.ts.map +0 -1
- package/dist/package-target.js +0 -81
- package/dist/package-target.js.map +0 -1
- package/dist/pep440.d.ts +0 -12
- package/dist/pep440.d.ts.map +0 -1
- package/dist/pep440.js +0 -21
- package/dist/pep440.js.map +0 -1
- package/dist/probes.d.ts +0 -37
- package/dist/probes.d.ts.map +0 -1
- package/dist/probes.js +0 -145
- package/dist/probes.js.map +0 -1
- package/dist/project-root.d.ts +0 -3
- package/dist/project-root.d.ts.map +0 -1
- package/dist/project-root.js +0 -22
- package/dist/project-root.js.map +0 -1
- package/dist/provision-lock.d.ts +0 -10
- package/dist/provision-lock.d.ts.map +0 -1
- package/dist/provision-lock.js +0 -24
- package/dist/provision-lock.js.map +0 -1
- package/dist/provision.d.ts +0 -26
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -685
- package/dist/provision.js.map +0 -1
- package/dist/runners.d.ts +0 -14
- package/dist/runners.d.ts.map +0 -1
- package/dist/runners.js +0 -71
- package/dist/runners.js.map +0 -1
- package/dist/runtime-files.d.ts +0 -3
- package/dist/runtime-files.d.ts.map +0 -1
- package/dist/runtime-files.js +0 -12
- package/dist/runtime-files.js.map +0 -1
- package/dist/sync-resolver.d.ts +0 -2
- package/dist/sync-resolver.d.ts.map +0 -1
- package/dist/sync-resolver.js +0 -15
- package/dist/sync-resolver.js.map +0 -1
- package/dist/virtual-environment.d.ts +0 -11
- package/dist/virtual-environment.d.ts.map +0 -1
- package/dist/virtual-environment.js +0 -49
- package/dist/virtual-environment.js.map +0 -1
package/dist/cjs/index.js
CHANGED
|
@@ -65,893 +65,184 @@ var __export = (target, all) => {
|
|
|
65
65
|
// src/index.ts
|
|
66
66
|
var exports_src = {};
|
|
67
67
|
__export(exports_src, {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
DEFAULT_REQUIRES_UV: () => DEFAULT_REQUIRES_UV,
|
|
71
|
-
createPyfig: () => createPyfig,
|
|
72
|
-
ensureEnvironment: () => ensureEnvironment,
|
|
73
|
-
ensureUv: () => ensureUv,
|
|
74
|
-
resolveProjectRoot: () => resolveProjectRoot,
|
|
75
|
-
uvSpawn: () => uvSpawn,
|
|
76
|
-
uvSpawnSync: () => uvSpawnSync
|
|
68
|
+
PythonContext: () => PythonContext,
|
|
69
|
+
pyfig: () => pyfig
|
|
77
70
|
});
|
|
78
71
|
module.exports = __toCommonJS(exports_src);
|
|
79
72
|
|
|
80
|
-
// src/
|
|
81
|
-
var
|
|
82
|
-
var envPaths = __toESM(require("env-paths"), 1);
|
|
83
|
-
|
|
84
|
-
// src/pep440.ts
|
|
85
|
-
var import_pep440 = require("@renovatebot/pep440");
|
|
86
|
-
function isValidPep440Specifier(specifier) {
|
|
87
|
-
return import_pep440.validRange(specifier);
|
|
88
|
-
}
|
|
89
|
-
function normalizePep440Version(version) {
|
|
90
|
-
return import_pep440.valid(version);
|
|
91
|
-
}
|
|
92
|
-
function pep440VersionSatisfies(version, specifier) {
|
|
93
|
-
return import_pep440.filter([version], specifier).length === 1;
|
|
94
|
-
}
|
|
95
|
-
function maxPep440Version(versions, specifier) {
|
|
96
|
-
return import_pep440.maxSatisfying([...versions], specifier);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/project-root.ts
|
|
100
|
-
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
101
|
-
var import_node_path = __toESM(require("node:path"), 1);
|
|
102
|
-
function resolveProjectRoot(startDirectory = process.cwd()) {
|
|
103
|
-
const start = import_node_path.default.resolve(startDirectory);
|
|
104
|
-
for (const directory of ancestors(start)) {
|
|
105
|
-
if (import_node_fs.default.existsSync(import_node_path.default.join(directory, ".git")))
|
|
106
|
-
return directory;
|
|
107
|
-
}
|
|
108
|
-
return start;
|
|
109
|
-
}
|
|
110
|
-
function* ancestors(startDirectory) {
|
|
111
|
-
let directory = startDirectory;
|
|
112
|
-
while (true) {
|
|
113
|
-
yield directory;
|
|
114
|
-
const parent = import_node_path.default.dirname(directory);
|
|
115
|
-
if (parent === directory)
|
|
116
|
-
return;
|
|
117
|
-
directory = parent;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/config.ts
|
|
122
|
-
var DEFAULT_REQUIRES_PYTHON = ">=3.10,<4";
|
|
123
|
-
var DEFAULT_MANAGED_PYTHON = "3.12";
|
|
124
|
-
var DEFAULT_REQUIRES_UV = ">=0.10,<1";
|
|
125
|
-
var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
|
|
126
|
-
var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60000;
|
|
127
|
-
var DEFAULT_DOWNLOAD_TIMEOUT_MS = 5 * 60000;
|
|
128
|
-
var envPathsFunction = (() => {
|
|
129
|
-
const imported = envPaths.default;
|
|
130
|
-
const implementation = typeof imported === "function" ? imported : imported.default;
|
|
131
|
-
if (!implementation)
|
|
132
|
-
throw new TypeError("env-paths did not expose its default function.");
|
|
133
|
-
return implementation;
|
|
134
|
-
})();
|
|
135
|
-
function resolveOptions(options = {}) {
|
|
136
|
-
const env = { ...options.env ?? process.env };
|
|
137
|
-
const requiresPython = options.requiresPython ?? env["PYFIG_REQUIRES_PYTHON"] ?? DEFAULT_REQUIRES_PYTHON;
|
|
138
|
-
const requiresUv = options.requiresUv ?? env["PYFIG_REQUIRES_UV"] ?? DEFAULT_REQUIRES_UV;
|
|
139
|
-
assertPep440Specifier("requires-python", requiresPython);
|
|
140
|
-
assertPep440Specifier("requires-uv", requiresUv);
|
|
141
|
-
const managedPython = options.managedPython ?? env["PYFIG_MANAGED_PYTHON"] ?? DEFAULT_MANAGED_PYTHON;
|
|
142
|
-
assertManagedPythonRequest(managedPython, requiresPython);
|
|
143
|
-
const probeTimeoutMs = resolveDuration("probeTimeoutMs", options.probeTimeoutMs, env["PYFIG_PROBE_TIMEOUT_MS"], DEFAULT_PROBE_TIMEOUT_MS);
|
|
144
|
-
const installTimeoutMs = resolveDuration("installTimeoutMs", options.installTimeoutMs, env["PYFIG_INSTALL_TIMEOUT_MS"], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
145
|
-
const downloadTimeoutMs = resolveDuration("downloadTimeoutMs", options.downloadTimeoutMs, env["PYFIG_DOWNLOAD_TIMEOUT_MS"], DEFAULT_DOWNLOAD_TIMEOUT_MS);
|
|
146
|
-
const coordinationTimeoutMs = resolveDuration("coordinationTimeoutMs", options.coordinationTimeoutMs, env["PYFIG_COORDINATION_TIMEOUT_MS"], installTimeoutMs + downloadTimeoutMs + 60000);
|
|
147
|
-
const configuredHome = options.cacheDir ?? env["PYFIG_HOME"];
|
|
148
|
-
const cacheDir = configuredHome ? import_node_path2.default.resolve(configuredHome) : envPathsFunction("pyfig", { suffix: "" }).cache;
|
|
149
|
-
const configuredProjectRoot = options.projectRoot ?? env["PYFIG_PROJECT_ROOT"];
|
|
150
|
-
const projectRoot = configuredProjectRoot ? import_node_path2.default.resolve(configuredProjectRoot) : resolveProjectRoot(env["INIT_CWD"] ?? process.cwd());
|
|
151
|
-
const autoInstall = options.autoInstall ?? parseBoolean(env["PYFIG_AUTO_INSTALL"], true);
|
|
152
|
-
return {
|
|
153
|
-
python: options.python ?? env["PYFIG_PYTHON"] ?? "",
|
|
154
|
-
requiresPython,
|
|
155
|
-
managedPython,
|
|
156
|
-
requiresUv,
|
|
157
|
-
requireEmbedding: options.requireEmbedding ?? parseBoolean(env["PYFIG_REQUIRE_EMBEDDING"], false),
|
|
158
|
-
requireGil: options.requireGil ?? parseBoolean(env["PYFIG_REQUIRE_GIL"], false),
|
|
159
|
-
autoInstall,
|
|
160
|
-
installPython: options.installPython ?? parseBoolean(env["PYFIG_INSTALL_PYTHON"], autoInstall),
|
|
161
|
-
installUv: options.installUv ?? parseBoolean(env["PYFIG_INSTALL_UV"], autoInstall),
|
|
162
|
-
createVirtualEnvironment: options.createVirtualEnvironment ?? parseBoolean(env["PYFIG_CREATE_VIRTUAL_ENVIRONMENT"], autoInstall),
|
|
163
|
-
cacheDir,
|
|
164
|
-
projectRoot,
|
|
165
|
-
probeTimeoutMs,
|
|
166
|
-
installTimeoutMs,
|
|
167
|
-
downloadTimeoutMs,
|
|
168
|
-
coordinationTimeoutMs,
|
|
169
|
-
env
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
function uvEnvironment(options) {
|
|
173
|
-
return {
|
|
174
|
-
...options.env,
|
|
175
|
-
UV_PYTHON_INSTALL_DIR: import_node_path2.default.join(options.cacheDir, "python"),
|
|
176
|
-
UV_CACHE_DIR: import_node_path2.default.join(options.cacheDir, "uv-cache"),
|
|
177
|
-
UV_PYTHON_PREFERENCE: "system",
|
|
178
|
-
UV_NO_CONFIG: "1"
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function assertPep440Specifier(name, specifier) {
|
|
182
|
-
if (!isValidPep440Specifier(specifier)) {
|
|
183
|
-
throw new TypeError(`Invalid ${name} PEP 440 specifier: ${specifier}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function assertManagedPythonRequest(request, requiresPython) {
|
|
187
|
-
if (!request || /[\\/\0\r\n]/u.test(request) || request.includes("..")) {
|
|
188
|
-
throw new TypeError(`Invalid managed Python request: ${request}`);
|
|
189
|
-
}
|
|
190
|
-
if (/^\d+\.\d+(?:\.\d+)?$/u.test(request)) {
|
|
191
|
-
const normalized = request.split(".").length === 2 ? `${request}.0` : request;
|
|
192
|
-
if (!pep440VersionSatisfies(normalized, requiresPython)) {
|
|
193
|
-
throw new TypeError(`Managed Python ${request} cannot satisfy requires-python ${requiresPython}.`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
function resolveDuration(name, explicit, environment, fallback, allowZero = false) {
|
|
198
|
-
const value = explicit ?? (environment === undefined ? fallback : Number(environment));
|
|
199
|
-
const minimum = allowZero ? 0 : 1;
|
|
200
|
-
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
201
|
-
throw new TypeError(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer.`);
|
|
202
|
-
}
|
|
203
|
-
return value;
|
|
204
|
-
}
|
|
205
|
-
function parseBoolean(value, fallback) {
|
|
206
|
-
if (value === undefined)
|
|
207
|
-
return fallback;
|
|
208
|
-
if (/^(1|true|yes|on)$/iu.test(value))
|
|
209
|
-
return true;
|
|
210
|
-
if (/^(0|false|no|off)$/iu.test(value))
|
|
211
|
-
return false;
|
|
212
|
-
throw new TypeError(`Invalid boolean value: ${value}`);
|
|
213
|
-
}
|
|
214
|
-
// src/client.ts
|
|
215
|
-
var import_loqkit2 = require("loqkit");
|
|
216
|
-
|
|
217
|
-
// src/provision.ts
|
|
218
|
-
var import_node_crypto = require("node:crypto");
|
|
219
|
-
var import_node_fs3 = require("node:fs");
|
|
220
|
-
var import_promises = require("node:fs/promises");
|
|
221
|
-
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
222
|
-
var import_node_os = __toESM(require("node:os"), 1);
|
|
223
|
-
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
224
|
-
var import_node_stream = require("node:stream");
|
|
225
|
-
var import_promises2 = require("node:stream/promises");
|
|
226
|
-
var import_fflate = require("fflate");
|
|
227
|
-
var tar = __toESM(require("tar"), 1);
|
|
228
|
-
var import_undici = require("undici");
|
|
229
|
-
|
|
230
|
-
// src/provision-lock.ts
|
|
231
|
-
var import_loqkit = require("loqkit");
|
|
232
|
-
async function withProvisioningLock(options) {
|
|
233
|
-
let existing;
|
|
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
|
-
}
|
|
253
|
-
|
|
254
|
-
// src/virtual-environment.ts
|
|
255
|
-
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
73
|
+
// src/pyfig.ts
|
|
74
|
+
var import_node_child_process2 = require("node:child_process");
|
|
256
75
|
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
257
|
-
var
|
|
258
|
-
function virtualEnvironmentDirectory(projectRoot) {
|
|
259
|
-
return import_node_path3.default.join(projectRoot, VIRTUAL_ENV_DIRECTORY_NAME);
|
|
260
|
-
}
|
|
261
|
-
function externalVirtualEnvironmentDirectories(projectRoot, env) {
|
|
262
|
-
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
|
-
return [...new Map(candidates.map((value) => [canonicalPath(value), value])).values()];
|
|
264
|
-
}
|
|
265
|
-
function virtualEnvironmentPythonExecutable(virtualEnvironment) {
|
|
266
|
-
return import_node_path3.default.join(virtualEnvironment, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
|
|
267
|
-
}
|
|
268
|
-
function activateVirtualEnvironment(env, virtualEnvironment, pythonExecutable) {
|
|
269
|
-
const activated = { ...env };
|
|
270
|
-
const pathEntry = Object.keys(activated).find((key) => key.toUpperCase() === "PATH");
|
|
271
|
-
const existingPath = pathEntry ? activated[pathEntry] : undefined;
|
|
272
|
-
for (const key of Object.keys(activated)) {
|
|
273
|
-
const normalized = key.toUpperCase();
|
|
274
|
-
if (normalized === "PATH" || normalized === "PYTHONHOME" || normalized === "VIRTUAL_ENV") {
|
|
275
|
-
delete activated[key];
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
activated["PATH"] = [import_node_path3.default.dirname(pythonExecutable), existingPath].filter((value) => Boolean(value)).join(import_node_path3.default.delimiter);
|
|
279
|
-
activated["VIRTUAL_ENV"] = virtualEnvironment;
|
|
280
|
-
return activated;
|
|
281
|
-
}
|
|
282
|
-
function pathsEqual(left, right) {
|
|
283
|
-
return canonicalPath(left) === canonicalPath(right);
|
|
284
|
-
}
|
|
285
|
-
function canonicalPath(value) {
|
|
286
|
-
const resolved = import_node_path3.default.resolve(value);
|
|
287
|
-
let canonical = resolved;
|
|
288
|
-
try {
|
|
289
|
-
canonical = import_node_fs2.default.realpathSync.native(resolved);
|
|
290
|
-
} catch {}
|
|
291
|
-
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// src/package-target.ts
|
|
295
|
-
var TARGET_OPTIONS = new Set([
|
|
296
|
-
"--active",
|
|
297
|
-
"--config-file",
|
|
298
|
-
"--directory",
|
|
299
|
-
"--isolated",
|
|
300
|
-
"--prefix",
|
|
301
|
-
"--project",
|
|
302
|
-
"--python",
|
|
303
|
-
"--root",
|
|
304
|
-
"--system",
|
|
305
|
-
"--target",
|
|
306
|
-
"--user"
|
|
307
|
-
]);
|
|
308
|
-
var TARGET_ENVIRONMENT_VARIABLES = new Set([
|
|
309
|
-
"CONDA_PREFIX",
|
|
310
|
-
"PIP_PREFIX",
|
|
311
|
-
"PIP_PYTHON",
|
|
312
|
-
"PIP_ROOT",
|
|
313
|
-
"PIP_TARGET",
|
|
314
|
-
"PIP_USER",
|
|
315
|
-
"PYTHONHOME",
|
|
316
|
-
"PYTHONUSERBASE",
|
|
317
|
-
"UV_CONFIG_FILE",
|
|
318
|
-
"UV_PROJECT",
|
|
319
|
-
"UV_PROJECT_ENVIRONMENT",
|
|
320
|
-
"UV_PYTHON",
|
|
321
|
-
"UV_PREFIX",
|
|
322
|
-
"UV_ROOT",
|
|
323
|
-
"UV_SYSTEM_PYTHON",
|
|
324
|
-
"UV_TARGET"
|
|
325
|
-
]);
|
|
326
|
-
function uvCommandNeedsEnvironment(args) {
|
|
327
|
-
return args[0] === "pip" || args[0] === "run";
|
|
328
|
-
}
|
|
329
|
-
function targetUvArguments(args, python) {
|
|
330
|
-
if (!python.virtualEnvironment) {
|
|
331
|
-
throw new Error("Targeted uv commands require a virtual environment.");
|
|
332
|
-
}
|
|
333
|
-
assertNoTargetOverride(args);
|
|
334
|
-
if (args[0] === "run") {
|
|
335
|
-
return ["run", "--no-project", "--python", python.executable, ...args.slice(1)];
|
|
336
|
-
}
|
|
337
|
-
if (args[0] === "pip" && args[1]) {
|
|
338
|
-
return ["pip", args[1], "--python", python.executable, ...args.slice(2)];
|
|
339
|
-
}
|
|
340
|
-
return [...args];
|
|
341
|
-
}
|
|
342
|
-
function uvCommandEnvironment(env, python) {
|
|
343
|
-
const sanitized = sanitizeUvEnvironment(env);
|
|
344
|
-
if (!python?.virtualEnvironment)
|
|
345
|
-
return sanitized;
|
|
346
|
-
return {
|
|
347
|
-
...activateVirtualEnvironment(sanitized, python.virtualEnvironment, python.executable),
|
|
348
|
-
UV_PYTHON: python.executable
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
function sanitizeUvEnvironment(env) {
|
|
352
|
-
const sanitized = { ...env };
|
|
353
|
-
for (const key of Object.keys(sanitized)) {
|
|
354
|
-
if (TARGET_ENVIRONMENT_VARIABLES.has(key.toUpperCase()))
|
|
355
|
-
delete sanitized[key];
|
|
356
|
-
}
|
|
357
|
-
return sanitized;
|
|
358
|
-
}
|
|
359
|
-
function assertNoTargetOverride(args) {
|
|
360
|
-
for (const argument of args.slice(1)) {
|
|
361
|
-
if (argument === "--")
|
|
362
|
-
return;
|
|
363
|
-
const [name] = argument.split("=", 1);
|
|
364
|
-
if (name && TARGET_OPTIONS.has(name)) {
|
|
365
|
-
throw new Error(`${name} cannot override the environment selected by Pyfig.`);
|
|
366
|
-
}
|
|
367
|
-
if (argument === "-p" || argument.startsWith("-p=") || /^-p[^-]/u.test(argument)) {
|
|
368
|
-
throw new Error("-p cannot override the environment selected by Pyfig.");
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
76
|
+
var import_pep4402 = require("@renovatebot/pep440");
|
|
372
77
|
|
|
373
78
|
// src/process.ts
|
|
374
79
|
var import_node_child_process = require("node:child_process");
|
|
375
|
-
var
|
|
80
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
376
81
|
var import_which = __toESM(require("which"), 1);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return null;
|
|
385
|
-
return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
|
|
386
|
-
}
|
|
387
|
-
function run(executable, args, env, timeoutMs) {
|
|
388
|
-
const result = import_node_child_process.spawnSync(executable, args, {
|
|
389
|
-
encoding: "utf8",
|
|
390
|
-
env,
|
|
391
|
-
timeout: timeoutMs,
|
|
392
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
393
|
-
windowsHide: true
|
|
82
|
+
var OUTPUT_LIMIT = 1024 * 1024;
|
|
83
|
+
async function findExecutable(requested, cwd, env) {
|
|
84
|
+
const candidate = import_node_path.default.isAbsolute(requested) || /[\\/]/u.test(requested) ? import_node_path.default.resolve(cwd, requested) : requested;
|
|
85
|
+
const resolved = await import_which.default(candidate, {
|
|
86
|
+
nothrow: true,
|
|
87
|
+
path: environmentValue(env, "PATH"),
|
|
88
|
+
pathExt: environmentValue(env, "PATHEXT")
|
|
394
89
|
});
|
|
395
|
-
|
|
396
|
-
const detail = result.stderr?.trim() || result.error?.message || `exit ${result.status}`;
|
|
397
|
-
throw new Error(`${import_node_path4.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`);
|
|
398
|
-
}
|
|
399
|
-
return { stdout: result.stdout, stderr: result.stderr };
|
|
90
|
+
return resolved ? import_node_path.default.resolve(cwd, resolved) : null;
|
|
400
91
|
}
|
|
401
|
-
async function
|
|
92
|
+
async function capture(executable, args, cwd, env) {
|
|
402
93
|
return new Promise((resolve, reject) => {
|
|
403
94
|
const child = import_node_child_process.spawn(executable, args, {
|
|
404
|
-
env,
|
|
405
95
|
cwd,
|
|
96
|
+
env,
|
|
406
97
|
stdio: ["ignore", "pipe", "pipe"],
|
|
407
98
|
windowsHide: true
|
|
408
99
|
});
|
|
409
100
|
let stdout = "";
|
|
410
101
|
let stderr = "";
|
|
411
102
|
let settled = false;
|
|
412
|
-
const finish = (error
|
|
103
|
+
const finish = (error) => {
|
|
413
104
|
if (settled)
|
|
414
105
|
return;
|
|
415
106
|
settled = true;
|
|
416
|
-
clearTimeout(timer);
|
|
417
|
-
signal?.removeEventListener("abort", abort);
|
|
418
107
|
if (error)
|
|
419
108
|
reject(error);
|
|
420
|
-
else
|
|
421
|
-
resolve(
|
|
422
|
-
};
|
|
423
|
-
const abort = () => {
|
|
424
|
-
child.kill();
|
|
425
|
-
const reason = signal?.reason;
|
|
426
|
-
finish(reason instanceof Error ? reason : new Error(`${import_node_path4.default.basename(executable)} was aborted.`));
|
|
109
|
+
else
|
|
110
|
+
resolve(stdout);
|
|
427
111
|
};
|
|
428
|
-
const timer = setTimeout(() => {
|
|
429
|
-
child.kill();
|
|
430
|
-
finish(new Error(`${import_node_path4.default.basename(executable)} timed out after ${timeoutMs}ms.`));
|
|
431
|
-
}, timeoutMs);
|
|
432
112
|
child.stdout.setEncoding("utf8");
|
|
433
113
|
child.stderr.setEncoding("utf8");
|
|
434
114
|
child.stdout.on("data", (chunk) => {
|
|
435
115
|
stdout += chunk;
|
|
436
|
-
if (stdout.length >
|
|
116
|
+
if (stdout.length > OUTPUT_LIMIT) {
|
|
437
117
|
child.kill();
|
|
438
|
-
finish(new Error(`${
|
|
118
|
+
finish(new Error(`${import_node_path.default.basename(executable)} exceeded its stdout limit.`));
|
|
439
119
|
}
|
|
440
120
|
});
|
|
441
121
|
child.stderr.on("data", (chunk) => {
|
|
442
122
|
stderr += chunk;
|
|
443
|
-
if (stderr.length >
|
|
123
|
+
if (stderr.length > OUTPUT_LIMIT) {
|
|
444
124
|
child.kill();
|
|
445
|
-
finish(new Error(`${
|
|
125
|
+
finish(new Error(`${import_node_path.default.basename(executable)} exceeded its stderr limit.`));
|
|
446
126
|
}
|
|
447
127
|
});
|
|
448
128
|
child.once("error", (error) => finish(error));
|
|
449
|
-
child.once("close", (code,
|
|
450
|
-
if (code
|
|
451
|
-
|
|
452
|
-
finish(new Error(`${import_node_path4.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
|
|
129
|
+
child.once("close", (code, signal) => {
|
|
130
|
+
if (code === 0) {
|
|
131
|
+
finish();
|
|
453
132
|
return;
|
|
454
133
|
}
|
|
455
|
-
|
|
134
|
+
const detail = stderr.trim() || (signal ? `signal ${signal}` : `exit ${code}`);
|
|
135
|
+
finish(new Error(`${import_node_path.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
|
|
456
136
|
});
|
|
457
|
-
if (signal?.aborted)
|
|
458
|
-
abort();
|
|
459
|
-
else
|
|
460
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
461
137
|
});
|
|
462
138
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
import platform
|
|
469
|
-
import struct
|
|
470
|
-
import sys
|
|
471
|
-
import sysconfig
|
|
472
|
-
|
|
473
|
-
base_prefix = getattr(sys, "base_prefix", sys.prefix)
|
|
474
|
-
include_directory = sysconfig.get_path("include") or ""
|
|
475
|
-
header_path = os.path.join(include_directory, "Python.h") if include_directory else ""
|
|
476
|
-
library_candidates = []
|
|
477
|
-
stable_abi_library_candidates = []
|
|
478
|
-
if os.name == "nt":
|
|
479
|
-
library_directory = sysconfig.get_config_var("LIBDIR") or os.path.join(base_prefix, "libs")
|
|
480
|
-
configured_library = sysconfig.get_config_var("LDLIBRARY") or ""
|
|
481
|
-
if configured_library:
|
|
482
|
-
library_candidates.append(os.path.join(library_directory, os.path.splitext(configured_library)[0] + ".lib"))
|
|
483
|
-
library_candidates.append(os.path.join(library_directory, f"python{sys.version_info.major}{sys.version_info.minor}.lib"))
|
|
484
|
-
stable_abi_library_candidates.append(os.path.join(library_directory, "python3.lib"))
|
|
485
|
-
else:
|
|
486
|
-
library_directories = [
|
|
487
|
-
sysconfig.get_config_var("LIBDIR"),
|
|
488
|
-
sysconfig.get_config_var("LIBPL"),
|
|
489
|
-
os.path.join(base_prefix, "lib"),
|
|
490
|
-
]
|
|
491
|
-
library_names = [
|
|
492
|
-
sysconfig.get_config_var("LDLIBRARY"),
|
|
493
|
-
sysconfig.get_config_var("LIBRARY"),
|
|
494
|
-
]
|
|
495
|
-
for directory in library_directories:
|
|
496
|
-
for name in library_names:
|
|
497
|
-
if directory and name:
|
|
498
|
-
library_candidates.append(os.path.join(directory, name))
|
|
499
|
-
if directory:
|
|
500
|
-
stable_abi_library_candidates.extend([
|
|
501
|
-
os.path.join(directory, "libpython3.so"),
|
|
502
|
-
os.path.join(directory, "libpython3.dylib"),
|
|
503
|
-
])
|
|
504
|
-
|
|
505
|
-
library_path = next((candidate for candidate in library_candidates if os.path.isfile(candidate)), "")
|
|
506
|
-
stable_abi_library_path = next(
|
|
507
|
-
(candidate for candidate in stable_abi_library_candidates if os.path.isfile(candidate)),
|
|
508
|
-
None,
|
|
509
|
-
)
|
|
510
|
-
embedding = None
|
|
511
|
-
if os.path.isfile(header_path) and library_path:
|
|
512
|
-
library_name = os.path.basename(library_path)
|
|
513
|
-
library_kind = "import" if os.name == "nt" else ("static" if library_name.endswith(".a") else "shared")
|
|
514
|
-
embedding = {
|
|
515
|
-
"includeDirectory": include_directory,
|
|
516
|
-
"headerPath": header_path,
|
|
517
|
-
"libraryPath": library_path,
|
|
518
|
-
"libraryDirectory": os.path.dirname(library_path),
|
|
519
|
-
"libraryName": library_name,
|
|
520
|
-
"libraryKind": library_kind,
|
|
521
|
-
"stableAbiLibraryPath": stable_abi_library_path,
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
release_level = sys.version_info.releaselevel
|
|
525
|
-
release_suffixes = {
|
|
526
|
-
"alpha": "a",
|
|
527
|
-
"beta": "b",
|
|
528
|
-
"candidate": "rc",
|
|
529
|
-
"final": "",
|
|
530
|
-
}
|
|
531
|
-
release_suffix = release_suffixes[release_level]
|
|
532
|
-
if release_suffix:
|
|
533
|
-
release_suffix += str(sys.version_info.serial)
|
|
534
|
-
|
|
535
|
-
print(json.dumps({
|
|
536
|
-
"executable": sys.executable,
|
|
537
|
-
"baseExecutable": getattr(sys, "_base_executable", sys.executable),
|
|
538
|
-
"version": ".".join(map(str, sys.version_info[:3])) + release_suffix,
|
|
539
|
-
"major": sys.version_info.major,
|
|
540
|
-
"minor": sys.version_info.minor,
|
|
541
|
-
"patch": sys.version_info.micro,
|
|
542
|
-
"releaseLevel": release_level,
|
|
543
|
-
"implementation": sys.implementation.name,
|
|
544
|
-
"architecture": platform.machine().lower(),
|
|
545
|
-
"pointerBits": struct.calcsize("P") * 8,
|
|
546
|
-
"prefix": sys.prefix,
|
|
547
|
-
"basePrefix": base_prefix,
|
|
548
|
-
"virtualEnvironment": sys.prefix if sys.prefix != base_prefix else None,
|
|
549
|
-
"gilDisabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
|
|
550
|
-
"embedding": embedding,
|
|
551
|
-
}))
|
|
552
|
-
`;
|
|
553
|
-
function pythonSatisfiesPolicy(python, options) {
|
|
554
|
-
return pep440VersionSatisfies(python.version, options.requiresPython) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
|
|
555
|
-
}
|
|
556
|
-
async function probePython(candidate, options) {
|
|
557
|
-
const executable = await resolveExecutable(candidate.executable, options.env);
|
|
558
|
-
if (!executable)
|
|
559
|
-
return null;
|
|
560
|
-
try {
|
|
561
|
-
const { stdout } = run(executable, [...candidate.args, "-c", PYTHON_PROBE], options.env, options.probeTimeoutMs);
|
|
562
|
-
const info = JSON.parse(stdout.trim());
|
|
563
|
-
return normalizePep440Version(info.version) ? info : null;
|
|
564
|
-
} catch {
|
|
565
|
-
return null;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
async function findUv(options) {
|
|
569
|
-
const configured = options.env["PYFIG_UV"] ?? "uv";
|
|
570
|
-
const executable = await resolveExecutable(configured, options.env);
|
|
571
|
-
const uv = executable ? probeUv(executable, options) : null;
|
|
572
|
-
return uv && pep440VersionSatisfies(uv.version, options.requiresUv) ? uv : null;
|
|
573
|
-
}
|
|
574
|
-
function probeUv(executable, options) {
|
|
575
|
-
try {
|
|
576
|
-
const { stdout } = run(executable, ["--version"], options.env, options.probeTimeoutMs);
|
|
577
|
-
const version = normalizeVersion(stdout.match(/^uv\s+(\S+)/iu)?.[1]);
|
|
578
|
-
return version ? { executable, version } : null;
|
|
579
|
-
} catch {
|
|
580
|
-
return null;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
function normalizeVersion(value) {
|
|
584
|
-
if (!value)
|
|
585
|
-
return null;
|
|
586
|
-
return normalizePep440Version(value);
|
|
587
|
-
}
|
|
588
|
-
function pythonArchitectureMatchesRuntime(python) {
|
|
589
|
-
const architecture = python.architecture.toLowerCase();
|
|
590
|
-
if (python.pointerBits !== (process.arch === "ia32" ? 32 : 64))
|
|
591
|
-
return false;
|
|
592
|
-
if (process.arch === "x64")
|
|
593
|
-
return architecture === "x86_64" || architecture === "amd64";
|
|
594
|
-
if (process.arch === "arm64")
|
|
595
|
-
return architecture === "aarch64" || architecture === "arm64";
|
|
596
|
-
if (process.arch === "ia32") {
|
|
597
|
-
return architecture === "x86" || architecture === "i386" || architecture === "i686";
|
|
598
|
-
}
|
|
599
|
-
return architecture === process.arch;
|
|
139
|
+
function environmentValue(env, name) {
|
|
140
|
+
if (env[name] !== undefined || process.platform !== "win32")
|
|
141
|
+
return env[name];
|
|
142
|
+
const key = Object.keys(env).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
|
|
143
|
+
return key ? env[key] : undefined;
|
|
600
144
|
}
|
|
601
145
|
|
|
602
|
-
// src/
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
146
|
+
// src/uv.ts
|
|
147
|
+
var import_node_crypto = require("node:crypto");
|
|
148
|
+
var import_node_fs = require("node:fs");
|
|
149
|
+
var import_promises = require("node:fs/promises");
|
|
150
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
151
|
+
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
152
|
+
var import_node_stream = require("node:stream");
|
|
153
|
+
var import_promises2 = require("node:stream/promises");
|
|
154
|
+
var import_pep440 = require("@renovatebot/pep440");
|
|
155
|
+
var import_fflate = require("fflate");
|
|
156
|
+
var import_loqkit = require("loqkit");
|
|
157
|
+
var tar = __toESM(require("tar"), 1);
|
|
158
|
+
var import_undici = require("undici");
|
|
159
|
+
var RELEASE_PAGE_SIZE = 100;
|
|
160
|
+
var RELEASE_MAX_PAGES = 20;
|
|
161
|
+
var RELEASE_METADATA_TTL_MS = 24 * 60 * 60000;
|
|
162
|
+
var METADATA_TIMEOUT_MS = 30000;
|
|
163
|
+
var DOWNLOAD_TIMEOUT_MS = 5 * 60000;
|
|
164
|
+
var COORDINATION_TIMEOUT_MS = 15 * 60000;
|
|
165
|
+
async function resolveUv(specifier, cwd, env) {
|
|
166
|
+
const context = {
|
|
167
|
+
cache: uvCacheDirectory(env),
|
|
168
|
+
cwd,
|
|
169
|
+
env,
|
|
170
|
+
specifier
|
|
619
171
|
};
|
|
620
|
-
|
|
621
|
-
async function ensureVirtualEnvironmentResolved(options, uv) {
|
|
622
|
-
const existing = await findReusableEnvironment(options);
|
|
172
|
+
const existing = await findAvailableUv(context);
|
|
623
173
|
if (existing)
|
|
624
174
|
return existing;
|
|
625
|
-
const
|
|
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
|
-
if (!options.createVirtualEnvironment) {
|
|
631
|
-
throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
|
|
632
|
-
}
|
|
633
|
-
const basePython = await ensureBasePythonResolved(uv, options);
|
|
175
|
+
const policy = import_node_crypto.createHash("sha256").update(specifier).digest("hex");
|
|
634
176
|
return withProvisioningLock({
|
|
635
|
-
key: `
|
|
636
|
-
|
|
637
|
-
check,
|
|
177
|
+
key: `uv-policy:${canonicalPath(context.cache)}:${policy}`,
|
|
178
|
+
check: () => findAvailableUv(context),
|
|
638
179
|
mutate: async () => {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
await runAsync(uv.executable, [
|
|
642
|
-
"venv",
|
|
643
|
-
"--no-project",
|
|
644
|
-
"--no-config",
|
|
645
|
-
"--no-python-downloads",
|
|
646
|
-
"--python",
|
|
647
|
-
basePython.executable,
|
|
648
|
-
directory
|
|
649
|
-
], isolatedUvEnvironment(options), options.installTimeoutMs);
|
|
650
|
-
const installed = await check();
|
|
651
|
-
if (!installed) {
|
|
652
|
-
throw new Error(`uv created ${directory}, but its Python does not satisfy the requested capabilities.`);
|
|
653
|
-
}
|
|
654
|
-
return installed;
|
|
655
|
-
}
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
async function findReusableEnvironment(options) {
|
|
659
|
-
if (options.python) {
|
|
660
|
-
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
661
|
-
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
662
|
-
throw incompatibleConfiguredPython(options);
|
|
663
|
-
}
|
|
664
|
-
if (configured.virtualEnvironment)
|
|
665
|
-
return configured;
|
|
666
|
-
}
|
|
667
|
-
for (const directory of [
|
|
668
|
-
...externalVirtualEnvironmentDirectories(options.projectRoot, options.env),
|
|
669
|
-
virtualEnvironmentDirectory(options.projectRoot)
|
|
670
|
-
]) {
|
|
671
|
-
const info = await inspectVirtualEnvironment(directory, options);
|
|
672
|
-
if (info && pythonSatisfiesPolicy(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
|
|
673
|
-
return info;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
async function ensureBasePythonResolved(uv, options) {
|
|
679
|
-
if (options.python) {
|
|
680
|
-
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
681
|
-
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
682
|
-
throw incompatibleConfiguredPython(options);
|
|
683
|
-
}
|
|
684
|
-
if (!configured.virtualEnvironment)
|
|
685
|
-
return configured;
|
|
686
|
-
const base = await probePython({ executable: configured.baseExecutable, args: [] }, options);
|
|
687
|
-
if (base && pythonSatisfiesPolicy(base, options))
|
|
688
|
-
return base;
|
|
689
|
-
throw incompatibleConfiguredPython(options);
|
|
690
|
-
}
|
|
691
|
-
const system = await findPythonWithUv(uv, options, "system");
|
|
692
|
-
if (system)
|
|
693
|
-
return system;
|
|
694
|
-
const cachedManaged = await findPythonWithUv(uv, options, "managed");
|
|
695
|
-
if (cachedManaged)
|
|
696
|
-
return cachedManaged;
|
|
697
|
-
if (!options.installPython) {
|
|
698
|
-
throw new Error(missingPythonMessage(options, "managed Python installation is disabled"));
|
|
699
|
-
}
|
|
700
|
-
return withProvisioningLock({
|
|
701
|
-
key: `python:${canonicalPath(import_node_path5.default.join(options.cacheDir, "python"))}`,
|
|
702
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
703
|
-
check: () => findPythonWithUv(uv, options, "managed"),
|
|
704
|
-
mutate: async () => {
|
|
705
|
-
await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython], isolatedUvEnvironment(options), options.installTimeoutMs);
|
|
706
|
-
const installed = await findPythonWithUv(uv, options, "managed");
|
|
707
|
-
if (!installed) {
|
|
708
|
-
throw new Error(missingPythonMessage(options, `uv installed ${options.managedPython}, but it remains incompatible`));
|
|
709
|
-
}
|
|
710
|
-
return installed;
|
|
711
|
-
}
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
async function findPythonWithUv(uv, options, source) {
|
|
715
|
-
for (const request of ["", options.managedPython]) {
|
|
716
|
-
const args = [
|
|
717
|
-
"python",
|
|
718
|
-
"find",
|
|
719
|
-
"--no-project",
|
|
720
|
-
"--no-config",
|
|
721
|
-
"--no-python-downloads",
|
|
722
|
-
source === "system" ? "--system" : "--managed-python"
|
|
723
|
-
];
|
|
724
|
-
if (request)
|
|
725
|
-
args.push(request);
|
|
726
|
-
try {
|
|
727
|
-
const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
|
|
728
|
-
const info = await probePython({ executable: stdout.trim(), args: [] }, options);
|
|
729
|
-
if (info && pythonSatisfiesPolicy(info, options))
|
|
730
|
-
return info;
|
|
731
|
-
} catch {}
|
|
732
|
-
}
|
|
733
|
-
try {
|
|
734
|
-
const { stdout } = await runAsync(uv.executable, [
|
|
735
|
-
"python",
|
|
736
|
-
"list",
|
|
737
|
-
"--no-config",
|
|
738
|
-
"--no-python-downloads",
|
|
739
|
-
"--only-installed",
|
|
740
|
-
"--output-format",
|
|
741
|
-
"json",
|
|
742
|
-
source === "system" ? "--no-managed-python" : "--managed-python"
|
|
743
|
-
], explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
|
|
744
|
-
const candidates = installedPythonPaths(stdout);
|
|
745
|
-
for (const executable of candidates) {
|
|
746
|
-
const info = await probePython({ executable, args: [] }, options);
|
|
747
|
-
if (info && !info.virtualEnvironment && pythonSatisfiesPolicy(info, options)) {
|
|
748
|
-
return info;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
} catch {}
|
|
752
|
-
return null;
|
|
753
|
-
}
|
|
754
|
-
function installedPythonPaths(output) {
|
|
755
|
-
const parsed = JSON.parse(output);
|
|
756
|
-
if (!Array.isArray(parsed))
|
|
757
|
-
return [];
|
|
758
|
-
const paths = parsed.flatMap((entry) => {
|
|
759
|
-
if (!entry || typeof entry !== "object" || !("path" in entry))
|
|
760
|
-
return [];
|
|
761
|
-
const candidate = entry.path;
|
|
762
|
-
return typeof candidate === "string" && candidate ? [candidate] : [];
|
|
763
|
-
});
|
|
764
|
-
return [...new Map(paths.map((candidate) => [canonicalPath(candidate), candidate])).values()];
|
|
765
|
-
}
|
|
766
|
-
async function inspectVirtualEnvironment(directory, options) {
|
|
767
|
-
const executable = virtualEnvironmentPythonExecutable(directory);
|
|
768
|
-
const info = await probePython({ executable, args: [] }, options);
|
|
769
|
-
return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
|
|
770
|
-
}
|
|
771
|
-
async function installRuntimeDependencies(uv, python, dependencies, dependencyOptions, options) {
|
|
772
|
-
if (!python.virtualEnvironment) {
|
|
773
|
-
throw new Error("Runtime dependency installation requires a virtual environment.");
|
|
774
|
-
}
|
|
775
|
-
await withProvisioningLock({
|
|
776
|
-
key: `dependencies:${canonicalPath(python.virtualEnvironment)}`,
|
|
777
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
778
|
-
check: async () => null,
|
|
779
|
-
mutate: async () => {
|
|
780
|
-
const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-dependencies-"));
|
|
781
|
-
try {
|
|
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(`
|
|
792
|
-
`)}
|
|
793
|
-
`, "utf8");
|
|
794
|
-
sources.push(additions);
|
|
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;
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
async function ensureUvResolved(options) {
|
|
821
|
-
const existing = await findAvailableUv(options);
|
|
822
|
-
if (existing)
|
|
823
|
-
return existing;
|
|
824
|
-
if (!options.installUv) {
|
|
825
|
-
throw new Error(`Compatible uv is unavailable (requires-uv ${options.requiresUv}), and uv installation is disabled.`);
|
|
826
|
-
}
|
|
827
|
-
const policyKey = import_node_crypto.createHash("sha256").update(options.requiresUv).digest("hex");
|
|
828
|
-
return withProvisioningLock({
|
|
829
|
-
key: `uv-policy:${canonicalPath(uvInstallRoot(options))}:${policyKey}`,
|
|
830
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
831
|
-
check: () => findAvailableUv(options),
|
|
832
|
-
mutate: async () => {
|
|
833
|
-
const release = await resolveUvRelease(options);
|
|
834
|
-
const installDir = uvInstallDirectory(options, release.version);
|
|
835
|
-
const executable = uvExecutable(options, release.version);
|
|
180
|
+
const release = await resolveUvRelease(context);
|
|
181
|
+
const destination = uvInstallDirectory(context, release.version);
|
|
836
182
|
return withProvisioningLock({
|
|
837
|
-
key: `uv-release:${canonicalPath(
|
|
838
|
-
|
|
839
|
-
check: () => findAvailableUv(options),
|
|
183
|
+
key: `uv-release:${canonicalPath(destination)}`,
|
|
184
|
+
check: () => probeUv(uvExecutable(context, release.version), context, release.version),
|
|
840
185
|
mutate: async () => {
|
|
841
|
-
await installUv(
|
|
842
|
-
const
|
|
843
|
-
if (!
|
|
844
|
-
throw new Error(`
|
|
186
|
+
await installUv(release, context);
|
|
187
|
+
const installed = await probeUv(uvExecutable(context, release.version), context, release.version);
|
|
188
|
+
if (!installed) {
|
|
189
|
+
throw new Error(`Managed uv ${release.version} failed its post-install probe.`);
|
|
845
190
|
}
|
|
846
|
-
return
|
|
191
|
+
return installed;
|
|
847
192
|
}
|
|
848
193
|
});
|
|
849
194
|
}
|
|
850
195
|
});
|
|
851
196
|
}
|
|
852
|
-
async function findAvailableUv(
|
|
853
|
-
const external = await
|
|
854
|
-
if (external)
|
|
197
|
+
async function findAvailableUv(context) {
|
|
198
|
+
const external = await findExecutable("uv", context.cwd, context.env);
|
|
199
|
+
if (external && await probeUv(external, context))
|
|
855
200
|
return external;
|
|
856
|
-
return
|
|
201
|
+
return findCachedUv(context);
|
|
857
202
|
}
|
|
858
|
-
async function
|
|
859
|
-
const
|
|
860
|
-
|
|
203
|
+
async function findCachedUv(context) {
|
|
204
|
+
const root = uvInstallRoot(context);
|
|
205
|
+
const entries = await import_promises.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
206
|
+
let versions = entries.filter((entry) => entry.isDirectory() && import_pep440.valid(entry.name) === entry.name).map((entry) => entry.name);
|
|
861
207
|
while (versions.length > 0) {
|
|
862
|
-
const
|
|
863
|
-
if (!
|
|
208
|
+
const version = import_pep440.maxSatisfying(versions, context.specifier);
|
|
209
|
+
if (!version)
|
|
864
210
|
return null;
|
|
865
|
-
const
|
|
866
|
-
if (
|
|
867
|
-
return
|
|
868
|
-
versions = versions.filter((
|
|
211
|
+
const executable = await probeUv(uvExecutable(context, version), context, version);
|
|
212
|
+
if (executable)
|
|
213
|
+
return executable;
|
|
214
|
+
versions = versions.filter((candidate) => candidate !== version);
|
|
869
215
|
}
|
|
870
216
|
return null;
|
|
871
217
|
}
|
|
872
|
-
function
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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));
|
|
903
|
-
}
|
|
904
|
-
function explicitPythonSourceEnvironment(options) {
|
|
905
|
-
const env = isolatedUvEnvironment(options);
|
|
906
|
-
delete env["UV_PYTHON_PREFERENCE"];
|
|
907
|
-
return env;
|
|
908
|
-
}
|
|
909
|
-
function targetUvEnvironment(options, python) {
|
|
910
|
-
return uvCommandEnvironment(uvEnvironment(options), python);
|
|
911
|
-
}
|
|
912
|
-
function incompatibleConfiguredPython(options) {
|
|
913
|
-
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
|
|
914
|
-
}
|
|
915
|
-
function missingPythonMessage(options, reason) {
|
|
916
|
-
const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
|
|
917
|
-
return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
|
|
918
|
-
}
|
|
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;
|
|
218
|
+
async function probeUv(executable, context, expectedVersion) {
|
|
219
|
+
try {
|
|
220
|
+
const output = await capture(executable, ["--version"], context.cwd, context.env);
|
|
221
|
+
const version = import_pep440.valid(output.match(/^uv\s+(\S+)/iu)?.[1] ?? "");
|
|
222
|
+
return version && (!expectedVersion || version === expectedVersion) && import_pep440.maxSatisfying([version], context.specifier) ? executable : null;
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
927
225
|
}
|
|
928
|
-
const configuredBase = configured.virtualEnvironment ? configured.baseExecutable : configured.executable;
|
|
929
|
-
return pathsEqual(configuredBase, virtualEnvironment.baseExecutable);
|
|
930
226
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
var UV_RELEASE_METADATA_TTL_MS = 24 * 60 * 60000;
|
|
934
|
-
async function resolveUvRelease(options) {
|
|
935
|
-
const catalog = await ensureUvReleaseCatalog(options);
|
|
227
|
+
async function resolveUvRelease(context) {
|
|
228
|
+
const catalog = await ensureUvReleaseCatalog(context);
|
|
936
229
|
const asset = uvAsset();
|
|
937
|
-
const
|
|
938
|
-
const version =
|
|
939
|
-
const release =
|
|
940
|
-
if (!release)
|
|
941
|
-
throw new Error(`No published uv release satisfies
|
|
942
|
-
}
|
|
230
|
+
const candidates = catalog.releases.filter((release) => release.assets.includes(asset) && release.assets.includes(`${asset}.sha256`));
|
|
231
|
+
const version = import_pep440.maxSatisfying(candidates.map((release) => release.version), context.specifier);
|
|
232
|
+
const release = candidates.find((candidate) => candidate.version === version);
|
|
233
|
+
if (!release)
|
|
234
|
+
throw new Error(`No published uv release satisfies ${context.specifier}.`);
|
|
943
235
|
return release;
|
|
944
236
|
}
|
|
945
|
-
async function ensureUvReleaseCatalog(
|
|
946
|
-
const file = uvReleaseMetadataFile(
|
|
237
|
+
async function ensureUvReleaseCatalog(context) {
|
|
238
|
+
const file = uvReleaseMetadataFile(context);
|
|
947
239
|
const stale = await readUvReleaseCatalog(file);
|
|
948
240
|
try {
|
|
949
241
|
return await withProvisioningLock({
|
|
950
242
|
key: `uv-metadata:${canonicalPath(file)}`,
|
|
951
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
952
243
|
check: () => readFreshUvReleaseCatalog(file),
|
|
953
244
|
mutate: async () => {
|
|
954
|
-
const catalog = await fetchUvReleaseCatalog(
|
|
245
|
+
const catalog = await fetchUvReleaseCatalog(context.env);
|
|
955
246
|
await writeUvReleaseCatalog(file, catalog);
|
|
956
247
|
return catalog;
|
|
957
248
|
}
|
|
@@ -963,30 +254,32 @@ async function ensureUvReleaseCatalog(options) {
|
|
|
963
254
|
throw error;
|
|
964
255
|
}
|
|
965
256
|
}
|
|
966
|
-
async function fetchUvReleaseCatalog(
|
|
967
|
-
const dispatcher = proxyDispatcher(
|
|
257
|
+
async function fetchUvReleaseCatalog(env) {
|
|
258
|
+
const dispatcher = proxyDispatcher(env);
|
|
968
259
|
const releases = new Map;
|
|
969
260
|
let exhausted = false;
|
|
970
261
|
try {
|
|
971
|
-
for (let page = 1;page <=
|
|
972
|
-
const text = await downloadText(`https://api.github.com/repos/astral-sh/uv/releases?per_page=${
|
|
262
|
+
for (let page = 1;page <= RELEASE_MAX_PAGES; page += 1) {
|
|
263
|
+
const text = await downloadText(`https://api.github.com/repos/astral-sh/uv/releases?per_page=${RELEASE_PAGE_SIZE}&page=${page}`, METADATA_TIMEOUT_MS, dispatcher, githubApiHeaders(env));
|
|
973
264
|
const payload = JSON.parse(text);
|
|
974
|
-
if (!Array.isArray(payload))
|
|
265
|
+
if (!Array.isArray(payload))
|
|
975
266
|
throw new Error("GitHub returned invalid uv release metadata.");
|
|
976
|
-
}
|
|
977
267
|
for (const candidate of payload) {
|
|
978
268
|
if (!candidate || typeof candidate !== "object")
|
|
979
269
|
continue;
|
|
980
|
-
const
|
|
981
|
-
if (
|
|
270
|
+
const value = candidate;
|
|
271
|
+
if (value.draft === true || typeof value.tag_name !== "string")
|
|
982
272
|
continue;
|
|
983
|
-
const
|
|
984
|
-
const version = normalizePep440Version(release.tag_name);
|
|
273
|
+
const version = import_pep440.valid(value.tag_name);
|
|
985
274
|
if (version) {
|
|
986
|
-
releases.set(version, {
|
|
275
|
+
releases.set(version, {
|
|
276
|
+
assets: releaseAssetNames(value.assets),
|
|
277
|
+
tag: value.tag_name,
|
|
278
|
+
version
|
|
279
|
+
});
|
|
987
280
|
}
|
|
988
281
|
}
|
|
989
|
-
if (payload.length <
|
|
282
|
+
if (payload.length < RELEASE_PAGE_SIZE) {
|
|
990
283
|
exhausted = true;
|
|
991
284
|
break;
|
|
992
285
|
}
|
|
@@ -994,16 +287,14 @@ async function fetchUvReleaseCatalog(options) {
|
|
|
994
287
|
} finally {
|
|
995
288
|
await dispatcher?.close();
|
|
996
289
|
}
|
|
997
|
-
if (!exhausted)
|
|
998
|
-
throw new Error(`uv release catalog exceeded ${
|
|
999
|
-
|
|
1000
|
-
if (releases.size === 0) {
|
|
290
|
+
if (!exhausted)
|
|
291
|
+
throw new Error(`uv release catalog exceeded ${RELEASE_MAX_PAGES} pages.`);
|
|
292
|
+
if (releases.size === 0)
|
|
1001
293
|
throw new Error("GitHub returned no usable uv releases.");
|
|
1002
|
-
}
|
|
1003
294
|
return { fetchedAt: Date.now(), releases: [...releases.values()] };
|
|
1004
295
|
}
|
|
1005
296
|
function githubApiHeaders(env) {
|
|
1006
|
-
const token = [env
|
|
297
|
+
const token = [environmentValue(env, "GITHUB_TOKEN"), environmentValue(env, "GH_TOKEN")].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
1007
298
|
if (token && /[\r\n]/u.test(token)) {
|
|
1008
299
|
throw new TypeError("GitHub API token must not contain line breaks.");
|
|
1009
300
|
}
|
|
@@ -1019,7 +310,7 @@ async function readFreshUvReleaseCatalog(file) {
|
|
|
1019
310
|
if (!catalog)
|
|
1020
311
|
return null;
|
|
1021
312
|
const age = Date.now() - catalog.fetchedAt;
|
|
1022
|
-
return age >= 0 && age <
|
|
313
|
+
return age >= 0 && age < RELEASE_METADATA_TTL_MS ? catalog : null;
|
|
1023
314
|
}
|
|
1024
315
|
async function readUvReleaseCatalog(file) {
|
|
1025
316
|
try {
|
|
@@ -1032,17 +323,15 @@ function parseUvReleaseCatalog(value) {
|
|
|
1032
323
|
if (!value || typeof value !== "object")
|
|
1033
324
|
return null;
|
|
1034
325
|
const catalog = value;
|
|
1035
|
-
if (!Number.isSafeInteger(catalog.fetchedAt) || catalog.fetchedAt < 0 || !Array.isArray(catalog.releases))
|
|
326
|
+
if (!Number.isSafeInteger(catalog.fetchedAt) || catalog.fetchedAt < 0 || !Array.isArray(catalog.releases))
|
|
1036
327
|
return null;
|
|
1037
|
-
}
|
|
1038
328
|
const releases = [];
|
|
1039
|
-
for (const
|
|
1040
|
-
if (!
|
|
329
|
+
for (const candidate of catalog.releases) {
|
|
330
|
+
if (!candidate || typeof candidate !== "object")
|
|
1041
331
|
return null;
|
|
1042
|
-
const release =
|
|
1043
|
-
if (!Array.isArray(release.assets) || !release.assets.every((asset) => typeof asset === "string") || typeof release.tag !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._+-]*$/u.test(release.tag) || typeof release.version !== "string" ||
|
|
332
|
+
const release = candidate;
|
|
333
|
+
if (!Array.isArray(release.assets) || !release.assets.every((asset) => typeof asset === "string") || typeof release.tag !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._+-]*$/u.test(release.tag) || typeof release.version !== "string" || import_pep440.valid(release.version) !== release.version)
|
|
1044
334
|
return null;
|
|
1045
|
-
}
|
|
1046
335
|
releases.push({
|
|
1047
336
|
assets: release.assets,
|
|
1048
337
|
tag: release.tag,
|
|
@@ -1052,8 +341,8 @@ function parseUvReleaseCatalog(value) {
|
|
|
1052
341
|
return { fetchedAt: catalog.fetchedAt, releases };
|
|
1053
342
|
}
|
|
1054
343
|
async function writeUvReleaseCatalog(file, catalog) {
|
|
1055
|
-
await import_promises.mkdir(
|
|
1056
|
-
const staged =
|
|
344
|
+
await import_promises.mkdir(import_node_path2.default.dirname(file), { recursive: true });
|
|
345
|
+
const staged = import_node_path2.default.join(import_node_path2.default.dirname(file), `.${import_node_path2.default.basename(file)}.${uniqueSuffix()}.tmp`);
|
|
1057
346
|
try {
|
|
1058
347
|
await import_promises.writeFile(staged, `${JSON.stringify(catalog)}
|
|
1059
348
|
`, { encoding: "utf8", flag: "wx" });
|
|
@@ -1064,47 +353,25 @@ async function writeUvReleaseCatalog(file, catalog) {
|
|
|
1064
353
|
await import_promises.rm(staged, { force: true });
|
|
1065
354
|
}
|
|
1066
355
|
}
|
|
1067
|
-
function
|
|
1068
|
-
return import_node_path5.default.join(options.cacheDir, "metadata", "uv-releases.json");
|
|
1069
|
-
}
|
|
1070
|
-
function releaseAssetNames(value) {
|
|
1071
|
-
if (!Array.isArray(value))
|
|
1072
|
-
return [];
|
|
1073
|
-
return value.flatMap((entry) => {
|
|
1074
|
-
if (!entry || typeof entry !== "object")
|
|
1075
|
-
return [];
|
|
1076
|
-
const name = entry.name;
|
|
1077
|
-
return typeof name === "string" ? [name] : [];
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
function uvInstallRoot(options) {
|
|
1081
|
-
return import_node_path5.default.join(options.cacheDir, "tools", "uv");
|
|
1082
|
-
}
|
|
1083
|
-
function uvInstallDirectory(options, version) {
|
|
1084
|
-
return import_node_path5.default.join(uvInstallRoot(options), version);
|
|
1085
|
-
}
|
|
1086
|
-
function uvExecutable(options, version) {
|
|
1087
|
-
return import_node_path5.default.join(uvInstallDirectory(options, version), process.platform === "win32" ? "uv.exe" : "uv");
|
|
1088
|
-
}
|
|
1089
|
-
async function installUv(installDir, release, options) {
|
|
356
|
+
async function installUv(release, context) {
|
|
1090
357
|
const asset = uvAsset();
|
|
1091
358
|
const baseUrl = `https://github.com/astral-sh/uv/releases/download/${release.tag}`;
|
|
1092
|
-
const temporary = await import_promises.mkdtemp(
|
|
1093
|
-
const dispatcher = proxyDispatcher(
|
|
359
|
+
const temporary = await import_promises.mkdtemp(import_node_path2.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
|
|
360
|
+
const dispatcher = proxyDispatcher(context.env);
|
|
1094
361
|
try {
|
|
1095
|
-
const archive =
|
|
1096
|
-
await download(`${baseUrl}/${asset}`, archive,
|
|
1097
|
-
const expected = (await downloadText(`${baseUrl}/${asset}.sha256`,
|
|
362
|
+
const archive = import_node_path2.default.join(temporary, asset);
|
|
363
|
+
await download(`${baseUrl}/${asset}`, archive, dispatcher);
|
|
364
|
+
const expected = (await downloadText(`${baseUrl}/${asset}.sha256`, DOWNLOAD_TIMEOUT_MS, dispatcher)).trim().split(/\s+/u)[0];
|
|
1098
365
|
const actual = await sha256File(archive);
|
|
1099
366
|
if (!expected || expected.toLowerCase() !== actual.toLowerCase()) {
|
|
1100
|
-
throw new Error(`uv archive checksum mismatch for ${asset}
|
|
367
|
+
throw new Error(`uv archive checksum mismatch for ${asset}.`);
|
|
1101
368
|
}
|
|
1102
|
-
const extracted =
|
|
369
|
+
const extracted = import_node_path2.default.join(temporary, "extracted");
|
|
1103
370
|
await import_promises.mkdir(extracted, { recursive: true });
|
|
1104
371
|
const source = await extractUvExecutable(archive, asset, extracted);
|
|
1105
372
|
if (!source)
|
|
1106
|
-
throw new Error(`uv archive did not contain the expected executable: ${asset}
|
|
1107
|
-
await
|
|
373
|
+
throw new Error(`uv archive did not contain the expected executable: ${asset}.`);
|
|
374
|
+
await publishUv(source, release.version, context);
|
|
1108
375
|
} finally {
|
|
1109
376
|
await dispatcher?.close();
|
|
1110
377
|
await import_promises.rm(temporary, { recursive: true, force: true });
|
|
@@ -1117,25 +384,25 @@ async function extractUvExecutable(archive, asset, destination) {
|
|
|
1117
384
|
return findFile(destination, executableName);
|
|
1118
385
|
}
|
|
1119
386
|
const entries = import_fflate.unzipSync(await import_promises.readFile(archive), {
|
|
1120
|
-
filter: ({ name }) =>
|
|
387
|
+
filter: ({ name }) => import_node_path2.default.posix.basename(name.replaceAll("\\", "/")) === executableName
|
|
1121
388
|
});
|
|
1122
|
-
const matches = Object.entries(entries).filter(([name]) =>
|
|
389
|
+
const matches = Object.entries(entries).filter(([name]) => import_node_path2.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
|
|
1123
390
|
if (matches.length !== 1 || !matches[0])
|
|
1124
391
|
return null;
|
|
1125
|
-
const source =
|
|
392
|
+
const source = import_node_path2.default.join(destination, executableName);
|
|
1126
393
|
await import_promises.writeFile(source, matches[0][1], { flag: "wx" });
|
|
1127
394
|
return source;
|
|
1128
395
|
}
|
|
1129
|
-
async function
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
const
|
|
396
|
+
async function publishUv(source, version, context) {
|
|
397
|
+
const installDirectory = uvInstallDirectory(context, version);
|
|
398
|
+
await import_promises.mkdir(installDirectory, { recursive: true });
|
|
399
|
+
const executable = uvExecutable(context, version);
|
|
400
|
+
const staged = import_node_path2.default.join(installDirectory, `.${import_node_path2.default.basename(executable)}.${uniqueSuffix()}.tmp`);
|
|
1133
401
|
try {
|
|
1134
402
|
await import_promises.copyFile(source, staged);
|
|
1135
403
|
if (process.platform !== "win32")
|
|
1136
404
|
await import_promises.chmod(staged, 493);
|
|
1137
|
-
|
|
1138
|
-
if (!stagedUv || stagedUv.version !== version) {
|
|
405
|
+
if (!await probeUv(staged, { ...context, specifier: `==${version}` })) {
|
|
1139
406
|
throw new Error(`Downloaded uv executable is not version ${version}.`);
|
|
1140
407
|
}
|
|
1141
408
|
if (process.platform === "win32")
|
|
@@ -1145,29 +412,17 @@ async function publishUvAtomically(source, installDir, version, options) {
|
|
|
1145
412
|
await import_promises.rm(staged, { force: true });
|
|
1146
413
|
}
|
|
1147
414
|
}
|
|
1148
|
-
function
|
|
1149
|
-
const architecture = process.arch === "x64" ? "x86_64" : process.arch === "arm64" ? "aarch64" : null;
|
|
1150
|
-
if (!architecture)
|
|
1151
|
-
throw new Error(`uv bootstrap does not support architecture ${process.arch}.`);
|
|
1152
|
-
if (process.platform === "win32")
|
|
1153
|
-
return `uv-${architecture}-pc-windows-msvc.zip`;
|
|
1154
|
-
if (process.platform === "darwin")
|
|
1155
|
-
return `uv-${architecture}-apple-darwin.tar.gz`;
|
|
1156
|
-
if (process.platform === "linux")
|
|
1157
|
-
return `uv-${architecture}-unknown-linux-musl.tar.gz`;
|
|
1158
|
-
throw new Error(`uv bootstrap does not support platform ${process.platform}.`);
|
|
1159
|
-
}
|
|
1160
|
-
async function download(url, destination, timeoutMs, dispatcher) {
|
|
415
|
+
async function download(url, destination, dispatcher) {
|
|
1161
416
|
const response = await import_undici.fetch(url, {
|
|
1162
417
|
dispatcher,
|
|
1163
418
|
redirect: "follow",
|
|
1164
|
-
signal: AbortSignal.timeout(
|
|
419
|
+
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
1165
420
|
});
|
|
1166
421
|
if (!response.ok || !response.body) {
|
|
1167
422
|
await response.body?.cancel();
|
|
1168
|
-
throw new Error(`Could not download ${url}: HTTP ${response.status}
|
|
423
|
+
throw new Error(`Could not download ${url}: HTTP ${response.status}.`);
|
|
1169
424
|
}
|
|
1170
|
-
await import_promises2.pipeline(import_node_stream.Readable.fromWeb(response.body),
|
|
425
|
+
await import_promises2.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs.createWriteStream(destination, { flags: "wx" }));
|
|
1171
426
|
}
|
|
1172
427
|
async function downloadText(url, timeoutMs, dispatcher, headers) {
|
|
1173
428
|
const response = await import_undici.fetch(url, {
|
|
@@ -1178,33 +433,30 @@ async function downloadText(url, timeoutMs, dispatcher, headers) {
|
|
|
1178
433
|
});
|
|
1179
434
|
if (!response.ok) {
|
|
1180
435
|
await response.body?.cancel();
|
|
1181
|
-
throw new Error(`Could not download ${url}: HTTP ${response.status}
|
|
436
|
+
throw new Error(`Could not download ${url}: HTTP ${response.status}.`);
|
|
1182
437
|
}
|
|
1183
438
|
return response.text();
|
|
1184
439
|
}
|
|
1185
440
|
function proxyDispatcher(env) {
|
|
1186
|
-
const httpProxy = environmentValue(env, "http_proxy", "HTTP_PROXY");
|
|
1187
|
-
const httpsProxy = environmentValue(env, "https_proxy", "HTTPS_PROXY");
|
|
441
|
+
const httpProxy = environmentValue(env, "http_proxy") ?? environmentValue(env, "HTTP_PROXY");
|
|
442
|
+
const httpsProxy = environmentValue(env, "https_proxy") ?? environmentValue(env, "HTTPS_PROXY");
|
|
1188
443
|
if (!httpProxy && !httpsProxy)
|
|
1189
444
|
return;
|
|
1190
445
|
return new import_undici.EnvHttpProxyAgent({
|
|
1191
446
|
httpProxy,
|
|
1192
447
|
httpsProxy,
|
|
1193
|
-
noProxy: environmentValue(env, "no_proxy", "NO_PROXY")
|
|
448
|
+
noProxy: environmentValue(env, "no_proxy") ?? environmentValue(env, "NO_PROXY")
|
|
1194
449
|
});
|
|
1195
450
|
}
|
|
1196
|
-
function environmentValue(env, lowercase, uppercase) {
|
|
1197
|
-
return env[lowercase] ?? env[uppercase];
|
|
1198
|
-
}
|
|
1199
451
|
async function sha256File(file) {
|
|
1200
452
|
const hash = import_node_crypto.createHash("sha256");
|
|
1201
|
-
for await (const chunk of
|
|
453
|
+
for await (const chunk of import_node_fs.createReadStream(file))
|
|
1202
454
|
hash.update(chunk);
|
|
1203
455
|
return hash.digest("hex");
|
|
1204
456
|
}
|
|
1205
457
|
async function findFile(directory, name) {
|
|
1206
458
|
for (const entry of await import_promises.readdir(directory, { withFileTypes: true })) {
|
|
1207
|
-
const target =
|
|
459
|
+
const target = import_node_path2.default.join(directory, entry.name);
|
|
1208
460
|
if (entry.isFile() && entry.name === name)
|
|
1209
461
|
return target;
|
|
1210
462
|
if (entry.isDirectory()) {
|
|
@@ -1215,93 +467,332 @@ async function findFile(directory, name) {
|
|
|
1215
467
|
}
|
|
1216
468
|
return null;
|
|
1217
469
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
async function uvSpawn(args = [], spawnOptions = {}, pyfigOptions = {}) {
|
|
1233
|
-
const options = optionsForInvocation(pyfigOptions, spawnOptions.env);
|
|
1234
|
-
const resolved = resolveOptions(options);
|
|
1235
|
-
const environment = uvCommandNeedsEnvironment(args) ? await ensureEnvironment(resolved) : undefined;
|
|
1236
|
-
const uv = environment?.uv ?? await ensureUv(resolved);
|
|
1237
|
-
const command = buildUvCommand(uv, args, resolved, environment?.python);
|
|
1238
|
-
return import_node_child_process2.spawn(command.executable, command.args, {
|
|
1239
|
-
...spawnOptions,
|
|
1240
|
-
env: command.env
|
|
470
|
+
async function withProvisioningLock(options) {
|
|
471
|
+
let existing;
|
|
472
|
+
const result = await import_loqkit.withLock({ resource: options.key }, options.mutate, {
|
|
473
|
+
key: options.key,
|
|
474
|
+
name: "Pyfig uv setup",
|
|
475
|
+
namespace: "pyfig",
|
|
476
|
+
timeout: COORDINATION_TIMEOUT_MS,
|
|
477
|
+
predicate: async () => {
|
|
478
|
+
const value = await options.check();
|
|
479
|
+
if (value === null)
|
|
480
|
+
return true;
|
|
481
|
+
existing = { value };
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
1241
484
|
});
|
|
485
|
+
if (result.context.state === "executed")
|
|
486
|
+
return result.value;
|
|
487
|
+
if (existing)
|
|
488
|
+
return existing.value;
|
|
489
|
+
throw new Error(`Provisioning check for ${options.key} did not produce uv.`);
|
|
490
|
+
}
|
|
491
|
+
function uvCacheDirectory(env) {
|
|
492
|
+
const home = environmentValue(env, process.platform === "win32" ? "USERPROFILE" : "HOME") ?? import_node_os.default.homedir();
|
|
493
|
+
const root = process.platform === "win32" ? environmentValue(env, "LOCALAPPDATA") ?? import_node_path2.default.join(home, "AppData", "Local") : process.platform === "darwin" ? import_node_path2.default.join(home, "Library", "Caches") : environmentValue(env, "XDG_CACHE_HOME") ?? import_node_path2.default.join(home, ".cache");
|
|
494
|
+
return import_node_path2.default.resolve(root, "pyfig");
|
|
495
|
+
}
|
|
496
|
+
function uvInstallRoot(context) {
|
|
497
|
+
return import_node_path2.default.join(context.cache, "tools", "uv");
|
|
498
|
+
}
|
|
499
|
+
function uvInstallDirectory(context, version) {
|
|
500
|
+
return import_node_path2.default.join(uvInstallRoot(context), version);
|
|
1242
501
|
}
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
502
|
+
function uvExecutable(context, version) {
|
|
503
|
+
return import_node_path2.default.join(uvInstallDirectory(context, version), process.platform === "win32" ? "uv.exe" : "uv");
|
|
504
|
+
}
|
|
505
|
+
function uvReleaseMetadataFile(context) {
|
|
506
|
+
return import_node_path2.default.join(context.cache, "metadata", "uv-releases.json");
|
|
507
|
+
}
|
|
508
|
+
function uvAsset() {
|
|
509
|
+
const architecture = process.arch === "x64" ? "x86_64" : process.arch === "arm64" ? "aarch64" : null;
|
|
510
|
+
if (!architecture)
|
|
511
|
+
throw new Error(`uv setup does not support architecture ${process.arch}.`);
|
|
512
|
+
if (process.platform === "win32")
|
|
513
|
+
return `uv-${architecture}-pc-windows-msvc.zip`;
|
|
514
|
+
if (process.platform === "darwin")
|
|
515
|
+
return `uv-${architecture}-apple-darwin.tar.gz`;
|
|
516
|
+
if (process.platform === "linux")
|
|
517
|
+
return `uv-${architecture}-unknown-linux-musl.tar.gz`;
|
|
518
|
+
throw new Error(`uv setup does not support platform ${process.platform}.`);
|
|
519
|
+
}
|
|
520
|
+
function releaseAssetNames(value) {
|
|
521
|
+
if (!Array.isArray(value))
|
|
522
|
+
return [];
|
|
523
|
+
return value.flatMap((entry) => {
|
|
524
|
+
if (!entry || typeof entry !== "object")
|
|
525
|
+
return [];
|
|
526
|
+
const name = entry.name;
|
|
527
|
+
return typeof name === "string" ? [name] : [];
|
|
1252
528
|
});
|
|
1253
529
|
}
|
|
1254
|
-
function
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
args: python ? targetUvArguments(args, python) : [...args],
|
|
1258
|
-
env: uvCommandEnvironment(uvEnvironment(options), python)
|
|
1259
|
-
};
|
|
530
|
+
function canonicalPath(value) {
|
|
531
|
+
const normalized = import_node_path2.default.resolve(value);
|
|
532
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
1260
533
|
}
|
|
1261
|
-
function
|
|
1262
|
-
return
|
|
534
|
+
function uniqueSuffix() {
|
|
535
|
+
return `${process.pid}.${import_node_crypto.randomUUID()}`;
|
|
1263
536
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
537
|
+
|
|
538
|
+
// src/pyfig.ts
|
|
539
|
+
var DEFAULT_UV = ">=0.10,<1";
|
|
540
|
+
var PYTHON_PROBE = String.raw`
|
|
541
|
+
import json
|
|
542
|
+
import os
|
|
543
|
+
import platform
|
|
544
|
+
import struct
|
|
545
|
+
import sys
|
|
546
|
+
import sysconfig
|
|
547
|
+
|
|
548
|
+
base_prefix = getattr(sys, "base_prefix", sys.prefix)
|
|
549
|
+
include_directory = sysconfig.get_path("include") or ""
|
|
550
|
+
header_path = os.path.join(include_directory, "Python.h") if include_directory else ""
|
|
551
|
+
library_candidates = []
|
|
552
|
+
if os.name == "nt":
|
|
553
|
+
library_directory = sysconfig.get_config_var("LIBDIR") or os.path.join(base_prefix, "libs")
|
|
554
|
+
configured_library = sysconfig.get_config_var("LDLIBRARY") or ""
|
|
555
|
+
if configured_library:
|
|
556
|
+
library_candidates.append(os.path.join(library_directory, os.path.splitext(configured_library)[0] + ".lib"))
|
|
557
|
+
library_candidates.append(os.path.join(library_directory, f"python{sys.version_info.major}{sys.version_info.minor}.lib"))
|
|
558
|
+
else:
|
|
559
|
+
for directory in [
|
|
560
|
+
sysconfig.get_config_var("LIBDIR"),
|
|
561
|
+
sysconfig.get_config_var("LIBPL"),
|
|
562
|
+
os.path.join(base_prefix, "lib"),
|
|
563
|
+
]:
|
|
564
|
+
for name in [sysconfig.get_config_var("LDLIBRARY"), sysconfig.get_config_var("LIBRARY")]:
|
|
565
|
+
if directory and name:
|
|
566
|
+
library_candidates.append(os.path.join(directory, name))
|
|
567
|
+
|
|
568
|
+
release_suffix = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}[sys.version_info.releaselevel]
|
|
569
|
+
if release_suffix:
|
|
570
|
+
release_suffix += str(sys.version_info.serial)
|
|
571
|
+
|
|
572
|
+
print(json.dumps({
|
|
573
|
+
"executable": sys.executable,
|
|
574
|
+
"version": ".".join(map(str, sys.version_info[:3])) + release_suffix,
|
|
575
|
+
"implementation": sys.implementation.name,
|
|
576
|
+
"architecture": platform.machine().lower(),
|
|
577
|
+
"pointerBits": struct.calcsize("P") * 8,
|
|
578
|
+
"gilDisabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
|
|
579
|
+
"embedding": os.path.isfile(header_path) and any(os.path.isfile(path) for path in library_candidates),
|
|
580
|
+
}))
|
|
581
|
+
`;
|
|
582
|
+
|
|
583
|
+
class PythonContext {
|
|
584
|
+
#cwd;
|
|
585
|
+
#env;
|
|
586
|
+
uv;
|
|
587
|
+
python;
|
|
588
|
+
constructor(uvPath, pythonPath, cwd = process.cwd(), env = process.env) {
|
|
589
|
+
this.#cwd = cwd;
|
|
590
|
+
this.#env = { ...env };
|
|
591
|
+
this.uv = uvPath;
|
|
592
|
+
this.python = pythonPath;
|
|
593
|
+
Object.freeze(this);
|
|
594
|
+
}
|
|
595
|
+
run(...input) {
|
|
596
|
+
const { args, spawnOptions } = splitRunInput(input);
|
|
597
|
+
return import_node_child_process2.spawn(this.uv, buildRunArgs(this.python, args), {
|
|
598
|
+
cwd: this.#cwd,
|
|
599
|
+
env: this.#env,
|
|
600
|
+
stdio: "inherit",
|
|
601
|
+
windowsHide: true,
|
|
602
|
+
...spawnOptions
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async function pyfig(options = {}) {
|
|
607
|
+
const {
|
|
608
|
+
uv: uvRequest = DEFAULT_UV,
|
|
609
|
+
python: pythonRequest,
|
|
610
|
+
requireEmbedding = false,
|
|
611
|
+
requireGil = false,
|
|
612
|
+
cwd = process.cwd(),
|
|
613
|
+
env: suppliedEnvironment
|
|
614
|
+
} = options;
|
|
615
|
+
const env = mergeEnvironment(suppliedEnvironment);
|
|
616
|
+
const workingDirectory = import_node_path3.default.resolve(cwd);
|
|
617
|
+
const uvPath = await selectUv(uvRequest, workingDirectory, env);
|
|
618
|
+
const selectedPythonPath = await selectPython({
|
|
619
|
+
cwd: workingDirectory,
|
|
620
|
+
env,
|
|
621
|
+
pythonRequest,
|
|
622
|
+
requireEmbedding,
|
|
623
|
+
requireGil,
|
|
624
|
+
uvPath
|
|
1274
625
|
});
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
626
|
+
return new PythonContext(uvPath, selectedPythonPath, workingDirectory, env);
|
|
627
|
+
}
|
|
628
|
+
function buildRunArgs(pythonPath, args) {
|
|
629
|
+
const separator = args.indexOf("--");
|
|
630
|
+
if (separator < 0) {
|
|
631
|
+
return ["run", "--python", pythonPath, "--", ...args];
|
|
632
|
+
}
|
|
633
|
+
const uvArgs = args.slice(0, separator);
|
|
634
|
+
const command = args.slice(separator + 1);
|
|
635
|
+
if (uvArgs.some((argument) => argument === "--python" || argument.startsWith("--python="))) {
|
|
636
|
+
throw new TypeError("run() does not accept --python before --; Python is selected by pyfig().");
|
|
637
|
+
}
|
|
638
|
+
uvArgs.push("--python", pythonPath);
|
|
639
|
+
return ["run", ...uvArgs, "--", ...command];
|
|
640
|
+
}
|
|
641
|
+
function splitRunInput(input) {
|
|
642
|
+
const values = [...input];
|
|
643
|
+
const last = values.at(-1);
|
|
644
|
+
const spawnOptions = typeof last === "object" && last !== null && !Array.isArray(last) ? values.pop() : {};
|
|
645
|
+
if (!values.every((value) => typeof value === "string")) {
|
|
646
|
+
throw new TypeError("uv run arguments must be strings with an optional SpawnOptions object last.");
|
|
647
|
+
}
|
|
648
|
+
return { args: values, spawnOptions };
|
|
649
|
+
}
|
|
650
|
+
async function selectUv(request, cwd, env) {
|
|
651
|
+
const selection = await executableOrSpecifier("uv", request, cwd, env);
|
|
652
|
+
if (selection.executable)
|
|
653
|
+
return selection.executable;
|
|
654
|
+
const specifier = selection.specifier;
|
|
655
|
+
if (!specifier)
|
|
656
|
+
throw new Error("uv selection did not contain a version specifier.");
|
|
657
|
+
return resolveUv(specifier, cwd, env);
|
|
658
|
+
}
|
|
659
|
+
async function selectPython(options) {
|
|
660
|
+
if (options.pythonRequest !== undefined) {
|
|
661
|
+
const selection = await executableOrSpecifier("python", options.pythonRequest, options.cwd, options.env);
|
|
662
|
+
if (selection.executable) {
|
|
663
|
+
const explicit = await probePython(selection.executable, options.cwd, options.env);
|
|
664
|
+
validatePythonCapabilities(explicit, options);
|
|
665
|
+
return explicit.executable;
|
|
666
|
+
}
|
|
667
|
+
if (!selection.specifier)
|
|
668
|
+
throw new Error("Python selection did not contain a version specifier.");
|
|
669
|
+
return selectDiscoveredPython(selection.specifier, options);
|
|
1278
670
|
}
|
|
671
|
+
return selectDiscoveredPython(null, options);
|
|
672
|
+
}
|
|
673
|
+
async function selectDiscoveredPython(specifier, options) {
|
|
674
|
+
const request = specifier ? [specifier] : [];
|
|
675
|
+
let systemFailure;
|
|
1279
676
|
try {
|
|
1280
|
-
|
|
677
|
+
const systemPath = await findPython(options.uvPath, ["--system", "--no-managed-python", ...request], options);
|
|
678
|
+
const system = await probePython(systemPath, options.cwd, options.env);
|
|
679
|
+
validatePythonVersion(system, specifier);
|
|
680
|
+
validatePythonCapabilities(system, options);
|
|
681
|
+
return system.executable;
|
|
1281
682
|
} catch (error) {
|
|
1282
|
-
|
|
1283
|
-
|
|
683
|
+
systemFailure = error;
|
|
684
|
+
}
|
|
685
|
+
try {
|
|
686
|
+
const managedPath = await findPython(options.uvPath, ["--managed-python", ...request], options);
|
|
687
|
+
const managed = await probePython(managedPath, options.cwd, options.env);
|
|
688
|
+
validatePythonVersion(managed, specifier);
|
|
689
|
+
validatePythonCapabilities(managed, options);
|
|
690
|
+
return managed.executable;
|
|
691
|
+
} catch (error) {
|
|
692
|
+
throw new Error(`No compatible Python was found. System: ${errorMessage(systemFailure)} Managed: ${errorMessage(error)}`, { cause: error });
|
|
1284
693
|
}
|
|
1285
694
|
}
|
|
1286
|
-
function
|
|
1287
|
-
|
|
695
|
+
async function findPython(uvPath, args, options) {
|
|
696
|
+
const output = await capture(uvPath, ["python", "find", ...args], options.cwd, options.env);
|
|
697
|
+
const executable = output.trim().split(/\r?\n/u).at(-1) ?? "";
|
|
698
|
+
if (!import_node_path3.default.isAbsolute(executable)) {
|
|
699
|
+
throw new Error(`uv did not report an absolute Python path: ${executable || "<empty>"}`);
|
|
700
|
+
}
|
|
701
|
+
return import_node_path3.default.normalize(executable);
|
|
1288
702
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
703
|
+
async function probePython(executable, cwd, env) {
|
|
704
|
+
const output = await capture(executable, ["-I", "-S", "-c", PYTHON_PROBE], cwd, env);
|
|
705
|
+
const line = output.trim().split(/\r?\n/u).at(-1) ?? "";
|
|
706
|
+
let value;
|
|
707
|
+
try {
|
|
708
|
+
value = JSON.parse(line);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
throw new Error(`${JSON.stringify(executable)} did not return valid Python metadata.`, {
|
|
711
|
+
cause: error
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (!isPythonProbe(value)) {
|
|
715
|
+
throw new Error(`${JSON.stringify(executable)} returned incomplete Python metadata.`);
|
|
716
|
+
}
|
|
717
|
+
if (!import_node_path3.default.isAbsolute(value.executable)) {
|
|
718
|
+
throw new Error(`${JSON.stringify(executable)} reported a non-absolute executable path.`);
|
|
719
|
+
}
|
|
720
|
+
return { ...value, executable: import_node_path3.default.normalize(value.executable) };
|
|
721
|
+
}
|
|
722
|
+
function validatePythonVersion(python, specifier) {
|
|
723
|
+
if (!specifier)
|
|
724
|
+
return;
|
|
725
|
+
const version = import_pep4402.valid(python.version);
|
|
726
|
+
if (!version)
|
|
727
|
+
throw new Error(`Python ${python.executable} reported invalid version ${python.version}.`);
|
|
728
|
+
if (import_pep4402.filter([version], specifier).length !== 1) {
|
|
729
|
+
throw new Error(`Python ${version} does not satisfy ${specifier}.`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function validatePythonCapabilities(python, options) {
|
|
733
|
+
if (options.requireEmbedding) {
|
|
734
|
+
if (python.implementation !== "cpython") {
|
|
735
|
+
throw new Error(`Python embedding requires CPython, received ${python.implementation}.`);
|
|
736
|
+
}
|
|
737
|
+
if (!python.embedding) {
|
|
738
|
+
throw new Error(`Python ${python.executable} does not provide embedding headers and a library.`);
|
|
739
|
+
}
|
|
740
|
+
if (!pythonArchitectureMatchesRuntime(python)) {
|
|
741
|
+
throw new Error(`Python ${python.executable} does not match the JavaScript runtime architecture.`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (options.requireGil && python.gilDisabled) {
|
|
745
|
+
throw new Error(`Python ${python.executable} is free-threaded but requireGil is enabled.`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function pythonArchitectureMatchesRuntime(python) {
|
|
749
|
+
if (python.pointerBits !== (process.arch === "ia32" ? 32 : 64))
|
|
750
|
+
return false;
|
|
751
|
+
if (process.arch === "x64")
|
|
752
|
+
return python.architecture === "x86_64" || python.architecture === "amd64";
|
|
753
|
+
if (process.arch === "arm64")
|
|
754
|
+
return python.architecture === "aarch64" || python.architecture === "arm64";
|
|
755
|
+
if (process.arch === "ia32") {
|
|
756
|
+
return python.architecture === "x86" || python.architecture === "i386" || python.architecture === "i686";
|
|
757
|
+
}
|
|
758
|
+
return python.architecture === process.arch;
|
|
759
|
+
}
|
|
760
|
+
async function executableOrSpecifier(name, value, cwd, env) {
|
|
761
|
+
assertNonempty(name, value);
|
|
762
|
+
const executable = await findExecutable(value, cwd, env);
|
|
763
|
+
if (executable)
|
|
764
|
+
return { executable, specifier: null };
|
|
765
|
+
if (import_pep4402.validRange(value))
|
|
766
|
+
return { executable: null, specifier: value };
|
|
767
|
+
throw new TypeError(`${name} must resolve to an executable or be a valid PEP 440 specifier, received ${JSON.stringify(value)}.`);
|
|
768
|
+
}
|
|
769
|
+
function isPythonProbe(value) {
|
|
770
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
771
|
+
return false;
|
|
772
|
+
const probe = value;
|
|
773
|
+
return typeof probe["architecture"] === "string" && typeof probe["embedding"] === "boolean" && typeof probe["executable"] === "string" && typeof probe["gilDisabled"] === "boolean" && typeof probe["implementation"] === "string" && typeof probe["pointerBits"] === "number" && typeof probe["version"] === "string";
|
|
774
|
+
}
|
|
775
|
+
function errorMessage(error) {
|
|
776
|
+
return error instanceof Error ? error.message : String(error);
|
|
777
|
+
}
|
|
778
|
+
function mergeEnvironment(supplied) {
|
|
779
|
+
const environment = { ...process.env };
|
|
780
|
+
for (const [key, value] of Object.entries(supplied ?? {})) {
|
|
781
|
+
if (process.platform === "win32") {
|
|
782
|
+
const existing = Object.keys(environment).find((candidate) => candidate.toLowerCase() === key.toLowerCase());
|
|
783
|
+
if (existing && existing !== key)
|
|
784
|
+
delete environment[existing];
|
|
785
|
+
}
|
|
786
|
+
if (value === undefined)
|
|
787
|
+
delete environment[key];
|
|
788
|
+
else
|
|
789
|
+
environment[key] = value;
|
|
790
|
+
}
|
|
791
|
+
return environment;
|
|
792
|
+
}
|
|
793
|
+
function assertNonempty(name, value) {
|
|
794
|
+
if (value.trim() === "")
|
|
795
|
+
throw new TypeError(`${name} must not be empty.`);
|
|
1305
796
|
}
|
|
1306
797
|
|
|
1307
|
-
//# debugId=
|
|
798
|
+
//# debugId=F23B5C2985518F2764756E2164756E21
|