pyfig 0.7.0 → 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 +76 -178
- package/SECURITY.md +16 -5
- package/dist/cjs/index.js +482 -1131
- package/dist/cjs/index.js.map +6 -16
- package/dist/index.d.ts +2 -8
- 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 -1342
- package/dist/cjs/sync-resolver.js.map +0 -20
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -57
- 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 -35
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -69
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -159
- 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 -20
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -673
- package/dist/provision.js.map +0 -1
- package/dist/python-venv.d.ts +0 -12
- package/dist/python-venv.d.ts.map +0 -1
- package/dist/python-venv.js +0 -52
- package/dist/python-venv.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/venv-identity.d.ts +0 -36
- package/dist/venv-identity.d.ts.map +0 -1
- package/dist/venv-identity.js +0 -80
- package/dist/venv-identity.js.map +0 -1
|
@@ -1,1342 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
function __accessProp(key) {
|
|
8
|
-
return this[key];
|
|
9
|
-
}
|
|
10
|
-
var __toESMCache_node;
|
|
11
|
-
var __toESMCache_esm;
|
|
12
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
-
var canCache = mod != null && typeof mod === "object";
|
|
14
|
-
if (canCache) {
|
|
15
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
-
var cached = cache.get(mod);
|
|
17
|
-
if (cached)
|
|
18
|
-
return cached;
|
|
19
|
-
}
|
|
20
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
-
const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
-
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
23
|
-
for (let key of __getOwnPropNames(mod))
|
|
24
|
-
if (!__hasOwnProp.call(to, key))
|
|
25
|
-
__defProp(to, key, {
|
|
26
|
-
get: __accessProp.bind(mod, key),
|
|
27
|
-
enumerable: true
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
if (canCache)
|
|
31
|
-
cache.set(mod, to);
|
|
32
|
-
return to;
|
|
33
|
-
};
|
|
34
|
-
var __toCommonJS = (from) => {
|
|
35
|
-
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
36
|
-
if (entry)
|
|
37
|
-
return entry;
|
|
38
|
-
entry = __defProp({}, "__esModule", { value: true });
|
|
39
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
40
|
-
for (var key of __getOwnPropNames(from))
|
|
41
|
-
if (!__hasOwnProp.call(entry, key))
|
|
42
|
-
__defProp(entry, key, {
|
|
43
|
-
get: __accessProp.bind(from, key),
|
|
44
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
__moduleCache.set(from, entry);
|
|
48
|
-
return entry;
|
|
49
|
-
};
|
|
50
|
-
var __moduleCache;
|
|
51
|
-
var __returnValue = (v) => v;
|
|
52
|
-
function __exportSetter(name, newValue) {
|
|
53
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
54
|
-
}
|
|
55
|
-
var __export = (target, all) => {
|
|
56
|
-
for (var name in all)
|
|
57
|
-
__defProp(target, name, {
|
|
58
|
-
get: all[name],
|
|
59
|
-
enumerable: true,
|
|
60
|
-
configurable: true,
|
|
61
|
-
set: __exportSetter.bind(all, name)
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// src/sync-resolver.ts
|
|
66
|
-
var import_node_fs4 = require("node:fs");
|
|
67
|
-
|
|
68
|
-
// src/provision.ts
|
|
69
|
-
var import_node_crypto2 = require("node:crypto");
|
|
70
|
-
var import_node_fs3 = require("node:fs");
|
|
71
|
-
var import_promises2 = require("node:fs/promises");
|
|
72
|
-
var import_node_os = __toESM(require("node:os"), 1);
|
|
73
|
-
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
74
|
-
var import_node_stream = require("node:stream");
|
|
75
|
-
var import_promises3 = require("node:stream/promises");
|
|
76
|
-
var import_fflate = require("fflate");
|
|
77
|
-
var tar = __toESM(require("tar"), 1);
|
|
78
|
-
var import_undici = require("undici");
|
|
79
|
-
|
|
80
|
-
// src/config.ts
|
|
81
|
-
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
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
|
-
const dependencies = normalizeDependencies(options.dependencies ?? []);
|
|
153
|
-
const requirements = normalizeRequirements(options.requirements ?? [], projectRoot);
|
|
154
|
-
return {
|
|
155
|
-
dependencies,
|
|
156
|
-
requirements,
|
|
157
|
-
python: options.python ?? env["PYFIG_PYTHON"] ?? "",
|
|
158
|
-
requiresPython,
|
|
159
|
-
managedPython,
|
|
160
|
-
requiresUv,
|
|
161
|
-
requireEmbedding: options.requireEmbedding ?? parseBoolean(env["PYFIG_REQUIRE_EMBEDDING"], false),
|
|
162
|
-
requireGil: options.requireGil ?? parseBoolean(env["PYFIG_REQUIRE_GIL"], false),
|
|
163
|
-
autoInstall,
|
|
164
|
-
installPython: options.installPython ?? parseBoolean(env["PYFIG_INSTALL_PYTHON"], autoInstall),
|
|
165
|
-
installUv: options.installUv ?? parseBoolean(env["PYFIG_INSTALL_UV"], autoInstall),
|
|
166
|
-
createVirtualEnvironment: options.createVirtualEnvironment ?? parseBoolean(env["PYFIG_CREATE_VIRTUAL_ENVIRONMENT"], autoInstall),
|
|
167
|
-
cacheDir,
|
|
168
|
-
projectRoot,
|
|
169
|
-
probeTimeoutMs,
|
|
170
|
-
installTimeoutMs,
|
|
171
|
-
downloadTimeoutMs,
|
|
172
|
-
coordinationTimeoutMs,
|
|
173
|
-
env
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
function hasRuntimeRequirements(options) {
|
|
177
|
-
return options.dependencies.length > 0 || options.requirements.length > 0;
|
|
178
|
-
}
|
|
179
|
-
function uvProcessEnvironment(options) {
|
|
180
|
-
return {
|
|
181
|
-
...options.env,
|
|
182
|
-
UV_PYTHON_INSTALL_DIR: import_node_path2.default.join(options.cacheDir, "python"),
|
|
183
|
-
UV_CACHE_DIR: import_node_path2.default.join(options.cacheDir, "uv-cache"),
|
|
184
|
-
UV_PYTHON_PREFERENCE: "system",
|
|
185
|
-
UV_NO_CONFIG: "1"
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function assertPep440Specifier(name, specifier) {
|
|
189
|
-
if (!isValidPep440Specifier(specifier)) {
|
|
190
|
-
throw new TypeError(`Invalid ${name} PEP 440 specifier: ${specifier}`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function assertManagedPythonRequest(request, requiresPython) {
|
|
194
|
-
if (!request || /[\\/\0\r\n]/u.test(request) || request.includes("..")) {
|
|
195
|
-
throw new TypeError(`Invalid managed Python request: ${request}`);
|
|
196
|
-
}
|
|
197
|
-
if (/^\d+\.\d+(?:\.\d+)?$/u.test(request)) {
|
|
198
|
-
const normalized = request.split(".").length === 2 ? `${request}.0` : request;
|
|
199
|
-
if (!pep440VersionSatisfies(normalized, requiresPython)) {
|
|
200
|
-
throw new TypeError(`Managed Python ${request} cannot satisfy requires-python ${requiresPython}.`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function resolveDuration(name, explicit, environment, fallback, allowZero = false) {
|
|
205
|
-
const value = explicit ?? (environment === undefined ? fallback : Number(environment));
|
|
206
|
-
const minimum = allowZero ? 0 : 1;
|
|
207
|
-
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
208
|
-
throw new TypeError(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer.`);
|
|
209
|
-
}
|
|
210
|
-
return value;
|
|
211
|
-
}
|
|
212
|
-
function parseBoolean(value, fallback) {
|
|
213
|
-
if (value === undefined)
|
|
214
|
-
return fallback;
|
|
215
|
-
if (/^(1|true|yes|on)$/iu.test(value))
|
|
216
|
-
return true;
|
|
217
|
-
if (/^(0|false|no|off)$/iu.test(value))
|
|
218
|
-
return false;
|
|
219
|
-
throw new TypeError(`Invalid boolean value: ${value}`);
|
|
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
|
-
}
|
|
259
|
-
|
|
260
|
-
// src/venv-identity.ts
|
|
261
|
-
var import_node_crypto = require("node:crypto");
|
|
262
|
-
var import_promises = require("node:fs/promises");
|
|
263
|
-
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
264
|
-
|
|
265
|
-
// src/python-venv.ts
|
|
266
|
-
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
267
|
-
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
268
|
-
var VIRTUAL_ENV_ROOT_NAME = import_node_path3.default.join("node_modules", ".pyfig", "venvs");
|
|
269
|
-
function pythonVenvRoot(projectRoot) {
|
|
270
|
-
return import_node_path3.default.join(projectRoot, VIRTUAL_ENV_ROOT_NAME);
|
|
271
|
-
}
|
|
272
|
-
function pythonVenvDirectory(projectRoot, venvDirectory) {
|
|
273
|
-
return import_node_path3.default.join(pythonVenvRoot(projectRoot), venvDirectory);
|
|
274
|
-
}
|
|
275
|
-
function externalPythonVenvDirectories(projectRoot, env) {
|
|
276
|
-
const candidates = [env["VIRTUAL_ENV"], import_node_path3.default.join(projectRoot, ".venv")].filter((value) => Boolean(value)).map((value) => import_node_path3.default.resolve(value));
|
|
277
|
-
return [...new Map(candidates.map((value) => [canonicalPath(value), value])).values()];
|
|
278
|
-
}
|
|
279
|
-
function pythonVenvExecutable(pythonVenv) {
|
|
280
|
-
return import_node_path3.default.join(pythonVenv, process.platform === "win32" ? "Scripts" : "bin", process.platform === "win32" ? "python.exe" : "python");
|
|
281
|
-
}
|
|
282
|
-
function activatePythonVenv(env, pythonVenv, pythonExecutable) {
|
|
283
|
-
const activated = { ...env };
|
|
284
|
-
const pathEntry = Object.keys(activated).find((key) => key.toUpperCase() === "PATH");
|
|
285
|
-
const existingPath = pathEntry ? activated[pathEntry] : undefined;
|
|
286
|
-
for (const key of Object.keys(activated)) {
|
|
287
|
-
const normalized = key.toUpperCase();
|
|
288
|
-
if (normalized === "PATH" || normalized === "PYTHONHOME" || normalized === "VIRTUAL_ENV") {
|
|
289
|
-
delete activated[key];
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
activated["PATH"] = [import_node_path3.default.dirname(pythonExecutable), existingPath].filter((value) => Boolean(value)).join(import_node_path3.default.delimiter);
|
|
293
|
-
activated["VIRTUAL_ENV"] = pythonVenv;
|
|
294
|
-
return activated;
|
|
295
|
-
}
|
|
296
|
-
function pathsEqual(left, right) {
|
|
297
|
-
return canonicalPath(left) === canonicalPath(right);
|
|
298
|
-
}
|
|
299
|
-
function canonicalPath(value) {
|
|
300
|
-
const resolved = import_node_path3.default.resolve(value);
|
|
301
|
-
let canonical = resolved;
|
|
302
|
-
try {
|
|
303
|
-
canonical = import_node_fs2.default.realpathSync.native(resolved);
|
|
304
|
-
} catch {}
|
|
305
|
-
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// src/venv-identity.ts
|
|
309
|
-
var VENV_SCHEMA = 1;
|
|
310
|
-
var VENV_METADATA = ".pyfig-venv.json";
|
|
311
|
-
function createVenvIdentity(python, requirementsSha256) {
|
|
312
|
-
const contract = {
|
|
313
|
-
schema: VENV_SCHEMA,
|
|
314
|
-
runtime: {
|
|
315
|
-
architecture: process.arch,
|
|
316
|
-
platform: process.platform
|
|
317
|
-
},
|
|
318
|
-
python: {
|
|
319
|
-
architecture: python.architecture,
|
|
320
|
-
baseExecutable: canonicalPath(python.executable),
|
|
321
|
-
basePrefix: canonicalPath(python.basePrefix),
|
|
322
|
-
embeddingHeader: python.embedding ? canonicalPath(python.embedding.headerPath) : null,
|
|
323
|
-
embeddingLibrary: python.embedding ? canonicalPath(python.embedding.libraryPath) : null,
|
|
324
|
-
gilDisabled: python.gilDisabled,
|
|
325
|
-
implementation: python.implementation,
|
|
326
|
-
pointerBits: python.pointerBits,
|
|
327
|
-
version: python.version
|
|
328
|
-
},
|
|
329
|
-
requirementsSha256
|
|
330
|
-
};
|
|
331
|
-
const id = sha256(JSON.stringify(contract));
|
|
332
|
-
const implementation = safePathSegment(python.implementation);
|
|
333
|
-
const version = safePathSegment(python.version);
|
|
334
|
-
return {
|
|
335
|
-
contract,
|
|
336
|
-
directoryName: `${implementation}-${version}-${id.slice(0, 20)}`,
|
|
337
|
-
id
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
function requirementsDigest(content) {
|
|
341
|
-
return sha256(content);
|
|
342
|
-
}
|
|
343
|
-
function venvMetadataFile(directory) {
|
|
344
|
-
return import_node_path4.default.join(directory, VENV_METADATA);
|
|
345
|
-
}
|
|
346
|
-
async function venvMatches(directory, venv) {
|
|
347
|
-
try {
|
|
348
|
-
const parsed = JSON.parse(await import_promises.readFile(venvMetadataFile(directory), "utf8"));
|
|
349
|
-
return parsed.venvId === venv.id && JSON.stringify(parsed.contract) === JSON.stringify(venv.contract);
|
|
350
|
-
} catch {
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
async function writeVenvMetadata(directory, venv) {
|
|
355
|
-
await import_promises.mkdir(directory, { recursive: true });
|
|
356
|
-
const file = venvMetadataFile(directory);
|
|
357
|
-
const staged = `${file}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`;
|
|
358
|
-
const metadata = {
|
|
359
|
-
contract: venv.contract,
|
|
360
|
-
venvId: venv.id
|
|
361
|
-
};
|
|
362
|
-
try {
|
|
363
|
-
await import_promises.writeFile(staged, `${JSON.stringify(metadata, null, 2)}
|
|
364
|
-
`, {
|
|
365
|
-
encoding: "utf8",
|
|
366
|
-
flag: "wx"
|
|
367
|
-
});
|
|
368
|
-
await import_promises.rename(staged, file);
|
|
369
|
-
} finally {
|
|
370
|
-
await import_promises.rm(staged, { force: true });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
function safePathSegment(value) {
|
|
374
|
-
return value.toLowerCase().replace(/[^a-z0-9.-]+/gu, "-").replace(/^-+|-+$/gu, "") || "python";
|
|
375
|
-
}
|
|
376
|
-
function sha256(value) {
|
|
377
|
-
return import_node_crypto.createHash("sha256").update(value).digest("hex");
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// src/provision-lock.ts
|
|
381
|
-
var import_loqkit = require("loqkit");
|
|
382
|
-
async function withProvisioningLock(options) {
|
|
383
|
-
let existing;
|
|
384
|
-
const result = await import_loqkit.withLock({ resource: options.key }, options.mutate, {
|
|
385
|
-
key: options.key,
|
|
386
|
-
name: "Pyfig provisioning",
|
|
387
|
-
namespace: "pyfig",
|
|
388
|
-
timeout: options.timeoutMs,
|
|
389
|
-
predicate: async () => {
|
|
390
|
-
const value = await options.check();
|
|
391
|
-
if (value === null)
|
|
392
|
-
return true;
|
|
393
|
-
existing = { value };
|
|
394
|
-
return false;
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
if (result.context.state === "executed")
|
|
398
|
-
return result.value;
|
|
399
|
-
if (existing)
|
|
400
|
-
return existing.value;
|
|
401
|
-
throw new Error(`Provisioning check for ${options.key} did not produce a resource.`);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/package-target.ts
|
|
405
|
-
var TARGET_OPTIONS = new Set([
|
|
406
|
-
"--active",
|
|
407
|
-
"--config-file",
|
|
408
|
-
"--directory",
|
|
409
|
-
"--isolated",
|
|
410
|
-
"--prefix",
|
|
411
|
-
"--project",
|
|
412
|
-
"--python",
|
|
413
|
-
"--root",
|
|
414
|
-
"--system",
|
|
415
|
-
"--target",
|
|
416
|
-
"--user"
|
|
417
|
-
]);
|
|
418
|
-
var TARGET_ENVIRONMENT_VARIABLES = new Set([
|
|
419
|
-
"CONDA_PREFIX",
|
|
420
|
-
"PIP_PREFIX",
|
|
421
|
-
"PIP_PYTHON",
|
|
422
|
-
"PIP_ROOT",
|
|
423
|
-
"PIP_TARGET",
|
|
424
|
-
"PIP_USER",
|
|
425
|
-
"PYTHONHOME",
|
|
426
|
-
"PYTHONUSERBASE",
|
|
427
|
-
"UV_CONFIG_FILE",
|
|
428
|
-
"UV_PROJECT",
|
|
429
|
-
"UV_PROJECT_ENVIRONMENT",
|
|
430
|
-
"UV_PYTHON",
|
|
431
|
-
"UV_PREFIX",
|
|
432
|
-
"UV_ROOT",
|
|
433
|
-
"UV_SYSTEM_PYTHON",
|
|
434
|
-
"UV_TARGET"
|
|
435
|
-
]);
|
|
436
|
-
function uvCommandNeedsPython(args) {
|
|
437
|
-
return args[0] === "pip" || args[0] === "run";
|
|
438
|
-
}
|
|
439
|
-
function targetUvArguments(args, python) {
|
|
440
|
-
if (!python.virtualEnvironment) {
|
|
441
|
-
throw new Error("Targeted uv commands require a virtual environment.");
|
|
442
|
-
}
|
|
443
|
-
assertNoTargetOverride(args);
|
|
444
|
-
if (args[0] === "run") {
|
|
445
|
-
return ["run", "--no-project", "--python", python.executable, ...args.slice(1)];
|
|
446
|
-
}
|
|
447
|
-
if (args[0] === "pip" && args[1]) {
|
|
448
|
-
return ["pip", args[1], "--python", python.executable, ...args.slice(2)];
|
|
449
|
-
}
|
|
450
|
-
return [...args];
|
|
451
|
-
}
|
|
452
|
-
function uvCommandProcessEnvironment(env, python) {
|
|
453
|
-
const sanitized = sanitizeUvProcessEnvironment(env);
|
|
454
|
-
if (!python?.virtualEnvironment)
|
|
455
|
-
return sanitized;
|
|
456
|
-
return {
|
|
457
|
-
...activatePythonVenv(sanitized, python.virtualEnvironment, python.executable),
|
|
458
|
-
UV_PYTHON: python.executable
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
function sanitizeUvProcessEnvironment(env) {
|
|
462
|
-
const sanitized = { ...env };
|
|
463
|
-
for (const key of Object.keys(sanitized)) {
|
|
464
|
-
if (TARGET_ENVIRONMENT_VARIABLES.has(key.toUpperCase()))
|
|
465
|
-
delete sanitized[key];
|
|
466
|
-
}
|
|
467
|
-
return sanitized;
|
|
468
|
-
}
|
|
469
|
-
function assertNoTargetOverride(args) {
|
|
470
|
-
for (const argument of args.slice(1)) {
|
|
471
|
-
if (argument === "--")
|
|
472
|
-
return;
|
|
473
|
-
const [name] = argument.split("=", 1);
|
|
474
|
-
if (name && TARGET_OPTIONS.has(name)) {
|
|
475
|
-
throw new Error(`${name} cannot override the environment selected by Pyfig.`);
|
|
476
|
-
}
|
|
477
|
-
if (argument === "-p" || argument.startsWith("-p=") || /^-p[^-]/u.test(argument)) {
|
|
478
|
-
throw new Error("-p cannot override the environment selected by Pyfig.");
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// src/process.ts
|
|
484
|
-
var import_node_child_process = require("node:child_process");
|
|
485
|
-
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
486
|
-
var import_which = __toESM(require("which"), 1);
|
|
487
|
-
async function resolveExecutable(value, env) {
|
|
488
|
-
if (!value)
|
|
489
|
-
return null;
|
|
490
|
-
if (import_node_path5.default.isAbsolute(value)) {
|
|
491
|
-
return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
|
|
492
|
-
}
|
|
493
|
-
if (!/^[A-Za-z0-9_.-]+$/u.test(value))
|
|
494
|
-
return null;
|
|
495
|
-
return await import_which.default(value, { nothrow: true, path: env["PATH"], pathExt: env["PATHEXT"] });
|
|
496
|
-
}
|
|
497
|
-
function run(executable, args, env, timeoutMs) {
|
|
498
|
-
const result = import_node_child_process.spawnSync(executable, args, {
|
|
499
|
-
encoding: "utf8",
|
|
500
|
-
env,
|
|
501
|
-
timeout: timeoutMs,
|
|
502
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
503
|
-
windowsHide: true
|
|
504
|
-
});
|
|
505
|
-
if (result.status !== 0 || result.error) {
|
|
506
|
-
const detail = result.stderr?.trim() || result.error?.message || `exit ${result.status}`;
|
|
507
|
-
throw new Error(`${import_node_path5.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`);
|
|
508
|
-
}
|
|
509
|
-
return { stdout: result.stdout, stderr: result.stderr };
|
|
510
|
-
}
|
|
511
|
-
async function runAsync(executable, args, env, timeoutMs, signal, cwd) {
|
|
512
|
-
return new Promise((resolve, reject) => {
|
|
513
|
-
const child = import_node_child_process.spawn(executable, args, {
|
|
514
|
-
env,
|
|
515
|
-
cwd,
|
|
516
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
517
|
-
windowsHide: true
|
|
518
|
-
});
|
|
519
|
-
let stdout = "";
|
|
520
|
-
let stderr = "";
|
|
521
|
-
let settled = false;
|
|
522
|
-
const finish = (error, result) => {
|
|
523
|
-
if (settled)
|
|
524
|
-
return;
|
|
525
|
-
settled = true;
|
|
526
|
-
clearTimeout(timer);
|
|
527
|
-
signal?.removeEventListener("abort", abort);
|
|
528
|
-
if (error)
|
|
529
|
-
reject(error);
|
|
530
|
-
else if (result)
|
|
531
|
-
resolve(result);
|
|
532
|
-
};
|
|
533
|
-
const abort = () => {
|
|
534
|
-
child.kill();
|
|
535
|
-
const reason = signal?.reason;
|
|
536
|
-
finish(reason instanceof Error ? reason : new Error(`${import_node_path5.default.basename(executable)} was aborted.`));
|
|
537
|
-
};
|
|
538
|
-
const timer = setTimeout(() => {
|
|
539
|
-
child.kill();
|
|
540
|
-
finish(new Error(`${import_node_path5.default.basename(executable)} timed out after ${timeoutMs}ms.`));
|
|
541
|
-
}, timeoutMs);
|
|
542
|
-
child.stdout.setEncoding("utf8");
|
|
543
|
-
child.stderr.setEncoding("utf8");
|
|
544
|
-
child.stdout.on("data", (chunk) => {
|
|
545
|
-
stdout += chunk;
|
|
546
|
-
if (stdout.length > 4 * 1024 * 1024) {
|
|
547
|
-
child.kill();
|
|
548
|
-
finish(new Error(`${import_node_path5.default.basename(executable)} exceeded the output limit.`));
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
child.stderr.on("data", (chunk) => {
|
|
552
|
-
stderr += chunk;
|
|
553
|
-
if (stderr.length > 4 * 1024 * 1024) {
|
|
554
|
-
child.kill();
|
|
555
|
-
finish(new Error(`${import_node_path5.default.basename(executable)} exceeded the error-output limit.`));
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
child.once("error", (error) => finish(error));
|
|
559
|
-
child.once("close", (code, childSignal) => {
|
|
560
|
-
if (code !== 0) {
|
|
561
|
-
const detail = stderr.trim() || (childSignal ? `signal ${childSignal}` : `exit ${code}`);
|
|
562
|
-
finish(new Error(`${import_node_path5.default.basename(executable)} ${args.slice(0, 3).join(" ")} failed: ${detail}`));
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
finish(undefined, { stdout, stderr });
|
|
566
|
-
});
|
|
567
|
-
if (signal?.aborted)
|
|
568
|
-
abort();
|
|
569
|
-
else
|
|
570
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// src/probes.ts
|
|
575
|
-
var PYTHON_PROBE = `
|
|
576
|
-
import json
|
|
577
|
-
import os
|
|
578
|
-
import platform
|
|
579
|
-
import struct
|
|
580
|
-
import sys
|
|
581
|
-
import sysconfig
|
|
582
|
-
|
|
583
|
-
base_prefix = getattr(sys, "base_prefix", sys.prefix)
|
|
584
|
-
include_directory = sysconfig.get_path("include") or ""
|
|
585
|
-
header_path = os.path.join(include_directory, "Python.h") if include_directory else ""
|
|
586
|
-
library_candidates = []
|
|
587
|
-
stable_abi_library_candidates = []
|
|
588
|
-
if os.name == "nt":
|
|
589
|
-
library_directory = sysconfig.get_config_var("LIBDIR") or os.path.join(base_prefix, "libs")
|
|
590
|
-
configured_library = sysconfig.get_config_var("LDLIBRARY") or ""
|
|
591
|
-
if configured_library:
|
|
592
|
-
library_candidates.append(os.path.join(library_directory, os.path.splitext(configured_library)[0] + ".lib"))
|
|
593
|
-
library_candidates.append(os.path.join(library_directory, f"python{sys.version_info.major}{sys.version_info.minor}.lib"))
|
|
594
|
-
stable_abi_library_candidates.append(os.path.join(library_directory, "python3.lib"))
|
|
595
|
-
else:
|
|
596
|
-
library_directories = [
|
|
597
|
-
sysconfig.get_config_var("LIBDIR"),
|
|
598
|
-
sysconfig.get_config_var("LIBPL"),
|
|
599
|
-
os.path.join(base_prefix, "lib"),
|
|
600
|
-
]
|
|
601
|
-
library_names = [
|
|
602
|
-
sysconfig.get_config_var("LDLIBRARY"),
|
|
603
|
-
sysconfig.get_config_var("LIBRARY"),
|
|
604
|
-
]
|
|
605
|
-
for directory in library_directories:
|
|
606
|
-
for name in library_names:
|
|
607
|
-
if directory and name:
|
|
608
|
-
library_candidates.append(os.path.join(directory, name))
|
|
609
|
-
if directory:
|
|
610
|
-
stable_abi_library_candidates.extend([
|
|
611
|
-
os.path.join(directory, "libpython3.so"),
|
|
612
|
-
os.path.join(directory, "libpython3.dylib"),
|
|
613
|
-
])
|
|
614
|
-
|
|
615
|
-
library_path = next((candidate for candidate in library_candidates if os.path.isfile(candidate)), "")
|
|
616
|
-
stable_abi_library_path = next(
|
|
617
|
-
(candidate for candidate in stable_abi_library_candidates if os.path.isfile(candidate)),
|
|
618
|
-
None,
|
|
619
|
-
)
|
|
620
|
-
embedding = None
|
|
621
|
-
if os.path.isfile(header_path) and library_path:
|
|
622
|
-
library_name = os.path.basename(library_path)
|
|
623
|
-
library_kind = "import" if os.name == "nt" else ("static" if library_name.endswith(".a") else "shared")
|
|
624
|
-
embedding = {
|
|
625
|
-
"includeDirectory": include_directory,
|
|
626
|
-
"headerPath": header_path,
|
|
627
|
-
"libraryPath": library_path,
|
|
628
|
-
"libraryDirectory": os.path.dirname(library_path),
|
|
629
|
-
"libraryName": library_name,
|
|
630
|
-
"libraryKind": library_kind,
|
|
631
|
-
"stableAbiLibraryPath": stable_abi_library_path,
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
release_level = sys.version_info.releaselevel
|
|
635
|
-
release_suffixes = {
|
|
636
|
-
"alpha": "a",
|
|
637
|
-
"beta": "b",
|
|
638
|
-
"candidate": "rc",
|
|
639
|
-
"final": "",
|
|
640
|
-
}
|
|
641
|
-
release_suffix = release_suffixes[release_level]
|
|
642
|
-
if release_suffix:
|
|
643
|
-
release_suffix += str(sys.version_info.serial)
|
|
644
|
-
|
|
645
|
-
print(json.dumps({
|
|
646
|
-
"executable": sys.executable,
|
|
647
|
-
"baseExecutable": getattr(sys, "_base_executable", sys.executable),
|
|
648
|
-
"version": ".".join(map(str, sys.version_info[:3])) + release_suffix,
|
|
649
|
-
"major": sys.version_info.major,
|
|
650
|
-
"minor": sys.version_info.minor,
|
|
651
|
-
"patch": sys.version_info.micro,
|
|
652
|
-
"releaseLevel": release_level,
|
|
653
|
-
"implementation": sys.implementation.name,
|
|
654
|
-
"architecture": platform.machine().lower(),
|
|
655
|
-
"pointerBits": struct.calcsize("P") * 8,
|
|
656
|
-
"prefix": sys.prefix,
|
|
657
|
-
"basePrefix": base_prefix,
|
|
658
|
-
"virtualEnvironment": sys.prefix if sys.prefix != base_prefix else None,
|
|
659
|
-
"gilDisabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
|
|
660
|
-
"embedding": embedding,
|
|
661
|
-
}))
|
|
662
|
-
`;
|
|
663
|
-
function pythonSatisfiesPolicy(python, options) {
|
|
664
|
-
return pep440VersionSatisfies(python.version, options.requiresPython) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
|
|
665
|
-
}
|
|
666
|
-
async function probePython(candidate, options) {
|
|
667
|
-
const executable = await resolveExecutable(candidate.executable, options.env);
|
|
668
|
-
if (!executable)
|
|
669
|
-
return null;
|
|
670
|
-
try {
|
|
671
|
-
const { stdout } = run(executable, [...candidate.args, "-c", PYTHON_PROBE], options.env, options.probeTimeoutMs);
|
|
672
|
-
const info = JSON.parse(stdout.trim());
|
|
673
|
-
return normalizePep440Version(info.version) ? info : null;
|
|
674
|
-
} catch {
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
async function findUv(options) {
|
|
679
|
-
const configured = options.env["PYFIG_UV"] ?? "uv";
|
|
680
|
-
const executable = await resolveExecutable(configured, options.env);
|
|
681
|
-
const uv = executable ? probeUv(executable, options) : null;
|
|
682
|
-
return uv && pep440VersionSatisfies(uv.version, options.requiresUv) ? uv : null;
|
|
683
|
-
}
|
|
684
|
-
function probeUv(executable, options) {
|
|
685
|
-
try {
|
|
686
|
-
const { stdout } = run(executable, ["--version"], options.env, options.probeTimeoutMs);
|
|
687
|
-
const version = normalizeVersion(stdout.match(/^uv\s+(\S+)/iu)?.[1]);
|
|
688
|
-
return version ? { executable, version } : null;
|
|
689
|
-
} catch {
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
function normalizeVersion(value) {
|
|
694
|
-
if (!value)
|
|
695
|
-
return null;
|
|
696
|
-
return normalizePep440Version(value);
|
|
697
|
-
}
|
|
698
|
-
function pythonArchitectureMatchesRuntime(python) {
|
|
699
|
-
const architecture = python.architecture.toLowerCase();
|
|
700
|
-
if (python.pointerBits !== (process.arch === "ia32" ? 32 : 64))
|
|
701
|
-
return false;
|
|
702
|
-
if (process.arch === "x64")
|
|
703
|
-
return architecture === "x86_64" || architecture === "amd64";
|
|
704
|
-
if (process.arch === "arm64")
|
|
705
|
-
return architecture === "aarch64" || architecture === "arm64";
|
|
706
|
-
if (process.arch === "ia32") {
|
|
707
|
-
return architecture === "x86" || architecture === "i386" || architecture === "i686";
|
|
708
|
-
}
|
|
709
|
-
return architecture === process.arch;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// src/provision.ts
|
|
713
|
-
async function ensureUv(options = {}) {
|
|
714
|
-
return ensureUvResolved(resolveOptions(options));
|
|
715
|
-
}
|
|
716
|
-
async function ensurePython(options = {}) {
|
|
717
|
-
const resolved = resolveOptions(options);
|
|
718
|
-
await validateRequirementsFiles(resolved.requirements);
|
|
719
|
-
const uv = await ensureUvResolved(resolved);
|
|
720
|
-
if (!hasRuntimeRequirements(resolved)) {
|
|
721
|
-
const external = await findReusableExternalPythonVenv(resolved);
|
|
722
|
-
if (external) {
|
|
723
|
-
return { managed: false, python: external, uv, venvId: null };
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
const basePython = await ensureBasePythonResolved(uv, resolved);
|
|
727
|
-
const resolution = await resolveRuntimeDependencies(uv, basePython, resolved);
|
|
728
|
-
const venv = createVenvIdentity(basePython, resolution.sha256);
|
|
729
|
-
const python = await ensureManagedPythonVenv(uv, basePython, venv, resolution, resolved);
|
|
730
|
-
return { managed: true, python, uv, venvId: venv.id };
|
|
731
|
-
}
|
|
732
|
-
async function ensureManagedPythonVenv(uv, basePython, venv, resolution, options) {
|
|
733
|
-
const directory = pythonVenvDirectory(options.projectRoot, venv.directoryName);
|
|
734
|
-
const check = async () => {
|
|
735
|
-
if (!await venvMatches(directory, venv))
|
|
736
|
-
return null;
|
|
737
|
-
const candidate = await inspectPythonVenv(directory, options);
|
|
738
|
-
return candidate && pythonSatisfiesPolicy(candidate, options) ? candidate : null;
|
|
739
|
-
};
|
|
740
|
-
const existing = await check();
|
|
741
|
-
if (existing)
|
|
742
|
-
return existing;
|
|
743
|
-
if (!options.createVirtualEnvironment) {
|
|
744
|
-
throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
|
|
745
|
-
}
|
|
746
|
-
return withProvisioningLock({
|
|
747
|
-
key: `venv:${canonicalPath(directory)}`,
|
|
748
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
749
|
-
check,
|
|
750
|
-
mutate: async () => {
|
|
751
|
-
await import_promises2.rm(directory, { recursive: true, force: true });
|
|
752
|
-
await import_promises2.mkdir(import_node_path6.default.dirname(directory), { recursive: true });
|
|
753
|
-
try {
|
|
754
|
-
await runAsync(uv.executable, [
|
|
755
|
-
"venv",
|
|
756
|
-
"--no-project",
|
|
757
|
-
"--no-config",
|
|
758
|
-
"--no-python-downloads",
|
|
759
|
-
"--python",
|
|
760
|
-
basePython.executable,
|
|
761
|
-
directory
|
|
762
|
-
], isolatedUvProcessEnvironment(options), options.installTimeoutMs);
|
|
763
|
-
const created = await inspectPythonVenv(directory, options);
|
|
764
|
-
if (!created || !pythonSatisfiesPolicy(created, options)) {
|
|
765
|
-
throw new Error(`uv created ${directory}, but its Python does not satisfy the requested capabilities.`);
|
|
766
|
-
}
|
|
767
|
-
await installResolvedDependencies(uv, created, resolution, options);
|
|
768
|
-
await writeVenvMetadata(directory, venv);
|
|
769
|
-
const installed = await check();
|
|
770
|
-
if (!installed)
|
|
771
|
-
throw new Error(`Pyfig could not validate environment ${directory}.`);
|
|
772
|
-
return installed;
|
|
773
|
-
} catch (error) {
|
|
774
|
-
await import_promises2.rm(directory, { recursive: true, force: true });
|
|
775
|
-
throw error;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
async function findReusableExternalPythonVenv(options) {
|
|
781
|
-
if (options.python) {
|
|
782
|
-
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
783
|
-
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
784
|
-
throw incompatibleConfiguredPython(options);
|
|
785
|
-
}
|
|
786
|
-
if (configured.virtualEnvironment)
|
|
787
|
-
return configured;
|
|
788
|
-
}
|
|
789
|
-
for (const directory of externalPythonVenvDirectories(options.projectRoot, options.env)) {
|
|
790
|
-
const info = await inspectPythonVenv(directory, options);
|
|
791
|
-
if (info && pythonSatisfiesPolicy(info, options))
|
|
792
|
-
return info;
|
|
793
|
-
}
|
|
794
|
-
return null;
|
|
795
|
-
}
|
|
796
|
-
async function ensureBasePythonResolved(uv, options) {
|
|
797
|
-
if (options.python) {
|
|
798
|
-
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
799
|
-
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
800
|
-
throw incompatibleConfiguredPython(options);
|
|
801
|
-
}
|
|
802
|
-
if (!configured.virtualEnvironment)
|
|
803
|
-
return configured;
|
|
804
|
-
const base = await probePython({ executable: configured.baseExecutable, args: [] }, options);
|
|
805
|
-
if (base && pythonSatisfiesPolicy(base, options))
|
|
806
|
-
return base;
|
|
807
|
-
throw incompatibleConfiguredPython(options);
|
|
808
|
-
}
|
|
809
|
-
const externalBase = await findExternalBasePython(options);
|
|
810
|
-
if (externalBase)
|
|
811
|
-
return externalBase;
|
|
812
|
-
const system = await findPythonWithUv(uv, options, "system");
|
|
813
|
-
if (system)
|
|
814
|
-
return system;
|
|
815
|
-
const cachedManaged = await findPythonWithUv(uv, options, "managed");
|
|
816
|
-
if (cachedManaged)
|
|
817
|
-
return cachedManaged;
|
|
818
|
-
if (!options.installPython) {
|
|
819
|
-
throw new Error(missingPythonMessage(options, "managed Python installation is disabled"));
|
|
820
|
-
}
|
|
821
|
-
return withProvisioningLock({
|
|
822
|
-
key: `python:${canonicalPath(import_node_path6.default.join(options.cacheDir, "python"))}`,
|
|
823
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
824
|
-
check: () => findPythonWithUv(uv, options, "managed"),
|
|
825
|
-
mutate: async () => {
|
|
826
|
-
await runAsync(uv.executable, ["python", "install", "--no-config", options.managedPython], isolatedUvProcessEnvironment(options), options.installTimeoutMs);
|
|
827
|
-
const installed = await findPythonWithUv(uv, options, "managed");
|
|
828
|
-
if (!installed) {
|
|
829
|
-
throw new Error(missingPythonMessage(options, `uv installed ${options.managedPython}, but it remains incompatible`));
|
|
830
|
-
}
|
|
831
|
-
return installed;
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
async function findExternalBasePython(options) {
|
|
836
|
-
for (const directory of externalPythonVenvDirectories(options.projectRoot, options.env)) {
|
|
837
|
-
const environment = await inspectPythonVenv(directory, options);
|
|
838
|
-
if (!environment || !pythonSatisfiesPolicy(environment, options))
|
|
839
|
-
continue;
|
|
840
|
-
const base = await probePython({ executable: environment.baseExecutable, args: [] }, options);
|
|
841
|
-
if (base && !base.virtualEnvironment && pythonSatisfiesPolicy(base, options))
|
|
842
|
-
return base;
|
|
843
|
-
}
|
|
844
|
-
return null;
|
|
845
|
-
}
|
|
846
|
-
async function findPythonWithUv(uv, options, source) {
|
|
847
|
-
for (const request of ["", options.managedPython]) {
|
|
848
|
-
const args = [
|
|
849
|
-
"python",
|
|
850
|
-
"find",
|
|
851
|
-
"--no-project",
|
|
852
|
-
"--no-config",
|
|
853
|
-
"--no-python-downloads",
|
|
854
|
-
source === "system" ? "--system" : "--managed-python"
|
|
855
|
-
];
|
|
856
|
-
if (request)
|
|
857
|
-
args.push(request);
|
|
858
|
-
try {
|
|
859
|
-
const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceProcessEnvironment(options), options.probeTimeoutMs);
|
|
860
|
-
const info = await probePython({ executable: stdout.trim(), args: [] }, options);
|
|
861
|
-
if (info && pythonSatisfiesPolicy(info, options))
|
|
862
|
-
return info;
|
|
863
|
-
} catch {}
|
|
864
|
-
}
|
|
865
|
-
try {
|
|
866
|
-
const { stdout } = await runAsync(uv.executable, [
|
|
867
|
-
"python",
|
|
868
|
-
"list",
|
|
869
|
-
"--no-config",
|
|
870
|
-
"--no-python-downloads",
|
|
871
|
-
"--only-installed",
|
|
872
|
-
"--output-format",
|
|
873
|
-
"json",
|
|
874
|
-
source === "system" ? "--no-managed-python" : "--managed-python"
|
|
875
|
-
], explicitPythonSourceProcessEnvironment(options), options.probeTimeoutMs);
|
|
876
|
-
const candidates = installedPythonPaths(stdout);
|
|
877
|
-
for (const executable of candidates) {
|
|
878
|
-
const info = await probePython({ executable, args: [] }, options);
|
|
879
|
-
if (info && !info.virtualEnvironment && pythonSatisfiesPolicy(info, options)) {
|
|
880
|
-
return info;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
} catch {}
|
|
884
|
-
return null;
|
|
885
|
-
}
|
|
886
|
-
function installedPythonPaths(output) {
|
|
887
|
-
const parsed = JSON.parse(output);
|
|
888
|
-
if (!Array.isArray(parsed))
|
|
889
|
-
return [];
|
|
890
|
-
const paths = parsed.flatMap((entry) => {
|
|
891
|
-
if (!entry || typeof entry !== "object" || !("path" in entry))
|
|
892
|
-
return [];
|
|
893
|
-
const candidate = entry.path;
|
|
894
|
-
return typeof candidate === "string" && candidate ? [candidate] : [];
|
|
895
|
-
});
|
|
896
|
-
return [...new Map(paths.map((candidate) => [canonicalPath(candidate), candidate])).values()];
|
|
897
|
-
}
|
|
898
|
-
async function inspectPythonVenv(directory, options) {
|
|
899
|
-
const executable = pythonVenvExecutable(directory);
|
|
900
|
-
const info = await probePython({ executable, args: [] }, options);
|
|
901
|
-
return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
|
|
902
|
-
}
|
|
903
|
-
async function resolveRuntimeDependencies(uv, python, options) {
|
|
904
|
-
if (!hasRuntimeRequirements(options)) {
|
|
905
|
-
return { content: "", sha256: requirementsDigest("") };
|
|
906
|
-
}
|
|
907
|
-
const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-resolution-"));
|
|
908
|
-
try {
|
|
909
|
-
const additions = import_node_path6.default.join(temporary, "runtime-dependencies.txt");
|
|
910
|
-
const output = import_node_path6.default.join(temporary, "resolved-dependencies.txt");
|
|
911
|
-
const sources = [...options.requirements];
|
|
912
|
-
if (options.dependencies.length > 0) {
|
|
913
|
-
await import_promises2.writeFile(additions, `${options.dependencies.join(`
|
|
914
|
-
`)}
|
|
915
|
-
`, "utf8");
|
|
916
|
-
sources.push(additions);
|
|
917
|
-
}
|
|
918
|
-
await runAsync(uv.executable, [
|
|
919
|
-
"pip",
|
|
920
|
-
"compile",
|
|
921
|
-
"--no-config",
|
|
922
|
-
"--no-header",
|
|
923
|
-
"--no-annotate",
|
|
924
|
-
"--generate-hashes",
|
|
925
|
-
"--python",
|
|
926
|
-
python.executable,
|
|
927
|
-
...sources,
|
|
928
|
-
"--output-file",
|
|
929
|
-
output
|
|
930
|
-
], isolatedUvProcessEnvironment(options), options.installTimeoutMs, undefined, options.projectRoot);
|
|
931
|
-
const content = normalizeResolution(await import_promises2.readFile(output, "utf8"));
|
|
932
|
-
return { content, sha256: requirementsDigest(content) };
|
|
933
|
-
} finally {
|
|
934
|
-
await import_promises2.rm(temporary, { recursive: true, force: true });
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
async function validateRequirementsFiles(requirements) {
|
|
938
|
-
for (const requirement of requirements) {
|
|
939
|
-
let information;
|
|
940
|
-
try {
|
|
941
|
-
information = await import_promises2.stat(requirement);
|
|
942
|
-
} catch (error) {
|
|
943
|
-
throw new Error(`Requirements file is unavailable: ${requirement}`, { cause: error });
|
|
944
|
-
}
|
|
945
|
-
if (!information.isFile()) {
|
|
946
|
-
throw new Error(`Requirements path is not a file: ${requirement}`);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
async function installResolvedDependencies(uv, python, resolution, options) {
|
|
951
|
-
if (!resolution.content)
|
|
952
|
-
return;
|
|
953
|
-
if (!python.virtualEnvironment) {
|
|
954
|
-
throw new Error("Runtime dependency installation requires a virtual environment.");
|
|
955
|
-
}
|
|
956
|
-
const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-install-"));
|
|
957
|
-
try {
|
|
958
|
-
const requirements = import_node_path6.default.join(temporary, "resolved-dependencies.txt");
|
|
959
|
-
await import_promises2.writeFile(requirements, resolution.content, { encoding: "utf8", flag: "wx" });
|
|
960
|
-
await runAsync(uv.executable, ["pip", "install", "--no-config", "--python", python.executable, "-r", requirements], targetUvProcessEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
|
|
961
|
-
await runAsync(uv.executable, ["pip", "check", "--no-config", "--python", python.executable], targetUvProcessEnvironment(options, python), options.installTimeoutMs, undefined, options.projectRoot);
|
|
962
|
-
} finally {
|
|
963
|
-
await import_promises2.rm(temporary, { recursive: true, force: true });
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
function normalizeResolution(content) {
|
|
967
|
-
const normalized = content.replace(/\r\n/gu, `
|
|
968
|
-
`).trimEnd();
|
|
969
|
-
return normalized ? `${normalized}
|
|
970
|
-
` : "";
|
|
971
|
-
}
|
|
972
|
-
async function ensureUvResolved(options) {
|
|
973
|
-
const existing = await findAvailableUv(options);
|
|
974
|
-
if (existing)
|
|
975
|
-
return existing;
|
|
976
|
-
if (!options.installUv) {
|
|
977
|
-
throw new Error(`Compatible uv is unavailable (requires-uv ${options.requiresUv}), and uv installation is disabled.`);
|
|
978
|
-
}
|
|
979
|
-
const policyKey = import_node_crypto2.createHash("sha256").update(options.requiresUv).digest("hex");
|
|
980
|
-
return withProvisioningLock({
|
|
981
|
-
key: `uv-policy:${canonicalPath(uvInstallRoot(options))}:${policyKey}`,
|
|
982
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
983
|
-
check: () => findAvailableUv(options),
|
|
984
|
-
mutate: async () => {
|
|
985
|
-
const release = await resolveUvRelease(options);
|
|
986
|
-
const installDir = uvInstallDirectory(options, release.version);
|
|
987
|
-
const executable = uvExecutable(options, release.version);
|
|
988
|
-
return withProvisioningLock({
|
|
989
|
-
key: `uv-release:${canonicalPath(installDir)}`,
|
|
990
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
991
|
-
check: () => findAvailableUv(options),
|
|
992
|
-
mutate: async () => {
|
|
993
|
-
await installUv(installDir, release, options);
|
|
994
|
-
const result = probeUv(executable, options);
|
|
995
|
-
if (!result || result.version !== release.version) {
|
|
996
|
-
throw new Error(`Expected managed uv ${release.version}, but the installed executable did not match.`);
|
|
997
|
-
}
|
|
998
|
-
return result;
|
|
999
|
-
}
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
async function findAvailableUv(options) {
|
|
1005
|
-
const external = await findUv(options);
|
|
1006
|
-
if (external)
|
|
1007
|
-
return external;
|
|
1008
|
-
return findManagedUv(options);
|
|
1009
|
-
}
|
|
1010
|
-
async function findManagedUv(options) {
|
|
1011
|
-
const entries = await import_promises2.readdir(uvInstallRoot(options), { withFileTypes: true }).catch(() => []);
|
|
1012
|
-
let versions = entries.filter((entry) => entry.isDirectory() && normalizePep440Version(entry.name) === entry.name).map((entry) => entry.name);
|
|
1013
|
-
while (versions.length > 0) {
|
|
1014
|
-
const selected = maxPep440Version(versions, options.requiresUv);
|
|
1015
|
-
if (!selected)
|
|
1016
|
-
return null;
|
|
1017
|
-
const candidate = probeUv(uvExecutable(options, selected), options);
|
|
1018
|
-
if (candidate?.version === selected)
|
|
1019
|
-
return candidate;
|
|
1020
|
-
versions = versions.filter((version) => version !== selected);
|
|
1021
|
-
}
|
|
1022
|
-
return null;
|
|
1023
|
-
}
|
|
1024
|
-
function isolatedUvProcessEnvironment(options) {
|
|
1025
|
-
return sanitizeUvProcessEnvironment(uvProcessEnvironment(options));
|
|
1026
|
-
}
|
|
1027
|
-
function explicitPythonSourceProcessEnvironment(options) {
|
|
1028
|
-
const env = isolatedUvProcessEnvironment(options);
|
|
1029
|
-
delete env["UV_PYTHON_PREFERENCE"];
|
|
1030
|
-
return env;
|
|
1031
|
-
}
|
|
1032
|
-
function targetUvProcessEnvironment(options, python) {
|
|
1033
|
-
return uvCommandProcessEnvironment(uvProcessEnvironment(options), python);
|
|
1034
|
-
}
|
|
1035
|
-
function incompatibleConfiguredPython(options) {
|
|
1036
|
-
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
|
|
1037
|
-
}
|
|
1038
|
-
function missingPythonMessage(options, reason) {
|
|
1039
|
-
const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
|
|
1040
|
-
return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
|
|
1041
|
-
}
|
|
1042
|
-
var UV_RELEASE_PAGE_SIZE = 100;
|
|
1043
|
-
var UV_RELEASE_MAX_PAGES = 20;
|
|
1044
|
-
var UV_RELEASE_METADATA_TTL_MS = 24 * 60 * 60000;
|
|
1045
|
-
async function resolveUvRelease(options) {
|
|
1046
|
-
const catalog = await ensureUvReleaseCatalog(options);
|
|
1047
|
-
const asset = uvAsset();
|
|
1048
|
-
const releases = catalog.releases.filter((release) => release.assets.includes(asset) && release.assets.includes(`${asset}.sha256`));
|
|
1049
|
-
const version = maxPep440Version(releases.map((release) => release.version), options.requiresUv);
|
|
1050
|
-
const release = releases.find((candidate) => candidate.version === version);
|
|
1051
|
-
if (!release) {
|
|
1052
|
-
throw new Error(`No published uv release satisfies requires-uv ${options.requiresUv}.`);
|
|
1053
|
-
}
|
|
1054
|
-
return release;
|
|
1055
|
-
}
|
|
1056
|
-
async function ensureUvReleaseCatalog(options) {
|
|
1057
|
-
const file = uvReleaseMetadataFile(options);
|
|
1058
|
-
const stale = await readUvReleaseCatalog(file);
|
|
1059
|
-
try {
|
|
1060
|
-
return await withProvisioningLock({
|
|
1061
|
-
key: `uv-metadata:${canonicalPath(file)}`,
|
|
1062
|
-
timeoutMs: options.coordinationTimeoutMs,
|
|
1063
|
-
check: () => readFreshUvReleaseCatalog(file),
|
|
1064
|
-
mutate: async () => {
|
|
1065
|
-
const catalog = await fetchUvReleaseCatalog(options);
|
|
1066
|
-
await writeUvReleaseCatalog(file, catalog);
|
|
1067
|
-
return catalog;
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
} catch (error) {
|
|
1071
|
-
const fallback = await readUvReleaseCatalog(file) ?? stale;
|
|
1072
|
-
if (fallback)
|
|
1073
|
-
return fallback;
|
|
1074
|
-
throw error;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
async function fetchUvReleaseCatalog(options) {
|
|
1078
|
-
const dispatcher = proxyDispatcher(options.env);
|
|
1079
|
-
const releases = new Map;
|
|
1080
|
-
let exhausted = false;
|
|
1081
|
-
try {
|
|
1082
|
-
for (let page = 1;page <= UV_RELEASE_MAX_PAGES; page += 1) {
|
|
1083
|
-
const text = await downloadText(`https://api.github.com/repos/astral-sh/uv/releases?per_page=${UV_RELEASE_PAGE_SIZE}&page=${page}`, options.downloadTimeoutMs, dispatcher, githubApiHeaders(options.env));
|
|
1084
|
-
const payload = JSON.parse(text);
|
|
1085
|
-
if (!Array.isArray(payload)) {
|
|
1086
|
-
throw new Error("GitHub returned invalid uv release metadata.");
|
|
1087
|
-
}
|
|
1088
|
-
for (const candidate of payload) {
|
|
1089
|
-
if (!candidate || typeof candidate !== "object")
|
|
1090
|
-
continue;
|
|
1091
|
-
const release = candidate;
|
|
1092
|
-
if (release.draft === true || typeof release.tag_name !== "string")
|
|
1093
|
-
continue;
|
|
1094
|
-
const assetNames = releaseAssetNames(release.assets);
|
|
1095
|
-
const version = normalizePep440Version(release.tag_name);
|
|
1096
|
-
if (version) {
|
|
1097
|
-
releases.set(version, { assets: assetNames, tag: release.tag_name, version });
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
if (payload.length < UV_RELEASE_PAGE_SIZE) {
|
|
1101
|
-
exhausted = true;
|
|
1102
|
-
break;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
} finally {
|
|
1106
|
-
await dispatcher?.close();
|
|
1107
|
-
}
|
|
1108
|
-
if (!exhausted) {
|
|
1109
|
-
throw new Error(`uv release catalog exceeded ${UV_RELEASE_MAX_PAGES} pages.`);
|
|
1110
|
-
}
|
|
1111
|
-
if (releases.size === 0) {
|
|
1112
|
-
throw new Error("GitHub returned no usable uv releases.");
|
|
1113
|
-
}
|
|
1114
|
-
return { fetchedAt: Date.now(), releases: [...releases.values()] };
|
|
1115
|
-
}
|
|
1116
|
-
function githubApiHeaders(env) {
|
|
1117
|
-
const token = [env["GITHUB_TOKEN"], env["GH_TOKEN"]].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
1118
|
-
if (token && /[\r\n]/u.test(token)) {
|
|
1119
|
-
throw new TypeError("GitHub API token must not contain line breaks.");
|
|
1120
|
-
}
|
|
1121
|
-
return {
|
|
1122
|
-
Accept: "application/vnd.github+json",
|
|
1123
|
-
"User-Agent": "pyfig",
|
|
1124
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
1125
|
-
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1128
|
-
async function readFreshUvReleaseCatalog(file) {
|
|
1129
|
-
const catalog = await readUvReleaseCatalog(file);
|
|
1130
|
-
if (!catalog)
|
|
1131
|
-
return null;
|
|
1132
|
-
const age = Date.now() - catalog.fetchedAt;
|
|
1133
|
-
return age >= 0 && age < UV_RELEASE_METADATA_TTL_MS ? catalog : null;
|
|
1134
|
-
}
|
|
1135
|
-
async function readUvReleaseCatalog(file) {
|
|
1136
|
-
try {
|
|
1137
|
-
return parseUvReleaseCatalog(JSON.parse(await import_promises2.readFile(file, "utf8")));
|
|
1138
|
-
} catch {
|
|
1139
|
-
return null;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
function parseUvReleaseCatalog(value) {
|
|
1143
|
-
if (!value || typeof value !== "object")
|
|
1144
|
-
return null;
|
|
1145
|
-
const catalog = value;
|
|
1146
|
-
if (!Number.isSafeInteger(catalog.fetchedAt) || catalog.fetchedAt < 0 || !Array.isArray(catalog.releases)) {
|
|
1147
|
-
return null;
|
|
1148
|
-
}
|
|
1149
|
-
const releases = [];
|
|
1150
|
-
for (const value of catalog.releases) {
|
|
1151
|
-
if (!value || typeof value !== "object")
|
|
1152
|
-
return null;
|
|
1153
|
-
const release = value;
|
|
1154
|
-
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" || normalizePep440Version(release.version) !== release.version) {
|
|
1155
|
-
return null;
|
|
1156
|
-
}
|
|
1157
|
-
releases.push({
|
|
1158
|
-
assets: release.assets,
|
|
1159
|
-
tag: release.tag,
|
|
1160
|
-
version: release.version
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
return { fetchedAt: catalog.fetchedAt, releases };
|
|
1164
|
-
}
|
|
1165
|
-
async function writeUvReleaseCatalog(file, catalog) {
|
|
1166
|
-
await import_promises2.mkdir(import_node_path6.default.dirname(file), { recursive: true });
|
|
1167
|
-
const staged = import_node_path6.default.join(import_node_path6.default.dirname(file), `.${import_node_path6.default.basename(file)}.${process.pid}.${import_node_crypto2.randomUUID()}.tmp`);
|
|
1168
|
-
try {
|
|
1169
|
-
await import_promises2.writeFile(staged, `${JSON.stringify(catalog)}
|
|
1170
|
-
`, { encoding: "utf8", flag: "wx" });
|
|
1171
|
-
if (process.platform === "win32")
|
|
1172
|
-
await import_promises2.rm(file, { force: true });
|
|
1173
|
-
await import_promises2.rename(staged, file);
|
|
1174
|
-
} finally {
|
|
1175
|
-
await import_promises2.rm(staged, { force: true });
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
function uvReleaseMetadataFile(options) {
|
|
1179
|
-
return import_node_path6.default.join(options.cacheDir, "metadata", "uv-releases.json");
|
|
1180
|
-
}
|
|
1181
|
-
function releaseAssetNames(value) {
|
|
1182
|
-
if (!Array.isArray(value))
|
|
1183
|
-
return [];
|
|
1184
|
-
return value.flatMap((entry) => {
|
|
1185
|
-
if (!entry || typeof entry !== "object")
|
|
1186
|
-
return [];
|
|
1187
|
-
const name = entry.name;
|
|
1188
|
-
return typeof name === "string" ? [name] : [];
|
|
1189
|
-
});
|
|
1190
|
-
}
|
|
1191
|
-
function uvInstallRoot(options) {
|
|
1192
|
-
return import_node_path6.default.join(options.cacheDir, "tools", "uv");
|
|
1193
|
-
}
|
|
1194
|
-
function uvInstallDirectory(options, version) {
|
|
1195
|
-
return import_node_path6.default.join(uvInstallRoot(options), version);
|
|
1196
|
-
}
|
|
1197
|
-
function uvExecutable(options, version) {
|
|
1198
|
-
return import_node_path6.default.join(uvInstallDirectory(options, version), process.platform === "win32" ? "uv.exe" : "uv");
|
|
1199
|
-
}
|
|
1200
|
-
async function installUv(installDir, release, options) {
|
|
1201
|
-
const asset = uvAsset();
|
|
1202
|
-
const baseUrl = `https://github.com/astral-sh/uv/releases/download/${release.tag}`;
|
|
1203
|
-
const temporary = await import_promises2.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
|
|
1204
|
-
const dispatcher = proxyDispatcher(options.env);
|
|
1205
|
-
try {
|
|
1206
|
-
const archive = import_node_path6.default.join(temporary, asset);
|
|
1207
|
-
await download(`${baseUrl}/${asset}`, archive, options.downloadTimeoutMs, dispatcher);
|
|
1208
|
-
const expected = (await downloadText(`${baseUrl}/${asset}.sha256`, options.downloadTimeoutMs, dispatcher)).trim().split(/\s+/u)[0];
|
|
1209
|
-
const actual = await sha256File(archive);
|
|
1210
|
-
if (!expected || expected.toLowerCase() !== actual.toLowerCase()) {
|
|
1211
|
-
throw new Error(`uv archive checksum mismatch for ${asset}`);
|
|
1212
|
-
}
|
|
1213
|
-
const extracted = import_node_path6.default.join(temporary, "extracted");
|
|
1214
|
-
await import_promises2.mkdir(extracted, { recursive: true });
|
|
1215
|
-
const source = await extractUvExecutable(archive, asset, extracted);
|
|
1216
|
-
if (!source)
|
|
1217
|
-
throw new Error(`uv archive did not contain the expected executable: ${asset}`);
|
|
1218
|
-
await publishUvAtomically(source, installDir, release.version, options);
|
|
1219
|
-
} finally {
|
|
1220
|
-
await dispatcher?.close();
|
|
1221
|
-
await import_promises2.rm(temporary, { recursive: true, force: true });
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
async function extractUvExecutable(archive, asset, destination) {
|
|
1225
|
-
const executableName = process.platform === "win32" ? "uv.exe" : "uv";
|
|
1226
|
-
if (!asset.endsWith(".zip")) {
|
|
1227
|
-
await tar.x({ file: archive, cwd: destination, strict: true });
|
|
1228
|
-
return findFile(destination, executableName);
|
|
1229
|
-
}
|
|
1230
|
-
const entries = import_fflate.unzipSync(await import_promises2.readFile(archive), {
|
|
1231
|
-
filter: ({ name }) => import_node_path6.default.posix.basename(name.replaceAll("\\", "/")) === executableName
|
|
1232
|
-
});
|
|
1233
|
-
const matches = Object.entries(entries).filter(([name]) => import_node_path6.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
|
|
1234
|
-
if (matches.length !== 1 || !matches[0])
|
|
1235
|
-
return null;
|
|
1236
|
-
const source = import_node_path6.default.join(destination, executableName);
|
|
1237
|
-
await import_promises2.writeFile(source, matches[0][1], { flag: "wx" });
|
|
1238
|
-
return source;
|
|
1239
|
-
}
|
|
1240
|
-
async function publishUvAtomically(source, installDir, version, options) {
|
|
1241
|
-
await import_promises2.mkdir(installDir, { recursive: true });
|
|
1242
|
-
const executable = uvExecutable(options, version);
|
|
1243
|
-
const staged = import_node_path6.default.join(installDir, `.${import_node_path6.default.basename(executable)}.${process.pid}.${import_node_crypto2.randomUUID()}.tmp`);
|
|
1244
|
-
try {
|
|
1245
|
-
await import_promises2.copyFile(source, staged);
|
|
1246
|
-
if (process.platform !== "win32")
|
|
1247
|
-
await import_promises2.chmod(staged, 493);
|
|
1248
|
-
const stagedUv = probeUv(staged, options);
|
|
1249
|
-
if (!stagedUv || stagedUv.version !== version) {
|
|
1250
|
-
throw new Error(`Downloaded uv executable is not version ${version}.`);
|
|
1251
|
-
}
|
|
1252
|
-
if (process.platform === "win32")
|
|
1253
|
-
await import_promises2.rm(executable, { force: true });
|
|
1254
|
-
await import_promises2.rename(staged, executable);
|
|
1255
|
-
} finally {
|
|
1256
|
-
await import_promises2.rm(staged, { force: true });
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
function uvAsset() {
|
|
1260
|
-
const architecture = process.arch === "x64" ? "x86_64" : process.arch === "arm64" ? "aarch64" : null;
|
|
1261
|
-
if (!architecture)
|
|
1262
|
-
throw new Error(`uv bootstrap does not support architecture ${process.arch}.`);
|
|
1263
|
-
if (process.platform === "win32")
|
|
1264
|
-
return `uv-${architecture}-pc-windows-msvc.zip`;
|
|
1265
|
-
if (process.platform === "darwin")
|
|
1266
|
-
return `uv-${architecture}-apple-darwin.tar.gz`;
|
|
1267
|
-
if (process.platform === "linux")
|
|
1268
|
-
return `uv-${architecture}-unknown-linux-musl.tar.gz`;
|
|
1269
|
-
throw new Error(`uv bootstrap does not support platform ${process.platform}.`);
|
|
1270
|
-
}
|
|
1271
|
-
async function download(url, destination, timeoutMs, dispatcher) {
|
|
1272
|
-
const response = await import_undici.fetch(url, {
|
|
1273
|
-
dispatcher,
|
|
1274
|
-
redirect: "follow",
|
|
1275
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
1276
|
-
});
|
|
1277
|
-
if (!response.ok || !response.body) {
|
|
1278
|
-
await response.body?.cancel();
|
|
1279
|
-
throw new Error(`Could not download ${url}: HTTP ${response.status}`);
|
|
1280
|
-
}
|
|
1281
|
-
await import_promises3.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs3.createWriteStream(destination, { flags: "wx" }));
|
|
1282
|
-
}
|
|
1283
|
-
async function downloadText(url, timeoutMs, dispatcher, headers) {
|
|
1284
|
-
const response = await import_undici.fetch(url, {
|
|
1285
|
-
dispatcher,
|
|
1286
|
-
headers,
|
|
1287
|
-
redirect: "follow",
|
|
1288
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
1289
|
-
});
|
|
1290
|
-
if (!response.ok) {
|
|
1291
|
-
await response.body?.cancel();
|
|
1292
|
-
throw new Error(`Could not download ${url}: HTTP ${response.status}`);
|
|
1293
|
-
}
|
|
1294
|
-
return response.text();
|
|
1295
|
-
}
|
|
1296
|
-
function proxyDispatcher(env) {
|
|
1297
|
-
const httpProxy = processEnvironmentValue(env, "http_proxy", "HTTP_PROXY");
|
|
1298
|
-
const httpsProxy = processEnvironmentValue(env, "https_proxy", "HTTPS_PROXY");
|
|
1299
|
-
if (!httpProxy && !httpsProxy)
|
|
1300
|
-
return;
|
|
1301
|
-
return new import_undici.EnvHttpProxyAgent({
|
|
1302
|
-
httpProxy,
|
|
1303
|
-
httpsProxy,
|
|
1304
|
-
noProxy: processEnvironmentValue(env, "no_proxy", "NO_PROXY")
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
function processEnvironmentValue(env, lowercase, uppercase) {
|
|
1308
|
-
return env[lowercase] ?? env[uppercase];
|
|
1309
|
-
}
|
|
1310
|
-
async function sha256File(file) {
|
|
1311
|
-
const hash = import_node_crypto2.createHash("sha256");
|
|
1312
|
-
for await (const chunk of import_node_fs3.createReadStream(file))
|
|
1313
|
-
hash.update(chunk);
|
|
1314
|
-
return hash.digest("hex");
|
|
1315
|
-
}
|
|
1316
|
-
async function findFile(directory, name) {
|
|
1317
|
-
for (const entry of await import_promises2.readdir(directory, { withFileTypes: true })) {
|
|
1318
|
-
const target = import_node_path6.default.join(directory, entry.name);
|
|
1319
|
-
if (entry.isFile() && entry.name === name)
|
|
1320
|
-
return target;
|
|
1321
|
-
if (entry.isDirectory()) {
|
|
1322
|
-
const nested = await findFile(target, name);
|
|
1323
|
-
if (nested)
|
|
1324
|
-
return nested;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
return null;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// src/sync-resolver.ts
|
|
1331
|
-
async function main() {
|
|
1332
|
-
const request = JSON.parse(import_node_fs4.readFileSync(0, "utf8"));
|
|
1333
|
-
const result = request.target === "uv" ? await ensureUv(request.options) : await ensurePython(request.options);
|
|
1334
|
-
process.stdout.write(JSON.stringify(result));
|
|
1335
|
-
}
|
|
1336
|
-
main().catch((error) => {
|
|
1337
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1338
|
-
process.stderr.write(message);
|
|
1339
|
-
process.exitCode = 1;
|
|
1340
|
-
});
|
|
1341
|
-
|
|
1342
|
-
//# debugId=0726BCDBF58AF5C064756E2164756E21
|