@vercel/python 6.48.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 +1021 -323
  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,11 +4361,14 @@ 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,
4367
4369
  diagnostics: () => diagnostics,
4368
4370
  downloadFilesInWorkPath: () => downloadFilesInWorkPath,
4371
+ getDevSidecars: () => getDevSidecars,
4369
4372
  installRequirement: () => installRequirement,
4370
4373
  installRequirementsFile: () => installRequirementsFile,
4371
4374
  prepareCache: () => prepareCache,
@@ -4376,15 +4379,15 @@ __export(src_exports, {
4376
4379
  });
4377
4380
  module.exports = __toCommonJS(src_exports);
4378
4381
  var import_assert = __toESM(require("assert"));
4379
- var import_fs15 = __toESM(require("fs"));
4380
- var import_path15 = require("path");
4382
+ var import_fs17 = __toESM(require("fs"));
4383
+ var import_path17 = require("path");
4381
4384
 
4382
4385
  // src/package-versions.ts
4383
4386
  var VERCEL_RUNTIME_VERSION = "0.16.0";
4384
4387
  var VERCEL_WORKERS_VERSION = "0.0.25";
4385
4388
 
4386
4389
  // src/index.ts
4387
- var import_build_utils19 = require("@vercel/build-utils");
4390
+ var import_build_utils20 = require("@vercel/build-utils");
4388
4391
 
4389
4392
  // src/install.ts
4390
4393
  var import_execa3 = __toESM(require_execa());
@@ -5649,39 +5652,35 @@ var import_execa4 = __toESM(require_execa());
5649
5652
  var import_build_utils6 = require("@vercel/build-utils");
5650
5653
  var import_fs5 = __toESM(require("fs"));
5651
5654
  var import_path6 = require("path");
5652
-
5653
- // src/large-functions.ts
5654
- function isLargeFunctionsEnabled() {
5655
- const value = process.env.VERCEL_SUPPORT_LARGE_FUNCTIONS;
5656
- return value === "1" || value === "true";
5657
- }
5658
-
5659
- // src/compileall.ts
5660
- function isCompileAllEnabled() {
5661
- if (!isLargeFunctionsEnabled())
5662
- return false;
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}`;
5658
+ function isCompileAllFlagEnabled() {
5663
5659
  const val = process.env.VERCEL_PYTHON_COMPILEALL;
5664
5660
  if (val === void 0 || val === "")
5665
5661
  return false;
5666
5662
  const lower = val.toLowerCase();
5667
5663
  return lower === "1" || lower === "true";
5668
5664
  }
5669
- function shouldUseCompileAll({
5665
+ function shouldCompileAll({
5670
5666
  isDev,
5671
5667
  hasCustomCommand,
5672
- hasCustomBuildCommand
5668
+ hasPreDeployCommand
5673
5669
  }) {
5674
5670
  if (isDev)
5675
5671
  return false;
5676
- if (hasCustomCommand || hasCustomBuildCommand)
5672
+ if (hasCustomCommand)
5677
5673
  return false;
5678
- return isCompileAllEnabled();
5674
+ if (hasPreDeployCommand)
5675
+ return false;
5676
+ return isCompileAllFlagEnabled();
5679
5677
  }
5680
5678
  async function runCompileAll({
5681
5679
  pythonBin,
5682
5680
  filesOrDirectories,
5683
5681
  env,
5684
- excludeRegex
5682
+ excludeRegex,
5683
+ pycachePrefix
5685
5684
  }) {
5686
5685
  if (filesOrDirectories.length === 0)
5687
5686
  return;
@@ -5697,8 +5696,13 @@ async function runCompileAll({
5697
5696
  ...excludeRegex ? ["-x", excludeRegex] : [],
5698
5697
  ...filesOrDirectories
5699
5698
  ];
5699
+ const baseEnv = env || process.env;
5700
+ const subprocessEnv = pycachePrefix ? { ...baseEnv, PYTHONPYCACHEPREFIX: pycachePrefix } : baseEnv;
5700
5701
  try {
5701
- await (0, import_execa4.default)(pythonBin, args, { env: env || process.env });
5702
+ await (0, import_execa4.default)(pythonBin, args, {
5703
+ env: subprocessEnv,
5704
+ timeout: COMPILEALL_TIMEOUT_MS
5705
+ });
5702
5706
  } catch (err) {
5703
5707
  (0, import_build_utils6.debug)(`compileall error details: ${JSON.stringify(err)}`);
5704
5708
  }
@@ -5711,6 +5715,42 @@ function derivePycPath(pyRelPath, pythonMajor, pythonMinor) {
5711
5715
  const baseName = pyRelPath.slice(lastSlash + 1, -3);
5712
5716
  return `${dir}__pycache__/${baseName}.cpython-${pythonMajor}${pythonMinor}.pyc`;
5713
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
+ }
5714
5754
  var COMPILEALL_APP_EXCLUDED_DIRS = [
5715
5755
  ".git",
5716
5756
  ".vercel",
@@ -5732,6 +5772,58 @@ function getCompileAllAppExcludeRegex(workPath) {
5732
5772
  const excludedDirs = COMPILEALL_APP_EXCLUDED_DIRS.map(escapePythonRegex).join("|");
5733
5773
  return `${escapePythonRegex(workPath)}[/\\\\](?:${excludedDirs})(?:[/\\\\]|$)`;
5734
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
+ }
5735
5827
  async function collectAppBytecodeFiles({
5736
5828
  workPath,
5737
5829
  files: appFiles,
@@ -5774,6 +5866,12 @@ async function collectAppBytecodeFiles({
5774
5866
  return { files, totalSize, perItemSizes };
5775
5867
  }
5776
5868
 
5869
+ // src/large-functions.ts
5870
+ function isLargeFunctionsEnabled() {
5871
+ const value = process.env.VERCEL_SUPPORT_LARGE_FUNCTIONS;
5872
+ return value === "1" || value === "true";
5873
+ }
5874
+
5777
5875
  // src/dependency-externalizer.ts
5778
5876
  var readFile = (0, import_util.promisify)(import_fs6.default.readFile);
5779
5877
  var STRIP_BASENAMES = /* @__PURE__ */ new Set([
@@ -5794,7 +5892,11 @@ function shouldStripVendorFile(filePath) {
5794
5892
  return false;
5795
5893
  }
5796
5894
  var LAMBDA_SIZE_THRESHOLD_BYTES = 225 * 1024 * 1024;
5895
+ var BYTECODE_FILL_MARGIN_BYTES = 5 * 1024 * 1024;
5896
+ var BYTECODE_FILL_CEILING_BYTES = LAMBDA_SIZE_THRESHOLD_BYTES - BYTECODE_FILL_MARGIN_BYTES;
5797
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";
5798
5900
  var MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE = 5 * 1024 * 1024 * 1024;
5799
5901
  var BUNDLING_DOCS_LINK = "https://vercel.com/docs/functions/runtimes/python#controlling-what-gets-bundled";
5800
5902
  var PythonDependencyExternalizer = class {
@@ -5905,7 +6007,7 @@ application into smaller functions.`,
5905
6007
  * back to bundling everything for Hive and returns `fellBackToFullBundle:
5906
6008
  * true` so the caller can apply the same compileall handling.
5907
6009
  */
5908
- async generateBundle(files) {
6010
+ async generateBundle(files, options = {}) {
5909
6011
  if (!this.analyzed) {
5910
6012
  throw new Error(
5911
6013
  "PythonDependencyExternalizer.analyze() must be called before generateBundle()"
@@ -6066,7 +6168,20 @@ To fix this, either:
6066
6168
  ([name]) => externalizableSet.has((0, import_python_analysis3.normalizePackageName)(name))
6067
6169
  )
6068
6170
  );
6069
- 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
+ );
6070
6185
  const allBundledPackages = [...alwaysBundled, ...bundledPublic];
6071
6186
  const selectedVendorFiles = await this.mirrorPackagesIntoVendor({
6072
6187
  vendorDirName: this.vendorDir,
@@ -6138,7 +6253,13 @@ functions.`,
6138
6253
  action: "Learn More"
6139
6254
  });
6140
6255
  }
6141
- return { fellBackToFullBundle: false };
6256
+ return {
6257
+ fellBackToFullBundle: false,
6258
+ packingMode,
6259
+ alwaysBundledPackages: alwaysBundled,
6260
+ bundledPublicPackages: bundledPublic,
6261
+ externalizedPublicPackages: externalizedPublic
6262
+ };
6142
6263
  }
6143
6264
  /**
6144
6265
  * Discard any runtime-install artifacts added so far and bundle every
@@ -6451,6 +6572,92 @@ functions.`,
6451
6572
  );
6452
6573
  return { files, totalSize, perItemSizes };
6453
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
+ }
6454
6661
  };
