@vercel/python-analysis 0.9.0 → 0.10.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.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;
@@ -824,356 +905,98 @@ var PyProjectToolSectionSchema = pyProjectToolSectionSchema.passthrough();
824
905
  var PyProjectTomlSchema = pyProjectTomlSchema.passthrough();
825
906
 
826
907
  // src/manifest/requirements-txt-parser.ts
827
- import { normalize } from "path";
828
- import { parsePipRequirementsFile } from "pip-requirements-js";
908
+ import { isAbsolute, join as join3, relative } from "path/posix";
829
909
  var PRIMARY_INDEX_NAME = "primary";
830
910
  var EXTRA_INDEX_PREFIX = "extra-";
831
911
  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);
912
+ async function parseWithWasm(content, readFile4, workingDir) {
913
+ const wasm = await importWasmModule();
914
+ const resolvedDir = workingDir ?? "/";
915
+ if (readFile4) {
916
+ return readFileStorage.run(
917
+ { readFile: readFile4, workingDir: resolvedDir },
918
+ () => wasm.parseRequirementsTxt(content, resolvedDir, void 0)
919
+ );
855
920
  }
856
- return {
857
- url: remaining,
858
- ref,
859
- egg
860
- };
861
- }
862
- function isGitUrl(url) {
863
- return url.startsWith("git+");
921
+ return wasm.parseRequirementsTxt(content, workingDir ?? void 0, void 0);
864
922
  }
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;
923
+ function wasmEntryToNormalized(entry, editable) {
924
+ let name = entry.name || "";
925
+ if (!name && entry.url) {
926
+ const urlPath = entry.url.replace(/^file:\/\//, "");
927
+ const basename = urlPath.replace(/\/$/, "").split("/").pop();
928
+ if (basename && !basename.includes(".")) {
929
+ name = basename;
912
930
  }
913
931
  }
914
- return {
915
- cleanedContent: cleanedLines.join("\n"),
916
- options,
917
- pathRequirements,
918
- editableRequirements
932
+ const req = {
933
+ name
919
934
  };
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
- }
935
+ if (entry.versionSpec) {
936
+ req.version = entry.versionSpec;
928
937
  }
929
- if (line.startsWith("--constraint")) {
930
- const path4 = extractArgValue(line, "--constraint");
931
- if (path4) {
932
- options.constraintFiles.push(path4);
933
- return true;
934
- }
938
+ if (entry.extras.length > 0) {
939
+ req.extras = entry.extras;
935
940
  }
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
- }
956
- }
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
- }
941
+ if (entry.markers) {
942
+ req.markers = entry.markers;
1078
943
  }
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;
944
+ if (entry.vcs) {
945
+ const source = {
946
+ git: entry.vcs.url
947
+ };
948
+ if (entry.vcs.rev) {
949
+ source.rev = entry.vcs.rev;
1087
950
  }
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;
951
+ if (editable) {
952
+ source.editable = true;
1122
953
  }
1123
- } else {
1124
- const cleanPath = line.replace(/\/+$/, "");
1125
- filename = cleanPath.split("/").pop() || "";
954
+ req.source = source;
1126
955
  }
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;
1135
- }
1136
- if (!name) {
1137
- const sdistParsed = parseSdistFilename(filename);
1138
- if (sdistParsed) {
1139
- name = sdistParsed.name;
1140
- version = sdistParsed.version;
956
+ if (entry.url) {
957
+ if (entry.url.startsWith("file://") && !entry.vcs) {
958
+ const given = entry.givenUrl ?? entry.url;
959
+ const path4 = given.startsWith("file://") ? given.slice("file://".length) : given;
960
+ req.source = { path: path4, ...editable ? { editable: true } : {} };
961
+ } else {
962
+ req.url = entry.url;
1141
963
  }
1142
964
  }
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 };
965
+ if (entry.hashes.length > 0) {
966
+ req.hashes = entry.hashes;
1162
967
  }
1163
968
  return req;
1164
969
  }
