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