@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.cjs CHANGED
@@ -70,7 +70,9 @@ __export(src_exports, {
70
70
  extendDistRecord: () => extendDistRecord,
71
71
  getStringConstant: () => getStringConstant,
72
72
  isPrivatePackageSource: () => isPrivatePackageSource,
73
+ isWheelCompatible: () => isWheelCompatible,
73
74
  normalizePackageName: () => normalizePackageName2,
75
+ parsePep508: () => parsePep508,
74
76
  parseUvLock: () => parseUvLock,
75
77
  scanDistributions: () => scanDistributions,
76
78
  selectPython: () => selectPython,
@@ -83,6 +85,84 @@ module.exports = __toCommonJS(src_exports);
83
85
  var import_promises = require("fs/promises");
84
86
  var import_node_module = require("module");
85
87
  var import_node_path = require("path");
88
+
89
+ // src/wasm/host-utils.ts
90
+ var import_node_async_hooks = require("async_hooks");
91
+ var import_posix = require("path/posix");
92
+ var import_url = require("url");
93
+ var readFileStorage = new import_node_async_hooks.AsyncLocalStorage();
94
+ var WitResultError = class extends Error {
95
+ constructor(message) {
96
+ super(message);
97
+ this.payload = message;
98
+ }
99
+ };
100
+ function createHostUtils() {
101
+ return {
102
+ readFile(path4) {
103
+ const ctx = readFileStorage.getStore();
104
+ if (ctx?.readFile) {
105
+ const relative2 = stripWorkingDir(path4, ctx.workingDir);
106
+ if (relative2 != null) {
107
+ const result = ctx.readFile(relative2);
108
+ if (result != null)
109
+ return result;
110
+ }
111
+ }
112
+ throw new WitResultError(`File not found: ${path4}`);
113
+ },
114
+ domainToAscii(domain) {
115
+ try {
116
+ if (/[:#?/@[\]%\\\t\n\r]/.test(domain)) {
117
+ throw new Error("domain contains invalid characters");
118
+ }
119
+ const url = new URL(`http://${domain}/`);
120
+ if (url.hostname === "" && domain !== "") {
121
+ throw new Error("domain resolved to empty hostname");
122
+ }
123
+ return url.hostname;
124
+ } catch {
125
+ throw { payload: `Invalid domain: ${domain}` };
126
+ }
127
+ },
128
+ domainToUnicode(domain) {
129
+ const result = (0, import_url.domainToUnicode)(domain);
130
+ if (result !== "" || domain === "") {
131
+ return [result, true];
132
+ }
133
+ return [domain, false];
134
+ },
135
+ nfcNormalize(s) {
136
+ return s.normalize("NFC");
137
+ },
138
+ nfdNormalize(s) {
139
+ return s.normalize("NFD");
140
+ },
141
+ nfkcNormalize(s) {
142
+ return s.normalize("NFKC");
143
+ },
144
+ nfkdNormalize(s) {
145
+ return s.normalize("NFKD");
146
+ }
147
+ };
148
+ }
149
+ function stripWorkingDir(path4, workingDir) {
150
+ const normalized = (0, import_posix.normalize)(path4);
151
+ if (!workingDir) {
152
+ return normalized;
153
+ }
154
+ const normalizedDir = (0, import_posix.normalize)(workingDir);
155
+ const prefix = normalizedDir.endsWith("/") ? normalizedDir : normalizedDir + "/";
156
+ if (normalized.startsWith(prefix)) {
157
+ return normalized.slice(prefix.length);
158
+ }
159
+ if (normalized === normalizedDir) {
160
+ return "";
161
+ }
162
+ return null;
163
+ }
164
+
165
+ // src/wasm/load.ts
86
166
  var WASI_SHIM_PATH = "@bytecodealliance/preview2-shim/instantiation";
87
167
  var WASM_MODULE_PATH = "#wasm/vercel_python_analysis.js";
88
168
  var wasmInstance = null;
@@ -110,7 +190,10 @@ async function importWasmModule() {
110
190
  const wasiShimModule = await import(WASI_SHIM_PATH);
111
191
  const WASIShim = wasiShimModule.WASIShim;
112
192
  const wasmModule = await import(WASM_MODULE_PATH);
113
- const imports = new WASIShim().getImportObject();
193
+ const imports = {
194
+ ...new WASIShim().getImportObject(),
195
+ "vercel:python-analysis/host-utils": createHostUtils()
196
+ };
114
197
  const instance = await wasmModule.instantiate(getCoreModule, imports);
115
198
  wasmInstance = instance;
116
199
  return instance;
@@ -183,6 +266,41 @@ function mergeExtras(existing, additional) {
183
266
  }
184
267
  return result.size > 0 ? Array.from(result) : void 0;
185
268
  }
269
+ function wasmEntryToRequirement(entry) {
270
+ const req = {
271
+ name: entry.name || ""
272
+ };
273
+ if (entry.versionSpec) {
274
+ req.version = entry.versionSpec;
275
+ }
276
+ if (entry.extras.length > 0) {
277
+ req.extras = entry.extras;
278
+ }
279
+ if (entry.markers) {
280
+ req.markers = entry.markers;
281
+ }
282
+ if (entry.url) {
283
+ req.url = entry.url;
284
+ }
285
+ return req;
286
+ }
287
+ async function parsePep508(depOrDeps) {
288
+ const wasm = await importWasmModule();
289
+ if (Array.isArray(depOrDeps)) {
290
+ return depOrDeps.map((dep) => {
291
+ try {
292
+ return wasmEntryToRequirement(wasm.parsePep508(dep));
293
+ } catch {
294
+ return null;
295
+ }
296
+ });
297
+ }
298
+ try {
299
+ return wasmEntryToRequirement(wasm.parsePep508(depOrDeps));
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
186
304
 
187
305
  // src/manifest/dist-metadata.ts
188
306
  async function readDistInfoFile(distInfoDir, filename) {
@@ -329,7 +447,7 @@ async function extendDistRecord(sitePackagesDir, packageName, paths) {
329
447
  }
330
448
 
331
449
  // src/manifest/package.ts
332
- var import_node_path6 = __toESM(require("path"), 1);
450
+ var import_node_path5 = __toESM(require("path"), 1);
333
451
  var import_minimatch = require("minimatch");
334
452
 
335
453
  // src/util/config.ts
@@ -837,7 +955,8 @@ var uvIndexEntrySchema = import_zod3.z.object({
837
955
  var uvConfigSchema = import_zod3.z.object({
838
956
  sources: import_zod3.z.record(import_zod3.z.union([dependencySourceSchema, import_zod3.z.array(dependencySourceSchema)])).optional(),
839
957
  index: import_zod3.z.array(uvIndexEntrySchema).optional(),
840
- workspace: uvConfigWorkspaceSchema.optional()
958
+ workspace: uvConfigWorkspaceSchema.optional(),
959
+ "dev-dependencies": import_zod3.z.array(import_zod3.z.string()).optional()
841
960
  });
842
961
 
843
962
  // src/manifest/pyproject/schema.zod.ts
@@ -878,7 +997,16 @@ var pyProjectProjectSchema = import_zod4.z.object({
878
997
  scripts: import_zod4.z.record(import_zod4.z.string()).optional(),
879
998
  entry_points: import_zod4.z.record(import_zod4.z.record(import_zod4.z.string())).optional()
880
999
  });
881
- var pyProjectDependencyGroupsSchema = import_zod4.z.record(import_zod4.z.array(import_zod4.z.string()));
1000
+ var dependencyGroupIncludeSchema = import_zod4.z.object({
1001
+ "include-group": import_zod4.z.string()
1002
+ });
1003
+ var dependencyGroupEntrySchema = import_zod4.z.union([
1004
+ import_zod4.z.string(),
1005
+ dependencyGroupIncludeSchema
1006
+ ]);
1007
+ var pyProjectDependencyGroupsSchema = import_zod4.z.record(
1008
+ import_zod4.z.array(dependencyGroupEntrySchema)
1009
+ );
882
1010
  var pyProjectToolSectionSchema = import_zod4.z.object({
883
1011
  uv: uvConfigSchema.optional()
884
1012
  });
@@ -902,356 +1030,98 @@ var PyProjectToolSectionSchema = pyProjectToolSectionSchema.passthrough();
902
1030
  var PyProjectTomlSchema = pyProjectTomlSchema.passthrough();
903
1031
 
904
1032
  // src/manifest/requirements-txt-parser.ts
905
- var import_node_path5 = require("path");
906
- var import_pip_requirements_js = require("pip-requirements-js");
1033
+ var import_posix2 = require("path/posix");
907
1034
  var PRIMARY_INDEX_NAME = "primary";
908
1035
  var EXTRA_INDEX_PREFIX = "extra-";
909
1036
  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);
1037
+ async function parseWithWasm(content, readFile4, workingDir) {
1038
+ const wasm = await importWasmModule();
1039
+ const resolvedDir = workingDir ?? "/";
1040
+ if (readFile4) {
1041
+ return readFileStorage.run(
1042
+ { readFile: readFile4, workingDir: resolvedDir },
1043
+ () => wasm.parseRequirementsTxt(content, resolvedDir, void 0)
1044
+ );
933
1045
  }
934
- return {
935
- url: remaining,
936
- ref,
937
- egg
938
- };
1046
+ return wasm.parseRequirementsTxt(content, workingDir ?? void 0, void 0);
939
1047
  }
940
- function isGitUrl(url) {
941
- return url.startsWith("git+");
942
- }
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;
1048
+ function wasmEntryToNormalized(entry, editable) {
1049
+ let name = entry.name || "";
1050
+ if (!name && entry.url) {
1051
+ const urlPath = entry.url.replace(/^file:\/\//, "");
1052
+ const basename = urlPath.replace(/\/$/, "").split("/").pop();
1053
+ if (basename && !basename.includes(".")) {
1054
+ name = basename;
990
1055
  }
991
1056
  }
992
- return {
993
- cleanedContent: cleanedLines.join("\n"),
994
- options,
995
- pathRequirements,
996
- editableRequirements
1057
+ const req = {
1058
+ name
997
1059
  };
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
- }
1060
+ if (entry.versionSpec) {
1061
+ req.version = entry.versionSpec;
1006
1062
  }
1007
- if (line.startsWith("--constraint")) {
1008
- const path4 = extractArgValue(line, "--constraint");
1009
- if (path4) {
1010
- options.constraintFiles.push(path4);
1011
- return true;
1012
- }
1063
+ if (entry.extras.length > 0) {
1064
+ req.extras = entry.extras;
1013
1065
  }
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
- }
1066
+ if (entry.markers) {
1067
+ req.markers = entry.markers;
1034
1068
  }
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
- }
1156
- }
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;
1069
+ if (entry.vcs) {
1070
+ const source = {
1071
+ git: entry.vcs.url
1072
+ };
1073
+ if (entry.vcs.rev) {
1074
+ source.rev = entry.vcs.rev;
1165
1075
  }
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;
1076
+ if (editable) {
1077
+ source.editable = true;
1200
1078
  }
1201
- } else {
1202
- const cleanPath = line.replace(/\/+$/, "");
1203
- filename = cleanPath.split("/").pop() || "";
1204
- }
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;
1079
+ req.source = source;
1213
1080
  }
1214
- if (!name) {
1215
- const sdistParsed = parseSdistFilename(filename);
1216
- if (sdistParsed) {
1217
- name = sdistParsed.name;
1218
- version = sdistParsed.version;
1081
+ if (entry.url) {
1082
+ if (entry.url.startsWith("file://") && !entry.vcs) {
1083
+ const given = entry.givenUrl ?? entry.url;
1084
+ const path4 = given.startsWith("file://") ? given.slice("file://".length) : given;
1085
+ req.source = { path: path4, ...editable ? { editable: true } : {} };
1086
+ } else {
1087
+ req.url = entry.url;
1219
1088
  }
1220
1089
  }
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 };
1090
+ if (entry.hashes.length > 0) {
1091
+ req.hashes = entry.hashes;
1240
1092
  }
1241
1093
  return req;
1242
1094
  }
1243
- function convertRequirementsToPyprojectToml(fileContent, readFile4) {
1095
+ function rebasePath(p, workingDir, packageRoot) {
1096
+ if (!workingDir || !packageRoot || workingDir === packageRoot)
1097
+ return p;
1098
+ if ((0, import_posix2.isAbsolute)(p))
1099
+ return p;
1100
+ const abs = (0, import_posix2.join)(workingDir, p);
1101
+ const rel = (0, import_posix2.relative)(packageRoot, abs);
1102
+ if ((0, import_posix2.isAbsolute)(rel))
1103
+ return rel;
1104
+ if (rel.startsWith(".."))
1105
+ return rel;
1106
+ return "./" + rel;
1107
+ }
1108
+ async function convertRequirementsToPyprojectToml(fileContent, options) {
1244
1109
  const pyproject = {};
1245
- const parsed = parseRequirementsFile(fileContent, readFile4);
1110
+ const parsed = await parseRequirementsFile(fileContent, options);
1246
1111
  const deps = [];
1247
1112
  const sources = {};
1113
+ const { workingDir, packageRoot } = options ?? {};
1248
1114
  for (const req of parsed.requirements) {
1249
1115
  deps.push(formatPep508(req));
1250
1116
  if (req.source) {
1117
+ const source = { ...req.source };
1118
+ if (source.path) {
1119
+ source.path = rebasePath(source.path, workingDir, packageRoot);
1120
+ }
1251
1121
  if (Object.prototype.hasOwnProperty.call(sources, req.name)) {
1252
- sources[req.name].push(req.source);
1122
+ sources[req.name].push(source);
1253
1123
  } else {
1254
- sources[req.name] = [req.source];
1124
+ sources[req.name] = [source];
1255
1125
  }
1256
1126
  }
1257
1127
  }
@@ -1301,217 +1171,39 @@ function buildIndexEntries(pipOptions) {
1301
1171
  }
1302
1172
  return indexes;
1303
1173
  }
1304
- function parseRequirementsFile(fileContent, readFile4) {
1305
- const visited = /* @__PURE__ */ new Set();
1306
- return parseRequirementsFileInternal(fileContent, readFile4, visited);
1174
+ async function parseRequirementsFile(fileContent, options) {
1175
+ const wasmResult = await parseWithWasm(
1176
+ fileContent,
1177
+ options?.readFile,
1178
+ options?.workingDir
1179
+ );
1180
+ return processWasmResult(wasmResult);
1307
1181
  }
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);
1182
+ function processWasmResult(wasmResult) {
1312
1183
  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
1184
+ for (const entry of wasmResult.requirements) {
1185
+ normalized.push(wasmEntryToNormalized(entry, false));
1186
+ }
1187
+ for (const entry of wasmResult.editables) {
1188
+ const norm = wasmEntryToNormalized(entry, true);
1189
+ if (!norm.source && !entry.vcs) {
1190
+ norm.source = { path: entry.pep508, editable: true };
1191
+ } else if (norm.source && !norm.source.editable) {
1192
+ norm.source.editable = true;
1193
+ }
1194
+ normalized.push(norm);
1195
+ }
1196
+ const pipOptions = {
1197
+ indexUrl: wasmResult.indexUrl || void 0,
1198
+ extraIndexUrls: [...wasmResult.extraIndexUrls],
1199
+ findLinks: wasmResult.findLinks.length > 0 ? [...wasmResult.findLinks] : void 0,
1200
+ noIndex: wasmResult.noIndex || void 0
1320
1201
  };
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
1202
  return {
1408
1203
  requirements: normalized,
1409
- pipOptions: mergedOptions
1204
+ pipOptions
1410
1205
  };
1411
1206
  }
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
1466
- };
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
- }
1515
1207
 
1516
1208
  // src/manifest/uv-config/schema.ts
1517
1209
  var UvConfigWorkspaceSchema = uvConfigWorkspaceSchema.passthrough();
@@ -1907,7 +1599,7 @@ async function discoverPythonPackage({
1907
1599
  }) {
1908
1600
  const entrypointPath = normalizePath(entrypointDir);
1909
1601
  const rootPath = normalizePath(rootDir);
1910
- let prefix = import_node_path6.default.relative(rootPath, entrypointPath);
1602
+ let prefix = import_node_path5.default.relative(rootPath, entrypointPath);
1911
1603
  if (prefix.startsWith("..")) {
1912
1604
  throw new PythonAnalysisError({
1913
1605
  message: "Entrypoint directory outside of repository root",
@@ -1931,7 +1623,7 @@ async function discoverPythonPackage({
1931
1623
  if (prefix === "" || prefix === ".") {
1932
1624
  break;
1933
1625
  }
1934
- prefix = import_node_path6.default.dirname(prefix);
1626
+ prefix = import_node_path5.default.dirname(prefix);
1935
1627
  }
1936
1628
  let entrypointManifest;
1937
1629
  let workspaceManifest;
@@ -1953,8 +1645,8 @@ async function discoverPythonPackage({
1953
1645
  configs = configs.filter(
1954
1646
  (config) => Object.values(config).some(
1955
1647
  (cfg) => cfg !== void 0 && isSubpath(
1956
- import_node_path6.default.dirname(cfg.path),
1957
- import_node_path6.default.dirname(entrypointWorkspaceManifest.path)
1648
+ import_node_path5.default.dirname(cfg.path),
1649
+ import_node_path5.default.dirname(entrypointWorkspaceManifest.path)
1958
1650
  )
1959
1651
  )
1960
1652
  );
@@ -1981,7 +1673,8 @@ function computeRequiresPython(manifest, workspaceManifest, configs) {
1981
1673
  constraints.push({
1982
1674
  request: pythonVersionConfig.data,
1983
1675
  source: pythonVersionConfig.path,
1984
- prettySource: pythonVersionConfig.path
1676
+ prettySource: pythonVersionConfig.path,
1677
+ specifier: pythonVersionConfig.specifier
1985
1678
  });
1986
1679
  hasPythonVersionFile = true;
1987
1680
  break;
@@ -1996,7 +1689,8 @@ function computeRequiresPython(manifest, workspaceManifest, configs) {
1996
1689
  constraints.push({
1997
1690
  request: [request],
1998
1691
  source: manifest.path,
1999
- prettySource: `"requires-python" key in ${manifest.path}`
1692
+ prettySource: `"requires-python" key in ${manifest.path}`,
1693
+ specifier: manifestRequiresPython
2000
1694
  });
2001
1695
  }
2002
1696
  } else {
@@ -2008,7 +1702,8 @@ function computeRequiresPython(manifest, workspaceManifest, configs) {
2008
1702
  constraints.push({
2009
1703
  request: [request],
2010
1704
  source: workspaceManifest.path,
2011
- prettySource: `"requires-python" key in ${workspaceManifest.path}`
1705
+ prettySource: `"requires-python" key in ${workspaceManifest.path}`,
1706
+ specifier: workspaceRequiresPython
2012
1707
  });
2013
1708
  }
2014
1709
  }
@@ -2034,9 +1729,9 @@ function findWorkspaceManifestFor(manifest, manifestStack) {
2034
1729
  if (!Array.isArray(exclude)) {
2035
1730
  exclude = [];
2036
1731
  }
2037
- const entrypointRelPath = import_node_path6.default.relative(
2038
- import_node_path6.default.dirname(parentManifest.path),
2039
- import_node_path6.default.dirname(manifest.path)
1732
+ const entrypointRelPath = import_node_path5.default.relative(
1733
+ import_node_path5.default.dirname(parentManifest.path),
1734
+ import_node_path5.default.dirname(manifest.path)
2040
1735
  );
2041
1736
  if (members.length > 0 && members.some(
2042
1737
  (pat) => (0, import_minimatch.match)([entrypointRelPath], pat).length > 0
@@ -2071,7 +1766,7 @@ async function loadPythonManifest(root, prefix) {
2071
1766
  "requirements-frozen.txt",
2072
1767
  "requirements.txt",
2073
1768
  "requirements.in",
2074
- import_node_path6.default.join("requirements", "prod.txt")
1769
+ import_node_path5.default.join("requirements", "prod.txt")
2075
1770
  ]) {
2076
1771
  const requirementsTxtManifest = await maybeLoadRequirementsTxt(
2077
1772
  root,
@@ -2090,14 +1785,14 @@ async function loadPythonManifest(root, prefix) {
2090
1785
  return manifest;
2091
1786
  }
2092
1787
  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);
1788
+ const uvLockRelPath = import_node_path5.default.join(subdir, "uv.lock");
1789
+ const uvLockPath = import_node_path5.default.join(root, uvLockRelPath);
2095
1790
  const uvLockContent = await readFileTextIfExists(uvLockPath);
2096
1791
  if (uvLockContent != null) {
2097
1792
  return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
2098
1793
  }
2099
- const pylockRelPath = import_node_path6.default.join(subdir, "pylock.toml");
2100
- const pylockPath = import_node_path6.default.join(root, pylockRelPath);
1794
+ const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
1795
+ const pylockPath = import_node_path5.default.join(root, pylockRelPath);
2101
1796
  const pylockContent = await readFileTextIfExists(pylockPath);
2102
1797
  if (pylockContent != null) {
2103
1798
  return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
@@ -2105,8 +1800,8 @@ async function maybeLoadLockFile(root, subdir) {
2105
1800
  return void 0;
2106
1801
  }
2107
1802
  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);
1803
+ const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
1804
+ const pyprojectTomlPath = import_node_path5.default.join(root, pyprojectTomlRelPath);
2110
1805
  let pyproject;
2111
1806
  try {
2112
1807
  pyproject = await readConfigIfExists(
@@ -2127,8 +1822,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
2127
1822
  if (pyproject == null) {
2128
1823
  return null;
2129
1824
  }
2130
- const uvTomlRelPath = import_node_path6.default.join(subdir, "uv.toml");
2131
- const uvTomlPath = import_node_path6.default.join(root, uvTomlRelPath);
1825
+ const uvTomlRelPath = import_node_path5.default.join(subdir, "uv.toml");
1826
+ const uvTomlPath = import_node_path5.default.join(root, uvTomlRelPath);
2132
1827
  let uvToml;
2133
1828
  try {
2134
1829
  uvToml = await readConfigIfExists(uvTomlPath, UvConfigSchema);
@@ -2158,8 +1853,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
2158
1853
  };
2159
1854
  }
2160
1855
  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);
1856
+ const pipfileRelPath = import_node_path5.default.join(subdir, "Pipfile");
1857
+ const pipfilePath = import_node_path5.default.join(root, pipfileRelPath);
2163
1858
  let pipfile;
2164
1859
  try {
2165
1860
  pipfile = await readConfigIfExists(pipfilePath, PipfileLikeSchema, ".toml");
@@ -2188,8 +1883,8 @@ async function maybeLoadPipfile(root, subdir) {
2188
1883
  };
2189
1884
  }
2190
1885
  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);
1886
+ const pipfileLockRelPath = import_node_path5.default.join(subdir, "Pipfile.lock");
1887
+ const pipfileLockPath = import_node_path5.default.join(root, pipfileLockRelPath);
2193
1888
  let pipfileLock;
2194
1889
  try {
2195
1890
  pipfileLock = await readConfigIfExists(
@@ -2222,14 +1917,20 @@ async function maybeLoadPipfileLock(root, subdir) {
2222
1917
  };
2223
1918
  }
2224
1919
  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);
1920
+ const requirementsTxtRelPath = import_node_path5.default.join(subdir, fileName);
1921
+ const requirementsTxtPath = import_node_path5.default.join(root, requirementsTxtRelPath);
2227
1922
  const requirementsContent = await readFileTextIfExists(requirementsTxtPath);
2228
1923
  if (requirementsContent == null) {
2229
1924
  return null;
2230
1925
  }
2231
1926
  try {
2232
- const pyproject = convertRequirementsToPyprojectToml(requirementsContent);
1927
+ const pyproject = await convertRequirementsToPyprojectToml(
1928
+ requirementsContent,
1929
+ {
1930
+ workingDir: import_node_path5.default.join(root, subdir),
1931
+ packageRoot: root
1932
+ }
1933
+ );
2233
1934
  return {
2234
1935
  path: requirementsTxtRelPath,
2235
1936
  data: pyproject,
@@ -2263,8 +1964,8 @@ async function loadPythonConfigs(root, prefix) {
2263
1964
  return configs;
2264
1965
  }
2265
1966
  async function maybeLoadPythonRequest(root, subdir) {
2266
- const dotPythonVersionRelPath = import_node_path6.default.join(subdir, ".python-version");
2267
- const dotPythonVersionPath = import_node_path6.default.join(
1967
+ const dotPythonVersionRelPath = import_node_path5.default.join(subdir, ".python-version");
1968
+ const dotPythonVersionPath = import_node_path5.default.join(
2268
1969
  root,
2269
1970
  dotPythonVersionRelPath
2270
1971
  );
@@ -2283,7 +1984,8 @@ async function maybeLoadPythonRequest(root, subdir) {
2283
1984
  return {
2284
1985
  kind: ".python-version" /* PythonVersion */,
2285
1986
  path: dotPythonVersionRelPath,
2286
- data: pyreq
1987
+ data: pyreq,
1988
+ specifier: data.trim()
2287
1989
  };
2288
1990
  }
2289
1991
 
@@ -2327,7 +2029,9 @@ function parseUvLock(content, path4) {
2327
2029
  const packages = (parsed.package ?? []).filter((pkg) => pkg.name && pkg.version).map((pkg) => ({
2328
2030
  name: pkg.name,
2329
2031
  version: pkg.version,
2330
- source: pkg.source
2032
+ source: pkg.source,
2033
+ wheels: (pkg.wheels ?? []).map((w) => ({ url: w.url })),
2034
+ ...pkg.dependencies ? { dependencies: pkg.dependencies } : {}
2331
2035
  }));
2332
2036
  return { version: parsed.version, packages };
2333
2037
  }
@@ -2383,6 +2087,20 @@ function classifyPackages(options) {
2383
2087
  return { privatePackages, publicPackages, packageVersions };
2384
2088
  }
2385
2089
 
2090
+ // src/manifest/wheel-compat.ts
2091
+ async function isWheelCompatible(wheelFilename, pythonMajor, pythonMinor, osName, archName, osMajor, osMinor) {
2092
+ const mod = await importWasmModule();
2093
+ return mod.isWheelCompatible(
2094
+ wheelFilename,
2095
+ pythonMajor,
2096
+ pythonMinor,
2097
+ osName,
2098
+ archName,
2099
+ osMajor,
2100
+ osMinor
2101
+ );
2102
+ }
2103
+
2386
2104
  // src/manifest/python-selector.ts
2387
2105
  function selectPython(constraints, available) {
2388
2106
  const warnings = [];
@@ -2696,7 +2414,9 @@ var HashDigestSchema = hashDigestSchema;
2696
2414
  extendDistRecord,
2697
2415
  getStringConstant,
2698
2416
  isPrivatePackageSource,
2417
+ isWheelCompatible,
2699
2418
  normalizePackageName,
2419
+ parsePep508,
2700
2420
  parseUvLock,
2701
2421
  scanDistributions,
2702
2422
  selectPython,