@vercel/python-analysis 0.9.1 → 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.cjs CHANGED
@@ -70,6 +70,7 @@ __export(src_exports, {
70
70
  extendDistRecord: () => extendDistRecord,
71
71
  getStringConstant: () => getStringConstant,
72
72
  isPrivatePackageSource: () => isPrivatePackageSource,
73
+ isWheelCompatible: () => isWheelCompatible,
73
74
  normalizePackageName: () => normalizePackageName2,
74
75
  parseUvLock: () => parseUvLock,
75
76
  scanDistributions: () => scanDistributions,
@@ -83,6 +84,84 @@ module.exports = __toCommonJS(src_exports);
83
84
  var import_promises = require("fs/promises");
84
85
  var import_node_module = require("module");
85
86
  var import_node_path = require("path");
87
+
88
+ // src/wasm/host-utils.ts
89
+ var import_node_async_hooks = require("async_hooks");
90
+ var import_posix = require("path/posix");
91
+ var import_url = require("url");
92
+ var readFileStorage = new import_node_async_hooks.AsyncLocalStorage();
93
+ var WitResultError = class extends Error {
94
+ constructor(message) {
95
+ super(message);
96
+ this.payload = message;
97
+ }
98
+ };
99
+ function createHostUtils() {
100
+ return {
101
+ readFile(path4) {
102
+ const ctx = readFileStorage.getStore();
103
+ if (ctx?.readFile) {
104
+ const relative2 = stripWorkingDir(path4, ctx.workingDir);
105
+ if (relative2 != null) {
106
+ const result = ctx.readFile(relative2);
107
+ if (result != null)
108
+ return result;
109
+ }
110
+ }
111
+ throw new WitResultError(`File not found: ${path4}`);
112
+ },
113
+ domainToAscii(domain) {
114
+ try {
115
+ if (/[:#?/@[\]%\\\t\n\r]/.test(domain)) {
116
+ throw new Error("domain contains invalid characters");
117
+ }
118
+ const url = new URL(`http://${domain}/`);
119
+ if (url.hostname === "" && domain !== "") {
120
+ throw new Error("domain resolved to empty hostname");
121
+ }
122
+ return url.hostname;
123
+ } catch {
124
+ throw { payload: `Invalid domain: ${domain}` };
125
+ }
126
+ },
127
+ domainToUnicode(domain) {
128
+ const result = (0, import_url.domainToUnicode)(domain);
129
+ if (result !== "" || domain === "") {
130
+ return [result, true];
131
+ }
132
+ return [domain, false];
133
+ },
134
+ nfcNormalize(s) {
135
+ return s.normalize("NFC");
136
+ },
137
+ nfdNormalize(s) {
138
+ return s.normalize("NFD");
139
+ },
140
+ nfkcNormalize(s) {
141
+ return s.normalize("NFKC");
142
+ },
143
+ nfkdNormalize(s) {
144
+ return s.normalize("NFKD");
145
+ }
146
+ };
147
+ }
148
+ function stripWorkingDir(path4, workingDir) {
149
+ const normalized = (0, import_posix.normalize)(path4);
150
+ if (!workingDir) {
151
+ return normalized;
152
+ }
153
+ const normalizedDir = (0, import_posix.normalize)(workingDir);
154
+ const prefix = normalizedDir.endsWith("/") ? normalizedDir : normalizedDir + "/";
155
+ if (normalized.startsWith(prefix)) {
156
+ return normalized.slice(prefix.length);
157
+ }
158
+ if (normalized === normalizedDir) {
159
+ return "";
160
+ }
161
+ return null;
162
+ }
163
+
164
+ // src/wasm/load.ts
86
165
  var WASI_SHIM_PATH = "@bytecodealliance/preview2-shim/instantiation";
87
166
  var WASM_MODULE_PATH = "#wasm/vercel_python_analysis.js";
88
167
  var wasmInstance = null;
@@ -110,7 +189,10 @@ async function importWasmModule() {
110
189
  const wasiShimModule = await import(WASI_SHIM_PATH);
111
190
  const WASIShim = wasiShimModule.WASIShim;
112
191
  const wasmModule = await import(WASM_MODULE_PATH);
113
- const imports = new WASIShim().getImportObject();
192
+ const imports = {
193
+ ...new WASIShim().getImportObject(),
194
+ "vercel:python-analysis/host-utils": createHostUtils()
195
+ };
114
196
  const instance = await wasmModule.instantiate(getCoreModule, imports);
115
197
  wasmInstance = instance;
116
198
  return instance;
@@ -329,7 +411,7 @@ async function extendDistRecord(sitePackagesDir, packageName, paths) {
329
411
  }
330
412
 
331
413
  // src/manifest/package.ts
332
- var import_node_path6 = __toESM(require("path"), 1);
414
+ var import_node_path5 = __toESM(require("path"), 1);
333
415
  var import_minimatch = require("minimatch");
334
416
 
335
417
  // src/util/config.ts
@@ -902,356 +984,98 @@ var PyProjectToolSectionSchema = pyProjectToolSectionSchema.passthrough();
902
984
  var PyProjectTomlSchema = pyProjectTomlSchema.passthrough();
903
985
 
904
986
  // src/manifest/requirements-txt-parser.ts
905
- var import_node_path5 = require("path");
906
- var import_pip_requirements_js = require("pip-requirements-js");
987
+ var import_posix2 = require("path/posix");
907
988
  var PRIMARY_INDEX_NAME = "primary";
908
989
  var EXTRA_INDEX_PREFIX = "extra-";
909
990
  var FIND_LINKS_PREFIX = "find-links-";
910
- function parseGitUrl(url) {
911
- if (!url.startsWith("git+")) {
912
- return null;
913
- }
914
- let remaining = url.slice(4);
915
- let egg;
916
- const fragmentIdx = remaining.indexOf("#");
917
- if (fragmentIdx !== -1) {
918
- const fragment = remaining.slice(fragmentIdx + 1);
919
- remaining = remaining.slice(0, fragmentIdx);
920
- for (const part of fragment.split("&")) {
921
- const [key, value] = part.split("=");
922
- if (key === "egg" && value) {
923
- egg = value;
924
- }
925
- }
926
- }
927
- let ref;
928
- const lastSlashIdx = remaining.lastIndexOf("/");
929
- const atIdx = remaining.indexOf("@", lastSlashIdx > 0 ? lastSlashIdx : 0);
930
- if (atIdx !== -1 && atIdx > remaining.indexOf("://")) {
931
- ref = remaining.slice(atIdx + 1);
932
- remaining = remaining.slice(0, atIdx);
991
+ async function parseWithWasm(content, readFile4, workingDir) {
992
+ const wasm = await importWasmModule();
993
+ const resolvedDir = workingDir ?? "/";
994
+ if (readFile4) {
995
+ return readFileStorage.run(
996
+ { readFile: readFile4, workingDir: resolvedDir },
997
+ () => wasm.parseRequirementsTxt(content, resolvedDir, void 0)
998
+ );
933
999
  }
934
- return {
935
- url: remaining,
936
- ref,
937
- egg
938
- };
939
- }
940
- function isGitUrl(url) {
941
- return url.startsWith("git+");
1000
+ return wasm.parseRequirementsTxt(content, workingDir ?? void 0, void 0);
942
1001
  }
943
- function extractPipArguments(fileContent) {
944
- const options = {
945
- requirementFiles: [],
946
- constraintFiles: [],
947
- extraIndexUrls: []
948
- };
949
- const lines = fileContent.split(/\r?\n/);
950
- const cleanedLines = [];
951
- const pathRequirements = [];
952
- const editableRequirements = [];
953
- for (let i = 0; i < lines.length; i++) {
954
- const line = lines[i];
955
- const trimmed = line.trim();
956
- if (trimmed === "" || trimmed.startsWith("#")) {
957
- cleanedLines.push(line);
958
- continue;
959
- }
960
- let fullLine = trimmed;
961
- let linesConsumed = 0;
962
- while (fullLine.endsWith("\\") && i + linesConsumed + 1 < lines.length) {
963
- linesConsumed++;
964
- fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
965
- }
966
- const extracted = tryExtractPipArgument(fullLine, options);
967
- if (extracted === true) {
968
- i += linesConsumed;
969
- } else if (typeof extracted === "object" && extracted.editable) {
970
- editableRequirements.push(extracted.editable);
971
- i += linesConsumed;
972
- } else {
973
- if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
974
- i += linesConsumed;
975
- continue;
976
- }
977
- const strippedLine = stripInlineHashes(fullLine);
978
- const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
979
- if (isPathOrUrlRequirement(effectiveLine)) {
980
- pathRequirements.push(effectiveLine);
981
- } else if (strippedLine !== fullLine) {
982
- cleanedLines.push(strippedLine);
983
- } else {
984
- cleanedLines.push(line);
985
- for (let j = 1; j <= linesConsumed; j++) {
986
- cleanedLines.push(lines[i + j]);
987
- }
988
- }
989
- i += linesConsumed;
1002
+ function wasmEntryToNormalized(entry, editable) {
1003
+ let name = entry.name || "";
1004
+ if (!name && entry.url) {
1005
+ const urlPath = entry.url.replace(/^file:\/\//, "");
1006
+ const basename = urlPath.replace(/\/$/, "").split("/").pop();
1007
+ if (basename && !basename.includes(".")) {
1008
+ name = basename;
990
1009
  }
991
1010
  }
992
- return {
993
- cleanedContent: cleanedLines.join("\n"),
994
- options,
995
- pathRequirements,
996
- editableRequirements
1011
+ const req = {
1012
+ name
997
1013
  };
998
- }
999
- function tryExtractPipArgument(line, options) {
1000
- if (line.startsWith("--requirement")) {
1001
- const path4 = extractArgValue(line, "--requirement");
1002
- if (path4) {
1003
- options.requirementFiles.push(path4);
1004
- return true;
1005
- }
1014
+ if (entry.versionSpec) {
1015
+ req.version = entry.versionSpec;
1006
1016
  }
1007
- if (line.startsWith("--constraint")) {
1008
- const path4 = extractArgValue(line, "--constraint");
1009
- if (path4) {
1010
- options.constraintFiles.push(path4);
1011
- return true;
1012
- }
1017
+ if (entry.extras.length > 0) {
1018
+ req.extras = entry.extras;
1013
1019
  }
1014
- if (line.startsWith("--index-url")) {
1015
- const url = extractArgValue(line, "--index-url");
1016
- if (url) {
1017
- options.indexUrl = url;
1018
- return true;
1019
- }
1020
- }
1021
- if (line.startsWith("-i ") || line === "-i") {
1022
- const match = line.match(/^-i\s+(\S+)/);
1023
- if (match) {
1024
- options.indexUrl = match[1];
1025
- return true;
1026
- }
1027
- }
1028
- if (line.startsWith("--extra-index-url")) {
1029
- const url = extractArgValue(line, "--extra-index-url");
1030
- if (url) {
1031
- options.extraIndexUrls.push(url);
1032
- return true;
1033
- }
1034
- }
1035
- if (line.startsWith("--editable")) {
1036
- const path4 = extractArgValue(line, "--editable");
1037
- if (path4) {
1038
- return { editable: path4 };
1039
- }
1040
- }
1041
- if (line.startsWith("-e ") || line.startsWith("-e ")) {
1042
- const path4 = line.slice(2).trim();
1043
- if (path4) {
1044
- return { editable: path4 };
1045
- }
1046
- }
1047
- if (line.startsWith("--find-links")) {
1048
- const url = extractArgValue(line, "--find-links");
1049
- if (url) {
1050
- if (!options.findLinks)
1051
- options.findLinks = [];
1052
- options.findLinks.push(url);
1053
- return true;
1054
- }
1055
- }
1056
- if (line.startsWith("-f ") || line.startsWith("-f ")) {
1057
- const match = line.match(/^-f\s+(\S+)/);
1058
- if (match) {
1059
- if (!options.findLinks)
1060
- options.findLinks = [];
1061
- options.findLinks.push(match[1]);
1062
- return true;
1063
- }
1064
- }
1065
- if (line === "--no-index" || line.startsWith("--no-index ")) {
1066
- options.noIndex = true;
1067
- return true;
1068
- }
1069
- if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
1070
- return true;
1071
- }
1072
- if (line.startsWith("--")) {
1073
- return true;
1074
- }
1075
- return false;
1076
- }
1077
- function stripInlineHashes(line) {
1078
- return line.replace(/\s+--hash=\S+/g, "").trim();
1079
- }
1080
- function extractInlineHashes(line) {
1081
- const hashes = [];
1082
- const hashRegex = /--hash=(\S+)/g;
1083
- let match;
1084
- while ((match = hashRegex.exec(line)) != null) {
1085
- hashes.push(match[1]);
1086
- }
1087
- return hashes;
1088
- }
1089
- function extractArgValue(line, option) {
1090
- let value = null;
1091
- if (line.startsWith(`${option}=`)) {
1092
- value = line.slice(option.length + 1).trim();
1093
- } else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
1094
- value = line.slice(option.length).trim();
1095
- }
1096
- if (!value)
1097
- return null;
1098
- const commentIdx = value.indexOf(" #");
1099
- if (commentIdx !== -1) {
1100
- value = value.slice(0, commentIdx).trim();
1101
- }
1102
- return value || null;
1103
- }
1104
- function isPathOrUrlRequirement(line) {
1105
- if (line.startsWith("./") || line.startsWith("../"))
1106
- return true;
1107
- if (line.startsWith("/"))
1108
- return true;
1109
- if (line.startsWith("~/"))
1110
- return true;
1111
- if (/^(https?|ftp|file):\/\//i.test(line))
1112
- return true;
1113
- if (isBareArchiveFilename(line))
1114
- return true;
1115
- return false;
1116
- }
1117
- function isBareArchiveFilename(line) {
1118
- if (line.includes(" @ "))
1119
- return false;
1120
- let check = line;
1121
- const commentIdx = check.indexOf(" #");
1122
- if (commentIdx !== -1)
1123
- check = check.slice(0, commentIdx);
1124
- const markerIdx = check.indexOf(" ;");
1125
- if (markerIdx !== -1)
1126
- check = check.slice(0, markerIdx);
1127
- const extrasIdx = check.indexOf("[");
1128
- if (extrasIdx !== -1)
1129
- check = check.slice(0, extrasIdx);
1130
- check = check.trim();
1131
- return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
1132
- }
1133
- function parseWheelFilename(filename) {
1134
- if (!filename.endsWith(".whl"))
1135
- return null;
1136
- const stem = filename.slice(0, -4);
1137
- const parts = stem.split("-");
1138
- if (parts.length < 5 || parts.length > 6)
1139
- return null;
1140
- const name = parts[0];
1141
- const version = parts[1];
1142
- if (!name || !version)
1143
- return null;
1144
- return {
1145
- name: name.replace(/_/g, "-"),
1146
- version
1147
- };
1148
- }
1149
- function parseSdistFilename(filename) {
1150
- let stem = filename;
1151
- for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
1152
- if (stem.endsWith(ext)) {
1153
- stem = stem.slice(0, -ext.length);
1154
- break;
1155
- }
1020
+ if (entry.markers) {
1021
+ req.markers = entry.markers;
1156
1022
  }
1157
- if (stem === filename)
1158
- return null;
1159
- const parts = stem.split("-");
1160
- let versionIdx = -1;
1161
- for (let i = 1; i < parts.length; i++) {
1162
- if (/^\d/.test(parts[i])) {
1163
- versionIdx = i;
1164
- break;
1023
+ if (entry.vcs) {
1024
+ const source = {
1025
+ git: entry.vcs.url
1026
+ };
1027
+ if (entry.vcs.rev) {
1028
+ source.rev = entry.vcs.rev;
1165
1029
  }
1166
- }
1167
- if (versionIdx === -1)
1168
- return null;
1169
- return {
1170
- name: parts.slice(0, versionIdx).join("-"),
1171
- version: parts.slice(versionIdx).join("-")
1172
- };
1173
- }
1174
- function normalizePathRequirement(rawLine) {
1175
- let line = rawLine;
1176
- const commentIdx = line.indexOf(" #");
1177
- if (commentIdx !== -1) {
1178
- line = line.slice(0, commentIdx).trim();
1179
- }
1180
- let markers;
1181
- const markerIdx = line.indexOf(" ;");
1182
- if (markerIdx !== -1) {
1183
- markers = line.slice(markerIdx + 2).trim();
1184
- line = line.slice(0, markerIdx).trim();
1185
- }
1186
- let extras;
1187
- const extrasMatch = line.match(/\[([^\]]+)\]$/);
1188
- if (extrasMatch) {
1189
- extras = extrasMatch[1].split(",").map((e) => e.trim());
1190
- line = line.slice(0, extrasMatch.index).trim();
1191
- }
1192
- const isUrl = /^(https?|ftp|file):\/\//i.test(line);
1193
- let filename;
1194
- if (isUrl) {
1195
- try {
1196
- const url = new URL(line);
1197
- filename = url.pathname.split("/").pop() || "";
1198
- } catch {
1199
- return null;
1030
+ if (editable) {
1031
+ source.editable = true;
1200
1032
  }
1201
- } else {
1202
- const cleanPath = line.replace(/\/+$/, "");
1203
- filename = cleanPath.split("/").pop() || "";
1033
+ req.source = source;
1204
1034
  }
1205
- if (!filename)
1206
- return null;
1207
- let name;
1208
- let version;
1209
- const wheelParsed = parseWheelFilename(filename);
1210
- if (wheelParsed) {
1211
- name = wheelParsed.name;
1212
- version = wheelParsed.version;
1213
- }
1214
- if (!name) {
1215
- const sdistParsed = parseSdistFilename(filename);
1216
- if (sdistParsed) {
1217
- name = sdistParsed.name;
1218
- version = sdistParsed.version;
1035
+ if (entry.url) {
1036
+ if (entry.url.startsWith("file://") && !entry.vcs) {
1037
+ const given = entry.givenUrl ?? entry.url;
1038
+ const path4 = given.startsWith("file://") ? given.slice("file://".length) : given;
1039
+ req.source = { path: path4, ...editable ? { editable: true } : {} };
1040
+ } else {
1041
+ req.url = entry.url;
1219
1042
  }
1220
1043
  }
1221
- if (!name) {
1222
- name = filename.replace(/[-_.]+/g, "-").toLowerCase();
1223
- }
1224
- if (!name)
1225
- return null;
1226
- const req = { name };
1227
- if (version) {
1228
- req.version = `==${version}`;
1229
- }
1230
- if (extras && extras.length > 0) {
1231
- req.extras = extras;
1232
- }
1233
- if (markers) {
1234
- req.markers = markers;
1235
- }
1236
- if (isUrl) {
1237
- req.url = line;
1238
- } else {
1239
- req.source = { path: line };
1044
+ if (entry.hashes.length > 0) {
1045
+ req.hashes = entry.hashes;
1240
1046
  }
1241
1047
  return req;
1242
1048
  }
1243
- function convertRequirementsToPyprojectToml(fileContent, readFile4) {
1049
+ function rebasePath(p, workingDir, packageRoot) {
1050
+ if (!workingDir || !packageRoot || workingDir === packageRoot)
1051
+ return p;
1052
+ if ((0, import_posix2.isAbsolute)(p))
1053
+ return p;
1054
+ const abs = (0, import_posix2.join)(workingDir, p);
1055
+ const rel = (0, import_posix2.relative)(packageRoot, abs);
1056
+ if ((0, import_posix2.isAbsolute)(rel))
1057
+ return rel;
1058
+ if (rel.startsWith(".."))
1059
+ return rel;
1060
+ return "./" + rel;
1061
+ }
1062
+ async function convertRequirementsToPyprojectToml(fileContent, options) {
1244
1063
  const pyproject = {};
1245
- const parsed = parseRequirementsFile(fileContent, readFile4);
1064
+ const parsed = await parseRequirementsFile(fileContent, options);
1246
1065
  const deps = [];
1247
1066
  const sources = {};
1067
+ const { workingDir, packageRoot } = options ?? {};
1248
1068
  for (const req of parsed.requirements) {
1249
1069
  deps.push(formatPep508(req));
1250
1070
  if (req.source) {
1071
+ const source = { ...req.source };
1072
+ if (source.path) {
1073
+ source.path = rebasePath(source.path, workingDir, packageRoot);
1074
+ }
1251
1075
  if (Object.prototype.hasOwnProperty.call(sources, req.name)) {
1252
- sources[req.name].push(req.source);
1076
+ sources[req.name].push(source);
1253
1077
  } else {
1254
- sources[req.name] = [req.source];
1078
+ sources[req.name] = [source];
1255
1079
  }
1256
1080
  }
1257
1081
  }
@@ -1301,216 +1125,38 @@ function buildIndexEntries(pipOptions) {
1301
1125
  }
1302
1126
  return indexes;
1303
1127
  }
1304
- function parseRequirementsFile(fileContent, readFile4) {
1305
- const visited = /* @__PURE__ */ new Set();
1306
- return parseRequirementsFileInternal(fileContent, readFile4, visited);
1128
+ async function parseRequirementsFile(fileContent, options) {
1129
+ const wasmResult = await parseWithWasm(
1130
+ fileContent,
1131
+ options?.readFile,
1132
+ options?.workingDir
1133
+ );
1134
+ return processWasmResult(wasmResult);
1307
1135
  }
1308
- function parseRequirementsFileInternal(fileContent, readFile4, visited) {
1309
- const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
1310
- const hashMap = buildHashMap(fileContent);
1311
- const requirements = (0, import_pip_requirements_js.parsePipRequirementsFile)(cleanedContent);
1136
+ function processWasmResult(wasmResult) {
1312
1137
  const normalized = [];
1313
- const mergedOptions = {
1314
- requirementFiles: [...options.requirementFiles],
1315
- constraintFiles: [...options.constraintFiles],
1316
- indexUrl: options.indexUrl,
1317
- extraIndexUrls: [...options.extraIndexUrls],
1318
- findLinks: options.findLinks ? [...options.findLinks] : void 0,
1319
- noIndex: options.noIndex
1138
+ for (const entry of wasmResult.requirements) {
1139
+ normalized.push(wasmEntryToNormalized(entry, false));
1140
+ }
1141
+ for (const entry of wasmResult.editables) {
1142
+ const norm = wasmEntryToNormalized(entry, true);
1143
+ if (!norm.source && !entry.vcs) {
1144
+ norm.source = { path: entry.pep508, editable: true };
1145
+ } else if (norm.source && !norm.source.editable) {
1146
+ norm.source.editable = true;
1147
+ }
1148
+ normalized.push(norm);
1149
+ }
1150
+ const pipOptions = {
1151
+ indexUrl: wasmResult.indexUrl || void 0,
1152
+ extraIndexUrls: [...wasmResult.extraIndexUrls],
1153
+ findLinks: wasmResult.findLinks.length > 0 ? [...wasmResult.findLinks] : void 0,
1154
+ noIndex: wasmResult.noIndex || void 0
1320
1155
  };
1321
- for (const req of requirements) {
1322
- if (req.type === "RequirementsFile") {
1323
- mergedOptions.requirementFiles.push(req.path);
1324
- continue;
1325
- }
1326
- if (req.type === "ConstraintsFile") {
1327
- mergedOptions.constraintFiles.push(req.path);
1328
- continue;
1329
- }
1330
- const norm = normalizeRequirement(req);
1331
- if (norm != null) {
1332
- const hashes = hashMap.get(norm.name.toLowerCase());
1333
- if (hashes && hashes.length > 0) {
1334
- norm.hashes = hashes;
1335
- }
1336
- normalized.push(norm);
1337
- }
1338
- }
1339
- for (const rawPath of pathRequirements) {
1340
- const norm = normalizePathRequirement(rawPath);
1341
- if (norm != null) {
1342
- normalized.push(norm);
1343
- }
1344
- }
1345
- for (const rawPath of editableRequirements) {
1346
- const norm = normalizePathRequirement(rawPath);
1347
- if (norm != null) {
1348
- if (norm.source) {
1349
- norm.source.editable = true;
1350
- } else {
1351
- norm.source = { path: rawPath, editable: true };
1352
- }
1353
- normalized.push(norm);
1354
- }
1355
- }
1356
- if (readFile4) {
1357
- for (const refPath of mergedOptions.requirementFiles) {
1358
- const refPathKey = (0, import_node_path5.normalize)(refPath);
1359
- if (visited.has(refPathKey)) {
1360
- continue;
1361
- }
1362
- visited.add(refPathKey);
1363
- const refContent = readFile4(refPath);
1364
- if (refContent != null) {
1365
- const refParsed = parseRequirementsFileInternal(
1366
- refContent,
1367
- readFile4,
1368
- visited
1369
- );
1370
- const existingNames = new Set(
1371
- normalized.map((r) => r.name.toLowerCase())
1372
- );
1373
- for (const req of refParsed.requirements) {
1374
- if (!existingNames.has(req.name.toLowerCase())) {
1375
- normalized.push(req);
1376
- existingNames.add(req.name.toLowerCase());
1377
- }
1378
- }
1379
- if (refParsed.pipOptions.indexUrl) {
1380
- mergedOptions.indexUrl = refParsed.pipOptions.indexUrl;
1381
- }
1382
- for (const url of refParsed.pipOptions.extraIndexUrls) {
1383
- if (!mergedOptions.extraIndexUrls.includes(url)) {
1384
- mergedOptions.extraIndexUrls.push(url);
1385
- }
1386
- }
1387
- for (const constraintPath of refParsed.pipOptions.constraintFiles) {
1388
- if (!mergedOptions.constraintFiles.includes(constraintPath)) {
1389
- mergedOptions.constraintFiles.push(constraintPath);
1390
- }
1391
- }
1392
- if (refParsed.pipOptions.findLinks) {
1393
- if (!mergedOptions.findLinks)
1394
- mergedOptions.findLinks = [];
1395
- for (const fl of refParsed.pipOptions.findLinks) {
1396
- if (!mergedOptions.findLinks.includes(fl)) {
1397
- mergedOptions.findLinks.push(fl);
1398
- }
1399
- }
1400
- }
1401
- if (refParsed.pipOptions.noIndex) {
1402
- mergedOptions.noIndex = true;
1403
- }
1404
- }
1405
- }
1406
- }
1407
1156
  return {
1408
1157
  requirements: normalized,
1409
- pipOptions: mergedOptions
1410
- };
1411
- }
1412
- function buildHashMap(fileContent) {
1413
- const hashMap = /* @__PURE__ */ new Map();
1414
- const lines = fileContent.split(/\r?\n/);
1415
- for (let i = 0; i < lines.length; i++) {
1416
- let line = lines[i].trim();
1417
- if (line === "" || line.startsWith("#") || line.startsWith("-")) {
1418
- continue;
1419
- }
1420
- while (line.endsWith("\\") && i + 1 < lines.length) {
1421
- i++;
1422
- line = line.slice(0, -1) + lines[i].trim();
1423
- }
1424
- const hashes = extractInlineHashes(line);
1425
- if (hashes.length === 0) {
1426
- continue;
1427
- }
1428
- const packageMatch = line.match(/^([a-zA-Z0-9][-a-zA-Z0-9._]*)/);
1429
- if (packageMatch) {
1430
- const packageName = packageMatch[1].toLowerCase();
1431
- hashMap.set(packageName, hashes);
1432
- }
1433
- }
1434
- return hashMap;
1435
- }
1436
- function normalizeRequirement(req) {
1437
- if (req.type === "RequirementsFile" || req.type === "ConstraintsFile") {
1438
- return null;
1439
- }
1440
- if (req.type === "ProjectURL") {
1441
- return normalizeProjectURLRequirement(req);
1442
- }
1443
- if (req.type === "ProjectName") {
1444
- return normalizeProjectNameRequirement(req);
1445
- }
1446
- return null;
1447
- }
1448
- function normalizeProjectNameRequirement(req) {
1449
- const normalized = {
1450
- name: req.name
1451
- };
1452
- if (req.extras && req.extras.length > 0) {
1453
- normalized.extras = req.extras;
1454
- }
1455
- if (req.versionSpec && req.versionSpec.length > 0) {
1456
- normalized.version = req.versionSpec.map((spec) => `${spec.operator}${spec.version}`).join(",");
1457
- }
1458
- if (req.environmentMarkerTree) {
1459
- normalized.markers = formatEnvironmentMarkers(req.environmentMarkerTree);
1460
- }
1461
- return normalized;
1462
- }
1463
- function normalizeProjectURLRequirement(req) {
1464
- const normalized = {
1465
- name: req.name
1158
+ pipOptions
1466
1159
  };
1467
- if (req.extras && req.extras.length > 0) {
1468
- normalized.extras = req.extras;
1469
- }
1470
- if (req.environmentMarkerTree) {
1471
- normalized.markers = formatEnvironmentMarkers(req.environmentMarkerTree);
1472
- }
1473
- if (isGitUrl(req.url)) {
1474
- const parsed = parseGitUrl(req.url);
1475
- if (parsed) {
1476
- const source = {
1477
- git: parsed.url
1478
- };
1479
- if (parsed.ref) {
1480
- source.rev = parsed.ref;
1481
- }
1482
- if (parsed.editable) {
1483
- source.editable = true;
1484
- }
1485
- normalized.source = source;
1486
- }
1487
- }
1488
- normalized.url = req.url;
1489
- return normalized;
1490
- }
1491
- function formatEnvironmentMarkers(marker) {
1492
- if (isEnvironmentMarkerNode(marker)) {
1493
- const left = formatEnvironmentMarkers(marker.left);
1494
- const right = formatEnvironmentMarkers(marker.right);
1495
- return `(${left}) ${marker.operator} (${right})`;
1496
- }
1497
- const leaf = marker;
1498
- const leftStr = formatMarkerValue(leaf.left);
1499
- const rightStr = formatMarkerValue(leaf.right);
1500
- return `${leftStr} ${leaf.operator} ${rightStr}`;
1501
- }
1502
- function isEnvironmentMarkerNode(marker) {
1503
- if (typeof marker !== "object" || marker == null) {
1504
- return false;
1505
- }
1506
- const op = marker.operator;
1507
- return op === "and" || op === "or";
1508
- }
1509
- function formatMarkerValue(value) {
1510
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1511
- return value;
1512
- }
1513
- return value;
1514
1160
  }
1515
1161
 
1516
1162
  // src/manifest/uv-config/schema.ts
@@ -1907,7 +1553,7 @@ async function discoverPythonPackage({
1907
1553
  }) {
1908
1554
  const entrypointPath = normalizePath(entrypointDir);
1909
1555
  const rootPath = normalizePath(rootDir);
1910
- let prefix = import_node_path6.default.relative(rootPath, entrypointPath);
1556
+ let prefix = import_node_path5.default.relative(rootPath, entrypointPath);
1911
1557
  if (prefix.startsWith("..")) {
1912
1558
  throw new PythonAnalysisError({
1913
1559
  message: "Entrypoint directory outside of repository root",
@@ -1931,7 +1577,7 @@ async function discoverPythonPackage({
1931
1577
  if (prefix === "" || prefix === ".") {
1932
1578
  break;
1933
1579
  }
1934
- prefix = import_node_path6.default.dirname(prefix);
1580
+ prefix = import_node_path5.default.dirname(prefix);
1935
1581
  }
1936
1582
  let entrypointManifest;
1937
1583
  let workspaceManifest;
@@ -1953,8 +1599,8 @@ async function discoverPythonPackage({
1953
1599
  configs = configs.filter(
1954
1600
  (config) => Object.values(config).some(
1955
1601
  (cfg) => cfg !== void 0 && isSubpath(
1956
- import_node_path6.default.dirname(cfg.path),
1957
- import_node_path6.default.dirname(entrypointWorkspaceManifest.path)
1602
+ import_node_path5.default.dirname(cfg.path),
1603
+ import_node_path5.default.dirname(entrypointWorkspaceManifest.path)
1958
1604
  )
1959
1605
  )
1960
1606
  );
@@ -2034,9 +1680,9 @@ function findWorkspaceManifestFor(manifest, manifestStack) {
2034
1680
  if (!Array.isArray(exclude)) {
2035
1681
  exclude = [];
2036
1682
  }
2037
- const entrypointRelPath = import_node_path6.default.relative(
2038
- import_node_path6.default.dirname(parentManifest.path),
2039
- import_node_path6.default.dirname(manifest.path)
1683
+ const entrypointRelPath = import_node_path5.default.relative(
1684
+ import_node_path5.default.dirname(parentManifest.path),
1685
+ import_node_path5.default.dirname(manifest.path)
2040
1686
  );
2041
1687
  if (members.length > 0 && members.some(
2042
1688
  (pat) => (0, import_minimatch.match)([entrypointRelPath], pat).length > 0
@@ -2071,7 +1717,7 @@ async function loadPythonManifest(root, prefix) {
2071
1717
  "requirements-frozen.txt",
2072
1718
  "requirements.txt",
2073
1719
  "requirements.in",
2074
- import_node_path6.default.join("requirements", "prod.txt")
1720
+ import_node_path5.default.join("requirements", "prod.txt")
2075
1721
  ]) {
2076
1722
  const requirementsTxtManifest = await maybeLoadRequirementsTxt(
2077
1723
  root,
@@ -2090,14 +1736,14 @@ async function loadPythonManifest(root, prefix) {
2090
1736
  return manifest;
2091
1737
  }
2092
1738
  async function maybeLoadLockFile(root, subdir) {
2093
- const uvLockRelPath = import_node_path6.default.join(subdir, "uv.lock");
2094
- const uvLockPath = import_node_path6.default.join(root, uvLockRelPath);
1739
+ const uvLockRelPath = import_node_path5.default.join(subdir, "uv.lock");
1740
+ const uvLockPath = import_node_path5.default.join(root, uvLockRelPath);
2095
1741
  const uvLockContent = await readFileTextIfExists(uvLockPath);
2096
1742
  if (uvLockContent != null) {
2097
1743
  return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
2098
1744
  }
2099
- const pylockRelPath = import_node_path6.default.join(subdir, "pylock.toml");
2100
- const pylockPath = import_node_path6.default.join(root, pylockRelPath);
1745
+ const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
1746
+ const pylockPath = import_node_path5.default.join(root, pylockRelPath);
2101
1747
  const pylockContent = await readFileTextIfExists(pylockPath);
2102
1748
  if (pylockContent != null) {
2103
1749
  return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
@@ -2105,8 +1751,8 @@ async function maybeLoadLockFile(root, subdir) {
2105
1751
  return void 0;
2106
1752
  }
2107
1753
  async function maybeLoadPyProjectToml(root, subdir) {
2108
- const pyprojectTomlRelPath = import_node_path6.default.join(subdir, "pyproject.toml");
2109
- const pyprojectTomlPath = import_node_path6.default.join(root, pyprojectTomlRelPath);
1754
+ const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
1755
+ const pyprojectTomlPath = import_node_path5.default.join(root, pyprojectTomlRelPath);
2110
1756
  let pyproject;
2111
1757
  try {
2112
1758
  pyproject = await readConfigIfExists(
@@ -2127,8 +1773,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
2127
1773
  if (pyproject == null) {
2128
1774
  return null;
2129
1775
  }
2130
- const uvTomlRelPath = import_node_path6.default.join(subdir, "uv.toml");
2131
- const uvTomlPath = import_node_path6.default.join(root, uvTomlRelPath);
1776
+ const uvTomlRelPath = import_node_path5.default.join(subdir, "uv.toml");
1777
+ const uvTomlPath = import_node_path5.default.join(root, uvTomlRelPath);
2132
1778
  let uvToml;
2133
1779
  try {
2134
1780
  uvToml = await readConfigIfExists(uvTomlPath, UvConfigSchema);
@@ -2158,8 +1804,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
2158
1804
  };
2159
1805
  }
2160
1806
  async function maybeLoadPipfile(root, subdir) {
2161
- const pipfileRelPath = import_node_path6.default.join(subdir, "Pipfile");
2162
- const pipfilePath = import_node_path6.default.join(root, pipfileRelPath);
1807
+ const pipfileRelPath = import_node_path5.default.join(subdir, "Pipfile");
1808
+ const pipfilePath = import_node_path5.default.join(root, pipfileRelPath);
2163
1809
  let pipfile;
2164
1810
  try {
2165
1811
  pipfile = await readConfigIfExists(pipfilePath, PipfileLikeSchema, ".toml");
@@ -2188,8 +1834,8 @@ async function maybeLoadPipfile(root, subdir) {
2188
1834
  };
2189
1835
  }
2190
1836
  async function maybeLoadPipfileLock(root, subdir) {
2191
- const pipfileLockRelPath = import_node_path6.default.join(subdir, "Pipfile.lock");
2192
- const pipfileLockPath = import_node_path6.default.join(root, pipfileLockRelPath);
1837
+ const pipfileLockRelPath = import_node_path5.default.join(subdir, "Pipfile.lock");
1838
+ const pipfileLockPath = import_node_path5.default.join(root, pipfileLockRelPath);
2193
1839
  let pipfileLock;
2194
1840
  try {
2195
1841
  pipfileLock = await readConfigIfExists(
@@ -2222,14 +1868,20 @@ async function maybeLoadPipfileLock(root, subdir) {
2222
1868
  };
2223
1869
  }
2224
1870
  async function maybeLoadRequirementsTxt(root, subdir, fileName) {
2225
- const requirementsTxtRelPath = import_node_path6.default.join(subdir, fileName);
2226
- const requirementsTxtPath = import_node_path6.default.join(root, requirementsTxtRelPath);
1871
+ const requirementsTxtRelPath = import_node_path5.default.join(subdir, fileName);
1872
+ const requirementsTxtPath = import_node_path5.default.join(root, requirementsTxtRelPath);
2227
1873
  const requirementsContent = await readFileTextIfExists(requirementsTxtPath);
2228
1874
  if (requirementsContent == null) {
2229
1875
  return null;
2230
1876
  }
2231
1877
  try {
2232
- const pyproject = convertRequirementsToPyprojectToml(requirementsContent);
1878
+ const pyproject = await convertRequirementsToPyprojectToml(
1879
+ requirementsContent,
1880
+ {
1881
+ workingDir: import_node_path5.default.join(root, subdir),
1882
+ packageRoot: root
1883
+ }
1884
+ );
2233
1885
  return {
2234
1886
  path: requirementsTxtRelPath,
2235
1887
  data: pyproject,
@@ -2263,8 +1915,8 @@ async function loadPythonConfigs(root, prefix) {
2263
1915
  return configs;
2264
1916
  }
2265
1917
  async function maybeLoadPythonRequest(root, subdir) {
2266
- const dotPythonVersionRelPath = import_node_path6.default.join(subdir, ".python-version");
2267
- const dotPythonVersionPath = import_node_path6.default.join(
1918
+ const dotPythonVersionRelPath = import_node_path5.default.join(subdir, ".python-version");
1919
+ const dotPythonVersionPath = import_node_path5.default.join(
2268
1920
  root,
2269
1921
  dotPythonVersionRelPath
2270
1922
  );
@@ -2327,7 +1979,8 @@ function parseUvLock(content, path4) {
2327
1979
  const packages = (parsed.package ?? []).filter((pkg) => pkg.name && pkg.version).map((pkg) => ({
2328
1980
  name: pkg.name,
2329
1981
  version: pkg.version,
2330
- source: pkg.source
1982
+ source: pkg.source,
1983
+ wheels: (pkg.wheels ?? []).map((w) => ({ url: w.url }))
2331
1984
  }));
2332
1985
  return { version: parsed.version, packages };
2333
1986
  }
@@ -2383,6 +2036,20 @@ function classifyPackages(options) {
2383
2036
  return { privatePackages, publicPackages, packageVersions };
2384
2037
  }
2385
2038
 
2039
+ // src/manifest/wheel-compat.ts
2040
+ async function isWheelCompatible(wheelFilename, pythonMajor, pythonMinor, osName, archName, osMajor, osMinor) {
2041
+ const mod = await importWasmModule();
2042
+ return mod.isWheelCompatible(
2043
+ wheelFilename,
2044
+ pythonMajor,
2045
+ pythonMinor,
2046
+ osName,
2047
+ archName,
2048
+ osMajor,
2049
+ osMinor
2050
+ );
2051
+ }
2052
+
2386
2053
  // src/manifest/python-selector.ts
2387
2054
  function selectPython(constraints, available) {
2388
2055
  const warnings = [];
@@ -2696,6 +2363,7 @@ var HashDigestSchema = hashDigestSchema;
2696
2363
  extendDistRecord,
2697
2364
  getStringConstant,
2698
2365
  isPrivatePackageSource,
2366
+ isWheelCompatible,
2699
2367
  normalizePackageName,
2700
2368
  parseUvLock,
2701
2369
  scanDistributions,