@vercel/python-analysis 0.3.2 → 0.4.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
@@ -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
 
@@ -501,6 +504,8 @@ function convertPipfileToPyprojectToml(pipfile) {
501
504
  }
502
505
  if (deps.length > 0) {
503
506
  pyproject.project = {
507
+ name: "app",
508
+ version: "0.1.0",
504
509
  dependencies: deps
505
510
  };
506
511
  }
@@ -558,6 +563,8 @@ function convertPipfileLockToPyprojectToml(pipfileLock) {
558
563
  }
559
564
  if (deps.length > 0) {
560
565
  pyproject.project = {
566
+ name: "app",
567
+ version: "0.1.0",
561
568
  dependencies: deps
562
569
  };
563
570
  }
@@ -634,7 +641,8 @@ var uvIndexEntrySchema = import_zod3.z.object({
634
641
  name: import_zod3.z.string(),
635
642
  url: import_zod3.z.string(),
636
643
  default: import_zod3.z.boolean().optional(),
637
- explicit: import_zod3.z.boolean().optional()
644
+ explicit: import_zod3.z.boolean().optional(),
645
+ format: import_zod3.z.string().optional()
638
646
  });
639
647
  var uvConfigSchema = import_zod3.z.object({
640
648
  sources: import_zod3.z.record(import_zod3.z.union([dependencySourceSchema, import_zod3.z.array(dependencySourceSchema)])).optional(),
@@ -708,6 +716,7 @@ var import_node_path4 = require("path");
708
716
  var import_pip_requirements_js = require("pip-requirements-js");
709
717
  var PRIMARY_INDEX_NAME = "primary";
710
718
  var EXTRA_INDEX_PREFIX = "extra-";
719
+ var FIND_LINKS_PREFIX = "find-links-";
711
720
  function parseGitUrl(url) {
712
721
  if (!url.startsWith("git+")) {
713
722
  return null;
@@ -749,6 +758,8 @@ function extractPipArguments(fileContent) {
749
758
  };
750
759
  const lines = fileContent.split(/\r?\n/);
751
760
  const cleanedLines = [];
761
+ const pathRequirements = [];
762
+ const editableRequirements = [];
752
763
  for (let i = 0; i < lines.length; i++) {
753
764
  const line = lines[i];
754
765
  const trimmed = line.trim();
@@ -763,11 +774,21 @@ function extractPipArguments(fileContent) {
763
774
  fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
764
775
  }
765
776
  const extracted = tryExtractPipArgument(fullLine, options);
766
- if (extracted) {
777
+ if (extracted === true) {
778
+ i += linesConsumed;
779
+ } else if (typeof extracted === "object" && extracted.editable) {
780
+ editableRequirements.push(extracted.editable);
767
781
  i += linesConsumed;
768
782
  } else {
783
+ if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
784
+ i += linesConsumed;
785
+ continue;
786
+ }
769
787
  const strippedLine = stripInlineHashes(fullLine);
770
- if (strippedLine !== fullLine) {
788
+ const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
789
+ if (isPathOrUrlRequirement(effectiveLine)) {
790
+ pathRequirements.push(effectiveLine);
791
+ } else if (strippedLine !== fullLine) {
771
792
  cleanedLines.push(strippedLine);
772
793
  } else {
773
794
  cleanedLines.push(line);
@@ -780,7 +801,9 @@ function extractPipArguments(fileContent) {
780
801
  }
781
802
  return {
782
803
  cleanedContent: cleanedLines.join("\n"),
783
- options
804
+ options,
805
+ pathRequirements,
806
+ editableRequirements
784
807
  };
785
808
  }
786
809
  function tryExtractPipArgument(line, options) {
@@ -819,6 +842,46 @@ function tryExtractPipArgument(line, options) {
819
842
  return true;
820
843
  }
821
844
  }
845
+ if (line.startsWith("--editable")) {
846
+ const path4 = extractArgValue(line, "--editable");
847
+ if (path4) {
848
+ return { editable: path4 };
849
+ }
850
+ }
851
+ if (line.startsWith("-e ") || line.startsWith("-e ")) {
852
+ const path4 = line.slice(2).trim();
853
+ if (path4) {
854
+ return { editable: path4 };
855
+ }
856
+ }
857
+ if (line.startsWith("--find-links")) {
858
+ const url = extractArgValue(line, "--find-links");
859
+ if (url) {
860
+ if (!options.findLinks)
861
+ options.findLinks = [];
862
+ options.findLinks.push(url);
863
+ return true;
864
+ }
865
+ }
866
+ if (line.startsWith("-f ") || line.startsWith("-f ")) {
867
+ const match = line.match(/^-f\s+(\S+)/);
868
+ if (match) {
869
+ if (!options.findLinks)
870
+ options.findLinks = [];
871
+ options.findLinks.push(match[1]);
872
+ return true;
873
+ }
874
+ }
875
+ if (line === "--no-index" || line.startsWith("--no-index ")) {
876
+ options.noIndex = true;
877
+ return true;
878
+ }
879
+ if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
880
+ return true;
881
+ }
882
+ if (line.startsWith("--")) {
883
+ return true;
884
+ }
822
885
  return false;
823
886
  }
824
887
  function stripInlineHashes(line) {
@@ -834,15 +897,158 @@ function extractInlineHashes(line) {
834
897
  return hashes;
835
898
  }
836
899
  function extractArgValue(line, option) {
900
+ let value = null;
837
901
  if (line.startsWith(`${option}=`)) {
838
- const value = line.slice(option.length + 1).trim();
839
- return value || null;
902
+ value = line.slice(option.length + 1).trim();
903
+ } else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
904
+ value = line.slice(option.length).trim();
840
905
  }
841
- if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
842
- const value = line.slice(option.length).trim();
843
- return value || null;
906
+ if (!value)
907
+ return null;
908
+ const commentIdx = value.indexOf(" #");
909
+ if (commentIdx !== -1) {
910
+ value = value.slice(0, commentIdx).trim();
844
911
  }
845
- return null;
912
+ return value || null;
913
+ }
914
+ function isPathOrUrlRequirement(line) {
915
+ if (line.startsWith("./") || line.startsWith("../"))
916
+ return true;
917
+ if (line.startsWith("/"))
918
+ return true;
919
+ if (line.startsWith("~/"))
920
+ return true;
921
+ if (/^(https?|ftp|file):\/\//i.test(line))
922
+ return true;
923
+ if (isBareArchiveFilename(line))
924
+ return true;
925
+ return false;
926
+ }
927
+ function isBareArchiveFilename(line) {
928
+ if (line.includes(" @ "))
929
+ return false;
930
+ let check = line;
931
+ const commentIdx = check.indexOf(" #");
932
+ if (commentIdx !== -1)
933
+ check = check.slice(0, commentIdx);
934
+ const markerIdx = check.indexOf(" ;");
935
+ if (markerIdx !== -1)
936
+ check = check.slice(0, markerIdx);
937
+ const extrasIdx = check.indexOf("[");
938
+ if (extrasIdx !== -1)
939
+ check = check.slice(0, extrasIdx);
940
+ check = check.trim();
941
+ return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
942
+ }
943
+ function parseWheelFilename(filename) {
944
+ if (!filename.endsWith(".whl"))
945
+ return null;
946
+ const stem = filename.slice(0, -4);
947
+ const parts = stem.split("-");
948
+ if (parts.length < 5 || parts.length > 6)
949
+ return null;
950
+ const name = parts[0];
951
+ const version = parts[1];
952
+ if (!name || !version)
953
+ return null;
954
+ return {
955
+ name: name.replace(/_/g, "-"),
956
+ version
957
+ };
958
+ }
959
+ function parseSdistFilename(filename) {
960
+ let stem = filename;
961
+ for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
962
+ if (stem.endsWith(ext)) {
963
+ stem = stem.slice(0, -ext.length);
964
+ break;
965
+ }
966
+ }
967
+ if (stem === filename)
968
+ return null;
969
+ const parts = stem.split("-");
970
+ let versionIdx = -1;
971
+ for (let i = 1; i < parts.length; i++) {
972
+ if (/^\d/.test(parts[i])) {
973
+ versionIdx = i;
974
+ break;
975
+ }
976
+ }
977
+ if (versionIdx === -1)
978
+ return null;
979
+ return {
980
+ name: parts.slice(0, versionIdx).join("-"),
981
+ version: parts.slice(versionIdx).join("-")
982
+ };
983
+ }
984
+ function normalizePathRequirement(rawLine) {
985
+ let line = rawLine;
986
+ const commentIdx = line.indexOf(" #");
987
+ if (commentIdx !== -1) {
988
+ line = line.slice(0, commentIdx).trim();
989
+ }
990
+ let markers;
991
+ const markerIdx = line.indexOf(" ;");
992
+ if (markerIdx !== -1) {
993
+ markers = line.slice(markerIdx + 2).trim();
994
+ line = line.slice(0, markerIdx).trim();
995
+ }
996
+ let extras;
997
+ const extrasMatch = line.match(/\[([^\]]+)\]$/);
998
+ if (extrasMatch) {
999
+ extras = extrasMatch[1].split(",").map((e) => e.trim());
1000
+ line = line.slice(0, extrasMatch.index).trim();
1001
+ }
1002
+ const isUrl = /^(https?|ftp|file):\/\//i.test(line);
1003
+ let filename;
1004
+ if (isUrl) {
1005
+ try {
1006
+ const url = new URL(line);
1007
+ filename = url.pathname.split("/").pop() || "";
1008
+ } catch {
1009
+ return null;
1010
+ }
1011
+ } else {
1012
+ const cleanPath = line.replace(/\/+$/, "");
1013
+ filename = cleanPath.split("/").pop() || "";
1014
+ }
1015
+ if (!filename)
1016
+ return null;
1017
+ let name;
1018
+ let version;
1019
+ const wheelParsed = parseWheelFilename(filename);
1020
+ if (wheelParsed) {
1021
+ name = wheelParsed.name;
1022
+ version = wheelParsed.version;
1023
+ }
1024
+ if (!name) {
1025
+ const sdistParsed = parseSdistFilename(filename);
1026
+ if (sdistParsed) {
1027
+ name = sdistParsed.name;
1028
+ version = sdistParsed.version;
1029
+ }
1030
+ }
1031
+ if (!name) {
1032
+ name = filename.replace(/[-_.]+/g, "-").toLowerCase();
1033
+ }
1034
+ if (!name)
1035
+ return null;
1036
+ const req = { name };
1037
+ if (version) {
1038
+ req.version = `==${version}`;
1039
+ }
1040
+ if (extras && extras.length > 0) {
1041
+ req.extras = extras;
1042
+ }
1043
+ if (markers) {
1044
+ req.markers = markers;
1045
+ }
1046
+ if (isUrl) {
1047
+ req.url = line;
1048
+ } else {
1049
+ req.source = { path: line };
1050
+ }
1051
+ return req;
846
1052
  }
847
1053
  function convertRequirementsToPyprojectToml(fileContent, readFile3) {
848
1054
  const pyproject = {};
@@ -861,6 +1067,8 @@ function convertRequirementsToPyprojectToml(fileContent, readFile3) {
861
1067
  }
862
1068
  if (deps.length > 0) {
863
1069
  pyproject.project = {
1070
+ name: "app",
1071
+ version: "0.1.0",
864
1072
  dependencies: deps
865
1073
  };
866
1074
  }
@@ -892,6 +1100,15 @@ function buildIndexEntries(pipOptions) {
892
1100
  url: pipOptions.extraIndexUrls[i]
893
1101
  });
894
1102
  }
1103
+ if (pipOptions.findLinks) {
1104
+ for (let i = 0; i < pipOptions.findLinks.length; i++) {
1105
+ indexes.push({
1106
+ name: `${FIND_LINKS_PREFIX}${i + 1}`,
1107
+ url: pipOptions.findLinks[i],
1108
+ format: "flat"
1109
+ });
1110
+ }
1111
+ }
895
1112
  return indexes;
896
1113
  }
897
1114
  function parseRequirementsFile(fileContent, readFile3) {
@@ -899,7 +1116,7 @@ function parseRequirementsFile(fileContent, readFile3) {
899
1116
  return parseRequirementsFileInternal(fileContent, readFile3, visited);
900
1117
  }
901
1118
  function parseRequirementsFileInternal(fileContent, readFile3, visited) {
902
- const { cleanedContent, options } = extractPipArguments(fileContent);
1119
+ const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
903
1120
  const hashMap = buildHashMap(fileContent);
904
1121
  const requirements = (0, import_pip_requirements_js.parsePipRequirementsFile)(cleanedContent);
905
1122
  const normalized = [];
@@ -907,7 +1124,9 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
907
1124
  requirementFiles: [...options.requirementFiles],
908
1125
  constraintFiles: [...options.constraintFiles],
909
1126
  indexUrl: options.indexUrl,
910
- extraIndexUrls: [...options.extraIndexUrls]
1127
+ extraIndexUrls: [...options.extraIndexUrls],
1128
+ findLinks: options.findLinks ? [...options.findLinks] : void 0,
1129
+ noIndex: options.noIndex
911
1130
  };
912
1131
  for (const req of requirements) {
913
1132
  if (req.type === "RequirementsFile") {
@@ -927,6 +1146,23 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
927
1146
  normalized.push(norm);
928
1147
  }
929
1148
  }
1149
+ for (const rawPath of pathRequirements) {
1150
+ const norm = normalizePathRequirement(rawPath);
1151
+ if (norm != null) {
1152
+ normalized.push(norm);
1153
+ }
1154
+ }
1155
+ for (const rawPath of editableRequirements) {
1156
+ const norm = normalizePathRequirement(rawPath);
1157
+ if (norm != null) {
1158
+ if (norm.source) {
1159
+ norm.source.editable = true;
1160
+ } else {
1161
+ norm.source = { path: rawPath, editable: true };
1162
+ }
1163
+ normalized.push(norm);
1164
+ }
1165
+ }
930
1166
  if (readFile3) {
931
1167
  for (const refPath of mergedOptions.requirementFiles) {
932
1168
  const refPathKey = (0, import_node_path4.normalize)(refPath);
@@ -963,6 +1199,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
963
1199
  mergedOptions.constraintFiles.push(constraintPath);
964
1200
  }
965
1201
  }
1202
+ if (refParsed.pipOptions.findLinks) {
1203
+ if (!mergedOptions.findLinks)
1204
+ mergedOptions.findLinks = [];
1205
+ for (const fl of refParsed.pipOptions.findLinks) {
1206
+ if (!mergedOptions.findLinks.includes(fl)) {
1207
+ mergedOptions.findLinks.push(fl);
1208
+ }
1209
+ }
1210
+ }
1211
+ if (refParsed.pipOptions.noIndex) {
1212
+ mergedOptions.noIndex = true;
1213
+ }
966
1214
  }
967
1215
  }
968
1216
  }
