@vercel/python-analysis 0.3.2 → 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/dist/index.cjs +314 -15
- package/dist/index.d.ts +3 -2
- package/dist/index.js +310 -14
- package/dist/manifest/package.d.ts +24 -0
- package/dist/manifest/pyproject/schema.zod.d.ts +16 -0
- package/dist/manifest/requirements-txt-parser.d.ts +4 -0
- package/dist/manifest/serialize.d.ts +22 -0
- package/dist/manifest/uv-config/schema.zod.d.ts +8 -0
- package/dist/manifest/uv-config/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(src_exports, {
|
|
|
52
52
|
PythonBuild: () => PythonBuild,
|
|
53
53
|
PythonConfigKind: () => PythonConfigKind,
|
|
54
54
|
PythonImplementation: () => PythonImplementation,
|
|
55
|
+
PythonLockFileKind: () => PythonLockFileKind,
|
|
55
56
|
PythonManifestConvertedKind: () => PythonManifestConvertedKind,
|
|
56
57
|
PythonManifestKind: () => PythonManifestKind,
|
|
57
58
|
PythonVariant: () => PythonVariant,
|
|
@@ -62,8 +63,10 @@ __export(src_exports, {
|
|
|
62
63
|
UvConfigWorkspaceSchema: () => UvConfigWorkspaceSchema,
|
|
63
64
|
UvIndexEntrySchema: () => UvIndexEntrySchema,
|
|
64
65
|
containsAppOrHandler: () => containsAppOrHandler,
|
|
66
|
+
createMinimalManifest: () => createMinimalManifest,
|
|
65
67
|
discoverPythonPackage: () => discoverPythonPackage,
|
|
66
|
-
selectPython: () => selectPython
|
|
68
|
+
selectPython: () => selectPython,
|
|
69
|
+
stringifyManifest: () => stringifyManifest
|
|
67
70
|
});
|
|
68
71
|
module.exports = __toCommonJS(src_exports);
|
|
69
72
|
|
|
@@ -501,6 +504,8 @@ function convertPipfileToPyprojectToml(pipfile) {
|
|
|
501
504
|
}
|
|
502
505
|
if (deps.length > 0) {
|
|
503
506
|
pyproject.project = {
|
|
507
|
+
name: "app",
|
|
508
|
+
version: "0.1.0",
|
|
504
509
|
dependencies: deps
|
|
505
510
|
};
|
|
506
511
|
}
|
|
@@ -558,6 +563,8 @@ function convertPipfileLockToPyprojectToml(pipfileLock) {
|
|
|
558
563
|
}
|
|
559
564
|
if (deps.length > 0) {
|
|
560
565
|
pyproject.project = {
|
|
566
|
+
name: "app",
|
|
567
|
+
version: "0.1.0",
|
|
561
568
|
dependencies: deps
|
|
562
569
|
};
|
|
563
570
|
}
|
|
@@ -634,7 +641,8 @@ var uvIndexEntrySchema = import_zod3.z.object({
|
|
|
634
641
|
name: import_zod3.z.string(),
|
|
635
642
|
url: import_zod3.z.string(),
|
|
636
643
|
default: import_zod3.z.boolean().optional(),
|
|
637
|
-
explicit: import_zod3.z.boolean().optional()
|
|
644
|
+
explicit: import_zod3.z.boolean().optional(),
|
|
645
|
+
format: import_zod3.z.string().optional()
|
|
638
646
|
});
|
|
639
647
|
var uvConfigSchema = import_zod3.z.object({
|
|
640
648
|
sources: import_zod3.z.record(import_zod3.z.union([dependencySourceSchema, import_zod3.z.array(dependencySourceSchema)])).optional(),
|
|
@@ -708,6 +716,7 @@ var import_node_path4 = require("path");
|
|
|
708
716
|
var import_pip_requirements_js = require("pip-requirements-js");
|
|
709
717
|
var PRIMARY_INDEX_NAME = "primary";
|
|
710
718
|
var EXTRA_INDEX_PREFIX = "extra-";
|
|
719
|
+
var FIND_LINKS_PREFIX = "find-links-";
|
|
711
720
|
function parseGitUrl(url) {
|
|
712
721
|
if (!url.startsWith("git+")) {
|
|
713
722
|
return null;
|
|
@@ -749,6 +758,8 @@ function extractPipArguments(fileContent) {
|
|
|
749
758
|
};
|
|
750
759
|
const lines = fileContent.split(/\r?\n/);
|
|
751
760
|
const cleanedLines = [];
|
|
761
|
+
const pathRequirements = [];
|
|
762
|
+
const editableRequirements = [];
|
|
752
763
|
for (let i = 0; i < lines.length; i++) {
|
|
753
764
|
const line = lines[i];
|
|
754
765
|
const trimmed = line.trim();
|
|
@@ -763,11 +774,21 @@ function extractPipArguments(fileContent) {
|
|
|
763
774
|
fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
|
|
764
775
|
}
|
|
765
776
|
const extracted = tryExtractPipArgument(fullLine, options);
|
|
766
|
-
if (extracted) {
|
|
777
|
+
if (extracted === true) {
|
|
778
|
+
i += linesConsumed;
|
|
779
|
+
} else if (typeof extracted === "object" && extracted.editable) {
|
|
780
|
+
editableRequirements.push(extracted.editable);
|
|
767
781
|
i += linesConsumed;
|
|
768
782
|
} else {
|
|
783
|
+
if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
|
|
784
|
+
i += linesConsumed;
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
769
787
|
const strippedLine = stripInlineHashes(fullLine);
|
|
770
|
-
|
|
788
|
+
const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
|
|
789
|
+
if (isPathOrUrlRequirement(effectiveLine)) {
|
|
790
|
+
pathRequirements.push(effectiveLine);
|
|
791
|
+
} else if (strippedLine !== fullLine) {
|
|
771
792
|
cleanedLines.push(strippedLine);
|
|
772
793
|
} else {
|
|
773
794
|
cleanedLines.push(line);
|
|
@@ -780,7 +801,9 @@ function extractPipArguments(fileContent) {
|
|
|
780
801
|
}
|
|
781
802
|
return {
|
|
782
803
|
cleanedContent: cleanedLines.join("\n"),
|
|
783
|
-
options
|
|
804
|
+
options,
|
|
805
|
+
pathRequirements,
|
|
806
|
+
editableRequirements
|
|
784
807
|
};
|
|
785
808
|
}
|
|
786
809
|
function tryExtractPipArgument(line, options) {
|
|
@@ -819,6 +842,46 @@ function tryExtractPipArgument(line, options) {
|
|
|
819
842
|
return true;
|
|
820
843
|
}
|
|
821
844
|
}
|
|
845
|
+
if (line.startsWith("--editable")) {
|
|
846
|
+
const path4 = extractArgValue(line, "--editable");
|
|
847
|
+
if (path4) {
|
|
848
|
+
return { editable: path4 };
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (line.startsWith("-e ") || line.startsWith("-e ")) {
|
|
852
|
+
const path4 = line.slice(2).trim();
|
|
853
|
+
if (path4) {
|
|
854
|
+
return { editable: path4 };
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (line.startsWith("--find-links")) {
|
|
858
|
+
const url = extractArgValue(line, "--find-links");
|
|
859
|
+
if (url) {
|
|
860
|
+
if (!options.findLinks)
|
|
861
|
+
options.findLinks = [];
|
|
862
|
+
options.findLinks.push(url);
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (line.startsWith("-f ") || line.startsWith("-f ")) {
|
|
867
|
+
const match = line.match(/^-f\s+(\S+)/);
|
|
868
|
+
if (match) {
|
|
869
|
+
if (!options.findLinks)
|
|
870
|
+
options.findLinks = [];
|
|
871
|
+
options.findLinks.push(match[1]);
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (line === "--no-index" || line.startsWith("--no-index ")) {
|
|
876
|
+
options.noIndex = true;
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
if (line.startsWith("--")) {
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
822
885
|
return false;
|
|
823
886
|
}
|
|
824
887
|
function stripInlineHashes(line) {
|
|
@@ -834,15 +897,158 @@ function extractInlineHashes(line) {
|
|
|
834
897
|
return hashes;
|
|
835
898
|
}
|
|
836
899
|
function extractArgValue(line, option) {
|
|
900
|
+
let value = null;
|
|
837
901
|
if (line.startsWith(`${option}=`)) {
|
|
838
|
-
|
|
839
|
-
|
|
902
|
+
value = line.slice(option.length + 1).trim();
|
|
903
|
+
} else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
|
|
904
|
+
value = line.slice(option.length).trim();
|
|
840
905
|
}
|
|
841
|
-
if (
|
|
842
|
-
|
|
843
|
-
|
|
906
|
+
if (!value)
|
|
907
|
+
return null;
|
|
908
|
+
const commentIdx = value.indexOf(" #");
|
|
909
|
+
if (commentIdx !== -1) {
|
|
910
|
+
value = value.slice(0, commentIdx).trim();
|
|
844
911
|
}
|
|
845
|
-
return null;
|
|
912
|
+
return value || null;
|
|
913
|
+
}
|
|
914
|
+
function isPathOrUrlRequirement(line) {
|
|
915
|
+
if (line.startsWith("./") || line.startsWith("../"))
|
|
916
|
+
return true;
|
|
917
|
+
if (line.startsWith("/"))
|
|
918
|
+
return true;
|
|
919
|
+
if (line.startsWith("~/"))
|
|
920
|
+
return true;
|
|
921
|
+
if (/^(https?|ftp|file):\/\//i.test(line))
|
|
922
|
+
return true;
|
|
923
|
+
if (isBareArchiveFilename(line))
|
|
924
|
+
return true;
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
function isBareArchiveFilename(line) {
|
|
928
|
+
if (line.includes(" @ "))
|
|
929
|
+
return false;
|
|
930
|
+
let check = line;
|
|
931
|
+
const commentIdx = check.indexOf(" #");
|
|
932
|
+
if (commentIdx !== -1)
|
|
933
|
+
check = check.slice(0, commentIdx);
|
|
934
|
+
const markerIdx = check.indexOf(" ;");
|
|
935
|
+
if (markerIdx !== -1)
|
|
936
|
+
check = check.slice(0, markerIdx);
|
|
937
|
+
const extrasIdx = check.indexOf("[");
|
|
938
|
+
if (extrasIdx !== -1)
|
|
939
|
+
check = check.slice(0, extrasIdx);
|
|
940
|
+
check = check.trim();
|
|
941
|
+
return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
|
|
942
|
+
}
|
|
943
|
+
function parseWheelFilename(filename) {
|
|
944
|
+
if (!filename.endsWith(".whl"))
|
|
945
|
+
return null;
|
|
946
|
+
const stem = filename.slice(0, -4);
|
|
947
|
+
const parts = stem.split("-");
|
|
948
|
+
if (parts.length < 5 || parts.length > 6)
|
|
949
|
+
return null;
|
|
950
|
+
const name = parts[0];
|
|
951
|
+
const version = parts[1];
|
|
952
|
+
if (!name || !version)
|
|
953
|
+
return null;
|
|
954
|
+
return {
|
|
955
|
+
name: name.replace(/_/g, "-"),
|
|
956
|
+
version
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function parseSdistFilename(filename) {
|
|
960
|
+
let stem = filename;
|
|
961
|
+
for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
|
|
962
|
+
if (stem.endsWith(ext)) {
|
|
963
|
+
stem = stem.slice(0, -ext.length);
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (stem === filename)
|
|
968
|
+
return null;
|
|
969
|
+
const parts = stem.split("-");
|
|
970
|
+
let versionIdx = -1;
|
|
971
|
+
for (let i = 1; i < parts.length; i++) {
|
|
972
|
+
if (/^\d/.test(parts[i])) {
|
|
973
|
+
versionIdx = i;
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (versionIdx === -1)
|
|
978
|
+
return null;
|
|
979
|
+
return {
|
|
980
|
+
name: parts.slice(0, versionIdx).join("-"),
|
|
981
|
+
version: parts.slice(versionIdx).join("-")
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function normalizePathRequirement(rawLine) {
|
|
985
|
+
let line = rawLine;
|
|
986
|
+
const commentIdx = line.indexOf(" #");
|
|
987
|
+
if (commentIdx !== -1) {
|
|
988
|
+
line = line.slice(0, commentIdx).trim();
|
|
989
|
+
}
|
|
990
|
+
let markers;
|
|
991
|
+
const markerIdx = line.indexOf(" ;");
|
|
992
|
+
if (markerIdx !== -1) {
|
|
993
|
+
markers = line.slice(markerIdx + 2).trim();
|
|
994
|
+
line = line.slice(0, markerIdx).trim();
|
|
995
|
+
}
|
|
996
|
+
let extras;
|
|
997
|
+
const extrasMatch = line.match(/\[([^\]]+)\]$/);
|
|
998
|
+
if (extrasMatch) {
|
|
999
|
+
extras = extrasMatch[1].split(",").map((e) => e.trim());
|
|
1000
|
+
line = line.slice(0, extrasMatch.index).trim();
|
|
1001
|
+
}
|
|
1002
|
+
const isUrl = /^(https?|ftp|file):\/\//i.test(line);
|
|
1003
|
+
let filename;
|
|
1004
|
+
if (isUrl) {
|
|
1005
|
+
try {
|
|
1006
|
+
const url = new URL(line);
|
|
1007
|
+
filename = url.pathname.split("/").pop() || "";
|
|
1008
|
+
} catch {
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
} else {
|
|
1012
|
+
const cleanPath = line.replace(/\/+$/, "");
|
|
1013
|
+
filename = cleanPath.split("/").pop() || "";
|
|
1014
|
+
}
|
|
1015
|
+
if (!filename)
|
|
1016
|
+
return null;
|
|
1017
|
+
let name;
|
|
1018
|
+
let version;
|
|
1019
|
+
const wheelParsed = parseWheelFilename(filename);
|
|
1020
|
+
if (wheelParsed) {
|
|
1021
|
+
name = wheelParsed.name;
|
|
1022
|
+
version = wheelParsed.version;
|
|
1023
|
+
}
|
|
1024
|
+
if (!name) {
|
|
1025
|
+
const sdistParsed = parseSdistFilename(filename);
|
|
1026
|
+
if (sdistParsed) {
|
|
1027
|
+
name = sdistParsed.name;
|
|
1028
|
+
version = sdistParsed.version;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (!name) {
|
|
1032
|
+
name = filename.replace(/[-_.]+/g, "-").toLowerCase();
|
|
1033
|
+
}
|
|
1034
|
+
if (!name)
|
|
1035
|
+
return null;
|
|
1036
|
+
const req = { name };
|
|
1037
|
+
if (version) {
|
|
1038
|
+
req.version = `==${version}`;
|
|
1039
|
+
}
|
|
1040
|
+
if (extras && extras.length > 0) {
|
|
1041
|
+
req.extras = extras;
|
|
1042
|
+
}
|
|
1043
|
+
if (markers) {
|
|
1044
|
+
req.markers = markers;
|
|
1045
|
+
}
|
|
1046
|
+
if (isUrl) {
|
|
1047
|
+
req.url = line;
|
|
1048
|
+
} else {
|
|
1049
|
+
req.source = { path: line };
|
|
1050
|
+
}
|
|
1051
|
+
return req;
|
|
846
1052
|
}
|
|
847
1053
|
function convertRequirementsToPyprojectToml(fileContent, readFile3) {
|
|
848
1054
|
const pyproject = {};
|
|
@@ -861,6 +1067,8 @@ function convertRequirementsToPyprojectToml(fileContent, readFile3) {
|
|
|
861
1067
|
}
|
|
862
1068
|
if (deps.length > 0) {
|
|
863
1069
|
pyproject.project = {
|
|
1070
|
+
name: "app",
|
|
1071
|
+
version: "0.1.0",
|
|
864
1072
|
dependencies: deps
|
|
865
1073
|
};
|
|
866
1074
|
}
|
|
@@ -892,6 +1100,15 @@ function buildIndexEntries(pipOptions) {
|
|
|
892
1100
|
url: pipOptions.extraIndexUrls[i]
|
|
893
1101
|
});
|
|
894
1102
|
}
|
|
1103
|
+
if (pipOptions.findLinks) {
|
|
1104
|
+
for (let i = 0; i < pipOptions.findLinks.length; i++) {
|
|
1105
|
+
indexes.push({
|
|
1106
|
+
name: `${FIND_LINKS_PREFIX}${i + 1}`,
|
|
1107
|
+
url: pipOptions.findLinks[i],
|
|
1108
|
+
format: "flat"
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
895
1112
|
return indexes;
|
|
896
1113
|
}
|
|
897
1114
|
function parseRequirementsFile(fileContent, readFile3) {
|
|
@@ -899,7 +1116,7 @@ function parseRequirementsFile(fileContent, readFile3) {
|
|
|
899
1116
|
return parseRequirementsFileInternal(fileContent, readFile3, visited);
|
|
900
1117
|
}
|
|
901
1118
|
function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
902
|
-
const { cleanedContent, options } = extractPipArguments(fileContent);
|
|
1119
|
+
const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
|
|
903
1120
|
const hashMap = buildHashMap(fileContent);
|
|
904
1121
|
const requirements = (0, import_pip_requirements_js.parsePipRequirementsFile)(cleanedContent);
|
|
905
1122
|
const normalized = [];
|
|
@@ -907,7 +1124,9 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
907
1124
|
requirementFiles: [...options.requirementFiles],
|
|
908
1125
|
constraintFiles: [...options.constraintFiles],
|
|
909
1126
|
indexUrl: options.indexUrl,
|
|
910
|
-
extraIndexUrls: [...options.extraIndexUrls]
|
|
1127
|
+
extraIndexUrls: [...options.extraIndexUrls],
|
|
1128
|
+
findLinks: options.findLinks ? [...options.findLinks] : void 0,
|
|
1129
|
+
noIndex: options.noIndex
|
|
911
1130
|
};
|
|
912
1131
|
for (const req of requirements) {
|
|
913
1132
|
if (req.type === "RequirementsFile") {
|
|
@@ -927,6 +1146,23 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
927
1146
|
normalized.push(norm);
|
|
928
1147
|
}
|
|
929
1148
|
}
|
|
1149
|
+
for (const rawPath of pathRequirements) {
|
|
1150
|
+
const norm = normalizePathRequirement(rawPath);
|
|
1151
|
+
if (norm != null) {
|
|
1152
|
+
normalized.push(norm);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
for (const rawPath of editableRequirements) {
|
|
1156
|
+
const norm = normalizePathRequirement(rawPath);
|
|
1157
|
+
if (norm != null) {
|
|
1158
|
+
if (norm.source) {
|
|
1159
|
+
norm.source.editable = true;
|
|
1160
|
+
} else {
|
|
1161
|
+
norm.source = { path: rawPath, editable: true };
|
|
1162
|
+
}
|
|
1163
|
+
normalized.push(norm);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
930
1166
|
if (readFile3) {
|
|
931
1167
|
for (const refPath of mergedOptions.requirementFiles) {
|
|
932
1168
|
const refPathKey = (0, import_node_path4.normalize)(refPath);
|
|
@@ -963,6 +1199,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
963
1199
|
mergedOptions.constraintFiles.push(constraintPath);
|
|
964
1200
|
}
|
|
965
1201
|
}
|
|
1202
|
+
if (refParsed.pipOptions.findLinks) {
|
|
1203
|
+
if (!mergedOptions.findLinks)
|
|
1204
|
+
mergedOptions.findLinks = [];
|
|
1205
|
+
for (const fl of refParsed.pipOptions.findLinks) {
|
|
1206
|
+
if (!mergedOptions.findLinks.includes(fl)) {
|
|
1207
|
+
mergedOptions.findLinks.push(fl);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (refParsed.pipOptions.noIndex) {
|
|
1212
|
+
mergedOptions.noIndex = true;
|
|
1213
|
+
}
|
|
966
1214
|
}
|
|
967
1215
|
}
|
|
968
1216
|
}
|
|
@@ -1451,6 +1699,11 @@ var PythonManifestKind = /* @__PURE__ */ ((PythonManifestKind2) => {
|
|
|
1451
1699
|
PythonManifestKind2["PyProjectToml"] = "pyproject.toml";
|
|
1452
1700
|
return PythonManifestKind2;
|
|
1453
1701
|
})(PythonManifestKind || {});
|
|
1702
|
+
var PythonLockFileKind = /* @__PURE__ */ ((PythonLockFileKind2) => {
|
|
1703
|
+
PythonLockFileKind2["UvLock"] = "uv.lock";
|
|
1704
|
+
PythonLockFileKind2["PylockToml"] = "pylock.toml";
|
|
1705
|
+
return PythonLockFileKind2;
|
|
1706
|
+
})(PythonLockFileKind || {});
|
|
1454
1707
|
var PythonManifestConvertedKind = /* @__PURE__ */ ((PythonManifestConvertedKind2) => {
|
|
1455
1708
|
PythonManifestConvertedKind2["Pipfile"] = "Pipfile";
|
|
1456
1709
|
PythonManifestConvertedKind2["PipfileLock"] = "Pipfile.lock";
|
|
@@ -1492,6 +1745,7 @@ async function discoverPythonPackage({
|
|
|
1492
1745
|
}
|
|
1493
1746
|
let entrypointManifest;
|
|
1494
1747
|
let workspaceManifest;
|
|
1748
|
+
let workspaceLockFile;
|
|
1495
1749
|
if (manifests.length === 0) {
|
|
1496
1750
|
return {
|
|
1497
1751
|
configs
|
|
@@ -1503,6 +1757,7 @@ async function discoverPythonPackage({
|
|
|
1503
1757
|
manifests
|
|
1504
1758
|
);
|
|
1505
1759
|
workspaceManifest = entrypointWorkspaceManifest;
|
|
1760
|
+
workspaceLockFile = entrypointWorkspaceManifest.lockFile;
|
|
1506
1761
|
configs = configs.filter(
|
|
1507
1762
|
(config) => Object.values(config).some(
|
|
1508
1763
|
(cfg) => cfg !== void 0 && isSubpath(
|
|
@@ -1520,6 +1775,7 @@ async function discoverPythonPackage({
|
|
|
1520
1775
|
return {
|
|
1521
1776
|
manifest: entrypointManifest,
|
|
1522
1777
|
workspaceManifest,
|
|
1778
|
+
workspaceLockFile,
|
|
1523
1779
|
configs,
|
|
1524
1780
|
requiresPython
|
|
1525
1781
|
};
|
|
@@ -1634,6 +1890,21 @@ async function loadPythonManifest(root, prefix) {
|
|
|
1634
1890
|
}
|
|
1635
1891
|
return manifest;
|
|
1636
1892
|
}
|
|
1893
|
+
async function maybeLoadLockFile(root, subdir) {
|
|
1894
|
+
const uvLockRelPath = import_node_path5.default.join(subdir, "uv.lock");
|
|
1895
|
+
const uvLockPath = import_node_path5.default.join(root, uvLockRelPath);
|
|
1896
|
+
const uvLockContent = await readFileTextIfExists(uvLockPath);
|
|
1897
|
+
if (uvLockContent != null) {
|
|
1898
|
+
return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
|
|
1899
|
+
}
|
|
1900
|
+
const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
|
|
1901
|
+
const pylockPath = import_node_path5.default.join(root, pylockRelPath);
|
|
1902
|
+
const pylockContent = await readFileTextIfExists(pylockPath);
|
|
1903
|
+
if (pylockContent != null) {
|
|
1904
|
+
return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
|
|
1905
|
+
}
|
|
1906
|
+
return void 0;
|
|
1907
|
+
}
|
|
1637
1908
|
async function maybeLoadPyProjectToml(root, subdir) {
|
|
1638
1909
|
const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
|
|
1639
1910
|
const pyprojectTomlPath = import_node_path5.default.join(root, pyprojectTomlRelPath);
|
|
@@ -1680,9 +1951,11 @@ async function maybeLoadPyProjectToml(root, subdir) {
|
|
|
1680
1951
|
pyproject.tool.uv = uvToml;
|
|
1681
1952
|
}
|
|
1682
1953
|
}
|
|
1954
|
+
const lockFile = await maybeLoadLockFile(root, subdir);
|
|
1683
1955
|
return {
|
|
1684
1956
|
path: pyprojectTomlRelPath,
|
|
1685
|
-
data: pyproject
|
|
1957
|
+
data: pyproject,
|
|
1958
|
+
lockFile
|
|
1686
1959
|
};
|
|
1687
1960
|
}
|
|
1688
1961
|
async function maybeLoadPipfile(root, subdir) {
|
|
@@ -1811,6 +2084,29 @@ async function maybeLoadPythonRequest(root, subdir) {
|
|
|
1811
2084
|
};
|
|
1812
2085
|
}
|
|
1813
2086
|
|
|
2087
|
+
// src/manifest/serialize.ts
|
|
2088
|
+
var import_smol_toml2 = __toESM(require("smol-toml"), 1);
|
|
2089
|
+
function stringifyManifest(data) {
|
|
2090
|
+
return import_smol_toml2.default.stringify(data);
|
|
2091
|
+
}
|
|
2092
|
+
function createMinimalManifest(options = {}) {
|
|
2093
|
+
const {
|
|
2094
|
+
name = "app",
|
|
2095
|
+
version = "0.1.0",
|
|
2096
|
+
requiresPython,
|
|
2097
|
+
dependencies = []
|
|
2098
|
+
} = options;
|
|
2099
|
+
return {
|
|
2100
|
+
project: {
|
|
2101
|
+
name,
|
|
2102
|
+
version,
|
|
2103
|
+
...requiresPython && { "requires-python": requiresPython },
|
|
2104
|
+
dependencies,
|
|
2105
|
+
classifiers: ["Private :: Do Not Upload"]
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
|
|
1814
2110
|
// src/manifest/python-selector.ts
|
|
1815
2111
|
function selectPython(constraints, available) {
|
|
1816
2112
|
const warnings = [];
|
|
@@ -1984,6 +2280,7 @@ var HashDigestSchema = hashDigestSchema;
|
|
|
1984
2280
|
PythonBuild,
|
|
1985
2281
|
PythonConfigKind,
|
|
1986
2282
|
PythonImplementation,
|
|
2283
|
+
PythonLockFileKind,
|
|
1987
2284
|
PythonManifestConvertedKind,
|
|
1988
2285
|
PythonManifestKind,
|
|
1989
2286
|
PythonVariant,
|
|
@@ -1994,6 +2291,8 @@ var HashDigestSchema = hashDigestSchema;
|
|
|
1994
2291
|
UvConfigWorkspaceSchema,
|
|
1995
2292
|
UvIndexEntrySchema,
|
|
1996
2293
|
containsAppOrHandler,
|
|
2294
|
+
createMinimalManifest,
|
|
1997
2295
|
discoverPythonPackage,
|
|
1998
|
-
selectPython
|
|
2296
|
+
selectPython,
|
|
2297
|
+
stringifyManifest
|
|
1999
2298
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* @module @vercel/python-analysis
|
|
8
8
|
*/
|
|
9
9
|
export { containsAppOrHandler } from './semantic/entrypoints';
|
|
10
|
-
export type { PythonConfig, PythonConfigs, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
|
|
11
|
-
export { discoverPythonPackage, PythonConfigKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
|
|
10
|
+
export type { PythonConfig, PythonConfigs, PythonLockFile, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
|
|
11
|
+
export { discoverPythonPackage, PythonConfigKind, PythonLockFileKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
|
|
12
|
+
export { createMinimalManifest, stringifyManifest, type CreateMinimalManifestOptions, } from './manifest/serialize';
|
|
12
13
|
export type { PythonSelectionResult } from './manifest/python-selector';
|
|
13
14
|
export { selectPython } from './manifest/python-selector';
|
|
14
15
|
export { PythonAnalysisError } from './util/error';
|
package/dist/index.js
CHANGED
|
@@ -435,6 +435,8 @@ function convertPipfileToPyprojectToml(pipfile) {
|
|
|
435
435
|
}
|
|
436
436
|
if (deps.length > 0) {
|
|
437
437
|
pyproject.project = {
|
|
438
|
+
name: "app",
|
|
439
|
+
version: "0.1.0",
|
|
438
440
|
dependencies: deps
|
|
439
441
|
};
|
|
440
442
|
}
|
|
@@ -492,6 +494,8 @@ function convertPipfileLockToPyprojectToml(pipfileLock) {
|
|
|
492
494
|
}
|
|
493
495
|
if (deps.length > 0) {
|
|
494
496
|
pyproject.project = {
|
|
497
|
+
name: "app",
|
|
498
|
+
version: "0.1.0",
|
|
495
499
|
dependencies: deps
|
|
496
500
|
};
|
|
497
501
|
}
|
|
@@ -568,7 +572,8 @@ var uvIndexEntrySchema = z3.object({
|
|
|
568
572
|
name: z3.string(),
|
|
569
573
|
url: z3.string(),
|
|
570
574
|
default: z3.boolean().optional(),
|
|
571
|
-
explicit: z3.boolean().optional()
|
|
575
|
+
explicit: z3.boolean().optional(),
|
|
576
|
+
format: z3.string().optional()
|
|
572
577
|
});
|
|
573
578
|
var uvConfigSchema = z3.object({
|
|
574
579
|
sources: z3.record(z3.union([dependencySourceSchema, z3.array(dependencySourceSchema)])).optional(),
|
|
@@ -642,6 +647,7 @@ import { normalize } from "path";
|
|
|
642
647
|
import { parsePipRequirementsFile } from "pip-requirements-js";
|
|
643
648
|
var PRIMARY_INDEX_NAME = "primary";
|
|
644
649
|
var EXTRA_INDEX_PREFIX = "extra-";
|
|
650
|
+
var FIND_LINKS_PREFIX = "find-links-";
|
|
645
651
|
function parseGitUrl(url) {
|
|
646
652
|
if (!url.startsWith("git+")) {
|
|
647
653
|
return null;
|
|
@@ -683,6 +689,8 @@ function extractPipArguments(fileContent) {
|
|
|
683
689
|
};
|
|
684
690
|
const lines = fileContent.split(/\r?\n/);
|
|
685
691
|
const cleanedLines = [];
|
|
692
|
+
const pathRequirements = [];
|
|
693
|
+
const editableRequirements = [];
|
|
686
694
|
for (let i = 0; i < lines.length; i++) {
|
|
687
695
|
const line = lines[i];
|
|
688
696
|
const trimmed = line.trim();
|
|
@@ -697,11 +705,21 @@ function extractPipArguments(fileContent) {
|
|
|
697
705
|
fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
|
|
698
706
|
}
|
|
699
707
|
const extracted = tryExtractPipArgument(fullLine, options);
|
|
700
|
-
if (extracted) {
|
|
708
|
+
if (extracted === true) {
|
|
709
|
+
i += linesConsumed;
|
|
710
|
+
} else if (typeof extracted === "object" && extracted.editable) {
|
|
711
|
+
editableRequirements.push(extracted.editable);
|
|
701
712
|
i += linesConsumed;
|
|
702
713
|
} else {
|
|
714
|
+
if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
|
|
715
|
+
i += linesConsumed;
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
703
718
|
const strippedLine = stripInlineHashes(fullLine);
|
|
704
|
-
|
|
719
|
+
const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
|
|
720
|
+
if (isPathOrUrlRequirement(effectiveLine)) {
|
|
721
|
+
pathRequirements.push(effectiveLine);
|
|
722
|
+
} else if (strippedLine !== fullLine) {
|
|
705
723
|
cleanedLines.push(strippedLine);
|
|
706
724
|
} else {
|
|
707
725
|
cleanedLines.push(line);
|
|
@@ -714,7 +732,9 @@ function extractPipArguments(fileContent) {
|
|
|
714
732
|
}
|
|
715
733
|
return {
|
|
716
734
|
cleanedContent: cleanedLines.join("\n"),
|
|
717
|
-
options
|
|
735
|
+
options,
|
|
736
|
+
pathRequirements,
|
|
737
|
+
editableRequirements
|
|
718
738
|
};
|
|
719
739
|
}
|
|
720
740
|
function tryExtractPipArgument(line, options) {
|
|
@@ -753,6 +773,46 @@ function tryExtractPipArgument(line, options) {
|
|
|
753
773
|
return true;
|
|
754
774
|
}
|
|
755
775
|
}
|
|
776
|
+
if (line.startsWith("--editable")) {
|
|
777
|
+
const path4 = extractArgValue(line, "--editable");
|
|
778
|
+
if (path4) {
|
|
779
|
+
return { editable: path4 };
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (line.startsWith("-e ") || line.startsWith("-e ")) {
|
|
783
|
+
const path4 = line.slice(2).trim();
|
|
784
|
+
if (path4) {
|
|
785
|
+
return { editable: path4 };
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (line.startsWith("--find-links")) {
|
|
789
|
+
const url = extractArgValue(line, "--find-links");
|
|
790
|
+
if (url) {
|
|
791
|
+
if (!options.findLinks)
|
|
792
|
+
options.findLinks = [];
|
|
793
|
+
options.findLinks.push(url);
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (line.startsWith("-f ") || line.startsWith("-f ")) {
|
|
798
|
+
const match = line.match(/^-f\s+(\S+)/);
|
|
799
|
+
if (match) {
|
|
800
|
+
if (!options.findLinks)
|
|
801
|
+
options.findLinks = [];
|
|
802
|
+
options.findLinks.push(match[1]);
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (line === "--no-index" || line.startsWith("--no-index ")) {
|
|
807
|
+
options.noIndex = true;
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
if (line.startsWith("--")) {
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
756
816
|
return false;
|
|
757
817
|
}
|
|
758
818
|
function stripInlineHashes(line) {
|
|
@@ -768,15 +828,158 @@ function extractInlineHashes(line) {
|
|
|
768
828
|
return hashes;
|
|
769
829
|
}
|
|
770
830
|
function extractArgValue(line, option) {
|
|
831
|
+
let value = null;
|
|
771
832
|
if (line.startsWith(`${option}=`)) {
|
|
772
|
-
|
|
773
|
-
|
|
833
|
+
value = line.slice(option.length + 1).trim();
|
|
834
|
+
} else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
|
|
835
|
+
value = line.slice(option.length).trim();
|
|
774
836
|
}
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
837
|
+
if (!value)
|
|
838
|
+
return null;
|
|
839
|
+
const commentIdx = value.indexOf(" #");
|
|
840
|
+
if (commentIdx !== -1) {
|
|
841
|
+
value = value.slice(0, commentIdx).trim();
|
|
778
842
|
}
|
|
779
|
-
return null;
|
|
843
|
+
return value || null;
|
|
844
|
+
}
|
|
845
|
+
function isPathOrUrlRequirement(line) {
|
|
846
|
+
if (line.startsWith("./") || line.startsWith("../"))
|
|
847
|
+
return true;
|
|
848
|
+
if (line.startsWith("/"))
|
|
849
|
+
return true;
|
|
850
|
+
if (line.startsWith("~/"))
|
|
851
|
+
return true;
|
|
852
|
+
if (/^(https?|ftp|file):\/\//i.test(line))
|
|
853
|
+
return true;
|
|
854
|
+
if (isBareArchiveFilename(line))
|
|
855
|
+
return true;
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
function isBareArchiveFilename(line) {
|
|
859
|
+
if (line.includes(" @ "))
|
|
860
|
+
return false;
|
|
861
|
+
let check = line;
|
|
862
|
+
const commentIdx = check.indexOf(" #");
|
|
863
|
+
if (commentIdx !== -1)
|
|
864
|
+
check = check.slice(0, commentIdx);
|
|
865
|
+
const markerIdx = check.indexOf(" ;");
|
|
866
|
+
if (markerIdx !== -1)
|
|
867
|
+
check = check.slice(0, markerIdx);
|
|
868
|
+
const extrasIdx = check.indexOf("[");
|
|
869
|
+
if (extrasIdx !== -1)
|
|
870
|
+
check = check.slice(0, extrasIdx);
|
|
871
|
+
check = check.trim();
|
|
872
|
+
return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
|
|
873
|
+
}
|
|
874
|
+
function parseWheelFilename(filename) {
|
|
875
|
+
if (!filename.endsWith(".whl"))
|
|
876
|
+
return null;
|
|
877
|
+
const stem = filename.slice(0, -4);
|
|
878
|
+
const parts = stem.split("-");
|
|
879
|
+
if (parts.length < 5 || parts.length > 6)
|
|
880
|
+
return null;
|
|
881
|
+
const name = parts[0];
|
|
882
|
+
const version = parts[1];
|
|
883
|
+
if (!name || !version)
|
|
884
|
+
return null;
|
|
885
|
+
return {
|
|
886
|
+
name: name.replace(/_/g, "-"),
|
|
887
|
+
version
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function parseSdistFilename(filename) {
|
|
891
|
+
let stem = filename;
|
|
892
|
+
for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
|
|
893
|
+
if (stem.endsWith(ext)) {
|
|
894
|
+
stem = stem.slice(0, -ext.length);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (stem === filename)
|
|
899
|
+
return null;
|
|
900
|
+
const parts = stem.split("-");
|
|
901
|
+
let versionIdx = -1;
|
|
902
|
+
for (let i = 1; i < parts.length; i++) {
|
|
903
|
+
if (/^\d/.test(parts[i])) {
|
|
904
|
+
versionIdx = i;
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (versionIdx === -1)
|
|
909
|
+
return null;
|
|
910
|
+
return {
|
|
911
|
+
name: parts.slice(0, versionIdx).join("-"),
|
|
912
|
+
version: parts.slice(versionIdx).join("-")
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function normalizePathRequirement(rawLine) {
|
|
916
|
+
let line = rawLine;
|
|
917
|
+
const commentIdx = line.indexOf(" #");
|
|
918
|
+
if (commentIdx !== -1) {
|
|
919
|
+
line = line.slice(0, commentIdx).trim();
|
|
920
|
+
}
|
|
921
|
+
let markers;
|
|
922
|
+
const markerIdx = line.indexOf(" ;");
|
|
923
|
+
if (markerIdx !== -1) {
|
|
924
|
+
markers = line.slice(markerIdx + 2).trim();
|
|
925
|
+
line = line.slice(0, markerIdx).trim();
|
|
926
|
+
}
|
|
927
|
+
let extras;
|
|
928
|
+
const extrasMatch = line.match(/\[([^\]]+)\]$/);
|
|
929
|
+
if (extrasMatch) {
|
|
930
|
+
extras = extrasMatch[1].split(",").map((e) => e.trim());
|
|
931
|
+
line = line.slice(0, extrasMatch.index).trim();
|
|
932
|
+
}
|
|
933
|
+
const isUrl = /^(https?|ftp|file):\/\//i.test(line);
|
|
934
|
+
let filename;
|
|
935
|
+
if (isUrl) {
|
|
936
|
+
try {
|
|
937
|
+
const url = new URL(line);
|
|
938
|
+
filename = url.pathname.split("/").pop() || "";
|
|
939
|
+
} catch {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
} else {
|
|
943
|
+
const cleanPath = line.replace(/\/+$/, "");
|
|
944
|
+
filename = cleanPath.split("/").pop() || "";
|
|
945
|
+
}
|
|
946
|
+
if (!filename)
|
|
947
|
+
return null;
|
|
948
|
+
let name;
|
|
949
|
+
let version;
|
|
950
|
+
const wheelParsed = parseWheelFilename(filename);
|
|
951
|
+
if (wheelParsed) {
|
|
952
|
+
name = wheelParsed.name;
|
|
953
|
+
version = wheelParsed.version;
|
|
954
|
+
}
|
|
955
|
+
if (!name) {
|
|
956
|
+
const sdistParsed = parseSdistFilename(filename);
|
|
957
|
+
if (sdistParsed) {
|
|
958
|
+
name = sdistParsed.name;
|
|
959
|
+
version = sdistParsed.version;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!name) {
|
|
963
|
+
name = filename.replace(/[-_.]+/g, "-").toLowerCase();
|
|
964
|
+
}
|
|
965
|
+
if (!name)
|
|
966
|
+
return null;
|
|
967
|
+
const req = { name };
|
|
968
|
+
if (version) {
|
|
969
|
+
req.version = `==${version}`;
|
|
970
|
+
}
|
|
971
|
+
if (extras && extras.length > 0) {
|
|
972
|
+
req.extras = extras;
|
|
973
|
+
}
|
|
974
|
+
if (markers) {
|
|
975
|
+
req.markers = markers;
|
|
976
|
+
}
|
|
977
|
+
if (isUrl) {
|
|
978
|
+
req.url = line;
|
|
979
|
+
} else {
|
|
980
|
+
req.source = { path: line };
|
|
981
|
+
}
|
|
982
|
+
return req;
|
|
780
983
|
}
|
|
781
984
|
function convertRequirementsToPyprojectToml(fileContent, readFile3) {
|
|
782
985
|
const pyproject = {};
|
|
@@ -795,6 +998,8 @@ function convertRequirementsToPyprojectToml(fileContent, readFile3) {
|
|
|
795
998
|
}
|
|
796
999
|
if (deps.length > 0) {
|
|
797
1000
|
pyproject.project = {
|
|
1001
|
+
name: "app",
|
|
1002
|
+
version: "0.1.0",
|
|
798
1003
|
dependencies: deps
|
|
799
1004
|
};
|
|
800
1005
|
}
|
|
@@ -826,6 +1031,15 @@ function buildIndexEntries(pipOptions) {
|
|
|
826
1031
|
url: pipOptions.extraIndexUrls[i]
|
|
827
1032
|
});
|
|
828
1033
|
}
|
|
1034
|
+
if (pipOptions.findLinks) {
|
|
1035
|
+
for (let i = 0; i < pipOptions.findLinks.length; i++) {
|
|
1036
|
+
indexes.push({
|
|
1037
|
+
name: `${FIND_LINKS_PREFIX}${i + 1}`,
|
|
1038
|
+
url: pipOptions.findLinks[i],
|
|
1039
|
+
format: "flat"
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
829
1043
|
return indexes;
|
|
830
1044
|
}
|
|
831
1045
|
function parseRequirementsFile(fileContent, readFile3) {
|
|
@@ -833,7 +1047,7 @@ function parseRequirementsFile(fileContent, readFile3) {
|
|
|
833
1047
|
return parseRequirementsFileInternal(fileContent, readFile3, visited);
|
|
834
1048
|
}
|
|
835
1049
|
function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
836
|
-
const { cleanedContent, options } = extractPipArguments(fileContent);
|
|
1050
|
+
const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
|
|
837
1051
|
const hashMap = buildHashMap(fileContent);
|
|
838
1052
|
const requirements = parsePipRequirementsFile(cleanedContent);
|
|
839
1053
|
const normalized = [];
|
|
@@ -841,7 +1055,9 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
841
1055
|
requirementFiles: [...options.requirementFiles],
|
|
842
1056
|
constraintFiles: [...options.constraintFiles],
|
|
843
1057
|
indexUrl: options.indexUrl,
|
|
844
|
-
extraIndexUrls: [...options.extraIndexUrls]
|
|
1058
|
+
extraIndexUrls: [...options.extraIndexUrls],
|
|
1059
|
+
findLinks: options.findLinks ? [...options.findLinks] : void 0,
|
|
1060
|
+
noIndex: options.noIndex
|
|
845
1061
|
};
|
|
846
1062
|
for (const req of requirements) {
|
|
847
1063
|
if (req.type === "RequirementsFile") {
|
|
@@ -861,6 +1077,23 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
861
1077
|
normalized.push(norm);
|
|
862
1078
|
}
|
|
863
1079
|
}
|
|
1080
|
+
for (const rawPath of pathRequirements) {
|
|
1081
|
+
const norm = normalizePathRequirement(rawPath);
|
|
1082
|
+
if (norm != null) {
|
|
1083
|
+
normalized.push(norm);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
for (const rawPath of editableRequirements) {
|
|
1087
|
+
const norm = normalizePathRequirement(rawPath);
|
|
1088
|
+
if (norm != null) {
|
|
1089
|
+
if (norm.source) {
|
|
1090
|
+
norm.source.editable = true;
|
|
1091
|
+
} else {
|
|
1092
|
+
norm.source = { path: rawPath, editable: true };
|
|
1093
|
+
}
|
|
1094
|
+
normalized.push(norm);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
864
1097
|
if (readFile3) {
|
|
865
1098
|
for (const refPath of mergedOptions.requirementFiles) {
|
|
866
1099
|
const refPathKey = normalize(refPath);
|
|
@@ -897,6 +1130,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
897
1130
|
mergedOptions.constraintFiles.push(constraintPath);
|
|
898
1131
|
}
|
|
899
1132
|
}
|
|
1133
|
+
if (refParsed.pipOptions.findLinks) {
|
|
1134
|
+
if (!mergedOptions.findLinks)
|
|
1135
|
+
mergedOptions.findLinks = [];
|
|
1136
|
+
for (const fl of refParsed.pipOptions.findLinks) {
|
|
1137
|
+
if (!mergedOptions.findLinks.includes(fl)) {
|
|
1138
|
+
mergedOptions.findLinks.push(fl);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (refParsed.pipOptions.noIndex) {
|
|
1143
|
+
mergedOptions.noIndex = true;
|
|
1144
|
+
}
|
|
900
1145
|
}
|
|
901
1146
|
}
|
|
902
1147
|
}
|
|
@@ -1385,6 +1630,11 @@ var PythonManifestKind = /* @__PURE__ */ ((PythonManifestKind2) => {
|
|
|
1385
1630
|
PythonManifestKind2["PyProjectToml"] = "pyproject.toml";
|
|
1386
1631
|
return PythonManifestKind2;
|
|
1387
1632
|
})(PythonManifestKind || {});
|
|
1633
|
+
var PythonLockFileKind = /* @__PURE__ */ ((PythonLockFileKind2) => {
|
|
1634
|
+
PythonLockFileKind2["UvLock"] = "uv.lock";
|
|
1635
|
+
PythonLockFileKind2["PylockToml"] = "pylock.toml";
|
|
1636
|
+
return PythonLockFileKind2;
|
|
1637
|
+
})(PythonLockFileKind || {});
|
|
1388
1638
|
var PythonManifestConvertedKind = /* @__PURE__ */ ((PythonManifestConvertedKind2) => {
|
|
1389
1639
|
PythonManifestConvertedKind2["Pipfile"] = "Pipfile";
|
|
1390
1640
|
PythonManifestConvertedKind2["PipfileLock"] = "Pipfile.lock";
|
|
@@ -1426,6 +1676,7 @@ async function discoverPythonPackage({
|
|
|
1426
1676
|
}
|
|
1427
1677
|
let entrypointManifest;
|
|
1428
1678
|
let workspaceManifest;
|
|
1679
|
+
let workspaceLockFile;
|
|
1429
1680
|
if (manifests.length === 0) {
|
|
1430
1681
|
return {
|
|
1431
1682
|
configs
|
|
@@ -1437,6 +1688,7 @@ async function discoverPythonPackage({
|
|
|
1437
1688
|
manifests
|
|
1438
1689
|
);
|
|
1439
1690
|
workspaceManifest = entrypointWorkspaceManifest;
|
|
1691
|
+
workspaceLockFile = entrypointWorkspaceManifest.lockFile;
|
|
1440
1692
|
configs = configs.filter(
|
|
1441
1693
|
(config) => Object.values(config).some(
|
|
1442
1694
|
(cfg) => cfg !== void 0 && isSubpath(
|
|
@@ -1454,6 +1706,7 @@ async function discoverPythonPackage({
|
|
|
1454
1706
|
return {
|
|
1455
1707
|
manifest: entrypointManifest,
|
|
1456
1708
|
workspaceManifest,
|
|
1709
|
+
workspaceLockFile,
|
|
1457
1710
|
configs,
|
|
1458
1711
|
requiresPython
|
|
1459
1712
|
};
|
|
@@ -1568,6 +1821,21 @@ async function loadPythonManifest(root, prefix) {
|
|
|
1568
1821
|
}
|
|
1569
1822
|
return manifest;
|
|
1570
1823
|
}
|
|
1824
|
+
async function maybeLoadLockFile(root, subdir) {
|
|
1825
|
+
const uvLockRelPath = path3.join(subdir, "uv.lock");
|
|
1826
|
+
const uvLockPath = path3.join(root, uvLockRelPath);
|
|
1827
|
+
const uvLockContent = await readFileTextIfExists(uvLockPath);
|
|
1828
|
+
if (uvLockContent != null) {
|
|
1829
|
+
return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
|
|
1830
|
+
}
|
|
1831
|
+
const pylockRelPath = path3.join(subdir, "pylock.toml");
|
|
1832
|
+
const pylockPath = path3.join(root, pylockRelPath);
|
|
1833
|
+
const pylockContent = await readFileTextIfExists(pylockPath);
|
|
1834
|
+
if (pylockContent != null) {
|
|
1835
|
+
return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
|
|
1836
|
+
}
|
|
1837
|
+
return void 0;
|
|
1838
|
+
}
|
|
1571
1839
|
async function maybeLoadPyProjectToml(root, subdir) {
|
|
1572
1840
|
const pyprojectTomlRelPath = path3.join(subdir, "pyproject.toml");
|
|
1573
1841
|
const pyprojectTomlPath = path3.join(root, pyprojectTomlRelPath);
|
|
@@ -1614,9 +1882,11 @@ async function maybeLoadPyProjectToml(root, subdir) {
|
|
|
1614
1882
|
pyproject.tool.uv = uvToml;
|
|
1615
1883
|
}
|
|
1616
1884
|
}
|
|
1885
|
+
const lockFile = await maybeLoadLockFile(root, subdir);
|
|
1617
1886
|
return {
|
|
1618
1887
|
path: pyprojectTomlRelPath,
|
|
1619
|
-
data: pyproject
|
|
1888
|
+
data: pyproject,
|
|
1889
|
+
lockFile
|
|
1620
1890
|
};
|
|
1621
1891
|
}
|
|
1622
1892
|
async function maybeLoadPipfile(root, subdir) {
|
|
@@ -1745,6 +2015,29 @@ async function maybeLoadPythonRequest(root, subdir) {
|
|
|
1745
2015
|
};
|
|
1746
2016
|
}
|
|
1747
2017
|
|
|
2018
|
+
// src/manifest/serialize.ts
|
|
2019
|
+
import toml2 from "smol-toml";
|
|
2020
|
+
function stringifyManifest(data) {
|
|
2021
|
+
return toml2.stringify(data);
|
|
2022
|
+
}
|
|
2023
|
+
function createMinimalManifest(options = {}) {
|
|
2024
|
+
const {
|
|
2025
|
+
name = "app",
|
|
2026
|
+
version = "0.1.0",
|
|
2027
|
+
requiresPython,
|
|
2028
|
+
dependencies = []
|
|
2029
|
+
} = options;
|
|
2030
|
+
return {
|
|
2031
|
+
project: {
|
|
2032
|
+
name,
|
|
2033
|
+
version,
|
|
2034
|
+
...requiresPython && { "requires-python": requiresPython },
|
|
2035
|
+
dependencies,
|
|
2036
|
+
classifiers: ["Private :: Do Not Upload"]
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
|
|
1748
2041
|
// src/manifest/python-selector.ts
|
|
1749
2042
|
function selectPython(constraints, available) {
|
|
1750
2043
|
const warnings = [];
|
|
@@ -1917,6 +2210,7 @@ export {
|
|
|
1917
2210
|
PythonBuild,
|
|
1918
2211
|
PythonConfigKind,
|
|
1919
2212
|
PythonImplementation,
|
|
2213
|
+
PythonLockFileKind,
|
|
1920
2214
|
PythonManifestConvertedKind,
|
|
1921
2215
|
PythonManifestKind,
|
|
1922
2216
|
PythonVariant,
|
|
@@ -1927,6 +2221,8 @@ export {
|
|
|
1927
2221
|
UvConfigWorkspaceSchema,
|
|
1928
2222
|
UvIndexEntrySchema,
|
|
1929
2223
|
containsAppOrHandler,
|
|
2224
|
+
createMinimalManifest,
|
|
1930
2225
|
discoverPythonPackage,
|
|
1931
|
-
selectPython
|
|
2226
|
+
selectPython,
|
|
2227
|
+
stringifyManifest
|
|
1932
2228
|
};
|
|
@@ -20,6 +20,26 @@ export declare enum PythonManifestKind {
|
|
|
20
20
|
/** Standard `pyproject.toml` file (PEP 517/518/621). */
|
|
21
21
|
PyProjectToml = "pyproject.toml"
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Kinds of Python lock files.
|
|
25
|
+
*
|
|
26
|
+
* Lock files pin exact dependency versions for reproducible installs.
|
|
27
|
+
*/
|
|
28
|
+
export declare enum PythonLockFileKind {
|
|
29
|
+
/** uv's lock file format. */
|
|
30
|
+
UvLock = "uv.lock",
|
|
31
|
+
/** PEP 751 standard lock file format. */
|
|
32
|
+
PylockToml = "pylock.toml"
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Information about a detected lock file.
|
|
36
|
+
*/
|
|
37
|
+
export interface PythonLockFile {
|
|
38
|
+
/** Relative path to the lock file. */
|
|
39
|
+
path: RelPath;
|
|
40
|
+
/** The lock file format. */
|
|
41
|
+
kind: PythonLockFileKind;
|
|
42
|
+
}
|
|
23
43
|
/**
|
|
24
44
|
* Kinds of Python package manifests that are converted to pyproject.toml format.
|
|
25
45
|
*
|
|
@@ -63,6 +83,8 @@ export interface PythonManifest {
|
|
|
63
83
|
origin?: PythonManifestOrigin;
|
|
64
84
|
/** Whether this manifest represents a workspace root. */
|
|
65
85
|
isRoot?: boolean;
|
|
86
|
+
/** Lock file associated with this manifest, if one exists. */
|
|
87
|
+
lockFile?: PythonLockFile;
|
|
66
88
|
}
|
|
67
89
|
/**
|
|
68
90
|
* Configuration from a `.python-version` file.
|
|
@@ -130,6 +152,8 @@ export interface PythonPackage {
|
|
|
130
152
|
requiresPython?: PythonConstraint[];
|
|
131
153
|
/** The workspace root manifest, if this package is part of a workspace. */
|
|
132
154
|
workspaceManifest?: PythonManifest;
|
|
155
|
+
/** Lock file at the workspace root, if one exists. */
|
|
156
|
+
workspaceLockFile?: PythonLockFile;
|
|
133
157
|
}
|
|
134
158
|
/**
|
|
135
159
|
* Discover Python package information starting from an entrypoint directory.
|
|
@@ -219,16 +219,19 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
|
|
|
219
219
|
url: z.ZodString;
|
|
220
220
|
default: z.ZodOptional<z.ZodBoolean>;
|
|
221
221
|
explicit: z.ZodOptional<z.ZodBoolean>;
|
|
222
|
+
format: z.ZodOptional<z.ZodString>;
|
|
222
223
|
}, "strip", z.ZodTypeAny, {
|
|
223
224
|
name: string;
|
|
224
225
|
url: string;
|
|
225
226
|
default?: boolean | undefined;
|
|
226
227
|
explicit?: boolean | undefined;
|
|
228
|
+
format?: string | undefined;
|
|
227
229
|
}, {
|
|
228
230
|
name: string;
|
|
229
231
|
url: string;
|
|
230
232
|
default?: boolean | undefined;
|
|
231
233
|
explicit?: boolean | undefined;
|
|
234
|
+
format?: string | undefined;
|
|
232
235
|
}>, "many">>;
|
|
233
236
|
workspace: z.ZodOptional<z.ZodObject<{
|
|
234
237
|
members: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -259,6 +262,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
|
|
|
259
262
|
url: string;
|
|
260
263
|
default?: boolean | undefined;
|
|
261
264
|
explicit?: boolean | undefined;
|
|
265
|
+
format?: string | undefined;
|
|
262
266
|
}[] | undefined;
|
|
263
267
|
workspace?: {
|
|
264
268
|
members?: string[] | undefined;
|
|
@@ -283,6 +287,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
|
|
|
283
287
|
url: string;
|
|
284
288
|
default?: boolean | undefined;
|
|
285
289
|
explicit?: boolean | undefined;
|
|
290
|
+
format?: string | undefined;
|
|
286
291
|
}[] | undefined;
|
|
287
292
|
workspace?: {
|
|
288
293
|
members?: string[] | undefined;
|
|
@@ -309,6 +314,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
|
|
|
309
314
|
url: string;
|
|
310
315
|
default?: boolean | undefined;
|
|
311
316
|
explicit?: boolean | undefined;
|
|
317
|
+
format?: string | undefined;
|
|
312
318
|
}[] | undefined;
|
|
313
319
|
workspace?: {
|
|
314
320
|
members?: string[] | undefined;
|
|
@@ -335,6 +341,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
|
|
|
335
341
|
url: string;
|
|
336
342
|
default?: boolean | undefined;
|
|
337
343
|
explicit?: boolean | undefined;
|
|
344
|
+
format?: string | undefined;
|
|
338
345
|
}[] | undefined;
|
|
339
346
|
workspace?: {
|
|
340
347
|
members?: string[] | undefined;
|
|
@@ -513,16 +520,19 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
513
520
|
url: z.ZodString;
|
|
514
521
|
default: z.ZodOptional<z.ZodBoolean>;
|
|
515
522
|
explicit: z.ZodOptional<z.ZodBoolean>;
|
|
523
|
+
format: z.ZodOptional<z.ZodString>;
|
|
516
524
|
}, "strip", z.ZodTypeAny, {
|
|
517
525
|
name: string;
|
|
518
526
|
url: string;
|
|
519
527
|
default?: boolean | undefined;
|
|
520
528
|
explicit?: boolean | undefined;
|
|
529
|
+
format?: string | undefined;
|
|
521
530
|
}, {
|
|
522
531
|
name: string;
|
|
523
532
|
url: string;
|
|
524
533
|
default?: boolean | undefined;
|
|
525
534
|
explicit?: boolean | undefined;
|
|
535
|
+
format?: string | undefined;
|
|
526
536
|
}>, "many">>;
|
|
527
537
|
workspace: z.ZodOptional<z.ZodObject<{
|
|
528
538
|
members: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -553,6 +563,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
553
563
|
url: string;
|
|
554
564
|
default?: boolean | undefined;
|
|
555
565
|
explicit?: boolean | undefined;
|
|
566
|
+
format?: string | undefined;
|
|
556
567
|
}[] | undefined;
|
|
557
568
|
workspace?: {
|
|
558
569
|
members?: string[] | undefined;
|
|
@@ -577,6 +588,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
577
588
|
url: string;
|
|
578
589
|
default?: boolean | undefined;
|
|
579
590
|
explicit?: boolean | undefined;
|
|
591
|
+
format?: string | undefined;
|
|
580
592
|
}[] | undefined;
|
|
581
593
|
workspace?: {
|
|
582
594
|
members?: string[] | undefined;
|
|
@@ -603,6 +615,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
603
615
|
url: string;
|
|
604
616
|
default?: boolean | undefined;
|
|
605
617
|
explicit?: boolean | undefined;
|
|
618
|
+
format?: string | undefined;
|
|
606
619
|
}[] | undefined;
|
|
607
620
|
workspace?: {
|
|
608
621
|
members?: string[] | undefined;
|
|
@@ -629,6 +642,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
629
642
|
url: string;
|
|
630
643
|
default?: boolean | undefined;
|
|
631
644
|
explicit?: boolean | undefined;
|
|
645
|
+
format?: string | undefined;
|
|
632
646
|
}[] | undefined;
|
|
633
647
|
workspace?: {
|
|
634
648
|
members?: string[] | undefined;
|
|
@@ -693,6 +707,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
693
707
|
url: string;
|
|
694
708
|
default?: boolean | undefined;
|
|
695
709
|
explicit?: boolean | undefined;
|
|
710
|
+
format?: string | undefined;
|
|
696
711
|
}[] | undefined;
|
|
697
712
|
workspace?: {
|
|
698
713
|
members?: string[] | undefined;
|
|
@@ -757,6 +772,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
|
|
|
757
772
|
url: string;
|
|
758
773
|
default?: boolean | undefined;
|
|
759
774
|
explicit?: boolean | undefined;
|
|
775
|
+
format?: string | undefined;
|
|
760
776
|
}[] | undefined;
|
|
761
777
|
workspace?: {
|
|
762
778
|
members?: string[] | undefined;
|
|
@@ -12,6 +12,10 @@ export interface PipOptions {
|
|
|
12
12
|
indexUrl?: string;
|
|
13
13
|
/** Extra index URLs (--extra-index-url) */
|
|
14
14
|
extraIndexUrls: string[];
|
|
15
|
+
/** Directories/URLs for --find-links / -f (only set when present) */
|
|
16
|
+
findLinks?: string[];
|
|
17
|
+
/** Whether --no-index was specified (only set when true) */
|
|
18
|
+
noIndex?: boolean;
|
|
15
19
|
}
|
|
16
20
|
/**
|
|
17
21
|
* Result of parsing a requirements file with pip options.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PyProjectToml } from './pyproject/types';
|
|
2
|
+
/**
|
|
3
|
+
* Serialize a PyProjectToml to TOML string format.
|
|
4
|
+
*/
|
|
5
|
+
export declare function stringifyManifest(data: PyProjectToml): string;
|
|
6
|
+
/**
|
|
7
|
+
* Options for creating a minimal pyproject.toml structure.
|
|
8
|
+
*/
|
|
9
|
+
export interface CreateMinimalManifestOptions {
|
|
10
|
+
/** Project name (defaults to 'app'). */
|
|
11
|
+
name?: string;
|
|
12
|
+
/** Project version (defaults to '0.1.0'). */
|
|
13
|
+
version?: string;
|
|
14
|
+
/** Python version constraint (e.g., '>=3.12' or '~=3.12.0'). */
|
|
15
|
+
requiresPython?: string;
|
|
16
|
+
/** Initial dependencies. */
|
|
17
|
+
dependencies?: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a minimal PyProjectToml structure for projects without a manifest.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createMinimalManifest(options?: CreateMinimalManifestOptions): PyProjectToml;
|
|
@@ -14,16 +14,19 @@ export declare const uvIndexEntrySchema: z.ZodObject<{
|
|
|
14
14
|
url: z.ZodString;
|
|
15
15
|
default: z.ZodOptional<z.ZodBoolean>;
|
|
16
16
|
explicit: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
format: z.ZodOptional<z.ZodString>;
|
|
17
18
|
}, "strip", z.ZodTypeAny, {
|
|
18
19
|
name: string;
|
|
19
20
|
url: string;
|
|
20
21
|
default?: boolean | undefined;
|
|
21
22
|
explicit?: boolean | undefined;
|
|
23
|
+
format?: string | undefined;
|
|
22
24
|
}, {
|
|
23
25
|
name: string;
|
|
24
26
|
url: string;
|
|
25
27
|
default?: boolean | undefined;
|
|
26
28
|
explicit?: boolean | undefined;
|
|
29
|
+
format?: string | undefined;
|
|
27
30
|
}>;
|
|
28
31
|
export declare const uvConfigSchema: z.ZodObject<{
|
|
29
32
|
sources: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
|
|
@@ -68,16 +71,19 @@ export declare const uvConfigSchema: z.ZodObject<{
|
|
|
68
71
|
url: z.ZodString;
|
|
69
72
|
default: z.ZodOptional<z.ZodBoolean>;
|
|
70
73
|
explicit: z.ZodOptional<z.ZodBoolean>;
|
|
74
|
+
format: z.ZodOptional<z.ZodString>;
|
|
71
75
|
}, "strip", z.ZodTypeAny, {
|
|
72
76
|
name: string;
|
|
73
77
|
url: string;
|
|
74
78
|
default?: boolean | undefined;
|
|
75
79
|
explicit?: boolean | undefined;
|
|
80
|
+
format?: string | undefined;
|
|
76
81
|
}, {
|
|
77
82
|
name: string;
|
|
78
83
|
url: string;
|
|
79
84
|
default?: boolean | undefined;
|
|
80
85
|
explicit?: boolean | undefined;
|
|
86
|
+
format?: string | undefined;
|
|
81
87
|
}>, "many">>;
|
|
82
88
|
workspace: z.ZodOptional<z.ZodObject<{
|
|
83
89
|
members: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -108,6 +114,7 @@ export declare const uvConfigSchema: z.ZodObject<{
|
|
|
108
114
|
url: string;
|
|
109
115
|
default?: boolean | undefined;
|
|
110
116
|
explicit?: boolean | undefined;
|
|
117
|
+
format?: string | undefined;
|
|
111
118
|
}[] | undefined;
|
|
112
119
|
workspace?: {
|
|
113
120
|
members?: string[] | undefined;
|
|
@@ -132,6 +139,7 @@ export declare const uvConfigSchema: z.ZodObject<{
|
|
|
132
139
|
url: string;
|
|
133
140
|
default?: boolean | undefined;
|
|
134
141
|
explicit?: boolean | undefined;
|
|
142
|
+
format?: string | undefined;
|
|
135
143
|
}[] | undefined;
|
|
136
144
|
workspace?: {
|
|
137
145
|
members?: string[] | undefined;
|
|
@@ -26,6 +26,8 @@ export interface UvIndexEntry {
|
|
|
26
26
|
default?: boolean;
|
|
27
27
|
/** Mark this index as explicit (must be explicitly referenced per-package) */
|
|
28
28
|
explicit?: boolean;
|
|
29
|
+
/** Index format: omit for standard (PEP 503), or "flat" for flat indexes (--find-links) */
|
|
30
|
+
format?: string;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* [tool.uv] section in pyproject.toml or uv.toml.
|