@vercel/python-analysis 0.5.0-canary.20260211174907.cdd2da6 → 0.5.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
@@ -62,15 +62,21 @@ __export(src_exports, {
62
62
  UvConfigSchema: () => UvConfigSchema,
63
63
  UvConfigWorkspaceSchema: () => UvConfigWorkspaceSchema,
64
64
  UvIndexEntrySchema: () => UvIndexEntrySchema,
65
+ classifyPackages: () => classifyPackages,
65
66
  containsAppOrHandler: () => containsAppOrHandler,
66
67
  createMinimalManifest: () => createMinimalManifest,
67
68
  discoverPythonPackage: () => discoverPythonPackage,
69
+ generateRuntimeRequirements: () => generateRuntimeRequirements,
70
+ isPrivatePackageSource: () => isPrivatePackageSource,
71
+ normalizePackageName: () => normalizePackageName,
72
+ parseUvLock: () => parseUvLock,
73
+ scanDistributions: () => scanDistributions,
68
74
  selectPython: () => selectPython,
69
75
  stringifyManifest: () => stringifyManifest
70
76
  });
71
77
  module.exports = __toCommonJS(src_exports);
72
78
 
73
- // src/semantic/load.ts
79
+ // src/wasm/load.ts
74
80
  var import_promises = require("fs/promises");
75
81
  var import_node_module = require("module");
76
82
  var import_node_path = require("path");
@@ -89,7 +95,7 @@ function getWasmDir() {
89
95
  }
90
96
  async function getCoreModule(path4) {
91
97
  const wasmPath = (0, import_node_path.join)(getWasmDir(), path4);
92
- const wasmBytes = await (0, import_promises.readFile)(wasmPath);
98
+ const wasmBytes = new Uint8Array(await (0, import_promises.readFile)(wasmPath));
93
99
  return WebAssembly.compile(wasmBytes);
94
100
  }