1165
- function convertRequirementsToPyprojectToml(fileContent, readFile4) {
970
+ function rebasePath(p, workingDir, packageRoot) {
971
+ if (!workingDir || !packageRoot || workingDir === packageRoot)
972
+ return p;
973
+ if (isAbsolute(p))
974
+ return p;
975
+ const abs = join3(workingDir, p);
976
+ const rel = relative(packageRoot, abs);
977
+ if (isAbsolute(rel))
978
+ return rel;
979
+ if (rel.startsWith(".."))
980
+ return rel;
981
+ return "./" + rel;
982
+ }
983
+ async function convertRequirementsToPyprojectToml(fileContent, options) {
1166
984
  const pyproject = {};
1167
- const parsed = parseRequirementsFile(fileContent, readFile4);
985
+ const parsed = await parseRequirementsFile(fileContent, options);
1168
986
  const deps = [];
1169
987
  const sources = {};
988
+ const { workingDir, packageRoot } = options ?? {};
1170
989
  for (const req of parsed.requirements) {
1171
990
  deps.push(formatPep508(req));
1172
991
  if (req.source) {
992
+ const source = { ...req.source };
993
+ if (source.path) {
994
+ source.path = rebasePath(source.path, workingDir, packageRoot);
995
+ }
1173
996
  if (Object.prototype.hasOwnProperty.call(sources, req.name)) {
1174
- sources[req.name].push(req.source);
997
+ sources[req.name].push(source);
1175
998
  } else {
1176
- sources[req.name] = [req.source];
999
+ sources[req.name] = [source];
1177
1000
  }
1178
1001
  }
1179
1002
  }
@@ -1223,216 +1046,38 @@ function buildIndexEntries(pipOptions) {
1223
1046
  }
1224
1047
  return indexes;
1225
1048
  }
1226
- function parseRequirementsFile(fileContent, readFile4) {
1227
- const visited = /* @__PURE__ */ new Set();
1228
- return parseRequirementsFileInternal(fileContent, readFile4, visited);
1049
+ async function parseRequirementsFile(fileContent, options) {
1050
+ const wasmResult = await parseWithWasm(
1051
+ fileContent,
1052
+ options?.readFile,
1053
+ options?.workingDir
1054
+ );
1055
+ return processWasmResult(wasmResult);
1229
1056
  }
1230
- function parseRequirementsFileInternal(fileContent, readFile4, visited) {
1231
- const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
1232
- const hashMap = buildHashMap(fileContent);
1233
- const requirements = parsePipRequirementsFile(cleanedContent);
1057
+ function processWasmResult(wasmResult) {
1234
1058
  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
1059
+ for (const entry of wasmResult.requirements) {
1060
+ normalized.push(wasmEntryToNormalized(entry, false));
1061
+ }
1062
+ for (const entry of wasmResult.editables) {
1063
+ const norm = wasmEntryToNormalized(entry, true);
1064
+ if (!norm.source && !entry.vcs) {
1065
+ norm.source = { path: entry.pep508, editable: true };
1066
+ } else if (norm.source && !norm.source.editable) {
1067
+ norm.source.editable = true;
1068
+ }
1069
+ normalized.push(norm);
1070
+ }
1071
+ const pipOptions = {
1072
+ indexUrl: wasmResult.indexUrl || void 0,
1073
+ extraIndexUrls: [...wasmResult.extraIndexUrls],
1074
+ findLinks: wasmResult.findLinks.length > 0 ? [...wasmResult.findLinks] : void 0,
1075
+ noIndex: wasmResult.noIndex || void 0
1242
1076
  };
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
1077
  return {
1330
1078
  requirements: normalized,
1331
- pipOptions: mergedOptions
1332
- };
1333
- }
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
1079
+ pipOptions
1388
1080
  };
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
1081
  }
1437
1082
 
1438
1083
  // src/manifest/uv-config/schema.ts
@@ -2151,7 +1796,13 @@ async function maybeLoadRequirementsTxt(root, subdir, fileName) {
2151
1796
  return null;
2152
1797
  }
2153
1798
  try {
2154
- const pyproject = convertRequirementsToPyprojectToml(requirementsContent);
1799
+ const pyproject = await convertRequirementsToPyprojectToml(
1800
+ requirementsContent,
1801
+ {
1802
+ workingDir: path3.join(root, subdir),
1803
+ packageRoot: root
1804
+ }
1805
+ );
2155
1806
  return {
2156
1807
  path: requirementsTxtRelPath,
2157
1808
  data: pyproject,
@@ -2249,7 +1900,8 @@ function parseUvLock(content, path4) {
2249
1900
  const packages = (parsed.package ?? []).filter((pkg) => pkg.name && pkg.version).map((pkg) => ({
2250
1901
  name: pkg.name,
2251
1902
  version: pkg.version,
2252
- source: pkg.source
1903
+ source: pkg.source,
1904
+ wheels: (pkg.wheels ?? []).map((w) => ({ url: w.url }))
2253
1905
  }));
2254
1906
  return { version: parsed.version, packages };
2255
1907
  }
@@ -2305,6 +1957,20 @@ function classifyPackages(options) {
2305
1957
  return { privatePackages, publicPackages, packageVersions };
2306
1958
  }
2307
1959
 
1960
+ // src/manifest/wheel-compat.ts
1961
+ async function isWheelCompatible(wheelFilename, pythonMajor, pythonMinor, osName, archName, osMajor, osMinor) {
1962
+ const mod = await importWasmModule();
1963
+ return mod.isWheelCompatible(
1964
+ wheelFilename,
1965
+ pythonMajor,
1966
+ pythonMinor,
1967
+ osName,
1968
+ archName,
1969
+ osMajor,
1970
+ osMinor
1971
+ );
1972
+ }
1973
+
2308
1974
  // src/manifest/python-selector.ts
2309
1975
  function selectPython(constraints, available) {
2310
1976
  const warnings = [];
@@ -2617,6 +2283,7 @@ export {
2617
2283
  extendDistRecord,
2618
2284
  getStringConstant,
2619
2285
  isPrivatePackageSource,
2286
+ isWheelCompatible,
2620
2287
  normalizePackageName2 as normalizePackageName,
2621
2288
  parseUvLock,
2622
2289
  scanDistributions,