@vercel/python-analysis 0.4.1 → 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
@@ -177,21 +273,21 @@ async function readFileTextIfExists(file, encoding = "utf8") {
177
273
  }
178
274
  }
179
275
  function normalizePath(p) {
180
- let np = import_node_path2.default.normalize(p);
181
- 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)) {
182
278
  np = np.slice(0, -1);
183
279
  }
184
280
  return np;
185
281
  }
186
282
  function isSubpath(somePath, parentPath) {
187
- const rel = import_node_path2.default.relative(parentPath, somePath);
188
- 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);
189
285
  }
190
286
 
191
287
  // src/util/config.ts
192
288
  function parseRawConfig(content, filename, filetype = void 0) {
193
289
  if (filetype === void 0) {
194
- filetype = import_node_path3.default.extname(filename.toLowerCase());
290
+ filetype = import_node_path4.default.extname(filename.toLowerCase());
195
291
  }
196
292
  try {
197
293
  if (filetype === ".json") {
@@ -722,7 +818,7 @@ var PyProjectToolSectionSchema = pyProjectToolSectionSchema.passthrough();
722
818
  var PyProjectTomlSchema = pyProjectTomlSchema.passthrough();
723
819
 
724
820
  // src/manifest/requirements-txt-parser.ts
725
- var import_node_path4 = require("path");
821
+ var import_node_path5 = require("path");
726
822
  var import_pip_requirements_js = require("pip-requirements-js");
727
823
  var PRIMARY_INDEX_NAME = "primary";
728
824
  var EXTRA_INDEX_PREFIX = "extra-";
@@ -1060,9 +1156,9 @@ function normalizePathRequirement(rawLine) {
1060
1156
  }
1061
1157
  return req;
1062
1158
  }
1063
- function convertRequirementsToPyprojectToml(fileContent, readFile3) {
1159
+ function convertRequirementsToPyprojectToml(fileContent, readFile4) {
1064
1160
  const pyproject = {};
1065
- const parsed = parseRequirementsFile(fileContent, readFile3);
1161
+ const parsed = parseRequirementsFile(fileContent, readFile4);
1066
1162
  const deps = [];
1067
1163
  const sources = {};
1068
1164
  for (const req of parsed.requirements) {
@@ -1121,11 +1217,11 @@ function buildIndexEntries(pipOptions) {
1121
1217
  }
1122
1218
  return indexes;
1123
1219
  }
1124
- function parseRequirementsFile(fileContent, readFile3) {
1220
+ function parseRequirementsFile(fileContent, readFile4) {
1125
1221
  const visited = /* @__PURE__ */ new Set();
1126
- return parseRequirementsFileInternal(fileContent, readFile3, visited);
1222
+ return parseRequirementsFileInternal(fileContent, readFile4, visited);
1127
1223
  }
1128
- function parseRequirementsFileInternal(fileContent, readFile3, visited) {
1224
+ function parseRequirementsFileInternal(fileContent, readFile4, visited) {
1129
1225
  const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
1130
1226
  const hashMap = buildHashMap(fileContent);
1131
1227
  const requirements = (0, import_pip_requirements_js.parsePipRequirementsFile)(cleanedContent);
@@ -1173,18 +1269,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
1173
1269
  normalized.push(norm);
1174
1270
  }
1175
1271
  }
1176
- if (readFile3) {
1272
+ if (readFile4) {
1177
1273
  for (const refPath of mergedOptions.requirementFiles) {
1178
- const refPathKey = (0, import_node_path4.normalize)(refPath);
1274
+ const refPathKey = (0, import_node_path5.normalize)(refPath);
1179
1275
  if (visited.has(refPathKey)) {
1180
1276
  continue;
1181
1277
  }
1182
1278
  visited.add(refPathKey);
1183
- const refContent = readFile3(refPath);
1279
+ const refContent = readFile4(refPath);
1184
1280
  if (refContent != null) {
1185
1281
  const refParsed = parseRequirementsFileInternal(
1186
1282
  refContent,
1187
- readFile3,
1283
+ readFile4,
1188
1284
  visited
1189
1285
  );
1190
1286
  const existingNames = new Set(
@@ -1727,7 +1823,7 @@ async function discoverPythonPackage({
1727
1823
  }) {
1728
1824
  const entrypointPath = normalizePath(entrypointDir);
1729
1825
  const rootPath = normalizePath(rootDir);
1730
- let prefix = import_node_path5.default.relative(rootPath, entrypointPath);
1826
+ let prefix = import_node_path6.default.relative(rootPath, entrypointPath);
1731
1827
  if (prefix.startsWith("..")) {
1732
1828
  throw new PythonAnalysisError({
1733
1829
  message: "Entrypoint directory outside of repository root",
@@ -1751,7 +1847,7 @@ async function discoverPythonPackage({
1751
1847
  if (prefix === "" || prefix === ".") {
1752
1848
  break;
1753
1849
  }
1754
- prefix = import_node_path5.default.dirname(prefix);
1850
+ prefix = import_node_path6.default.dirname(prefix);
1755
1851
  }
1756
1852
  let entrypointManifest;
1757
1853
  let workspaceManifest;
@@ -1771,8 +1867,8 @@ async function discoverPythonPackage({
1771
1867
  configs = configs.filter(
1772
1868
  (config) => Object.values(config).some(
1773
1869
  (cfg) => cfg !== void 0 && isSubpath(
1774
- import_node_path5.default.dirname(cfg.path),
1775
- import_node_path5.default.dirname(entrypointWorkspaceManifest.path)
1870
+ import_node_path6.default.dirname(cfg.path),
1871
+ import_node_path6.default.dirname(entrypointWorkspaceManifest.path)
1776
1872
  )
1777
1873
  )
1778
1874
  );
@@ -1845,9 +1941,9 @@ function findWorkspaceManifestFor(manifest, manifestStack) {
1845
1941
  if (!Array.isArray(exclude)) {
1846
1942
  exclude = [];
1847
1943
  }
1848
- const entrypointRelPath = import_node_path5.default.relative(
1849
- import_node_path5.default.dirname(parentManifest.path),
1850
- 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)
1851
1947
  );
1852
1948
  if (members.length > 0 && members.some(
1853
1949
  (pat) => (0, import_minimatch.match)([entrypointRelPath], pat).length > 0
@@ -1882,7 +1978,7 @@ async function loadPythonManifest(root, prefix) {
1882
1978
  "requirements-frozen.txt",
1883
1979
  "requirements.txt",
1884
1980
  "requirements.in",
1885
- import_node_path5.default.join("requirements", "prod.txt")
1981
+ import_node_path6.default.join("requirements", "prod.txt")
1886
1982
  ]) {
1887
1983
  const requirementsTxtManifest = await maybeLoadRequirementsTxt(
1888
1984
  root,
@@ -1901,14 +1997,14 @@ async function loadPythonManifest(root, prefix) {
1901
1997
  return manifest;
1902
1998
  }
1903
1999
  async function maybeLoadLockFile(root, subdir) {
1904
- const uvLockRelPath = import_node_path5.default.join(subdir, "uv.lock");
1905
- 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);
1906
2002
  const uvLockContent = await readFileTextIfExists(uvLockPath);
1907
2003
  if (uvLockContent != null) {
1908
2004
  return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
1909
2005
  }
1910
- const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
1911
- 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);
1912
2008
  const pylockContent = await readFileTextIfExists(pylockPath);
1913
2009
  if (pylockContent != null) {
1914
2010
  return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
@@ -1916,8 +2012,8 @@ async function maybeLoadLockFile(root, subdir) {
1916
2012
  return void 0;
1917
2013
  }
1918
2014
  async function maybeLoadPyProjectToml(root, subdir) {
1919
- const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
1920
- 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);
1921
2017
  let pyproject;
1922
2018
  try {
1923
2019
  pyproject = await readConfigIfExists(
@@ -1938,8 +2034,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
1938
2034
  if (pyproject == null) {
1939
2035
  return null;
1940
2036
  }
1941
- const uvTomlRelPath = import_node_path5.default.join(subdir, "uv.toml");
1942
- 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);
1943
2039
  let uvToml;
1944
2040
  try {
1945
2041
  uvToml = await readConfigIfExists(uvTomlPath, UvConfigSchema);
@@ -1969,8 +2065,8 @@ async function maybeLoadPyProjectToml(root, subdir) {
1969
2065
  };
1970
2066
  }
1971
2067
  async function maybeLoadPipfile(root, subdir) {
1972
- const pipfileRelPath = import_node_path5.default.join(subdir, "Pipfile");
1973
- 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);
1974
2070
  let pipfile;
1975
2071
  try {
1976
2072
  pipfile = await readConfigIfExists(pipfilePath, PipfileLikeSchema, ".toml");
@@ -1999,8 +2095,8 @@ async function maybeLoadPipfile(root, subdir) {
1999
2095
  };
2000
2096
  }
2001
2097
  async function maybeLoadPipfileLock(root, subdir) {
2002
- const pipfileLockRelPath = import_node_path5.default.join(subdir, "Pipfile.lock");
2003
- 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);
2004
2100
  let pipfileLock;
2005
2101
  try {
2006
2102
  pipfileLock = await readConfigIfExists(
@@ -2033,8 +2129,8 @@ async function maybeLoadPipfileLock(root, subdir) {
2033
2129
  };
2034
2130
  }
2035
2131
  async function maybeLoadRequirementsTxt(root, subdir, fileName) {
2036
- const requirementsTxtRelPath = import_node_path5.default.join(subdir, fileName);
2037
- 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);
2038
2134
  const requirementsContent = await readFileTextIfExists(requirementsTxtPath);
2039
2135
  if (requirementsContent == null) {
2040
2136
  return null;
@@ -2074,8 +2170,8 @@ async function loadPythonConfigs(root, prefix) {
2074
2170
  return configs;
2075
2171
  }
2076
2172
  async function maybeLoadPythonRequest(root, subdir) {
2077
- const dotPythonVersionRelPath = import_node_path5.default.join(subdir, ".python-version");
2078
- 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(
2079
2175
  root,
2080
2176
  dotPythonVersionRelPath
2081
2177
  );
@@ -2121,6 +2217,95 @@ function createMinimalManifest(options = {}) {
2121
2217
  };
2122
2218
  }
2123
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
+
2124
2309
  // src/manifest/python-selector.ts
2125
2310
  function selectPython(constraints, available) {
2126
2311
  const warnings = [];
@@ -2304,9 +2489,15 @@ var HashDigestSchema = hashDigestSchema;
2304
2489
  UvConfigSchema,
2305
2490
  UvConfigWorkspaceSchema,
2306
2491
  UvIndexEntrySchema,
2492
+ classifyPackages,
2307
2493
  containsAppOrHandler,
2308
2494
  createMinimalManifest,
2309
2495
  discoverPythonPackage,
2496
+ generateRuntimeRequirements,
2497
+ isPrivatePackageSource,
2498
+ normalizePackageName,
2499
+ parseUvLock,
2500
+ scanDistributions,
2310
2501
  selectPython,
2311
2502
  stringifyManifest
2312
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';