@vercel/python-analysis 0.8.0 → 0.8.2
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/dist/index.cjs +269 -64
- package/dist/index.d.ts +3 -3
- package/dist/index.js +268 -65
- package/dist/manifest/dist-metadata.d.ts +14 -0
- package/dist/manifest/python-selector.d.ts +41 -0
- package/dist/manifest/python-specifiers.d.ts +3 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -66,13 +66,15 @@ __export(src_exports, {
|
|
|
66
66
|
containsAppOrHandler: () => containsAppOrHandler,
|
|
67
67
|
createMinimalManifest: () => createMinimalManifest,
|
|
68
68
|
discoverPythonPackage: () => discoverPythonPackage,
|
|
69
|
+
extendDistRecord: () => extendDistRecord,
|
|
69
70
|
getStringConstant: () => getStringConstant,
|
|
70
71
|
isPrivatePackageSource: () => isPrivatePackageSource,
|
|
71
|
-
normalizePackageName: () =>
|
|
72
|
+
normalizePackageName: () => normalizePackageName2,
|
|
72
73
|
parseDjangoSettingsModule: () => parseDjangoSettingsModule,
|
|
73
74
|
parseUvLock: () => parseUvLock,
|
|
74
75
|
scanDistributions: () => scanDistributions,
|
|
75
76
|
selectPython: () => selectPython,
|
|
77
|
+
selectPythonVersion: () => selectPythonVersion,
|
|
76
78
|
stringifyManifest: () => stringifyManifest
|
|
77
79
|
});
|
|
78
80
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -139,8 +141,51 @@ async function parseDjangoSettingsModule(content) {
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
// src/manifest/dist-metadata.ts
|
|
144
|
+
var import_node_crypto = require("crypto");
|
|
145
|
+
var import_node_fs = require("fs");
|
|
142
146
|
var import_promises2 = require("fs/promises");
|
|
143
147
|
var import_node_path2 = require("path");
|
|
148
|
+
|
|
149
|
+
// src/manifest/pep508.ts
|
|
150
|
+
var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
|
|
151
|
+
function splitExtras(spec) {
|
|
152
|
+
const match = EXTRAS_REGEX.exec(spec);
|
|
153
|
+
if (!match) {
|
|
154
|
+
return [spec, void 0];
|
|
155
|
+
}
|
|
156
|
+
const extras = match[2].split(",").map((e) => e.trim());
|
|
157
|
+
return [match[1], extras];
|
|
158
|
+
}
|
|
159
|
+
function normalizePackageName(name) {
|
|
160
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
161
|
+
}
|
|
162
|
+
function formatPep508(req) {
|
|
163
|
+
let result = req.name;
|
|
164
|
+
if (req.extras && req.extras.length > 0) {
|
|
165
|
+
result += `[${req.extras.join(",")}]`;
|
|
166
|
+
}
|
|
167
|
+
if (req.url) {
|
|
168
|
+
result += ` @ ${req.url}`;
|
|
169
|
+
} else if (req.version && req.version !== "*") {
|
|
170
|
+
result += req.version;
|
|
171
|
+
}
|
|
172
|
+
if (req.markers) {
|
|
173
|
+
result += ` ; ${req.markers}`;
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
function mergeExtras(existing, additional) {
|
|
178
|
+
const result = new Set(existing || []);
|
|
179
|
+
if (additional) {
|
|
180
|
+
const additionalArray = Array.isArray(additional) ? additional : [additional];
|
|
181
|
+
for (const extra of additionalArray) {
|
|
182
|
+
result.add(extra);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return result.size > 0 ? Array.from(result) : void 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/manifest/dist-metadata.ts
|
|
144
189
|
async function readDistInfoFile(distInfoDir, filename) {
|
|
145
190
|
try {
|
|
146
191
|
return await (0, import_promises2.readFile)((0, import_node_path2.join)(distInfoDir, filename), "utf-8");
|
|
@@ -227,6 +272,62 @@ async function scanDistributions(sitePackagesDir) {
|
|
|
227
272
|
}
|
|
228
273
|
return index;
|
|
229
274
|
}
|
|
275
|
+
function hashFile(filePath) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
const h = (0, import_node_crypto.createHash)("sha256");
|
|
278
|
+
let size = 0;
|
|
279
|
+
const stream = (0, import_node_fs.createReadStream)(filePath);
|
|
280
|
+
stream.on("data", (chunk) => {
|
|
281
|
+
size += chunk.length;
|
|
282
|
+
h.update(chunk);
|
|
283
|
+
});
|
|
284
|
+
stream.on("error", reject);
|
|
285
|
+
stream.on("end", () => {
|
|
286
|
+
resolve({ hash: h.digest("base64url"), size });
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async function extendDistRecord(sitePackagesDir, packageName, paths) {
|
|
291
|
+
const normalizedTarget = normalizePackageName(packageName);
|
|
292
|
+
const entries = await (0, import_promises2.readdir)(sitePackagesDir);
|
|
293
|
+
const distInfoDirName = entries.find((e) => {
|
|
294
|
+
if (!e.endsWith(".dist-info"))
|
|
295
|
+
return false;
|
|
296
|
+
const withoutSuffix = e.slice(0, -".dist-info".length);
|
|
297
|
+
const lastHyphen = withoutSuffix.lastIndexOf("-");
|
|
298
|
+
if (lastHyphen === -1)
|
|
299
|
+
return false;
|
|
300
|
+
const dirName = withoutSuffix.slice(0, lastHyphen);
|
|
301
|
+
return normalizePackageName(dirName) === normalizedTarget;
|
|
302
|
+
});
|
|
303
|
+
if (!distInfoDirName) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`No .dist-info directory found for package "${packageName}" in ${sitePackagesDir}`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
const recordPath = (0, import_node_path2.join)(sitePackagesDir, distInfoDirName, "RECORD");
|
|
309
|
+
let existingRecord;
|
|
310
|
+
try {
|
|
311
|
+
existingRecord = await (0, import_promises2.readFile)(recordPath, "utf-8");
|
|
312
|
+
} catch {
|
|
313
|
+
throw new Error(`RECORD file not found in ${distInfoDirName}`);
|
|
314
|
+
}
|
|
315
|
+
const existingPaths = new Set(
|
|
316
|
+
existingRecord.split("\n").filter((line) => line.length > 0).map((line) => line.split(",")[0])
|
|
317
|
+
);
|
|
318
|
+
const newEntries = paths.filter((p) => !existingPaths.has(p));
|
|
319
|
+
if (newEntries.length > 0) {
|
|
320
|
+
const prefix = existingRecord.length > 0 && !existingRecord.endsWith("\n") ? "\n" : "";
|
|
321
|
+
const lines = [];
|
|
322
|
+
for (const p of newEntries) {
|
|
323
|
+
const fullPath = (0, import_node_path2.join)(sitePackagesDir, p);
|
|
324
|
+
const { hash, size } = await hashFile(fullPath);
|
|
325
|
+
lines.push(`${p},sha256=${hash},${size}`);
|
|
326
|
+
}
|
|
327
|
+
await (0, import_promises2.appendFile)(recordPath, prefix + lines.join("\n") + "\n");
|
|
328
|
+
}
|
|
329
|
+
return newEntries.length;
|
|
330
|
+
}
|
|
230
331
|
|
|
231
332
|
// src/manifest/package.ts
|
|
232
333
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
@@ -447,42 +548,6 @@ var PipfileLikeSchema = pipfileLikeSchema;
|
|
|
447
548
|
var PipfileLockMetaSchema = pipfileLockMetaSchema.passthrough();
|
|
448
549
|
var PipfileLockLikeSchema = pipfileLockLikeSchema;
|
|
449
550
|
|
|
450
|
-
// src/manifest/pep508.ts
|
|
451
|
-
var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
|
|
452
|
-
function splitExtras(spec) {
|
|
453
|
-
const match = EXTRAS_REGEX.exec(spec);
|
|
454
|
-
if (!match) {
|
|
455
|
-
return [spec, void 0];
|
|
456
|
-
}
|
|
457
|
-
const extras = match[2].split(",").map((e) => e.trim());
|
|
458
|
-
return [match[1], extras];
|
|
459
|
-
}
|
|
460
|
-
function formatPep508(req) {
|
|
461
|
-
let result = req.name;
|
|
462
|
-
if (req.extras && req.extras.length > 0) {
|
|
463
|
-
result += `[${req.extras.join(",")}]`;
|
|
464
|
-
}
|
|
465
|
-
if (req.url) {
|
|
466
|
-
result += ` @ ${req.url}`;
|
|
467
|
-
} else if (req.version && req.version !== "*") {
|
|
468
|
-
result += req.version;
|
|
469
|
-
}
|
|
470
|
-
if (req.markers) {
|
|
471
|
-
result += ` ; ${req.markers}`;
|
|
472
|
-
}
|
|
473
|
-
return result;
|
|
474
|
-
}
|
|
475
|
-
function mergeExtras(existing, additional) {
|
|
476
|
-
const result = new Set(existing || []);
|
|
477
|
-
if (additional) {
|
|
478
|
-
const additionalArray = Array.isArray(additional) ? additional : [additional];
|
|
479
|
-
for (const extra of additionalArray) {
|
|
480
|
-
result.add(extra);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return result.size > 0 ? Array.from(result) : void 0;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
551
|
// src/util/type.ts
|
|
487
552
|
function isPlainObject(value) {
|
|
488
553
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -675,17 +740,24 @@ function convertPipfileToPyprojectToml(pipfile) {
|
|
|
675
740
|
function convertPipfileLockToPyprojectToml(pipfileLock) {
|
|
676
741
|
const sources = {};
|
|
677
742
|
const pyproject = {};
|
|
743
|
+
const pythonVersion = pipfileLock._meta?.requires?.python_version;
|
|
678
744
|
const deps = [];
|
|
679
745
|
for (const dep of pipfileLockDepsToRequirements(pipfileLock.default || {})) {
|
|
680
746
|
deps.push(formatPep508(dep));
|
|
681
747
|
addDepSource(sources, dep);
|
|
682
748
|
}
|
|
683
|
-
if (deps.length > 0) {
|
|
684
|
-
|
|
749
|
+
if (deps.length > 0 || pythonVersion) {
|
|
750
|
+
const project = {
|
|
685
751
|
name: "app",
|
|
686
|
-
version: "0.1.0"
|
|
687
|
-
dependencies: deps
|
|
752
|
+
version: "0.1.0"
|
|
688
753
|
};
|
|
754
|
+
if (pythonVersion) {
|
|
755
|
+
project["requires-python"] = `==${pythonVersion}.*`;
|
|
756
|
+
}
|
|
757
|
+
if (deps.length > 0) {
|
|
758
|
+
project.dependencies = deps;
|
|
759
|
+
}
|
|
760
|
+
pyproject.project = project;
|
|
689
761
|
}
|
|
690
762
|
const devDeps = [];
|
|
691
763
|
for (const dep of pipfileLockDepsToRequirements(pipfileLock.develop || {})) {
|
|
@@ -1866,8 +1938,10 @@ async function discoverPythonPackage({
|
|
|
1866
1938
|
let workspaceManifest;
|
|
1867
1939
|
let workspaceLockFile;
|
|
1868
1940
|
if (manifests.length === 0) {
|
|
1941
|
+
const requiresPython2 = computeRequiresPython(void 0, void 0, configs);
|
|
1869
1942
|
return {
|
|
1870
|
-
configs
|
|
1943
|
+
configs,
|
|
1944
|
+
requiresPython: requiresPython2
|
|
1871
1945
|
};
|
|
1872
1946
|
} else {
|
|
1873
1947
|
entrypointManifest = manifests[0];
|
|
@@ -1901,37 +1975,44 @@ async function discoverPythonPackage({
|
|
|
1901
1975
|
}
|
|
1902
1976
|
function computeRequiresPython(manifest, workspaceManifest, configs) {
|
|
1903
1977
|
const constraints = [];
|
|
1978
|
+
let hasPythonVersionFile = false;
|
|
1904
1979
|
for (const configSet of configs) {
|
|
1905
1980
|
const pythonVersionConfig = configSet[".python-version" /* PythonVersion */];
|
|
1906
1981
|
if (pythonVersionConfig !== void 0) {
|
|
1907
1982
|
constraints.push({
|
|
1908
1983
|
request: pythonVersionConfig.data,
|
|
1909
|
-
source:
|
|
1984
|
+
source: pythonVersionConfig.path,
|
|
1985
|
+
prettySource: pythonVersionConfig.path
|
|
1910
1986
|
});
|
|
1987
|
+
hasPythonVersionFile = true;
|
|
1911
1988
|
break;
|
|
1912
1989
|
}
|
|
1913
1990
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
const request = pythonRequestFromConstraint(parsed);
|
|
1919
|
-
constraints.push({
|
|
1920
|
-
request: [request],
|
|
1921
|
-
source: `"requires-python" key in ${manifest.path}`
|
|
1922
|
-
});
|
|
1923
|
-
}
|
|
1924
|
-
} else {
|
|
1925
|
-
const workspaceRequiresPython = workspaceManifest?.data.project?.["requires-python"];
|
|
1926
|
-
if (workspaceRequiresPython) {
|
|
1927
|
-
const parsed = (0, import_specifier.parse)(workspaceRequiresPython);
|
|
1991
|
+
if (!hasPythonVersionFile) {
|
|
1992
|
+
const manifestRequiresPython = manifest?.data.project?.["requires-python"];
|
|
1993
|
+
if (manifestRequiresPython) {
|
|
1994
|
+
const parsed = (0, import_specifier.parse)(manifestRequiresPython);
|
|
1928
1995
|
if (parsed?.length) {
|
|
1929
1996
|
const request = pythonRequestFromConstraint(parsed);
|
|
1930
1997
|
constraints.push({
|
|
1931
1998
|
request: [request],
|
|
1932
|
-
source:
|
|
1999
|
+
source: manifest.path,
|
|
2000
|
+
prettySource: `"requires-python" key in ${manifest.path}`
|
|
1933
2001
|
});
|
|
1934
2002
|
}
|
|
2003
|
+
} else {
|
|
2004
|
+
const workspaceRequiresPython = workspaceManifest?.data.project?.["requires-python"];
|
|
2005
|
+
if (workspaceRequiresPython) {
|
|
2006
|
+
const parsed = (0, import_specifier.parse)(workspaceRequiresPython);
|
|
2007
|
+
if (parsed?.length) {
|
|
2008
|
+
const request = pythonRequestFromConstraint(parsed);
|
|
2009
|
+
constraints.push({
|
|
2010
|
+
request: [request],
|
|
2011
|
+
source: workspaceManifest.path,
|
|
2012
|
+
prettySource: `"requires-python" key in ${workspaceManifest.path}`
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
1935
2016
|
}
|
|
1936
2017
|
}
|
|
1937
2018
|
return constraints;
|
|
@@ -2280,7 +2361,7 @@ function isPrivatePackageSource(source) {
|
|
|
2280
2361
|
}
|
|
2281
2362
|
return false;
|
|
2282
2363
|
}
|
|
2283
|
-
function
|
|
2364
|
+
function normalizePackageName2(name) {
|
|
2284
2365
|
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
2285
2366
|
}
|
|
2286
2367
|
function classifyPackages(options) {
|
|
@@ -2288,9 +2369,9 @@ function classifyPackages(options) {
|
|
|
2288
2369
|
const privatePackages = [];
|
|
2289
2370
|
const publicPackages = [];
|
|
2290
2371
|
const packageVersions = {};
|
|
2291
|
-
const excludeSet = new Set(excludePackages.map(
|
|
2372
|
+
const excludeSet = new Set(excludePackages.map(normalizePackageName2));
|
|
2292
2373
|
for (const pkg of lockFile.packages) {
|
|
2293
|
-
if (excludeSet.has(
|
|
2374
|
+
if (excludeSet.has(normalizePackageName2(pkg.name))) {
|
|
2294
2375
|
continue;
|
|
2295
2376
|
}
|
|
2296
2377
|
packageVersions[pkg.name] = pkg.version;
|
|
@@ -2343,13 +2424,15 @@ function selectPython(constraints, available) {
|
|
|
2343
2424
|
}
|
|
2344
2425
|
}
|
|
2345
2426
|
if (constraintsWithMatches.length > 1) {
|
|
2346
|
-
const sources = constraintsWithMatches.map(
|
|
2427
|
+
const sources = constraintsWithMatches.map(
|
|
2428
|
+
(i) => constraints[i].prettySource
|
|
2429
|
+
);
|
|
2347
2430
|
warnings.push(
|
|
2348
2431
|
`Python version constraints may not overlap: ${sources.join(", ")}`
|
|
2349
2432
|
);
|
|
2350
2433
|
}
|
|
2351
2434
|
}
|
|
2352
|
-
const constraintDescriptions = constraints.map((c) => c.
|
|
2435
|
+
const constraintDescriptions = constraints.map((c) => c.prettySource).join(", ");
|
|
2353
2436
|
errors.push(
|
|
2354
2437
|
`No Python build satisfies all constraints: ${constraintDescriptions}`
|
|
2355
2438
|
);
|
|
@@ -2359,6 +2442,126 @@ function selectPython(constraints, available) {
|
|
|
2359
2442
|
warnings: warnings.length > 0 ? warnings : void 0
|
|
2360
2443
|
};
|
|
2361
2444
|
}
|
|
2445
|
+
function selectPythonVersion({
|
|
2446
|
+
constraints,
|
|
2447
|
+
availableBuilds,
|
|
2448
|
+
allBuilds,
|
|
2449
|
+
defaultBuild,
|
|
2450
|
+
majorMinorOnly,
|
|
2451
|
+
legacyTildeEquals
|
|
2452
|
+
}) {
|
|
2453
|
+
const source = constraints?.[0]?.source;
|
|
2454
|
+
if (!constraints || constraints.length === 0) {
|
|
2455
|
+
return { build: defaultBuild };
|
|
2456
|
+
}
|
|
2457
|
+
let effectiveConstraints = majorMinorOnly ? constraints.map((c) => ({
|
|
2458
|
+
...c,
|
|
2459
|
+
request: c.request.map(truncatePatchVersionsInRequest)
|
|
2460
|
+
})) : constraints;
|
|
2461
|
+
if (legacyTildeEquals) {
|
|
2462
|
+
effectiveConstraints = effectiveConstraints.map((c) => ({
|
|
2463
|
+
...c,
|
|
2464
|
+
request: c.request.map(legacyTildeEqualsTransform)
|
|
2465
|
+
}));
|
|
2466
|
+
}
|
|
2467
|
+
const result = selectPython(effectiveConstraints, availableBuilds);
|
|
2468
|
+
if (result.build) {
|
|
2469
|
+
return { build: result.build, source };
|
|
2470
|
+
}
|
|
2471
|
+
const allResult = selectPython(effectiveConstraints, allBuilds);
|
|
2472
|
+
if (allResult.build) {
|
|
2473
|
+
const version = pythonVersionToString(allResult.build.version);
|
|
2474
|
+
return {
|
|
2475
|
+
build: defaultBuild,
|
|
2476
|
+
source,
|
|
2477
|
+
notAvailable: { build: allResult.build, version }
|
|
2478
|
+
};
|
|
2479
|
+
}
|
|
2480
|
+
const versionString = extractConstraintVersionString(constraints);
|
|
2481
|
+
return {
|
|
2482
|
+
build: defaultBuild,
|
|
2483
|
+
source,
|
|
2484
|
+
invalidConstraint: { versionString }
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function extractConstraintVersionString(constraints) {
|
|
2488
|
+
for (const c of constraints) {
|
|
2489
|
+
for (const req of c.request) {
|
|
2490
|
+
if (req.version?.constraint && req.version.constraint.length > 0) {
|
|
2491
|
+
const specs = req.version.constraint;
|
|
2492
|
+
if (specs.length === 1 && specs[0].operator === "==" && (!specs[0].prefix || specs[0].prefix === ".*")) {
|
|
2493
|
+
return specs[0].version;
|
|
2494
|
+
}
|
|
2495
|
+
return pep440ConstraintsToString(specs);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return "unknown";
|
|
2500
|
+
}
|
|
2501
|
+
function truncatePatchVersionsInRequest(req) {
|
|
2502
|
+
if (!req.version?.constraint || req.version.constraint.length === 0) {
|
|
2503
|
+
return req;
|
|
2504
|
+
}
|
|
2505
|
+
const newConstraints = [];
|
|
2506
|
+
for (const c of req.version.constraint) {
|
|
2507
|
+
const result = truncatePatchConstraint(c);
|
|
2508
|
+
if (result !== null) {
|
|
2509
|
+
newConstraints.push(result);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const newVersion = {
|
|
2513
|
+
...req.version,
|
|
2514
|
+
constraint: newConstraints
|
|
2515
|
+
};
|
|
2516
|
+
return { ...req, version: newVersion };
|
|
2517
|
+
}
|
|
2518
|
+
function truncatePatchConstraint(c) {
|
|
2519
|
+
if (c.operator === "===") {
|
|
2520
|
+
return c;
|
|
2521
|
+
}
|
|
2522
|
+
const parts = c.version.split(".");
|
|
2523
|
+
if (parts.length < 3) {
|
|
2524
|
+
return c;
|
|
2525
|
+
}
|
|
2526
|
+
const majorMinor = parts.slice(0, 2).join(".");
|
|
2527
|
+
const patch = parseInt(parts[2], 10);
|
|
2528
|
+
switch (c.operator) {
|
|
2529
|
+
case "==":
|
|
2530
|
+
return { operator: "==", version: majorMinor, prefix: ".*" };
|
|
2531
|
+
case "!=":
|
|
2532
|
+
return null;
|
|
2533
|
+
case "~=":
|
|
2534
|
+
return { operator: "==", version: majorMinor, prefix: ".*" };
|
|
2535
|
+
case ">=":
|
|
2536
|
+
return { operator: ">=", version: majorMinor, prefix: "" };
|
|
2537
|
+
case "<=":
|
|
2538
|
+
return { operator: "<=", version: majorMinor, prefix: "" };
|
|
2539
|
+
case ">":
|
|
2540
|
+
return patch === 0 ? { operator: ">", version: majorMinor, prefix: "" } : { operator: ">=", version: majorMinor, prefix: "" };
|
|
2541
|
+
case "<":
|
|
2542
|
+
return patch === 0 ? { operator: "<", version: majorMinor, prefix: "" } : { operator: "<=", version: majorMinor, prefix: "" };
|
|
2543
|
+
default:
|
|
2544
|
+
return c;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function legacyTildeEqualsTransform(req) {
|
|
2548
|
+
if (!req.version?.constraint || req.version.constraint.length === 0) {
|
|
2549
|
+
return req;
|
|
2550
|
+
}
|
|
2551
|
+
const newConstraints = req.version.constraint.map((c) => {
|
|
2552
|
+
if (c.operator !== "~=")
|
|
2553
|
+
return c;
|
|
2554
|
+
const parts = c.version.split(".");
|
|
2555
|
+
if (parts.length === 2) {
|
|
2556
|
+
return { operator: "==", version: c.version, prefix: ".*" };
|
|
2557
|
+
}
|
|
2558
|
+
return c;
|
|
2559
|
+
});
|
|
2560
|
+
return {
|
|
2561
|
+
...req,
|
|
2562
|
+
version: { ...req.version, constraint: newConstraints }
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2362
2565
|
function pythonVersionToString(version) {
|
|
2363
2566
|
let str = `${version.major}.${version.minor}`;
|
|
2364
2567
|
if (version.patch !== void 0) {
|
|
@@ -2370,7 +2573,7 @@ function pythonVersionToString(version) {
|
|
|
2370
2573
|
return str;
|
|
2371
2574
|
}
|
|
2372
2575
|
function pep440ConstraintsToString(constraints) {
|
|
2373
|
-
return constraints.map((c) => `${c.operator}${c.
|
|
2576
|
+
return constraints.map((c) => `${c.operator}${c.version}${c.prefix ?? ""}`).join(",");
|
|
2374
2577
|
}
|
|
2375
2578
|
function implementationsMatch(buildImpl, requestImpl) {
|
|
2376
2579
|
if (PythonImplementation.isUnknown(buildImpl)) {
|
|
@@ -2490,6 +2693,7 @@ var HashDigestSchema = hashDigestSchema;
|
|
|
2490
2693
|
containsAppOrHandler,
|
|
2491
2694
|
createMinimalManifest,
|
|
2492
2695
|
discoverPythonPackage,
|
|
2696
|
+
extendDistRecord,
|
|
2493
2697
|
getStringConstant,
|
|
2494
2698
|
isPrivatePackageSource,
|
|
2495
2699
|
normalizePackageName,
|
|
@@ -2497,5 +2701,6 @@ var HashDigestSchema = hashDigestSchema;
|
|
|
2497
2701
|
parseUvLock,
|
|
2498
2702
|
scanDistributions,
|
|
2499
2703
|
selectPython,
|
|
2704
|
+
selectPythonVersion,
|
|
2500
2705
|
stringifyManifest
|
|
2501
2706
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { containsAppOrHandler, getStringConstant, parseDjangoSettingsModule, } from './semantic/entrypoints';
|
|
10
10
|
export type { Distribution, DistributionIndex, PackagePath, DirectUrlInfo, } from './manifest/dist-metadata';
|
|
11
|
-
export { scanDistributions } from './manifest/dist-metadata';
|
|
11
|
+
export { extendDistRecord, scanDistributions } from './manifest/dist-metadata';
|
|
12
12
|
export type { PythonConfig, PythonConfigs, PythonLockFile, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
|
|
13
13
|
export { discoverPythonPackage, PythonConfigKind, PythonLockFileKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
|
|
14
14
|
export { createMinimalManifest, stringifyManifest, type CreateMinimalManifestOptions, } from './manifest/serialize';
|
|
15
15
|
export type { ClassifyPackagesOptions, PackageClassification, UvLockFile, UvLockPackage, UvLockPackageSource, } from './manifest/uv-lock-parser';
|
|
16
16
|
export { classifyPackages, isPrivatePackageSource, normalizePackageName, parseUvLock, } from './manifest/uv-lock-parser';
|
|
17
|
-
export type { PythonSelectionResult } from './manifest/python-selector';
|
|
18
|
-
export { selectPython } from './manifest/python-selector';
|
|
17
|
+
export type { PythonSelectionResult, PythonVersionSelectionResult, } from './manifest/python-selector';
|
|
18
|
+
export { selectPython, selectPythonVersion } from './manifest/python-selector';
|
|
19
19
|
export { PythonAnalysisError } from './util/error';
|
|
20
20
|
export { LicenseObjectSchema, LicenseSchema, PersonSchema, PyProjectBuildSystemSchema, PyProjectDependencyGroupsSchema, PyProjectProjectSchema, PyProjectToolSectionSchema, PyProjectTomlSchema, ReadmeObjectSchema, ReadmeSchema, } from './manifest/pyproject/schema';
|
|
21
21
|
export type { License, LicenseObject, Person, PyProjectBuildSystem, PyProjectDependencyGroups, PyProjectProject, PyProjectToml, PyProjectToolSection, Readme, ReadmeObject, } from './manifest/pyproject/types';
|
package/dist/index.js
CHANGED
|
@@ -60,8 +60,51 @@ async function parseDjangoSettingsModule(content) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// src/manifest/dist-metadata.ts
|
|
63
|
-
import {
|
|
63
|
+
import { createHash } from "crypto";
|
|
64
|
+
import { createReadStream } from "fs";
|
|
65
|
+
import { appendFile, readdir, readFile as readFile2 } from "fs/promises";
|
|
64
66
|
import { join as join2 } from "path";
|
|
67
|
+
|
|
68
|
+
// src/manifest/pep508.ts
|
|
69
|
+
var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
|
|
70
|
+
function splitExtras(spec) {
|
|
71
|
+
const match = EXTRAS_REGEX.exec(spec);
|
|
72
|
+
if (!match) {
|
|
73
|
+
return [spec, void 0];
|
|
74
|
+
}
|
|
75
|
+
const extras = match[2].split(",").map((e) => e.trim());
|
|
76
|
+
return [match[1], extras];
|
|
77
|
+
}
|
|
78
|
+
function normalizePackageName(name) {
|
|
79
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
80
|
+
}
|
|
81
|
+
function formatPep508(req) {
|
|
82
|
+
let result = req.name;
|
|
83
|
+
if (req.extras && req.extras.length > 0) {
|
|
84
|
+
result += `[${req.extras.join(",")}]`;
|
|
85
|
+
}
|
|
86
|
+
if (req.url) {
|
|
87
|
+
result += ` @ ${req.url}`;
|
|
88
|
+
} else if (req.version && req.version !== "*") {
|
|
89
|
+
result += req.version;
|
|
90
|
+
}
|
|
91
|
+
if (req.markers) {
|
|
92
|
+
result += ` ; ${req.markers}`;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function mergeExtras(existing, additional) {
|
|
97
|
+
const result = new Set(existing || []);
|
|
98
|
+
if (additional) {
|
|
99
|
+
const additionalArray = Array.isArray(additional) ? additional : [additional];
|
|
100
|
+
for (const extra of additionalArray) {
|
|
101
|
+
result.add(extra);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result.size > 0 ? Array.from(result) : void 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/manifest/dist-metadata.ts
|
|
65
108
|
async function readDistInfoFile(distInfoDir, filename) {
|
|
66
109
|
try {
|
|
67
110
|
return await readFile2(join2(distInfoDir, filename), "utf-8");
|
|
@@ -148,6 +191,62 @@ async function scanDistributions(sitePackagesDir) {
|
|
|
148
191
|
}
|
|
149
192
|
return index;
|
|
150
193
|
}
|
|
194
|
+
function hashFile(filePath) {
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
const h = createHash("sha256");
|
|
197
|
+
let size = 0;
|
|
198
|
+
const stream = createReadStream(filePath);
|
|
199
|
+
stream.on("data", (chunk) => {
|
|
200
|
+
size += chunk.length;
|
|
201
|
+
h.update(chunk);
|
|
202
|
+
});
|
|
203
|
+
stream.on("error", reject);
|
|
204
|
+
stream.on("end", () => {
|
|
205
|
+
resolve({ hash: h.digest("base64url"), size });
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async function extendDistRecord(sitePackagesDir, packageName, paths) {
|
|
210
|
+
const normalizedTarget = normalizePackageName(packageName);
|
|
211
|
+
const entries = await readdir(sitePackagesDir);
|
|
212
|
+
const distInfoDirName = entries.find((e) => {
|
|
213
|
+
if (!e.endsWith(".dist-info"))
|
|
214
|
+
return false;
|
|
215
|
+
const withoutSuffix = e.slice(0, -".dist-info".length);
|
|
216
|
+
const lastHyphen = withoutSuffix.lastIndexOf("-");
|
|
217
|
+
if (lastHyphen === -1)
|
|
218
|
+
return false;
|
|
219
|
+
const dirName = withoutSuffix.slice(0, lastHyphen);
|
|
220
|
+
return normalizePackageName(dirName) === normalizedTarget;
|
|
221
|
+
});
|
|
222
|
+
if (!distInfoDirName) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`No .dist-info directory found for package "${packageName}" in ${sitePackagesDir}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const recordPath = join2(sitePackagesDir, distInfoDirName, "RECORD");
|
|
228
|
+
let existingRecord;
|
|
229
|
+
try {
|
|
230
|
+
existingRecord = await readFile2(recordPath, "utf-8");
|
|
231
|
+
} catch {
|
|
232
|
+
throw new Error(`RECORD file not found in ${distInfoDirName}`);
|
|
233
|
+
}
|
|
234
|
+
const existingPaths = new Set(
|
|
235
|
+
existingRecord.split("\n").filter((line) => line.length > 0).map((line) => line.split(",")[0])
|
|
236
|
+
);
|
|
237
|
+
const newEntries = paths.filter((p) => !existingPaths.has(p));
|
|
238
|
+
if (newEntries.length > 0) {
|
|
239
|
+
const prefix = existingRecord.length > 0 && !existingRecord.endsWith("\n") ? "\n" : "";
|
|
240
|
+
const lines = [];
|
|
241
|
+
for (const p of newEntries) {
|
|
242
|
+
const fullPath = join2(sitePackagesDir, p);
|
|
243
|
+
const { hash, size } = await hashFile(fullPath);
|
|
244
|
+
lines.push(`${p},sha256=${hash},${size}`);
|
|
245
|
+
}
|
|
246
|
+
await appendFile(recordPath, prefix + lines.join("\n") + "\n");
|
|
247
|
+
}
|
|
248
|
+
return newEntries.length;
|
|
249
|
+
}
|
|
151
250
|
|
|
152
251
|
// src/manifest/package.ts
|
|
153
252
|
import path3 from "path";
|
|
@@ -371,42 +470,6 @@ var PipfileLikeSchema = pipfileLikeSchema;
|
|
|
371
470
|
var PipfileLockMetaSchema = pipfileLockMetaSchema.passthrough();
|
|
372
471
|
var PipfileLockLikeSchema = pipfileLockLikeSchema;
|
|
373
472
|
|
|
374
|
-
// src/manifest/pep508.ts
|
|
375
|
-
var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
|
|
376
|
-
function splitExtras(spec) {
|
|
377
|
-
const match = EXTRAS_REGEX.exec(spec);
|
|
378
|
-
if (!match) {
|
|
379
|
-
return [spec, void 0];
|
|
380
|
-
}
|
|
381
|
-
const extras = match[2].split(",").map((e) => e.trim());
|
|
382
|
-
return [match[1], extras];
|
|
383
|
-
}
|
|
384
|
-
function formatPep508(req) {
|
|
385
|
-
let result = req.name;
|
|
386
|
-
if (req.extras && req.extras.length > 0) {
|
|
387
|
-
result += `[${req.extras.join(",")}]`;
|
|
388
|
-
}
|
|
389
|
-
if (req.url) {
|
|
390
|
-
result += ` @ ${req.url}`;
|
|
391
|
-
} else if (req.version && req.version !== "*") {
|
|
392
|
-
result += req.version;
|
|
393
|
-
}
|
|
394
|
-
if (req.markers) {
|
|
395
|
-
result += ` ; ${req.markers}`;
|
|
396
|
-
}
|
|
397
|
-
return result;
|
|
398
|
-
}
|
|
399
|
-
function mergeExtras(existing, additional) {
|
|
400
|
-
const result = new Set(existing || []);
|
|
401
|
-
if (additional) {
|
|
402
|
-
const additionalArray = Array.isArray(additional) ? additional : [additional];
|
|
403
|
-
for (const extra of additionalArray) {
|
|
404
|
-
result.add(extra);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
return result.size > 0 ? Array.from(result) : void 0;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
473
|
// src/util/type.ts
|
|
411
474
|
function isPlainObject(value) {
|
|
412
475
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -599,17 +662,24 @@ function convertPipfileToPyprojectToml(pipfile) {
|
|
|
599
662
|
function convertPipfileLockToPyprojectToml(pipfileLock) {
|
|
600
663
|
const sources = {};
|
|
601
664
|
const pyproject = {};
|
|
665
|
+
const pythonVersion = pipfileLock._meta?.requires?.python_version;
|
|
602
666
|
const deps = [];
|
|
603
667
|
for (const dep of pipfileLockDepsToRequirements(pipfileLock.default || {})) {
|
|
604
668
|
deps.push(formatPep508(dep));
|
|
605
669
|
addDepSource(sources, dep);
|
|
606
670
|
}
|
|
607
|
-
if (deps.length > 0) {
|
|
608
|
-
|
|
671
|
+
if (deps.length > 0 || pythonVersion) {
|
|
672
|
+
const project = {
|
|
609
673
|
name: "app",
|
|
610
|
-
version: "0.1.0"
|
|
611
|
-
dependencies: deps
|
|
674
|
+
version: "0.1.0"
|
|
612
675
|
};
|
|
676
|
+
if (pythonVersion) {
|
|
677
|
+
project["requires-python"] = `==${pythonVersion}.*`;
|
|
678
|
+
}
|
|
679
|
+
if (deps.length > 0) {
|
|
680
|
+
project.dependencies = deps;
|
|
681
|
+
}
|
|
682
|
+
pyproject.project = project;
|
|
613
683
|
}
|
|
614
684
|
const devDeps = [];
|
|
615
685
|
for (const dep of pipfileLockDepsToRequirements(pipfileLock.develop || {})) {
|
|
@@ -1790,8 +1860,10 @@ async function discoverPythonPackage({
|
|
|
1790
1860
|
let workspaceManifest;
|
|
1791
1861
|
let workspaceLockFile;
|
|
1792
1862
|
if (manifests.length === 0) {
|
|
1863
|
+
const requiresPython2 = computeRequiresPython(void 0, void 0, configs);
|
|
1793
1864
|
return {
|
|
1794
|
-
configs
|
|
1865
|
+
configs,
|
|
1866
|
+
requiresPython: requiresPython2
|
|
1795
1867
|
};
|
|
1796
1868
|
} else {
|
|
1797
1869
|
entrypointManifest = manifests[0];
|
|
@@ -1825,37 +1897,44 @@ async function discoverPythonPackage({
|
|
|
1825
1897
|
}
|
|
1826
1898
|
function computeRequiresPython(manifest, workspaceManifest, configs) {
|
|
1827
1899
|
const constraints = [];
|
|
1900
|
+
let hasPythonVersionFile = false;
|
|
1828
1901
|
for (const configSet of configs) {
|
|
1829
1902
|
const pythonVersionConfig = configSet[".python-version" /* PythonVersion */];
|
|
1830
1903
|
if (pythonVersionConfig !== void 0) {
|
|
1831
1904
|
constraints.push({
|
|
1832
1905
|
request: pythonVersionConfig.data,
|
|
1833
|
-
source:
|
|
1906
|
+
source: pythonVersionConfig.path,
|
|
1907
|
+
prettySource: pythonVersionConfig.path
|
|
1834
1908
|
});
|
|
1909
|
+
hasPythonVersionFile = true;
|
|
1835
1910
|
break;
|
|
1836
1911
|
}
|
|
1837
1912
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
const request = pythonRequestFromConstraint(parsed);
|
|
1843
|
-
constraints.push({
|
|
1844
|
-
request: [request],
|
|
1845
|
-
source: `"requires-python" key in ${manifest.path}`
|
|
1846
|
-
});
|
|
1847
|
-
}
|
|
1848
|
-
} else {
|
|
1849
|
-
const workspaceRequiresPython = workspaceManifest?.data.project?.["requires-python"];
|
|
1850
|
-
if (workspaceRequiresPython) {
|
|
1851
|
-
const parsed = parse2(workspaceRequiresPython);
|
|
1913
|
+
if (!hasPythonVersionFile) {
|
|
1914
|
+
const manifestRequiresPython = manifest?.data.project?.["requires-python"];
|
|
1915
|
+
if (manifestRequiresPython) {
|
|
1916
|
+
const parsed = parse2(manifestRequiresPython);
|
|
1852
1917
|
if (parsed?.length) {
|
|
1853
1918
|
const request = pythonRequestFromConstraint(parsed);
|
|
1854
1919
|
constraints.push({
|
|
1855
1920
|
request: [request],
|
|
1856
|
-
source:
|
|
1921
|
+
source: manifest.path,
|
|
1922
|
+
prettySource: `"requires-python" key in ${manifest.path}`
|
|
1857
1923
|
});
|
|
1858
1924
|
}
|
|
1925
|
+
} else {
|
|
1926
|
+
const workspaceRequiresPython = workspaceManifest?.data.project?.["requires-python"];
|
|
1927
|
+
if (workspaceRequiresPython) {
|
|
1928
|
+
const parsed = parse2(workspaceRequiresPython);
|
|
1929
|
+
if (parsed?.length) {
|
|
1930
|
+
const request = pythonRequestFromConstraint(parsed);
|
|
1931
|
+
constraints.push({
|
|
1932
|
+
request: [request],
|
|
1933
|
+
source: workspaceManifest.path,
|
|
1934
|
+
prettySource: `"requires-python" key in ${workspaceManifest.path}`
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1859
1938
|
}
|
|
1860
1939
|
}
|
|
1861
1940
|
return constraints;
|
|
@@ -2204,7 +2283,7 @@ function isPrivatePackageSource(source) {
|
|
|
2204
2283
|
}
|
|
2205
2284
|
return false;
|
|
2206
2285
|
}
|
|
2207
|
-
function
|
|
2286
|
+
function normalizePackageName2(name) {
|
|
2208
2287
|
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
2209
2288
|
}
|
|
2210
2289
|
function classifyPackages(options) {
|
|
@@ -2212,9 +2291,9 @@ function classifyPackages(options) {
|
|
|
2212
2291
|
const privatePackages = [];
|
|
2213
2292
|
const publicPackages = [];
|
|
2214
2293
|
const packageVersions = {};
|
|
2215
|
-
const excludeSet = new Set(excludePackages.map(
|
|
2294
|
+
const excludeSet = new Set(excludePackages.map(normalizePackageName2));
|
|
2216
2295
|
for (const pkg of lockFile.packages) {
|
|
2217
|
-
if (excludeSet.has(
|
|
2296
|
+
if (excludeSet.has(normalizePackageName2(pkg.name))) {
|
|
2218
2297
|
continue;
|
|
2219
2298
|
}
|
|
2220
2299
|
packageVersions[pkg.name] = pkg.version;
|
|
@@ -2267,13 +2346,15 @@ function selectPython(constraints, available) {
|
|
|
2267
2346
|
}
|
|
2268
2347
|
}
|
|
2269
2348
|
if (constraintsWithMatches.length > 1) {
|
|
2270
|
-
const sources = constraintsWithMatches.map(
|
|
2349
|
+
const sources = constraintsWithMatches.map(
|
|
2350
|
+
(i) => constraints[i].prettySource
|
|
2351
|
+
);
|
|
2271
2352
|
warnings.push(
|
|
2272
2353
|
`Python version constraints may not overlap: ${sources.join(", ")}`
|
|
2273
2354
|
);
|
|
2274
2355
|
}
|
|
2275
2356
|
}
|
|
2276
|
-
const constraintDescriptions = constraints.map((c) => c.
|
|
2357
|
+
const constraintDescriptions = constraints.map((c) => c.prettySource).join(", ");
|
|
2277
2358
|
errors.push(
|
|
2278
2359
|
`No Python build satisfies all constraints: ${constraintDescriptions}`
|
|
2279
2360
|
);
|
|
@@ -2283,6 +2364,126 @@ function selectPython(constraints, available) {
|
|
|
2283
2364
|
warnings: warnings.length > 0 ? warnings : void 0
|
|
2284
2365
|
};
|
|
2285
2366
|
}
|
|
2367
|
+
function selectPythonVersion({
|
|
2368
|
+
constraints,
|
|
2369
|
+
availableBuilds,
|
|
2370
|
+
allBuilds,
|
|
2371
|
+
defaultBuild,
|
|
2372
|
+
majorMinorOnly,
|
|
2373
|
+
legacyTildeEquals
|
|
2374
|
+
}) {
|
|
2375
|
+
const source = constraints?.[0]?.source;
|
|
2376
|
+
if (!constraints || constraints.length === 0) {
|
|
2377
|
+
return { build: defaultBuild };
|
|
2378
|
+
}
|
|
2379
|
+
let effectiveConstraints = majorMinorOnly ? constraints.map((c) => ({
|
|
2380
|
+
...c,
|
|
2381
|
+
request: c.request.map(truncatePatchVersionsInRequest)
|
|
2382
|
+
})) : constraints;
|
|
2383
|
+
if (legacyTildeEquals) {
|
|
2384
|
+
effectiveConstraints = effectiveConstraints.map((c) => ({
|
|
2385
|
+
...c,
|
|
2386
|
+
request: c.request.map(legacyTildeEqualsTransform)
|
|
2387
|
+
}));
|
|
2388
|
+
}
|
|
2389
|
+
const result = selectPython(effectiveConstraints, availableBuilds);
|
|
2390
|
+
if (result.build) {
|
|
2391
|
+
return { build: result.build, source };
|
|
2392
|
+
}
|
|
2393
|
+
const allResult = selectPython(effectiveConstraints, allBuilds);
|
|
2394
|
+
if (allResult.build) {
|
|
2395
|
+
const version = pythonVersionToString(allResult.build.version);
|
|
2396
|
+
return {
|
|
2397
|
+
build: defaultBuild,
|
|
2398
|
+
source,
|
|
2399
|
+
notAvailable: { build: allResult.build, version }
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
const versionString = extractConstraintVersionString(constraints);
|
|
2403
|
+
return {
|
|
2404
|
+
build: defaultBuild,
|
|
2405
|
+
source,
|
|
2406
|
+
invalidConstraint: { versionString }
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
function extractConstraintVersionString(constraints) {
|
|
2410
|
+
for (const c of constraints) {
|
|
2411
|
+
for (const req of c.request) {
|
|
2412
|
+
if (req.version?.constraint && req.version.constraint.length > 0) {
|
|
2413
|
+
const specs = req.version.constraint;
|
|
2414
|
+
if (specs.length === 1 && specs[0].operator === "==" && (!specs[0].prefix || specs[0].prefix === ".*")) {
|
|
2415
|
+
return specs[0].version;
|
|
2416
|
+
}
|
|
2417
|
+
return pep440ConstraintsToString(specs);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
return "unknown";
|
|
2422
|
+
}
|
|
2423
|
+
function truncatePatchVersionsInRequest(req) {
|
|
2424
|
+
if (!req.version?.constraint || req.version.constraint.length === 0) {
|
|
2425
|
+
return req;
|
|
2426
|
+
}
|
|
2427
|
+
const newConstraints = [];
|
|
2428
|
+
for (const c of req.version.constraint) {
|
|
2429
|
+
const result = truncatePatchConstraint(c);
|
|
2430
|
+
if (result !== null) {
|
|
2431
|
+
newConstraints.push(result);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
const newVersion = {
|
|
2435
|
+
...req.version,
|
|
2436
|
+
constraint: newConstraints
|
|
2437
|
+
};
|
|
2438
|
+
return { ...req, version: newVersion };
|
|
2439
|
+
}
|
|
2440
|
+
function truncatePatchConstraint(c) {
|
|
2441
|
+
if (c.operator === "===") {
|
|
2442
|
+
return c;
|
|
2443
|
+
}
|
|
2444
|
+
const parts = c.version.split(".");
|
|
2445
|
+
if (parts.length < 3) {
|
|
2446
|
+
return c;
|
|
2447
|
+
}
|
|
2448
|
+
const majorMinor = parts.slice(0, 2).join(".");
|
|
2449
|
+
const patch = parseInt(parts[2], 10);
|
|
2450
|
+
switch (c.operator) {
|
|
2451
|
+
case "==":
|
|
2452
|
+
return { operator: "==", version: majorMinor, prefix: ".*" };
|
|
2453
|
+
case "!=":
|
|
2454
|
+
return null;
|
|
2455
|
+
case "~=":
|
|
2456
|
+
return { operator: "==", version: majorMinor, prefix: ".*" };
|
|
2457
|
+
case ">=":
|
|
2458
|
+
return { operator: ">=", version: majorMinor, prefix: "" };
|
|
2459
|
+
case "<=":
|
|
2460
|
+
return { operator: "<=", version: majorMinor, prefix: "" };
|
|
2461
|
+
case ">":
|
|
2462
|
+
return patch === 0 ? { operator: ">", version: majorMinor, prefix: "" } : { operator: ">=", version: majorMinor, prefix: "" };
|
|
2463
|
+
case "<":
|
|
2464
|
+
return patch === 0 ? { operator: "<", version: majorMinor, prefix: "" } : { operator: "<=", version: majorMinor, prefix: "" };
|
|
2465
|
+
default:
|
|
2466
|
+
return c;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
function legacyTildeEqualsTransform(req) {
|
|
2470
|
+
if (!req.version?.constraint || req.version.constraint.length === 0) {
|
|
2471
|
+
return req;
|
|
2472
|
+
}
|
|
2473
|
+
const newConstraints = req.version.constraint.map((c) => {
|
|
2474
|
+
if (c.operator !== "~=")
|
|
2475
|
+
return c;
|
|
2476
|
+
const parts = c.version.split(".");
|
|
2477
|
+
if (parts.length === 2) {
|
|
2478
|
+
return { operator: "==", version: c.version, prefix: ".*" };
|
|
2479
|
+
}
|
|
2480
|
+
return c;
|
|
2481
|
+
});
|
|
2482
|
+
return {
|
|
2483
|
+
...req,
|
|
2484
|
+
version: { ...req.version, constraint: newConstraints }
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2286
2487
|
function pythonVersionToString(version) {
|
|
2287
2488
|
let str = `${version.major}.${version.minor}`;
|
|
2288
2489
|
if (version.patch !== void 0) {
|
|
@@ -2294,7 +2495,7 @@ function pythonVersionToString(version) {
|
|
|
2294
2495
|
return str;
|
|
2295
2496
|
}
|
|
2296
2497
|
function pep440ConstraintsToString(constraints) {
|
|
2297
|
-
return constraints.map((c) => `${c.operator}${c.
|
|
2498
|
+
return constraints.map((c) => `${c.operator}${c.version}${c.prefix ?? ""}`).join(",");
|
|
2298
2499
|
}
|
|
2299
2500
|
function implementationsMatch(buildImpl, requestImpl) {
|
|
2300
2501
|
if (PythonImplementation.isUnknown(buildImpl)) {
|
|
@@ -2413,12 +2614,14 @@ export {
|
|
|
2413
2614
|
containsAppOrHandler,
|
|
2414
2615
|
createMinimalManifest,
|
|
2415
2616
|
discoverPythonPackage,
|
|
2617
|
+
extendDistRecord,
|
|
2416
2618
|
getStringConstant,
|
|
2417
2619
|
isPrivatePackageSource,
|
|
2418
|
-
normalizePackageName,
|
|
2620
|
+
normalizePackageName2 as normalizePackageName,
|
|
2419
2621
|
parseDjangoSettingsModule,
|
|
2420
2622
|
parseUvLock,
|
|
2421
2623
|
scanDistributions,
|
|
2422
2624
|
selectPython,
|
|
2625
|
+
selectPythonVersion,
|
|
2423
2626
|
stringifyManifest
|
|
2424
2627
|
};
|
|
@@ -101,3 +101,17 @@ export type DistributionIndex = Map<string, Distribution>;
|
|
|
101
101
|
* @returns Map of normalized package name to distribution info
|
|
102
102
|
*/
|
|
103
103
|
export declare function scanDistributions(sitePackagesDir: string): Promise<DistributionIndex>;
|
|
104
|
+
/**
|
|
105
|
+
* Append new file entries to a distribution's RECORD file.
|
|
106
|
+
*
|
|
107
|
+
* Finds the `.dist-info` directory for the given package, reads its RECORD,
|
|
108
|
+
* and appends any paths not already tracked. The caller is responsible for
|
|
109
|
+
* scanning directories and building the path list.
|
|
110
|
+
*
|
|
111
|
+
* @param sitePackagesDir - Absolute path to a site-packages directory
|
|
112
|
+
* @param packageName - Distribution name (matched via PEP 503 normalization)
|
|
113
|
+
* @param paths - File paths relative to site-packages to add
|
|
114
|
+
* @returns Count of new entries appended (skips paths already in RECORD)
|
|
115
|
+
* @throws If the dist-info directory or RECORD file is not found
|
|
116
|
+
*/
|
|
117
|
+
export declare function extendDistRecord(sitePackagesDir: string, packageName: string, paths: string[]): Promise<number>;
|
|
@@ -20,6 +20,47 @@ export interface PythonSelectionResult {
|
|
|
20
20
|
* @returns The first matching build, or null with errors/warnings if none match
|
|
21
21
|
*/
|
|
22
22
|
export declare function selectPython(constraints: PythonConstraint[], available: PythonBuild[]): PythonSelectionResult;
|
|
23
|
+
/**
|
|
24
|
+
* Result of the higher-level selectPythonVersion function.
|
|
25
|
+
*/
|
|
26
|
+
export interface PythonVersionSelectionResult {
|
|
27
|
+
/** The selected build. Falls back to defaultBuild if no constraints match. */
|
|
28
|
+
build: PythonBuild;
|
|
29
|
+
/** Source file where the constraint originated (e.g. "pyproject.toml"). */
|
|
30
|
+
source?: string;
|
|
31
|
+
/** Diagnostic indicating the constraint was found but the build isn't in availableBuilds. */
|
|
32
|
+
notAvailable?: {
|
|
33
|
+
build: PythonBuild;
|
|
34
|
+
/** The version string of the unavailable build. */
|
|
35
|
+
version: string;
|
|
36
|
+
};
|
|
37
|
+
/** Diagnostic indicating no build matches the constraint at all. */
|
|
38
|
+
invalidConstraint?: {
|
|
39
|
+
/** Human-readable version string from the constraint. */
|
|
40
|
+
versionString: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Higher-level Python version selection with two-pass matching and diagnostics.
|
|
45
|
+
*
|
|
46
|
+
* First tries to match constraints against availableBuilds. If no match,
|
|
47
|
+
* tries against allBuilds to produce diagnostic information. Falls back
|
|
48
|
+
* to defaultBuild if no constraints are provided or no match is found.
|
|
49
|
+
*/
|
|
50
|
+
export declare function selectPythonVersion({ constraints, availableBuilds, allBuilds, defaultBuild, majorMinorOnly, legacyTildeEquals, }: {
|
|
51
|
+
constraints?: PythonConstraint[];
|
|
52
|
+
availableBuilds: PythonBuild[];
|
|
53
|
+
allBuilds: PythonBuild[];
|
|
54
|
+
defaultBuild: PythonBuild;
|
|
55
|
+
majorMinorOnly?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* When true, treat 2-part compatible-release specifiers (`~=X.Y`) as
|
|
58
|
+
* pinning to exactly that minor version (`==X.Y.*`) rather than the
|
|
59
|
+
* PEP 440 correct `>=X.Y, <(X+1).0`. This preserves the historical
|
|
60
|
+
* behaviour of the Python builder prior to the python-analysis migration.
|
|
61
|
+
*/
|
|
62
|
+
legacyTildeEquals?: boolean;
|
|
63
|
+
}): PythonVersionSelectionResult;
|
|
23
64
|
/**
|
|
24
65
|
* Convert a PythonVersion to a string suitable for PEP 440 comparison.
|
|
25
66
|
*
|
|
@@ -51,8 +51,10 @@ export interface PythonVersionRequest {
|
|
|
51
51
|
export interface PythonConstraint {
|
|
52
52
|
/** The Python version request(s) that define this constraint. */
|
|
53
53
|
request: PythonRequest[];
|
|
54
|
-
/**
|
|
54
|
+
/** Config file name where this constraint originated (e.g. "pyproject.toml", ".python-version"). */
|
|
55
55
|
source: string;
|
|
56
|
+
/** Human-readable description of where this constraint came from. */
|
|
57
|
+
prettySource: string;
|
|
56
58
|
}
|
|
57
59
|
export type PythonVersion = {
|
|
58
60
|
major: number;
|