@vercel/python-analysis 0.3.2 → 0.4.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
@@ -52,6 +52,7 @@ __export(src_exports, {
52
52
  PythonBuild: () => PythonBuild,
53
53
  PythonConfigKind: () => PythonConfigKind,
54
54
  PythonImplementation: () => PythonImplementation,
55
+ PythonLockFileKind: () => PythonLockFileKind,
55
56
  PythonManifestConvertedKind: () => PythonManifestConvertedKind,
56
57
  PythonManifestKind: () => PythonManifestKind,
57
58
  PythonVariant: () => PythonVariant,
@@ -62,8 +63,10 @@ __export(src_exports, {
62
63
  UvConfigWorkspaceSchema: () => UvConfigWorkspaceSchema,
63
64
  UvIndexEntrySchema: () => UvIndexEntrySchema,
64
65
  containsAppOrHandler: () => containsAppOrHandler,
66
+ createMinimalManifest: () => createMinimalManifest,
65
67
  discoverPythonPackage: () => discoverPythonPackage,
66
- selectPython: () => selectPython
68
+ selectPython: () => selectPython,
69
+ stringifyManifest: () => stringifyManifest
67
70
  });
68
71
  module.exports = __toCommonJS(src_exports);
69
72
 
@@ -135,7 +138,14 @@ var isErrnoException = (error, code = void 0) => {
135
138
  return import_node_util.default.types.isNativeError(error) && "code" in error && (code === void 0 || error.code === code);
136
139
  };
137
140
  var PythonAnalysisError = class extends Error {
138
- constructor({ message, code, path: path4, link, action }) {
141
+ constructor({
142
+ message,
143
+ code,
144
+ path: path4,
145
+ link,
146
+ action,
147
+ fileContent
148
+ }) {
139
149
  super(message);
140
150
  this.hideStackTrace = true;
141
151
  this.name = "PythonAnalysisError";
@@ -143,6 +153,7 @@ var PythonAnalysisError = class extends Error {
143
153
  this.path = path4;
144
154
  this.link = link;
145
155
  this.action = action;
156
+ this.fileContent = fileContent;
146
157
  }
147
158
  };
148
159
 
@@ -204,7 +215,8 @@ function parseRawConfig(content, filename, filetype = void 0) {
204
215
  throw new PythonAnalysisError({
205
216
  message: `Could not parse config file "${filename}": ${error.message}`,
206
217
  code: "PYTHON_CONFIG_PARSE_ERROR",
207
- path: filename
218
+ path: filename,
219
+ fileContent: content
208
220
  });
209
221
  }
210
222
  throw error;
@@ -222,7 +234,8 @@ function parseConfig(content, filename, schema, filetype = void 0) {
222
234
  message: `Invalid config in "${filename}":
223
235
  ${issues}`,
224
236
  code: "PYTHON_CONFIG_VALIDATION_ERROR",
225
- path: filename
237
+ path: filename,
238
+ fileContent: content
226
239
  });
227
240
  }
228
241
  return result.data;
@@ -501,6 +514,8 @@ function convertPipfileToPyprojectToml(pipfile) {
501
514
  }
502
515
  if (deps.length > 0) {
503
516
  pyproject.project = {
517
+ name: "app",
518
+ version: "0.1.0",
504
519
  dependencies: deps
505
520
  };
506
521
  }
@@ -558,6 +573,8 @@ function convertPipfileLockToPyprojectToml(pipfileLock) {
558
573
  }
559
574
  if (deps.length > 0) {
560
575
  pyproject.project = {
576
+ name: "app",
577
+ version: "0.1.0",
561
578
  dependencies: deps
562
579
  };
563
580
  }
@@ -634,7 +651,8 @@ var uvIndexEntrySchema = import_zod3.z.object({
634
651
  name: import_zod3.z.string(),
635
652
  url: import_zod3.z.string(),
636
653
  default: import_zod3.z.boolean().optional(),
637
- explicit: import_zod3.z.boolean().optional()
654
+ explicit: import_zod3.z.boolean().optional(),
655
+ format: import_zod3.z.string().optional()
638
656
  });
639
657
  var uvConfigSchema = import_zod3.z.object({
640
658
  sources: import_zod3.z.record(import_zod3.z.union([dependencySourceSchema, import_zod3.z.array(dependencySourceSchema)])).optional(),
@@ -708,6 +726,7 @@ var import_node_path4 = require("path");
708
726
  var import_pip_requirements_js = require("pip-requirements-js");
709
727
  var PRIMARY_INDEX_NAME = "primary";
710
728
  var EXTRA_INDEX_PREFIX = "extra-";
729
+ var FIND_LINKS_PREFIX = "find-links-";
711
730
  function parseGitUrl(url) {
712
731
  if (!url.startsWith("git+")) {
713
732
  return null;
@@ -749,6 +768,8 @@ function extractPipArguments(fileContent) {
749
768
  };
750
769
  const lines = fileContent.split(/\r?\n/);
751
770
  const cleanedLines = [];
771
+ const pathRequirements = [];
772
+ const editableRequirements = [];
752
773
  for (let i = 0; i < lines.length; i++) {
753
774
  const line = lines[i];
754
775
  const trimmed = line.trim();
@@ -763,11 +784,21 @@ function extractPipArguments(fileContent) {
763
784
  fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
764
785
  }
765
786
  const extracted = tryExtractPipArgument(fullLine, options);
766
- if (extracted) {
787
+ if (extracted === true) {
788
+ i += linesConsumed;
789
+ } else if (typeof extracted === "object" && extracted.editable) {
790
+ editableRequirements.push(extracted.editable);
767
791
  i += linesConsumed;
768
792
  } else {
793
+ if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
794
+ i += linesConsumed;
795
+ continue;
796
+ }
769
797
  const strippedLine = stripInlineHashes(fullLine);
770
- if (strippedLine !== fullLine) {
798
+ const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
799
+ if (isPathOrUrlRequirement(effectiveLine)) {
800
+ pathRequirements.push(effectiveLine);
801
+ } else if (strippedLine !== fullLine) {
771
802
  cleanedLines.push(strippedLine);
772
803
  } else {
773
804
  cleanedLines.push(line);
@@ -780,7 +811,9 @@ function extractPipArguments(fileContent) {
780
811
  }
781
812
  return {
782
813
  cleanedContent: cleanedLines.join("\n"),
783
- options
814
+ options,
815
+ pathRequirements,
816
+ editableRequirements
784
817
  };
785
818
  }
786
819
  function tryExtractPipArgument(line, options) {
@@ -819,6 +852,46 @@ function tryExtractPipArgument(line, options) {
819
852
  return true;
820
853
  }
821
854
  }
855
+ if (line.startsWith("--editable")) {
856
+ const path4 = extractArgValue(line, "--editable");
857
+ if (path4) {
858
+ return { editable: path4 };
859
+ }
860
+ }
861
+ if (line.startsWith("-e ") || line.startsWith("-e ")) {
862
+ const path4 = line.slice(2).trim();
863
+ if (path4) {
864
+ return { editable: path4 };
865
+ }
866
+ }
867
+ if (line.startsWith("--find-links")) {
868
+ const url = extractArgValue(line, "--find-links");
869
+ if (url) {
870
+ if (!options.findLinks)
871
+ options.findLinks = [];
872
+ options.findLinks.push(url);
873
+ return true;
874
+ }
875
+ }
876
+ if (line.startsWith("-f ") || line.startsWith("-f ")) {
877
+ const match = line.match(/^-f\s+(\S+)/);
878
+ if (match) {
879
+ if (!options.findLinks)
880
+ options.findLinks = [];
881
+ options.findLinks.push(match[1]);
882
+ return true;
883
+ }
884
+ }
885
+ if (line === "--no-index" || line.startsWith("--no-index ")) {
886
+ options.noIndex = true;
887
+ return true;
888
+ }
889
+ if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
890
+ return true;
891
+ }
892
+ if (line.startsWith("--")) {
893
+ return true;
894
+ }
822
895
  return false;
823
896
  }
824
897
  function stripInlineHashes(line) {
@@ -834,15 +907,158 @@ function extractInlineHashes(line) {
834
907
  return hashes;
835
908
  }
836
909
  function extractArgValue(line, option) {
910
+ let value = null;
837
911
  if (line.startsWith(`${option}=`)) {
838
- const value = line.slice(option.length + 1).trim();
839
- return value || null;
912
+ value = line.slice(option.length + 1).trim();
913
+ } else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
914
+ value = line.slice(option.length).trim();
840
915
  }
841
- if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
842
- const value = line.slice(option.length).trim();
843
- return value || null;
916
+ if (!value)
917
+ return null;
918
+ const commentIdx = value.indexOf(" #");
919
+ if (commentIdx !== -1) {
920
+ value = value.slice(0, commentIdx).trim();
844
921
  }
845
- return null;
922
+ return value || null;
923
+ }
924
+ function isPathOrUrlRequirement(line) {
925
+ if (line.startsWith("./") || line.startsWith("../"))
926
+ return true;
927
+ if (line.startsWith("/"))
928
+ return true;
929
+ if (line.startsWith("~/"))
930
+ return true;
931
+ if (/^(https?|ftp|file):\/\//i.test(line))
932
+ return true;
933
+ if (isBareArchiveFilename(line))
934
+ return true;
935
+ return false;
936
+ }
937
+ function isBareArchiveFilename(line) {
938
+ if (line.includes(" @ "))
939
+ return false;
940
+ let check = line;
941
+ const commentIdx = check.indexOf(" #");
942
+ if (commentIdx !== -1)
943
+ check = check.slice(0, commentIdx);
944
+ const markerIdx = check.indexOf(" ;");
945
+ if (markerIdx !== -1)
946
+ check = check.slice(0, markerIdx);
947
+ const extrasIdx = check.indexOf("[");
948
+ if (extrasIdx !== -1)
949
+ check = check.slice(0, extrasIdx);
950
+ check = check.trim();
951
+ return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
952
+ }
953
+ function parseWheelFilename(filename) {
954
+ if (!filename.endsWith(".whl"))
955
+ return null;
956
+ const stem = filename.slice(0, -4);
957
+ const parts = stem.split("-");
958
+ if (parts.length < 5 || parts.length > 6)
959
+ return null;
960
+ const name = parts[0];
961
+ const version = parts[1];
962
+ if (!name || !version)
963
+ return null;
964
+ return {
965
+ name: name.replace(/_/g, "-"),
966
+ version
967
+ };
968
+ }
969
+ function parseSdistFilename(filename) {
970
+ let stem = filename;
971
+ for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
972
+ if (stem.endsWith(ext)) {
973
+ stem = stem.slice(0, -ext.length);
974
+ break;
975
+ }
976
+ }
977
+ if (stem === filename)
978
+ return null;
979
+ const parts = stem.split("-");
980
+ let versionIdx = -1;
981
+ for (let i = 1; i < parts.length; i++) {
982
+ if (/^\d/.test(parts[i])) {
983
+ versionIdx = i;
984
+ break;
985
+ }
986
+ }
987
+ if (versionIdx === -1)
988
+ return null;
989
+ return {
990
+ name: parts.slice(0, versionIdx).join("-"),
991
+ version: parts.slice(versionIdx).join("-")
992
+ };
993
+ }
994
+ function normalizePathRequirement(rawLine) {
995
+ let line = rawLine;
996
+ const commentIdx = line.indexOf(" #");
997
+ if (commentIdx !== -1) {
998
+ line = line.slice(0, commentIdx).trim();
999
+ }
1000
+ let markers;
1001
+ const markerIdx = line.indexOf(" ;");
1002
+ if (markerIdx !== -1) {
1003
+ markers = line.slice(markerIdx + 2).trim();
1004
+ line = line.slice(0, markerIdx).trim();
1005
+ }
1006
+ let extras;
1007
+ const extrasMatch = line.match(/\[([^\]]+)\]$/);
1008
+ if (extrasMatch) {
1009
+ extras = extrasMatch[1].split(",").map((e) => e.trim());
1010
+ line = line.slice(0, extrasMatch.index).trim();
1011
+ }
1012
+ const isUrl = /^(https?|ftp|file):\/\//i.test(line);
1013
+ let filename;
1014
+ if (isUrl) {
1015
+ try {
1016
+ const url = new URL(line);
1017
+ filename = url.pathname.split("/").pop() || "";
1018
+ } catch {
1019
+ return null;
1020
+ }
1021
+ } else {
1022
+ const cleanPath = line.replace(/\/+$/, "");
1023
+ filename = cleanPath.split("/").pop() || "";
1024
+ }
1025
+ if (!filename)
1026
+ return null;
1027
+ let name;
1028
+ let version;
1029
+ const wheelParsed = parseWheelFilename(filename);
1030
+ if (wheelParsed) {
1031
+ name = wheelParsed.name;
1032
+ version = wheelParsed.version;
1033
+ }
1034
+ if (!name) {
1035
+ const sdistParsed = parseSdistFilename(filename);
1036
+ if (sdistParsed) {
1037
+ name = sdistParsed.name;
1038
+ version = sdistParsed.version;
1039
+ }
1040
+ }
1041
+ if (!name) {
1042
+ name = filename.replace(/[-_.]+/g, "-").toLowerCase();
1043
+ }
1044
+ if (!name)
1045
+ return null;
1046
+ const req = { name };
1047
+ if (version) {
1048
+ req.version = `==${version}`;
1049
+ }
1050
+ if (extras && extras.length > 0) {
1051
+ req.extras = extras;
1052
+ }
1053
+ if (markers) {
1054
+ req.markers = markers;
1055
+ }
1056
+ if (isUrl) {
1057
+ req.url = line;
1058
+ } else {
1059
+ req.source = { path: line };
1060
+ }
1061
+ return req;
846
1062
  }
847
1063
  function convertRequirementsToPyprojectToml(fileContent, readFile3) {
848
1064
  const pyproject = {};
@@ -861,6 +1077,8 @@ function convertRequirementsToPyprojectToml(fileContent, readFile3) {
861
1077
  }
862
1078
  if (deps.length > 0) {
863
1079
  pyproject.project = {
1080
+ name: "app",
1081
+ version: "0.1.0",
864
1082
  dependencies: deps
865
1083
  };
866
1084
  }
@@ -892,6 +1110,15 @@ function buildIndexEntries(pipOptions) {
892
1110
  url: pipOptions.extraIndexUrls[i]
893
1111
  });
894
1112
  }
1113
+ if (pipOptions.findLinks) {
1114
+ for (let i = 0; i < pipOptions.findLinks.length; i++) {
1115
+ indexes.push({
1116
+ name: `${FIND_LINKS_PREFIX}${i + 1}`,
1117
+ url: pipOptions.findLinks[i],
1118
+ format: "flat"
1119
+ });
1120
+ }
1121
+ }
895
1122
  return indexes;
896
1123
  }
897
1124
  function parseRequirementsFile(fileContent, readFile3) {
@@ -899,7 +1126,7 @@ function parseRequirementsFile(fileContent, readFile3) {
899
1126
  return parseRequirementsFileInternal(fileContent, readFile3, visited);
900
1127
  }
901
1128
  function parseRequirementsFileInternal(fileContent, readFile3, visited) {
902
- const { cleanedContent, options } = extractPipArguments(fileContent);
1129
+ const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
903
1130
  const hashMap = buildHashMap(fileContent);
904
1131
  const requirements = (0, import_pip_requirements_js.parsePipRequirementsFile)(cleanedContent);
905
1132
  const normalized = [];
@@ -907,7 +1134,9 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
907
1134
  requirementFiles: [...options.requirementFiles],
908
1135
  constraintFiles: [...options.constraintFiles],
909
1136
  indexUrl: options.indexUrl,
910
- extraIndexUrls: [...options.extraIndexUrls]
1137
+ extraIndexUrls: [...options.extraIndexUrls],
1138
+ findLinks: options.findLinks ? [...options.findLinks] : void 0,
1139
+ noIndex: options.noIndex
911
1140
  };
912
1141
  for (const req of requirements) {
913
1142
  if (req.type === "RequirementsFile") {
@@ -927,6 +1156,23 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
927
1156
  normalized.push(norm);
928
1157
  }
929
1158
  }
1159
+ for (const rawPath of pathRequirements) {
1160
+ const norm = normalizePathRequirement(rawPath);
1161
+ if (norm != null) {
1162
+ normalized.push(norm);
1163
+ }
1164
+ }
1165
+ for (const rawPath of editableRequirements) {
1166
+ const norm = normalizePathRequirement(rawPath);
1167
+ if (norm != null) {
1168
+ if (norm.source) {
1169
+ norm.source.editable = true;
1170
+ } else {
1171
+ norm.source = { path: rawPath, editable: true };
1172
+ }
1173
+ normalized.push(norm);
1174
+ }
1175
+ }
930
1176
  if (readFile3) {
931
1177
  for (const refPath of mergedOptions.requirementFiles) {
932
1178
  const refPathKey = (0, import_node_path4.normalize)(refPath);
@@ -963,6 +1209,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
963
1209
  mergedOptions.constraintFiles.push(constraintPath);
964
1210
  }
965
1211
  }
1212
+ if (refParsed.pipOptions.findLinks) {
1213
+ if (!mergedOptions.findLinks)
1214
+ mergedOptions.findLinks = [];
1215
+ for (const fl of refParsed.pipOptions.findLinks) {
1216
+ if (!mergedOptions.findLinks.includes(fl)) {
1217
+ mergedOptions.findLinks.push(fl);
1218
+ }
1219
+ }
1220
+ }
1221
+ if (refParsed.pipOptions.noIndex) {
1222
+ mergedOptions.noIndex = true;
1223
+ }
966
1224
  }
967
1225
  }
968
1226
  }
@@ -1451,6 +1709,11 @@ var PythonManifestKind = /* @__PURE__ */ ((PythonManifestKind2) => {
1451
1709
  PythonManifestKind2["PyProjectToml"] = "pyproject.toml";
1452
1710
  return PythonManifestKind2;
1453
1711
  })(PythonManifestKind || {});
1712
+ var PythonLockFileKind = /* @__PURE__ */ ((PythonLockFileKind2) => {
1713
+ PythonLockFileKind2["UvLock"] = "uv.lock";
1714
+ PythonLockFileKind2["PylockToml"] = "pylock.toml";
1715
+ return PythonLockFileKind2;
1716
+ })(PythonLockFileKind || {});
1454
1717
  var PythonManifestConvertedKind = /* @__PURE__ */ ((PythonManifestConvertedKind2) => {
1455
1718
  PythonManifestConvertedKind2["Pipfile"] = "Pipfile";
1456
1719
  PythonManifestConvertedKind2["PipfileLock"] = "Pipfile.lock";
@@ -1492,6 +1755,7 @@ async function discoverPythonPackage({
1492
1755
  }
1493
1756
  let entrypointManifest;
1494
1757
  let workspaceManifest;
1758
+ let workspaceLockFile;
1495
1759
  if (manifests.length === 0) {
1496
1760
  return {
1497
1761
  configs
@@ -1503,6 +1767,7 @@ async function discoverPythonPackage({
1503
1767
  manifests
1504
1768
  );
1505
1769
  workspaceManifest = entrypointWorkspaceManifest;
1770
+ workspaceLockFile = entrypointWorkspaceManifest.lockFile;
1506
1771
  configs = configs.filter(
1507
1772
  (config) => Object.values(config).some(
1508
1773
  (cfg) => cfg !== void 0 && isSubpath(
@@ -1520,6 +1785,7 @@ async function discoverPythonPackage({
1520
1785
  return {
1521
1786
  manifest: entrypointManifest,
1522
1787
  workspaceManifest,
1788
+ workspaceLockFile,
1523
1789
  configs,
1524
1790
  requiresPython
1525
1791
  };
@@ -1634,6 +1900,21 @@ async function loadPythonManifest(root, prefix) {
1634
1900
  }
1635
1901
  return manifest;
1636
1902
  }
1903
+ 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);
1906
+ const uvLockContent = await readFileTextIfExists(uvLockPath);
1907
+ if (uvLockContent != null) {
1908
+ return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
1909
+ }
1910
+ const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
1911
+ const pylockPath = import_node_path5.default.join(root, pylockRelPath);
1912
+ const pylockContent = await readFileTextIfExists(pylockPath);
1913
+ if (pylockContent != null) {
1914
+ return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
1915
+ }
1916
+ return void 0;
1917
+ }
1637
1918
  async function maybeLoadPyProjectToml(root, subdir) {
1638
1919
  const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
1639
1920
  const pyprojectTomlPath = import_node_path5.default.join(root, pyprojectTomlRelPath);
@@ -1680,9 +1961,11 @@ async function maybeLoadPyProjectToml(root, subdir) {
1680
1961
  pyproject.tool.uv = uvToml;
1681
1962
  }
1682
1963
  }
1964
+ const lockFile = await maybeLoadLockFile(root, subdir);
1683
1965
  return {
1684
1966
  path: pyprojectTomlRelPath,
1685
- data: pyproject
1967
+ data: pyproject,
1968
+ lockFile
1686
1969
  };
1687
1970
  }
1688
1971
  async function maybeLoadPipfile(root, subdir) {
@@ -1769,12 +2052,16 @@ async function maybeLoadRequirementsTxt(root, subdir, fileName) {
1769
2052
  } catch (error) {
1770
2053
  if (error instanceof PythonAnalysisError) {
1771
2054
  error.path = requirementsTxtRelPath;
2055
+ if (!error.fileContent) {
2056
+ error.fileContent = requirementsContent;
2057
+ }
1772
2058
  throw error;
1773
2059
  }
1774
2060
  throw new PythonAnalysisError({
1775
2061
  message: `could not parse ${fileName}: ${error instanceof Error ? error.message : String(error)}`,
1776
2062
  code: "PYTHON_REQUIREMENTS_PARSE_ERROR",
1777
- path: requirementsTxtRelPath
2063
+ path: requirementsTxtRelPath,
2064
+ fileContent: requirementsContent
1778
2065
  });
1779
2066
  }
1780
2067
  }
@@ -1811,6 +2098,29 @@ async function maybeLoadPythonRequest(root, subdir) {
1811
2098
  };
1812
2099
  }
1813
2100
 
2101
+ // src/manifest/serialize.ts
2102
+ var import_smol_toml2 = __toESM(require("smol-toml"), 1);
2103
+ function stringifyManifest(data) {
2104
+ return import_smol_toml2.default.stringify(data);
2105
+ }
2106
+ function createMinimalManifest(options = {}) {
2107
+ const {
2108
+ name = "app",
2109
+ version = "0.1.0",
2110
+ requiresPython,
2111
+ dependencies = []
2112
+ } = options;
2113
+ return {
2114
+ project: {
2115
+ name,
2116
+ version,
2117
+ ...requiresPython && { "requires-python": requiresPython },
2118
+ dependencies,
2119
+ classifiers: ["Private :: Do Not Upload"]
2120
+ }
2121
+ };
2122
+ }
2123
+
1814
2124
  // src/manifest/python-selector.ts
1815
2125
  function selectPython(constraints, available) {
1816
2126
  const warnings = [];
@@ -1984,6 +2294,7 @@ var HashDigestSchema = hashDigestSchema;
1984
2294
  PythonBuild,
1985
2295
  PythonConfigKind,
1986
2296
  PythonImplementation,
2297
+ PythonLockFileKind,
1987
2298
  PythonManifestConvertedKind,
1988
2299
  PythonManifestKind,
1989
2300
  PythonVariant,
@@ -1994,6 +2305,8 @@ var HashDigestSchema = hashDigestSchema;
1994
2305
  UvConfigWorkspaceSchema,
1995
2306
  UvIndexEntrySchema,
1996
2307
  containsAppOrHandler,
2308
+ createMinimalManifest,
1997
2309
  discoverPythonPackage,
1998
- selectPython
2310
+ selectPython,
2311
+ stringifyManifest
1999
2312
  });
package/dist/index.d.ts CHANGED
@@ -7,8 +7,9 @@
7
7
  * @module @vercel/python-analysis
8
8
  */
9
9
  export { containsAppOrHandler } from './semantic/entrypoints';
10
- export type { PythonConfig, PythonConfigs, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
11
- export { discoverPythonPackage, PythonConfigKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
10
+ export type { PythonConfig, PythonConfigs, PythonLockFile, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
11
+ export { discoverPythonPackage, PythonConfigKind, PythonLockFileKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
12
+ export { createMinimalManifest, stringifyManifest, type CreateMinimalManifestOptions, } from './manifest/serialize';
12
13
  export type { PythonSelectionResult } from './manifest/python-selector';
13
14
  export { selectPython } from './manifest/python-selector';
14
15
  export { PythonAnalysisError } from './util/error';