6455
6662
  async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor, pythonMinor, sysPlatform, platformMachine) {
6456
6663
  if (!projectName)
@@ -6510,10 +6717,12 @@ async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor
6510
6717
  }
6511
6718
  return visited;
6512
6719
  }
6513
- async function calculateBundleSize(files) {
6720
+ async function calculateBundleSize(files, filter) {
6514
6721
  let knownSize = 0;
6515
6722
  const statPromises = [];
6516
6723
  for (const filePath of Object.keys(files)) {
6724
+ if (filter && !filter(filePath))
6725
+ continue;
6517
6726
  const file = files[filePath];
6518
6727
  if ("fsPath" in file && file.fsPath) {
6519
6728
  const fsRef = file;
@@ -6521,7 +6730,10 @@ async function calculateBundleSize(files) {
6521
6730
  knownSize += fsRef.size;
6522
6731
  } else {
6523
6732
  statPromises.push(
6524
- import_fs6.default.promises.stat(fsRef.fsPath).then((stats) => stats.size).catch((err) => {
6733
+ import_fs6.default.promises.stat(fsRef.fsPath).then((stats) => {
6734
+ fsRef.size = stats.size;
6735
+ return stats.size;
6736
+ }).catch((err) => {
6525
6737
  console.warn(
6526
6738
  `Warning: Failed to stat file ${fsRef.fsPath}, size will not be included in bundle calculation: ${err}`
6527
6739
  );
@@ -6541,6 +6753,12 @@ async function calculateBundleSize(files) {
6541
6753
  }
6542
6754
  return totalSize;
6543
6755
  }
6756
+ var PYC_TO_PY_RATIO = 1.2;
6757
+ var BYTECODE_COVERAGE_FLOOR = 0.5;
6758
+ async function estimateBytecodeSize(files) {
6759
+ const pyBytes = await calculateBundleSize(files, (p) => p.endsWith(".py"));
6760
+ return PYC_TO_PY_RATIO * pyBytes;
6761
+ }
6544
6762
  function lambdaKnapsack(packages, capacity) {
6545
6763
  if (capacity <= 0) {
6546
6764
  return [];
@@ -6556,6 +6774,23 @@ function lambdaKnapsack(packages, capacity) {
6556
6774
  }
6557
6775
  return bundled;
6558
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
+ }
6559
6794
 
6560
6795
  // src/diagnostics.ts
6561
6796
  var import_fs7 = __toESM(require("fs"));
@@ -6870,7 +7105,7 @@ function getMissingExportMessage(framework, entrypoints) {
6870
7105
  }
6871
7106
  }
6872
7107
  function entrypointToModule(entrypoint) {
6873
- return entrypoint.replace(/\\/g, "/").replace(/\.py$/i, "").replace(/\//g, ".");
7108
+ return entrypoint.replace(/\\/g, "/").replace(/\.py$/i, "").replace(/\/__init__$/i, "").replace(/\//g, ".");
6874
7109
  }
6875
7110
  async function checkEntrypoint(workPath, relPath) {
6876
7111
  const absPath = (0, import_path8.join)(workPath, relPath);
@@ -6946,14 +7181,37 @@ async function resolveModuleAttrEntrypoint(workPath, value) {
6946
7181
  }
6947
7182
  return null;
6948
7183
  }
6949
- async function getVercelToolsEntrypoint(workPath) {
6950
- const pyprojectData = await (0, import_build_utils11.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
7184
+ async function getVercelToolsEntrypoint(workPath, repoRootPath) {
7185
+ const pyprojectPath = (0, import_path8.join)(workPath, "pyproject.toml");
7186
+ const pyprojectData = await (0, import_build_utils11.readConfigFile)(pyprojectPath);
6951
7187
  if (!pyprojectData)
6952
7188
  return null;
6953
7189
  const vercelEntrypoint = pyprojectData.tool?.vercel?.entrypoint;
6954
- if (typeof vercelEntrypoint !== "string")
7190
+ if (vercelEntrypoint === void 0)
6955
7191
  return null;
6956
- return resolveModuleAttrEntrypoint(workPath, vercelEntrypoint);
7192
+ const relPyprojectPath = (0, import_path8.relative)(repoRootPath ?? workPath, pyprojectPath);
7193
+ const displayPath = !relPyprojectPath || relPyprojectPath.startsWith("..") ? pyprojectPath : relPyprojectPath;
7194
+ if (typeof vercelEntrypoint !== "string") {
7195
+ throw new import_build_utils9.NowBuildError({
7196
+ code: "PYTHON_INVALID_ENTRYPOINT",
7197
+ message: `"tool.vercel.entrypoint" in "${displayPath}" must be a string in "module:object" format (e.g. "main:app").`,
7198
+ link: PYTHON_ENTRYPOINT_DOCS_URL,
7199
+ action: "Learn More"
7200
+ });
7201
+ }
7202
+ const resolved = await resolveModuleAttrEntrypoint(
7203
+ workPath,
7204
+ vercelEntrypoint
7205
+ );
7206
+ if (!resolved) {
7207
+ throw new import_build_utils9.NowBuildError({
7208
+ code: "PYTHON_ENTRYPOINT_NOT_FOUND",
7209
+ message: `"tool.vercel.entrypoint" in "${displayPath}" is "${vercelEntrypoint}" but no matching module file was found. Use "module:object" format (e.g. "main:app") and ensure the module file exists, or remove the setting to use automatic entrypoint detection.`,
7210
+ link: PYTHON_ENTRYPOINT_DOCS_URL,
7211
+ action: "Learn More"
7212
+ });
7213
+ }
7214
+ return resolved;
6957
7215
  }
6958
7216
  async function getPyprojectEntrypointWithDiagnostics(workPath) {
6959
7217
  const pyprojectData = await (0, import_build_utils11.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
@@ -7107,7 +7365,7 @@ async function detectDjangoPythonEntrypoint(workPath) {
7107
7365
  return emptyResult;
7108
7366
  }
7109
7367
  }
7110
- async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service) {
7368
+ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service, repoRootPath) {
7111
7369
  if (configuredEntrypoint) {
7112
7370
  const { filePath: configEntryFile, varName: configEntryVar } = configuredEntrypoint;
7113
7371
  const entrypoint = configEntryFile.endsWith(".py") ? configEntryFile : `${configEntryFile}.py`;
@@ -7146,7 +7404,7 @@ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint,
7146
7404
  if (!framework) {
7147
7405
  return null;
7148
7406
  }
7149
- const vercelEntry = await getVercelToolsEntrypoint(workPath);
7407
+ const vercelEntry = await getVercelToolsEntrypoint(workPath, repoRootPath);
7150
7408
  if (vercelEntry)
7151
7409
  return { entrypoint: vercelEntry };
7152
7410
  let findDiagnostics;
@@ -7186,10 +7444,18 @@ var detectEntrypoint = async ({
7186
7444
  }) => {
7187
7445
  if (!(0, import_build_utils9.isPythonFramework)(framework))
7188
7446
  return null;
7189
- const detected = await detectPythonEntrypoint(
7190
- framework,
7191
- workPath
7192
- );
7447
+ let detected;
7448
+ try {
7449
+ detected = await detectPythonEntrypoint(
7450
+ framework,
7451
+ workPath
7452
+ );
7453
+ } catch (err) {
7454
+ (0, import_build_utils10.debug)(
7455
+ `Python entrypoint detection failed for ${workPath}: ${err instanceof Error ? err.message : String(err)}`
7456
+ );
7457
+ return null;
7458
+ }
7193
7459
  if (!detected?.entrypoint)
7194
7460
  return null;
7195
7461
  const { entrypoint, variableName } = detected.entrypoint;
@@ -7415,6 +7681,23 @@ async function getReachableHost(port) {
7415
7681
  ]);
7416
7682
  return results.find(Boolean) || false;
7417
7683
  }
7684
+ async function dedupePendingOperation(operations, key, operation) {
7685
+ const existing = operations.get(key);
7686
+ if (existing) {
7687
+ return existing;
7688
+ }
7689
+ const pending = operation();
7690
+ operations.set(key, pending);
7691
+ try {
7692
+ return await pending;
7693
+ } finally {
7694
+ if (operations.get(key) === pending) {
7695
+ operations.delete(key);
7696
+ }
7697
+ }
7698
+ }
7699
+ var PENDING_MANAGED_VENV_CREATIONS = /* @__PURE__ */ new Map();
7700
+ var PENDING_DEPENDENCY_SYNCS = /* @__PURE__ */ new Map();
7418
7701
  async function syncDependencies({
7419
7702
  workPath,
7420
7703
  uvPath,
@@ -7558,16 +7841,29 @@ async function runSync({
7558
7841
  });
7559
7842
  }
7560
7843
  var PENDING_INSTALLS = /* @__PURE__ */ new Map();
7844
+ var COMPLETED_INSTALLS = /* @__PURE__ */ new Set();
7845
+ function hasInstalledDistribution(targetDir, packageName) {
7846
+ const prefix = `${packageName.replace("-", "_")}-`;
7847
+ try {
7848
+ return (0, import_fs10.readdirSync)(targetDir).some(
7849
+ (entry) => entry.startsWith(prefix) && entry.endsWith(".dist-info")
7850
+ );
7851
+ } catch {
7852
+ return false;
7853
+ }
7854
+ }
7561
7855
  async function installInjectedDevPackage(pkg, opts) {
7562
7856
  const targetDir = (0, import_path10.join)(opts.workPath, ".vercel", "python");
7563
- const key = `${targetDir}:${pkg.name}`;
7564
- let pending = PENDING_INSTALLS.get(key);
7565
- if (!pending) {
7566
- pending = doInstallInjectedDevPackage(pkg, { ...opts, targetDir });
7567
- PENDING_INSTALLS.set(key, pending);
7568
- pending.finally(() => PENDING_INSTALLS.delete(key));
7857
+ const source = pkg.envOverride || pkg.pinnedVersion;
7858
+ const key = `${targetDir}:${pkg.name}:${source}`;
7859
+ if (COMPLETED_INSTALLS.has(key) && hasInstalledDistribution(targetDir, pkg.name)) {
7860
+ return;
7569
7861
  }
7570
- await pending;
7862
+ COMPLETED_INSTALLS.delete(key);
7863
+ await dedupePendingOperation(PENDING_INSTALLS, key, async () => {
7864
+ await doInstallInjectedDevPackage(pkg, { ...opts, targetDir });
7865
+ COMPLETED_INSTALLS.add(key);
7866
+ });
7571
7867
  }
7572
7868
  async function doInstallInjectedDevPackage(pkg, opts) {
7573
7869
  const { targetDir, workPath, uvPath, pythonBin, env, onStdout, onStderr } = opts;
@@ -7707,18 +8003,26 @@ function createDevShim(workPath, entry, modulePath, serviceName, framework, vari
7707
8003
  }
7708
8004
  }
7709
8005
  async function getMultiServicePythonRunner(workPath, env, systemPython, uvPath) {
8006
+ const venvPath = (0, import_path10.join)(workPath, ".venv");
8007
+ const pendingCreation = PENDING_MANAGED_VENV_CREATIONS.get(venvPath);
8008
+ if (pendingCreation) {
8009
+ await pendingCreation;
8010
+ }
7710
8011
  const { pythonCmd, venvRoot } = useVirtualEnv(workPath, env, systemPython);
7711
8012
  if (venvRoot) {
7712
8013
  (0, import_build_utils13.debug)(`Using existing virtualenv at ${venvRoot} for multi-service dev`);
7713
8014
  return { command: pythonCmd, args: [] };
7714
8015
  }
7715
- const venvPath = (0, import_path10.join)(workPath, ".venv");
7716
- await ensureVenv({
7717
- pythonVersion: { pythonPath: systemPython },
8016
+ await dedupePendingOperation(
8017
+ PENDING_MANAGED_VENV_CREATIONS,
7718
8018
  venvPath,
7719
- uvPath,
7720
- quiet: true
7721
- });
8019
+ () => ensureVenv({
8020
+ pythonVersion: { pythonPath: systemPython },
8021
+ venvPath,
8022
+ uvPath,
8023
+ quiet: true
8024
+ })
8025
+ );
7722
8026
  (0, import_build_utils13.debug)(`Created virtualenv at ${venvPath} for multi-service dev`);
7723
8027
  const pythonBin = getVenvPythonBin(venvPath);
7724
8028
  const binDir = getVenvBinDir(venvPath);
@@ -7779,7 +8083,8 @@ var startDevServer = async (opts) => {
7779
8083
  // Other services use handlerFunction as the entrypoint variable name.
7780
8084
  varName: service && (0, import_build_utils13.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
7781
8085
  } : void 0,
7782
- service
8086
+ service,
8087
+ opts.repoRootPath
7783
8088
  );
7784
8089
  let hookResult;
7785
8090
  if (detected?.entrypoint) {
@@ -7893,7 +8198,11 @@ If you are using a virtual environment, activate it before running "vercel dev",
7893
8198
  } else {
7894
8199
  console.log(syncMessage);
7895
8200
  }
7896
- await syncDependencies(devOpts);
8201
+ await dedupePendingOperation(
8202
+ PENDING_DEPENDENCY_SYNCS,
8203
+ workPath,
8204
+ () => syncDependencies(devOpts)
8205
+ );
7897
8206
  }
7898
8207
  await installInjectedDevPackage(
7899
8208
  {
@@ -8584,11 +8893,55 @@ async function runDjangoCollectStatic(venvPath, workPath, djangoPath, env, outpu
8584
8893
  var import_python_analysis9 = require("@vercel/python-analysis");
8585
8894
 
8586
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
8587
8901
  var import_path14 = require("path");
8588
8902
  var import_fs14 = __toESM(require("fs"));
8589
- var import_build_utils18 = require("@vercel/build-utils");
8590
- var SUBSCRIBER_NAME_RE = /^[A-Za-z]([A-Za-z0-9_-]*[A-Za-z0-9])?$/;
8591
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
8944
+ var SUBSCRIBER_OUTPUT_DIR = "_py_subscribers";
8592
8945
  var TRIGGER_NUMBER_FIELDS = [
8593
8946
  {
8594
8947
  field: "max_deliveries",
@@ -8620,17 +8973,15 @@ var SUBSCRIBER_FIELD_NAMES = /* @__PURE__ */ new Set([
8620
8973
  "topics",
8621
8974
  ...TRIGGER_NUMBER_FIELDS.map(({ field }) => field)
8622
8975
  ]);
8623
- function safePathSegment(value) {
8624
- return [...value].map((char) => {
8625
- if (char === "_") {
8626
- return "__";
8627
- }
8628
- return /[A-Za-z0-9-]/.test(char) ? char : `_${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
8629
- }).join("");
8976
+ function getSubscriberOutputPath(subscriberName) {
8977
+ return `${SUBSCRIBER_OUTPUT_DIR}/${safePathSegment(subscriberName)}`;
8978
+ }
8979
+ function getSubscriberConsumerName(subscriberName) {
8980
+ return (0, import_build_utils18.sanitizeConsumerName)(getSubscriberOutputPath(subscriberName));
8630
8981
  }
8631
8982
  async function getPyprojectSubscribers(workPath) {
8632
- const pyprojectPath = (0, import_path14.join)(workPath, "pyproject.toml");
8633
- if (!import_fs14.default.existsSync(pyprojectPath)) {
8983
+ const pyprojectPath = (0, import_path15.join)(workPath, "pyproject.toml");
8984
+ if (!import_fs15.default.existsSync(pyprojectPath)) {
8634
8985
  return [];
8635
8986
  }
8636
8987
  const pyproject = await (0, import_build_utils18.readConfigFile)(pyprojectPath);
@@ -8638,37 +8989,43 @@ async function getPyprojectSubscribers(workPath) {
8638
8989
  if (!subscribers) {
8639
8990
  return [];
8640
8991
  }
8641
- if (typeof subscribers !== "object" || Array.isArray(subscribers)) {
8642
- throw subscriberError('"tool.vercel.subscribers" must be an object');
8992
+ if (!Array.isArray(subscribers)) {
8993
+ throw subscriberError('"tool.vercel.subscribers" must be an array');
8643
8994
  }
8644
- return Promise.all(
8645
- Object.entries(subscribers).map(
8646
- ([name, config]) => parseSubscriber(workPath, name, config)
8647
- )
8995
+ const parsedSubscribers = await Promise.all(
8996
+ subscribers.map((config, index) => parseSubscriber(workPath, index, config))
8648
8997
  );
8649
- }
8650
- async function parseSubscriber(workPath, name, config) {
8651
- if (!SUBSCRIBER_NAME_RE.test(name)) {
8652
- throw subscriberError(
8653
- `subscriber name "${name}" is invalid. Names must start with a letter, end with an alphanumeric character, and contain only alphanumeric characters, hyphens, and underscores`
8654
- );
8998
+ const seenNames = /* @__PURE__ */ new Set();
8999
+ for (const subscriber of parsedSubscribers) {
9000
+ if (seenNames.has(subscriber.name)) {
9001
+ throw subscriberError(
9002
+ `subscriber "${subscriber.name}" is declared more than once`
9003
+ );
9004
+ }
9005
+ seenNames.add(subscriber.name);
8655
9006
  }
9007
+ return parsedSubscribers;
9008
+ }
9009
+ async function parseSubscriber(workPath, index, config) {
9010
+ const label = `subscriber #${index + 1}`;
8656
9011
  if (!config || typeof config !== "object" || Array.isArray(config)) {
8657
- throw subscriberError(`subscriber "${name}" must be an object`);
9012
+ throw subscriberError(`${label} must be an object`);
8658
9013
  }
8659
9014
  for (const key of Object.keys(config)) {
8660
9015
  if (!SUBSCRIBER_FIELD_NAMES.has(key)) {
8661
- throw subscriberError(
8662
- `subscriber "${name}" has unrecognized field "${key}"`
8663
- );
9016
+ throw subscriberError(`${label} has unrecognized field "${key}"`);
8664
9017
  }
8665
9018
  }
8666
9019
  if (typeof config.entrypoint !== "string") {
9020
+ throw subscriberError(`${label} must define string field "entrypoint"`);
9021
+ }
9022
+ const entrypoint = parseModuleEntrypoint(config.entrypoint);
9023
+ if (!entrypoint) {
8667
9024
  throw subscriberError(
8668
- `subscriber "${name}" must define string field "entrypoint"`
9025
+ `${label} has invalid entrypoint "${config.entrypoint}". Use "module:object"`
8669
9026
  );
8670
9027
  }
8671
- const entrypoint = parseEntrypoint(name, config.entrypoint);
9028
+ const name = getModuleEntrypointName(entrypoint);
8672
9029
  const existingEntrypoint = await resolveExistingEntrypoint(
8673
9030
  workPath,
8674
9031
  entrypoint.filePath
@@ -8687,32 +9044,6 @@ async function parseSubscriber(workPath, name, config) {
8687
9044
  triggerDefaults: parseTriggerDefaults(name, config)
8688
9045
  };
8689
9046
  }
8690
- function parseEntrypoint(name, value) {
8691
- const match = MODULE_ATTR_RE.exec(value);
8692
- if (!match) {
8693
- throw subscriberError(
8694
- `subscriber "${name}" has invalid entrypoint "${value}". Use "module:object"`
8695
- );
8696
- }
8697
- return {
8698
- moduleName: match[1],
8699
- variableName: match[2],
8700
- filePath: `${match[1].replace(/\./g, "/")}.py`
8701
- };
8702
- }
8703
- async function resolveExistingEntrypoint(workPath, filePath) {
8704
- const candidates = [filePath, filePath.replace(/\.py$/i, "/__init__.py")];
8705
- for (const candidate of candidates) {
8706
- try {
8707
- const stat = await import_fs14.default.promises.stat((0, import_path14.join)(workPath, candidate));
8708
- if (stat.isFile()) {
8709
- return candidate;
8710
- }
8711
- } catch {
8712
- }
8713
- }
8714
- return null;
8715
- }
8716
9047
  function parseTopics(name, value) {
8717
9048
  if (!Array.isArray(value) || value.length === 0) {
8718
9049
  throw subscriberError(
@@ -8751,10 +9082,145 @@ function subscriberError(message) {
8751
9082
  });
8752
9083
  }
8753
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
+
8754
9163
  // src/index.ts
8755
- var writeFile = import_fs15.default.promises.writeFile;
9164
+ var writeFile = import_fs17.default.promises.writeFile;
8756
9165
  var PYTHON_ENTRYPOINT_DOCS_URL2 = "https://vercel.com/docs/functions/runtimes/python#python-entrypoints";
8757
9166
  var version = -1;
9167
+ function getDevSubscriberTopics(subscriber) {
9168
+ const { retryAfterSeconds, initialDelaySeconds } = subscriber.triggerDefaults;
9169
+ return subscriber.topics.map((topic) => ({
9170
+ topic,
9171
+ ...retryAfterSeconds === void 0 ? {} : { retryAfterSeconds },
9172
+ ...initialDelaySeconds === void 0 ? {} : { initialDelaySeconds }
9173
+ }));
9174
+ }
9175
+ async function getDevSidecars({
9176
+ workPath,
9177
+ build: build2
9178
+ }) {
9179
+ const framework = build2.config?.framework;
9180
+ if (build2.config?.middleware === true || typeof framework !== "string" || !(0, import_build_utils20.isPythonFramework)(framework)) {
9181
+ return [];
9182
+ }
9183
+ const subscribers = await getPyprojectSubscribers(workPath);
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
+ ];
9223
+ }
8758
9224
  function addFiles(target, source) {
8759
9225
  for (const [p, f] of Object.entries(source)) {
8760
9226
  target[p] = f;
@@ -8803,6 +9269,54 @@ async function addVendorBytecodeWithinCapacity({
8803
9269
  addFiles(files, selectedBytecode.files);
8804
9270
  return capacity - selectedBytecode.totalSize;
8805
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
+ }
8806
9320
  async function runFrameworkHook(framework, ctx) {
8807
9321
  const hook = framework ? frameworkHooks[framework] : void 0;
8808
9322
  return hook?.(ctx);
@@ -8816,13 +9330,13 @@ var frameworkHooks = {
8816
9330
  }) => {
8817
9331
  let baseDir = detected?.baseDir;
8818
9332
  if (baseDir === void 0) {
8819
- if (!import_fs15.default.existsSync((0, import_path15.join)(workPath, "manage.py"))) {
8820
- (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");
8821
9335
  return;
8822
9336
  }
8823
9337
  baseDir = "";
8824
9338
  }
8825
- const djangoPath = (0, import_path15.join)(workPath, baseDir);
9339
+ const djangoPath = (0, import_path17.join)(workPath, baseDir);
8826
9340
  let settingsResult;
8827
9341
  try {
8828
9342
  settingsResult = await getDjangoSettings(djangoPath, pythonEnv);
@@ -8834,13 +9348,13 @@ Hint: activate a venv or run with \`uv run vercel dev\``;
8834
9348
  } else {
8835
9349
  detail = err?.stderr || err?.message || String(err);
8836
9350
  }
8837
- throw new import_build_utils19.NowBuildError({
9351
+ throw new import_build_utils20.NowBuildError({
8838
9352
  code: "DJANGO_SETTINGS_FAILED",
8839
9353
  message: `Failed to read Django application settings from ${djangoPath}/manage.py:
8840
9354
  ${detail}`
8841
9355
  });
8842
9356
  }
8843
- (0, import_build_utils19.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
9357
+ (0, import_build_utils20.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
8844
9358
  const { djangoSettings, settingsModule, djangoVersion } = settingsResult;
8845
9359
  if (djangoVersion) {
8846
9360
  console.log(`Django ${djangoVersion.join(".")} detected`);
@@ -8852,7 +9366,7 @@ ${detail}`
8852
9366
  const variableName = parts.at(-1);
8853
9367
  const rel = `${parts.slice(0, -1).join("/")}.py`;
8854
9368
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
8855
- (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})`);
8856
9370
  resolvedEntrypoint = { entrypoint: ep, variableName };
8857
9371
  } else {
8858
9372
  const wsgiApp = djangoSettings["WSGI_APPLICATION"];
@@ -8861,7 +9375,7 @@ ${detail}`
8861
9375
  const variableName = parts.at(-1);
8862
9376
  const rel = `${parts.slice(0, -1).join("/")}.py`;
8863
9377
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
8864
- (0, import_build_utils19.debug)(
9378
+ (0, import_build_utils20.debug)(
8865
9379
  `Django hook: WSGI entrypoint: ${ep} (variable: ${variableName})`
8866
9380
  );
8867
9381
  resolvedEntrypoint = { entrypoint: ep, variableName };
@@ -8869,7 +9383,7 @@ ${detail}`
8869
9383
  }
8870
9384
  let djangoStatic = null;
8871
9385
  if (workPath && venvPath) {
8872
- const outputStaticDir = (0, import_path15.join)(workPath, ".vercel", "output", "static");
9386
+ const outputStaticDir = (0, import_path17.join)(workPath, ".vercel", "output", "static");
8873
9387
  djangoStatic = await runDjangoCollectStatic(
8874
9388
  venvPath,
8875
9389
  workPath,
@@ -8884,7 +9398,7 @@ ${detail}`
8884
9398
  return {
8885
9399
  entrypoint: resolvedEntrypoint,
8886
9400
  djangoStatic,
8887
- extraPythonPath: baseDir ? (0, import_path15.join)(workPath, baseDir) : void 0
9401
+ extraPythonPath: baseDir ? (0, import_path17.join)(workPath, baseDir) : void 0
8888
9402
  };
8889
9403
  }
8890
9404
  };
@@ -8943,23 +9457,23 @@ async function downloadFilesInWorkPath({
8943
9457
  files,
8944
9458
  meta = {}
8945
9459
  }) {
8946
- (0, import_build_utils19.debug)("Downloading user files...");
8947
- 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);
8948
9462
  if (meta.isDev && entrypoint) {
8949
9463
  const normalizedEntrypoint = entrypoint.endsWith(".py") ? entrypoint : `${entrypoint}.py`;
8950
9464
  if (!hasProp(downloadedFiles, entrypoint) && !hasProp(downloadedFiles, normalizedEntrypoint)) {
8951
- throw new import_build_utils19.NowBuildError({
9465
+ throw new import_build_utils20.NowBuildError({
8952
9466
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
8953
9467
  message: `Configured Python entrypoint "${normalizedEntrypoint}" was not found.`,
8954
9468
  link: PYTHON_ENTRYPOINT_DOCS_URL2,
8955
9469
  action: "Learn More"
8956
9470
  });
8957
9471
  }
8958
- const { devCacheDir = (0, import_path15.join)(workPath, ".now", "cache") } = meta;
8959
- const cacheKey = (0, import_path15.basename)(entrypoint).replace(/\./g, "_");
8960
- const destCache = (0, import_path15.join)(devCacheDir, cacheKey);
8961
- await (0, import_build_utils19.download)(downloadedFiles, destCache);
8962
- 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);
8963
9477
  workPath = destCache;
8964
9478
  }
8965
9479
  return workPath;
@@ -8995,7 +9509,7 @@ async function getPythonLambdaOptions({
8995
9509
  sources.add(entrypoint.slice(0, -".py".length));
8996
9510
  }
8997
9511
  for (const sourceFile of sources) {
8998
- const lambdaOptions = await (0, import_build_utils19.getLambdaOptionsFromFunction)({
9512
+ const lambdaOptions = await (0, import_build_utils20.getLambdaOptionsFromFunction)({
8999
9513
  sourceFile,
9000
9514
  config
9001
9515
  });
@@ -9015,11 +9529,11 @@ async function installInjectedPackage({
9015
9529
  projectDir,
9016
9530
  pipPlatformArgs
9017
9531
  }) {
9018
- const localDir = (0, import_path15.join)(__dirname, "..", "..", "..", "python", name);
9019
- 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"));
9020
9534
  const dep = envOverride || (isLocalDev ? localDir : pinned);
9021
9535
  const noExclude = ["--exclude-newer-package", `${name}=false`];
9022
- (0, import_build_utils19.debug)(`Installing ${dep}`);
9536
+ (0, import_build_utils20.debug)(`Installing ${dep}`);
9023
9537
  await uv.pip({
9024
9538
  venvPath,
9025
9539
  projectDir,
@@ -9045,29 +9559,37 @@ var build = async ({
9045
9559
  registerPreDeploy
9046
9560
  }) => {
9047
9561
  let entrypoint = rawEntrypoint === "<detect>" ? void 0 : rawEntrypoint;
9048
- 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" });
9049
9570
  const framework = config?.framework;
9050
9571
  let shouldInstallVercelWorkers = config?.hasWorkerServices === true;
9051
9572
  let subscribers = [];
9573
+ let workflows = [];
9052
9574
  let spawnEnv;
9053
9575
  let projectInstallCommand;
9054
9576
  let hasCustomCommand = false;
9055
- let hasCustomBuildCommand = false;
9056
9577
  const target = getTargetPlatform(meta.isDev ?? false);
9057
- (0, import_build_utils19.debug)(`workPath: ${workPath}`);
9578
+ (0, import_build_utils20.debug)(`workPath: ${workPath}`);
9058
9579
  workPath = await downloadFilesInWorkPath({
9059
9580
  workPath,
9060
9581
  files: originalFiles,
9061
9582
  entrypoint,
9062
9583
  meta
9063
9584
  });
9064
- if (!service && (0, import_build_utils19.isPythonFramework)(framework)) {
9585
+ if (isPyprojectEntrypoint || !service && (0, import_build_utils20.isPythonFramework)(framework)) {
9065
9586
  subscribers = await getPyprojectSubscribers(workPath);
9066
- shouldInstallVercelWorkers ||= subscribers.length > 0;
9587
+ workflows = await getPyprojectWorkflows(workPath);
9588
+ shouldInstallVercelWorkers ||= subscribers.length > 0 || workflows.length > 0;
9067
9589
  }
9068
9590
  try {
9069
9591
  if (meta.isDev) {
9070
- const setupCfg = (0, import_path15.join)(workPath, "setup.cfg");
9592
+ const setupCfg = (0, import_path17.join)(workPath, "setup.cfg");
9071
9593
  await writeFile(setupCfg, "[install]\nprefix=\n");
9072
9594
  }
9073
9595
  } catch (err) {
@@ -9076,22 +9598,36 @@ var build = async ({
9076
9598
  }
9077
9599
  let detected;
9078
9600
  const handlerFunction = typeof config?.handlerFunction === "string" ? config.handlerFunction : void 0;
9079
- detected = await detectPythonEntrypoint(
9080
- config.framework,
9081
- workPath,
9082
- entrypoint ? {
9083
- filePath: entrypoint,
9084
- // For schedule-triggered jobs, the WSGI variable is always 'app' (created dynamically).
9085
- // For other services, handlerFunction is used as the entrypoint variable name.
9086
- varName: service && (0, import_build_utils19.isScheduleTriggeredService)(service) ? void 0 : handlerFunction
9087
- } : void 0,
9088
- service
9089
- ) ?? 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
+ }
9090
9626
  if (detected?.error && detected?.baseDir === void 0) {
9091
9627
  throw detected?.error;
9092
9628
  }
9093
- const entryDirectory = detected?.baseDir ?? (entrypoint ? (0, import_path15.dirname)(entrypoint) : ".");
9094
- 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);
9095
9631
  const rootDir = repoRootPath ?? workPath;
9096
9632
  const pythonPackage = await builderSpan.child("vc.builder.python.discover").trace(
9097
9633
  () => discoverPackage({
@@ -9132,12 +9668,12 @@ var build = async ({
9132
9668
  }
9133
9669
  const uvVersion = checkUvBinaryVersion(uv.getPath());
9134
9670
  console.log(`Using ${uvVersion}`);
9135
- const venvPath = service?.name ? (0, import_path15.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path15.join)(workPath, ".vercel", "python", ".venv");
9136
- const hasCachedVenv = import_fs15.default.existsSync((0, import_path15.join)(venvPath, "pyvenv.cfg"));
9137
- 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);
9138
9674
  const restoredCache = hasCachedVenv && hasCachedUv ? "both" : hasCachedVenv ? "venv" : hasCachedUv ? "uv" : "none";
9139
9675
  if (hasCachedVenv || hasCachedUv) {
9140
- (0, import_build_utils19.debug)(
9676
+ (0, import_build_utils20.debug)(
9141
9677
  `Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
9142
9678
  );
9143
9679
  }
@@ -9149,14 +9685,14 @@ var build = async ({
9149
9685
  uvCacheDir
9150
9686
  });
9151
9687
  });
9152
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
9688
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9153
9689
  const {
9154
9690
  cliType,
9155
9691
  lockfileVersion,
9156
9692
  packageJsonPackageManager,
9157
9693
  turboSupportsCorepackHome
9158
- } = await (0, import_build_utils19.scanParentDirs)(workPath, true);
9159
- spawnEnv = (0, import_build_utils19.getEnvForPackageManager)({
9694
+ } = await (0, import_build_utils20.scanParentDirs)(workPath, true);
9695
+ spawnEnv = (0, import_build_utils20.getEnvForPackageManager)({
9160
9696
  cliType,
9161
9697
  lockfileVersion,
9162
9698
  packageJsonPackageManager,
@@ -9181,13 +9717,13 @@ var build = async ({
9181
9717
  let uvLockPath = null;
9182
9718
  let uvProjectDir = null;
9183
9719
  let projectName;
9184
- await builderSpan.child(import_build_utils19.BUILDER_INSTALLER_STEP, {
9720
+ await builderSpan.child(import_build_utils20.BUILDER_INSTALLER_STEP, {
9185
9721
  installCommand: projectInstallCommand || void 0,
9186
9722
  runtime: "python",
9187
9723
  "python.cache.restored": restoredCache
9188
9724
  }).trace(async () => {
9189
9725
  if (projectInstallCommand) {
9190
- await import_fs15.default.promises.rm(venvPath, { recursive: true, force: true });
9726
+ await import_fs17.default.promises.rm(venvPath, { recursive: true, force: true });
9191
9727
  await ensureVenv({
9192
9728
  pythonVersion,
9193
9729
  venvPath,
@@ -9198,7 +9734,7 @@ var build = async ({
9198
9734
  console.log(
9199
9735
  `Running "install" command: \`${projectInstallCommand}\`...`
9200
9736
  );
9201
- await (0, import_build_utils19.execCommand)(projectInstallCommand, {
9737
+ await (0, import_build_utils20.execCommand)(projectInstallCommand, {
9202
9738
  env: pythonEnv,
9203
9739
  cwd: workPath
9204
9740
  });
@@ -9219,7 +9755,7 @@ var build = async ({
9219
9755
  }
9220
9756
  if (!assumeDepsInstalled) {
9221
9757
  const lockCacheKey = service?.name ? `uv.lock.${service.name}` : "uv.lock";
9222
- const cachedLockPath = (0, import_path15.join)(uvCacheDir, lockCacheKey);
9758
+ const cachedLockPath = (0, import_path17.join)(uvCacheDir, lockCacheKey);
9223
9759
  const { projectDir, lockPath, lockFileProvidedByUser } = await ensureUvProject({
9224
9760
  workPath,
9225
9761
  rootDir,
@@ -9240,34 +9776,30 @@ var build = async ({
9240
9776
  locked: !lockFileProvidedByUser,
9241
9777
  pythonPlatform: target.uvPlatform
9242
9778
  });
9243
- if (lockPath && import_fs15.default.existsSync(lockPath)) {
9244
- await import_fs15.default.promises.mkdir(uvCacheDir, { recursive: true });
9245
- 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);
9246
9782
  }
9247
9783
  }
9248
9784
  });
9249
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
9785
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9250
9786
  const projectBuildCommand = config?.projectSettings?.buildCommand ?? // fallback if provided directly on config (some callers set this)
9251
9787
  config?.buildCommand;
9252
- await builderSpan.child(import_build_utils19.BUILDER_COMPILE_STEP, {
9788
+ await builderSpan.child(import_build_utils20.BUILDER_COMPILE_STEP, {
9253
9789
  buildCommand: projectBuildCommand || void 0
9254
9790
  }).trace(async () => {
9255
9791
  if (projectBuildCommand) {
9256
9792
  console.log(`Running "${projectBuildCommand}"`);
9257
- await (0, import_build_utils19.execCommand)(projectBuildCommand, {
9793
+ await (0, import_build_utils20.execCommand)(projectBuildCommand, {
9258
9794
  env: pythonEnv,
9259
9795
  cwd: workPath
9260
9796
  });
9261
- hasCustomBuildCommand = true;
9262
9797
  } else {
9263
- const ranBuildScript = await runPyprojectScript(
9798
+ await runPyprojectScript(
9264
9799
  workPath,
9265
9800
  ["vercel-build", "now-build", "build"],
9266
9801
  pythonEnv
9267
9802
  );
9268
- if (ranBuildScript) {
9269
- hasCustomBuildCommand = true;
9270
- }
9271
9803
  }
9272
9804
  });
9273
9805
  }
@@ -9278,13 +9810,14 @@ var build = async ({
9278
9810
  entrypoint,
9279
9811
  detected
9280
9812
  });
9281
- const resolved = hookResult?.entrypoint ?? detected?.entrypoint;
9813
+ const resolved = isPyprojectEntrypoint ? detected?.entrypoint : hookResult?.entrypoint ?? detected?.entrypoint;
9282
9814
  if (!resolved && detected?.error) {
9283
9815
  throw detected?.error;
9284
9816
  }
9285
9817
  entrypoint = resolved?.entrypoint;
9286
- if (!entrypoint) {
9287
- 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({
9288
9821
  code: "PYTHON_ENTRYPOINT_NOT_FOUND",
9289
9822
  message: "No Python entrypoint could be detected. Please specify an entrypoint file."
9290
9823
  });
@@ -9297,7 +9830,7 @@ var build = async ({
9297
9830
  envOverride: baseEnv.VERCEL_RUNTIME_PYTHON,
9298
9831
  uv,
9299
9832
  venvPath,
9300
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9833
+ projectDir: (0, import_path17.join)(workPath, entryDirectory),
9301
9834
  pipPlatformArgs
9302
9835
  });
9303
9836
  if (shouldInstallVercelWorkers) {
@@ -9307,7 +9840,7 @@ var build = async ({
9307
9840
  envOverride: baseEnv.VERCEL_WORKERS_PYTHON,
9308
9841
  uv,
9309
9842
  venvPath,
9310
- projectDir: (0, import_path15.join)(workPath, entryDirectory),
9843
+ projectDir: (0, import_path17.join)(workPath, entryDirectory),
9311
9844
  pipPlatformArgs
9312
9845
  });
9313
9846
  }
@@ -9320,61 +9853,67 @@ var build = async ({
9320
9853
  const capturedEnv = { ...pythonEnv };
9321
9854
  const capturedCwd = workPath;
9322
9855
  registerPreDeploy(async () => {
9323
- await builderSpan.child(import_build_utils19.BUILDER_PRE_DEPLOY_STEP, {
9856
+ await builderSpan.child(import_build_utils20.BUILDER_PRE_DEPLOY_STEP, {
9324
9857
  preDeployCommand
9325
9858
  }).trace(async () => {
9326
9859
  console.log(`Running pre-deploy command: \`${preDeployCommand}\``);
9327
- await (0, import_build_utils19.execCommand)(preDeployCommand, {
9860
+ await (0, import_build_utils20.execCommand)(preDeployCommand, {
9328
9861
  env: capturedEnv,
9329
9862
  cwd: capturedCwd
9330
9863
  });
9331
9864
  });
9332
9865
  });
9333
9866
  }
9334
- (0, import_build_utils19.debug)("Entrypoint is", entrypoint);
9335
- const moduleName = entrypointToModule(entrypoint);
9336
- if (handlerFunction) {
9337
- const entrypointPath = (0, import_path15.join)(workPath, entrypoint);
9338
- const source = await import_fs15.default.promises.readFile(entrypointPath, "utf-8");
9339
- const found = await (0, import_python_analysis9.containsTopLevelCallable)(source, handlerFunction);
9340
- if (!found) {
9341
- throw new import_build_utils19.NowBuildError({
9342
- code: "PYTHON_HANDLER_NOT_FOUND",
9343
- message: `Handler function "${handlerFunction}" not found in ${entrypoint}. Ensure it is defined at the module's top level.`
9344
- });
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
+ }
9345
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
+ });
9346
9910
  }
9347
- const vendorDir = resolveVendorDir();
9348
- const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
9349
- const entrypointWithSuffix = `${entrypoint}${suffix}`;
9350
- (0, import_build_utils19.debug)("Entrypoint with suffix is", entrypointWithSuffix);
9351
- const crons = await getServiceCrons({
9352
- service,
9353
- entrypoint,
9354
- rawEntrypoint,
9355
- handlerFunction,
9356
- pythonBin: getVenvPythonBin(venvPath),
9357
- env: pythonEnv,
9358
- workPath
9359
- });
9360
- const extraTrampolineEnv = [];
9361
- if (crons?.length) {
9362
- const json = JSON.stringify(buildCronRouteTable(crons));
9363
- (0, import_assert.default)(!json.includes("\\"), `backslash in cron route table: ${json}`);
9364
- extraTrampolineEnv.push(`"__VC_CRON_ROUTES": '${json}'`);
9365
- }
9366
- const variableName = resolved?.variableName ?? "";
9367
- const runtimeTrampoline = createRuntimeTrampoline({
9368
- moduleName,
9369
- entrypoint: entrypointWithSuffix,
9370
- vendorDir,
9371
- variableName,
9372
- extraEnv: extraTrampolineEnv
9373
- });
9374
- const automaticCompileAllEnabled = shouldUseCompileAll({
9911
+ const compileAllEnabled = shouldCompileAll({
9375
9912
  isDev: meta.isDev,
9376
9913
  hasCustomCommand,
9377
- hasCustomBuildCommand
9914
+ // A pre-deploy command can rewrite source after the build, which would make
9915
+ // unchecked-hash precompiled bytecode stale; skip precompilation to avoid serving it.
9916
+ hasPreDeployCommand: typeof preDeployCommand === "string"
9378
9917
  });
9379
9918
  const predefinedExcludes = [
9380
9919
  ".git/**",
@@ -9405,16 +9944,16 @@ var build = async ({
9405
9944
  cwd: workPath,
9406
9945
  ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
9407
9946
  };
9408
- const files = await (0, import_build_utils19.glob)("**", globOptions);
9947
+ const files = await (0, import_build_utils20.glob)("**", globOptions);
9409
9948
  if (djangoStatic?.manifestRelPath) {
9410
- files[djangoStatic.manifestRelPath] = new import_build_utils19.FileFsRef({
9411
- 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)
9412
9951
  });
9413
9952
  }
9414
9953
  const handlerPyFilename = "vc__handler__python";
9415
9954
  if (config.framework === "fasthtml") {
9416
9955
  const { SESSKEY = "" } = process.env;
9417
- files[".sesskey"] = new import_build_utils19.FileBlob({ data: `"${SESSKEY}"` });
9956
+ files[".sesskey"] = new import_build_utils20.FileBlob({ data: `"${SESSKEY}"` });
9418
9957
  }
9419
9958
  const depExternalizer = new PythonDependencyExternalizer({
9420
9959
  venvPath,
@@ -9443,99 +9982,217 @@ var build = async ({
9443
9982
  });
9444
9983
  }
9445
9984
  });
9446
- const runCompileAllAndFillBytecode = async () => {
9447
- await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
9448
- const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs15.default.existsSync(d));
9449
- const pythonBin = getVenvPythonBin(venvPath);
9450
- console.log("Compiling Python application bytecode...");
9451
- await runCompileAll({
9452
- pythonBin,
9453
- filesOrDirectories: [workPath],
9454
- env: pythonEnv,
9455
- excludeRegex: getCompileAllAppExcludeRegex(workPath)
9985
+ const runCompileAllAndFillBytecode = async (capacityBytes, vendorPackageTiers) => {
9986
+ try {
9987
+ await builderSpan.child("vc.builder.python.compileall").trace(async (compileSpan) => {
9988
+ const sitePackageDirs = (await getVenvSitePackagesDirs(venvPath)).filter((d) => import_fs17.default.existsSync(d));
9989
+ const pythonBin = getVenvPythonBin(venvPath);
9990
+ console.log("Compiling Python bytecode...");
9991
+ await runCompileAll({
9992
+ pythonBin,
9993
+ filesOrDirectories: [workPath],
9994
+ env: pythonEnv,
9995
+ excludeRegex: getCompileAllAppExcludeRegex(workPath)
9996
+ });
9997
+ await runCompileAll({
9998
+ pythonBin,
9999
+ filesOrDirectories: sitePackageDirs,
10000
+ env: pythonEnv
10001
+ });
10002
+ compileSpan.setAttributes({
10003
+ "python.compileall.enabled": "true",
10004
+ "python.compileall.sitePackageDirectoryCount": String(
10005
+ sitePackageDirs.length
10006
+ )
10007
+ });
9456
10008
  });
9457
- console.log("Compiling Python dependency bytecode...");
9458
- await runCompileAll({
9459
- pythonBin,
9460
- filesOrDirectories: sitePackageDirs,
9461
- env: pythonEnv
10009
+ const currentSize = await calculateBundleSize(files);
10010
+ let remainingCapacity = capacityBytes - currentSize;
10011
+ if (pythonVersion.major != null && pythonVersion.minor != null) {
10012
+ const appBytecodeInfo = await collectAppBytecodeFiles({
10013
+ workPath,
10014
+ files,
10015
+ pythonMajor: pythonVersion.major,
10016
+ pythonMinor: pythonVersion.minor
10017
+ });
10018
+ remainingCapacity = addBytecodeWithinCapacity(
10019
+ files,
10020
+ appBytecodeInfo,
10021
+ remainingCapacity
10022
+ );
10023
+ }
10024
+ await addVendorBytecodeInTiers({
10025
+ files,
10026
+ depExternalizer,
10027
+ vendorDir,
10028
+ capacity: remainingCapacity,
10029
+ vendorPackageTiers: vendorPackageTiers ?? [void 0]
9462
10030
  });
9463
- compileSpan.setAttributes({
9464
- "python.compileall.enabled": "true",
9465
- "python.compileall.sitePackageDirectoryCount": String(
9466
- sitePackageDirs.length
9467
- )
10031
+ } catch (err) {
10032
+ console.log(
10033
+ "Bytecode precompilation failed; continuing without precompiled bytecode."
10034
+ );
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
+ });
9468
10079
  });
9469
- });
9470
- const currentSize = await calculateBundleSize(files);
9471
- let remainingCapacity = MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE - currentSize;
9472
- if (pythonVersion.major != null && pythonVersion.minor != null) {
9473
- const appBytecodeInfo = await collectAppBytecodeFiles({
10080
+ const beforeCount = Object.keys(files).length;
10081
+ const appInfo = await collectAppPrefixBytecodeFiles({
10082
+ stagingDir,
9474
10083
  workPath,
9475
10084
  files,
9476
- pythonMajor: pythonVersion.major,
9477
- pythonMinor: pythonVersion.minor
10085
+ runtimeTaskRoot: "/var/task",
10086
+ pythonMajor: pyMajor,
10087
+ pythonMinor: pyMinor
9478
10088
  });
9479
10089
  remainingCapacity = addBytecodeWithinCapacity(
9480
10090
  files,
9481
- appBytecodeInfo,
10091
+ appInfo,
9482
10092
  remainingCapacity
9483
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}`);
9484
10122
  }
9485
- const vendorBytecodeInfo = await depExternalizer.collectBytecodeFiles({
9486
- vendorDirName: vendorDir
9487
- });
9488
- await addVendorBytecodeWithinCapacity({
9489
- files,
9490
- depExternalizer,
9491
- vendorDir,
9492
- bytecodeInfo: vendorBytecodeInfo,
9493
- capacity: remainingCapacity
9494
- });
9495
10123
  };
9496
10124
  const announceLargeFunction = () => console.log(
9497
- `Function "${entrypoint}" exceeds the standard size limit; enabling large functions (beta).`
10125
+ `Function "${entrypoint ?? rawEntrypoint}" exceeds the standard size limit; enabling large functions (beta).`
9498
10126
  );
9499
10127
  if (depAnalysis.runtimeInstallEnabled) {
9500
- const { fellBackToFullBundle } = await depExternalizer.generateBundle(files);
9501
- 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) {
9502
10133
  announceLargeFunction();
9503
- if (automaticCompileAllEnabled) {
9504
- await runCompileAllAndFillBytecode();
10134
+ if (compileAllEnabled) {
10135
+ await runCompileAllAndFillBytecode(
10136
+ MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
10137
+ );
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
+ ]);
9505
10150
  }
9506
10151
  }
9507
10152
  } else {
9508
10153
  addFiles(files, depAnalysis.allVendorFiles);
9509
- if (isLargeFunctionsEnabled() && depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
9510
- announceLargeFunction();
9511
- }
9512
- if (automaticCompileAllEnabled && depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
9513
- await runCompileAllAndFillBytecode();
10154
+ if (depAnalysis.totalBundleSize > LAMBDA_SIZE_THRESHOLD_BYTES) {
10155
+ if (isLargeFunctionsEnabled()) {
10156
+ announceLargeFunction();
10157
+ }
10158
+ if (compileAllEnabled) {
10159
+ await runCompileAllAndFillBytecode(
10160
+ MAX_LARGE_FUNCTION_UNCOMPRESSED_SIZE
10161
+ );
10162
+ }
10163
+ } else if (compileAllEnabled) {
10164
+ const capacity = BYTECODE_FILL_CEILING_BYTES - depAnalysis.totalBundleSize;
10165
+ const estimate = await estimateBytecodeSize(files);
10166
+ if (capacity >= BYTECODE_COVERAGE_FLOOR * estimate) {
10167
+ await runCompileAllAndFillBytecode(BYTECODE_FILL_CEILING_BYTES);
10168
+ }
9514
10169
  }
9515
10170
  }
9516
10171
  });
9517
- const webFiles = {
9518
- ...files,
9519
- [`${handlerPyFilename}.py`]: new import_build_utils19.FileBlob({ data: runtimeTrampoline })
9520
- };
9521
- const lambdaOptions = await getPythonLambdaOptions({
9522
- config,
9523
- entrypoint
9524
- });
9525
- const output = new import_build_utils19.Lambda({
9526
- files: webFiles,
9527
- handler: `${handlerPyFilename}.vc_handler`,
9528
- runtime: pythonVersion.runtime,
9529
- ...lambdaOptions,
9530
- architecture: target.architecture,
9531
- environment: lambdaEnv,
9532
- supportsResponseStreaming: true
9533
- });
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
+ }
9534
10192
  const subscriberLambdas = {};
9535
10193
  for (const subscriber of subscribers) {
9536
- const safeName = safePathSegment(subscriber.name);
9537
- const outputPath = `_py_subscribers/${safeName}`;
9538
- const consumer = (0, import_build_utils19.sanitizeConsumerName)(outputPath);
10194
+ const outputPath = getSubscriberOutputPath(subscriber.name);
10195
+ const consumer = getSubscriberConsumerName(subscriber.name);
9539
10196
  const experimentalTriggers = subscriber.topics.map(
9540
10197
  (topic) => ({
9541
10198
  type: "queue/v2beta",
@@ -9544,10 +10201,10 @@ var build = async ({
9544
10201
  ...subscriber.triggerDefaults
9545
10202
  })
9546
10203
  );
9547
- subscriberLambdas[outputPath] = new import_build_utils19.Lambda({
10204
+ subscriberLambdas[outputPath] = new import_build_utils20.Lambda({
9548
10205
  files: {
9549
10206
  ...files,
9550
- [`${handlerPyFilename}.py`]: new import_build_utils19.FileBlob({
10207
+ [`${handlerPyFilename}.py`]: new import_build_utils20.FileBlob({
9551
10208
  data: createRuntimeTrampoline({
9552
10209
  moduleName: subscriber.moduleName,
9553
10210
  entrypoint: subscriber.entrypoint,
@@ -9562,6 +10219,42 @@ var build = async ({
9562
10219
  environment: {
9563
10220
  ...lambdaEnv,
9564
10221
  VERCEL_HAS_WORKER_SERVICES: "1",
10222
+ // Compatibility marker consumed by the current Python runtime.
10223
+ VERCEL_SERVICE_TYPE: "worker"
10224
+ },
10225
+ experimentalTriggers,
10226
+ supportsResponseStreaming: true
10227
+ });
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.
9565
10258
  VERCEL_SERVICE_TYPE: "worker"
9566
10259
  },
9567
10260
  experimentalTriggers,
@@ -9576,21 +10269,22 @@ var build = async ({
9576
10269
  pythonVersion,
9577
10270
  uvLockPath,
9578
10271
  framework,
9579
- serviceType: service ? (0, import_build_utils19.getReportedServiceType)(service) : void 0
10272
+ serviceType: service ? (0, import_build_utils20.getReportedServiceType)(service) : void 0
9580
10273
  });
9581
10274
  } catch (err) {
9582
- (0, import_build_utils19.debug)(
10275
+ (0, import_build_utils20.debug)(
9583
10276
  `Failed to write project manifest: ${err instanceof Error ? err.message : String(err)}`
9584
10277
  );
9585
10278
  }
9586
10279
  }
9587
- 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");
9588
10282
  return { resultVersion: 3, result: { output } };
9589
10283
  }
9590
10284
  const lambdaPath = service?.name && service.type ? `_svc/${service.name}/index` : "index";
9591
- 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 }) : {};
9592
10286
  const isNonWebService = service?.name && service.type && service.type !== "web";
9593
- const routes = isNonWebService ? void 0 : [
10287
+ const routes = isNonWebService || !output ? void 0 : [
9594
10288
  { handle: "filesystem" },
9595
10289
  { src: "/(.*)", dest: `/${lambdaPath}` }
9596
10290
  ];
@@ -9598,8 +10292,9 @@ var build = async ({
9598
10292
  resultVersion: 2,
9599
10293
  result: {
9600
10294
  output: {
9601
- [lambdaPath]: output,
10295
+ ...output ? { [lambdaPath]: output } : {},
9602
10296
  ...subscriberLambdas,
10297
+ ...workflowLambdas,
9603
10298
  ...staticFiles
9604
10299
  },
9605
10300
  ...routes ? { routes } : {},
@@ -9622,14 +10317,14 @@ var prepareCache = async ({
9622
10317
  }
9623
10318
  } catch {
9624
10319
  }
9625
- 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}/**", {
9626
10321
  cwd: root,
9627
10322
  ignore
9628
10323
  });
9629
10324
  };
9630
10325
  var shouldServe = (opts) => {
9631
10326
  const framework = opts.config.framework;
9632
- if ((0, import_build_utils19.isPythonFramework)(framework)) {
10327
+ if ((0, import_build_utils20.isPythonFramework)(framework)) {
9633
10328
  const requestPath = opts.requestPath.replace(/\/$/, "");
9634
10329
  if (requestPath.startsWith("api") && opts.hasMatched) {
9635
10330
  return false;
@@ -9648,7 +10343,7 @@ var defaultShouldServe = ({
9648
10343
  if (entrypoint === requestPath && hasProp(files, entrypoint)) {
9649
10344
  return true;
9650
10345
  }
9651
- const { dir, name } = (0, import_path15.parse)(entrypoint);
10346
+ const { dir, name } = (0, import_path17.parse)(entrypoint);
9652
10347
  if (name === "index" && dir === requestPath && hasProp(files, entrypoint)) {
9653
10348
  return true;
9654
10349
  }
@@ -9659,11 +10354,14 @@ function hasProp(obj, key) {
9659
10354
  }
9660
10355
  // Annotate the CommonJS export names for ESM import in node:
9661
10356
  0 && (module.exports = {
10357
+ addCollectedVendorBytecode,
10358
+ addVendorBytecodeInTiers,
9662
10359
  build,
9663
10360
  defaultShouldServe,
9664
10361
  detectEntrypoint,
9665
10362
  diagnostics,
9666
10363
  downloadFilesInWorkPath,
10364
+ getDevSidecars,
9667
10365
  installRequirement,
9668
10366
  installRequirementsFile,
9669
10367
  prepareCache,