@vercel/python 6.49.0 → 6.51.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.
Files changed (2) hide show
  1. package/dist/index.js +841 -272
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var require_windows = __commonJS({
48
48
  "../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module2) {
49
49
  module2.exports = isexe;
50
50
  isexe.sync = sync;
51
- var fs14 = require("fs");
51
+ var fs16 = require("fs");
52
52
  function checkPathExt(path, options) {
53
53
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
54
54
  if (!pathext) {
@@ -73,12 +73,12 @@ var require_windows = __commonJS({
73
73
  return checkPathExt(path, options);
74
74
  }
75
75
  function isexe(path, options, cb) {
76
- fs14.stat(path, function(er, stat) {
76
+ fs16.stat(path, function(er, stat) {
77
77
  cb(er, er ? false : checkStat(stat, path, options));
78
78
  });
79
79
  }
80
80
  function sync(path, options) {
81
- return checkStat(fs14.statSync(path), path, options);
81
+ return checkStat(fs16.statSync(path), path, options);
82
82
  }
83
83
  }
84
84
  });
@@ -88,14 +88,14 @@ var require_mode = __commonJS({
88
88
  "../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module2) {
89
89
  module2.exports = isexe;
90
90
  isexe.sync = sync;
91
- var fs14 = require("fs");
91
+ var fs16 = require("fs");
92
92
  function isexe(path, options, cb) {
93
- fs14.stat(path, function(er, stat) {
93
+ fs16.stat(path, function(er, stat) {
94
94
  cb(er, er ? false : checkStat(stat, options));
95
95
  });
96
96
  }
97
97
  function sync(path, options) {
98
- return checkStat(fs14.statSync(path), options);
98
+ return checkStat(fs16.statSync(path), options);
99
99
  }
100
100
  function checkStat(stat, options) {
101
101
  return stat.isFile() && checkMode(stat, options);
@@ -119,7 +119,7 @@ var require_mode = __commonJS({
119
119
  // ../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
120
120
  var require_isexe = __commonJS({
121
121
  "../../node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module2) {
122
- var fs14 = require("fs");
122
+ var fs16 = require("fs");
123
123
  var core;
124
124
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
125
125
  core = require_windows();
@@ -395,7 +395,7 @@ var require_shebang_command = __commonJS({
395
395
  var require_readShebang = __commonJS({
396
396
  "../../node_modules/.pnpm/cross-spawn@6.0.5/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module2) {
397
397
  "use strict";
398
- var fs14 = require("fs");
398
+ var fs16 = require("fs");
399
399
  var shebangCommand = require_shebang_command();
400
400
  function readShebang(command) {
401
401
  const size = 150;
@@ -408,9 +408,9 @@ var require_readShebang = __commonJS({
408
408
  }
409
409
  let fd;
410
410
  try {
411
- fd = fs14.openSync(command, "r");
412
- fs14.readSync(fd, buffer, 0, size, 0);
413
- fs14.closeSync(fd);
411
+ fd = fs16.openSync(command, "r");
412
+ fs16.readSync(fd, buffer, 0, size, 0);
413
+ fs16.closeSync(fd);
414
414
  } catch (e) {
415
415
  }
416
416
  return shebangCommand(buffer.toString());
@@ -1895,9 +1895,9 @@ var require_pump = __commonJS({
1895
1895
  "../../node_modules/.pnpm/pump@3.0.4/node_modules/pump/index.js"(exports, module2) {
1896
1896
  var once = require_once();
1897
1897
  var eos = require_end_of_stream();
1898
- var fs14;
1898
+ var fs16;
1899
1899
  try {
1900
- fs14 = require("fs");
1900
+ fs16 = require("fs");
1901
1901
  } catch (e) {
1902
1902
  }
1903
1903
  var noop = function() {
@@ -1909,9 +1909,9 @@ var require_pump = __commonJS({
1909
1909
  var isFS = function(stream) {
1910
1910
  if (!ancient)
1911
1911
  return false;
1912
- if (!fs14)
1912
+ if (!fs16)
1913
1913
  return false;
1914
- return (stream instanceof (fs14.ReadStream || noop) || stream instanceof (fs14.WriteStream || noop)) && isFn(stream.close);
1914
+ return (stream instanceof (fs16.ReadStream || noop) || stream instanceof (fs16.WriteStream || noop)) && isFn(stream.close);
1915
1915
  };
1916
1916
  var isRequest = function(stream) {
1917
1917
  return stream.setHeader && isFn(stream.abort);
@@ -2651,7 +2651,7 @@ ${stderr}${stdout}`;
2651
2651
  var require_lib = __commonJS({
2652
2652
  "../../node_modules/.pnpm/which@3.0.0/node_modules/which/lib/index.js"(exports, module2) {
2653
2653
  var isexe = require_isexe();
2654
- var { join: join15, delimiter: delimiter2, sep: sep3, posix } = require("path");
2654
+ var { join: join17, delimiter: delimiter2, sep: sep3, posix } = require("path");
2655
2655
  var isWindows = process.platform === "win32";
2656
2656
  var rSlash = new RegExp(`[${posix.sep}${sep3 === posix.sep ? "" : sep3}]`.replace(/(\\)/g, "\\$1"));
2657
2657
  var rRel = new RegExp(`^\\.${rSlash.source}`);
@@ -2680,7 +2680,7 @@ var require_lib = __commonJS({
2680
2680
  var getPathPart = (raw, cmd) => {
2681
2681
  const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw;
2682
2682
  const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : "";
2683
- return prefix + join15(pathPart, cmd);
2683
+ return prefix + join17(pathPart, cmd);
2684
2684
  };
2685
2685
  var which2 = async (cmd, opt = {}) => {
2686
2686
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
@@ -3912,27 +3912,27 @@ var require_process = __commonJS({
3912
3912
  var require_filesystem = __commonJS({
3913
3913
  "../../node_modules/.pnpm/detect-libc@2.1.2/node_modules/detect-libc/lib/filesystem.js"(exports, module2) {
3914
3914
  "use strict";
3915
- var fs14 = require("fs");
3915
+ var fs16 = require("fs");
3916
3916
  var LDD_PATH = "/usr/bin/ldd";
3917
3917
  var SELF_PATH = "/proc/self/exe";
3918
3918
  var MAX_LENGTH = 2048;
3919
3919
  var readFileSync2 = (path) => {
3920
- const fd = fs14.openSync(path, "r");
3920
+ const fd = fs16.openSync(path, "r");
3921
3921
  const buffer = Buffer.alloc(MAX_LENGTH);
3922
- const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3923
- fs14.close(fd, () => {
3922
+ const bytesRead = fs16.readSync(fd, buffer, 0, MAX_LENGTH, 0);
3923
+ fs16.close(fd, () => {
3924
3924
  });
3925
3925
  return buffer.subarray(0, bytesRead);
3926
3926
  };
3927
3927
  var readFile2 = (path) => new Promise((resolve3, reject) => {
3928
- fs14.open(path, "r", (err, fd) => {
3928
+ fs16.open(path, "r", (err, fd) => {
3929
3929
  if (err) {
3930
3930
  reject(err);
3931
3931
  } else {
3932
3932
  const buffer = Buffer.alloc(MAX_LENGTH);
3933
- fs14.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3933
+ fs16.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
3934
3934
  resolve3(buffer.subarray(0, bytesRead));
3935
- fs14.close(fd, () => {
3935
+ fs16.close(fd, () => {
3936
3936
  });
3937
3937
  });
3938
3938
  }
@@ -4361,6 +4361,8 @@ var require_is_port_reachable = __commonJS({
4361
4361
  // src/index.ts
4362
4362
  var src_exports = {};
4363
4363
  __export(src_exports, {
4364
+ addCollectedVendorBytecode: () => addCollectedVendorBytecode,
4365
+ addVendorBytecodeInTiers: () => addVendorBytecodeInTiers,
4364
4366
  build: () => build,
4365
4367
  defaultShouldServe: () => defaultShouldServe,
4366
4368
  detectEntrypoint: () => detectEntrypoint,
@@ -4377,15 +4379,15 @@ __export(src_exports, {
4377
4379
  });
4378
4380
  module.exports = __toCommonJS(src_exports);
4379
4381
  var import_assert = __toESM(require("assert"));
4380
- var import_fs15 = __toESM(require("fs"));
4381
- var import_path15 = require("path");
4382
+ var import_fs17 = __toESM(require("fs"));
4383
+ var import_path17 = require("path");
4382
4384
 
4383
4385
  // src/package-versions.ts
4384
4386
  var VERCEL_RUNTIME_VERSION = "0.16.0";
4385
4387
  var VERCEL_WORKERS_VERSION = "0.0.25";
4386
4388
 
4387
4389
  // src/index.ts
4388
- var import_build_utils19 = require("@vercel/build-utils");
4390
+ var import_build_utils20 = require("@vercel/build-utils");
4389
4391
 
4390
4392
  // src/install.ts
4391
4393
  var import_execa3 = __toESM(require_execa());
@@ -5651,6 +5653,8 @@ var import_build_utils6 = require("@vercel/build-utils");
5651
5653
  var import_fs5 = __toESM(require("fs"));
5652
5654
  var import_path6 = require("path");
5653
5655
  var COMPILEALL_TIMEOUT_MS = 5 * 60 * 1e3;
5656
+ var PYCACHE_PREFIX_DIR = "_vc_pycache";
5657
+ var RUNTIME_PYCACHE_PREFIX = `/var/task/${PYCACHE_PREFIX_DIR}`;
5654
5658
  function isCompileAllFlagEnabled() {
5655
5659
  const val = process.env.VERCEL_PYTHON_COMPILEALL;
5656
5660
  if (val === void 0 || val === "")
@@ -5675,7 +5679,8 @@ async function runCompileAll({
5675
5679
  pythonBin,
5676
5680
  filesOrDirectories,
5677
5681
  env,
5678
- excludeRegex
5682
+ excludeRegex,
5683
+ pycachePrefix
5679
5684
  }) {
5680
5685
  if (filesOrDirectories.length === 0)
5681
5686
  return;
@@ -5691,9 +5696,11 @@ async function runCompileAll({
5691
5696
  ...excludeRegex ? ["-x", excludeRegex] : [],
5692
5697
  ...filesOrDirectories
5693
5698
  ];
5699
+ const baseEnv = env || process.env;
5700
+ const subprocessEnv = pycachePrefix ? { ...baseEnv, PYTHONPYCACHEPREFIX: pycachePrefix } : baseEnv;
5694
5701
  try {
5695
5702
  await (0, import_execa4.default)(pythonBin, args, {
5696
- env: env || process.env,
5703
+ env: subprocessEnv,
5697
5704
  timeout: COMPILEALL_TIMEOUT_MS
5698
5705
  });
5699
5706
  } catch (err) {
@@ -5708,6 +5715,42 @@ function derivePycPath(pyRelPath, pythonMajor, pythonMinor) {
5708
5715
  const baseName = pyRelPath.slice(lastSlash + 1, -3);
5709
5716
  return `${dir}__pycache__/${baseName}.cpython-${pythonMajor}${pythonMinor}.pyc`;
5710
5717
  }
5718
+ function derivePrefixPycRelPath(pyRelPath, pythonMajor, pythonMinor) {
5719
+ if (!pyRelPath.endsWith(".py"))
5720
+ return null;
5721
+ return `${pyRelPath.slice(0, -3)}.cpython-${pythonMajor}${pythonMinor}.pyc`;
5722
+ }
5723
+ function stripPycachePrefixRoot(head) {
5724
+ const pycachePathSeparators = /* @__PURE__ */ new Set(["/", "\\"]);
5725
+ if (head.length > 1 && head[1] === ":" && !pycachePathSeparators.has(head[0])) {
5726
+ head = head.slice(2);
5727
+ }
5728
+ let start = 0;
5729
+ while (start < head.length && pycachePathSeparators.has(head[start])) {
5730
+ start++;
5731
+ }
5732
+ return head.slice(start);
5733
+ }
5734
+ function deriveStagedPycFsPath(stagingDir, srcAbsPath, pythonMajor, pythonMinor) {
5735
+ const rel = derivePrefixPycRelPath(
5736
+ stripPycachePrefixRoot(srcAbsPath),
5737
+ pythonMajor,
5738
+ pythonMinor
5739
+ );
5740
+ if (!rel)
5741
+ return null;
5742
+ return (0, import_path6.join)(stagingDir, rel.replaceAll("/", import_path6.sep));
5743
+ }
5744
+ function derivePrefixPycBundlePath(runtimeAbsPath, pythonMajor, pythonMinor) {
5745
+ const rel = derivePrefixPycRelPath(
5746
+ stripPycachePrefixRoot(runtimeAbsPath),
5747
+ pythonMajor,
5748
+ pythonMinor
5749
+ );
5750
+ if (!rel)
5751
+ return null;
5752
+ return `${PYCACHE_PREFIX_DIR}/${rel}`;
5753
+ }
5711
5754
  var COMPILEALL_APP_EXCLUDED_DIRS = [
5712
5755
  ".git",
5713
5756
  ".vercel",
@@ -5729,6 +5772,58 @@ function getCompileAllAppExcludeRegex(workPath) {
5729
5772
  const excludedDirs = COMPILEALL_APP_EXCLUDED_DIRS.map(escapePythonRegex).join("|");
5730
5773
  return `${escapePythonRegex(workPath)}[/\\\\](?:${excludedDirs})(?:[/\\\\]|$)`;
5731
5774
  }
5775
+ async function collectAppPrefixBytecodeFiles({
5776
+ stagingDir,
5777
+ workPath,
5778
+ files: appFiles,
5779
+ runtimeTaskRoot,
5780
+ pythonMajor,
5781
+ pythonMinor
5782
+ }) {
5783
+ const pending = [];
5784
+ for (const bundlePath of Object.keys(appFiles)) {
5785
+ if (!bundlePath.endsWith(".py"))
5786
+ continue;
5787
+ const stagedFsPath = deriveStagedPycFsPath(
5788
+ stagingDir,
5789
+ (0, import_path6.join)(workPath, bundlePath.replaceAll("/", import_path6.sep)),
5790
+ pythonMajor,
5791
+ pythonMinor
5792
+ );
5793
+ const pycBundlePath = derivePrefixPycBundlePath(
5794
+ `${runtimeTaskRoot}/${bundlePath}`,
5795
+ pythonMajor,
5796
+ pythonMinor
5797
+ );
5798
+ if (!stagedFsPath || !pycBundlePath)
5799
+ continue;
5800
+ pending.push({ bundlePath: pycBundlePath, srcFsPath: stagedFsPath });
5801
+ }
5802
+ const results = await Promise.all(
5803
+ pending.map(async ({ bundlePath, srcFsPath }) => {
5804
+ try {
5805
+ const stats = await import_fs5.default.promises.stat(srcFsPath);
5806
+ return { bundlePath, srcFsPath, size: stats.size };
5807
+ } catch {
5808
+ return null;
5809
+ }
5810
+ })
5811
+ );
5812
+ const files = {};
5813
+ const perItemSizes = /* @__PURE__ */ new Map();
5814
+ let totalSize = 0;
5815
+ for (const result of results) {
5816
+ if (!result)
5817
+ continue;
5818
+ files[result.bundlePath] = new import_build_utils6.FileFsRef({
5819
+ fsPath: result.srcFsPath,
5820
+ size: result.size
5821
+ });
5822
+ perItemSizes.set(result.bundlePath, result.size);
5823
+ totalSize += result.size;
5824
+ }
5825
+ return { files, totalSize, perItemSizes };
5826
+ }
5732
5827
  async function collectAppBytecodeFiles({
5733
5828
  workPath,
5734
5829
  files: appFiles,
@@ -5800,7 +5895,10 @@ var LAMBDA_SIZE_THRESHOLD_BYTES = 225 * 1024 * 1024;
5800
5895
  var BYTECODE_FILL_MARGIN_BYTES = 5 * 1024 * 1024;
5801
5896
  var BYTECODE_FILL_CEILING_BYTES = LAMBDA_SIZE_THRESHOLD_BYTES - BYTECODE_FILL_MARGIN_BYTES;
5802
5897
  var LAMBDA_EPHEMERAL_STORAGE_BYTES = 500 * 1024 * 1024;
5898
+ var EPHEMERAL_INSTALL_BUDGET_BYTES = LAMBDA_EPHEMERAL_STORAGE_BYTES - 50 * 1024 * 1024;
5899
+ var RUNTIME_DEPS_DIR = "/tmp/_vc_deps";
5803
5900
  var MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE = 5 * 1024 * 1024 * 1024;
5901
+ var LARGE_FUNCTION_FILL_CEILING_BYTES = MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE - BYTECODE_FILL_MARGIN_BYTES;
5804
5902
  var BUNDLING_DOCS_LINK = "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled";
5805
5903
  var PythonDependencyExternalizer = class {
5806
5904
  constructor(options) {
@@ -5910,7 +6008,7 @@ application into smaller functions.`,
5910
6008
  * back to bundling everything for Hive and returns `fellBackToFullBundle:
5911
6009
  * true` so the caller can apply the same compileall handling.
5912
6010
  */
5913
- async generateBundle(files) {
6011
+ async generateBundle(files, options = {}) {
5914
6012
  if (!this.analyzed) {
5915
6013
  throw new Error(
5916
6014
  "PythonDependencyExternalizer.analyze() must be called before generateBundle()"
@@ -6071,7 +6169,20 @@ To fix this, either:
6071
6169
  ([name]) => externalizableSet.has((0, import_python_analysis3.normalizePackageName)(name))
6072
6170
  )
6073
6171
  );
6074
- const bundledPublic = lambdaKnapsack(publicPackageSizes, remainingCapacity);
6172
+ const { packingMode, bundledPublic } = planPublicPackagePacking({
6173
+ publicPackageSizes,
6174
+ zipCapacity: remainingCapacity,
6175
+ bytecodeFirst: options.bytecodeFirst ?? false
6176
+ });
6177
+ if (options.bytecodeFirst && packingMode === "knapsack") {
6178
+ (0, import_build_utils7.debug)(
6179
+ `bytecode-first packing disabled: the public package set exceeds the ephemeral install budget (${(EPHEMERAL_INSTALL_BUDGET_BYTES / (1024 * 1024)).toFixed(2)} MB); falling back to knapsack packing`
6180
+ );
6181
+ }
6182
+ const bundledPublicSet = new Set(bundledPublic.map(import_python_analysis3.normalizePackageName));
6183
+ const externalizedPublic = externalizablePublic.filter(
6184
+ (name) => !bundledPublicSet.has((0, import_python_analysis3.normalizePackageName)(name))
6185
+ );
6075
6186
  const allBundledPackages = [...alwaysBundled, ...bundledPublic];
6076
6187
  const selectedVendorFiles = await this.mirrorPackagesIntoVendor({
6077
6188
  vendorDirName: this.vendorDir,
@@ -6143,7 +6254,13 @@ functions.`,
6143
6254
  action: "Learn More"
6144
6255
  });
6145
6256
  }
6146
- return { fellBackToFullBundle: false };
6257
+ return {
6258
+ fellBackToFullBundle: false,
6259
+ packingMode,
6260
+ alwaysBundledPackages: alwaysBundled,
6261
+ bundledPublicPackages: bundledPublic,
6262
+ externalizedPublicPackages: externalizedPublic
6263
+ };
6147
6264
  }
6148
6265
  /**
6149
6266
  * Discard any runtime-install artifacts added so far and bundle every
@@ -6456,6 +6573,92 @@ functions.`,
6456
6573
  );
6457
6574
  return { files, totalSize, perItemSizes };
6458
6575
  }
6576
+ /**
6577
+ * Collect staged prefix `.pyc` files for vendor packages, keyed under
6578
+ * `_vc_pycache/<runtimeRoot>/...` (`runtimeRoot` = `/var/task/_vendor`
6579
+ * for bundled packages, the /tmp site-packages dir for externalized
6580
+ * ones). Missing staged files are silently skipped. Must be called after
6581
+ * `analyze()`.
6582
+ */
6583
+ async collectPrefixBytecodeFiles({
6584
+ stagingDir,
6585
+ runtimeRoot,
6586
+ includePackages
6587
+ }) {
6588
+ if (!this.sitePackageDirs || !this.distributions) {
6589
+ throw new Error(
6590
+ "collectPrefixBytecodeFiles() must be called after analyze()"
6591
+ );
6592
+ }
6593
+ if (this.pythonMajor == null || this.pythonMinor == null) {
6594
+ return { files: {}, totalSize: 0, perItemSizes: /* @__PURE__ */ new Map() };
6595
+ }
6596
+ const includeSet = includePackages ? new Set(includePackages.map(import_python_analysis3.normalizePackageName)) : null;
6597
+ const pending = [];
6598
+ for (const dir of this.sitePackageDirs) {
6599
+ const dirDistributions = this.distributions.get(dir);
6600
+ if (!dirDistributions)
6601
+ continue;
6602
+ const resolvedDir = (0, import_path7.resolve)(dir);
6603
+ const dirPrefix = resolvedDir + import_path7.sep;
6604
+ for (const [name, dist] of dirDistributions) {
6605
+ if (includeSet && !includeSet.has(name))
6606
+ continue;
6607
+ for (const { path: rawPath } of dist.files) {
6608
+ if (!rawPath.endsWith(".py"))
6609
+ continue;
6610
+ const filePath = rawPath.replaceAll("/", import_path7.sep);
6611
+ if (!(0, import_path7.resolve)(resolvedDir, filePath).startsWith(dirPrefix)) {
6612
+ continue;
6613
+ }
6614
+ const srcFsPath = deriveStagedPycFsPath(
6615
+ stagingDir,
6616
+ (0, import_path7.join)(dir, filePath),
6617
+ this.pythonMajor,
6618
+ this.pythonMinor
6619
+ );
6620
+ const bundlePath = derivePrefixPycBundlePath(
6621
+ `${runtimeRoot}/${rawPath}`,
6622
+ this.pythonMajor,
6623
+ this.pythonMinor
6624
+ );
6625
+ if (!srcFsPath || !bundlePath)
6626
+ continue;
6627
+ pending.push({ bundlePath, srcFsPath, packageName: name });
6628
+ }
6629
+ }
6630
+ }
6631
+ const results = await Promise.all(
6632
+ pending.map(async ({ bundlePath, srcFsPath, packageName }) => {
6633
+ try {
6634
+ const stats = await import_fs6.default.promises.stat(srcFsPath);
6635
+ return { bundlePath, srcFsPath, size: stats.size, packageName };
6636
+ } catch {
6637
+ return null;
6638
+ }
6639
+ })
6640
+ );
6641
+ const files = {};
6642
+ let totalSize = 0;
6643
+ const perItemSizes = /* @__PURE__ */ new Map();
6644
+ for (const result of results) {
6645
+ if (!result)
6646
+ continue;
6647
+ files[result.bundlePath] = new import_build_utils7.FileFsRef({
6648
+ fsPath: result.srcFsPath,
6649
+ size: result.size
6650
+ });
6651
+ totalSize += result.size;
6652
+ perItemSizes.set(
6653
+ result.packageName,
6654
+ (perItemSizes.get(result.packageName) ?? 0) + result.size
6655
+ );
6656
+ }
6657
+ (0, import_build_utils7.debug)(
6658
+ `Collected ${Object.keys(files).length} prefix bytecode files (${(totalSize / (1024 * 1024)).toFixed(2)} MB) for runtime root ${runtimeRoot}` + (includePackages ? ` from ${includePackages.length} packages` : "")
6659
+ );
6660
+ return { files, totalSize, perItemSizes };
6661
+ }
6459
6662
  };
6460
6663
  async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor, pythonMinor, sysPlatform, platformMachine) {
6461
6664
  if (!projectName)
@@ -6551,12 +6754,6 @@ async function calculateBundleSize(files, filter) {
6551
6754
  }
6552
6755
  return totalSize;
6553
6756
  }
6554
- var PYC_TO_PY_RATIO = 1.2;
6555
- var BYTECODE_COVERAGE_FLOOR = 0.5;
6556
- async function estimateBytecodeSize(files) {
6557
- const pyBytes = await calculateBundleSize(files, (p) => p.endsWith(".py"));
6558
- return PYC_TO_PY_RATIO * pyBytes;
6559
- }
6560
6757
  function lambdaKnapsack(packages, capacity) {
6561
6758
  if (capacity <= 0) {
6562
6759
  return [];
@@ -6572,6 +6769,23 @@ function lambdaKnapsack(packages, capacity) {
6572
6769
  }
6573
6770
  return bundled;
6574
6771
  }
6772
+ function planPublicPackagePacking({
6773
+ publicPackageSizes,
6774
+ zipCapacity,
6775
+ bytecodeFirst
6776
+ }) {
6777
+ let totalPublicSize = 0;
6778
+ for (const size of publicPackageSizes.values()) {
6779
+ totalPublicSize += size;
6780
+ }
6781
+ if (bytecodeFirst && totalPublicSize <= EPHEMERAL_INSTALL_BUDGET_BYTES) {
6782
+ return { packingMode: "bytecode-first", bundledPublic: [] };
6783
+ }
6784
+ return {
6785
+ packingMode: "knapsack",
6786
+ bundledPublic: lambdaKnapsack(publicPackageSizes, zipCapacity)
6787
+ };
6788
+ }
6575
6789
 
6576
6790
  // src/diagnostics.ts
6577
6791
  var import_fs7 = __toESM(require("fs"));
@@ -7853,24 +8067,34 @@ var startDevServer = async (opts) => {
7853
8067
  installGlobalCleanupHandlers();
7854
8068
  const env = { ...process.env, ...meta.env || {} };
7855
8069
  const entrypoint = rawEntrypoint === "<detect>" ? void 0 : rawEntrypoint;
8070
+ const isPyprojectEntrypoint = entrypoint === "pyproject.toml";
7856
8071
  let resolved;
7857
8072
  const handlerFunction = typeof config?.handlerFunction === "string" ? config.handlerFunction : void 0;
7858
- const detected = await detectPythonEntrypoint(
7859
- framework,
7860
- workPath,
7861
- entrypoint ? {
7862
- filePath: entrypoint,
7863
- // Schedule-triggered services create their own "app" wrapper dynamically.
7864
- // Other services use handlerFunction as the entrypoint variable name.
7865
- varName: service && (0, import_build_utils13.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
7866
- } : void 0,
7867
- service,
7868
- opts.repoRootPath
7869
- );
8073
+ let detected;
8074
+ if (isPyprojectEntrypoint) {
8075
+ const declaredEntrypoint = await getVercelToolsEntrypoint(
8076
+ workPath,
8077
+ opts.repoRootPath
8078
+ );
8079
+ detected = declaredEntrypoint ? { entrypoint: declaredEntrypoint } : null;
8080
+ } else {
8081
+ detected = await detectPythonEntrypoint(
8082
+ framework,
8083
+ workPath,
8084
+ entrypoint ? {
8085
+ filePath: entrypoint,
8086
+ // Schedule-triggered services create their own "app" wrapper dynamically.
8087
+ // Other services use handlerFunction as the entrypoint variable name.
8088
+ varName: service && (0, import_build_utils13.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
8089
+ } : void 0,
8090
+ service,
8091
+ opts.repoRootPath
8092
+ );
8093
+ }
7870
8094
  let hookResult;
7871
8095
  if (detected?.entrypoint) {
7872
8096
  resolved = detected.entrypoint;
7873
- } else {
8097
+ } else if (!isPyprojectEntrypoint) {
7874
8098
  hookResult = await runFrameworkHook(framework, {
7875
8099
  pythonEnv: env,
7876
8100
  workPath,
@@ -7884,8 +8108,8 @@ var startDevServer = async (opts) => {
7884
8108
  throw detected.error;
7885
8109
  }
7886
8110
  throw new import_build_utils13.NowBuildError({
7887
- code: "PYTHON_ENTRYPOINT_NOT_FOUND",
7888
- message: "No Python entrypoint could be detected. Please specify an entrypoint file."
8111
+ code: isPyprojectEntrypoint ? "PYTHON_PYPROJECT_NOTHING_TO_BUILD" : "PYTHON_ENTRYPOINT_NOT_FOUND",
8112
+ message: isPyprojectEntrypoint ? 'Entrypoint "pyproject.toml" does not declare a web app. Set "tool.vercel.entrypoint" in pyproject.toml.' : "No Python entrypoint could be detected. Please specify an entrypoint file."
7889
8113
  });
7890
8114
  }
7891
8115
  const { entrypoint: entry, variableName } = resolved;
@@ -8674,10 +8898,54 @@ async function runDjangoCollectStatic(venvPath, workPath, djangoPath, env, outpu
8674
8898
  var import_python_analysis9 = require("@vercel/python-analysis");
8675
8899
 
8676
8900
  // src/subscribers.ts
8901
+ var import_path15 = require("path");
8902
+ var import_fs15 = __toESM(require("fs"));
8903
+ var import_build_utils18 = require("@vercel/build-utils");
8904
+
8905
+ // src/module-entrypoint.ts
8677
8906
  var import_path14 = require("path");
8678
8907
  var import_fs14 = __toESM(require("fs"));
8679
- var import_build_utils18 = require("@vercel/build-utils");
8680
8908
  var MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
8909
+ function parseModuleEntrypoint(value) {
8910
+ const match = MODULE_ATTR_RE.exec(value);
8911
+ if (!match) {
8912
+ return null;
8913
+ }
8914
+ return {
8915
+ moduleName: match[1],
8916
+ variableName: match[2],
8917
+ filePath: `${match[1].replace(/\./g, "/")}.py`
8918
+ };
8919
+ }
8920
+ function getModuleEntrypointName({
8921
+ moduleName,
8922
+ variableName
8923
+ }) {
8924
+ return `${moduleName.replace(/\./g, "-")}_${variableName}`;
8925
+ }
8926
+ function safePathSegment(value) {
8927
+ return [...value].map((char) => {
8928
+ if (char === "_") {
8929
+ return "__";
8930
+ }
8931
+ return /[A-Za-z0-9-]/.test(char) ? char : `_${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
8932
+ }).join("");
8933
+ }
8934
+ async function resolveExistingEntrypoint(workPath, filePath) {
8935
+ const candidates = [filePath, filePath.replace(/\.py$/i, "/__init__.py")];
8936
+ for (const candidate of candidates) {
8937
+ try {
8938
+ const stat = await import_fs14.default.promises.stat((0, import_path14.join)(workPath, candidate));
8939
+ if (stat.isFile()) {
8940
+ return candidate;
8941
+ }
8942
+ } catch {
8943
+ }
8944
+ }
8945
+ return null;
8946
+ }
8947
+
8948
+ // src/subscribers.ts
8681
8949
  var SUBSCRIBER_OUTPUT_DIR = "_py_subscribers";
8682
8950
  var TRIGGER_NUMBER_FIELDS = [
8683
8951
  {
@@ -8710,14 +8978,6 @@ var SUBSCRIBER_FIELD_NAMES = /* @__PURE__ */ new Set([
8710
8978
  "topics",
8711
8979
  ...TRIGGER_NUMBER_FIELDS.map(({ field }) => field)
8712
8980
  ]);
8713
- function safePathSegment(value) {
8714
- return [...value].map((char) => {
8715
- if (char === "_") {
8716
- return "__";
8717
- }
8718
- return /[A-Za-z0-9-]/.test(char) ? char : `_${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
8719
- }).join("");
8720
- }
8721
8981
  function getSubscriberOutputPath(subscriberName) {
8722
8982
  return `${SUBSCRIBER_OUTPUT_DIR}/${safePathSegment(subscriberName)}`;
8723
8983
  }
@@ -8725,8 +8985,8 @@ function getSubscriberConsumerName(subscriberName) {
8725
8985
  return (0, import_build_utils18.sanitizeConsumerName)(getSubscriberOutputPath(subscriberName));
8726
8986
  }
8727
8987
  async function getPyprojectSubscribers(workPath) {
8728
- const pyprojectPath = (0, import_path14.join)(workPath, "pyproject.toml");
8729
- if (!import_fs14.default.existsSync(pyprojectPath)) {
8988
+ const pyprojectPath = (0, import_path15.join)(workPath, "pyproject.toml");
8989
+ if (!import_fs15.default.existsSync(pyprojectPath)) {
8730
8990
  return [];
8731
8991
  }
8732
8992
  const pyproject = await (0, import_build_utils18.readConfigFile)(pyprojectPath);
@@ -8764,8 +9024,13 @@ async function parseSubscriber(workPath, index, config) {
8764
9024
  if (typeof config.entrypoint !== "string") {
8765
9025
  throw subscriberError(`${label} must define string field "entrypoint"`);
8766
9026
  }
8767
- const entrypoint = parseEntrypoint(label, config.entrypoint);
8768
- const name = getSubscriberName(entrypoint);
9027
+ const entrypoint = parseModuleEntrypoint(config.entrypoint);
9028
+ if (!entrypoint) {
9029
+ throw subscriberError(
9030
+ `${label} has invalid entrypoint "${config.entrypoint}". Use "module:object"`
9031
+ );
9032
+ }
9033
+ const name = getModuleEntrypointName(entrypoint);
8769
9034
  const existingEntrypoint = await resolveExistingEntrypoint(
8770
9035
  workPath,
8771
9036
  entrypoint.filePath
@@ -8784,38 +9049,6 @@ async function parseSubscriber(workPath, index, config) {
8784
9049
  triggerDefaults: parseTriggerDefaults(name, config)
8785
9050
  };
8786
9051
  }
8787
- function getSubscriberName({
8788
- moduleName,
8789
- variableName
8790
- }) {
8791
- return `${moduleName.replace(/\./g, "-")}_${variableName}`;
8792
- }
8793
- function parseEntrypoint(name, value) {
8794
- const match = MODULE_ATTR_RE.exec(value);
8795
- if (!match) {
8796
- throw subscriberError(
8797
- `subscriber "${name}" has invalid entrypoint "${value}". Use "module:object"`
8798
- );
8799
- }
8800
- return {
8801
- moduleName: match[1],
8802
- variableName: match[2],
8803
- filePath: `${match[1].replace(/\./g, "/")}.py`
8804
- };
8805
- }
8806
- async function resolveExistingEntrypoint(workPath, filePath) {
8807
- const candidates = [filePath, filePath.replace(/\.py$/i, "/__init__.py")];
8808
- for (const candidate of candidates) {
8809
- try {
8810
- const stat = await import_fs14.default.promises.stat((0, import_path14.join)(workPath, candidate));
8811
- if (stat.isFile()) {
8812
- return candidate;
8813
- }
8814
- } catch {
8815
- }
8816
- }
8817
- return null;
8818
- }
8819
9052
  function parseTopics(name, value) {
8820
9053
  if (!Array.isArray(value) || value.length === 0) {
8821
9054
  throw subscriberError(
@@ -8854,8 +9087,86 @@ function subscriberError(message) {
8854
9087
  });
8855
9088
  }
8856
9089
 
9090
+ // src/workflows.ts
9091
+ var import_path16 = require("path");
9092
+ var import_fs16 = __toESM(require("fs"));
9093
+ var import_build_utils19 = require("@vercel/build-utils");
9094
+ var WORKFLOW_OUTPUT_DIR = "_py_workflows";
9095
+ var WORKFLOW_TOPIC_PATTERN = "__wkf_*";
9096
+ var WORKFLOW_FIELD_NAMES = /* @__PURE__ */ new Set(["entrypoint"]);
9097
+ function getWorkflowOutputPath(workflowName) {
9098
+ return `${WORKFLOW_OUTPUT_DIR}/${safePathSegment(workflowName)}`;
9099
+ }
9100
+ function getWorkflowConsumerName(workflowName) {
9101
+ return (0, import_build_utils19.sanitizeConsumerName)(getWorkflowOutputPath(workflowName));
9102
+ }
9103
+ async function getPyprojectWorkflows(workPath) {
9104
+ const pyprojectPath = (0, import_path16.join)(workPath, "pyproject.toml");
9105
+ if (!import_fs16.default.existsSync(pyprojectPath)) {
9106
+ return [];
9107
+ }
9108
+ const pyproject = await (0, import_build_utils19.readConfigFile)(pyprojectPath);
9109
+ const workflows = pyproject?.tool?.vercel?.workflows;
9110
+ if (!workflows) {
9111
+ return [];
9112
+ }
9113
+ if (!Array.isArray(workflows)) {
9114
+ throw workflowError('"tool.vercel.workflows" must be an array');
9115
+ }
9116
+ if (workflows.length > 1) {
9117
+ throw workflowError(
9118
+ '"tool.vercel.workflows" must declare a single entrypoint that registers every workflow'
9119
+ );
9120
+ }
9121
+ return Promise.all(
9122
+ workflows.map((config, index) => parseWorkflow(workPath, index, config))
9123
+ );
9124
+ }
9125
+ async function parseWorkflow(workPath, index, config) {
9126
+ const label = `workflow #${index + 1}`;
9127
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
9128
+ throw workflowError(`${label} must be an object`);
9129
+ }
9130
+ for (const key of Object.keys(config)) {
9131
+ if (!WORKFLOW_FIELD_NAMES.has(key)) {
9132
+ throw workflowError(`${label} has unrecognized field "${key}"`);
9133
+ }
9134
+ }
9135
+ if (typeof config.entrypoint !== "string") {
9136
+ throw workflowError(`${label} must define string field "entrypoint"`);
9137
+ }
9138
+ const entrypoint = parseModuleEntrypoint(config.entrypoint);
9139
+ if (!entrypoint) {
9140
+ throw workflowError(
9141
+ `${label} has invalid entrypoint "${config.entrypoint}". Use "module:object"`
9142
+ );
9143
+ }
9144
+ const name = getModuleEntrypointName(entrypoint);
9145
+ const existingEntrypoint = await resolveExistingEntrypoint(
9146
+ workPath,
9147
+ entrypoint.filePath
9148
+ );
9149
+ if (!existingEntrypoint) {
9150
+ throw workflowError(
9151
+ `workflow "${name}" has entrypoint "${config.entrypoint}" but file "${entrypoint.filePath}" does not exist`
9152
+ );
9153
+ }
9154
+ return {
9155
+ name,
9156
+ entrypoint: existingEntrypoint,
9157
+ moduleName: entrypoint.moduleName,
9158
+ variableName: entrypoint.variableName
9159
+ };
9160
+ }
9161
+ function workflowError(message) {
9162
+ return new import_build_utils19.NowBuildError({
9163
+ code: "PYTHON_INVALID_WORKFLOW_CONFIG",
9164
+ message
9165
+ });
9166
+ }
9167
+
8857
9168
  // src/index.ts
8858
- var writeFile = import_fs15.default.promises.writeFile;
9169
+ var writeFile = import_fs17.default.promises.writeFile;
8859
9170
  var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
8860
9171
  var version = -1;
8861
9172
  function getDevSubscriberTopics(subscriber) {
@@ -8868,29 +9179,55 @@ function getDevSubscriberTopics(subscriber) {
8868
9179
  }
8869
9180
  async function getDevSidecars({
8870
9181
  workPath,
8871
- build: build2
9182
+ build: build2,
9183
+ service
8872
9184
  }) {
8873
9185
  const framework = build2.config?.framework;
8874
- if (build2.config?.middleware === true || typeof framework !== "string" || !(0, import_build_utils19.isPythonFramework)(framework)) {
9186
+ const isPyprojectEntrypoint = (0, import_path17.basename)(build2.src ?? "") === "pyproject.toml";
9187
+ const isPyprojectService = service !== void 0 && (0, import_path17.basename)(service.entrypoint ?? "") === "pyproject.toml";
9188
+ if (build2.config?.middleware === true || service !== void 0 && !isPyprojectService || service === void 0 && !isPyprojectEntrypoint && (typeof framework !== "string" || !(0, import_build_utils20.isPythonFramework)(framework))) {
8875
9189
  return [];
8876
9190
  }
8877
9191
  const subscribers = await getPyprojectSubscribers(workPath);
8878
- return subscribers.map((subscriber) => ({
8879
- type: "subscriber",
8880
- name: subscriber.name,
8881
- consumer: getSubscriberConsumerName(subscriber.name),
8882
- workspace: ".",
8883
- framework,
8884
- runtime: "python",
8885
- builder: {
8886
- use: build2.use,
8887
- src: subscriber.entrypoint,
8888
- config: {
8889
- handlerFunction: subscriber.variableName
8890
- }
8891
- },
8892
- topics: getDevSubscriberTopics(subscriber)
8893
- }));
9192
+ const workflows = await getPyprojectWorkflows(workPath);
9193
+ return [
9194
+ ...subscribers.map(
9195
+ (subscriber) => ({
9196
+ type: "subscriber",
9197
+ name: subscriber.name,
9198
+ consumer: getSubscriberConsumerName(subscriber.name),
9199
+ workspace: ".",
9200
+ framework: typeof framework === "string" ? framework : void 0,
9201
+ runtime: "python",
9202
+ builder: {
9203
+ use: build2.use,
9204
+ src: subscriber.entrypoint,
9205
+ config: {
9206
+ handlerFunction: subscriber.variableName
9207
+ }
9208
+ },
9209
+ topics: getDevSubscriberTopics(subscriber)
9210
+ })
9211
+ ),
9212
+ ...workflows.map(
9213
+ (workflow) => ({
9214
+ type: "subscriber",
9215
+ name: workflow.name,
9216
+ consumer: getWorkflowConsumerName(workflow.name),
9217
+ workspace: ".",
9218
+ framework: typeof framework === "string" ? framework : void 0,
9219
+ runtime: "python",
9220
+ builder: {
9221
+ use: build2.use,
9222
+ src: workflow.entrypoint,
9223
+ config: {
9224
+ handlerFunction: workflow.variableName
9225
+ }
9226
+ },
9227
+ topics: [{ topic: WORKFLOW_TOPIC_PATTERN }]
9228
+ })
9229
+ )
9230
+ ];
8894
9231
  }
8895
9232
  function addFiles(target, source) {
8896
9233
  for (const [p, f] of Object.entries(source)) {
@@ -8940,6 +9277,54 @@ async function addVendorBytecodeWithinCapacity({
8940
9277
  addFiles(files, selectedBytecode.files);
8941
9278
  return capacity - selectedBytecode.totalSize;
8942
9279
  }
9280
+ async function addVendorBytecodeInTiers({
9281
+ files,
9282
+ depExternalizer,
9283
+ vendorDir,
9284
+ capacity,
9285
+ vendorPackageTiers
9286
+ }) {
9287
+ let remainingCapacity = capacity;
9288
+ for (const tier of vendorPackageTiers) {
9289
+ if (remainingCapacity <= 0)
9290
+ break;
9291
+ if (tier && tier.length === 0)
9292
+ continue;
9293
+ const bytecodeInfo = await depExternalizer.collectBytecodeFiles({
9294
+ vendorDirName: vendorDir,
9295
+ includePackages: tier
9296
+ });
9297
+ remainingCapacity = await addVendorBytecodeWithinCapacity({
9298
+ files,
9299
+ depExternalizer,
9300
+ vendorDir,
9301
+ bytecodeInfo,
9302
+ capacity: remainingCapacity
9303
+ });
9304
+ }
9305
+ return remainingCapacity;
9306
+ }
9307
+ async function addCollectedVendorBytecode({
9308
+ files,
9309
+ capacity,
9310
+ collect
9311
+ }) {
9312
+ if (capacity <= 0)
9313
+ return capacity;
9314
+ const info = await collect(void 0);
9315
+ if (!info || info.totalSize <= 0)
9316
+ return capacity;
9317
+ if (info.totalSize <= capacity) {
9318
+ addFiles(files, info.files);
9319
+ return capacity - info.totalSize;
9320
+ }
9321
+ const selected = lambdaKnapsack(info.perItemSizes, capacity);
9322
+ if (selected.length === 0)
9323
+ return capacity;
9324
+ const selectedInfo = await collect(selected);
9325
+ addFiles(files, selectedInfo.files);
9326
+ return capacity - selectedInfo.totalSize;
9327
+ }
8943
9328
  async function runFrameworkHook(framework, ctx) {
8944
9329
  const hook = framework ? frameworkHooks[framework] : void 0;
8945
9330
  return hook?.(ctx);
@@ -8953,13 +9338,13 @@ var frameworkHooks = {
8953
9338
  }) => {
8954
9339
  let baseDir = detected?.baseDir;
8955
9340
  if (baseDir === void 0) {
8956
- if (!import_fs15.default.existsSync((0, import_path15.join)(workPath, "manage.py"))) {
8957
- (0, import_build_utils19.debug)("Django hook: no manage.py detected, skipping");
9341
+ if (!import_fs17.default.existsSync((0, import_path17.join)(workPath, "manage.py"))) {
9342
+ (0, import_build_utils20.debug)("Django hook: no manage.py detected, skipping");
8958
9343
  return;
8959
9344
  }
8960
9345
  baseDir = "";
8961
9346
  }
8962
- const djangoPath = (0, import_path15.join)(workPath, baseDir);
9347
+ const djangoPath = (0, import_path17.join)(workPath, baseDir);
8963
9348
  let settingsResult;
8964
9349
  try {
8965
9350
  settingsResult = await getDjangoSettings(djangoPath, pythonEnv);
@@ -8971,13 +9356,13 @@ Hint: activate a venv or run with \`uv run vercel dev\``;
8971
9356
  } else {
8972
9357
  detail = err?.stderr || err?.message || String(err);
8973
9358
  }
8974
- throw new import_build_utils19.NowBuildError({
9359
+ throw new import_build_utils20.NowBuildError({
8975
9360
  code: "DJANGO_SETTINGS_FAILED",
8976
9361
  message: `Failed to read Django application settings from ${djangoPath}/manage.py:
8977
9362
  ${detail}`
8978
9363
  });
8979
9364
  }
8980
- (0, import_build_utils19.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
9365
+ (0, import_build_utils20.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
8981
9366
  const { djangoSettings, settingsModule, djangoVersion } = settingsResult;
8982
9367
  if (djangoVersion) {
8983
9368
  console.log(`Django ${djangoVersion.join(".")} detected`);
@@ -8989,7 +9374,7 @@ ${detail}`
8989
9374
  const variableName = parts.at(-1);
8990
9375
  const rel = `${parts.slice(0, -1).join("/")}.py`;
8991
9376
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
8992
- (0, import_build_utils19.debug)(`Django hook: ASGI entrypoint: ${ep} (variable: ${variableName})`);
9377
+ (0, import_build_utils20.debug)(`Django hook: ASGI entrypoint: ${ep} (variable: ${variableName})`);
8993
9378
  resolvedEntrypoint = { entrypoint: ep, variableName };
8994
9379
  } else {
8995
9380
  const wsgiApp = djangoSettings["WSGI_APPLICATION"];
@@ -8998,7 +9383,7 @@ ${detail}`
8998
9383
  const variableName = parts.at(-1);
8999
9384
  const rel = `${parts.slice(0, -1).join("/")}.py`;
9000
9385
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
9001
- (0, import_build_utils19.debug)(
9386
+ (0, import_build_utils20.debug)(
9002
9387
  `Django hook: WSGI entrypoint: ${ep} (variable: ${variableName})`
9003
9388
  );
9004
9389
  resolvedEntrypoint = { entrypoint: ep, variableName };
@@ -9006,7 +9391,7 @@ ${detail}`
9006
9391
  }
9007
9392
  let djangoStatic = null;
9008
9393
  if (workPath && venvPath) {
9009
- const outputStaticDir = (0, import_path15.join)(workPath, ".vercel", "output", "static");
9394
+ const outputStaticDir = (0, import_path17.join)(workPath, ".vercel", "output", "static");
9010
9395
  djangoStatic = await runDjangoCollectStatic(
9011
9396
  venvPath,
9012
9397
  workPath,
@@ -9021,7 +9406,7 @@ ${detail}`
9021
9406
  return {
9022
9407
  entrypoint: resolvedEntrypoint,
9023
9408
  djangoStatic,
9024
- extraPythonPath: baseDir ? (0, import_path15.join)(workPath, baseDir) : void 0
9409
+ extraPythonPath: baseDir ? (0, import_path17.join)(workPath, baseDir) : void 0
9025
9410
  };
9026
9411
  }
9027
9412
  };
@@ -9080,23 +9465,23 @@ async function downloadFilesInWorkPath({
9080
9465
  files,
9081
9466
  meta = {}
9082
9467
  }) {
9083
- (0, import_build_utils19.debug)("Downloading user files...");
9084
- let downloadedFiles = await (0, import_build_utils19.download)(files, workPath, meta);
9468
+ (0, import_build_utils20.debug)("Downloading user files...");
9469
+ let downloadedFiles = await (0, import_build_utils20.download)(files, workPath, meta);
9085
9470
  if (meta.isDev && entrypoint) {
9086
9471
  const normalizedEntrypoint = entrypoint.endsWith(".py") ? entrypoint : `${entrypoint}.py`;
9087
9472
  if (!hasProp(downloadedFiles, entrypoint) && !hasProp(downloadedFiles, normalizedEntrypoint)) {
9088
- throw new import_build_utils19.NowBuildError({
9473
+ throw new import_build_utils20.NowBuildError({
9089
9474
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
9090
9475
  message: `Configured Python entrypoint "${normalizedEntrypoint}" was not found.`,
9091
9476
  link: PYTHON_ENTRYPOINT_DOCS_URL2,
9092
9477
  action: "Learn More"
9093
9478
  });
9094
9479
  }
9095
- const { devCacheDir = (0, import_path15.join)(workPath, ".now", "cache") } = meta;
9096
- const cacheKey = (0, import_path15.basename)(entrypoint).replace(/\./g, "_");
9097
- const destCache = (0, import_path15.join)(devCacheDir, cacheKey);
9098
- await (0, import_build_utils19.download)(downloadedFiles, destCache);
9099
- downloadedFiles = await (0, import_build_utils19.glob)("**", destCache);
9480
+ const { devCacheDir = (0, import_path17.join)(workPath, ".now", "cache") } = meta;
9481
+ const cacheKey = (0, import_path17.basename)(entrypoint).replace(/\./g, "_");
9482
+ const destCache = (0, import_path17.join)(devCacheDir, cacheKey);
9483
+ await (0, import_build_utils20.download)(downloadedFiles, destCache);
9484
+ downloadedFiles = await (0, import_build_utils20.glob)("**", destCache);
9100
9485
  workPath = destCache;
9101
9486
  }
9102
9487
  return workPath;
@@ -9132,7 +9517,7 @@ async function getPythonLambdaOptions({
9132
9517
  sources.add(entrypoint.slice(0, -".py".length));
9133
9518
  }
9134
9519
  for (const sourceFile of sources) {
9135
- const lambdaOptions = await (0, import_build_utils19.getLambdaOptionsFromFunction)({
9520
+ const lambdaOptions = await (0, import_build_utils20.getLambdaOptionsFromFunction)({
9136
9521
  sourceFile,
9137
9522
  config
9138
9523
  });
@@ -9152,11 +9537,11 @@ async function installInjectedPackage({
9152
9537
  projectDir,
9153
9538
  pipPlatformArgs
9154
9539
  }) {
9155
- const localDir = (0, import_path15.join)(__dirname, "..", "..", "..", "python", name);
9156
- const isLocalDev = import_fs15.default.existsSync((0, import_path15.join)(localDir, "pyproject.toml"));
9540
+ const localDir = (0, import_path17.join)(__dirname, "..", "..", "..", "python", name);
9541
+ const isLocalDev = import_fs17.default.existsSync((0, import_path17.join)(localDir, "pyproject.toml"));
9157
9542
  const dep = envOverride || (isLocalDev ? localDir : pinned);
9158
9543
  const noExclude = ["--exclude-newer-package", `${name}=false`];
9159
- (0, import_build_utils19.debug)(`Installing ${dep}`);
9544
+ (0, import_build_utils20.debug)(`Installing ${dep}`);
9160
9545
  await uv.pip({
9161
9546
  venvPath,
9162
9547
  projectDir,
@@ -9182,28 +9567,37 @@ var build = async ({
9182
9567
  registerPreDeploy
9183
9568
  }) => {
9184
9569
  let entrypoint = rawEntrypoint === "<detect>" ? void 0 : rawEntrypoint;
9185
- const builderSpan = parentSpan ?? new import_build_utils19.Span({ name: "vc.builder" });
9570
+ const isPyprojectEntrypoint = entrypoint !== void 0 && (0, import_path17.basename)(entrypoint) === "pyproject.toml";
9571
+ if (isPyprojectEntrypoint && entrypoint !== "pyproject.toml") {
9572
+ throw new import_build_utils20.NowBuildError({
9573
+ code: "PYTHON_INVALID_PYPROJECT_ENTRYPOINT",
9574
+ message: `A "pyproject.toml" entrypoint must sit at the service root. Set the service "root" to "${(0, import_path17.dirname)(entrypoint)}" and use entrypoint "pyproject.toml".`
9575
+ });
9576
+ }
9577
+ const builderSpan = parentSpan ?? new import_build_utils20.Span({ name: "vc.builder" });
9186
9578
  const framework = config?.framework;
9187
9579
  let shouldInstallVercelWorkers = config?.hasWorkerServices === true;
9188
9580
  let subscribers = [];
9581
+ let workflows = [];
9189
9582
  let spawnEnv;
9190
9583
  let projectInstallCommand;
9191
9584
  let hasCustomCommand = false;
9192
9585
  const target = getTargetPlatform(meta.isDev ?? false);
9193
- (0, import_build_utils19.debug)(`workPath: ${workPath}`);
9586
+ (0, import_build_utils20.debug)(`workPath: ${workPath}`);
9194
9587
  workPath = await downloadFilesInWorkPath({
9195
9588
  workPath,
9196
9589
  files: originalFiles,
9197
9590
  entrypoint,
9198
9591
  meta
9199
9592
  });
9200
- if (!service && (0, import_build_utils19.isPythonFramework)(framework)) {
9593
+ if (isPyprojectEntrypoint || !service && (0, import_build_utils20.isPythonFramework)(framework)) {
9201
9594
  subscribers = await getPyprojectSubscribers(workPath);
9202
- shouldInstallVercelWorkers ||= subscribers.length > 0;
9595
+ workflows = await getPyprojectWorkflows(workPath);
9596
+ shouldInstallVercelWorkers ||= subscribers.length > 0 || workflows.length > 0;
9203
9597
  }
9204
9598
  try {
9205
9599
  if (meta.isDev) {
9206
- const setupCfg = (0, import_path15.join)(workPath, "setup.cfg");
9600
+ const setupCfg = (0, import_path17.join)(workPath, "setup.cfg");
9207
9601
  await writeFile(setupCfg, "[install]\nprefix=\n");
9208
9602
  }
9209
9603
  } catch (err) {
@@ -9212,23 +9606,36 @@ var build = async ({
9212
9606
  }
9213
9607
  let detected;
9214
9608
  const handlerFunction = typeof config?.handlerFunction === "string" ? config.handlerFunction : void 0;
9215
- detected = await detectPythonEntrypoint(
9216
- config.framework,
9217
- workPath,
9218
- entrypoint ? {
9219
- filePath: entrypoint,
9220
- // For schedule-triggered jobs, the WSGI variable is always 'app' (created dynamically).
9221
- // For other services, handlerFunction is used as the entrypoint variable name.
9222
- varName: service && (0, import_build_utils19.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9223
- } : void 0,
9224
- service,
9225
- repoRootPath
9226
- ) ?? void 0;
9609
+ if (isPyprojectEntrypoint) {
9610
+ const declared = await getVercelToolsEntrypoint(workPath, repoRootPath);
9611
+ if (declared) {
9612
+ detected = { entrypoint: declared };
9613
+ } else if (subscribers.length === 0 && workflows.length === 0) {
9614
+ throw new import_build_utils20.NowBuildError({
9615
+ code: "PYTHON_PYPROJECT_NOTHING_TO_BUILD",
9616
+ message: 'Entrypoint "pyproject.toml" declares nothing to build. Set "tool.vercel.entrypoint" for a web app and/or declare "[[tool.vercel.subscribers]]" or "[[tool.vercel.workflows]]" entries in pyproject.toml.'
9617
+ });
9618
+ }
9619
+ entrypoint = void 0;
9620
+ } else {
9621
+ detected = await detectPythonEntrypoint(
9622
+ config.framework,
9623
+ workPath,
9624
+ entrypoint ? {
9625
+ filePath: entrypoint,
9626
+ // For schedule-triggered jobs, the WSGI variable is always 'app' (created dynamically).
9627
+ // For other services, handlerFunction is used as the entrypoint variable name.
9628
+ varName: service && (0, import_build_utils20.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9629
+ } : void 0,
9630
+ service,
9631
+ repoRootPath
9632
+ ) ?? void 0;
9633
+ }
9227
9634
  if (detected?.error && detected?.baseDir === void 0) {
9228
9635
  throw detected?.error;
9229
9636
  }
9230
- const entryDirectory = detected?.baseDir ?? (entrypoint ? (0, import_path15.dirname)(entrypoint) : ".");
9231
- const entrypointAbsDir = (0, import_path15.join)(workPath, entryDirectory);
9637
+ const entryDirectory = detected?.baseDir ?? (entrypoint ? (0, import_path17.dirname)(entrypoint) : ".");
9638
+ const entrypointAbsDir = (0, import_path17.join)(workPath, entryDirectory);
9232
9639
  const rootDir = repoRootPath ?? workPath;
9233
9640
  const pythonPackage = await builderSpan.child("vc.builder.python.discover").trace(
9234
9641
  () => discoverPackage({
@@ -9269,12 +9676,12 @@ var build = async ({
9269
9676
  }
9270
9677
  const uvVersion = checkUvBinaryVersion(uv.getPath());
9271
9678
  console.log(`Using ${uvVersion}`);
9272
- const venvPath = service?.name ? (0, import_path15.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path15.join)(workPath, ".vercel", "python", ".venv");
9273
- const hasCachedVenv = import_fs15.default.existsSync((0, import_path15.join)(venvPath, "pyvenv.cfg"));
9274
- const hasCachedUv = import_fs15.default.existsSync(uvCacheDir);
9679
+ const venvPath = service?.name ? (0, import_path17.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path17.join)(workPath, ".vercel", "python", ".venv");
9680
+ const hasCachedVenv = import_fs17.default.existsSync((0, import_path17.join)(venvPath, "pyvenv.cfg"));
9681
+ const hasCachedUv = import_fs17.default.existsSync(uvCacheDir);
9275
9682
  const restoredCache = hasCachedVenv && hasCachedUv ? "both" : hasCachedVenv ? "venv" : hasCachedUv ? "uv" : "none";
9276
9683
  if (hasCachedVenv || hasCachedUv) {
9277
- (0, import_build_utils19.debug)(
9684
+ (0, import_build_utils20.debug)(
9278
9685
  `Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
9279
9686
  );
9280
9687
  }
@@ -9286,14 +9693,14 @@ var build = async ({
9286
9693
  uvCacheDir
9287
9694
  });
9288
9695
  });
9289
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
9696
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9290
9697
  const {
9291
9698
  cliType,
9292
9699
  lockfileVersion,
9293
9700
  packageJsonPackageManager,
9294
9701
  turboSupportsCorepackHome
9295
- } = await (0, import_build_utils19.scanParentDirs)(workPath, true);
9296
- spawnEnv = (0, import_build_utils19.getEnvForPackageManager)({
9702
+ } = await (0, import_build_utils20.scanParentDirs)(workPath, true);
9703
+ spawnEnv = (0, import_build_utils20.getEnvForPackageManager)({
9297
9704
  cliType,
9298
9705
  lockfileVersion,
9299
9706
  packageJsonPackageManager,
@@ -9318,13 +9725,13 @@ var build = async ({
9318
9725
  let uvLockPath = null;
9319
9726
  let uvProjectDir = null;
9320
9727
  let projectName;
9321
- await builderSpan.child(import_build_utils19.BUILDER_INSTALLER_STEP, {
9728
+ await builderSpan.child(import_build_utils20.BUILDER_INSTALLER_STEP, {
9322
9729
  installCommand: projectInstallCommand || void 0,
9323
9730
  runtime: "python",
9324
9731
  "python.cache.restored": restoredCache
9325
9732
  }).trace(async () => {
9326
9733
  if (projectInstallCommand) {
9327
- await import_fs15.default.promises.rm(venvPath, { recursive: true, force: true });
9734
+ await import_fs17.default.promises.rm(venvPath, { recursive: true, force: true });
9328
9735
  await ensureVenv({
9329
9736
  pythonVersion,
9330
9737
  venvPath,
@@ -9335,7 +9742,7 @@ var build = async ({
9335
9742
  console.log(
9336
9743
  `Running "install" command: \`${projectInstallCommand}\`...`
9337
9744
  );
9338
- await (0, import_build_utils19.execCommand)(projectInstallCommand, {
9745
+ await (0, import_build_utils20.execCommand)(projectInstallCommand, {
9339
9746
  env: pythonEnv,
9340
9747
  cwd: workPath
9341
9748
  });
@@ -9356,7 +9763,7 @@ var build = async ({
9356
9763
  }
9357
9764
  if (!assumeDepsInstalled) {
9358
9765
  const lockCacheKey = service?.name ? `uv.lock.${service.name}` : "uv.lock";
9359
- const cachedLockPath = (0, import_path15.join)(uvCacheDir, lockCacheKey);
9766
+ const cachedLockPath = (0, import_path17.join)(uvCacheDir, lockCacheKey);
9360
9767
  const { projectDir, lockPath, lockFileProvidedByUser } = await ensureUvProject({
9361
9768
  workPath,
9362
9769
  rootDir,
@@ -9377,21 +9784,21 @@ var build = async ({
9377
9784
  locked: !lockFileProvidedByUser,
9378
9785
  pythonPlatform: target.uvPlatform
9379
9786
  });
9380
- if (lockPath && import_fs15.default.existsSync(lockPath)) {
9381
- await import_fs15.default.promises.mkdir(uvCacheDir, { recursive: true });
9382
- await import_fs15.default.promises.copyFile(lockPath, cachedLockPath);
9787
+ if (lockPath && import_fs17.default.existsSync(lockPath)) {
9788
+ await import_fs17.default.promises.mkdir(uvCacheDir, { recursive: true });
9789
+ await import_fs17.default.promises.copyFile(lockPath, cachedLockPath);
9383
9790
  }
9384
9791
  }
9385
9792
  });
9386
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
9793
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9387
9794
  const projectBuildCommand = config?.projectSettings?.buildCommand ?? // fallback if provided directly on config (some callers set this)
9388
9795
  config?.buildCommand;
9389
- await builderSpan.child(import_build_utils19.BUILDER_COMPILE_STEP, {
9796
+ await builderSpan.child(import_build_utils20.BUILDER_COMPILE_STEP, {
9390
9797
  buildCommand: projectBuildCommand || void 0
9391
9798
  }).trace(async () => {
9392
9799
  if (projectBuildCommand) {
9393
9800
  console.log(`Running "${projectBuildCommand}"`);
9394
- await (0, import_build_utils19.execCommand)(projectBuildCommand, {
9801
+ await (0, import_build_utils20.execCommand)(projectBuildCommand, {
9395
9802
  env: pythonEnv,
9396
9803
  cwd: workPath
9397
9804
  });
@@ -9411,13 +9818,14 @@ var build = async ({
9411
9818
  entrypoint,
9412
9819
  detected
9413
9820
  });
9414
- const resolved = hookResult?.entrypoint ?? detected?.entrypoint;
9821
+ const resolved = isPyprojectEntrypoint ? detected?.entrypoint : hookResult?.entrypoint ?? detected?.entrypoint;
9415
9822
  if (!resolved && detected?.error) {
9416
9823
  throw detected?.error;
9417
9824
  }
9418
9825
  entrypoint = resolved?.entrypoint;
9419
- if (!entrypoint) {
9420
- throw new import_build_utils19.NowBuildError({
9826
+ const isWorkersOnly = !entrypoint && isPyprojectEntrypoint && (subscribers.length > 0 || workflows.length > 0);
9827
+ if (!entrypoint && !isWorkersOnly) {
9828
+ throw new import_build_utils20.NowBuildError({
9421
9829
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
9422
9830
  message: "No Python entrypoint could be detected. Please specify an entrypoint file."
9423
9831
  });
@@ -9430,7 +9838,7 @@ var build = async ({
9430
9838
  envOverride: baseEnv.VERCEL_RUNTIME_PYTHON,
9431
9839
  uv,
9432
9840
  venvPath,
9433
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9841
+ projectDir: (0, import_path17.join)(workPath, entryDirectory),
9434
9842
  pipPlatformArgs
9435
9843
  });
9436
9844
  if (shouldInstallVercelWorkers) {
@@ -9440,7 +9848,7 @@ var build = async ({
9440
9848
  envOverride: baseEnv.VERCEL_WORKERS_PYTHON,
9441
9849
  uv,
9442
9850
  venvPath,
9443
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9851
+ projectDir: (0, import_path17.join)(workPath, entryDirectory),
9444
9852
  pipPlatformArgs
9445
9853
  });
9446
9854
  }
@@ -9453,57 +9861,61 @@ var build = async ({
9453
9861
  const capturedEnv = { ...pythonEnv };
9454
9862
  const capturedCwd = workPath;
9455
9863
  registerPreDeploy(async () => {
9456
- await builderSpan.child(import_build_utils19.BUILDER_PRE_DEPLOY_STEP, {
9864
+ await builderSpan.child(import_build_utils20.BUILDER_PRE_DEPLOY_STEP, {
9457
9865
  preDeployCommand
9458
9866
  }).trace(async () => {
9459
9867
  console.log(`Running pre-deploy command: \`${preDeployCommand}\``);
9460
- await (0, import_build_utils19.execCommand)(preDeployCommand, {
9868
+ await (0, import_build_utils20.execCommand)(preDeployCommand, {
9461
9869
  env: capturedEnv,
9462
9870
  cwd: capturedCwd
9463
9871
  });
9464
9872
  });
9465
9873
  });
9466
9874
  }
9467
- (0, import_build_utils19.debug)("Entrypoint is", entrypoint);
9468
- const moduleName = entrypointToModule(entrypoint);
9469
- if (handlerFunction) {
9470
- const entrypointPath = (0, import_path15.join)(workPath, entrypoint);
9471
- const source = await import_fs15.default.promises.readFile(entrypointPath, "utf-8");
9472
- const found = await (0, import_python_analysis9.containsTopLevelCallable)(source, handlerFunction);
9473
- if (!found) {
9474
- throw new import_build_utils19.NowBuildError({
9475
- code: "PYTHON_HANDLER_NOT_FOUND",
9476
- message: `Handler function "${handlerFunction}" not found in ${entrypoint}. Ensure it is defined at the module's top level.`
9477
- });
9875
+ const vendorDir = resolveVendorDir();
9876
+ let crons;
9877
+ let runtimeTrampoline;
9878
+ if (entrypoint) {
9879
+ (0, import_build_utils20.debug)("Entrypoint is", entrypoint);
9880
+ const moduleName = entrypointToModule(entrypoint);
9881
+ if (handlerFunction) {
9882
+ const entrypointPath = (0, import_path17.join)(workPath, entrypoint);
9883
+ const source = await import_fs17.default.promises.readFile(entrypointPath, "utf-8");
9884
+ const found = await (0, import_python_analysis9.containsTopLevelCallable)(source, handlerFunction);
9885
+ if (!found) {
9886
+ throw new import_build_utils20.NowBuildError({
9887
+ code: "PYTHON_HANDLER_NOT_FOUND",
9888
+ message: `Handler function "${handlerFunction}" not found in ${entrypoint}. Ensure it is defined at the module's top level.`
9889
+ });
9890
+ }
9478
9891
  }
9892
+ const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
9893
+ const entrypointWithSuffix = `${entrypoint}${suffix}`;
9894
+ (0, import_build_utils20.debug)("Entrypoint with suffix is", entrypointWithSuffix);
9895
+ crons = await getServiceCrons({
9896
+ service,
9897
+ entrypoint,
9898
+ rawEntrypoint,
9899
+ handlerFunction,
9900
+ pythonBin: getVenvPythonBin(venvPath),
9901
+ env: pythonEnv,
9902
+ workPath
9903
+ });
9904
+ const extraTrampolineEnv = [];
9905
+ if (crons?.length) {
9906
+ const json = JSON.stringify(buildCronRouteTable(crons));
9907
+ (0, import_assert.default)(!json.includes("\\"), `backslash in cron route table: ${json}`);
9908
+ extraTrampolineEnv.push(`"__VC_CRON_ROUTES": '${json}'`);
9909
+ }
9910
+ const variableName = resolved?.variableName ?? "";
9911
+ runtimeTrampoline = createRuntimeTrampoline({
9912
+ moduleName,
9913
+ entrypoint: entrypointWithSuffix,
9914
+ vendorDir,
9915
+ variableName,
9916
+ extraEnv: extraTrampolineEnv
9917
+ });
9479
9918
  }
9480
- const vendorDir = resolveVendorDir();
9481
- const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
9482
- const entrypointWithSuffix = `${entrypoint}${suffix}`;
9483
- (0, import_build_utils19.debug)("Entrypoint with suffix is", entrypointWithSuffix);
9484
- const crons = await getServiceCrons({
9485
- service,
9486
- entrypoint,
9487
- rawEntrypoint,
9488
- handlerFunction,
9489
- pythonBin: getVenvPythonBin(venvPath),
9490
- env: pythonEnv,
9491
- workPath
9492
- });
9493
- const extraTrampolineEnv = [];
9494
- if (crons?.length) {
9495
- const json = JSON.stringify(buildCronRouteTable(crons));
9496
- (0, import_assert.default)(!json.includes("\\"), `backslash in cron route table: ${json}`);
9497
- extraTrampolineEnv.push(`"__VC_CRON_ROUTES": '${json}'`);
9498
- }
9499
- const variableName = resolved?.variableName ?? "";
9500
- const runtimeTrampoline = createRuntimeTrampoline({
9501
- moduleName,
9502
- entrypoint: entrypointWithSuffix,
9503
- vendorDir,
9504
- variableName,
9505
- extraEnv: extraTrampolineEnv
9506
- });
9507
9919
  const compileAllEnabled = shouldCompileAll({
9508
9920
  isDev: meta.isDev,
9509
9921
  hasCustomCommand,
@@ -9540,16 +9952,16 @@ var build = async ({
9540
9952
  cwd: workPath,
9541
9953
  ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
9542
9954
  };
9543
- const files = await (0, import_build_utils19.glob)("**", globOptions);
9955
+ const files = await (0, import_build_utils20.glob)("**", globOptions);
9544
9956
  if (djangoStatic?.manifestRelPath) {
9545
- files[djangoStatic.manifestRelPath] = new import_build_utils19.FileFsRef({
9546
- fsPath: (0, import_path15.join)(workPath, djangoStatic.manifestRelPath)
9957
+ files[djangoStatic.manifestRelPath] = new import_build_utils20.FileFsRef({
9958
+ fsPath: (0, import_path17.join)(workPath, djangoStatic.manifestRelPath)
9547
9959
  });
9548
9960
  }
9549
9961
  const handlerPyFilename = "vc__handler__python";
9550
9962
  if (config.framework === "fasthtml") {
9551
9963
  const { SESSKEY = "" } = process.env;
9552
- files[".sesskey"] = new import_build_utils19.FileBlob({ data: `"${SESSKEY}"` });
9964
+ files[".sesskey"] = new import_build_utils20.FileBlob({ data: `"${SESSKEY}"` });
9553
9965
  }
9554
9966
  const depExternalizer = new PythonDependencyExternalizer({
9555
9967
  venvPath,
@@ -9578,10 +9990,10 @@ var build = async ({
9578
9990
  });
9579
9991
  }
9580
9992
  });
9581
- const runCompileAllAndFillBytecode = async (capacityBytes) => {
9993
+ const runCompileAllAndFillBytecode = async (capacityBytes, vendorPackageTiers) => {
9582
9994
  try {
9583
9995
  await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
9584
- const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs15.default.existsSync(d));
9996
+ const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs17.default.existsSync(d));
9585
9997
  const pythonBin = getVenvPythonBin(venvPath);
9586
9998
  console.log("Compiling Python bytecode...");
9587
9999
  await runCompileAll({
@@ -9617,75 +10029,193 @@ var build = async ({
9617
10029
  remainingCapacity
9618
10030
  );
9619
10031
  }
9620
- const vendorBytecodeInfo = await depExternalizer.collectBytecodeFiles(
9621
- {
9622
- vendorDirName: vendorDir
9623
- }
9624
- );
9625
- await addVendorBytecodeWithinCapacity({
10032
+ await addVendorBytecodeInTiers({
9626
10033
  files,
9627
10034
  depExternalizer,
9628
10035
  vendorDir,
9629
- bytecodeInfo: vendorBytecodeInfo,
9630
- capacity: remainingCapacity
10036
+ capacity: remainingCapacity,
10037
+ vendorPackageTiers: vendorPackageTiers ?? [void 0]
10038
+ });
10039
+ } catch (err) {
10040
+ console.log(
10041
+ "Bytecode precompilation failed; continuing without precompiled bytecode."
10042
+ );
10043
+ (0, import_build_utils20.debug)(`bytecode precompilation error details: ${err}`);
10044
+ }
10045
+ };
10046
+ const runPrefixCompileAndFill = async (bundleResult) => {
10047
+ const pyMajor = pythonVersion.major;
10048
+ const pyMinor = pythonVersion.minor;
10049
+ if (pyMajor == null || pyMinor == null)
10050
+ return;
10051
+ try {
10052
+ const currentSize = await calculateBundleSize(files);
10053
+ let remainingCapacity = BYTECODE_FILL_CEILING_BYTES - currentSize;
10054
+ if (remainingCapacity <= 0) {
10055
+ (0, import_build_utils20.debug)(
10056
+ `skipping bytecode precompilation: no zip capacity remaining (bundle is ${(currentSize / (1024 * 1024)).toFixed(2)} MB)`
10057
+ );
10058
+ return;
10059
+ }
10060
+ const stagingDir = (0, import_path17.join)(workPath, ".vercel", "python", "pycache");
10061
+ await import_fs17.default.promises.rm(stagingDir, { recursive: true, force: true });
10062
+ await import_fs17.default.promises.mkdir(stagingDir, { recursive: true });
10063
+ await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
10064
+ const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs17.default.existsSync(d));
10065
+ const pythonBin = getVenvPythonBin(venvPath);
10066
+ console.log("Compiling Python bytecode...");
10067
+ await runCompileAll({
10068
+ pythonBin,
10069
+ filesOrDirectories: [workPath],
10070
+ env: pythonEnv,
10071
+ excludeRegex: getCompileAllAppExcludeRegex(workPath),
10072
+ pycachePrefix: stagingDir
10073
+ });
10074
+ await runCompileAll({
10075
+ pythonBin,
10076
+ filesOrDirectories: sitePackageDirs,
10077
+ env: pythonEnv,
10078
+ pycachePrefix: stagingDir
10079
+ });
10080
+ compileSpan.setAttributes({
10081
+ "python.compileall.enabled": "true",
10082
+ "python.compileall.mode": "pycache-prefix",
10083
+ "python.compileall.sitePackageDirectoryCount": String(
10084
+ sitePackageDirs.length
10085
+ )
10086
+ });
10087
+ });
10088
+ const beforeCount = Object.keys(files).length;
10089
+ const appInfo = await collectAppPrefixBytecodeFiles({
10090
+ stagingDir,
10091
+ workPath,
10092
+ files,
10093
+ runtimeTaskRoot: "/var/task",
10094
+ pythonMajor: pyMajor,
10095
+ pythonMinor: pyMinor
10096
+ });
10097
+ remainingCapacity = addBytecodeWithinCapacity(
10098
+ files,
10099
+ appInfo,
10100
+ remainingCapacity
10101
+ );
10102
+ const alwaysBundled = bundleResult.alwaysBundledPackages ?? [];
10103
+ remainingCapacity = await addCollectedVendorBytecode({
10104
+ files,
10105
+ capacity: remainingCapacity,
10106
+ collect: (include) => depExternalizer.collectPrefixBytecodeFiles({
10107
+ stagingDir,
10108
+ runtimeRoot: `/var/task/${vendorDir}`,
10109
+ includePackages: include ?? alwaysBundled
10110
+ })
9631
10111
  });
10112
+ const externalized = bundleResult.externalizedPublicPackages ?? [];
10113
+ await addCollectedVendorBytecode({
10114
+ files,
10115
+ capacity: remainingCapacity,
10116
+ collect: (include) => depExternalizer.collectPrefixBytecodeFiles({
10117
+ stagingDir,
10118
+ runtimeRoot: `${RUNTIME_DEPS_DIR}/lib/python${pyMajor}.${pyMinor}/site-packages`,
10119
+ includePackages: include ?? externalized
10120
+ })
10121
+ });
10122
+ if (Object.keys(files).length > beforeCount) {
10123
+ lambdaEnv.PYTHONPYCACHEPREFIX = RUNTIME_PYCACHE_PREFIX;
10124
+ }
9632
10125
  } catch (err) {
9633
10126
  console.log(
9634
10127
  "Bytecode precompilation failed; continuing without precompiled bytecode."
9635
10128
  );
9636
- (0, import_build_utils19.debug)(`bytecode precompilation error details: ${err}`);
10129
+ (0, import_build_utils20.debug)(`bytecode precompilation error details: ${err}`);
9637
10130
  }
9638
10131
  };
9639
10132
  const announceLargeFunction = () => console.log(
9640
- `Function "${entrypoint}" exceeds the standard size limit; enabling large functions (beta).`
10133
+ `Function "${entrypoint ?? rawEntrypoint}" exceeds the standard size limit; enabling large functions (beta).`
9641
10134
  );
10135
+ let packingMode;
9642
10136
  if (depAnalysis.runtimeInstallEnabled) {
9643
- const { fellBackToFullBundle } = await depExternalizer.generateBundle(files);
9644
- if (fellBackToFullBundle) {
10137
+ packingMode = "runtime-install";
10138
+ const bytecodeFirst = compileAllEnabled && pythonVersion.major != null && pythonVersion.minor != null;
10139
+ const bundleResult = await depExternalizer.generateBundle(files, {
10140
+ bytecodeFirst
10141
+ });
10142
+ if (bundleResult.fellBackToFullBundle) {
10143
+ packingMode = "hive";
9645
10144
  announceLargeFunction();
9646
10145
  if (compileAllEnabled) {
9647
10146
  await runCompileAllAndFillBytecode(
9648
- MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
10147
+ LARGE_FUNCTION_FILL_CEILING_BYTES
10148
+ );
10149
+ }
10150
+ } else if (bundleResult.packingMode === "bytecode-first") {
10151
+ await runPrefixCompileAndFill(bundleResult);
10152
+ } else if (compileAllEnabled) {
10153
+ const currentSize = await calculateBundleSize(files);
10154
+ const capacity = BYTECODE_FILL_CEILING_BYTES - currentSize;
10155
+ if (capacity > 0) {
10156
+ await runCompileAllAndFillBytecode(BYTECODE_FILL_CEILING_BYTES, [
10157
+ bundleResult.alwaysBundledPackages ?? [],
10158
+ bundleResult.bundledPublicPackages ?? []
10159
+ ]);
10160
+ } else {
10161
+ (0, import_build_utils20.debug)(
10162
+ `skipping bytecode precompilation: no zip capacity remaining (bundle is ${(currentSize / (1024 * 1024)).toFixed(2)} MB)`
9649
10163
  );
9650
10164
  }
9651
10165
  }
9652
10166
  } else {
9653
10167
  addFiles(files, depAnalysis.allVendorFiles);
9654
10168
  if (depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
10169
+ packingMode = "hive";
9655
10170
  if (isLargeFunctionsEnabled()) {
9656
10171
  announceLargeFunction();
9657
10172
  }
9658
10173
  if (compileAllEnabled) {
9659
10174
  await runCompileAllAndFillBytecode(
9660
- MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
10175
+ LARGE_FUNCTION_FILL_CEILING_BYTES
9661
10176
  );
9662
10177
  }
9663
- } else if (compileAllEnabled) {
9664
- const capacity = BYTECODE_FILL_CEILING_BYTES - depAnalysis.totalBundleSize;
9665
- const estimate = await estimateBytecodeSize(files);
9666
- if (capacity >= BYTECODE_COVERAGE_FLOOR * estimate) {
9667
- await runCompileAllAndFillBytecode(BYTECODE_FILL_CEILING_BYTES);
10178
+ } else {
10179
+ packingMode = "standard";
10180
+ if (compileAllEnabled) {
10181
+ const capacity = BYTECODE_FILL_CEILING_BYTES - depAnalysis.totalBundleSize;
10182
+ if (capacity > 0) {
10183
+ await runCompileAllAndFillBytecode(BYTECODE_FILL_CEILING_BYTES);
10184
+ } else {
10185
+ (0, import_build_utils20.debug)(
10186
+ `skipping bytecode precompilation: no zip capacity remaining (bundle is ${(depAnalysis.totalBundleSize / (1024 * 1024)).toFixed(2)} MB)`
10187
+ );
10188
+ }
9668
10189
  }
9669
10190
  }
9670
10191
  }
10192
+ bundleSpan.setAttributes({
10193
+ "python.bundle.totalSizeBytes": String(
10194
+ await calculateBundleSize(files)
10195
+ ),
10196
+ "python.bundle.packingMode": packingMode
10197
+ });
9671
10198
  });
9672
- const webFiles = {
9673
- ...files,
9674
- [`${handlerPyFilename}.py`]: new import_build_utils19.FileBlob({ data: runtimeTrampoline })
9675
- };
9676
- const lambdaOptions = await getPythonLambdaOptions({
9677
- config,
9678
- entrypoint
9679
- });
9680
- const output = new import_build_utils19.Lambda({
9681
- files: webFiles,
9682
- handler: `${handlerPyFilename}.vc_handler`,
9683
- runtime: pythonVersion.runtime,
9684
- ...lambdaOptions,
9685
- architecture: target.architecture,
9686
- environment: lambdaEnv,
9687
- supportsResponseStreaming: true
9688
- });
10199
+ let output;
10200
+ if (entrypoint && runtimeTrampoline) {
10201
+ const webFiles = {
10202
+ ...files,
10203
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({ data: runtimeTrampoline })
10204
+ };
10205
+ const lambdaOptions = await getPythonLambdaOptions({
10206
+ config,
10207
+ entrypoint
10208
+ });
10209
+ output = new import_build_utils20.Lambda({
10210
+ files: webFiles,
10211
+ handler: `${handlerPyFilename}.vc_handler`,
10212
+ runtime: pythonVersion.runtime,
10213
+ ...lambdaOptions,
10214
+ architecture: target.architecture,
10215
+ environment: lambdaEnv,
10216
+ supportsResponseStreaming: true
10217
+ });
10218
+ }
9689
10219
  const subscriberLambdas = {};
9690
10220
  for (const subscriber of subscribers) {
9691
10221
  const outputPath = getSubscriberOutputPath(subscriber.name);
@@ -9698,10 +10228,10 @@ var build = async ({
9698
10228
  ...subscriber.triggerDefaults
9699
10229
  })
9700
10230
  );
9701
- subscriberLambdas[outputPath] = new import_build_utils19.Lambda({
10231
+ subscriberLambdas[outputPath] = new import_build_utils20.Lambda({
9702
10232
  files: {
9703
10233
  ...files,
9704
- [`${handlerPyFilename}.py`]: new import_build_utils19.FileBlob({
10234
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({
9705
10235
  data: createRuntimeTrampoline({
9706
10236
  moduleName: subscriber.moduleName,
9707
10237
  entrypoint: subscriber.entrypoint,
@@ -9723,6 +10253,41 @@ var build = async ({
9723
10253
  supportsResponseStreaming: true
9724
10254
  });
9725
10255
  }
10256
+ const workflowLambdas = {};
10257
+ for (const workflow of workflows) {
10258
+ const outputPath = getWorkflowOutputPath(workflow.name);
10259
+ const experimentalTriggers = [
10260
+ {
10261
+ type: "queue/v2beta",
10262
+ topic: WORKFLOW_TOPIC_PATTERN,
10263
+ consumer: getWorkflowConsumerName(workflow.name)
10264
+ }
10265
+ ];
10266
+ workflowLambdas[outputPath] = new import_build_utils20.Lambda({
10267
+ files: {
10268
+ ...files,
10269
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({
10270
+ data: createRuntimeTrampoline({
10271
+ moduleName: workflow.moduleName,
10272
+ entrypoint: workflow.entrypoint,
10273
+ vendorDir,
10274
+ variableName: workflow.variableName
10275
+ })
10276
+ })
10277
+ },
10278
+ handler: `${handlerPyFilename}.vc_handler`,
10279
+ runtime: pythonVersion.runtime,
10280
+ architecture: target.architecture,
10281
+ environment: {
10282
+ ...lambdaEnv,
10283
+ VERCEL_HAS_WORKER_SERVICES: "1",
10284
+ // Compatibility marker consumed by the current Python runtime.
10285
+ VERCEL_SERVICE_TYPE: "worker"
10286
+ },
10287
+ experimentalTriggers,
10288
+ supportsResponseStreaming: true
10289
+ });
10290
+ }
9726
10291
  if (uvLockPath && !meta.isDev) {
9727
10292
  try {
9728
10293
  await generateProjectManifest({
@@ -9731,21 +10296,22 @@ var build = async ({
9731
10296
  pythonVersion,
9732
10297
  uvLockPath,
9733
10298
  framework,
9734
- serviceType: service ? (0, import_build_utils19.getReportedServiceType)(service) : void 0
10299
+ serviceType: service ? (0, import_build_utils20.getReportedServiceType)(service) : void 0
9735
10300
  });
9736
10301
  } catch (err) {
9737
- (0, import_build_utils19.debug)(
10302
+ (0, import_build_utils20.debug)(
9738
10303
  `Failed to write project manifest: ${err instanceof Error ? err.message : String(err)}`
9739
10304
  );
9740
10305
  }
9741
10306
  }
9742
- if (!(0, import_build_utils19.isPythonFramework)(framework) && !service?.name) {
10307
+ if (!(0, import_build_utils20.isPythonFramework)(framework) && !service?.name && !isPyprojectEntrypoint) {
10308
+ (0, import_assert.default)(output, "web Lambda must exist for non-service builds");
9743
10309
  return { resultVersion: 3, result: { output } };
9744
10310
  }
9745
10311
  const lambdaPath = service?.name && service.type ? `_svc/${service.name}/index` : "index";
9746
- const staticFiles = djangoStatic?.cdnOutputDir ? await (0, import_build_utils19.glob)("**", { cwd: djangoStatic.cdnOutputDir }) : {};
10312
+ const staticFiles = djangoStatic?.cdnOutputDir ? await (0, import_build_utils20.glob)("**", { cwd: djangoStatic.cdnOutputDir }) : {};
9747
10313
  const isNonWebService = service?.name && service.type && service.type !== "web";
9748
- const routes = isNonWebService ? void 0 : [
10314
+ const routes = isNonWebService || !output ? void 0 : [
9749
10315
  { handle: "filesystem" },
9750
10316
  { src: "/(.*)", dest: `/${lambdaPath}` }
9751
10317
  ];
@@ -9753,8 +10319,9 @@ var build = async ({
9753
10319
  resultVersion: 2,
9754
10320
  result: {
9755
10321
  output: {
9756
- [lambdaPath]: output,
10322
+ ...output ? { [lambdaPath]: output } : {},
9757
10323
  ...subscriberLambdas,
10324
+ ...workflowLambdas,
9758
10325
  ...staticFiles
9759
10326
  },
9760
10327
  ...routes ? { routes } : {},
@@ -9777,14 +10344,14 @@ var prepareCache = async ({
9777
10344
  }
9778
10345
  } catch {
9779
10346
  }
9780
- return (0, import_build_utils19.glob)("**/.vercel/python/{.venv,services/*/.venv,cache/uv}/**", {
10347
+ return (0, import_build_utils20.glob)("**/.vercel/python/{.venv,services/*/.venv,cache/uv}/**", {
9781
10348
  cwd: root,
9782
10349
  ignore
9783
10350
  });
9784
10351
  };
9785
10352
  var shouldServe = (opts) => {
9786
10353
  const framework = opts.config.framework;
9787
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
10354
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9788
10355
  const requestPath = opts.requestPath.replace(/\/$/, "");
9789
10356
  if (requestPath.startsWith("api") && opts.hasMatched) {
9790
10357
  return false;
@@ -9803,7 +10370,7 @@ var defaultShouldServe = ({
9803
10370
  if (entrypoint === requestPath && hasProp(files, entrypoint)) {
9804
10371
  return true;
9805
10372
  }
9806
- const { dir, name } = (0, import_path15.parse)(entrypoint);
10373
+ const { dir, name } = (0, import_path17.parse)(entrypoint);
9807
10374
  if (name === "index" && dir === requestPath && hasProp(files, entrypoint)) {
9808
10375
  return true;
9809
10376
  }
@@ -9814,6 +10381,8 @@ function hasProp(obj, key) {
9814
10381
  }
9815
10382
  // Annotate the CommonJS export names for ESM import in node:
9816
10383
  0 && (module.exports = {
10384
+ addCollectedVendorBytecode,
10385
+ addVendorBytecodeInTiers,
9817
10386
  build,
9818
10387
  defaultShouldServe,
9819
10388
  detectEntrypoint,