pyfig 0.4.0 → 0.6.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 +19 -16
- package/SECURITY.md +2 -2
- package/dist/cjs/index.js +282 -97
- package/dist/cjs/index.js.map +8 -7
- package/dist/cjs/sync-resolver.js +279 -93
- package/dist/cjs/sync-resolver.js.map +8 -7
- package/dist/cli.js +4 -6
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +2 -2
- package/dist/config.d.ts +10 -14
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -29
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pep440.d.ts +12 -0
- package/dist/pep440.d.ts.map +1 -0
- package/dist/pep440.js +21 -0
- package/dist/pep440.js.map +1 -0
- package/dist/probes.d.ts +1 -1
- package/dist/probes.d.ts.map +1 -1
- package/dist/probes.js +15 -7
- package/dist/probes.js.map +1 -1
- package/dist/provision.d.ts +2 -2
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +254 -58
- package/dist/provision.js.map +1 -1
- package/package.json +7 -6
package/dist/cjs/index.js
CHANGED
|
@@ -66,9 +66,8 @@ var __export = (target, all) => {
|
|
|
66
66
|
var exports_src = {};
|
|
67
67
|
__export(exports_src, {
|
|
68
68
|
DEFAULT_MANAGED_PYTHON: () => DEFAULT_MANAGED_PYTHON,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
DEFAULT_UV_VERSION: () => DEFAULT_UV_VERSION,
|
|
69
|
+
DEFAULT_REQUIRES_PYTHON: () => DEFAULT_REQUIRES_PYTHON,
|
|
70
|
+
DEFAULT_REQUIRES_UV: () => DEFAULT_REQUIRES_UV,
|
|
72
71
|
createPyfig: () => createPyfig,
|
|
73
72
|
ensureEnvironment: () => ensureEnvironment,
|
|
74
73
|
ensureUv: () => ensureUv,
|
|
@@ -81,7 +80,21 @@ module.exports = __toCommonJS(exports_src);
|
|
|
81
80
|
// src/config.ts
|
|
82
81
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
83
82
|
var envPaths = __toESM(require("env-paths"), 1);
|
|
84
|
-
|
|
83
|
+
|
|
84
|
+
// src/pep440.ts
|
|
85
|
+
var import_pep440 = require("@renovatebot/pep440");
|
|
86
|
+
function isValidPep440Specifier(specifier) {
|
|
87
|
+
return import_pep440.validRange(specifier);
|
|
88
|
+
}
|
|
89
|
+
function normalizePep440Version(version) {
|
|
90
|
+
return import_pep440.valid(version);
|
|
91
|
+
}
|
|
92
|
+
function pep440VersionSatisfies(version, specifier) {
|
|
93
|
+
return import_pep440.filter([version], specifier).length === 1;
|
|
94
|
+
}
|
|
95
|
+
function maxPep440Version(versions, specifier) {
|
|
96
|
+
return import_pep440.maxSatisfying([...versions], specifier);
|
|
97
|
+
}
|
|
85
98
|
|
|
86
99
|
// src/project-root.ts
|
|
87
100
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
@@ -106,10 +119,9 @@ function* ancestors(startDirectory) {
|
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
// src/config.ts
|
|
109
|
-
var
|
|
122
|
+
var DEFAULT_REQUIRES_PYTHON = ">=3.10,<4";
|
|
110
123
|
var DEFAULT_MANAGED_PYTHON = "3.12";
|
|
111
|
-
var
|
|
112
|
-
var DEFAULT_UV_RANGE = ">=0.10.0 <1.0.0";
|
|
124
|
+
var DEFAULT_REQUIRES_UV = ">=0.10,<1";
|
|
113
125
|
var DEFAULT_PROBE_TIMEOUT_MS = 1e4;
|
|
114
126
|
var DEFAULT_INSTALL_TIMEOUT_MS = 10 * 60000;
|
|
115
127
|
var DEFAULT_DOWNLOAD_TIMEOUT_MS = 5 * 60000;
|
|
@@ -122,14 +134,12 @@ var envPathsFunction = (() => {
|
|
|
122
134
|
})();
|
|
123
135
|
function resolveOptions(options = {}) {
|
|
124
136
|
const env = { ...options.env ?? process.env };
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const requiresPython = options.requiresPython ?? env["PYFIG_REQUIRES_PYTHON"] ?? DEFAULT_REQUIRES_PYTHON;
|
|
138
|
+
const requiresUv = options.requiresUv ?? env["PYFIG_REQUIRES_UV"] ?? DEFAULT_REQUIRES_UV;
|
|
139
|
+
assertPep440Specifier("requires-python", requiresPython);
|
|
140
|
+
assertPep440Specifier("requires-uv", requiresUv);
|
|
129
141
|
const managedPython = options.managedPython ?? env["PYFIG_MANAGED_PYTHON"] ?? DEFAULT_MANAGED_PYTHON;
|
|
130
|
-
|
|
131
|
-
assertManagedPythonRequest(managedPython, pythonRange);
|
|
132
|
-
assertUvVersion(uvVersion, uvRange);
|
|
142
|
+
assertManagedPythonRequest(managedPython, requiresPython);
|
|
133
143
|
const probeTimeoutMs = resolveDuration("probeTimeoutMs", options.probeTimeoutMs, env["PYFIG_PROBE_TIMEOUT_MS"], DEFAULT_PROBE_TIMEOUT_MS);
|
|
134
144
|
const installTimeoutMs = resolveDuration("installTimeoutMs", options.installTimeoutMs, env["PYFIG_INSTALL_TIMEOUT_MS"], DEFAULT_INSTALL_TIMEOUT_MS);
|
|
135
145
|
const downloadTimeoutMs = resolveDuration("downloadTimeoutMs", options.downloadTimeoutMs, env["PYFIG_DOWNLOAD_TIMEOUT_MS"], DEFAULT_DOWNLOAD_TIMEOUT_MS);
|
|
@@ -141,10 +151,9 @@ function resolveOptions(options = {}) {
|
|
|
141
151
|
const autoInstall = options.autoInstall ?? parseBoolean(env["PYFIG_AUTO_INSTALL"], true);
|
|
142
152
|
return {
|
|
143
153
|
python: options.python ?? env["PYFIG_PYTHON"] ?? "",
|
|
144
|
-
|
|
154
|
+
requiresPython,
|
|
145
155
|
managedPython,
|
|
146
|
-
|
|
147
|
-
uvRange,
|
|
156
|
+
requiresUv,
|
|
148
157
|
requireEmbedding: options.requireEmbedding ?? parseBoolean(env["PYFIG_REQUIRE_EMBEDDING"], false),
|
|
149
158
|
requireGil: options.requireGil ?? parseBoolean(env["PYFIG_REQUIRE_GIL"], false),
|
|
150
159
|
autoInstall,
|
|
@@ -169,27 +178,19 @@ function uvEnvironment(options) {
|
|
|
169
178
|
UV_NO_CONFIG: "1"
|
|
170
179
|
};
|
|
171
180
|
}
|
|
172
|
-
function
|
|
173
|
-
if (!
|
|
174
|
-
throw new TypeError(`Invalid ${
|
|
181
|
+
function assertPep440Specifier(name, specifier) {
|
|
182
|
+
if (!isValidPep440Specifier(specifier)) {
|
|
183
|
+
throw new TypeError(`Invalid ${name} PEP 440 specifier: ${specifier}`);
|
|
175
184
|
}
|
|
176
185
|
}
|
|
177
|
-
function
|
|
178
|
-
if (!import_semver.default.valid(version)) {
|
|
179
|
-
throw new TypeError(`Invalid exact uv version: ${version}`);
|
|
180
|
-
}
|
|
181
|
-
if (!import_semver.default.satisfies(version, range)) {
|
|
182
|
-
throw new TypeError(`Pinned uv ${version} does not satisfy supported range ${range}.`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
function assertManagedPythonRequest(request, pythonRange) {
|
|
186
|
+
function assertManagedPythonRequest(request, requiresPython) {
|
|
186
187
|
if (!request || /[\\/\0\r\n]/u.test(request) || request.includes("..")) {
|
|
187
188
|
throw new TypeError(`Invalid managed Python request: ${request}`);
|
|
188
189
|
}
|
|
189
190
|
if (/^\d+\.\d+(?:\.\d+)?$/u.test(request)) {
|
|
190
191
|
const normalized = request.split(".").length === 2 ? `${request}.0` : request;
|
|
191
|
-
if (!
|
|
192
|
-
throw new TypeError(`Managed Python ${request} cannot satisfy
|
|
192
|
+
if (!pep440VersionSatisfies(normalized, requiresPython)) {
|
|
193
|
+
throw new TypeError(`Managed Python ${request} cannot satisfy requires-python ${requiresPython}.`);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
}
|
|
@@ -222,7 +223,7 @@ var import_node_os = __toESM(require("node:os"), 1);
|
|
|
222
223
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
223
224
|
var import_node_stream = require("node:stream");
|
|
224
225
|
var import_promises2 = require("node:stream/promises");
|
|
225
|
-
var
|
|
226
|
+
var import_fflate = require("fflate");
|
|
226
227
|
var tar = __toESM(require("tar"), 1);
|
|
227
228
|
var import_undici = require("undici");
|
|
228
229
|
|
|
@@ -369,9 +370,6 @@ function assertNoTargetOverride(args) {
|
|
|
369
370
|
}
|
|
370
371
|
}
|
|
371
372
|
|
|
372
|
-
// src/probes.ts
|
|
373
|
-
var import_semver2 = __toESM(require("semver"), 1);
|
|
374
|
-
|
|
375
373
|
// src/process.ts
|
|
376
374
|
var import_node_child_process = require("node:child_process");
|
|
377
375
|
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
@@ -512,7 +510,15 @@ if os.path.isfile(header_path) and library_path:
|
|
|
512
510
|
}
|
|
513
511
|
|
|
514
512
|
release_level = sys.version_info.releaselevel
|
|
515
|
-
|
|
513
|
+
release_suffixes = {
|
|
514
|
+
"alpha": "a",
|
|
515
|
+
"beta": "b",
|
|
516
|
+
"candidate": "rc",
|
|
517
|
+
"final": "",
|
|
518
|
+
}
|
|
519
|
+
release_suffix = release_suffixes[release_level]
|
|
520
|
+
if release_suffix:
|
|
521
|
+
release_suffix += str(sys.version_info.serial)
|
|
516
522
|
|
|
517
523
|
print(json.dumps({
|
|
518
524
|
"executable": sys.executable,
|
|
@@ -532,8 +538,8 @@ print(json.dumps({
|
|
|
532
538
|
"embedding": embedding,
|
|
533
539
|
}))
|
|
534
540
|
`;
|
|
535
|
-
function
|
|
536
|
-
return
|
|
541
|
+
function pythonSatisfiesPolicy(python, options) {
|
|
542
|
+
return pep440VersionSatisfies(python.version, options.requiresPython) && (!options.requireEmbedding || python.implementation === "cpython" && python.embedding !== null && pythonArchitectureMatchesRuntime(python)) && (!options.requireGil || !python.gilDisabled);
|
|
537
543
|
}
|
|
538
544
|
async function probePython(candidate, options) {
|
|
539
545
|
const executable = await resolveExecutable(candidate.executable, options.env);
|
|
@@ -542,7 +548,7 @@ async function probePython(candidate, options) {
|
|
|
542
548
|
try {
|
|
543
549
|
const { stdout } = run(executable, [...candidate.args, "-c", PYTHON_PROBE], options.env, options.probeTimeoutMs);
|
|
544
550
|
const info = JSON.parse(stdout.trim());
|
|
545
|
-
return
|
|
551
|
+
return normalizePep440Version(info.version) ? info : null;
|
|
546
552
|
} catch {
|
|
547
553
|
return null;
|
|
548
554
|
}
|
|
@@ -551,7 +557,7 @@ async function findUv(options) {
|
|
|
551
557
|
const configured = options.env["PYFIG_UV"] ?? "uv";
|
|
552
558
|
const executable = await resolveExecutable(configured, options.env);
|
|
553
559
|
const uv = executable ? probeUv(executable, options) : null;
|
|
554
|
-
return uv &&
|
|
560
|
+
return uv && pep440VersionSatisfies(uv.version, options.requiresUv) ? uv : null;
|
|
555
561
|
}
|
|
556
562
|
function probeUv(executable, options) {
|
|
557
563
|
try {
|
|
@@ -565,7 +571,7 @@ function probeUv(executable, options) {
|
|
|
565
571
|
function normalizeVersion(value) {
|
|
566
572
|
if (!value)
|
|
567
573
|
return null;
|
|
568
|
-
return
|
|
574
|
+
return normalizePep440Version(value);
|
|
569
575
|
}
|
|
570
576
|
function pythonArchitectureMatchesRuntime(python) {
|
|
571
577
|
const architecture = python.architecture.toLowerCase();
|
|
@@ -585,14 +591,14 @@ function pythonArchitectureMatchesRuntime(python) {
|
|
|
585
591
|
async function ensureUv(options = {}) {
|
|
586
592
|
return ensureUvResolved(resolveOptions(options));
|
|
587
593
|
}
|
|
588
|
-
async function ensureEnvironment(options = {},
|
|
594
|
+
async function ensureEnvironment(options = {}, dependencies = [], dependencyOptions = {}) {
|
|
589
595
|
const resolved = resolveOptions(options);
|
|
590
|
-
const normalized =
|
|
596
|
+
const normalized = normalizeDependencies(dependencies);
|
|
591
597
|
const selections = normalizeDependencyOptions(dependencyOptions);
|
|
592
598
|
const uv = await ensureUvResolved(resolved);
|
|
593
599
|
const python = await ensureVirtualEnvironmentResolved(resolved, uv);
|
|
594
600
|
if (normalized.length > 0 || selections.extras.length > 0 || selections.groups.length > 0 || selections.allExtras) {
|
|
595
|
-
await
|
|
601
|
+
await installRuntimeDependencies(uv, python, normalized, selections, resolved);
|
|
596
602
|
}
|
|
597
603
|
return {
|
|
598
604
|
python,
|
|
@@ -607,7 +613,7 @@ async function ensureVirtualEnvironmentResolved(options, uv) {
|
|
|
607
613
|
const directory = virtualEnvironmentDirectory(options.projectRoot);
|
|
608
614
|
const check = async () => {
|
|
609
615
|
const candidate = await inspectVirtualEnvironment(directory, options);
|
|
610
|
-
return candidate &&
|
|
616
|
+
return candidate && pythonSatisfiesPolicy(candidate, options) && await virtualEnvironmentMatchesConfiguredPython(candidate, options) ? candidate : null;
|
|
611
617
|
};
|
|
612
618
|
if (!options.createVirtualEnvironment) {
|
|
613
619
|
throw new Error(`No compatible environment is available and creation is disabled. ` + `Pyfig would create ${directory}.`);
|
|
@@ -640,7 +646,7 @@ async function ensureVirtualEnvironmentResolved(options, uv) {
|
|
|
640
646
|
async function findReusableEnvironment(options) {
|
|
641
647
|
if (options.python) {
|
|
642
648
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
643
|
-
if (!configured || !
|
|
649
|
+
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
644
650
|
throw incompatibleConfiguredPython(options);
|
|
645
651
|
}
|
|
646
652
|
if (configured.virtualEnvironment)
|
|
@@ -651,7 +657,7 @@ async function findReusableEnvironment(options) {
|
|
|
651
657
|
virtualEnvironmentDirectory(options.projectRoot)
|
|
652
658
|
]) {
|
|
653
659
|
const info = await inspectVirtualEnvironment(directory, options);
|
|
654
|
-
if (info &&
|
|
660
|
+
if (info && pythonSatisfiesPolicy(info, options) && await virtualEnvironmentMatchesConfiguredPython(info, options)) {
|
|
655
661
|
return info;
|
|
656
662
|
}
|
|
657
663
|
}
|
|
@@ -660,13 +666,13 @@ async function findReusableEnvironment(options) {
|
|
|
660
666
|
async function ensureBasePythonResolved(uv, options) {
|
|
661
667
|
if (options.python) {
|
|
662
668
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
663
|
-
if (!configured || !
|
|
669
|
+
if (!configured || !pythonSatisfiesPolicy(configured, options)) {
|
|
664
670
|
throw incompatibleConfiguredPython(options);
|
|
665
671
|
}
|
|
666
672
|
if (!configured.virtualEnvironment)
|
|
667
673
|
return configured;
|
|
668
674
|
const base = await probePython({ executable: configured.baseExecutable, args: [] }, options);
|
|
669
|
-
if (base &&
|
|
675
|
+
if (base && pythonSatisfiesPolicy(base, options))
|
|
670
676
|
return base;
|
|
671
677
|
throw incompatibleConfiguredPython(options);
|
|
672
678
|
}
|
|
@@ -708,7 +714,7 @@ async function findPythonWithUv(uv, options, source) {
|
|
|
708
714
|
try {
|
|
709
715
|
const { stdout } = await runAsync(uv.executable, args, explicitPythonSourceEnvironment(options), options.probeTimeoutMs);
|
|
710
716
|
const info = await probePython({ executable: stdout.trim(), args: [] }, options);
|
|
711
|
-
if (info &&
|
|
717
|
+
if (info && pythonSatisfiesPolicy(info, options))
|
|
712
718
|
return info;
|
|
713
719
|
} catch {}
|
|
714
720
|
}
|
|
@@ -726,7 +732,7 @@ async function findPythonWithUv(uv, options, source) {
|
|
|
726
732
|
const candidates = installedPythonPaths(stdout);
|
|
727
733
|
for (const executable of candidates) {
|
|
728
734
|
const info = await probePython({ executable, args: [] }, options);
|
|
729
|
-
if (info && !info.virtualEnvironment &&
|
|
735
|
+
if (info && !info.virtualEnvironment && pythonSatisfiesPolicy(info, options)) {
|
|
730
736
|
return info;
|
|
731
737
|
}
|
|
732
738
|
}
|
|
@@ -750,7 +756,7 @@ async function inspectVirtualEnvironment(directory, options) {
|
|
|
750
756
|
const info = await probePython({ executable, args: [] }, options);
|
|
751
757
|
return info?.virtualEnvironment && pathsEqual(info.virtualEnvironment, directory) ? info : null;
|
|
752
758
|
}
|
|
753
|
-
async function
|
|
759
|
+
async function installRuntimeDependencies(uv, python, dependencies, dependencyOptions, options) {
|
|
754
760
|
if (!python.virtualEnvironment) {
|
|
755
761
|
throw new Error("Runtime dependency installation requires a virtual environment.");
|
|
756
762
|
}
|
|
@@ -759,18 +765,18 @@ async function installRuntimeRequirements(uv, python, requirements, dependencyOp
|
|
|
759
765
|
timeoutMs: options.coordinationTimeoutMs,
|
|
760
766
|
check: async () => null,
|
|
761
767
|
mutate: async () => {
|
|
762
|
-
const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-
|
|
768
|
+
const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-dependencies-"));
|
|
763
769
|
try {
|
|
764
|
-
const additions = import_node_path5.default.join(temporary, "runtime-
|
|
765
|
-
const resolved = import_node_path5.default.join(temporary, "resolved-
|
|
770
|
+
const additions = import_node_path5.default.join(temporary, "runtime-dependencies.txt");
|
|
771
|
+
const resolved = import_node_path5.default.join(temporary, "resolved-dependencies.txt");
|
|
766
772
|
const pyproject = import_node_path5.default.join(options.projectRoot, "pyproject.toml");
|
|
767
773
|
const hasPyproject = import_node_fs4.default.existsSync(pyproject);
|
|
768
774
|
if (!hasPyproject && (dependencyOptions.extras.length > 0 || dependencyOptions.groups.length > 0 || dependencyOptions.allExtras)) {
|
|
769
775
|
throw new Error("Runtime extras and groups require an existing pyproject.toml.");
|
|
770
776
|
}
|
|
771
777
|
const sources = hasPyproject ? [pyproject] : [];
|
|
772
|
-
if (
|
|
773
|
-
await import_promises.writeFile(additions, `${
|
|
778
|
+
if (dependencies.length > 0) {
|
|
779
|
+
await import_promises.writeFile(additions, `${dependencies.join(`
|
|
774
780
|
`)}
|
|
775
781
|
`, "utf8");
|
|
776
782
|
sources.push(additions);
|
|
@@ -804,21 +810,30 @@ async function ensureUvResolved(options) {
|
|
|
804
810
|
if (existing)
|
|
805
811
|
return existing;
|
|
806
812
|
if (!options.installUv) {
|
|
807
|
-
throw new Error(`Compatible uv is unavailable (
|
|
813
|
+
throw new Error(`Compatible uv is unavailable (requires-uv ${options.requiresUv}), and uv installation is disabled.`);
|
|
808
814
|
}
|
|
809
|
-
const
|
|
810
|
-
const executable = uvExecutable(options);
|
|
815
|
+
const policyKey = import_node_crypto.createHash("sha256").update(options.requiresUv).digest("hex");
|
|
811
816
|
return withProvisioningLock({
|
|
812
|
-
key: `uv:${canonicalPath(
|
|
817
|
+
key: `uv-policy:${canonicalPath(uvInstallRoot(options))}:${policyKey}`,
|
|
813
818
|
timeoutMs: options.coordinationTimeoutMs,
|
|
814
819
|
check: () => findAvailableUv(options),
|
|
815
820
|
mutate: async () => {
|
|
816
|
-
await
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
821
|
+
const release = await resolveUvRelease(options);
|
|
822
|
+
const installDir = uvInstallDirectory(options, release.version);
|
|
823
|
+
const executable = uvExecutable(options, release.version);
|
|
824
|
+
return withProvisioningLock({
|
|
825
|
+
key: `uv-release:${canonicalPath(installDir)}`,
|
|
826
|
+
timeoutMs: options.coordinationTimeoutMs,
|
|
827
|
+
check: () => findAvailableUv(options),
|
|
828
|
+
mutate: async () => {
|
|
829
|
+
await installUv(installDir, release, options);
|
|
830
|
+
const result = probeUv(executable, options);
|
|
831
|
+
if (!result || result.version !== release.version) {
|
|
832
|
+
throw new Error(`Expected managed uv ${release.version}, but the installed executable did not match.`);
|
|
833
|
+
}
|
|
834
|
+
return result;
|
|
835
|
+
}
|
|
836
|
+
});
|
|
822
837
|
}
|
|
823
838
|
});
|
|
824
839
|
}
|
|
@@ -826,14 +841,27 @@ async function findAvailableUv(options) {
|
|
|
826
841
|
const external = await findUv(options);
|
|
827
842
|
if (external)
|
|
828
843
|
return external;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
844
|
+
return findManagedUv(options);
|
|
845
|
+
}
|
|
846
|
+
async function findManagedUv(options) {
|
|
847
|
+
const entries = await import_promises.readdir(uvInstallRoot(options), { withFileTypes: true }).catch(() => []);
|
|
848
|
+
let versions = entries.filter((entry) => entry.isDirectory() && normalizePep440Version(entry.name) === entry.name).map((entry) => entry.name);
|
|
849
|
+
while (versions.length > 0) {
|
|
850
|
+
const selected = maxPep440Version(versions, options.requiresUv);
|
|
851
|
+
if (!selected)
|
|
852
|
+
return null;
|
|
853
|
+
const candidate = probeUv(uvExecutable(options, selected), options);
|
|
854
|
+
if (candidate?.version === selected)
|
|
855
|
+
return candidate;
|
|
856
|
+
versions = versions.filter((version) => version !== selected);
|
|
857
|
+
}
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
function normalizeDependencies(dependencies) {
|
|
861
|
+
const normalized = dependencies.map((dependency) => dependency.trim());
|
|
862
|
+
for (const dependency of normalized) {
|
|
863
|
+
if (!dependency || /[\0\r\n]/u.test(dependency)) {
|
|
864
|
+
throw new TypeError(`Invalid runtime dependency: ${JSON.stringify(dependency)}`);
|
|
837
865
|
}
|
|
838
866
|
}
|
|
839
867
|
return [...new Set(normalized)];
|
|
@@ -870,17 +898,17 @@ function targetUvEnvironment(options, python) {
|
|
|
870
898
|
return uvCommandEnvironment(uvEnvironment(options), python);
|
|
871
899
|
}
|
|
872
900
|
function incompatibleConfiguredPython(options) {
|
|
873
|
-
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` +
|
|
901
|
+
return new Error(`Configured Python ${options.python} is unavailable or does not satisfy ` + `requires-python ${options.requiresPython}` + `${options.requireEmbedding ? " with CPython embedding files" : ""}.`);
|
|
874
902
|
}
|
|
875
903
|
function missingPythonMessage(options, reason) {
|
|
876
904
|
const embedding = options.requireEmbedding ? " with compatible CPython headers, library, architecture, and GIL ABI" : "";
|
|
877
|
-
return `No Python satisfies ${options.
|
|
905
|
+
return `No Python satisfies requires-python ${options.requiresPython}${embedding}; ${reason}.`;
|
|
878
906
|
}
|
|
879
907
|
async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, options) {
|
|
880
908
|
if (!options.python)
|
|
881
909
|
return true;
|
|
882
910
|
const configured = await probePython({ executable: options.python, args: [] }, options);
|
|
883
|
-
if (!configured || !
|
|
911
|
+
if (!configured || !pythonSatisfiesPolicy(configured, options))
|
|
884
912
|
return false;
|
|
885
913
|
if (configured.virtualEnvironment && pathsEqual(configured.executable, virtualEnvironment.executable)) {
|
|
886
914
|
return true;
|
|
@@ -888,15 +916,159 @@ async function virtualEnvironmentMatchesConfiguredPython(virtualEnvironment, opt
|
|
|
888
916
|
const configuredBase = configured.virtualEnvironment ? configured.baseExecutable : configured.executable;
|
|
889
917
|
return pathsEqual(configuredBase, virtualEnvironment.baseExecutable);
|
|
890
918
|
}
|
|
891
|
-
|
|
892
|
-
|
|
919
|
+
var UV_RELEASE_PAGE_SIZE = 100;
|
|
920
|
+
var UV_RELEASE_MAX_PAGES = 20;
|
|
921
|
+
var UV_RELEASE_METADATA_TTL_MS = 24 * 60 * 60000;
|
|
922
|
+
async function resolveUvRelease(options) {
|
|
923
|
+
const catalog = await ensureUvReleaseCatalog(options);
|
|
924
|
+
const asset = uvAsset();
|
|
925
|
+
const releases = catalog.releases.filter((release) => release.assets.includes(asset) && release.assets.includes(`${asset}.sha256`));
|
|
926
|
+
const version = maxPep440Version(releases.map((release) => release.version), options.requiresUv);
|
|
927
|
+
const release = releases.find((candidate) => candidate.version === version);
|
|
928
|
+
if (!release) {
|
|
929
|
+
throw new Error(`No published uv release satisfies requires-uv ${options.requiresUv}.`);
|
|
930
|
+
}
|
|
931
|
+
return release;
|
|
932
|
+
}
|
|
933
|
+
async function ensureUvReleaseCatalog(options) {
|
|
934
|
+
const file = uvReleaseMetadataFile(options);
|
|
935
|
+
const stale = await readUvReleaseCatalog(file);
|
|
936
|
+
try {
|
|
937
|
+
return await withProvisioningLock({
|
|
938
|
+
key: `uv-metadata:${canonicalPath(file)}`,
|
|
939
|
+
timeoutMs: options.coordinationTimeoutMs,
|
|
940
|
+
check: () => readFreshUvReleaseCatalog(file),
|
|
941
|
+
mutate: async () => {
|
|
942
|
+
const catalog = await fetchUvReleaseCatalog(options);
|
|
943
|
+
await writeUvReleaseCatalog(file, catalog);
|
|
944
|
+
return catalog;
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
} catch (error) {
|
|
948
|
+
const fallback = await readUvReleaseCatalog(file) ?? stale;
|
|
949
|
+
if (fallback)
|
|
950
|
+
return fallback;
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async function fetchUvReleaseCatalog(options) {
|
|
955
|
+
const dispatcher = proxyDispatcher(options.env);
|
|
956
|
+
const releases = new Map;
|
|
957
|
+
let exhausted = false;
|
|
958
|
+
try {
|
|
959
|
+
for (let page = 1;page <= UV_RELEASE_MAX_PAGES; page += 1) {
|
|
960
|
+
const text = await downloadText(`https://api.github.com/repos/astral-sh/uv/releases?per_page=${UV_RELEASE_PAGE_SIZE}&page=${page}`, options.downloadTimeoutMs, dispatcher, {
|
|
961
|
+
Accept: "application/vnd.github+json",
|
|
962
|
+
"User-Agent": "pyfig",
|
|
963
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
964
|
+
});
|
|
965
|
+
const payload = JSON.parse(text);
|
|
966
|
+
if (!Array.isArray(payload)) {
|
|
967
|
+
throw new Error("GitHub returned invalid uv release metadata.");
|
|
968
|
+
}
|
|
969
|
+
for (const candidate of payload) {
|
|
970
|
+
if (!candidate || typeof candidate !== "object")
|
|
971
|
+
continue;
|
|
972
|
+
const release = candidate;
|
|
973
|
+
if (release.draft === true || typeof release.tag_name !== "string")
|
|
974
|
+
continue;
|
|
975
|
+
const assetNames = releaseAssetNames(release.assets);
|
|
976
|
+
const version = normalizePep440Version(release.tag_name);
|
|
977
|
+
if (version) {
|
|
978
|
+
releases.set(version, { assets: assetNames, tag: release.tag_name, version });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (payload.length < UV_RELEASE_PAGE_SIZE) {
|
|
982
|
+
exhausted = true;
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
} finally {
|
|
987
|
+
await dispatcher?.close();
|
|
988
|
+
}
|
|
989
|
+
if (!exhausted) {
|
|
990
|
+
throw new Error(`uv release catalog exceeded ${UV_RELEASE_MAX_PAGES} pages.`);
|
|
991
|
+
}
|
|
992
|
+
if (releases.size === 0) {
|
|
993
|
+
throw new Error("GitHub returned no usable uv releases.");
|
|
994
|
+
}
|
|
995
|
+
return { fetchedAt: Date.now(), releases: [...releases.values()] };
|
|
996
|
+
}
|
|
997
|
+
async function readFreshUvReleaseCatalog(file) {
|
|
998
|
+
const catalog = await readUvReleaseCatalog(file);
|
|
999
|
+
if (!catalog)
|
|
1000
|
+
return null;
|
|
1001
|
+
const age = Date.now() - catalog.fetchedAt;
|
|
1002
|
+
return age >= 0 && age < UV_RELEASE_METADATA_TTL_MS ? catalog : null;
|
|
1003
|
+
}
|
|
1004
|
+
async function readUvReleaseCatalog(file) {
|
|
1005
|
+
try {
|
|
1006
|
+
return parseUvReleaseCatalog(JSON.parse(await import_promises.readFile(file, "utf8")));
|
|
1007
|
+
} catch {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function parseUvReleaseCatalog(value) {
|
|
1012
|
+
if (!value || typeof value !== "object")
|
|
1013
|
+
return null;
|
|
1014
|
+
const catalog = value;
|
|
1015
|
+
if (!Number.isSafeInteger(catalog.fetchedAt) || catalog.fetchedAt < 0 || !Array.isArray(catalog.releases)) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
const releases = [];
|
|
1019
|
+
for (const value of catalog.releases) {
|
|
1020
|
+
if (!value || typeof value !== "object")
|
|
1021
|
+
return null;
|
|
1022
|
+
const release = value;
|
|
1023
|
+
if (!Array.isArray(release.assets) || !release.assets.every((asset) => typeof asset === "string") || typeof release.tag !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._+-]*$/u.test(release.tag) || typeof release.version !== "string" || normalizePep440Version(release.version) !== release.version) {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
releases.push({
|
|
1027
|
+
assets: release.assets,
|
|
1028
|
+
tag: release.tag,
|
|
1029
|
+
version: release.version
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
return { fetchedAt: catalog.fetchedAt, releases };
|
|
1033
|
+
}
|
|
1034
|
+
async function writeUvReleaseCatalog(file, catalog) {
|
|
1035
|
+
await import_promises.mkdir(import_node_path5.default.dirname(file), { recursive: true });
|
|
1036
|
+
const staged = import_node_path5.default.join(import_node_path5.default.dirname(file), `.${import_node_path5.default.basename(file)}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`);
|
|
1037
|
+
try {
|
|
1038
|
+
await import_promises.writeFile(staged, `${JSON.stringify(catalog)}
|
|
1039
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
1040
|
+
if (process.platform === "win32")
|
|
1041
|
+
await import_promises.rm(file, { force: true });
|
|
1042
|
+
await import_promises.rename(staged, file);
|
|
1043
|
+
} finally {
|
|
1044
|
+
await import_promises.rm(staged, { force: true });
|
|
1045
|
+
}
|
|
893
1046
|
}
|
|
894
|
-
function
|
|
895
|
-
return import_node_path5.default.join(
|
|
1047
|
+
function uvReleaseMetadataFile(options) {
|
|
1048
|
+
return import_node_path5.default.join(options.cacheDir, "metadata", "uv-releases.json");
|
|
1049
|
+
}
|
|
1050
|
+
function releaseAssetNames(value) {
|
|
1051
|
+
if (!Array.isArray(value))
|
|
1052
|
+
return [];
|
|
1053
|
+
return value.flatMap((entry) => {
|
|
1054
|
+
if (!entry || typeof entry !== "object")
|
|
1055
|
+
return [];
|
|
1056
|
+
const name = entry.name;
|
|
1057
|
+
return typeof name === "string" ? [name] : [];
|
|
1058
|
+
});
|
|
896
1059
|
}
|
|
897
|
-
|
|
1060
|
+
function uvInstallRoot(options) {
|
|
1061
|
+
return import_node_path5.default.join(options.cacheDir, "tools", "uv");
|
|
1062
|
+
}
|
|
1063
|
+
function uvInstallDirectory(options, version) {
|
|
1064
|
+
return import_node_path5.default.join(uvInstallRoot(options), version);
|
|
1065
|
+
}
|
|
1066
|
+
function uvExecutable(options, version) {
|
|
1067
|
+
return import_node_path5.default.join(uvInstallDirectory(options, version), process.platform === "win32" ? "uv.exe" : "uv");
|
|
1068
|
+
}
|
|
1069
|
+
async function installUv(installDir, release, options) {
|
|
898
1070
|
const asset = uvAsset();
|
|
899
|
-
const baseUrl = `https://github.com/astral-sh/uv/releases/download/${
|
|
1071
|
+
const baseUrl = `https://github.com/astral-sh/uv/releases/download/${release.tag}`;
|
|
900
1072
|
const temporary = await import_promises.mkdtemp(import_node_path5.default.join(import_node_os.default.tmpdir(), "pyfig-uv-"));
|
|
901
1073
|
const dispatcher = proxyDispatcher(options.env);
|
|
902
1074
|
try {
|
|
@@ -909,30 +1081,42 @@ async function installUv(installDir, options) {
|
|
|
909
1081
|
}
|
|
910
1082
|
const extracted = import_node_path5.default.join(temporary, "extracted");
|
|
911
1083
|
await import_promises.mkdir(extracted, { recursive: true });
|
|
912
|
-
|
|
913
|
-
await import_extract_zip.default(archive, { dir: extracted });
|
|
914
|
-
else
|
|
915
|
-
await tar.x({ file: archive, cwd: extracted, strict: true });
|
|
916
|
-
const source = await findFile(extracted, process.platform === "win32" ? "uv.exe" : "uv");
|
|
1084
|
+
const source = await extractUvExecutable(archive, asset, extracted);
|
|
917
1085
|
if (!source)
|
|
918
1086
|
throw new Error(`uv archive did not contain the expected executable: ${asset}`);
|
|
919
|
-
await publishUvAtomically(source, installDir, options);
|
|
1087
|
+
await publishUvAtomically(source, installDir, release.version, options);
|
|
920
1088
|
} finally {
|
|
921
1089
|
await dispatcher?.close();
|
|
922
1090
|
await import_promises.rm(temporary, { recursive: true, force: true });
|
|
923
1091
|
}
|
|
924
1092
|
}
|
|
925
|
-
async function
|
|
1093
|
+
async function extractUvExecutable(archive, asset, destination) {
|
|
1094
|
+
const executableName = process.platform === "win32" ? "uv.exe" : "uv";
|
|
1095
|
+
if (!asset.endsWith(".zip")) {
|
|
1096
|
+
await tar.x({ file: archive, cwd: destination, strict: true });
|
|
1097
|
+
return findFile(destination, executableName);
|
|
1098
|
+
}
|
|
1099
|
+
const entries = import_fflate.unzipSync(await import_promises.readFile(archive), {
|
|
1100
|
+
filter: ({ name }) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName
|
|
1101
|
+
});
|
|
1102
|
+
const matches = Object.entries(entries).filter(([name]) => import_node_path5.default.posix.basename(name.replaceAll("\\", "/")) === executableName);
|
|
1103
|
+
if (matches.length !== 1 || !matches[0])
|
|
1104
|
+
return null;
|
|
1105
|
+
const source = import_node_path5.default.join(destination, executableName);
|
|
1106
|
+
await import_promises.writeFile(source, matches[0][1], { flag: "wx" });
|
|
1107
|
+
return source;
|
|
1108
|
+
}
|
|
1109
|
+
async function publishUvAtomically(source, installDir, version, options) {
|
|
926
1110
|
await import_promises.mkdir(installDir, { recursive: true });
|
|
927
|
-
const executable = uvExecutable(options);
|
|
1111
|
+
const executable = uvExecutable(options, version);
|
|
928
1112
|
const staged = import_node_path5.default.join(installDir, `.${import_node_path5.default.basename(executable)}.${process.pid}.${import_node_crypto.randomUUID()}.tmp`);
|
|
929
1113
|
try {
|
|
930
1114
|
await import_promises.copyFile(source, staged);
|
|
931
1115
|
if (process.platform !== "win32")
|
|
932
1116
|
await import_promises.chmod(staged, 493);
|
|
933
1117
|
const stagedUv = probeUv(staged, options);
|
|
934
|
-
if (!stagedUv || stagedUv.version !==
|
|
935
|
-
throw new Error(`Downloaded uv executable is not version ${
|
|
1118
|
+
if (!stagedUv || stagedUv.version !== version) {
|
|
1119
|
+
throw new Error(`Downloaded uv executable is not version ${version}.`);
|
|
936
1120
|
}
|
|
937
1121
|
if (process.platform === "win32")
|
|
938
1122
|
await import_promises.rm(executable, { force: true });
|
|
@@ -965,9 +1149,10 @@ async function download(url, destination, timeoutMs, dispatcher) {
|
|
|
965
1149
|
}
|
|
966
1150
|
await import_promises2.pipeline(import_node_stream.Readable.fromWeb(response.body), import_node_fs3.createWriteStream(destination, { flags: "wx" }));
|
|
967
1151
|
}
|
|
968
|
-
async function downloadText(url, timeoutMs, dispatcher) {
|
|
1152
|
+
async function downloadText(url, timeoutMs, dispatcher, headers) {
|
|
969
1153
|
const response = await import_undici.fetch(url, {
|
|
970
1154
|
dispatcher,
|
|
1155
|
+
headers,
|
|
971
1156
|
redirect: "follow",
|
|
972
1157
|
signal: AbortSignal.timeout(timeoutMs)
|
|
973
1158
|
});
|
|
@@ -1091,7 +1276,7 @@ function createPyfig(options = {}) {
|
|
|
1091
1276
|
const baseEnvironment = import_loqkit2.memoize(() => ensureEnvironment(scopedOptions));
|
|
1092
1277
|
const uvInfo = import_loqkit2.memoize(() => ensureUv(scopedOptions));
|
|
1093
1278
|
return {
|
|
1094
|
-
prepare: (
|
|
1279
|
+
prepare: (dependencies = [], dependencyOptions = {}) => dependencies.length === 0 && !dependencyOptions.allExtras && (dependencyOptions.extras?.length ?? 0) === 0 && (dependencyOptions.groups?.length ?? 0) === 0 ? baseEnvironment() : ensureEnvironment(scopedOptions, dependencies, dependencyOptions),
|
|
1095
1280
|
pythonInfo: async () => (await baseEnvironment()).python,
|
|
1096
1281
|
uvInfo,
|
|
1097
1282
|
uv: (args = [], spawnOptions = {}) => uvSpawn(args, spawnOptions, scopedOptions),
|
|
@@ -1099,4 +1284,4 @@ function createPyfig(options = {}) {
|
|
|
1099
1284
|
};
|
|
1100
1285
|
}
|
|
1101
1286
|
|
|
1102
|
-
//# debugId=
|
|
1287
|
+
//# debugId=25C05D1900A6412264756E2164756E21
|