95
101
  async function importWasmModule() {
@@ -119,17 +125,107 @@ async function containsAppOrHandler(source) {
119
125
  return mod.containsAppOrHandler(source);
120
126
  }
121
127
 
128
+ // src/manifest/dist-metadata.ts
129
+ var import_promises2 = require("fs/promises");
130
+ var import_node_path2 = require("path");
131
+ async function readDistInfoFile(distInfoDir, filename) {
132
+ try {
133
+ return await (0, import_promises2.readFile)((0, import_node_path2.join)(distInfoDir, filename), "utf-8");
134
+ } catch {
135
+ return void 0;
136
+ }
137
+ }
138
+ async function scanDistributions(sitePackagesDir) {
139
+ const mod = await importWasmModule();
140
+ const index = /* @__PURE__ */ new Map();
141
+ let entries;
142
+ try {
143
+ entries = await (0, import_promises2.readdir)(sitePackagesDir);
144
+ } catch {
145
+ return index;
146
+ }
147
+ const distInfoDirs = entries.filter((e) => e.endsWith(".dist-info"));
148
+ for (const dirName of distInfoDirs) {
149
+ const distInfoPath = (0, import_node_path2.join)(sitePackagesDir, dirName);
150
+ const metadataContent = await readDistInfoFile(distInfoPath, "METADATA");
151
+ if (!metadataContent) {
152
+ console.debug(`Missing METADATA in ${dirName}`);
153
+ continue;
154
+ }
155
+ let metadata;
156
+ try {
157
+ metadata = mod.parseDistMetadata(
158
+ new TextEncoder().encode(metadataContent)
159
+ );
160
+ } catch (e) {
161
+ console.debug(`Failed to parse METADATA for ${dirName}: ${e}`);
162
+ continue;
163
+ }
164
+ const normalizedName = mod.normalizePackageName(metadata.name);
165
+ let files = [];
166
+ const recordContent = await readDistInfoFile(distInfoPath, "RECORD");
167
+ if (recordContent) {
168
+ try {
169
+ files = mod.parseRecord(recordContent);
170
+ } catch (e) {
171
+ console.warn(`Failed to parse RECORD for ${dirName}: ${e}`);
172
+ }
173
+ }
174
+ let origin;
175
+ const directUrlContent = await readDistInfoFile(
176
+ distInfoPath,
177
+ "direct_url.json"
178
+ );
179
+ if (directUrlContent) {
180
+ try {
181
+ origin = mod.parseDirectUrl(directUrlContent);
182
+ } catch (e) {
183
+ console.debug(`Failed to parse direct_url.json for ${dirName}: ${e}`);
184
+ }
185
+ }
186
+ const installerContent = await readDistInfoFile(distInfoPath, "INSTALLER");
187
+ const installer = installerContent?.trim() || void 0;
188
+ const dist = {
189
+ name: normalizedName,
190
+ version: metadata.version,
191
+ metadataVersion: metadata.metadataVersion,
192
+ summary: metadata.summary,
193
+ description: metadata.description,
194
+ descriptionContentType: metadata.descriptionContentType,
195
+ requiresDist: metadata.requiresDist,
196
+ requiresPython: metadata.requiresPython,
197
+ providesExtra: metadata.providesExtra,
198
+ author: metadata.author,
199
+ authorEmail: metadata.authorEmail,
200
+ maintainer: metadata.maintainer,
201
+ maintainerEmail: metadata.maintainerEmail,
202
+ license: metadata.license,
203
+ licenseExpression: metadata.licenseExpression,
204
+ classifiers: metadata.classifiers,
205
+ homePage: metadata.homePage,
206
+ projectUrls: metadata.projectUrls,
207
+ platforms: metadata.platforms,
208
+ dynamic: metadata.dynamic,
209
+ files,
210
+ origin,
211
+ installer
212
+ };
213
+ index.set(normalizedName, dist);
214
+ }
215
+ return index;
216
+ }
217
+
122
218
  // src/manifest/package.ts
123
- var import_node_path5 = __toESM(require("path"), 1);
219
+ var import_node_path6 = __toESM(require("path"), 1);
124
220
  var import_minimatch = require("minimatch");
125
221
 
126
222
  // src/util/config.ts
127
- var import_node_path3 = __toESM(require("path"), 1);
223
+ var import_node_path4 = __toESM(require("path"), 1);
128
224
  var import_js_yaml = __toESM(require("js-yaml"), 1);
129
225
  var import_smol_toml = __toESM(require("smol-toml"), 1);
130
226
 
131
227
  // src/util/fs.ts
132
- var import_node_path2 = __toESM(require("path"), 1);
228
+ var import_node_path3 = __toESM(require("path"), 1);
133
229
  var import_fs_extra = require("fs-extra");
134
230
 
135
231
  // src/util/error.ts
@@ -138,7 +234,14 @@ var isErrnoException = (error, code = void 0) => {
138
234
  return import_node_util.default.types.isNativeError(error) && "code" in error && (code === void 0 || error.code === code);
139
235
  };
140
236
  var PythonAnalysisError = class extends Error {
141
- constructor({ message, code, path: path4, link, action }) {
237
+ constructor({
238
+ message,
239
+ code,
240
+ path: path4,
241
+ link,
242
+ action,
243
+ fileContent
244
+ }) {
142
245
  super(message);
143
246
  this.hideStackTrace = true;
144
247
  this.name = "PythonAnalysisError";
@@ -146,6 +249,7 @@ var PythonAnalysisError = class extends Error {
146
249
  this.path = path4;
147
250
  this.link = link;
148
251
  this.action = action;
252
+ this.fileContent = fileContent;
149
253
  }
150
254
  };
151
255
 
@@ -169,21 +273,21 @@ async function readFileTextIfExists(file, encoding = "utf8") {
169
273
  }
170
274
  }
171
275
  function normalizePath(p) {
172
- let np = import_node_path2.default.normalize(p);
173
- if (np.endsWith(import_node_path2.default.sep)) {
276
+ let np = import_node_path3.default.normalize(p);
277
+ if (np.endsWith(import_node_path3.default.sep)) {
174
278
  np = np.slice(0, -1);
175
279
  }
176
280
  return np;
177
281
  }
178
282
  function isSubpath(somePath, parentPath) {
179
- const rel = import_node_path2.default.relative(parentPath, somePath);
180
- return rel === "" || !rel.startsWith("..") && !import_node_path2.default.isAbsolute(rel);
283
+ const rel = import_node_path3.default.relative(parentPath, somePath);
284
+ return rel === "" || !rel.startsWith("..") && !import_node_path3.default.isAbsolute(rel);
181
285
  }
182
286
 
183
287
  // src/util/config.ts
184
288
  function parseRawConfig(content, filename, filetype = void 0) {
185
289
  if (filetype === void 0) {
186
- filetype = import_node_path3.default.extname(filename.toLowerCase());
290
+ filetype = import_node_path4.default.extname(filename.toLowerCase());
187
291
  }
188
292
  try {
189
293
  if (filetype === ".json") {
@@ -207,7 +311,8 @@ function parseRawConfig(content, filename, filetype = void 0) {
207
311
  throw new PythonAnalysisError({
208
312
  message: `Could not parse config file "${filename}": ${error.message}`,
209
313
  code: "PYTHON_CONFIG_PARSE_ERROR",
210
- path: filename
314
+ path: filename,
315
+ fileContent: content
211
316
  });
212
317
  }
213
318
  throw error;
@@ -225,7 +330,8 @@ function parseConfig(content, filename, schema, filetype = void 0) {
225
330
  message: `Invalid config in "${filename}":
226
331
  ${issues}`,
227
332
  code: "PYTHON_CONFIG_VALIDATION_ERROR",
228
- path: filename
333
+ path: filename,
334
+ fileContent: content
229
335
  });
230
336
  }
231
337
  return result.data;
@@ -712,7 +818,7 @@ var PyProjectToolSectionSchema = pyProjectToolSectionSchema.passthrough();
712
818
  var PyProjectTomlSchema = pyProjectTomlSchema.passthrough();
713
819
 
714
820
  // src/manifest/requirements-txt-parser.ts
715
- var import_node_path4 = require("path");
821
+ var import_node_path5 = require("path");
716
822
  var import_pip_requirements_js = require("pip-requirements-js");
717
823
  var PRIMARY_INDEX_NAME = "primary";
718
824
  var EXTRA_INDEX_PREFIX = "extra-";
@@ -1050,9 +1156,9 @@ function normalizePathRequirement(rawLine) {
1050
1156
  }
1051
1157
  return req;
1052
1158
  }
1053
- function convertRequirementsToPyprojectToml(fileContent, readFile3) {
1159
+ function convertRequirementsToPyprojectToml(fileContent, readFile4) {
1054
1160
  const pyproject = {};
1055
- const parsed = parseRequirementsFile(fileContent, readFile3);
1161
+ const parsed = parseRequirementsFile(fileContent, readFile4);
1056
1162
  const deps = [];
1057
1163
  const sources = {};
1058
1164
  for (const req of parsed.requirements) {
@@ -1111,11 +1217,11 @@ function buildIndexEntries(pipOptions) {
1111
1217
  }
1112
1218
  return indexes;
1113
1219
  }
1114
- function parseRequirementsFile(fileContent, readFile3) {
1220
+ function parseRequirementsFile(fileContent, readFile4) {
1115
1221
  const visited = /* @__PURE__ */ new Set();
1116
- return parseRequirementsFileInternal(fileContent, readFile3, visited);
1222
+ return parseRequirementsFileInternal(fileContent, readFile4, visited);
1117
1223
  }
1118
- function parseRequirementsFileInternal(fileContent, readFile3, visited) {
1224
+ function parseRequirementsFileInternal(fileContent, readFile4, visited) {
1119
1225
  const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
1120
1226
  const hashMap = buildHashMap(fileContent);
1121
1227
  const requirements = (0, import_pip_requirements_js.parsePipRequirementsFile)(cleanedContent);
@@ -1163,18 +1269,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
1163
1269
  normalized.push(norm);
1164
1270
  }
1165
1271
  }
1166
- if (readFile3) {
1272
+ if (readFile4) {
1167
1273
  for (const refPath of mergedOptions.requirementFiles) {
1168
- const refPathKey = (0, import_node_path4.normalize)(refPath);
1274
+ const refPathKey = (0, import_node_path5.normalize)(refPath);
1169
1275
  if (visited.has(refPathKey)) {
1170
1276
  continue;
1171
1277
  }
1172
1278
  visited.add(refPathKey);
1173
- const refContent = readFile3(refPath);
1279
+ const refContent = readFile4(refPath);
1174
1280
  if (refContent != null) {
1175
1281
  const refParsed = parseRequirementsFileInternal(
1176
1282
  refContent,
1177
- readFile3,
1283
+ readFile4,
1178
1284
  visited
1179
1285
  );
1180
1286
  const existingNames = new Set(
@@ -1717,7 +1823,7 @@ async function discoverPythonPackage({
1717
1823
  }) {
1718
1824
  const entrypointPath = normalizePath(entrypointDir);
1719
1825
  const rootPath = normalizePath(rootDir);
1720
- let prefix = import_node_path5.default.relative(rootPath, entrypointPath);
1826
+ let prefix = import_node_path6.default.relative(rootPath, entrypointPath);
1721
1827
  if (prefix.startsWith("..")) {
1722
1828
  throw new PythonAnalysisError({
1723
1829
  message: "Entrypoint directory outside of repository root",
@@ -1741,7 +1847,7 @@ async function discoverPythonPackage({
1741
1847
  if (prefix === "" || prefix === ".") {
1742
1848
  break;
1743
1849
  }
1744
- prefix = import_node_path5.default.dirname(prefix);
1850
+ prefix = import_node_path6.default.dirname(prefix);
1745
1851
  }
1746
1852
  let entrypointManifest;
1747
1853
  let workspaceManifest;
@@ -1761,8 +1867,8 @@ async function discoverPythonPackage({
1761
1867
  configs = configs.filter(
1762
1868
  (config) => Object.values(config).some(
1763
1869
  (cfg) => cfg !== void 0 && isSubpath(
1764
- import_node_path5.default.dirname(cfg.path),
1765
- import_node_path5.default.dirname(entrypointWorkspaceManifest.path)
1870
+ import_node_path6.default.dirname(cfg.path),
1871
+ import_node_path6.default.dirname(entrypointWorkspaceManifest.path)
1766
1872
  )
1767
1873
  )
1768
1874
  );
@@ -1835,9 +1941,9 @@ function findWorkspaceManifestFor(manifest, manifestStack) {
1835
1941
  if (!Array.isArray(exclude)) {
1836
1942
  exclude = [];
1837
1943
  }
1838
- const entrypointRelPath = import_node_path5.default.relative(
1839
- import_node_path5.default.dirname(parentManifest.path),
1840
- import_node_path5.default.dirname(manifest.path)
1944
+ const entrypointRelPath = import_node_path6.default.relative(
1945
+ import_node_path6.default.dirname(parentManifest.path),
1946
+ import_node_path6.default.dirname(manifest.path)
1841
1947
  );
1842
1948
  if (members.length > 0 && members.some(
1843
1949
  (pat) => (0, import_minimatch.match)([entrypointRelPath], pat).length > 0
@@ -1872,7 +1978,7 @@ async function loadPythonManifest(root, prefix) {
1872
1978
  "requirements-frozen.txt",
1873
1979
  "requirements.txt",
1874
1980
  "requirements.in",
1875
- import_node_path5.default.join("requirements", "prod.txt")
1981
+ import_node_path6.default.join("requirements", "prod.txt")
1876
1982
  ]) {
1877
1983
  const requirementsTxtManifest = await maybeLoadRequirementsTxt(
1878
1984
  root,
@@ -1891,14 +1997,14 @@ async function loadPythonManifest(root, prefix) {
1891
1997
  return manifest;
1892
1998
  }
1893
1999
  async function maybeLoadLockFile(root, subdir) {
1894
- const uvLockRelPath = import_node_path5.default.join(subdir, "uv.lock");
1895
- const uvLockPath = import_node_path5.default.join(root, uvLockRelPath);
2000
+ const uvLockRelPath = import_node_path6.default.join(subdir, "uv.lock");
2001
+ const uvLockPath = import_node_path6.default.join(root, uvLockRelPath);
1896
2002
  const uvLockContent = await readFileTextIfExists(uvLockPath);
1897
2003
  if (uvLockContent != null) {
1898
2004
  return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
1899
2005
  }
1900
- const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
1901
- const pylockPath = import_node_path5.default.join(root, pylockRelPath);
2006
+ const pylockRelPath = import_node_path6.default.join(subdir, "pylock.toml");
2007
+ const pylockPath = import_node_path6.default.join(root, pylockRelPath);
1902
2008
  const pylockContent = await readFileTextIfExists(pylockPath);
1903
2009
  if (pylockContent != null) {
1904
2010
  return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
@@ -1906,8 +2012,8 @@ async function maybeLoadLockFile(root, subdir) {
1906
2012
  return void 0;
1907
2013
  }
1908
2014
  async function maybeLoadPyProjectToml(root, subdir) {
1909
- const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
1910
- const pyprojectTomlPath = import_node_path5.default.join(root, pyprojectTomlRelPath);
2015
+ const pyprojectTomlRelPath = import_node_path6.default.join(subdir, "pyproject.toml");
2016
+ const pyprojectTomlPath = import_node_path6.default.join(root, pyprojectTomlRelPath);
1911
2017
  let pyproject;
1912
2018
  try {
1913
2019
  pyproject = await readConfigIfExists(
@@ -1928,8 +2034,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
1928
2034
  if (pyproject == null) {
1929
2035
  return null;
1930
2036
  }
1931
- const uvTomlRelPath = import_node_path5.default.join(subdir, "uv.toml");
1932
- const uvTomlPath = import_node_path5.default.join(root, uvTomlRelPath);
2037
+ const uvTomlRelPath = import_node_path6.default.join(subdir, "uv.toml");
2038
+ const uvTomlPath = import_node_path6.default.join(root, uvTomlRelPath);
1933
2039
  let uvToml;
1934
2040
  try {
1935
2041
  uvToml = await readConfigIfExists(uvTomlPath, UvConfigSchema);
@@ -1959,8 +2065,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
1959
2065
  };
1960
2066
  }
1961
2067
  async function maybeLoadPipfile(root, subdir) {
1962
- const pipfileRelPath = import_node_path5.default.join(subdir, "Pipfile");
1963
- const pipfilePath = import_node_path5.default.join(root, pipfileRelPath);
2068
+ const pipfileRelPath = import_node_path6.default.join(subdir, "Pipfile");
2069
+ const pipfilePath = import_node_path6.default.join(root, pipfileRelPath);
1964
2070
  let pipfile;
1965
2071
  try {
1966
2072
  pipfile = await readConfigIfExists(pipfilePath, PipfileLikeSchema, ".toml");
@@ -1989,8 +2095,8 @@ async function maybeLoadPipfile(root, subdir) {
1989
2095
  };
1990
2096
  }
1991
2097
  async function maybeLoadPipfileLock(root, subdir) {
1992
- const pipfileLockRelPath = import_node_path5.default.join(subdir, "Pipfile.lock");
1993
- const pipfileLockPath = import_node_path5.default.join(root, pipfileLockRelPath);
2098
+ const pipfileLockRelPath = import_node_path6.default.join(subdir, "Pipfile.lock");
2099
+ const pipfileLockPath = import_node_path6.default.join(root, pipfileLockRelPath);
1994
2100
  let pipfileLock;
1995
2101
  try {
1996
2102
  pipfileLock = await readConfigIfExists(
@@ -2023,8 +2129,8 @@ async function maybeLoadPipfileLock(root, subdir) {
2023
2129
  };
2024
2130
  }
2025
2131
  async function maybeLoadRequirementsTxt(root, subdir, fileName) {
2026
- const requirementsTxtRelPath = import_node_path5.default.join(subdir, fileName);
2027
- const requirementsTxtPath = import_node_path5.default.join(root, requirementsTxtRelPath);
2132
+ const requirementsTxtRelPath = import_node_path6.default.join(subdir, fileName);
2133
+ const requirementsTxtPath = import_node_path6.default.join(root, requirementsTxtRelPath);
2028
2134
  const requirementsContent = await readFileTextIfExists(requirementsTxtPath);
2029
2135
  if (requirementsContent == null) {
2030
2136
  return null;
@@ -2042,12 +2148,16 @@ async function maybeLoadRequirementsTxt(root, subdir, fileName) {
2042
2148
  } catch (error) {
2043
2149
  if (error instanceof PythonAnalysisError) {
2044
2150
  error.path = requirementsTxtRelPath;
2151
+ if (!error.fileContent) {
2152
+ error.fileContent = requirementsContent;
2153
+ }
2045
2154
  throw error;
2046
2155
  }
2047
2156
  throw new PythonAnalysisError({
2048
2157
  message: `could not parse ${fileName}: ${error instanceof Error ? error.message : String(error)}`,
2049
2158
  code: "PYTHON_REQUIREMENTS_PARSE_ERROR",
2050
- path: requirementsTxtRelPath
2159
+ path: requirementsTxtRelPath,
2160
+ fileContent: requirementsContent
2051
2161
  });
2052
2162
  }
2053
2163
  }
@@ -2060,8 +2170,8 @@ async function loadPythonConfigs(root, prefix) {
2060
2170
  return configs;
2061
2171
  }
2062
2172
  async function maybeLoadPythonRequest(root, subdir) {
2063
- const dotPythonVersionRelPath = import_node_path5.default.join(subdir, ".python-version");
2064
- const dotPythonVersionPath = import_node_path5.default.join(
2173
+ const dotPythonVersionRelPath = import_node_path6.default.join(subdir, ".python-version");
2174
+ const dotPythonVersionPath = import_node_path6.default.join(
2065
2175
  root,
2066
2176
  dotPythonVersionRelPath
2067
2177
  );
@@ -2107,6 +2217,95 @@ function createMinimalManifest(options = {}) {
2107
2217
  };
2108
2218
  }
2109
2219
 
2220
+ // src/manifest/uv-lock-parser.ts
2221
+ var import_smol_toml3 = __toESM(require("smol-toml"), 1);
2222
+ function parseUvLock(content, path4) {
2223
+ let parsed;
2224
+ try {
2225
+ parsed = import_smol_toml3.default.parse(content);
2226
+ } catch (error) {
2227
+ throw new PythonAnalysisError({
2228
+ message: `Could not parse uv.lock: ${error instanceof Error ? error.message : String(error)}`,
2229
+ code: "PYTHON_UV_LOCK_PARSE_ERROR",
2230
+ path: path4,
2231
+ fileContent: content
2232
+ });
2233
+ }
2234
+ const packages = (parsed.package ?? []).filter((pkg) => pkg.name && pkg.version).map((pkg) => ({
2235
+ name: pkg.name,
2236
+ version: pkg.version,
2237
+ source: pkg.source
2238
+ }));
2239
+ return { version: parsed.version, packages };
2240
+ }
2241
+ var PUBLIC_PYPI_PATTERNS = [
2242
+ "https://pypi.org",
2243
+ "https://files.pythonhosted.org",
2244
+ "pypi.org"
2245
+ ];
2246
+ function isPublicPyPIRegistry(registryUrl) {
2247
+ if (!registryUrl)
2248
+ return true;
2249
+ const normalized = registryUrl.toLowerCase();
2250
+ return PUBLIC_PYPI_PATTERNS.some((pattern) => normalized.includes(pattern));
2251
+ }
2252
+ function isPrivatePackageSource(source) {
2253
+ if (!source)
2254
+ return false;
2255
+ if (source.git)
2256
+ return true;
2257
+ if (source.path)
2258
+ return true;
2259
+ if (source.editable)
2260
+ return true;
2261
+ if (source.url)
2262
+ return true;
2263
+ if (source.virtual)
2264
+ return true;
2265
+ if (source.registry && !isPublicPyPIRegistry(source.registry)) {
2266
+ return true;
2267
+ }
2268
+ return false;
2269
+ }
2270
+ function normalizePackageName(name) {
2271
+ return name.toLowerCase().replace(/[-_.]+/g, "-");
2272
+ }
2273
+ function classifyPackages(options) {
2274
+ const { lockFile, excludePackages = [] } = options;
2275
+ const privatePackages = [];
2276
+ const publicPackages = [];
2277
+ const packageVersions = {};
2278
+ const excludeSet = new Set(excludePackages.map(normalizePackageName));
2279
+ for (const pkg of lockFile.packages) {
2280
+ if (excludeSet.has(normalizePackageName(pkg.name))) {
2281
+ continue;
2282
+ }
2283
+ packageVersions[pkg.name] = pkg.version;
2284
+ if (isPrivatePackageSource(pkg.source)) {
2285
+ privatePackages.push(pkg.name);
2286
+ } else {
2287
+ publicPackages.push(pkg.name);
2288
+ }
2289
+ }
2290
+ return { privatePackages, publicPackages, packageVersions };
2291
+ }
2292
+ function generateRuntimeRequirements(classification) {
2293
+ const lines = [
2294
+ "# Auto-generated requirements for runtime installation",
2295
+ "# Private packages are bundled separately and not listed here.",
2296
+ ""
2297
+ ];
2298
+ for (const pkgName of classification.publicPackages) {
2299
+ const version = classification.packageVersions[pkgName];
2300
+ if (version) {
2301
+ lines.push(`${pkgName}==${version}`);
2302
+ } else {
2303
+ lines.push(pkgName);
2304
+ }
2305
+ }
2306
+ return lines.join("\n");
2307
+ }
2308
+
2110
2309
  // src/manifest/python-selector.ts
2111
2310
  function selectPython(constraints, available) {
2112
2311
  const warnings = [];
@@ -2290,9 +2489,15 @@ var HashDigestSchema = hashDigestSchema;
2290
2489
  UvConfigSchema,
2291
2490
  UvConfigWorkspaceSchema,
2292
2491
  UvIndexEntrySchema,
2492
+ classifyPackages,
2293
2493
  containsAppOrHandler,
2294
2494
  createMinimalManifest,
2295
2495
  discoverPythonPackage,
2496
+ generateRuntimeRequirements,
2497
+ isPrivatePackageSource,
2498
+ normalizePackageName,
2499
+ parseUvLock,
2500
+ scanDistributions,
2296
2501
  selectPython,
2297
2502
  stringifyManifest
2298
2503
  });
package/dist/index.d.ts CHANGED
@@ -7,9 +7,13 @@
7
7
  * @module @vercel/python-analysis
8
8
  */
9
9
  export { containsAppOrHandler } from './semantic/entrypoints';
10
+ export type { Distribution, DistributionIndex, PackagePath, DirectUrlInfo, } from './manifest/dist-metadata';
11
+ export { scanDistributions } from './manifest/dist-metadata';
10
12
  export type { PythonConfig, PythonConfigs, PythonLockFile, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
11
13
  export { discoverPythonPackage, PythonConfigKind, PythonLockFileKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
12
14
  export { createMinimalManifest, stringifyManifest, type CreateMinimalManifestOptions, } from './manifest/serialize';
15
+ export type { ClassifyPackagesOptions, PackageClassification, UvLockFile, UvLockPackage, UvLockPackageSource, } from './manifest/uv-lock-parser';
16
+ export { classifyPackages, generateRuntimeRequirements, isPrivatePackageSource, normalizePackageName, parseUvLock, } from './manifest/uv-lock-parser';
13
17
  export type { PythonSelectionResult } from './manifest/python-selector';
14
18
  export { selectPython } from './manifest/python-selector';
15
19
  export { PythonAnalysisError } from './util/error';