@vercel/python-analysis 0.9.1 → 0.10.1

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.js CHANGED
@@ -2,6 +2,84 @@
2
2
  import { readFile } from "fs/promises";
3
3
  import { createRequire } from "module";
4
4
  import { dirname, join } from "path";
5
+
6
+ // src/wasm/host-utils.ts
7
+ import { AsyncLocalStorage } from "async_hooks";
8
+ import { normalize } from "path/posix";
9
+ import { domainToUnicode as nodeDomainToUnicode } from "url";
10
+ var readFileStorage = new AsyncLocalStorage();
11
+ var WitResultError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.payload = message;
15
+ }
16
+ };
17
+ function createHostUtils() {
18
+ return {
19
+ readFile(path4) {
20
+ const ctx = readFileStorage.getStore();
21
+ if (ctx?.readFile) {
22
+ const relative2 = stripWorkingDir(path4, ctx.workingDir);
23
+ if (relative2 != null) {
24
+ const result = ctx.readFile(relative2);
25
+ if (result != null)
26
+ return result;
27
+ }
28
+ }
29
+ throw new WitResultError(`File not found: ${path4}`);
30
+ },
31
+ domainToAscii(domain) {
32
+ try {
33
+ if (/[:#?/@[\]%\\\t\n\r]/.test(domain)) {
34
+ throw new Error("domain contains invalid characters");
35
+ }
36
+ const url = new URL(`http://${domain}/`);
37
+ if (url.hostname === "" && domain !== "") {
38
+ throw new Error("domain resolved to empty hostname");
39
+ }
40
+ return url.hostname;
41
+ } catch {
42
+ throw { payload: `Invalid domain: ${domain}` };
43
+ }
44
+ },
45
+ domainToUnicode(domain) {
46
+ const result = nodeDomainToUnicode(domain);
47
+ if (result !== "" || domain === "") {
48
+ return [result, true];
49
+ }
50
+ return [domain, false];
51
+ },
52
+ nfcNormalize(s) {
53
+ return s.normalize("NFC");
54
+ },
55
+ nfdNormalize(s) {
56
+ return s.normalize("NFD");
57
+ },
58
+ nfkcNormalize(s) {
59
+ return s.normalize("NFKC");
60
+ },
61
+ nfkdNormalize(s) {
62
+ return s.normalize("NFKD");
63
+ }
64
+ };
65
+ }
66
+ function stripWorkingDir(path4, workingDir) {
67
+ const normalized = normalize(path4);
68
+ if (!workingDir) {
69
+ return normalized;
70
+ }
71
+ const normalizedDir = normalize(workingDir);
72
+ const prefix = normalizedDir.endsWith("/") ? normalizedDir : normalizedDir + "/";
73
+ if (normalized.startsWith(prefix)) {
74
+ return normalized.slice(prefix.length);
75
+ }
76
+ if (normalized === normalizedDir) {
77
+ return "";
78
+ }
79
+ return null;
80
+ }
81
+
82
+ // src/wasm/load.ts
5
83
  var WASI_SHIM_PATH = "@bytecodealliance/preview2-shim/instantiation";
6
84
  var WASM_MODULE_PATH = "#wasm/vercel_python_analysis.js";
7
85
  var wasmInstance = null;
@@ -29,7 +107,10 @@ async function importWasmModule() {
29
107
  const wasiShimModule = await import(WASI_SHIM_PATH);
30
108
  const WASIShim = wasiShimModule.WASIShim;
31
109
  const wasmModule = await import(WASM_MODULE_PATH);
32
- const imports = new WASIShim().getImportObject();
110
+ const imports = {
111
+ ...new WASIShim().getImportObject(),
112
+ "vercel:python-analysis/host-utils": createHostUtils()
113
+ };
33
114
  const instance = await wasmModule.instantiate(getCoreModule, imports);
34
115
  wasmInstance = instance;
35
116
  return instance;
@@ -102,6 +183,41 @@ function mergeExtras(existing, additional) {
102
183
  }
103
184
  return result.size > 0 ? Array.from(result) : void 0;
104
185
  }
186
+ function wasmEntryToRequirement(entry) {
187
+ const req = {
188
+ name: entry.name || ""
189
+ };
190
+ if (entry.versionSpec) {
191
+ req.version = entry.versionSpec;
192
+ }
193
+ if (entry.extras.length > 0) {
194
+ req.extras = entry.extras;
195
+ }
196
+ if (entry.markers) {
197
+ req.markers = entry.markers;
198
+ }
199
+ if (entry.url) {
200
+ req.url = entry.url;
201
+ }
202
+ return req;
203
+ }
204
+ async function parsePep508(depOrDeps) {
205
+ const wasm = await importWasmModule();
206
+ if (Array.isArray(depOrDeps)) {
207
+ return depOrDeps.map((dep) => {
208
+ try {
209
+ return wasmEntryToRequirement(wasm.parsePep508(dep));
210
+ } catch {
211
+ return null;
212
+ }
213
+ });
214
+ }
215
+ try {
216
+ return wasmEntryToRequirement(wasm.parsePep508(depOrDeps));
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
105
221
 
106
222
  // src/manifest/dist-metadata.ts
107
223
  async function readDistInfoFile(distInfoDir, filename) {
@@ -759,7 +875,8 @@ var uvIndexEntrySchema = z3.object({
759
875
  var uvConfigSchema = z3.object({
760
876
  sources: z3.record(z3.union([dependencySourceSchema, z3.array(dependencySourceSchema)])).optional(),
761
877
  index: z3.array(uvIndexEntrySchema).optional(),
762
- workspace: uvConfigWorkspaceSchema.optional()
878
+ workspace: uvConfigWorkspaceSchema.optional(),
879
+ "dev-dependencies": z3.array(z3.string()).optional()
763
880
  });
764
881
 
765
882
  // src/manifest/pyproject/schema.zod.ts
@@ -800,7 +917,16 @@ var pyProjectProjectSchema = z4.object({
800
917
  scripts: z4.record(z4.string()).optional(),
801
918
  entry_points: z4.record(z4.record(z4.string())).optional()
802
919
  });
803
- var pyProjectDependencyGroupsSchema = z4.record(z4.array(z4.string()));
920
+ var dependencyGroupIncludeSchema = z4.object({
921
+ "include-group": z4.string()
922
+ });
923
+ var dependencyGroupEntrySchema = z4.union([
924
+ z4.string(),
925
+ dependencyGroupIncludeSchema
926
+ ]);
927
+ var pyProjectDependencyGroupsSchema = z4.record(
928
+ z4.array(dependencyGroupEntrySchema)
929
+ );
804
930
  var pyProjectToolSectionSchema = z4.object({
805
931
  uv: uvConfigSchema.optional()
806
932
  });
@@ -824,356 +950,98 @@ var PyProjectToolSectionSchema = pyProjectToolSectionSchema.passthrough();
824
950
  var PyProjectTomlSchema = pyProjectTomlSchema.passthrough();
825
951
 
826
952
  // src/manifest/requirements-txt-parser.ts
827
- import { normalize } from "path";
828
- import { parsePipRequirementsFile } from "pip-requirements-js";
953
+ import { isAbsolute, join as join3, relative } from "path/posix";
829
954
  var PRIMARY_INDEX_NAME = "primary";
830
955
  var EXTRA_INDEX_PREFIX = "extra-";
831
956
  var FIND_LINKS_PREFIX = "find-links-";
832
- function parseGitUrl(url) {
833
- if (!url.startsWith("git+")) {
834
- return null;
835
- }
836
- let remaining = url.slice(4);
837
- let egg;
838
- const fragmentIdx = remaining.indexOf("#");
839
- if (fragmentIdx !== -1) {
840
- const fragment = remaining.slice(fragmentIdx + 1);
841
- remaining = remaining.slice(0, fragmentIdx);
842
- for (const part of fragment.split("&")) {
843
- const [key, value] = part.split("=");
844
- if (key === "egg" && value) {
845
- egg = value;
846
- }
847
- }
848
- }
849
- let ref;
850
- const lastSlashIdx = remaining.lastIndexOf("/");
851
- const atIdx = remaining.indexOf("@", lastSlashIdx > 0 ? lastSlashIdx : 0);
852
- if (atIdx !== -1 && atIdx > remaining.indexOf("://")) {
853
- ref = remaining.slice(atIdx + 1);
854
- remaining = remaining.slice(0, atIdx);
957
+ async function parseWithWasm(content, readFile4, workingDir) {
958
+ const wasm = await importWasmModule();
959
+ const resolvedDir = workingDir ?? "/";
960
+ if (readFile4) {
961
+ return readFileStorage.run(
962
+ { readFile: readFile4, workingDir: resolvedDir },
963
+ () => wasm.parseRequirementsTxt(content, resolvedDir, void 0)
964
+ );
855
965
  }
856
- return {
857
- url: remaining,
858
- ref,
859
- egg
860
- };
966
+ return wasm.parseRequirementsTxt(content, workingDir ?? void 0, void 0);
861
967
  }
862
- function isGitUrl(url) {
863
- return url.startsWith("git+");
864
- }
865
- function extractPipArguments(fileContent) {
866
- const options = {
867
- requirementFiles: [],
868
- constraintFiles: [],
869
- extraIndexUrls: []
870
- };
871
- const lines = fileContent.split(/\r?\n/);
872
- const cleanedLines = [];
873
- const pathRequirements = [];
874
- const editableRequirements = [];
875
- for (let i = 0; i < lines.length; i++) {
876
- const line = lines[i];
877
- const trimmed = line.trim();
878
- if (trimmed === "" || trimmed.startsWith("#")) {
879
- cleanedLines.push(line);
880
- continue;
881
- }
882
- let fullLine = trimmed;
883
- let linesConsumed = 0;
884
- while (fullLine.endsWith("\\") && i + linesConsumed + 1 < lines.length) {
885
- linesConsumed++;
886
- fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
887
- }
888
- const extracted = tryExtractPipArgument(fullLine, options);
889
- if (extracted === true) {
890
- i += linesConsumed;
891
- } else if (typeof extracted === "object" && extracted.editable) {
892
- editableRequirements.push(extracted.editable);
893
- i += linesConsumed;
894
- } else {
895
- if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
896
- i += linesConsumed;
897
- continue;
898
- }
899
- const strippedLine = stripInlineHashes(fullLine);
900
- const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
901
- if (isPathOrUrlRequirement(effectiveLine)) {
902
- pathRequirements.push(effectiveLine);
903
- } else if (strippedLine !== fullLine) {
904
- cleanedLines.push(strippedLine);
905
- } else {
906
- cleanedLines.push(line);
907
- for (let j = 1; j <= linesConsumed; j++) {
908
- cleanedLines.push(lines[i + j]);
909
- }
910
- }
911
- i += linesConsumed;
968
+ function wasmEntryToNormalized(entry, editable) {
969
+ let name = entry.name || "";
970
+ if (!name && entry.url) {
971
+ const urlPath = entry.url.replace(/^file:\/\//, "");
972
+ const basename = urlPath.replace(/\/$/, "").split("/").pop();
973
+ if (basename && !basename.includes(".")) {
974
+ name = basename;
912
975
  }
913
976
  }
914
- return {
915
- cleanedContent: cleanedLines.join("\n"),
916
- options,
917
- pathRequirements,
918
- editableRequirements
977
+ const req = {
978
+ name
919
979
  };
920
- }
921
- function tryExtractPipArgument(line, options) {
922
- if (line.startsWith("--requirement")) {
923
- const path4 = extractArgValue(line, "--requirement");
924
- if (path4) {
925
- options.requirementFiles.push(path4);
926
- return true;
927
- }
980
+ if (entry.versionSpec) {
981
+ req.version = entry.versionSpec;
928
982
  }
929
- if (line.startsWith("--constraint")) {
930
- const path4 = extractArgValue(line, "--constraint");
931
- if (path4) {
932
- options.constraintFiles.push(path4);
933
- return true;
934
- }
983
+ if (entry.extras.length > 0) {
984
+ req.extras = entry.extras;
935
985
  }
936
- if (line.startsWith("--index-url")) {
937
- const url = extractArgValue(line, "--index-url");
938
- if (url) {
939
- options.indexUrl = url;
940
- return true;
941
- }
942
- }
943
- if (line.startsWith("-i ") || line === "-i") {
944
- const match = line.match(/^-i\s+(\S+)/);
945
- if (match) {
946
- options.indexUrl = match[1];
947
- return true;
948
- }
949
- }
950
- if (line.startsWith("--extra-index-url")) {
951
- const url = extractArgValue(line, "--extra-index-url");
952
- if (url) {
953
- options.extraIndexUrls.push(url);
954
- return true;
955
- }
986
+ if (entry.markers) {
987
+ req.markers = entry.markers;
956
988
  }
957
- if (line.startsWith("--editable")) {
958
- const path4 = extractArgValue(line, "--editable");
959
- if (path4) {
960
- return { editable: path4 };
961
- }
962
- }
963
- if (line.startsWith("-e ") || line.startsWith("-e ")) {
964
- const path4 = line.slice(2).trim();
965
- if (path4) {
966
- return { editable: path4 };
967
- }
968
- }
969
- if (line.startsWith("--find-links")) {
970
- const url = extractArgValue(line, "--find-links");
971
- if (url) {
972
- if (!options.findLinks)
973
- options.findLinks = [];
974
- options.findLinks.push(url);
975
- return true;
976
- }
977
- }
978
- if (line.startsWith("-f ") || line.startsWith("-f ")) {
979
- const match = line.match(/^-f\s+(\S+)/);
980
- if (match) {
981
- if (!options.findLinks)
982
- options.findLinks = [];
983
- options.findLinks.push(match[1]);
984
- return true;
985
- }
986
- }
987
- if (line === "--no-index" || line.startsWith("--no-index ")) {
988
- options.noIndex = true;
989
- return true;
990
- }
991
- if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
992
- return true;
993
- }
994
- if (line.startsWith("--")) {
995
- return true;
996
- }
997
- return false;
998
- }
999
- function stripInlineHashes(line) {
1000
- return line.replace(/\s+--hash=\S+/g, "").trim();
1001
- }
1002
- function extractInlineHashes(line) {
1003
- const hashes = [];
1004
- const hashRegex = /--hash=(\S+)/g;
1005
- let match;
1006
- while ((match = hashRegex.exec(line)) != null) {
1007
- hashes.push(match[1]);
1008
- }
1009
- return hashes;
1010
- }
1011
- function extractArgValue(line, option) {
1012
- let value = null;
1013
- if (line.startsWith(`${option}=`)) {
1014
- value = line.slice(option.length + 1).trim();
1015
- } else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
1016
- value = line.slice(option.length).trim();
1017
- }
1018
- if (!value)
1019
- return null;
1020
- const commentIdx = value.indexOf(" #");
1021
- if (commentIdx !== -1) {
1022
- value = value.slice(0, commentIdx).trim();
1023
- }
1024
- return value || null;
1025
- }
1026
- function isPathOrUrlRequirement(line) {
1027
- if (line.startsWith("./") || line.startsWith("../"))
1028
- return true;
1029
- if (line.startsWith("/"))
1030
- return true;
1031
- if (line.startsWith("~/"))
1032
- return true;
1033
- if (/^(https?|ftp|file):\/\//i.test(line))
1034
- return true;
1035
- if (isBareArchiveFilename(line))
1036
- return true;
1037
- return false;
1038
- }
1039
- function isBareArchiveFilename(line) {
1040
- if (line.includes(" @ "))
1041
- return false;
1042
- let check = line;
1043
- const commentIdx = check.indexOf(" #");
1044
- if (commentIdx !== -1)
1045
- check = check.slice(0, commentIdx);
1046
- const markerIdx = check.indexOf(" ;");
1047
- if (markerIdx !== -1)
1048
- check = check.slice(0, markerIdx);
1049
- const extrasIdx = check.indexOf("[");
1050
- if (extrasIdx !== -1)
1051
- check = check.slice(0, extrasIdx);
1052
- check = check.trim();
1053
- return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
1054
- }
1055
- function parseWheelFilename(filename) {
1056
- if (!filename.endsWith(".whl"))
1057
- return null;
1058
- const stem = filename.slice(0, -4);
1059
- const parts = stem.split("-");
1060
- if (parts.length < 5 || parts.length > 6)
1061
- return null;
1062
- const name = parts[0];
1063
- const version = parts[1];
1064
- if (!name || !version)
1065
- return null;
1066
- return {
1067
- name: name.replace(/_/g, "-"),
1068
- version
1069
- };
1070
- }
1071
- function parseSdistFilename(filename) {
1072
- let stem = filename;
1073
- for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
1074
- if (stem.endsWith(ext)) {
1075
- stem = stem.slice(0, -ext.length);
1076
- break;
1077
- }
1078
- }
1079
- if (stem === filename)
1080
- return null;
1081
- const parts = stem.split("-");
1082
- let versionIdx = -1;
1083
- for (let i = 1; i < parts.length; i++) {
1084
- if (/^\d/.test(parts[i])) {
1085
- versionIdx = i;
1086
- break;
989
+ if (entry.vcs) {
990
+ const source = {
991
+ git: entry.vcs.url
992
+ };
993
+ if (entry.vcs.rev) {
994
+ source.rev = entry.vcs.rev;
1087
995
  }
1088
- }
1089
- if (versionIdx === -1)
1090
- return null;
1091
- return {
1092
- name: parts.slice(0, versionIdx).join("-"),
1093
- version: parts.slice(versionIdx).join("-")
1094
- };
1095
- }
1096
- function normalizePathRequirement(rawLine) {
1097
- let line = rawLine;
1098
- const commentIdx = line.indexOf(" #");
1099
- if (commentIdx !== -1) {
1100
- line = line.slice(0, commentIdx).trim();
1101
- }
1102
- let markers;
1103
- const markerIdx = line.indexOf(" ;");
1104
- if (markerIdx !== -1) {
1105
- markers = line.slice(markerIdx + 2).trim();
1106
- line = line.slice(0, markerIdx).trim();
1107
- }
1108
- let extras;
1109
- const extrasMatch = line.match(/\[([^\]]+)\]$/);
1110
- if (extrasMatch) {
1111
- extras = extrasMatch[1].split(",").map((e) => e.trim());
1112
- line = line.slice(0, extrasMatch.index).trim();
1113
- }
1114
- const isUrl = /^(https?|ftp|file):\/\//i.test(line);
1115
- let filename;
1116
- if (isUrl) {
1117
- try {
1118
- const url = new URL(line);
1119
- filename = url.pathname.split("/").pop() || "";
1120
- } catch {
1121
- return null;
996
+ if (editable) {
997
+ source.editable = true;
1122
998
  }
1123
- } else {
1124
- const cleanPath = line.replace(/\/+$/, "");
1125
- filename = cleanPath.split("/").pop() || "";
1126
- }
1127
- if (!filename)
1128
- return null;
1129
- let name;
1130
- let version;
1131
- const wheelParsed = parseWheelFilename(filename);
1132
- if (wheelParsed) {
1133
- name = wheelParsed.name;
1134
- version = wheelParsed.version;
999
+ req.source = source;
1135
1000
  }
1136
- if (!name) {
1137
- const sdistParsed = parseSdistFilename(filename);
1138
- if (sdistParsed) {
1139
- name = sdistParsed.name;
1140
- version = sdistParsed.version;
1001
+ if (entry.url) {
1002
+ if (entry.url.startsWith("file://") && !entry.vcs) {
1003
+ const given = entry.givenUrl ?? entry.url;
1004
+ const path4 = given.startsWith("file://") ? given.slice("file://".length) : given;
1005
+ req.source = { path: path4, ...editable ? { editable: true } : {} };
1006
+ } else {
1007
+ req.url = entry.url;
1141
1008
  }
1142
1009
  }
1143
- if (!name) {
1144
- name = filename.replace(/[-_.]+/g, "-").toLowerCase();
1145
- }
1146
- if (!name)
1147
- return null;
1148
- const req = { name };
1149
- if (version) {
1150
- req.version = `==${version}`;
1151
- }
1152
- if (extras && extras.length > 0) {
1153
- req.extras = extras;
1154
- }
1155
- if (markers) {
1156
- req.markers = markers;
1157
- }
1158
- if (isUrl) {
1159
- req.url = line;
1160
- } else {
1161
- req.source = { path: line };
1010
+ if (entry.hashes.length > 0) {
1011
+ req.hashes = entry.hashes;
1162
1012
  }
1163
1013
  return req;
1164
1014
  }
1165
- function convertRequirementsToPyprojectToml(fileContent, readFile4) {
1015
+ function rebasePath(p, workingDir, packageRoot) {
1016
+ if (!workingDir || !packageRoot || workingDir === packageRoot)
1017
+ return p;
1018
+ if (isAbsolute(p))
1019
+ return p;
1020
+ const abs = join3(workingDir, p);
1021
+ const rel = relative(packageRoot, abs);
1022
+ if (isAbsolute(rel))
1023
+ return rel;
1024
+ if (rel.startsWith(".."))
1025
+ return rel;
1026
+ return "./" + rel;
1027
+ }
1028
+ async function convertRequirementsToPyprojectToml(fileContent, options) {
1166
1029
  const pyproject = {};
1167
- const parsed = parseRequirementsFile(fileContent, readFile4);
1030
+ const parsed = await parseRequirementsFile(fileContent, options);
1168
1031
  const deps = [];
1169
1032
  const sources = {};
1033
+ const { workingDir, packageRoot } = options ?? {};
1170
1034
  for (const req of parsed.requirements) {
1171
1035
  deps.push(formatPep508(req));
1172
1036
  if (req.source) {
1037
+ const source = { ...req.source };
1038
+ if (source.path) {
1039
+ source.path = rebasePath(source.path, workingDir, packageRoot);
1040
+ }
1173
1041
  if (Object.prototype.hasOwnProperty.call(sources, req.name)) {
1174
- sources[req.name].push(req.source);
1042
+ sources[req.name].push(source);
1175
1043
  } else {
1176
- sources[req.name] = [req.source];
1044
+ sources[req.name] = [source];
1177
1045
  }
1178
1046
  }
1179
1047
  }
@@ -1223,217 +1091,39 @@ function buildIndexEntries(pipOptions) {
1223
1091
  }
1224
1092
  return indexes;
1225
1093
  }
1226
- function parseRequirementsFile(fileContent, readFile4) {
1227
- const visited = /* @__PURE__ */ new Set();
1228
- return parseRequirementsFileInternal(fileContent, readFile4, visited);
1094
+ async function parseRequirementsFile(fileContent, options) {
1095
+ const wasmResult = await parseWithWasm(
1096
+ fileContent,
1097
+ options?.readFile,
1098
+ options?.workingDir
1099
+ );
1100
+ return processWasmResult(wasmResult);
1229
1101
  }
1230
- function parseRequirementsFileInternal(fileContent, readFile4, visited) {
1231
- const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
1232
- const hashMap = buildHashMap(fileContent);
1233
- const requirements = parsePipRequirementsFile(cleanedContent);
1102
+ function processWasmResult(wasmResult) {
1234
1103
  const normalized = [];
1235
- const mergedOptions = {
1236
- requirementFiles: [...options.requirementFiles],
1237
- constraintFiles: [...options.constraintFiles],
1238
- indexUrl: options.indexUrl,
1239
- extraIndexUrls: [...options.extraIndexUrls],
1240
- findLinks: options.findLinks ? [...options.findLinks] : void 0,
1241
- noIndex: options.noIndex
1104
+ for (const entry of wasmResult.requirements) {
1105
+ normalized.push(wasmEntryToNormalized(entry, false));
1106
+ }
1107
+ for (const entry of wasmResult.editables) {
1108
+ const norm = wasmEntryToNormalized(entry, true);
1109
+ if (!norm.source && !entry.vcs) {
1110
+ norm.source = { path: entry.pep508, editable: true };
1111
+ } else if (norm.source && !norm.source.editable) {
1112
+ norm.source.editable = true;
1113
+ }
1114
+ normalized.push(norm);
1115
+ }
1116
+ const pipOptions = {
1117
+ indexUrl: wasmResult.indexUrl || void 0,
1118
+ extraIndexUrls: [...wasmResult.extraIndexUrls],
1119
+ findLinks: wasmResult.findLinks.length > 0 ? [...wasmResult.findLinks] : void 0,
1120
+ noIndex: wasmResult.noIndex || void 0
1242
1121
  };
1243
- for (const req of requirements) {
1244
- if (req.type === "RequirementsFile") {
1245
- mergedOptions.requirementFiles.push(req.path);
1246
- continue;
1247
- }
1248
- if (req.type === "ConstraintsFile") {
1249
- mergedOptions.constraintFiles.push(req.path);
1250
- continue;
1251
- }
1252
- const norm = normalizeRequirement(req);
1253
- if (norm != null) {
1254
- const hashes = hashMap.get(norm.name.toLowerCase());
1255
- if (hashes && hashes.length > 0) {
1256
- norm.hashes = hashes;
1257
- }
1258
- normalized.push(norm);
1259
- }
1260
- }
1261
- for (const rawPath of pathRequirements) {
1262
- const norm = normalizePathRequirement(rawPath);
1263
- if (norm != null) {
1264
- normalized.push(norm);
1265
- }
1266
- }
1267
- for (const rawPath of editableRequirements) {
1268
- const norm = normalizePathRequirement(rawPath);
1269
- if (norm != null) {
1270
- if (norm.source) {
1271
- norm.source.editable = true;
1272
- } else {
1273
- norm.source = { path: rawPath, editable: true };
1274
- }
1275
- normalized.push(norm);
1276
- }
1277
- }
1278
- if (readFile4) {
1279
- for (const refPath of mergedOptions.requirementFiles) {
1280
- const refPathKey = normalize(refPath);
1281
- if (visited.has(refPathKey)) {
1282
- continue;
1283
- }
1284
- visited.add(refPathKey);
1285
- const refContent = readFile4(refPath);
1286
- if (refContent != null) {
1287
- const refParsed = parseRequirementsFileInternal(
1288
- refContent,
1289
- readFile4,
1290
- visited
1291
- );
1292
- const existingNames = new Set(
1293
- normalized.map((r) => r.name.toLowerCase())
1294
- );
1295
- for (const req of refParsed.requirements) {
1296
- if (!existingNames.has(req.name.toLowerCase())) {
1297
- normalized.push(req);
1298
- existingNames.add(req.name.toLowerCase());
1299
- }
1300
- }
1301
- if (refParsed.pipOptions.indexUrl) {
1302
- mergedOptions.indexUrl = refParsed.pipOptions.indexUrl;
1303
- }
1304
- for (const url of refParsed.pipOptions.extraIndexUrls) {
1305
- if (!mergedOptions.extraIndexUrls.includes(url)) {
1306
- mergedOptions.extraIndexUrls.push(url);
1307
- }
1308
- }
1309
- for (const constraintPath of refParsed.pipOptions.constraintFiles) {
1310
- if (!mergedOptions.constraintFiles.includes(constraintPath)) {
1311
- mergedOptions.constraintFiles.push(constraintPath);
1312
- }
1313
- }
1314
- if (refParsed.pipOptions.findLinks) {
1315
- if (!mergedOptions.findLinks)
1316
- mergedOptions.findLinks = [];
1317
- for (const fl of refParsed.pipOptions.findLinks) {
1318
- if (!mergedOptions.findLinks.includes(fl)) {
1319
- mergedOptions.findLinks.push(fl);
1320
- }
1321
- }
1322
- }
1323
- if (refParsed.pipOptions.noIndex) {
1324
- mergedOptions.noIndex = true;
1325
- }
1326
- }
1327
- }
1328
- }
1329
1122
  return {
1330
1123
  requirements: normalized,
1331
- pipOptions: mergedOptions
1124
+ pipOptions
1332
1125
  };
1333
1126
  }
1334
- function buildHashMap(fileContent) {
1335
- const hashMap = /* @__PURE__ */ new Map();
1336
- const lines = fileContent.split(/\r?\n/);
1337
- for (let i = 0; i < lines.length; i++) {
1338
- let line = lines[i].trim();
1339
- if (line === "" || line.startsWith("#") || line.startsWith("-")) {
1340
- continue;
1341
- }
1342
- while (line.endsWith("\\") && i + 1 < lines.length) {
1343
- i++;
1344
- line = line.slice(0, -1) + lines[i].trim();
1345
- }
1346
- const hashes = extractInlineHashes(line);
1347
- if (hashes.length === 0) {
1348
- continue;
1349
- }
1350
- const packageMatch = line.match(/^([a-zA-Z0-9][-a-zA-Z0-9._]*)/);
1351
- if (packageMatch) {
1352
- const packageName = packageMatch[1].toLowerCase();
1353
- hashMap.set(packageName, hashes);
1354
- }
1355
- }
1356
- return hashMap;
1357
- }
1358
- function normalizeRequirement(req) {
1359
- if (req.type === "RequirementsFile" || req.type === "ConstraintsFile") {
1360
- return null;
1361
- }
1362
- if (req.type === "ProjectURL") {
1363
- return normalizeProjectURLRequirement(req);
1364
- }
1365
- if (req.type === "ProjectName") {
1366
- return normalizeProjectNameRequirement(req);
1367
- }
1368
- return null;
1369
- }
1370
- function normalizeProjectNameRequirement(req) {
1371
- const normalized = {
1372
- name: req.name
1373
- };
1374
- if (req.extras && req.extras.length > 0) {
1375
- normalized.extras = req.extras;
1376
- }
1377
- if (req.versionSpec && req.versionSpec.length > 0) {
1378
- normalized.version = req.versionSpec.map((spec) => `${spec.operator}${spec.version}`).join(",");
1379
- }
1380
- if (req.environmentMarkerTree) {
1381
- normalized.markers = formatEnvironmentMarkers(req.environmentMarkerTree);
1382
- }
1383
- return normalized;
1384
- }
1385
- function normalizeProjectURLRequirement(req) {
1386
- const normalized = {
1387
- name: req.name
1388
- };
1389
- if (req.extras && req.extras.length > 0) {
1390
- normalized.extras = req.extras;
1391
- }
1392
- if (req.environmentMarkerTree) {
1393
- normalized.markers = formatEnvironmentMarkers(req.environmentMarkerTree);
1394
- }
1395
- if (isGitUrl(req.url)) {
1396
- const parsed = parseGitUrl(req.url);
1397
- if (parsed) {
1398
- const source = {
1399
- git: parsed.url
1400
- };
1401
- if (parsed.ref) {
1402
- source.rev = parsed.ref;
1403
- }
1404
- if (parsed.editable) {
1405
- source.editable = true;
1406
- }
1407
- normalized.source = source;
1408
- }
1409
- }
1410
- normalized.url = req.url;
1411
- return normalized;
1412
- }
1413
- function formatEnvironmentMarkers(marker) {
1414
- if (isEnvironmentMarkerNode(marker)) {
1415
- const left = formatEnvironmentMarkers(marker.left);
1416
- const right = formatEnvironmentMarkers(marker.right);
1417
- return `(${left}) ${marker.operator} (${right})`;
1418
- }
1419
- const leaf = marker;
1420
- const leftStr = formatMarkerValue(leaf.left);
1421
- const rightStr = formatMarkerValue(leaf.right);
1422
- return `${leftStr} ${leaf.operator} ${rightStr}`;
1423
- }
1424
- function isEnvironmentMarkerNode(marker) {
1425
- if (typeof marker !== "object" || marker == null) {
1426
- return false;
1427
- }
1428
- const op = marker.operator;
1429
- return op === "and" || op === "or";
1430
- }
1431
- function formatMarkerValue(value) {
1432
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1433
- return value;
1434
- }
1435
- return value;
1436
- }
1437
1127
 
1438
1128
  // src/manifest/uv-config/schema.ts
1439
1129
  var UvConfigWorkspaceSchema = uvConfigWorkspaceSchema.passthrough();
@@ -1903,7 +1593,8 @@ function computeRequiresPython(manifest, workspaceManifest, configs) {
1903
1593
  constraints.push({
1904
1594
  request: pythonVersionConfig.data,
1905
1595
  source: pythonVersionConfig.path,
1906
- prettySource: pythonVersionConfig.path
1596
+ prettySource: pythonVersionConfig.path,
1597
+ specifier: pythonVersionConfig.specifier
1907
1598
  });
1908
1599
  hasPythonVersionFile = true;
1909
1600
  break;
@@ -1918,7 +1609,8 @@ function computeRequiresPython(manifest, workspaceManifest, configs) {
1918
1609
  constraints.push({
1919
1610
  request: [request],
1920
1611
  source: manifest.path,
1921
- prettySource: `"requires-python" key in ${manifest.path}`
1612
+ prettySource: `"requires-python" key in ${manifest.path}`,
1613
+ specifier: manifestRequiresPython
1922
1614
  });
1923
1615
  }
1924
1616
  } else {
@@ -1930,7 +1622,8 @@ function computeRequiresPython(manifest, workspaceManifest, configs) {
1930
1622
  constraints.push({
1931
1623
  request: [request],
1932
1624
  source: workspaceManifest.path,
1933
- prettySource: `"requires-python" key in ${workspaceManifest.path}`
1625
+ prettySource: `"requires-python" key in ${workspaceManifest.path}`,
1626
+ specifier: workspaceRequiresPython
1934
1627
  });
1935
1628
  }
1936
1629
  }
@@ -2151,7 +1844,13 @@ async function maybeLoadRequirementsTxt(root, subdir, fileName) {
2151
1844
  return null;
2152
1845
  }
2153
1846
  try {
2154
- const pyproject = convertRequirementsToPyprojectToml(requirementsContent);
1847
+ const pyproject = await convertRequirementsToPyprojectToml(
1848
+ requirementsContent,
1849
+ {
1850
+ workingDir: path3.join(root, subdir),
1851
+ packageRoot: root
1852
+ }
1853
+ );
2155
1854
  return {
2156
1855
  path: requirementsTxtRelPath,
2157
1856
  data: pyproject,
@@ -2205,7 +1904,8 @@ async function maybeLoadPythonRequest(root, subdir) {
2205
1904
  return {
2206
1905
  kind: ".python-version" /* PythonVersion */,
2207
1906
  path: dotPythonVersionRelPath,
2208
- data: pyreq
1907
+ data: pyreq,
1908
+ specifier: data.trim()
2209
1909
  };
2210
1910
  }
2211
1911
 
@@ -2249,7 +1949,9 @@ function parseUvLock(content, path4) {
2249
1949
  const packages = (parsed.package ?? []).filter((pkg) => pkg.name && pkg.version).map((pkg) => ({
2250
1950
  name: pkg.name,
2251
1951
  version: pkg.version,
2252
- source: pkg.source
1952
+ source: pkg.source,
1953
+ wheels: (pkg.wheels ?? []).map((w) => ({ url: w.url })),
1954
+ ...pkg.dependencies ? { dependencies: pkg.dependencies } : {}
2253
1955
  }));
2254
1956
  return { version: parsed.version, packages };
2255
1957
  }
@@ -2305,6 +2007,20 @@ function classifyPackages(options) {
2305
2007
  return { privatePackages, publicPackages, packageVersions };
2306
2008
  }
2307
2009
 
2010
+ // src/manifest/wheel-compat.ts
2011
+ async function isWheelCompatible(wheelFilename, pythonMajor, pythonMinor, osName, archName, osMajor, osMinor) {
2012
+ const mod = await importWasmModule();
2013
+ return mod.isWheelCompatible(
2014
+ wheelFilename,
2015
+ pythonMajor,
2016
+ pythonMinor,
2017
+ osName,
2018
+ archName,
2019
+ osMajor,
2020
+ osMinor
2021
+ );
2022
+ }
2023
+
2308
2024
  // src/manifest/python-selector.ts
2309
2025
  function selectPython(constraints, available) {
2310
2026
  const warnings = [];
@@ -2617,7 +2333,9 @@ export {
2617
2333
  extendDistRecord,
2618
2334
  getStringConstant,
2619
2335
  isPrivatePackageSource,
2336
+ isWheelCompatible,
2620
2337
  normalizePackageName2 as normalizePackageName,
2338
+ parsePep508,
2621
2339
  parseUvLock,
2622
2340
  scanDistributions,
2623
2341
  selectPython,