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