@vercel/python 6.49.0 → 6.50.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 +785 -243
  2. package/package.json +4 -4
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,6 +5895,8 @@ 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;
5804
5901
  var BUNDLING_DOCS_LINK = "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled";
5805
5902
  var PythonDependencyExternalizer = class {
@@ -5910,7 +6007,7 @@ application into smaller functions.`,
5910
6007
  * back to bundling everything for Hive and returns `fellBackToFullBundle:
5911
6008
  * true` so the caller can apply the same compileall handling.
5912
6009
  */
5913
- async generateBundle(files) {
6010
+ async generateBundle(files, options = {}) {
5914
6011
  if (!this.analyzed) {
5915
6012
  throw new Error(
5916
6013
  "PythonDependencyExternalizer.analyze() must be called before generateBundle()"
@@ -6071,7 +6168,20 @@ To fix this, either:
6071
6168
  ([name]) => externalizableSet.has((0, import_python_analysis3.normalizePackageName)(name))
6072
6169
  )
6073
6170
  );
6074
- const bundledPublic = lambdaKnapsack(publicPackageSizes, remainingCapacity);
6171
+ const { packingMode, bundledPublic } = planPublicPackagePacking({
6172
+ publicPackageSizes,
6173
+ zipCapacity: remainingCapacity,
6174
+ bytecodeFirst: options.bytecodeFirst ?? false
6175
+ });
6176
+ if (options.bytecodeFirst && packingMode === "knapsack") {
6177
+ (0, import_build_utils7.debug)(
6178
+ `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`
6179
+ );
6180
+ }
6181
+ const bundledPublicSet = new Set(bundledPublic.map(import_python_analysis3.normalizePackageName));
6182
+ const externalizedPublic = externalizablePublic.filter(
6183
+ (name) => !bundledPublicSet.has((0, import_python_analysis3.normalizePackageName)(name))
6184
+ );
6075
6185
  const allBundledPackages = [...alwaysBundled, ...bundledPublic];
6076
6186
  const selectedVendorFiles = await this.mirrorPackagesIntoVendor({
6077
6187
  vendorDirName: this.vendorDir,
@@ -6143,7 +6253,13 @@ functions.`,
6143
6253
  action: "Learn More"
6144
6254
  });
6145
6255
  }
6146
- return { fellBackToFullBundle: false };
6256
+ return {
6257
+ fellBackToFullBundle: false,
6258
+ packingMode,
6259
+ alwaysBundledPackages: alwaysBundled,
6260
+ bundledPublicPackages: bundledPublic,
6261
+ externalizedPublicPackages: externalizedPublic
6262
+ };
6147
6263
  }
6148
6264
  /**
6149
6265
  * Discard any runtime-install artifacts added so far and bundle every
@@ -6456,6 +6572,92 @@ functions.`,
6456
6572
  );
6457
6573
  return { files, totalSize, perItemSizes };
6458
6574
  }
6575
+ /**
6576
+ * Collect staged prefix `.pyc` files for vendor packages, keyed under
6577
+ * `_vc_pycache/<runtimeRoot>/...` (`runtimeRoot` = `/var/task/_vendor`
6578
+ * for bundled packages, the /tmp site-packages dir for externalized
6579
+ * ones). Missing staged files are silently skipped. Must be called after
6580
+ * `analyze()`.
6581
+ */
6582
+ async collectPrefixBytecodeFiles({
6583
+ stagingDir,
6584
+ runtimeRoot,
6585
+ includePackages
6586
+ }) {
6587
+ if (!this.sitePackageDirs || !this.distributions) {
6588
+ throw new Error(
6589
+ "collectPrefixBytecodeFiles() must be called after analyze()"
6590
+ );
6591
+ }
6592
+ if (this.pythonMajor == null || this.pythonMinor == null) {
6593
+ return { files: {}, totalSize: 0, perItemSizes: /* @__PURE__ */ new Map() };
6594
+ }
6595
+ const includeSet = includePackages ? new Set(includePackages.map(import_python_analysis3.normalizePackageName)) : null;
6596
+ const pending = [];
6597
+ for (const dir of this.sitePackageDirs) {
6598
+ const dirDistributions = this.distributions.get(dir);
6599
+ if (!dirDistributions)
6600
+ continue;
6601
+ const resolvedDir = (0, import_path7.resolve)(dir);
6602
+ const dirPrefix = resolvedDir + import_path7.sep;
6603
+ for (const [name, dist] of dirDistributions) {
6604
+ if (includeSet && !includeSet.has(name))
6605
+ continue;
6606
+ for (const { path: rawPath } of dist.files) {
6607
+ if (!rawPath.endsWith(".py"))
6608
+ continue;
6609
+ const filePath = rawPath.replaceAll("/", import_path7.sep);
6610
+ if (!(0, import_path7.resolve)(resolvedDir, filePath).startsWith(dirPrefix)) {
6611
+ continue;
6612
+ }
6613
+ const srcFsPath = deriveStagedPycFsPath(
6614
+ stagingDir,
6615
+ (0, import_path7.join)(dir, filePath),
6616
+ this.pythonMajor,
6617
+ this.pythonMinor
6618
+ );
6619
+ const bundlePath = derivePrefixPycBundlePath(
6620
+ `${runtimeRoot}/${rawPath}`,
6621
+ this.pythonMajor,
6622
+ this.pythonMinor
6623
+ );
6624
+ if (!srcFsPath || !bundlePath)
6625
+ continue;
6626
+ pending.push({ bundlePath, srcFsPath, packageName: name });
6627
+ }
6628
+ }
6629
+ }
6630
+ const results = await Promise.all(
6631
+ pending.map(async ({ bundlePath, srcFsPath, packageName }) => {
6632
+ try {
6633
+ const stats = await import_fs6.default.promises.stat(srcFsPath);
6634
+ return { bundlePath, srcFsPath, size: stats.size, packageName };
6635
+ } catch {
6636
+ return null;
6637
+ }
6638
+ })
6639
+ );
6640
+ const files = {};
6641
+ let totalSize = 0;
6642
+ const perItemSizes = /* @__PURE__ */ new Map();
6643
+ for (const result of results) {
6644
+ if (!result)
6645
+ continue;
6646
+ files[result.bundlePath] = new import_build_utils7.FileFsRef({
6647
+ fsPath: result.srcFsPath,
6648
+ size: result.size
6649
+ });
6650
+ totalSize += result.size;
6651
+ perItemSizes.set(
6652
+ result.packageName,
6653
+ (perItemSizes.get(result.packageName) ?? 0) + result.size
6654
+ );
6655
+ }
6656
+ (0, import_build_utils7.debug)(
6657
+ `Collected ${Object.keys(files).length} prefix bytecode files (${(totalSize / (1024 * 1024)).toFixed(2)} MB) for runtime root ${runtimeRoot}` + (includePackages ? ` from ${includePackages.length} packages` : "")
6658
+ );
6659
+ return { files, totalSize, perItemSizes };
6660
+ }
6459
6661
  };
6460
6662
  async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor, pythonMinor, sysPlatform, platformMachine) {
6461
6663
  if (!projectName)
@@ -6572,6 +6774,23 @@ function lambdaKnapsack(packages, capacity) {
6572
6774
  }
6573
6775
  return bundled;
6574
6776
  }