@@ -1451,6 +1699,11 @@ var PythonManifestKind = /* @__PURE__ */ ((PythonManifestKind2) => {
1451
1699
  PythonManifestKind2["PyProjectToml"] = "pyproject.toml";
1452
1700
  return PythonManifestKind2;
1453
1701
  })(PythonManifestKind || {});
1702
+ var PythonLockFileKind = /* @__PURE__ */ ((PythonLockFileKind2) => {
1703
+ PythonLockFileKind2["UvLock"] = "uv.lock";
1704
+ PythonLockFileKind2["PylockToml"] = "pylock.toml";
1705
+ return PythonLockFileKind2;
1706
+ })(PythonLockFileKind || {});
1454
1707
  var PythonManifestConvertedKind = /* @__PURE__ */ ((PythonManifestConvertedKind2) => {
1455
1708
  PythonManifestConvertedKind2["Pipfile"] = "Pipfile";
1456
1709
  PythonManifestConvertedKind2["PipfileLock"] = "Pipfile.lock";
@@ -1492,6 +1745,7 @@ async function discoverPythonPackage({
1492
1745
  }
1493
1746
  let entrypointManifest;
1494
1747
  let workspaceManifest;
1748
+ let workspaceLockFile;
1495
1749
  if (manifests.length === 0) {
1496
1750
  return {
1497
1751
  configs
@@ -1503,6 +1757,7 @@ async function discoverPythonPackage({
1503
1757
  manifests
1504
1758
  );
1505
1759
  workspaceManifest = entrypointWorkspaceManifest;
1760
+ workspaceLockFile = entrypointWorkspaceManifest.lockFile;
1506
1761
  configs = configs.filter(
1507
1762
  (config) => Object.values(config).some(
1508
1763
  (cfg) => cfg !== void 0 && isSubpath(
@@ -1520,6 +1775,7 @@ async function discoverPythonPackage({
1520
1775
  return {
1521
1776
  manifest: entrypointManifest,
1522
1777
  workspaceManifest,
1778
+ workspaceLockFile,
1523
1779
  configs,
1524
1780
  requiresPython
1525
1781
  };
@@ -1634,6 +1890,21 @@ async function loadPythonManifest(root, prefix) {
1634
1890
  }
1635
1891
  return manifest;
1636
1892
  }
1893
+ 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);
1896
+ const uvLockContent = await readFileTextIfExists(uvLockPath);
1897
+ if (uvLockContent != null) {
1898
+ return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
1899
+ }
1900
+ const pylockRelPath = import_node_path5.default.join(subdir, "pylock.toml");
1901
+ const pylockPath = import_node_path5.default.join(root, pylockRelPath);
1902
+ const pylockContent = await readFileTextIfExists(pylockPath);
1903
+ if (pylockContent != null) {
1904
+ return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
1905
+ }
1906
+ return void 0;
1907
+ }
1637
1908
  async function maybeLoadPyProjectToml(root, subdir) {
1638
1909
  const pyprojectTomlRelPath = import_node_path5.default.join(subdir, "pyproject.toml");
1639
1910
  const pyprojectTomlPath = import_node_path5.default.join(root, pyprojectTomlRelPath);
@@ -1680,9 +1951,11 @@ async function maybeLoadPyProjectToml(root, subdir) {
1680
1951
  pyproject.tool.uv = uvToml;
1681
1952
  }
1682
1953
  }
1954
+ const lockFile = await maybeLoadLockFile(root, subdir);
1683
1955
  return {
1684
1956
  path: pyprojectTomlRelPath,
1685
- data: pyproject
1957
+ data: pyproject,
1958
+ lockFile
1686
1959
  };
1687
1960
  }
1688
1961
  async function maybeLoadPipfile(root, subdir) {
@@ -1811,6 +2084,29 @@ async function maybeLoadPythonRequest(root, subdir) {
1811
2084
  };
1812
2085
  }
1813
2086
 
2087
+ // src/manifest/serialize.ts
2088
+ var import_smol_toml2 = __toESM(require("smol-toml"), 1);
2089
+ function stringifyManifest(data) {
2090
+ return import_smol_toml2.default.stringify(data);
2091
+ }
2092
+ function createMinimalManifest(options = {}) {
2093
+ const {
2094
+ name = "app",
2095
+ version = "0.1.0",
2096
+ requiresPython,
2097
+ dependencies = []
2098
+ } = options;
2099
+ return {
2100
+ project: {
2101
+ name,
2102
+ version,
2103
+ ...requiresPython && { "requires-python": requiresPython },
2104
+ dependencies,
2105
+ classifiers: ["Private :: Do Not Upload"]
2106
+ }
2107
+ };
2108
+ }
2109
+
1814
2110
  // src/manifest/python-selector.ts
1815
2111
  function selectPython(constraints, available) {
1816
2112
  const warnings = [];
@@ -1984,6 +2280,7 @@ var HashDigestSchema = hashDigestSchema;
1984
2280
  PythonBuild,
1985
2281
  PythonConfigKind,
1986
2282
  PythonImplementation,
2283
+ PythonLockFileKind,
1987
2284
  PythonManifestConvertedKind,
1988
2285
  PythonManifestKind,
1989
2286
  PythonVariant,
@@ -1994,6 +2291,8 @@ var HashDigestSchema = hashDigestSchema;
1994
2291
  UvConfigWorkspaceSchema,
1995
2292
  UvIndexEntrySchema,
1996
2293
  containsAppOrHandler,
2294
+ createMinimalManifest,
1997
2295
  discoverPythonPackage,
1998
- selectPython
2296
+ selectPython,
2297
+ stringifyManifest
1999
2298
  });
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';
package/dist/index.js CHANGED
@@ -435,6 +435,8 @@ function convertPipfileToPyprojectToml(pipfile) {
435
435
  }
436
436
  if (deps.length > 0) {
437
437
  pyproject.project = {
438
+ name: "app",
439
+ version: "0.1.0",
438
440
  dependencies: deps
439
441
  };
440
442
  }
@@ -492,6 +494,8 @@ function convertPipfileLockToPyprojectToml(pipfileLock) {
492
494
  }
493
495
  if (deps.length > 0) {
494
496
  pyproject.project = {
497
+ name: "app",
498
+ version: "0.1.0",
495
499
  dependencies: deps
496
500
  };
497
501
  }
@@ -568,7 +572,8 @@ var uvIndexEntrySchema = z3.object({
568
572
  name: z3.string(),
569
573
  url: z3.string(),
570
574
  default: z3.boolean().optional(),
571
- explicit: z3.boolean().optional()
575
+ explicit: z3.boolean().optional(),
576
+ format: z3.string().optional()
572
577
  });
573
578
  var uvConfigSchema = z3.object({
574
579
  sources: z3.record(z3.union([dependencySourceSchema, z3.array(dependencySourceSchema)])).optional(),
@@ -642,6 +647,7 @@ import { normalize } from "path";
642
647
  import { parsePipRequirementsFile } from "pip-requirements-js";
643
648
  var PRIMARY_INDEX_NAME = "primary";
644
649
  var EXTRA_INDEX_PREFIX = "extra-";
650
+ var FIND_LINKS_PREFIX = "find-links-";
645
651
  function parseGitUrl(url) {
646
652
  if (!url.startsWith("git+")) {
647
653
  return null;
@@ -683,6 +689,8 @@ function extractPipArguments(fileContent) {
683
689
  };
684
690
  const lines = fileContent.split(/\r?\n/);
685
691
  const cleanedLines = [];
692
+ const pathRequirements = [];
693
+ const editableRequirements = [];
686
694
  for (let i = 0; i < lines.length; i++) {
687
695
  const line = lines[i];
688
696
  const trimmed = line.trim();
@@ -697,11 +705,21 @@ function extractPipArguments(fileContent) {
697
705
  fullLine = fullLine.slice(0, -1) + lines[i + linesConsumed].trim();
698
706
  }
699
707
  const extracted = tryExtractPipArgument(fullLine, options);
700
- if (extracted) {
708
+ if (extracted === true) {
709
+ i += linesConsumed;
710
+ } else if (typeof extracted === "object" && extracted.editable) {
711
+ editableRequirements.push(extracted.editable);
701
712
  i += linesConsumed;
702
713
  } else {
714
+ if (/^-[a-zA-Z]/.test(fullLine) && !fullLine.startsWith("-r") && !fullLine.startsWith("-c")) {
715
+ i += linesConsumed;
716
+ continue;
717
+ }
703
718
  const strippedLine = stripInlineHashes(fullLine);
704
- if (strippedLine !== fullLine) {
719
+ const effectiveLine = (strippedLine !== fullLine ? strippedLine : fullLine).trim();
720
+ if (isPathOrUrlRequirement(effectiveLine)) {
721
+ pathRequirements.push(effectiveLine);
722
+ } else if (strippedLine !== fullLine) {
705
723
  cleanedLines.push(strippedLine);
706
724
  } else {
707
725
  cleanedLines.push(line);
@@ -714,7 +732,9 @@ function extractPipArguments(fileContent) {
714
732
  }
715
733
  return {
716
734
  cleanedContent: cleanedLines.join("\n"),
717
- options
735
+ options,
736
+ pathRequirements,
737
+ editableRequirements
718
738
  };
719
739
  }
720
740
  function tryExtractPipArgument(line, options) {
@@ -753,6 +773,46 @@ function tryExtractPipArgument(line, options) {
753
773
  return true;
754
774
  }
755
775
  }
776
+ if (line.startsWith("--editable")) {
777
+ const path4 = extractArgValue(line, "--editable");
778
+ if (path4) {
779
+ return { editable: path4 };
780
+ }
781
+ }
782
+ if (line.startsWith("-e ") || line.startsWith("-e ")) {
783
+ const path4 = line.slice(2).trim();
784
+ if (path4) {
785
+ return { editable: path4 };
786
+ }
787
+ }
788
+ if (line.startsWith("--find-links")) {
789
+ const url = extractArgValue(line, "--find-links");
790
+ if (url) {
791
+ if (!options.findLinks)
792
+ options.findLinks = [];
793
+ options.findLinks.push(url);
794
+ return true;
795
+ }
796
+ }
797
+ if (line.startsWith("-f ") || line.startsWith("-f ")) {
798
+ const match = line.match(/^-f\s+(\S+)/);
799
+ if (match) {
800
+ if (!options.findLinks)
801
+ options.findLinks = [];
802
+ options.findLinks.push(match[1]);
803
+ return true;
804
+ }
805
+ }
806
+ if (line === "--no-index" || line.startsWith("--no-index ")) {
807
+ options.noIndex = true;
808
+ return true;
809
+ }
810
+ if (line.startsWith("--no-binary") || line.startsWith("--only-binary")) {
811
+ return true;
812
+ }
813
+ if (line.startsWith("--")) {
814
+ return true;
815
+ }
756
816
  return false;
757
817
  }
758
818
  function stripInlineHashes(line) {
@@ -768,15 +828,158 @@ function extractInlineHashes(line) {
768
828
  return hashes;
769
829
  }
770
830
  function extractArgValue(line, option) {
831
+ let value = null;
771
832
  if (line.startsWith(`${option}=`)) {
772
- const value = line.slice(option.length + 1).trim();
773
- return value || null;
833
+ value = line.slice(option.length + 1).trim();
834
+ } else if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
835
+ value = line.slice(option.length).trim();
774
836
  }
775
- if (line.startsWith(`${option} `) || line.startsWith(`${option} `)) {
776
- const value = line.slice(option.length).trim();
777
- return value || null;
837
+ if (!value)
838
+ return null;
839
+ const commentIdx = value.indexOf(" #");
840
+ if (commentIdx !== -1) {
841
+ value = value.slice(0, commentIdx).trim();
778
842
  }
779
- return null;
843
+ return value || null;
844
+ }
845
+ function isPathOrUrlRequirement(line) {
846
+ if (line.startsWith("./") || line.startsWith("../"))
847
+ return true;
848
+ if (line.startsWith("/"))
849
+ return true;
850
+ if (line.startsWith("~/"))
851
+ return true;
852
+ if (/^(https?|ftp|file):\/\//i.test(line))
853
+ return true;
854
+ if (isBareArchiveFilename(line))
855
+ return true;
856
+ return false;
857
+ }
858
+ function isBareArchiveFilename(line) {
859
+ if (line.includes(" @ "))
860
+ return false;
861
+ let check = line;
862
+ const commentIdx = check.indexOf(" #");
863
+ if (commentIdx !== -1)
864
+ check = check.slice(0, commentIdx);
865
+ const markerIdx = check.indexOf(" ;");
866
+ if (markerIdx !== -1)
867
+ check = check.slice(0, markerIdx);
868
+ const extrasIdx = check.indexOf("[");
869
+ if (extrasIdx !== -1)
870
+ check = check.slice(0, extrasIdx);
871
+ check = check.trim();
872
+ return /\.(whl|tar\.gz|tar\.bz2|tar\.xz|zip)$/i.test(check);
873
+ }
874
+ function parseWheelFilename(filename) {
875
+ if (!filename.endsWith(".whl"))
876
+ return null;
877
+ const stem = filename.slice(0, -4);
878
+ const parts = stem.split("-");
879
+ if (parts.length < 5 || parts.length > 6)
880
+ return null;
881
+ const name = parts[0];
882
+ const version = parts[1];
883
+ if (!name || !version)
884
+ return null;
885
+ return {
886
+ name: name.replace(/_/g, "-"),
887
+ version
888
+ };
889
+ }
890
+ function parseSdistFilename(filename) {
891
+ let stem = filename;
892
+ for (const ext of [".tar.gz", ".tar.bz2", ".tar.xz", ".zip"]) {
893
+ if (stem.endsWith(ext)) {
894
+ stem = stem.slice(0, -ext.length);
895
+ break;
896
+ }
897
+ }
898
+ if (stem === filename)
899
+ return null;
900
+ const parts = stem.split("-");
901
+ let versionIdx = -1;
902
+ for (let i = 1; i < parts.length; i++) {
903
+ if (/^\d/.test(parts[i])) {
904
+ versionIdx = i;
905
+ break;
906
+ }
907
+ }
908
+ if (versionIdx === -1)
909
+ return null;
910
+ return {
911
+ name: parts.slice(0, versionIdx).join("-"),
912
+ version: parts.slice(versionIdx).join("-")
913
+ };
914
+ }
915
+ function normalizePathRequirement(rawLine) {
916
+ let line = rawLine;
917
+ const commentIdx = line.indexOf(" #");
918
+ if (commentIdx !== -1) {
919
+ line = line.slice(0, commentIdx).trim();
920
+ }
921
+ let markers;
922
+ const markerIdx = line.indexOf(" ;");
923
+ if (markerIdx !== -1) {
924
+ markers = line.slice(markerIdx + 2).trim();
925
+ line = line.slice(0, markerIdx).trim();
926
+ }
927
+ let extras;
928
+ const extrasMatch = line.match(/\[([^\]]+)\]$/);
929
+ if (extrasMatch) {
930
+ extras = extrasMatch[1].split(",").map((e) => e.trim());
931
+ line = line.slice(0, extrasMatch.index).trim();
932
+ }
933
+ const isUrl = /^(https?|ftp|file):\/\//i.test(line);
934
+ let filename;
935
+ if (isUrl) {
936
+ try {
937
+ const url = new URL(line);
938
+ filename = url.pathname.split("/").pop() || "";
939
+ } catch {
940
+ return null;
941
+ }
942
+ } else {
943
+ const cleanPath = line.replace(/\/+$/, "");
944
+ filename = cleanPath.split("/").pop() || "";
945
+ }
946
+ if (!filename)
947
+ return null;
948
+ let name;
949
+ let version;
950
+ const wheelParsed = parseWheelFilename(filename);
951
+ if (wheelParsed) {
952
+ name = wheelParsed.name;
953
+ version = wheelParsed.version;
954
+ }
955
+ if (!name) {
956
+ const sdistParsed = parseSdistFilename(filename);
957
+ if (sdistParsed) {
958
+ name = sdistParsed.name;
959
+ version = sdistParsed.version;
960
+ }
961
+ }
962
+ if (!name) {
963
+ name = filename.replace(/[-_.]+/g, "-").toLowerCase();
964
+ }
965
+ if (!name)
966
+ return null;
967
+ const req = { name };
968
+ if (version) {
969
+ req.version = `==${version}`;
970
+ }
971
+ if (extras && extras.length > 0) {
972
+ req.extras = extras;
973
+ }
974
+ if (markers) {
975
+ req.markers = markers;
976
+ }
977
+ if (isUrl) {
978
+ req.url = line;
979
+ } else {
980
+ req.source = { path: line };
981
+ }
982
+ return req;
780
983
  }
781
984
  function convertRequirementsToPyprojectToml(fileContent, readFile3) {
782
985
  const pyproject = {};
@@ -795,6 +998,8 @@ function convertRequirementsToPyprojectToml(fileContent, readFile3) {
795
998
  }
796
999
  if (deps.length > 0) {
797
1000
  pyproject.project = {
1001
+ name: "app",
1002
+ version: "0.1.0",
798
1003
  dependencies: deps
799
1004
  };
800
1005
  }
@@ -826,6 +1031,15 @@ function buildIndexEntries(pipOptions) {
826
1031
  url: pipOptions.extraIndexUrls[i]
827
1032
  });
828
1033
  }
1034
+ if (pipOptions.findLinks) {
1035
+ for (let i = 0; i < pipOptions.findLinks.length; i++) {
1036
+ indexes.push({
1037
+ name: `${FIND_LINKS_PREFIX}${i + 1}`,
1038
+ url: pipOptions.findLinks[i],
1039
+ format: "flat"
1040
+ });
1041
+ }
1042
+ }
829
1043
  return indexes;
830
1044
  }
831
1045
  function parseRequirementsFile(fileContent, readFile3) {
@@ -833,7 +1047,7 @@ function parseRequirementsFile(fileContent, readFile3) {
833
1047
  return parseRequirementsFileInternal(fileContent, readFile3, visited);
834
1048
  }
835
1049
  function parseRequirementsFileInternal(fileContent, readFile3, visited) {
836
- const { cleanedContent, options } = extractPipArguments(fileContent);
1050
+ const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
837
1051
  const hashMap = buildHashMap(fileContent);
838
1052
  const requirements = parsePipRequirementsFile(cleanedContent);
839
1053
  const normalized = [];
@@ -841,7 +1055,9 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
841
1055
  requirementFiles: [...options.requirementFiles],
842
1056
  constraintFiles: [...options.constraintFiles],
843
1057
  indexUrl: options.indexUrl,
844
- extraIndexUrls: [...options.extraIndexUrls]
1058
+ extraIndexUrls: [...options.extraIndexUrls],
1059
+ findLinks: options.findLinks ? [...options.findLinks] : void 0,
1060
+ noIndex: options.noIndex
845
1061
  };
846
1062
  for (const req of requirements) {
847
1063
  if (req.type === "RequirementsFile") {
@@ -861,6 +1077,23 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
861
1077
  normalized.push(norm);
862
1078
  }
863
1079
  }
1080
+ for (const rawPath of pathRequirements) {
1081
+ const norm = normalizePathRequirement(rawPath);
1082
+ if (norm != null) {
1083
+ normalized.push(norm);
1084
+ }
1085
+ }
1086
+ for (const rawPath of editableRequirements) {
1087
+ const norm = normalizePathRequirement(rawPath);
1088
+ if (norm != null) {
1089
+ if (norm.source) {
1090
+ norm.source.editable = true;
1091
+ } else {
1092
+ norm.source = { path: rawPath, editable: true };
1093
+ }
1094
+ normalized.push(norm);
1095
+ }
1096
+ }
864
1097
  if (readFile3) {
865
1098
  for (const refPath of mergedOptions.requirementFiles) {
866
1099
  const refPathKey = normalize(refPath);
@@ -897,6 +1130,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
897
1130
  mergedOptions.constraintFiles.push(constraintPath);
898
1131
  }
899
1132
  }
1133
+ if (refParsed.pipOptions.findLinks) {
1134
+ if (!mergedOptions.findLinks)
1135
+ mergedOptions.findLinks = [];
1136
+ for (const fl of refParsed.pipOptions.findLinks) {
1137
+ if (!mergedOptions.findLinks.includes(fl)) {
1138
+ mergedOptions.findLinks.push(fl);
1139
+ }
1140
+ }
1141
+ }
1142
+ if (refParsed.pipOptions.noIndex) {
1143
+ mergedOptions.noIndex = true;
1144
+ }
900
1145
  }
901
1146
  }
902
1147
  }
@@ -1385,6 +1630,11 @@ var PythonManifestKind = /* @__PURE__ */ ((PythonManifestKind2) => {
1385
1630
  PythonManifestKind2["PyProjectToml"] = "pyproject.toml";
1386
1631
  return PythonManifestKind2;
1387
1632
  })(PythonManifestKind || {});
1633
+ var PythonLockFileKind = /* @__PURE__ */ ((PythonLockFileKind2) => {
1634
+ PythonLockFileKind2["UvLock"] = "uv.lock";
1635
+ PythonLockFileKind2["PylockToml"] = "pylock.toml";
1636
+ return PythonLockFileKind2;
1637
+ })(PythonLockFileKind || {});
1388
1638
  var PythonManifestConvertedKind = /* @__PURE__ */ ((PythonManifestConvertedKind2) => {
1389
1639
  PythonManifestConvertedKind2["Pipfile"] = "Pipfile";
1390
1640
  PythonManifestConvertedKind2["PipfileLock"] = "Pipfile.lock";
@@ -1426,6 +1676,7 @@ async function discoverPythonPackage({
1426
1676
  }
1427
1677
  let entrypointManifest;
1428
1678
  let workspaceManifest;
1679
+ let workspaceLockFile;
1429
1680
  if (manifests.length === 0) {
1430
1681
  return {
1431
1682
  configs
@@ -1437,6 +1688,7 @@ async function discoverPythonPackage({
1437
1688
  manifests
1438
1689
  );
1439
1690
  workspaceManifest = entrypointWorkspaceManifest;
1691
+ workspaceLockFile = entrypointWorkspaceManifest.lockFile;
1440
1692
  configs = configs.filter(
1441
1693
  (config) => Object.values(config).some(
1442
1694
  (cfg) => cfg !== void 0 && isSubpath(
@@ -1454,6 +1706,7 @@ async function discoverPythonPackage({
1454
1706
  return {
1455
1707
  manifest: entrypointManifest,
1456
1708
  workspaceManifest,
1709
+ workspaceLockFile,
1457
1710
  configs,
1458
1711
  requiresPython
1459
1712
  };
@@ -1568,6 +1821,21 @@ async function loadPythonManifest(root, prefix) {
1568
1821
  }
1569
1822
  return manifest;
1570
1823
  }
1824
+ async function maybeLoadLockFile(root, subdir) {
1825
+ const uvLockRelPath = path3.join(subdir, "uv.lock");
1826
+ const uvLockPath = path3.join(root, uvLockRelPath);
1827
+ const uvLockContent = await readFileTextIfExists(uvLockPath);
1828
+ if (uvLockContent != null) {
1829
+ return { path: uvLockRelPath, kind: "uv.lock" /* UvLock */ };
1830
+ }
1831
+ const pylockRelPath = path3.join(subdir, "pylock.toml");
1832
+ const pylockPath = path3.join(root, pylockRelPath);
1833
+ const pylockContent = await readFileTextIfExists(pylockPath);
1834
+ if (pylockContent != null) {
1835
+ return { path: pylockRelPath, kind: "pylock.toml" /* PylockToml */ };
1836
+ }
1837
+ return void 0;
1838
+ }
1571
1839
  async function maybeLoadPyProjectToml(root, subdir) {
1572
1840
  const pyprojectTomlRelPath = path3.join(subdir, "pyproject.toml");
1573
1841
  const pyprojectTomlPath = path3.join(root, pyprojectTomlRelPath);
@@ -1614,9 +1882,11 @@ async function maybeLoadPyProjectToml(root, subdir) {
1614
1882
  pyproject.tool.uv = uvToml;
1615
1883
  }
1616
1884
  }
1885
+ const lockFile = await maybeLoadLockFile(root, subdir);
1617
1886
  return {
1618
1887
  path: pyprojectTomlRelPath,
1619
- data: pyproject
1888
+ data: pyproject,
1889
+ lockFile
1620
1890
  };
1621
1891
  }
1622
1892
  async function maybeLoadPipfile(root, subdir) {
@@ -1745,6 +2015,29 @@ async function maybeLoadPythonRequest(root, subdir) {
1745
2015
  };
1746
2016
  }
1747
2017
 
2018
+ // src/manifest/serialize.ts
2019
+ import toml2 from "smol-toml";
2020
+ function stringifyManifest(data) {
2021
+ return toml2.stringify(data);
2022
+ }
2023
+ function createMinimalManifest(options = {}) {
2024
+ const {
2025
+ name = "app",
2026
+ version = "0.1.0",
2027
+ requiresPython,
2028
+ dependencies = []
2029
+ } = options;
2030
+ return {
2031
+ project: {
2032
+ name,
2033
+ version,
2034
+ ...requiresPython && { "requires-python": requiresPython },
2035
+ dependencies,
2036
+ classifiers: ["Private :: Do Not Upload"]
2037
+ }
2038
+ };
2039
+ }
2040
+
1748
2041
  // src/manifest/python-selector.ts
1749
2042
  function selectPython(constraints, available) {
1750
2043
  const warnings = [];
@@ -1917,6 +2210,7 @@ export {
1917
2210
  PythonBuild,
1918
2211
  PythonConfigKind,
1919
2212
  PythonImplementation,
2213
+ PythonLockFileKind,
1920
2214
  PythonManifestConvertedKind,
1921
2215
  PythonManifestKind,
1922
2216
  PythonVariant,
@@ -1927,6 +2221,8 @@ export {
1927
2221
  UvConfigWorkspaceSchema,
1928
2222
  UvIndexEntrySchema,
1929
2223
  containsAppOrHandler,
2224
+ createMinimalManifest,
1930
2225
  discoverPythonPackage,
1931
- selectPython
2226
+ selectPython,
2227
+ stringifyManifest
1932
2228
  };
@@ -20,6 +20,26 @@ export declare enum PythonManifestKind {
20
20
  /** Standard `pyproject.toml` file (PEP 517/518/621). */
21
21
  PyProjectToml = "pyproject.toml"
22
22
  }
23
+ /**
24
+ * Kinds of Python lock files.
25
+ *
26
+ * Lock files pin exact dependency versions for reproducible installs.
27
+ */
28
+ export declare enum PythonLockFileKind {
29
+ /** uv's lock file format. */
30
+ UvLock = "uv.lock",
31
+ /** PEP 751 standard lock file format. */
32
+ PylockToml = "pylock.toml"
33
+ }
34
+ /**
35
+ * Information about a detected lock file.
36
+ */
37
+ export interface PythonLockFile {
38
+ /** Relative path to the lock file. */
39
+ path: RelPath;
40
+ /** The lock file format. */
41
+ kind: PythonLockFileKind;
42
+ }
23
43
  /**
24
44
  * Kinds of Python package manifests that are converted to pyproject.toml format.
25
45
  *
@@ -63,6 +83,8 @@ export interface PythonManifest {
63
83
  origin?: PythonManifestOrigin;
64
84
  /** Whether this manifest represents a workspace root. */
65
85
  isRoot?: boolean;
86
+ /** Lock file associated with this manifest, if one exists. */
87
+ lockFile?: PythonLockFile;
66
88
  }
67
89
  /**
68
90
  * Configuration from a `.python-version` file.
@@ -130,6 +152,8 @@ export interface PythonPackage {
130
152
  requiresPython?: PythonConstraint[];
131
153
  /** The workspace root manifest, if this package is part of a workspace. */
132
154
  workspaceManifest?: PythonManifest;
155
+ /** Lock file at the workspace root, if one exists. */
156
+ workspaceLockFile?: PythonLockFile;
133
157
  }
134
158
  /**
135
159
  * Discover Python package information starting from an entrypoint directory.
@@ -219,16 +219,19 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
219
219
  url: z.ZodString;
220
220
  default: z.ZodOptional<z.ZodBoolean>;
221
221
  explicit: z.ZodOptional<z.ZodBoolean>;
222
+ format: z.ZodOptional<z.ZodString>;
222
223
  }, "strip", z.ZodTypeAny, {
223
224
  name: string;
224
225
  url: string;
225
226
  default?: boolean | undefined;
226
227
  explicit?: boolean | undefined;
228
+ format?: string | undefined;
227
229
  }, {
228
230
  name: string;
229
231
  url: string;
230
232
  default?: boolean | undefined;
231
233
  explicit?: boolean | undefined;
234
+ format?: string | undefined;
232
235
  }>, "many">>;
233
236
  workspace: z.ZodOptional<z.ZodObject<{
234
237
  members: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -259,6 +262,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
259
262
  url: string;
260
263
  default?: boolean | undefined;
261
264
  explicit?: boolean | undefined;
265
+ format?: string | undefined;
262
266
  }[] | undefined;
263
267
  workspace?: {
264
268
  members?: string[] | undefined;
@@ -283,6 +287,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
283
287
  url: string;
284
288
  default?: boolean | undefined;
285
289
  explicit?: boolean | undefined;
290
+ format?: string | undefined;
286
291
  }[] | undefined;
287
292
  workspace?: {
288
293
  members?: string[] | undefined;
@@ -309,6 +314,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
309
314
  url: string;
310
315
  default?: boolean | undefined;
311
316
  explicit?: boolean | undefined;
317
+ format?: string | undefined;
312
318
  }[] | undefined;
313
319
  workspace?: {
314
320
  members?: string[] | undefined;
@@ -335,6 +341,7 @@ export declare const pyProjectToolSectionSchema: z.ZodObject<{
335
341
  url: string;
336
342
  default?: boolean | undefined;
337
343
  explicit?: boolean | undefined;
344
+ format?: string | undefined;
338
345
  }[] | undefined;
339
346
  workspace?: {
340
347
  members?: string[] | undefined;
@@ -513,16 +520,19 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
513
520
  url: z.ZodString;
514
521
  default: z.ZodOptional<z.ZodBoolean>;
515
522
  explicit: z.ZodOptional<z.ZodBoolean>;
523
+ format: z.ZodOptional<z.ZodString>;
516
524
  }, "strip", z.ZodTypeAny, {
517
525
  name: string;
518
526
  url: string;
519
527
  default?: boolean | undefined;
520
528
  explicit?: boolean | undefined;
529
+ format?: string | undefined;
521
530
  }, {
522
531
  name: string;
523
532
  url: string;
524
533
  default?: boolean | undefined;
525
534
  explicit?: boolean | undefined;
535
+ format?: string | undefined;
526
536
  }>, "many">>;
527
537
  workspace: z.ZodOptional<z.ZodObject<{
528
538
  members: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -553,6 +563,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
553
563
  url: string;
554
564
  default?: boolean | undefined;
555
565
  explicit?: boolean | undefined;
566
+ format?: string | undefined;
556
567
  }[] | undefined;
557
568
  workspace?: {
558
569
  members?: string[] | undefined;
@@ -577,6 +588,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
577
588
  url: string;
578
589
  default?: boolean | undefined;
579
590
  explicit?: boolean | undefined;
591
+ format?: string | undefined;
580
592
  }[] | undefined;
581
593
  workspace?: {
582
594
  members?: string[] | undefined;
@@ -603,6 +615,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
603
615
  url: string;
604
616
  default?: boolean | undefined;
605
617
  explicit?: boolean | undefined;
618
+ format?: string | undefined;
606
619
  }[] | undefined;
607
620
  workspace?: {
608
621
  members?: string[] | undefined;
@@ -629,6 +642,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
629
642
  url: string;
630
643
  default?: boolean | undefined;
631
644
  explicit?: boolean | undefined;
645
+ format?: string | undefined;
632
646
  }[] | undefined;
633
647
  workspace?: {
634
648
  members?: string[] | undefined;
@@ -693,6 +707,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
693
707
  url: string;
694
708
  default?: boolean | undefined;
695
709
  explicit?: boolean | undefined;
710
+ format?: string | undefined;
696
711
  }[] | undefined;
697
712
  workspace?: {
698
713
  members?: string[] | undefined;
@@ -757,6 +772,7 @@ export declare const pyProjectTomlSchema: z.ZodObject<{
757
772
  url: string;
758
773
  default?: boolean | undefined;
759
774
  explicit?: boolean | undefined;
775
+ format?: string | undefined;
760
776
  }[] | undefined;
761
777
  workspace?: {
762
778
  members?: string[] | undefined;
@@ -12,6 +12,10 @@ export interface PipOptions {
12
12
  indexUrl?: string;
13
13
  /** Extra index URLs (--extra-index-url) */
14
14
  extraIndexUrls: string[];
15
+ /** Directories/URLs for --find-links / -f (only set when present) */
16
+ findLinks?: string[];
17
+ /** Whether --no-index was specified (only set when true) */
18
+ noIndex?: boolean;
15
19
  }
16
20
  /**
17
21
  * Result of parsing a requirements file with pip options.
@@ -0,0 +1,22 @@
1
+ import type { PyProjectToml } from './pyproject/types';
2
+ /**
3
+ * Serialize a PyProjectToml to TOML string format.
4
+ */
5
+ export declare function stringifyManifest(data: PyProjectToml): string;
6
+ /**
7
+ * Options for creating a minimal pyproject.toml structure.
8
+ */
9
+ export interface CreateMinimalManifestOptions {
10
+ /** Project name (defaults to 'app'). */
11
+ name?: string;
12
+ /** Project version (defaults to '0.1.0'). */
13
+ version?: string;
14
+ /** Python version constraint (e.g., '>=3.12' or '~=3.12.0'). */
15
+ requiresPython?: string;
16
+ /** Initial dependencies. */
17
+ dependencies?: string[];
18
+ }
19
+ /**
20
+ * Create a minimal PyProjectToml structure for projects without a manifest.
21
+ */
22
+ export declare function createMinimalManifest(options?: CreateMinimalManifestOptions): PyProjectToml;
@@ -14,16 +14,19 @@ export declare const uvIndexEntrySchema: z.ZodObject<{
14
14
  url: z.ZodString;
15
15
  default: z.ZodOptional<z.ZodBoolean>;
16
16
  explicit: z.ZodOptional<z.ZodBoolean>;
17
+ format: z.ZodOptional<z.ZodString>;
17
18
  }, "strip", z.ZodTypeAny, {
18
19
  name: string;
19
20
  url: string;
20
21
  default?: boolean | undefined;
21
22
  explicit?: boolean | undefined;
23
+ format?: string | undefined;
22
24
  }, {
23
25
  name: string;
24
26
  url: string;
25
27
  default?: boolean | undefined;
26
28
  explicit?: boolean | undefined;
29
+ format?: string | undefined;
27
30
  }>;
28
31
  export declare const uvConfigSchema: z.ZodObject<{
29
32
  sources: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodObject<{
@@ -68,16 +71,19 @@ export declare const uvConfigSchema: z.ZodObject<{
68
71
  url: z.ZodString;
69
72
  default: z.ZodOptional<z.ZodBoolean>;
70
73
  explicit: z.ZodOptional<z.ZodBoolean>;
74
+ format: z.ZodOptional<z.ZodString>;
71
75
  }, "strip", z.ZodTypeAny, {
72
76
  name: string;
73
77
  url: string;
74
78
  default?: boolean | undefined;
75
79
  explicit?: boolean | undefined;
80
+ format?: string | undefined;
76
81
  }, {
77
82
  name: string;
78
83
  url: string;
79
84
  default?: boolean | undefined;
80
85
  explicit?: boolean | undefined;
86
+ format?: string | undefined;
81
87
  }>, "many">>;
82
88
  workspace: z.ZodOptional<z.ZodObject<{
83
89
  members: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -108,6 +114,7 @@ export declare const uvConfigSchema: z.ZodObject<{
108
114
  url: string;
109
115
  default?: boolean | undefined;
110
116
  explicit?: boolean | undefined;
117
+ format?: string | undefined;
111
118
  }[] | undefined;
112
119
  workspace?: {
113
120
  members?: string[] | undefined;
@@ -132,6 +139,7 @@ export declare const uvConfigSchema: z.ZodObject<{
132
139
  url: string;
133
140
  default?: boolean | undefined;
134
141
  explicit?: boolean | undefined;
142
+ format?: string | undefined;
135
143
  }[] | undefined;
136
144
  workspace?: {
137
145
  members?: string[] | undefined;
@@ -26,6 +26,8 @@ export interface UvIndexEntry {
26
26
  default?: boolean;
27
27
  /** Mark this index as explicit (must be explicitly referenced per-package) */
28
28
  explicit?: boolean;
29
+ /** Index format: omit for standard (PEP 503), or "flat" for flat indexes (--find-links) */
30
+ format?: string;
29
31
  }
30
32
  /**
31
33
  * [tool.uv] section in pyproject.toml or uv.toml.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python-analysis",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",