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