6777
+ function planPublicPackagePacking({
6778
+ publicPackageSizes,
6779
+ zipCapacity,
6780
+ bytecodeFirst
6781
+ }) {
6782
+ let totalPublicSize = 0;
6783
+ for (const size of publicPackageSizes.values()) {
6784
+ totalPublicSize += size;
6785
+ }
6786
+ if (bytecodeFirst && totalPublicSize <= EPHEMERAL_INSTALL_BUDGET_BYTES) {
6787
+ return { packingMode: "bytecode-first", bundledPublic: [] };
6788
+ }
6789
+ return {
6790
+ packingMode: "knapsack",
6791
+ bundledPublic: lambdaKnapsack(publicPackageSizes, zipCapacity)
6792
+ };
6793
+ }
6575
6794
 
6576
6795
  // src/diagnostics.ts
6577
6796
  var import_fs7 = __toESM(require("fs"));
@@ -8674,10 +8893,54 @@ async function runDjangoCollectStatic(venvPath, workPath, djangoPath, env, outpu
8674
8893
  var import_python_analysis9 = require("@vercel/python-analysis");
8675
8894
 
8676
8895
  // src/subscribers.ts
8896
+ var import_path15 = require("path");
8897
+ var import_fs15 = __toESM(require("fs"));
8898
+ var import_build_utils18 = require("@vercel/build-utils");
8899
+
8900
+ // src/module-entrypoint.ts
8677
8901
  var import_path14 = require("path");
8678
8902
  var import_fs14 = __toESM(require("fs"));
8679
- var import_build_utils18 = require("@vercel/build-utils");
8680
8903
  var MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
8904
+ function parseModuleEntrypoint(value) {
8905
+ const match = MODULE_ATTR_RE.exec(value);
8906
+ if (!match) {
8907
+ return null;
8908
+ }
8909
+ return {
8910
+ moduleName: match[1],
8911
+ variableName: match[2],
8912
+ filePath: `${match[1].replace(/\./g, "/")}.py`
8913
+ };
8914
+ }
8915
+ function getModuleEntrypointName({
8916
+ moduleName,
8917
+ variableName
8918
+ }) {
8919
+ return `${moduleName.replace(/\./g, "-")}_${variableName}`;
8920
+ }
8921
+ function safePathSegment(value) {
8922
+ return [...value].map((char) => {
8923
+ if (char === "_") {
8924
+ return "__";
8925
+ }
8926
+ return /[A-Za-z0-9-]/.test(char) ? char : `_${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
8927
+ }).join("");
8928
+ }
8929
+ async function resolveExistingEntrypoint(workPath, filePath) {
8930
+ const candidates = [filePath, filePath.replace(/\.py$/i, "/__init__.py")];
8931
+ for (const candidate of candidates) {
8932
+ try {
8933
+ const stat = await import_fs14.default.promises.stat((0, import_path14.join)(workPath, candidate));
8934
+ if (stat.isFile()) {
8935
+ return candidate;
8936
+ }
8937
+ } catch {
8938
+ }
8939
+ }
8940
+ return null;
8941
+ }
8942
+
8943
+ // src/subscribers.ts
8681
8944
  var SUBSCRIBER_OUTPUT_DIR = "_py_subscribers";
8682
8945
  var TRIGGER_NUMBER_FIELDS = [
8683
8946
  {
@@ -8710,14 +8973,6 @@ var SUBSCRIBER_FIELD_NAMES = /* @__PURE__ */ new Set([
8710
8973
  "topics",
8711
8974
  ...TRIGGER_NUMBER_FIELDS.map(({ field }) => field)
8712
8975
  ]);
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
8976
  function getSubscriberOutputPath(subscriberName) {
8722
8977
  return `${SUBSCRIBER_OUTPUT_DIR}/${safePathSegment(subscriberName)}`;
8723
8978
  }
@@ -8725,8 +8980,8 @@ function getSubscriberConsumerName(subscriberName) {
8725
8980
  return (0, import_build_utils18.sanitizeConsumerName)(getSubscriberOutputPath(subscriberName));
8726
8981
  }
8727
8982
  async function getPyprojectSubscribers(workPath) {
8728
- const pyprojectPath = (0, import_path14.join)(workPath, "pyproject.toml");
8729
- if (!import_fs14.default.existsSync(pyprojectPath)) {
8983
+ const pyprojectPath = (0, import_path15.join)(workPath, "pyproject.toml");
8984
+ if (!import_fs15.default.existsSync(pyprojectPath)) {
8730
8985
  return [];
8731
8986
  }
8732
8987
  const pyproject = await (0, import_build_utils18.readConfigFile)(pyprojectPath);
@@ -8764,8 +9019,13 @@ async function parseSubscriber(workPath, index, config) {
8764
9019
  if (typeof config.entrypoint !== "string") {
8765
9020
  throw subscriberError(`${label} must define string field "entrypoint"`);
8766
9021
  }
8767
- const entrypoint = parseEntrypoint(label, config.entrypoint);
8768
- const name = getSubscriberName(entrypoint);
9022
+ const entrypoint = parseModuleEntrypoint(config.entrypoint);
9023
+ if (!entrypoint) {
9024
+ throw subscriberError(
9025
+ `${label} has invalid entrypoint "${config.entrypoint}". Use "module:object"`
9026
+ );
9027
+ }
9028
+ const name = getModuleEntrypointName(entrypoint);
8769
9029
  const existingEntrypoint = await resolveExistingEntrypoint(
8770
9030
  workPath,
8771
9031
  entrypoint.filePath
@@ -8784,38 +9044,6 @@ async function parseSubscriber(workPath, index, config) {
8784
9044
  triggerDefaults: parseTriggerDefaults(name, config)
8785
9045
  };
8786
9046
  }
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
9047
  function parseTopics(name, value) {
8820
9048
  if (!Array.isArray(value) || value.length === 0) {
8821
9049
  throw subscriberError(
@@ -8854,8 +9082,86 @@ function subscriberError(message) {
8854
9082
  });
8855
9083
  }
8856
9084
 
9085
+ // src/workflows.ts
9086
+ var import_path16 = require("path");
9087
+ var import_fs16 = __toESM(require("fs"));
9088
+ var import_build_utils19 = require("@vercel/build-utils");
9089
+ var WORKFLOW_OUTPUT_DIR = "_py_workflows";
9090
+ var WORKFLOW_TOPIC_PATTERN = "__wkf_*";
9091
+ var WORKFLOW_FIELD_NAMES = /* @__PURE__ */ new Set(["entrypoint"]);
9092
+ function getWorkflowOutputPath(workflowName) {
9093
+ return `${WORKFLOW_OUTPUT_DIR}/${safePathSegment(workflowName)}`;
9094
+ }
9095
+ function getWorkflowConsumerName(workflowName) {
9096
+ return (0, import_build_utils19.sanitizeConsumerName)(getWorkflowOutputPath(workflowName));
9097
+ }
9098
+ async function getPyprojectWorkflows(workPath) {
9099
+ const pyprojectPath = (0, import_path16.join)(workPath, "pyproject.toml");
9100
+ if (!import_fs16.default.existsSync(pyprojectPath)) {
9101
+ return [];
9102
+ }
9103
+ const pyproject = await (0, import_build_utils19.readConfigFile)(pyprojectPath);
9104
+ const workflows = pyproject?.tool?.vercel?.workflows;
9105
+ if (!workflows) {
9106
+ return [];
9107
+ }
9108
+ if (!Array.isArray(workflows)) {
9109
+ throw workflowError('"tool.vercel.workflows" must be an array');
9110
+ }
9111
+ if (workflows.length > 1) {
9112
+ throw workflowError(
9113
+ '"tool.vercel.workflows" must declare a single entrypoint that registers every workflow'
9114
+ );
9115
+ }
9116
+ return Promise.all(
9117
+ workflows.map((config, index) => parseWorkflow(workPath, index, config))
9118
+ );
9119
+ }
9120
+ async function parseWorkflow(workPath, index, config) {
9121
+ const label = `workflow #${index + 1}`;
9122
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
9123
+ throw workflowError(`${label} must be an object`);
9124
+ }
9125
+ for (const key of Object.keys(config)) {
9126
+ if (!WORKFLOW_FIELD_NAMES.has(key)) {
9127
+ throw workflowError(`${label} has unrecognized field "${key}"`);
9128
+ }
9129
+ }
9130
+ if (typeof config.entrypoint !== "string") {
9131
+ throw workflowError(`${label} must define string field "entrypoint"`);
9132
+ }
9133
+ const entrypoint = parseModuleEntrypoint(config.entrypoint);
9134
+ if (!entrypoint) {
9135
+ throw workflowError(
9136
+ `${label} has invalid entrypoint "${config.entrypoint}". Use "module:object"`
9137
+ );
9138
+ }
9139
+ const name = getModuleEntrypointName(entrypoint);
9140
+ const existingEntrypoint = await resolveExistingEntrypoint(
9141
+ workPath,
9142
+ entrypoint.filePath
9143
+ );
9144
+ if (!existingEntrypoint) {
9145
+ throw workflowError(
9146
+ `workflow "${name}" has entrypoint "${config.entrypoint}" but file "${entrypoint.filePath}" does not exist`
9147
+ );
9148
+ }
9149
+ return {
9150
+ name,
9151
+ entrypoint: existingEntrypoint,
9152
+ moduleName: entrypoint.moduleName,
9153
+ variableName: entrypoint.variableName
9154
+ };
9155
+ }
9156
+ function workflowError(message) {
9157
+ return new import_build_utils19.NowBuildError({
9158
+ code: "PYTHON_INVALID_WORKFLOW_CONFIG",
9159
+ message
9160
+ });
9161
+ }
9162
+
8857
9163
  // src/index.ts
8858
- var writeFile = import_fs15.default.promises.writeFile;
9164
+ var writeFile = import_fs17.default.promises.writeFile;
8859
9165
  var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
8860
9166
  var version = -1;
8861
9167
  function getDevSubscriberTopics(subscriber) {
@@ -8871,26 +9177,49 @@ async function getDevSidecars({
8871
9177
  build: build2
8872
9178
  }) {
8873
9179
  const framework = build2.config?.framework;
8874
- if (build2.config?.middleware === true || typeof framework !== "string" || !(0, import_build_utils19.isPythonFramework)(framework)) {
9180
+ if (build2.config?.middleware === true || typeof framework !== "string" || !(0, import_build_utils20.isPythonFramework)(framework)) {
8875
9181
  return [];
8876
9182
  }
8877
9183
  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
- }));
9184
+ const workflows = await getPyprojectWorkflows(workPath);
9185
+ return [
9186
+ ...subscribers.map(
9187
+ (subscriber) => ({
9188
+ type: "subscriber",
9189
+ name: subscriber.name,
9190
+ consumer: getSubscriberConsumerName(subscriber.name),
9191
+ workspace: ".",
9192
+ framework,
9193
+ runtime: "python",
9194
+ builder: {
9195
+ use: build2.use,
9196
+ src: subscriber.entrypoint,
9197
+ config: {
9198
+ handlerFunction: subscriber.variableName
9199
+ }
9200
+ },
9201
+ topics: getDevSubscriberTopics(subscriber)
9202
+ })
9203
+ ),
9204
+ ...workflows.map(
9205
+ (workflow) => ({
9206
+ type: "subscriber",
9207
+ name: workflow.name,
9208
+ consumer: getWorkflowConsumerName(workflow.name),
9209
+ workspace: ".",
9210
+ framework,
9211
+ runtime: "python",
9212
+ builder: {
9213
+ use: build2.use,
9214
+ src: workflow.entrypoint,
9215
+ config: {
9216
+ handlerFunction: workflow.variableName
9217
+ }
9218
+ },
9219
+ topics: [{ topic: WORKFLOW_TOPIC_PATTERN }]
9220
+ })
9221
+ )
9222
+ ];
8894
9223
  }
8895
9224
  function addFiles(target, source) {
8896
9225
  for (const [p, f] of Object.entries(source)) {
@@ -8940,6 +9269,54 @@ async function addVendorBytecodeWithinCapacity({
8940
9269
  addFiles(files, selectedBytecode.files);
8941
9270
  return capacity - selectedBytecode.totalSize;
8942
9271
  }
9272
+ async function addVendorBytecodeInTiers({
9273
+ files,
9274
+ depExternalizer,
9275
+ vendorDir,
9276
+ capacity,
9277
+ vendorPackageTiers
9278
+ }) {
9279
+ let remainingCapacity = capacity;
9280
+ for (const tier of vendorPackageTiers) {
9281
+ if (remainingCapacity <= 0)
9282
+ break;
9283
+ if (tier && tier.length === 0)
9284
+ continue;
9285
+ const bytecodeInfo = await depExternalizer.collectBytecodeFiles({
9286
+ vendorDirName: vendorDir,
9287
+ includePackages: tier
9288
+ });
9289
+ remainingCapacity = await addVendorBytecodeWithinCapacity({
9290
+ files,
9291
+ depExternalizer,
9292
+ vendorDir,
9293
+ bytecodeInfo,
9294
+ capacity: remainingCapacity
9295
+ });
9296
+ }
9297
+ return remainingCapacity;
9298
+ }
9299
+ async function addCollectedVendorBytecode({
9300
+ files,
9301
+ capacity,
9302
+ collect
9303
+ }) {
9304
+ if (capacity <= 0)
9305
+ return capacity;
9306
+ const info = await collect(void 0);
9307
+ if (!info || info.totalSize <= 0)
9308
+ return capacity;
9309
+ if (info.totalSize <= capacity) {
9310
+ addFiles(files, info.files);
9311
+ return capacity - info.totalSize;
9312
+ }
9313
+ const selected = lambdaKnapsack(info.perItemSizes, capacity);
9314
+ if (selected.length === 0)
9315
+ return capacity;
9316
+ const selectedInfo = await collect(selected);
9317
+ addFiles(files, selectedInfo.files);
9318
+ return capacity - selectedInfo.totalSize;
9319
+ }
8943
9320
  async function runFrameworkHook(framework, ctx) {
8944
9321
  const hook = framework ? frameworkHooks[framework] : void 0;
8945
9322
  return hook?.(ctx);
@@ -8953,13 +9330,13 @@ var frameworkHooks = {
8953
9330
  }) => {
8954
9331
  let baseDir = detected?.baseDir;
8955
9332
  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");
9333
+ if (!import_fs17.default.existsSync((0, import_path17.join)(workPath, "manage.py"))) {
9334
+ (0, import_build_utils20.debug)("Django hook: no manage.py detected, skipping");
8958
9335
  return;
8959
9336
  }
8960
9337
  baseDir = "";
8961
9338
  }
8962
- const djangoPath = (0, import_path15.join)(workPath, baseDir);
9339
+ const djangoPath = (0, import_path17.join)(workPath, baseDir);
8963
9340
  let settingsResult;
8964
9341
  try {
8965
9342
  settingsResult = await getDjangoSettings(djangoPath, pythonEnv);
@@ -8971,13 +9348,13 @@ Hint: activate a venv or run with \`uv run vercel dev\``;
8971
9348
  } else {
8972
9349
  detail = err?.stderr || err?.message || String(err);
8973
9350
  }
8974
- throw new import_build_utils19.NowBuildError({
9351
+ throw new import_build_utils20.NowBuildError({
8975
9352
  code: "DJANGO_SETTINGS_FAILED",
8976
9353
  message: `Failed to read Django application settings from ${djangoPath}/manage.py:
8977
9354
  ${detail}`
8978
9355
  });
8979
9356
  }
8980
- (0, import_build_utils19.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
9357
+ (0, import_build_utils20.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
8981
9358
  const { djangoSettings, settingsModule, djangoVersion } = settingsResult;
8982
9359
  if (djangoVersion) {
8983
9360
  console.log(`Django ${djangoVersion.join(".")} detected`);
@@ -8989,7 +9366,7 @@ ${detail}`
8989
9366
  const variableName = parts.at(-1);
8990
9367
  const rel = `${parts.slice(0, -1).join("/")}.py`;
8991
9368
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
8992
- (0, import_build_utils19.debug)(`Django hook: ASGI entrypoint: ${ep} (variable: ${variableName})`);
9369
+ (0, import_build_utils20.debug)(`Django hook: ASGI entrypoint: ${ep} (variable: ${variableName})`);
8993
9370
  resolvedEntrypoint = { entrypoint: ep, variableName };
8994
9371
  } else {
8995
9372
  const wsgiApp = djangoSettings["WSGI_APPLICATION"];
@@ -8998,7 +9375,7 @@ ${detail}`
8998
9375
  const variableName = parts.at(-1);
8999
9376
  const rel = `${parts.slice(0, -1).join("/")}.py`;
9000
9377
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
9001
- (0, import_build_utils19.debug)(
9378
+ (0, import_build_utils20.debug)(
9002
9379
  `Django hook: WSGI entrypoint: ${ep} (variable: ${variableName})`
9003
9380
  );
9004
9381
  resolvedEntrypoint = { entrypoint: ep, variableName };
@@ -9006,7 +9383,7 @@ ${detail}`
9006
9383
  }
9007
9384
  let djangoStatic = null;
9008
9385
  if (workPath && venvPath) {
9009
- const outputStaticDir = (0, import_path15.join)(workPath, ".vercel", "output", "static");
9386
+ const outputStaticDir = (0, import_path17.join)(workPath, ".vercel", "output", "static");
9010
9387
  djangoStatic = await runDjangoCollectStatic(
9011
9388
  venvPath,
9012
9389
  workPath,
@@ -9021,7 +9398,7 @@ ${detail}`
9021
9398
  return {
9022
9399
  entrypoint: resolvedEntrypoint,
9023
9400
  djangoStatic,
9024
- extraPythonPath: baseDir ? (0, import_path15.join)(workPath, baseDir) : void 0
9401
+ extraPythonPath: baseDir ? (0, import_path17.join)(workPath, baseDir) : void 0
9025
9402
  };
9026
9403
  }
9027
9404
  };
@@ -9080,23 +9457,23 @@ async function downloadFilesInWorkPath({
9080
9457
  files,
9081
9458
  meta = {}
9082
9459
  }) {
9083
- (0, import_build_utils19.debug)("Downloading user files...");
9084
- let downloadedFiles = await (0, import_build_utils19.download)(files, workPath, meta);
9460
+ (0, import_build_utils20.debug)("Downloading user files...");
9461
+ let downloadedFiles = await (0, import_build_utils20.download)(files, workPath, meta);
9085
9462
  if (meta.isDev && entrypoint) {
9086
9463
  const normalizedEntrypoint = entrypoint.endsWith(".py") ? entrypoint : `${entrypoint}.py`;
9087
9464
  if (!hasProp(downloadedFiles, entrypoint) && !hasProp(downloadedFiles, normalizedEntrypoint)) {
9088
- throw new import_build_utils19.NowBuildError({
9465
+ throw new import_build_utils20.NowBuildError({
9089
9466
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
9090
9467
  message: `Configured Python entrypoint "${normalizedEntrypoint}" was not found.`,
9091
9468
  link: PYTHON_ENTRYPOINT_DOCS_URL2,
9092
9469
  action: "Learn More"
9093
9470
  });
9094
9471
  }
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);
9472
+ const { devCacheDir = (0, import_path17.join)(workPath, ".now", "cache") } = meta;
9473
+ const cacheKey = (0, import_path17.basename)(entrypoint).replace(/\./g, "_");
9474
+ const destCache = (0, import_path17.join)(devCacheDir, cacheKey);
9475
+ await (0, import_build_utils20.download)(downloadedFiles, destCache);
9476
+ downloadedFiles = await (0, import_build_utils20.glob)("**", destCache);
9100
9477
  workPath = destCache;
9101
9478
  }
9102
9479
  return workPath;
@@ -9132,7 +9509,7 @@ async function getPythonLambdaOptions({
9132
9509
  sources.add(entrypoint.slice(0, -".py".length));
9133
9510
  }
9134
9511
  for (const sourceFile of sources) {
9135
- const lambdaOptions = await (0, import_build_utils19.getLambdaOptionsFromFunction)({
9512
+ const lambdaOptions = await (0, import_build_utils20.getLambdaOptionsFromFunction)({
9136
9513
  sourceFile,
9137
9514
  config
9138
9515
  });
@@ -9152,11 +9529,11 @@ async function installInjectedPackage({
9152
9529
  projectDir,
9153
9530
  pipPlatformArgs
9154
9531
  }) {
9155
- const localDir = (0, import_path15.join)(__dirname, "..", "..", "..", "python", name);
9156
- const isLocalDev = import_fs15.default.existsSync((0, import_path15.join)(localDir, "pyproject.toml"));
9532
+ const localDir = (0, import_path17.join)(__dirname, "..", "..", "..", "python", name);
9533
+ const isLocalDev = import_fs17.default.existsSync((0, import_path17.join)(localDir, "pyproject.toml"));
9157
9534
  const dep = envOverride || (isLocalDev ? localDir : pinned);
9158
9535
  const noExclude = ["--exclude-newer-package", `${name}=false`];
9159
- (0, import_build_utils19.debug)(`Installing ${dep}`);
9536
+ (0, import_build_utils20.debug)(`Installing ${dep}`);
9160
9537
  await uv.pip({
9161
9538
  venvPath,
9162
9539
  projectDir,
@@ -9182,28 +9559,37 @@ var build = async ({
9182
9559
  registerPreDeploy
9183
9560
  }) => {
9184
9561
  let entrypoint = rawEntrypoint === "<detect>" ? void 0 : rawEntrypoint;
9185
- const builderSpan = parentSpan ?? new import_build_utils19.Span({ name: "vc.builder" });
9562
+ const isPyprojectEntrypoint = entrypoint !== void 0 && (0, import_path17.basename)(entrypoint) === "pyproject.toml";
9563
+ if (isPyprojectEntrypoint && entrypoint !== "pyproject.toml") {
9564
+ throw new import_build_utils20.NowBuildError({
9565
+ code: "PYTHON_INVALID_PYPROJECT_ENTRYPOINT",
9566
+ 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".`
9567
+ });
9568
+ }
9569
+ const builderSpan = parentSpan ?? new import_build_utils20.Span({ name: "vc.builder" });
9186
9570
  const framework = config?.framework;
9187
9571
  let shouldInstallVercelWorkers = config?.hasWorkerServices === true;
9188
9572
  let subscribers = [];
9573
+ let workflows = [];
9189
9574
  let spawnEnv;
9190
9575
  let projectInstallCommand;
9191
9576
  let hasCustomCommand = false;
9192
9577
  const target = getTargetPlatform(meta.isDev ?? false);
9193
- (0, import_build_utils19.debug)(`workPath: ${workPath}`);
9578
+ (0, import_build_utils20.debug)(`workPath: ${workPath}`);
9194
9579
  workPath = await downloadFilesInWorkPath({
9195
9580
  workPath,
9196
9581
  files: originalFiles,
9197
9582
  entrypoint,
9198
9583
  meta
9199
9584
  });
9200
- if (!service && (0, import_build_utils19.isPythonFramework)(framework)) {
9585
+ if (isPyprojectEntrypoint || !service && (0, import_build_utils20.isPythonFramework)(framework)) {
9201
9586
  subscribers = await getPyprojectSubscribers(workPath);
9202
- shouldInstallVercelWorkers ||= subscribers.length > 0;
9587
+ workflows = await getPyprojectWorkflows(workPath);
9588
+ shouldInstallVercelWorkers ||= subscribers.length > 0 || workflows.length > 0;
9203
9589
  }
9204
9590
  try {
9205
9591
  if (meta.isDev) {
9206
- const setupCfg = (0, import_path15.join)(workPath, "setup.cfg");
9592
+ const setupCfg = (0, import_path17.join)(workPath, "setup.cfg");
9207
9593
  await writeFile(setupCfg, "[install]\nprefix=\n");
9208
9594
  }
9209
9595
  } catch (err) {
@@ -9212,23 +9598,36 @@ var build = async ({
9212
9598
  }
9213
9599
  let detected;
9214
9600
  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;
9601
+ if (isPyprojectEntrypoint) {
9602
+ const declared = await getVercelToolsEntrypoint(workPath, repoRootPath);
9603
+ if (declared) {
9604
+ detected = { entrypoint: declared };
9605
+ } else if (subscribers.length === 0 && workflows.length === 0) {
9606
+ throw new import_build_utils20.NowBuildError({
9607
+ code: "PYTHON_PYPROJECT_NOTHING_TO_BUILD",
9608
+ 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.'
9609
+ });
9610
+ }
9611
+ entrypoint = void 0;
9612
+ } else {
9613
+ detected = await detectPythonEntrypoint(
9614
+ config.framework,
9615
+ workPath,
9616
+ entrypoint ? {
9617
+ filePath: entrypoint,
9618
+ // For schedule-triggered jobs, the WSGI variable is always 'app' (created dynamically).
9619
+ // For other services, handlerFunction is used as the entrypoint variable name.
9620
+ varName: service && (0, import_build_utils20.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9621
+ } : void 0,
9622
+ service,
9623
+ repoRootPath
9624
+ ) ?? void 0;
9625
+ }
9227
9626
  if (detected?.error && detected?.baseDir === void 0) {
9228
9627
  throw detected?.error;
9229
9628
  }
9230
- const entryDirectory = detected?.baseDir ?? (entrypoint ? (0, import_path15.dirname)(entrypoint) : ".");
9231
- const entrypointAbsDir = (0, import_path15.join)(workPath, entryDirectory);
9629
+ const entryDirectory = detected?.baseDir ?? (entrypoint ? (0, import_path17.dirname)(entrypoint) : ".");
9630
+ const entrypointAbsDir = (0, import_path17.join)(workPath, entryDirectory);
9232
9631
  const rootDir = repoRootPath ?? workPath;
9233
9632
  const pythonPackage = await builderSpan.child("vc.builder.python.discover").trace(
9234
9633
  () => discoverPackage({
@@ -9269,12 +9668,12 @@ var build = async ({
9269
9668
  }
9270
9669
  const uvVersion = checkUvBinaryVersion(uv.getPath());
9271
9670
  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);
9671
+ const venvPath = service?.name ? (0, import_path17.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path17.join)(workPath, ".vercel", "python", ".venv");
9672
+ const hasCachedVenv = import_fs17.default.existsSync((0, import_path17.join)(venvPath, "pyvenv.cfg"));
9673
+ const hasCachedUv = import_fs17.default.existsSync(uvCacheDir);
9275
9674
  const restoredCache = hasCachedVenv && hasCachedUv ? "both" : hasCachedVenv ? "venv" : hasCachedUv ? "uv" : "none";
9276
9675
  if (hasCachedVenv || hasCachedUv) {
9277
- (0, import_build_utils19.debug)(
9676
+ (0, import_build_utils20.debug)(
9278
9677
  `Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
9279
9678
  );
9280
9679
  }
@@ -9286,14 +9685,14 @@ var build = async ({
9286
9685
  uvCacheDir
9287
9686
  });
9288
9687
  });
9289
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
9688
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9290
9689
  const {
9291
9690
  cliType,
9292
9691
  lockfileVersion,
9293
9692
  packageJsonPackageManager,
9294
9693
  turboSupportsCorepackHome
9295
- } = await (0, import_build_utils19.scanParentDirs)(workPath, true);
9296
- spawnEnv = (0, import_build_utils19.getEnvForPackageManager)({
9694
+ } = await (0, import_build_utils20.scanParentDirs)(workPath, true);
9695
+ spawnEnv = (0, import_build_utils20.getEnvForPackageManager)({
9297
9696
  cliType,
9298
9697
  lockfileVersion,
9299
9698
  packageJsonPackageManager,
@@ -9318,13 +9717,13 @@ var build = async ({
9318
9717
  let uvLockPath = null;
9319
9718
  let uvProjectDir = null;
9320
9719
  let projectName;
9321
- await builderSpan.child(import_build_utils19.BUILDER_INSTALLER_STEP, {
9720
+ await builderSpan.child(import_build_utils20.BUILDER_INSTALLER_STEP, {
9322
9721
  installCommand: projectInstallCommand || void 0,
9323
9722
  runtime: "python",
9324
9723
  "python.cache.restored": restoredCache
9325
9724
  }).trace(async () => {
9326
9725
  if (projectInstallCommand) {
9327
- await import_fs15.default.promises.rm(venvPath, { recursive: true, force: true });
9726
+ await import_fs17.default.promises.rm(venvPath, { recursive: true, force: true });
9328
9727
  await ensureVenv({
9329
9728
  pythonVersion,
9330
9729
  venvPath,
@@ -9335,7 +9734,7 @@ var build = async ({
9335
9734
  console.log(
9336
9735
  `Running "install" command: \`${projectInstallCommand}\`...`
9337
9736
  );
9338
- await (0, import_build_utils19.execCommand)(projectInstallCommand, {
9737
+ await (0, import_build_utils20.execCommand)(projectInstallCommand, {
9339
9738
  env: pythonEnv,
9340
9739
  cwd: workPath
9341
9740
  });
@@ -9356,7 +9755,7 @@ var build = async ({
9356
9755
  }
9357
9756
  if (!assumeDepsInstalled) {
9358
9757
  const lockCacheKey = service?.name ? `uv.lock.${service.name}` : "uv.lock";
9359
- const cachedLockPath = (0, import_path15.join)(uvCacheDir, lockCacheKey);
9758
+ const cachedLockPath = (0, import_path17.join)(uvCacheDir, lockCacheKey);
9360
9759
  const { projectDir, lockPath, lockFileProvidedByUser } = await ensureUvProject({
9361
9760
  workPath,
9362
9761
  rootDir,
@@ -9377,21 +9776,21 @@ var build = async ({
9377
9776
  locked: !lockFileProvidedByUser,
9378
9777
  pythonPlatform: target.uvPlatform
9379
9778
  });
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);
9779
+ if (lockPath && import_fs17.default.existsSync(lockPath)) {
9780
+ await import_fs17.default.promises.mkdir(uvCacheDir, { recursive: true });
9781
+ await import_fs17.default.promises.copyFile(lockPath, cachedLockPath);
9383
9782
  }
9384
9783
  }
9385
9784
  });
9386
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
9785
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9387
9786
  const projectBuildCommand = config?.projectSettings?.buildCommand ?? // fallback if provided directly on config (some callers set this)
9388
9787
  config?.buildCommand;
9389
- await builderSpan.child(import_build_utils19.BUILDER_COMPILE_STEP, {
9788
+ await builderSpan.child(import_build_utils20.BUILDER_COMPILE_STEP, {
9390
9789
  buildCommand: projectBuildCommand || void 0
9391
9790
  }).trace(async () => {
9392
9791
  if (projectBuildCommand) {
9393
9792
  console.log(`Running "${projectBuildCommand}"`);
9394
- await (0, import_build_utils19.execCommand)(projectBuildCommand, {
9793
+ await (0, import_build_utils20.execCommand)(projectBuildCommand, {
9395
9794
  env: pythonEnv,
9396
9795
  cwd: workPath
9397
9796
  });
@@ -9411,13 +9810,14 @@ var build = async ({
9411
9810
  entrypoint,
9412
9811
  detected
9413
9812
  });
9414
- const resolved = hookResult?.entrypoint ?? detected?.entrypoint;
9813
+ const resolved = isPyprojectEntrypoint ? detected?.entrypoint : hookResult?.entrypoint ?? detected?.entrypoint;
9415
9814
  if (!resolved && detected?.error) {
9416
9815
  throw detected?.error;
9417
9816
  }
9418
9817
  entrypoint = resolved?.entrypoint;
9419
- if (!entrypoint) {
9420
- throw new import_build_utils19.NowBuildError({
9818
+ const isWorkersOnly = !entrypoint && isPyprojectEntrypoint && (subscribers.length > 0 || workflows.length > 0);
9819
+ if (!entrypoint && !isWorkersOnly) {
9820
+ throw new import_build_utils20.NowBuildError({
9421
9821
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
9422
9822
  message: "No Python entrypoint could be detected. Please specify an entrypoint file."
9423
9823
  });
@@ -9430,7 +9830,7 @@ var build = async ({
9430
9830
  envOverride: baseEnv.VERCEL_RUNTIME_PYTHON,
9431
9831
  uv,
9432
9832
  venvPath,
9433
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9833
+ projectDir: (0, import_path17.join)(workPath, entryDirectory),
9434
9834
  pipPlatformArgs
9435
9835
  });
9436
9836
  if (shouldInstallVercelWorkers) {
@@ -9440,7 +9840,7 @@ var build = async ({
9440
9840
  envOverride: baseEnv.VERCEL_WORKERS_PYTHON,
9441
9841
  uv,
9442
9842
  venvPath,
9443
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9843
+ projectDir: (0, import_path17.join)(workPath, entryDirectory),
9444
9844
  pipPlatformArgs
9445
9845
  });
9446
9846
  }
@@ -9453,57 +9853,61 @@ var build = async ({
9453
9853
  const capturedEnv = { ...pythonEnv };
9454
9854
  const capturedCwd = workPath;
9455
9855
  registerPreDeploy(async () => {
9456
- await builderSpan.child(import_build_utils19.BUILDER_PRE_DEPLOY_STEP, {
9856
+ await builderSpan.child(import_build_utils20.BUILDER_PRE_DEPLOY_STEP, {
9457
9857
  preDeployCommand
9458
9858
  }).trace(async () => {
9459
9859
  console.log(`Running pre-deploy command: \`${preDeployCommand}\``);
9460
- await (0, import_build_utils19.execCommand)(preDeployCommand, {
9860
+ await (0, import_build_utils20.execCommand)(preDeployCommand, {
9461
9861
  env: capturedEnv,
9462
9862
  cwd: capturedCwd
9463
9863
  });
9464
9864
  });
9465
9865
  });
9466
9866
  }
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
- });
9867
+ const vendorDir = resolveVendorDir();
9868
+ let crons;
9869
+ let runtimeTrampoline;
9870
+ if (entrypoint) {
9871
+ (0, import_build_utils20.debug)("Entrypoint is", entrypoint);
9872
+ const moduleName = entrypointToModule(entrypoint);
9873
+ if (handlerFunction) {
9874
+ const entrypointPath = (0, import_path17.join)(workPath, entrypoint);
9875
+ const source = await import_fs17.default.promises.readFile(entrypointPath, "utf-8");
9876
+ const found = await (0, import_python_analysis9.containsTopLevelCallable)(source, handlerFunction);
9877
+ if (!found) {
9878
+ throw new import_build_utils20.NowBuildError({
9879
+ code: "PYTHON_HANDLER_NOT_FOUND",
9880
+ message: `Handler function "${handlerFunction}" not found in ${entrypoint}. Ensure it is defined at the module's top level.`
9881
+ });
9882
+ }
9478
9883
  }
9884
+ const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
9885
+ const entrypointWithSuffix = `${entrypoint}${suffix}`;
9886
+ (0, import_build_utils20.debug)("Entrypoint with suffix is", entrypointWithSuffix);
9887
+ crons = await getServiceCrons({
9888
+ service,
9889
+ entrypoint,
9890
+ rawEntrypoint,
9891
+ handlerFunction,
9892
+ pythonBin: getVenvPythonBin(venvPath),
9893
+ env: pythonEnv,
9894
+ workPath
9895
+ });
9896
+ const extraTrampolineEnv = [];
9897
+ if (crons?.length) {
9898
+ const json = JSON.stringify(buildCronRouteTable(crons));
9899
+ (0, import_assert.default)(!json.includes("\\"), `backslash in cron route table: ${json}`);
9900
+ extraTrampolineEnv.push(`"__VC_CRON_ROUTES": '${json}'`);
9901
+ }
9902
+ const variableName = resolved?.variableName ?? "";
9903
+ runtimeTrampoline = createRuntimeTrampoline({
9904
+ moduleName,
9905
+ entrypoint: entrypointWithSuffix,
9906
+ vendorDir,
9907
+ variableName,
9908
+ extraEnv: extraTrampolineEnv
9909
+ });
9479
9910
  }
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
9911
  const compileAllEnabled = shouldCompileAll({
9508
9912
  isDev: meta.isDev,
9509
9913
  hasCustomCommand,
@@ -9540,16 +9944,16 @@ var build = async ({
9540
9944
  cwd: workPath,
9541
9945
  ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
9542
9946
  };
9543
- const files = await (0, import_build_utils19.glob)("**", globOptions);
9947
+ const files = await (0, import_build_utils20.glob)("**", globOptions);
9544
9948
  if (djangoStatic?.manifestRelPath) {
9545
- files[djangoStatic.manifestRelPath] = new import_build_utils19.FileFsRef({
9546
- fsPath: (0, import_path15.join)(workPath, djangoStatic.manifestRelPath)
9949
+ files[djangoStatic.manifestRelPath] = new import_build_utils20.FileFsRef({
9950
+ fsPath: (0, import_path17.join)(workPath, djangoStatic.manifestRelPath)
9547
9951
  });
9548
9952
  }
9549
9953
  const handlerPyFilename = "vc__handler__python";
9550
9954
  if (config.framework === "fasthtml") {
9551
9955
  const { SESSKEY = "" } = process.env;
9552
- files[".sesskey"] = new import_build_utils19.FileBlob({ data: `"${SESSKEY}"` });
9956
+ files[".sesskey"] = new import_build_utils20.FileBlob({ data: `"${SESSKEY}"` });
9553
9957
  }
9554
9958
  const depExternalizer = new PythonDependencyExternalizer({
9555
9959
  venvPath,
@@ -9578,10 +9982,10 @@ var build = async ({
9578
9982
  });
9579
9983
  }
9580
9984
  });
9581
- const runCompileAllAndFillBytecode = async (capacityBytes) => {
9985
+ const runCompileAllAndFillBytecode = async (capacityBytes, vendorPackageTiers) => {
9582
9986
  try {
9583
9987
  await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
9584
- const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs15.default.existsSync(d));
9988
+ const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs17.default.existsSync(d));
9585
9989
  const pythonBin = getVenvPythonBin(venvPath);
9586
9990
  console.log("Compiling Python bytecode...");
9587
9991
  await runCompileAll({
@@ -9617,37 +10021,133 @@ var build = async ({
9617
10021
  remainingCapacity
9618
10022
  );
9619
10023
  }
9620
- const vendorBytecodeInfo = await depExternalizer.collectBytecodeFiles(
9621
- {
9622
- vendorDirName: vendorDir
9623
- }
9624
- );
9625
- await addVendorBytecodeWithinCapacity({
10024
+ await addVendorBytecodeInTiers({
9626
10025
  files,
9627
10026
  depExternalizer,
9628
10027
  vendorDir,
9629
- bytecodeInfo: vendorBytecodeInfo,
9630
- capacity: remainingCapacity
10028
+ capacity: remainingCapacity,
10029
+ vendorPackageTiers: vendorPackageTiers ?? [void 0]
9631
10030
  });
9632
10031
  } catch (err) {
9633
10032
  console.log(
9634
10033
  "Bytecode precompilation failed; continuing without precompiled bytecode."
9635
10034
  );
9636
- (0, import_build_utils19.debug)(`bytecode precompilation error details: ${err}`);
10035
+ (0, import_build_utils20.debug)(`bytecode precompilation error details: ${err}`);
10036
+ }
10037
+ };
10038
+ const runPrefixCompileAndFill = async (bundleResult) => {
10039
+ const pyMajor = pythonVersion.major;
10040
+ const pyMinor = pythonVersion.minor;
10041
+ if (pyMajor == null || pyMinor == null)
10042
+ return;
10043
+ try {
10044
+ const currentSize = await calculateBundleSize(files);
10045
+ let remainingCapacity = BYTECODE_FILL_CEILING_BYTES - currentSize;
10046
+ if (remainingCapacity <= 0) {
10047
+ (0, import_build_utils20.debug)(
10048
+ `skipping bytecode precompilation: no zip capacity remaining (bundle is ${(currentSize / (1024 * 1024)).toFixed(2)} MB)`
10049
+ );
10050
+ return;
10051
+ }
10052
+ const stagingDir = (0, import_path17.join)(workPath, ".vercel", "python", "pycache");
10053
+ await import_fs17.default.promises.rm(stagingDir, { recursive: true, force: true });
10054
+ await import_fs17.default.promises.mkdir(stagingDir, { recursive: true });
10055
+ await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
10056
+ const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs17.default.existsSync(d));
10057
+ const pythonBin = getVenvPythonBin(venvPath);
10058
+ console.log("Compiling Python bytecode...");
10059
+ await runCompileAll({
10060
+ pythonBin,
10061
+ filesOrDirectories: [workPath],
10062
+ env: pythonEnv,
10063
+ excludeRegex: getCompileAllAppExcludeRegex(workPath),
10064
+ pycachePrefix: stagingDir
10065
+ });
10066
+ await runCompileAll({
10067
+ pythonBin,
10068
+ filesOrDirectories: sitePackageDirs,
10069
+ env: pythonEnv,
10070
+ pycachePrefix: stagingDir
10071
+ });
10072
+ compileSpan.setAttributes({
10073
+ "python.compileall.enabled": "true",
10074
+ "python.compileall.mode": "pycache-prefix",
10075
+ "python.compileall.sitePackageDirectoryCount": String(
10076
+ sitePackageDirs.length
10077
+ )
10078
+ });
10079
+ });
10080
+ const beforeCount = Object.keys(files).length;
10081
+ const appInfo = await collectAppPrefixBytecodeFiles({
10082
+ stagingDir,
10083
+ workPath,
10084
+ files,
10085
+ runtimeTaskRoot: "/var/task",
10086
+ pythonMajor: pyMajor,
10087
+ pythonMinor: pyMinor
10088
+ });
10089
+ remainingCapacity = addBytecodeWithinCapacity(
10090
+ files,
10091
+ appInfo,
10092
+ remainingCapacity
10093
+ );
10094
+ const alwaysBundled = bundleResult.alwaysBundledPackages ?? [];
10095
+ remainingCapacity = await addCollectedVendorBytecode({
10096
+ files,
10097
+ capacity: remainingCapacity,
10098
+ collect: (include) => depExternalizer.collectPrefixBytecodeFiles({
10099
+ stagingDir,
10100
+ runtimeRoot: `/var/task/${vendorDir}`,
10101
+ includePackages: include ?? alwaysBundled
10102
+ })
10103
+ });
10104
+ const externalized = bundleResult.externalizedPublicPackages ?? [];
10105
+ await addCollectedVendorBytecode({
10106
+ files,
10107
+ capacity: remainingCapacity,
10108
+ collect: (include) => depExternalizer.collectPrefixBytecodeFiles({
10109
+ stagingDir,
10110
+ runtimeRoot: `${RUNTIME_DEPS_DIR}/lib/python${pyMajor}.${pyMinor}/site-packages`,
10111
+ includePackages: include ?? externalized
10112
+ })
10113
+ });
10114
+ if (Object.keys(files).length > beforeCount) {
10115
+ lambdaEnv.PYTHONPYCACHEPREFIX = RUNTIME_PYCACHE_PREFIX;
10116
+ }
10117
+ } catch (err) {
10118
+ console.log(
10119
+ "Bytecode precompilation failed; continuing without precompiled bytecode."
10120
+ );
10121
+ (0, import_build_utils20.debug)(`bytecode precompilation error details: ${err}`);
9637
10122
  }
9638
10123
  };
9639
10124
  const announceLargeFunction = () => console.log(
9640
- `Function "${entrypoint}" exceeds the standard size limit; enabling large functions (beta).`
10125
+ `Function "${entrypoint ?? rawEntrypoint}" exceeds the standard size limit; enabling large functions (beta).`
9641
10126
  );
9642
10127
  if (depAnalysis.runtimeInstallEnabled) {
9643
- const { fellBackToFullBundle } = await depExternalizer.generateBundle(files);
9644
- if (fellBackToFullBundle) {
10128
+ const bytecodeFirst = compileAllEnabled && pythonVersion.major != null && pythonVersion.minor != null;
10129
+ const bundleResult = await depExternalizer.generateBundle(files, {
10130
+ bytecodeFirst
10131
+ });
10132
+ if (bundleResult.fellBackToFullBundle) {
9645
10133
  announceLargeFunction();
9646
10134
  if (compileAllEnabled) {
9647
10135
  await runCompileAllAndFillBytecode(
9648
10136
  MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
9649
10137
  );
9650
10138
  }
10139
+ } else if (bundleResult.packingMode === "bytecode-first") {
10140
+ await runPrefixCompileAndFill(bundleResult);
10141
+ } else if (compileAllEnabled) {
10142
+ const currentSize = await calculateBundleSize(files);
10143
+ const capacity = BYTECODE_FILL_CEILING_BYTES - currentSize;
10144
+ const estimate = await estimateBytecodeSize(files);
10145
+ if (capacity >= BYTECODE_COVERAGE_FLOOR * estimate) {
10146
+ await runCompileAllAndFillBytecode(BYTECODE_FILL_CEILING_BYTES, [
10147
+ bundleResult.alwaysBundledPackages ?? [],
10148
+ bundleResult.bundledPublicPackages ?? []
10149
+ ]);
10150
+ }
9651
10151
  }
9652
10152
  } else {
9653
10153
  addFiles(files, depAnalysis.allVendorFiles);
@@ -9669,23 +10169,26 @@ var build = async ({
9669
10169
  }
9670
10170
  }
9671
10171
  });
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
- });
10172
+ let output;
10173
+ if (entrypoint && runtimeTrampoline) {
10174
+ const webFiles = {
10175
+ ...files,
10176
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({ data: runtimeTrampoline })
10177
+ };
10178
+ const lambdaOptions = await getPythonLambdaOptions({
10179
+ config,
10180
+ entrypoint
10181
+ });
10182
+ output = new import_build_utils20.Lambda({
10183
+ files: webFiles,
10184
+ handler: `${handlerPyFilename}.vc_handler`,
10185
+ runtime: pythonVersion.runtime,
10186
+ ...lambdaOptions,
10187
+ architecture: target.architecture,
10188
+ environment: lambdaEnv,
10189
+ supportsResponseStreaming: true
10190
+ });
10191
+ }
9689
10192
  const subscriberLambdas = {};
9690
10193
  for (const subscriber of subscribers) {
9691
10194
  const outputPath = getSubscriberOutputPath(subscriber.name);
@@ -9698,10 +10201,10 @@ var build = async ({
9698
10201
  ...subscriber.triggerDefaults
9699
10202
  })
9700
10203
  );
9701
- subscriberLambdas[outputPath] = new import_build_utils19.Lambda({
10204
+ subscriberLambdas[outputPath] = new import_build_utils20.Lambda({
9702
10205
  files: {
9703
10206
  ...files,
9704
- [`${handlerPyFilename}.py`]: new import_build_utils19.FileBlob({
10207
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({
9705
10208
  data: createRuntimeTrampoline({
9706
10209
  moduleName: subscriber.moduleName,
9707
10210
  entrypoint: subscriber.entrypoint,
@@ -9723,6 +10226,41 @@ var build = async ({
9723
10226
  supportsResponseStreaming: true
9724
10227
  });
9725
10228
  }
10229
+ const workflowLambdas = {};
10230
+ for (const workflow of workflows) {
10231
+ const outputPath = getWorkflowOutputPath(workflow.name);
10232
+ const experimentalTriggers = [
10233
+ {
10234
+ type: "queue/v2beta",
10235
+ topic: WORKFLOW_TOPIC_PATTERN,
10236
+ consumer: getWorkflowConsumerName(workflow.name)
10237
+ }
10238
+ ];
10239
+ workflowLambdas[outputPath] = new import_build_utils20.Lambda({
10240
+ files: {
10241
+ ...files,
10242
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({
10243
+ data: createRuntimeTrampoline({
10244
+ moduleName: workflow.moduleName,
10245
+ entrypoint: workflow.entrypoint,
10246
+ vendorDir,
10247
+ variableName: workflow.variableName
10248
+ })
10249
+ })
10250
+ },
10251
+ handler: `${handlerPyFilename}.vc_handler`,
10252
+ runtime: pythonVersion.runtime,
10253
+ architecture: target.architecture,
10254
+ environment: {
10255
+ ...lambdaEnv,
10256
+ VERCEL_HAS_WORKER_SERVICES: "1",
10257
+ // Compatibility marker consumed by the current Python runtime.
10258
+ VERCEL_SERVICE_TYPE: "worker"
10259
+ },
10260
+ experimentalTriggers,
10261
+ supportsResponseStreaming: true
10262
+ });
10263
+ }
9726
10264
  if (uvLockPath && !meta.isDev) {
9727
10265
  try {
9728
10266
  await generateProjectManifest({
@@ -9731,21 +10269,22 @@ var build = async ({
9731
10269
  pythonVersion,
9732
10270
  uvLockPath,
9733
10271
  framework,
9734
- serviceType: service ? (0, import_build_utils19.getReportedServiceType)(service) : void 0
10272
+ serviceType: service ? (0, import_build_utils20.getReportedServiceType)(service) : void 0
9735
10273
  });
9736
10274
  } catch (err) {
9737
- (0, import_build_utils19.debug)(
10275
+ (0, import_build_utils20.debug)(
9738
10276
  `Failed to write project manifest: ${err instanceof Error ? err.message : String(err)}`
9739
10277
  );
9740
10278
  }
9741
10279
  }
9742
- if (!(0, import_build_utils19.isPythonFramework)(framework) && !service?.name) {
10280
+ if (!(0, import_build_utils20.isPythonFramework)(framework) && !service?.name && !isPyprojectEntrypoint) {
10281
+ (0, import_assert.default)(output, "web Lambda must exist for non-service builds");
9743
10282
  return { resultVersion: 3, result: { output } };
9744
10283
  }
9745
10284
  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 }) : {};
10285
+ const staticFiles = djangoStatic?.cdnOutputDir ? await (0, import_build_utils20.glob)("**", { cwd: djangoStatic.cdnOutputDir }) : {};
9747
10286
  const isNonWebService = service?.name && service.type && service.type !== "web";
9748
- const routes = isNonWebService ? void 0 : [
10287
+ const routes = isNonWebService || !output ? void 0 : [
9749
10288
  { handle: "filesystem" },
9750
10289
  { src: "/(.*)", dest: `/${lambdaPath}` }
9751
10290
  ];
@@ -9753,8 +10292,9 @@ var build = async ({
9753
10292
  resultVersion: 2,
9754
10293
  result: {
9755
10294
  output: {
9756
- [lambdaPath]: output,
10295
+ ...output ? { [lambdaPath]: output } : {},
9757
10296
  ...subscriberLambdas,
10297
+ ...workflowLambdas,
9758
10298
  ...staticFiles
9759
10299
  },
9760
10300
  ...routes ? { routes } : {},
@@ -9777,14 +10317,14 @@ var prepareCache = async ({
9777
10317
  }
9778
10318
  } catch {
9779
10319
  }
9780
- return (0, import_build_utils19.glob)("**/.vercel/python/{.venv,services/*/.venv,cache/uv}/**", {
10320
+ return (0, import_build_utils20.glob)("**/.vercel/python/{.venv,services/*/.venv,cache/uv}/**", {
9781
10321
  cwd: root,
9782
10322
  ignore
9783
10323
  });
9784
10324
  };
9785
10325
  var shouldServe = (opts) => {
9786
10326
  const framework = opts.config.framework;
9787
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
10327
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9788
10328
  const requestPath = opts.requestPath.replace(/\/$/, "");
9789
10329
  if (requestPath.startsWith("api") && opts.hasMatched) {
9790
10330
  return false;
@@ -9803,7 +10343,7 @@ var defaultShouldServe = ({
9803
10343
  if (entrypoint === requestPath && hasProp(files, entrypoint)) {
9804
10344
  return true;
9805
10345
  }
9806
- const { dir, name } = (0, import_path15.parse)(entrypoint);
10346
+ const { dir, name } = (0, import_path17.parse)(entrypoint);
9807
10347
  if (name === "index" && dir === requestPath && hasProp(files, entrypoint)) {
9808
10348
  return true;
9809
10349
  }
@@ -9814,6 +10354,8 @@ function hasProp(obj, key) {
9814
10354
  }
9815
10355
  // Annotate the CommonJS export names for ESM import in node:
9816
10356
  0 && (module.exports = {
10357
+ addCollectedVendorBytecode,
10358
+ addVendorBytecodeInTiers,
9817
10359
  build,
9818
10360
  defaultShouldServe,
9819
10361
  detectEntrypoint,