@vercel/python 6.28.0 → 6.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -423,15 +423,15 @@ var require_readShebang = __commonJS({
423
423
  var require_semver = __commonJS({
424
424
  "../../node_modules/.pnpm/semver@5.7.2/node_modules/semver/semver.js"(exports, module2) {
425
425
  exports = module2.exports = SemVer;
426
- var debug12;
426
+ var debug13;
427
427
  if (typeof process === "object" && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG)) {
428
- debug12 = function() {
428
+ debug13 = function() {
429
429
  var args = Array.prototype.slice.call(arguments, 0);
430
430
  args.unshift("SEMVER");
431
431
  console.log.apply(console, args);
432
432
  };
433
433
  } else {
434
- debug12 = function() {
434
+ debug13 = function() {
435
435
  };
436
436
  }
437
437
  exports.SEMVER_SPEC_VERSION = "2.0.0";
@@ -540,7 +540,7 @@ var require_semver = __commonJS({
540
540
  var STAR = R++;
541
541
  src[STAR] = "(<|>)?=?\\s*\\*";
542
542
  for (i = 0; i < R; i++) {
543
- debug12(i, src[i]);
543
+ debug13(i, src[i]);
544
544
  if (!re[i]) {
545
545
  re[i] = new RegExp(src[i]);
546
546
  safeRe[i] = new RegExp(makeSafeRe(src[i]));
@@ -607,7 +607,7 @@ var require_semver = __commonJS({
607
607
  if (!(this instanceof SemVer)) {
608
608
  return new SemVer(version2, options);
609
609
  }
610
- debug12("SemVer", version2, options);
610
+ debug13("SemVer", version2, options);
611
611
  this.options = options;
612
612
  this.loose = !!options.loose;
613
613
  var m = version2.trim().match(options.loose ? safeRe[LOOSE] : safeRe[FULL]);
@@ -654,7 +654,7 @@ var require_semver = __commonJS({
654
654
  return this.version;
655
655
  };
656
656
  SemVer.prototype.compare = function(other) {
657
- debug12("SemVer.compare", this.version, this.options, other);
657
+ debug13("SemVer.compare", this.version, this.options, other);
658
658
  if (!(other instanceof SemVer)) {
659
659
  other = new SemVer(other, this.options);
660
660
  }
@@ -681,7 +681,7 @@ var require_semver = __commonJS({
681
681
  do {
682
682
  var a = this.prerelease[i2];
683
683
  var b = other.prerelease[i2];
684
- debug12("prerelease compare", i2, a, b);
684
+ debug13("prerelease compare", i2, a, b);
685
685
  if (a === void 0 && b === void 0) {
686
686
  return 0;
687
687
  } else if (b === void 0) {
@@ -935,7 +935,7 @@ var require_semver = __commonJS({
935
935
  return new Comparator(comp, options);
936
936
  }
937
937
  comp = comp.trim().split(/\s+/).join(" ");
938
- debug12("comparator", comp, options);
938
+ debug13("comparator", comp, options);
939
939
  this.options = options;
940
940
  this.loose = !!options.loose;
941
941
  this.parse(comp);
@@ -944,7 +944,7 @@ var require_semver = __commonJS({
944
944
  } else {
945
945
  this.value = this.operator + this.semver.version;
946
946
  }
947
- debug12("comp", this);
947
+ debug13("comp", this);
948
948
  }
949
949
  var ANY = {};
950
950
  Comparator.prototype.parse = function(comp) {
@@ -967,7 +967,7 @@ var require_semver = __commonJS({
967
967
  return this.value;
968
968
  };
969
969
  Comparator.prototype.test = function(version2) {
970
- debug12("Comparator.test", version2, this.options.loose);
970
+ debug13("Comparator.test", version2, this.options.loose);
971
971
  if (this.semver === ANY) {
972
972
  return true;
973
973
  }
@@ -1050,9 +1050,9 @@ var require_semver = __commonJS({
1050
1050
  var loose = this.options.loose;
1051
1051
  var hr = loose ? safeRe[HYPHENRANGELOOSE] : safeRe[HYPHENRANGE];
1052
1052
  range = range.replace(hr, hyphenReplace);
1053
- debug12("hyphen replace", range);
1053
+ debug13("hyphen replace", range);
1054
1054
  range = range.replace(safeRe[COMPARATORTRIM], comparatorTrimReplace);
1055
- debug12("comparator trim", range, safeRe[COMPARATORTRIM]);
1055
+ debug13("comparator trim", range, safeRe[COMPARATORTRIM]);
1056
1056
  range = range.replace(safeRe[TILDETRIM], tildeTrimReplace);
1057
1057
  range = range.replace(safeRe[CARETTRIM], caretTrimReplace);
1058
1058
  var compRe = loose ? safeRe[COMPARATORLOOSE] : safeRe[COMPARATOR];
@@ -1092,15 +1092,15 @@ var require_semver = __commonJS({
1092
1092
  });
1093
1093
  }
1094
1094
  function parseComparator(comp, options) {
1095
- debug12("comp", comp, options);
1095
+ debug13("comp", comp, options);
1096
1096
  comp = replaceCarets(comp, options);
1097
- debug12("caret", comp);
1097
+ debug13("caret", comp);
1098
1098
  comp = replaceTildes(comp, options);
1099
- debug12("tildes", comp);
1099
+ debug13("tildes", comp);
1100
1100
  comp = replaceXRanges(comp, options);
1101
- debug12("xrange", comp);
1101
+ debug13("xrange", comp);
1102
1102
  comp = replaceStars(comp, options);
1103
- debug12("stars", comp);
1103
+ debug13("stars", comp);
1104
1104
  return comp;
1105
1105
  }
1106
1106
  function isX(id) {
@@ -1114,7 +1114,7 @@ var require_semver = __commonJS({
1114
1114
  function replaceTilde(comp, options) {
1115
1115
  var r = options.loose ? safeRe[TILDELOOSE] : safeRe[TILDE];
1116
1116
  return comp.replace(r, function(_, M, m, p, pr) {
1117
- debug12("tilde", comp, _, M, m, p, pr);
1117
+ debug13("tilde", comp, _, M, m, p, pr);
1118
1118
  var ret;
1119
1119
  if (isX(M)) {
1120
1120
  ret = "";
@@ -1123,12 +1123,12 @@ var require_semver = __commonJS({
1123
1123
  } else if (isX(p)) {
1124
1124
  ret = ">=" + M + "." + m + ".0 <" + M + "." + (+m + 1) + ".0";
1125
1125
  } else if (pr) {
1126
- debug12("replaceTilde pr", pr);
1126
+ debug13("replaceTilde pr", pr);
1127
1127
  ret = ">=" + M + "." + m + "." + p + "-" + pr + " <" + M + "." + (+m + 1) + ".0";
1128
1128
  } else {
1129
1129
  ret = ">=" + M + "." + m + "." + p + " <" + M + "." + (+m + 1) + ".0";
1130
1130
  }
1131
- debug12("tilde return", ret);
1131
+ debug13("tilde return", ret);
1132
1132
  return ret;
1133
1133
  });
1134
1134
  }
@@ -1138,10 +1138,10 @@ var require_semver = __commonJS({
1138
1138
  }).join(" ");
1139
1139
  }
1140
1140
  function replaceCaret(comp, options) {
1141
- debug12("caret", comp, options);
1141
+ debug13("caret", comp, options);
1142
1142
  var r = options.loose ? safeRe[CARETLOOSE] : safeRe[CARET];
1143
1143
  return comp.replace(r, function(_, M, m, p, pr) {
1144
- debug12("caret", comp, _, M, m, p, pr);
1144
+ debug13("caret", comp, _, M, m, p, pr);
1145
1145
  var ret;
1146
1146
  if (isX(M)) {
1147
1147
  ret = "";
@@ -1154,7 +1154,7 @@ var require_semver = __commonJS({
1154
1154
  ret = ">=" + M + "." + m + ".0 <" + (+M + 1) + ".0.0";
1155
1155
  }
1156
1156
  } else if (pr) {
1157
- debug12("replaceCaret pr", pr);
1157
+ debug13("replaceCaret pr", pr);
1158
1158
  if (M === "0") {
1159
1159
  if (m === "0") {
1160
1160
  ret = ">=" + M + "." + m + "." + p + "-" + pr + " <" + M + "." + m + "." + (+p + 1);
@@ -1165,7 +1165,7 @@ var require_semver = __commonJS({
1165
1165
  ret = ">=" + M + "." + m + "." + p + "-" + pr + " <" + (+M + 1) + ".0.0";
1166
1166
  }
1167
1167
  } else {
1168
- debug12("no pr");
1168
+ debug13("no pr");
1169
1169
  if (M === "0") {
1170
1170
  if (m === "0") {
1171
1171
  ret = ">=" + M + "." + m + "." + p + " <" + M + "." + m + "." + (+p + 1);
@@ -1176,12 +1176,12 @@ var require_semver = __commonJS({
1176
1176
  ret = ">=" + M + "." + m + "." + p + " <" + (+M + 1) + ".0.0";
1177
1177
  }
1178
1178
  }
1179
- debug12("caret return", ret);
1179
+ debug13("caret return", ret);
1180
1180
  return ret;
1181
1181
  });
1182
1182
  }
1183
1183
  function replaceXRanges(comp, options) {
1184
- debug12("replaceXRanges", comp, options);
1184
+ debug13("replaceXRanges", comp, options);
1185
1185
  return comp.split(/\s+/).map(function(comp2) {
1186
1186
  return replaceXRange(comp2, options);
1187
1187
  }).join(" ");
@@ -1190,7 +1190,7 @@ var require_semver = __commonJS({
1190
1190
  comp = comp.trim();
1191
1191
  var r = options.loose ? safeRe[XRANGELOOSE] : safeRe[XRANGE];
1192
1192
  return comp.replace(r, function(ret, gtlt, M, m, p, pr) {
1193
- debug12("xRange", comp, ret, gtlt, M, m, p, pr);
1193
+ debug13("xRange", comp, ret, gtlt, M, m, p, pr);
1194
1194
  var xM = isX(M);
1195
1195
  var xm = xM || isX(m);
1196
1196
  var xp = xm || isX(p);
@@ -1233,12 +1233,12 @@ var require_semver = __commonJS({
1233
1233
  } else if (xp) {
1234
1234
  ret = ">=" + M + "." + m + ".0 <" + M + "." + (+m + 1) + ".0";
1235
1235
  }
1236
- debug12("xRange return", ret);
1236
+ debug13("xRange return", ret);
1237
1237
  return ret;
1238
1238
  });
1239
1239
  }
1240
1240
  function replaceStars(comp, options) {
1241
- debug12("replaceStars", comp, options);
1241
+ debug13("replaceStars", comp, options);
1242
1242
  return comp.trim().replace(safeRe[STAR], "");
1243
1243
  }
1244
1244
  function hyphenReplace($0, from, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr, tb) {
@@ -1286,7 +1286,7 @@ var require_semver = __commonJS({
1286
1286
  }
1287
1287
  if (version2.prerelease.length && !options.includePrerelease) {
1288
1288
  for (i2 = 0; i2 < set.length; i2++) {
1289
- debug12(set[i2].semver);
1289
+ debug13(set[i2].semver);
1290
1290
  if (set[i2].semver === ANY) {
1291
1291
  continue;
1292
1292
  }
@@ -3217,6 +3217,7 @@ __export(src_exports, {
3217
3217
  downloadFilesInWorkPath: () => downloadFilesInWorkPath,
3218
3218
  installRequirement: () => installRequirement,
3219
3219
  installRequirementsFile: () => installRequirementsFile,
3220
+ prepareCache: () => prepareCache,
3220
3221
  runFrameworkHook: () => runFrameworkHook,
3221
3222
  shouldServe: () => shouldServe,
3222
3223
  startDevServer: () => startDevServer,
@@ -3228,11 +3229,11 @@ var import_fs11 = __toESM(require("fs"));
3228
3229
  var import_path13 = require("path");
3229
3230
 
3230
3231
  // src/package-versions.ts
3231
- var VERCEL_RUNTIME_VERSION = "0.11.0";
3232
- var VERCEL_WORKERS_VERSION = "0.0.13";
3232
+ var VERCEL_RUNTIME_VERSION = "0.12.0";
3233
+ var VERCEL_WORKERS_VERSION = "0.0.15";
3233
3234
 
3234
3235
  // src/index.ts
3235
- var import_build_utils14 = require("@vercel/build-utils");
3236
+ var import_build_utils15 = require("@vercel/build-utils");
3236
3237
 
3237
3238
  // src/install.ts
3238
3239
  var import_execa3 = __toESM(require_execa());
@@ -3261,14 +3262,19 @@ var import_build_utils = require("@vercel/build-utils");
3261
3262
  var UV_VERSION = "0.10.11";
3262
3263
  var UV_PYTHON_PATH_PREFIX = "/uv/python/";
3263
3264
  var UV_PYTHON_DOWNLOADS_MODE = "automatic";
3265
+ var UV_CACHE_DIR_SUBPATH = [".vercel", "python", "cache", "uv"];
3264
3266
  var isWin = process.platform === "win32";
3265
3267
  var uvExec = isWin ? "uv.exe" : "uv";
3266
3268
  function findUvInPath() {
3267
3269
  return import_which.default.sync("uv", { nothrow: true });
3268
3270
  }
3271
+ function getUvCacheDir(workPath) {
3272
+ return (0, import_path.join)(workPath, ...UV_CACHE_DIR_SUBPATH);
3273
+ }
3269
3274
  var UvRunner = class {
3270
- constructor(uvPath) {
3275
+ constructor(uvPath, uvCacheDir) {
3271
3276
  this.uvPath = uvPath;
3277
+ this.uvCacheDir = uvCacheDir;
3272
3278
  }
3273
3279
  getPath() {
3274
3280
  return this.uvPath;
@@ -3317,7 +3323,7 @@ var UvRunner = class {
3317
3323
  }
3318
3324
  async sync(options) {
3319
3325
  const { venvPath, projectDir, locked, frozen, noBuild, noInstallProject } = options;
3320
- const args = ["sync", "--active", "--no-dev", "--link-mode", "copy"];
3326
+ const args = ["sync", "--active", "--no-dev", "--link-mode", "hardlink"];
3321
3327
  if (frozen) {
3322
3328
  args.push("--frozen");
3323
3329
  } else if (locked) {
@@ -3366,6 +3372,24 @@ var UvRunner = class {
3366
3372
  const fullArgs = ["pip", ...args];
3367
3373
  await this.runUvCmd(fullArgs, projectDir, venvPath);
3368
3374
  }
3375
+ /**
3376
+ * Prune the uv cache for CI: removes pre-built wheels and unzipped source
3377
+ * distributions while retaining source-built wheels.
3378
+ */
3379
+ async cachePrune() {
3380
+ const args = ["cache", "prune", "--ci"];
3381
+ const pretty = `uv ${args.join(" ")}`;
3382
+ (0, import_build_utils.debug)(`Running "${pretty}"...`);
3383
+ try {
3384
+ await (0, import_execa.default)(this.uvPath, args, {
3385
+ env: getProtectedUvEnv(process.env, this.uvCacheDir)
3386
+ });
3387
+ } catch (err) {
3388
+ (0, import_build_utils.debug)(
3389
+ `Warning: ${pretty} failed: ${err instanceof Error ? err.message : String(err)}`
3390
+ );
3391
+ }
3392
+ }
3369
3393
  async runUvCmd(args, cwd, venvPath) {
3370
3394
  const pretty = `uv ${args.join(" ")}`;
3371
3395
  (0, import_build_utils.debug)(`Running "${pretty}"...`);
@@ -3392,7 +3416,7 @@ var UvRunner = class {
3392
3416
  const binDir = isWin ? (0, import_path.join)(venvPath, "Scripts") : (0, import_path.join)(venvPath, "bin");
3393
3417
  const existingPath = process.env.PATH || "";
3394
3418
  return {
3395
- ...getProtectedUvEnv(process.env),
3419
+ ...getProtectedUvEnv(process.env, this.uvCacheDir),
3396
3420
  VIRTUAL_ENV: venvPath,
3397
3421
  PATH: existingPath ? `${binDir}${import_path2.delimiter}${existingPath}` : binDir
3398
3422
  };
@@ -3499,11 +3523,15 @@ async function getUvBinaryOrInstall(pythonPath) {
3499
3523
  function filterUnsafeUvPipArgs(args) {
3500
3524
  return args.filter((arg) => arg !== "--no-warn-script-location");
3501
3525
  }
3502
- function getProtectedUvEnv(baseEnv = process.env) {
3503
- return {
3526
+ function getProtectedUvEnv(baseEnv = process.env, uvCacheDir) {
3527
+ const env = {
3504
3528
  ...baseEnv,
3505
3529
  UV_PYTHON_DOWNLOADS: UV_PYTHON_DOWNLOADS_MODE
3506
3530
  };
3531
+ if (uvCacheDir) {
3532
+ env.UV_CACHE_DIR = uvCacheDir;
3533
+ }
3534
+ return env;
3507
3535
  }
3508
3536
  var UV_BUNDLE_DIR = "_uv";
3509
3537
  async function getUvBinaryForBundling(pythonPath) {
@@ -3542,9 +3570,9 @@ function useVirtualEnv(workPath, env, systemPython) {
3542
3570
  }
3543
3571
  return { pythonCmd };
3544
3572
  }
3545
- function createVenvEnv(venvPath, baseEnv = process.env) {
3573
+ function createVenvEnv(venvPath, baseEnv = process.env, uvCacheDir) {
3546
3574
  const env = {
3547
- ...getProtectedUvEnv(baseEnv),
3575
+ ...getProtectedUvEnv(baseEnv, uvCacheDir),
3548
3576
  VIRTUAL_ENV: venvPath
3549
3577
  };
3550
3578
  const binDir = getVenvBinDir(venvPath);
@@ -3552,26 +3580,60 @@ function createVenvEnv(venvPath, baseEnv = process.env) {
3552
3580
  env.PATH = existingPath ? `${binDir}${import_path3.delimiter}${existingPath}` : binDir;
3553
3581
  return env;
3554
3582
  }
3583
+ async function readVenvPythonVersion(pyvenvCfgPath) {
3584
+ try {
3585
+ const content = await import_fs2.default.promises.readFile(pyvenvCfgPath, "utf-8");
3586
+ const match = content.match(/^version\s*=\s*(\d+)\.(\d+)/m);
3587
+ return match ? `${match[1]}.${match[2]}` : null;
3588
+ } catch {
3589
+ return null;
3590
+ }
3591
+ }
3555
3592
  async function ensureVenv({
3556
- pythonPath,
3593
+ pythonVersion,
3557
3594
  venvPath,
3558
3595
  uvPath,
3596
+ uvCacheDir,
3559
3597
  quiet
3560
3598
  }) {
3561
3599
  const marker = (0, import_path3.join)(venvPath, "pyvenv.cfg");
3600
+ let venvExists = false;
3562
3601
  try {
3563
3602
  await import_fs2.default.promises.access(marker);
3564
- return;
3603
+ venvExists = true;
3565
3604
  } catch {
3566
3605
  }
3567
- await import_fs2.default.promises.mkdir(venvPath, { recursive: true });
3568
- if (!quiet) {
3569
- console.log(`Creating virtual environment at "${venvPath}"...`);
3606
+ if (venvExists && pythonVersion.major != null && pythonVersion.minor != null) {
3607
+ const expected = `${pythonVersion.major}.${pythonVersion.minor}`;
3608
+ const cachedVersion = await readVenvPythonVersion(marker);
3609
+ if (cachedVersion && cachedVersion !== expected) {
3610
+ if (!quiet) {
3611
+ console.log(
3612
+ `Cached venv Python ${cachedVersion} differs from requested ${expected}, recreating...`
3613
+ );
3614
+ }
3615
+ await import_fs2.default.promises.rm(venvPath, { recursive: true, force: true });
3616
+ venvExists = false;
3617
+ }
3618
+ }
3619
+ if (venvExists) {
3620
+ (0, import_build_utils2.debug)(`Refreshing cached virtual environment at "${venvPath}"`);
3621
+ } else {
3622
+ await import_fs2.default.promises.mkdir(venvPath, { recursive: true });
3623
+ if (!quiet) {
3624
+ console.log(`Creating virtual environment at "${venvPath}"...`);
3625
+ }
3570
3626
  }
3571
3627
  if (uvPath) {
3572
- await (0, import_execa2.default)(uvPath, ["venv", venvPath]);
3628
+ const args = ["venv", venvPath, "--allow-existing"];
3629
+ if (pythonVersion.major != null && pythonVersion.minor != null) {
3630
+ args.push("--python", `${pythonVersion.major}.${pythonVersion.minor}`);
3631
+ }
3632
+ await (0, import_execa2.default)(uvPath, args, {
3633
+ env: getProtectedUvEnv(process.env, uvCacheDir)
3634
+ });
3573
3635
  } else {
3574
- await (0, import_execa2.default)(pythonPath, ["-m", "venv", venvPath]);
3636
+ await (0, import_execa2.default)(pythonVersion.pythonPath, ["-m", "venv", venvPath]);
3575
3637
  }
3576
3638
  }
3577
3639
  function getVenvPythonBin(venvPath) {
@@ -3900,6 +3962,15 @@ function isInstalled(pv) {
3900
3962
  }
3901
3963
 
3902
3964
  // src/install.ts
3965
+ async function restoreCachedLock(cachedLockPath, projectDir) {
3966
+ if (!cachedLockPath)
3967
+ return;
3968
+ const targetLockPath = (0, import_path5.join)(projectDir, "uv.lock");
3969
+ if (import_fs3.default.existsSync(cachedLockPath) && !import_fs3.default.existsSync(targetLockPath)) {
3970
+ (0, import_build_utils4.debug)("Restoring cached uv.lock");
3971
+ await import_fs3.default.promises.copyFile(cachedLockPath, targetLockPath);
3972
+ }
3973
+ }
3903
3974
  var makeDependencyCheckCode = (dependency) => `
3904
3975
  from importlib import util
3905
3976
  dep = '${dependency}'.replace('-', '_')
@@ -4024,8 +4095,8 @@ async function ensureUvProject({
4024
4095
  pythonPackage,
4025
4096
  pythonVersion,
4026
4097
  uv,
4027
- generateLockFile = false,
4028
- requireBinaryWheels = false
4098
+ requireBinaryWheels = false,
4099
+ cachedLockPath
4029
4100
  }) {
4030
4101
  const { manifestType } = detectInstallSource(pythonPackage, rootDir);
4031
4102
  const manifest = pythonPackage.manifest;
@@ -4077,18 +4148,13 @@ async function ensureUvProject({
4077
4148
  if (workspaceLockFile) {
4078
4149
  lockPath = (0, import_path5.join)(rootDir, workspaceLockFile.path);
4079
4150
  } else {
4151
+ await restoreCachedLock(cachedLockPath, projectDir);
4080
4152
  await uv.lock({
4081
4153
  projectDir,
4082
4154
  venvPath,
4083
4155
  ...requireBinaryWheels ? { noBuild: true, upgrade: true } : {}
4084
4156
  });
4085
- }
4086
- if (generateLockFile && !lockPath) {
4087
- await uv.lock({
4088
- projectDir,
4089
- venvPath,
4090
- ...requireBinaryWheels ? { noBuild: true, upgrade: true } : {}
4091
- });
4157
+ lockPath = (0, import_path5.join)(projectDir, "uv.lock");
4092
4158
  }
4093
4159
  } else {
4094
4160
  projectDir = workPath;
@@ -4104,6 +4170,7 @@ async function ensureUvProject({
4104
4170
  });
4105
4171
  const content = (0, import_python_analysis2.stringifyManifest)(minimalManifest);
4106
4172
  await import_fs3.default.promises.writeFile(pyprojectPath, content);
4173
+ await restoreCachedLock(cachedLockPath, projectDir);
4107
4174
  await uv.lock({
4108
4175
  projectDir,
4109
4176
  venvPath,
@@ -4136,7 +4203,7 @@ async function pipInstall(pipPath, uvPath, workPath, args, targetDir) {
4136
4203
  try {
4137
4204
  await (0, import_execa3.default)(uvPath, uvArgs, {
4138
4205
  cwd: workPath,
4139
- env: getProtectedUvEnv()
4206
+ env: getProtectedUvEnv(process.env, getUvCacheDir(workPath))
4140
4207
  });
4141
4208
  return;
4142
4209
  } catch (err) {
@@ -5004,7 +5071,7 @@ var diagnostics = async ({
5004
5071
  var import_child_process2 = require("child_process");
5005
5072
  var import_fs7 = require("fs");
5006
5073
  var import_path9 = require("path");
5007
- var import_build_utils9 = require("@vercel/build-utils");
5074
+ var import_build_utils10 = require("@vercel/build-utils");
5008
5075
  var import_get_port = __toESM(require_get_port());
5009
5076
  var import_is_port_reachable = __toESM(require_is_port_reachable());
5010
5077
 
@@ -5013,6 +5080,7 @@ var import_fs6 = __toESM(require("fs"));
5013
5080
  var import_path8 = require("path");
5014
5081
  var import_build_utils7 = require("@vercel/build-utils");
5015
5082
  var import_build_utils8 = require("@vercel/build-utils");
5083
+ var import_build_utils9 = require("@vercel/build-utils");
5016
5084
  var import_python_analysis5 = require("@vercel/python-analysis");
5017
5085
  var PYTHON_ENTRYPOINT_FILENAMES = [
5018
5086
  "app",
@@ -5049,7 +5117,7 @@ async function checkEntrypoint(workPath, relPath) {
5049
5117
  return (0, import_python_analysis5.findAppOrHandler)(content);
5050
5118
  }
5051
5119
  async function getPyprojectEntrypoint(workPath) {
5052
- const pyprojectData = await (0, import_build_utils8.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
5120
+ const pyprojectData = await (0, import_build_utils9.readConfigFile)((0, import_path8.join)(workPath, "pyproject.toml"));
5053
5121
  if (!pyprojectData)
5054
5122
  return null;
5055
5123
  const scripts = pyprojectData.project?.scripts;
@@ -5074,7 +5142,7 @@ async function findValidEntrypoint(workPath, candidates) {
5074
5142
  for (const candidate of candidates) {
5075
5143
  const varName = await checkEntrypoint(workPath, candidate);
5076
5144
  if (varName) {
5077
- (0, import_build_utils7.debug)(`Detected Python entrypoint: ${candidate} (variable: ${varName})`);
5145
+ (0, import_build_utils8.debug)(`Detected Python entrypoint: ${candidate} (variable: ${varName})`);
5078
5146
  return { entrypoint: candidate, variableName: varName };
5079
5147
  }
5080
5148
  }
@@ -5086,7 +5154,7 @@ async function checkDjangoManage(workPath) {
5086
5154
  const content = await import_fs6.default.promises.readFile(managePath, "utf-8");
5087
5155
  if (!content.includes("DJANGO_SETTINGS_MODULE"))
5088
5156
  return false;
5089
- (0, import_build_utils7.debug)(`Found Django manage.py with DJANGO_SETTINGS_MODULE at ${workPath}`);
5157
+ (0, import_build_utils8.debug)(`Found Django manage.py with DJANGO_SETTINGS_MODULE at ${workPath}`);
5090
5158
  return true;
5091
5159
  } catch {
5092
5160
  return false;
@@ -5102,55 +5170,78 @@ async function getSubdirectories(workPath) {
5102
5170
  return [];
5103
5171
  }
5104
5172
  }
5105
- async function detectGenericPythonEntrypoint(workPath, configuredEntrypoint) {
5106
- const entry = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
5173
+ function makeDetectError(framework) {
5174
+ const searchedList = PYTHON_CANDIDATE_ENTRYPOINTS.join(", ");
5175
+ return new import_build_utils7.NowBuildError({
5176
+ code: `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`,
5177
+ message: `No ${framework} entrypoint found. Add an 'app' script in pyproject.toml or define an entrypoint in one of: ${searchedList}.`,
5178
+ link: `https://vercel.com/docs/frameworks/backend/${framework}#exporting-the-${framework}-application`,
5179
+ action: "Learn More"
5180
+ });
5181
+ }
5182
+ async function detectGenericPythonEntrypoint(workPath) {
5107
5183
  try {
5108
- const varName = await checkEntrypoint(workPath, entry);
5109
- if (varName) {
5110
- (0, import_build_utils7.debug)(`Using configured Python entrypoint: ${entry}`);
5111
- return { entrypoint: { entrypoint: entry, variableName: varName } };
5112
- }
5113
5184
  const found = await findValidEntrypoint(
5114
5185
  workPath,
5115
5186
  PYTHON_CANDIDATE_ENTRYPOINTS
5116
5187
  );
5117
5188
  return found ? { entrypoint: found } : null;
5118
5189
  } catch {
5119
- (0, import_build_utils7.debug)("Failed to discover Python entrypoint");
5190
+ (0, import_build_utils8.debug)("Failed to discover Python entrypoint");
5120
5191
  return null;
5121
5192
  }
5122
5193
  }
5123
- async function detectDjangoPythonEntrypoint(workPath, configuredEntrypoint) {
5124
- const entry = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
5194
+ async function detectDjangoPythonEntrypoint(workPath) {
5125
5195
  try {
5126
- const varName = await checkEntrypoint(workPath, entry);
5127
- if (varName) {
5128
- (0, import_build_utils7.debug)(`Using configured Python entrypoint: ${entry}`);
5129
- return { entrypoint: { entrypoint: entry, variableName: varName } };
5130
- }
5131
5196
  const subdirs = await getSubdirectories(workPath);
5132
5197
  const rootDirs = ["", ...subdirs];
5133
5198
  for (const rootDir of rootDirs) {
5134
5199
  const currPath = (0, import_path8.join)(workPath, rootDir);
5135
5200
  const isDjango = await checkDjangoManage(currPath);
5136
5201
  if (isDjango) {
5137
- return { baseDir: rootDir };
5202
+ return { baseDir: rootDir, error: makeDetectError("django") };
5138
5203
  }
5139
5204
  }
5140
5205
  const candidates = getCandidateEntrypointsInDirs(rootDirs);
5141
5206
  const found = await findValidEntrypoint(workPath, candidates);
5142
5207
  return found ? { entrypoint: found } : null;
5143
5208
  } catch {
5144
- (0, import_build_utils7.debug)("Failed to discover Django Python entrypoint");
5209
+ (0, import_build_utils8.debug)("Failed to discover Django Python entrypoint");
5145
5210
  return null;
5146
5211
  }
5147
5212
  }
5148
- async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint) {
5149
- const result = framework === "django" ? await detectDjangoPythonEntrypoint(workPath, configuredEntrypoint) : await detectGenericPythonEntrypoint(workPath, configuredEntrypoint);
5213
+ async function detectPythonEntrypoint(framework, workPath, configuredEntrypoint, service) {
5214
+ if (configuredEntrypoint) {
5215
+ const entrypoint = configuredEntrypoint.endsWith(".py") ? configuredEntrypoint : `${configuredEntrypoint}.py`;
5216
+ let varName = await checkEntrypoint(workPath, entrypoint);
5217
+ if (!varName) {
5218
+ const isSpecialService = service?.type === "cron" || service?.type === "worker";
5219
+ if (isSpecialService) {
5220
+ varName = "app";
5221
+ }
5222
+ }
5223
+ if (varName) {
5224
+ (0, import_build_utils8.debug)(`Using configured Python entrypoint: ${entrypoint}`);
5225
+ return { entrypoint: { entrypoint, variableName: varName } };
5226
+ } else {
5227
+ return {
5228
+ error: new import_build_utils7.NowBuildError({
5229
+ code: "PYTHON_ENTRYPOINT_NOT_FOUND",
5230
+ message: `Could not find a top-level "app", "application", or "handler" in "${entrypoint}".`,
5231
+ link: "https://vercel.com/docs/functions/serverless-functions/runtimes/python",
5232
+ action: "Learn More"
5233
+ })
5234
+ };
5235
+ }
5236
+ }
5237
+ if (!framework) {
5238
+ return null;
5239
+ }
5240
+ const result = framework === "django" ? await detectDjangoPythonEntrypoint(workPath) : await detectGenericPythonEntrypoint(workPath);
5150
5241
  if (result)
5151
5242
  return result;
5152
5243
  const pyprojectEntry = await getPyprojectEntrypoint(workPath);
5153
- return pyprojectEntry ? { entrypoint: pyprojectEntry } : null;
5244
+ return pyprojectEntry ? { entrypoint: pyprojectEntry } : { error: makeDetectError(framework) };
5154
5245
  }
5155
5246
 
5156
5247
  // src/start-dev-server.ts
@@ -5240,7 +5331,7 @@ async function syncDependencies({
5240
5331
  let { manifestPath } = installInfo;
5241
5332
  const manifest = pythonPackage.manifest;
5242
5333
  if (!manifestType || !manifestPath) {
5243
- (0, import_build_utils9.debug)("No Python project manifest found, skipping dependency sync");
5334
+ (0, import_build_utils10.debug)("No Python project manifest found, skipping dependency sync");
5244
5335
  return;
5245
5336
  }
5246
5337
  if (manifest?.origin && manifestType === "pyproject.toml") {
@@ -5250,7 +5341,7 @@ async function syncDependencies({
5250
5341
  const content = (0, import_python_analysis6.stringifyManifest)(manifest.data);
5251
5342
  (0, import_fs7.writeFileSync)(tempPyproject, content, "utf8");
5252
5343
  manifestPath = tempPyproject;
5253
- (0, import_build_utils9.debug)(
5344
+ (0, import_build_utils10.debug)(
5254
5345
  `Wrote converted ${manifest.origin.kind} manifest to ${tempPyproject}`
5255
5346
  );
5256
5347
  }
@@ -5283,7 +5374,7 @@ async function syncDependencies({
5283
5374
  for (const [channel, chunk] of captured) {
5284
5375
  (channel === "stdout" ? writeOut : writeErr)(chunk.toString());
5285
5376
  }
5286
- throw new import_build_utils9.NowBuildError({
5377
+ throw new import_build_utils10.NowBuildError({
5287
5378
  code: "PYTHON_DEPENDENCY_SYNC_FAILED",
5288
5379
  message: `Failed to install Python dependencies from ${manifestType}: ${err instanceof Error ? err.message : String(err)}`
5289
5380
  });
@@ -5305,7 +5396,7 @@ async function runSync({
5305
5396
  switch (manifestType) {
5306
5397
  case "uv.lock": {
5307
5398
  if (!uvPath) {
5308
- throw new import_build_utils9.NowBuildError({
5399
+ throw new import_build_utils10.NowBuildError({
5309
5400
  code: "PYTHON_DEPENDENCY_SYNC_FAILED",
5310
5401
  message: "uv is required to install dependencies from uv.lock.",
5311
5402
  link: "https://docs.astral.sh/uv/getting-started/installation/",
@@ -5327,11 +5418,11 @@ async function runSync({
5327
5418
  break;
5328
5419
  }
5329
5420
  default:
5330
- (0, import_build_utils9.debug)(`Unknown manifest type: ${manifestType}`);
5421
+ (0, import_build_utils10.debug)(`Unknown manifest type: ${manifestType}`);
5331
5422
  return;
5332
5423
  }
5333
5424
  await new Promise((resolve3, reject) => {
5334
- (0, import_build_utils9.debug)(`Running "${spawnCmd} ${spawnArgs.join(" ")}" in ${projectDir}...`);
5425
+ (0, import_build_utils10.debug)(`Running "${spawnCmd} ${spawnArgs.join(" ")}" in ${projectDir}...`);
5335
5426
  const child = (0, import_child_process2.spawn)(spawnCmd, spawnArgs, {
5336
5427
  cwd: projectDir,
5337
5428
  env: getProtectedUvEnv(env),
@@ -5418,13 +5509,13 @@ async function doInstallVercelRuntime({
5418
5509
  `vercel_runtime-${VERCEL_RUNTIME_VERSION}.dist-info`
5419
5510
  );
5420
5511
  if ((0, import_fs7.existsSync)(distInfo)) {
5421
- (0, import_build_utils9.debug)(
5512
+ (0, import_build_utils10.debug)(
5422
5513
  `vercel-runtime ${VERCEL_RUNTIME_VERSION} already installed, skipping`
5423
5514
  );
5424
5515
  return;
5425
5516
  }
5426
5517
  }
5427
- (0, import_build_utils9.debug)(
5518
+ (0, import_build_utils10.debug)(
5428
5519
  `Installing vercel-runtime into ${targetDir} (type: ${isLocalDev ? "local" : "pypi"}, source: ${runtimeDep})`
5429
5520
  );
5430
5521
  const pip = uvPath ? { cmd: uvPath, prefix: ["pip", "install"] } : { cmd: pythonBin, prefix: ["-m", "pip", "install"] };
@@ -5439,14 +5530,14 @@ async function doInstallVercelRuntime({
5439
5530
  if (onStdout) {
5440
5531
  onStdout(data);
5441
5532
  } else {
5442
- (0, import_build_utils9.debug)(data.toString());
5533
+ (0, import_build_utils10.debug)(data.toString());
5443
5534
  }
5444
5535
  });
5445
5536
  child.stderr?.on("data", (data) => {
5446
5537
  if (onStderr) {
5447
5538
  onStderr(data);
5448
5539
  } else {
5449
- (0, import_build_utils9.debug)(data.toString());
5540
+ (0, import_build_utils10.debug)(data.toString());
5450
5541
  }
5451
5542
  });
5452
5543
  child.on("error", reject);
@@ -5514,13 +5605,13 @@ async function doInstallVercelWorkers({
5514
5605
  `vercel_workers-${VERCEL_WORKERS_VERSION}.dist-info`
5515
5606
  );
5516
5607
  if ((0, import_fs7.existsSync)(distInfo)) {
5517
- (0, import_build_utils9.debug)(
5608
+ (0, import_build_utils10.debug)(
5518
5609
  `vercel-workers ${VERCEL_WORKERS_VERSION} already installed, skipping`
5519
5610
  );
5520
5611
  return;
5521
5612
  }
5522
5613
  }
5523
- (0, import_build_utils9.debug)(
5614
+ (0, import_build_utils10.debug)(
5524
5615
  `Installing vercel-workers into ${targetDir} (type: ${isLocalDev ? "local" : "pypi"}, source: ${workersDep})`
5525
5616
  );
5526
5617
  const pip = uvPath ? { cmd: uvPath, prefix: ["pip", "install"] } : { cmd: pythonBin, prefix: ["-m", "pip", "install"] };
@@ -5535,14 +5626,14 @@ async function doInstallVercelWorkers({
5535
5626
  if (onStdout) {
5536
5627
  onStdout(data);
5537
5628
  } else {
5538
- (0, import_build_utils9.debug)(data.toString());
5629
+ (0, import_build_utils10.debug)(data.toString());
5539
5630
  }
5540
5631
  });
5541
5632
  child.stderr?.on("data", (data) => {
5542
5633
  if (onStderr) {
5543
5634
  onStderr(data);
5544
5635
  } else {
5545
- (0, import_build_utils9.debug)(data.toString());
5636
+ (0, import_build_utils10.debug)(data.toString());
5546
5637
  }
5547
5638
  });
5548
5639
  child.on("error", reject);
@@ -5572,12 +5663,12 @@ function installGlobalCleanupHandlers() {
5572
5663
  try {
5573
5664
  process.kill(info.pid, "SIGTERM");
5574
5665
  } catch (err) {
5575
- (0, import_build_utils9.debug)(`Error sending SIGTERM to ${info.pid}: ${err}`);
5666
+ (0, import_build_utils10.debug)(`Error sending SIGTERM to ${info.pid}: ${err}`);
5576
5667
  }
5577
5668
  try {
5578
5669
  process.kill(info.pid, "SIGKILL");
5579
5670
  } catch (err) {
5580
- (0, import_build_utils9.debug)(`Error sending SIGKILL to ${info.pid}: ${err}`);
5671
+ (0, import_build_utils10.debug)(`Error sending SIGKILL to ${info.pid}: ${err}`);
5581
5672
  }
5582
5673
  PERSISTENT_SERVERS.delete(key);
5583
5674
  }
@@ -5585,7 +5676,7 @@ function installGlobalCleanupHandlers() {
5585
5676
  try {
5586
5677
  restoreWarnings();
5587
5678
  } catch (err) {
5588
- (0, import_build_utils9.debug)(`Error restoring warnings: ${err}`);
5679
+ (0, import_build_utils10.debug)(`Error restoring warnings: ${err}`);
5589
5680
  }
5590
5681
  restoreWarnings = null;
5591
5682
  }
@@ -5622,26 +5713,31 @@ function createDevShim(workPath, entry, modulePath, serviceName, framework, vari
5622
5713
  const template = (0, import_fs7.readFileSync)(templatePath, "utf8");
5623
5714
  const shimSource = template.replace(/__VC_DEV_MODULE_NAME__/g, qualifiedModule).replace(/__VC_DEV_ENTRY_ABS__/g, entryAbs).replace(/__VC_DEV_FRAMEWORK__/g, framework).replace(/__VC_DEV_VARIABLE_NAME__/g, variableName);
5624
5715
  (0, import_fs7.writeFileSync)(shimPath, shimSource, "utf8");
5625
- (0, import_build_utils9.debug)(`Prepared Python dev shim at ${shimPath}`);
5716
+ (0, import_build_utils10.debug)(`Prepared Python dev shim at ${shimPath}`);
5626
5717
  return {
5627
5718
  module: DEV_SHIM_MODULE,
5628
5719
  extraPythonPath,
5629
5720
  shimDir: vercelPythonDir
5630
5721
  };
5631
5722
  } catch (err) {
5632
- (0, import_build_utils9.debug)(`Failed to prepare dev shim: ${err?.message || err}`);
5723
+ (0, import_build_utils10.debug)(`Failed to prepare dev shim: ${err?.message || err}`);
5633
5724
  return null;
5634
5725
  }
5635
5726
  }
5636
5727
  async function getMultiServicePythonRunner(workPath, env, systemPython, uvPath) {
5637
5728
  const { pythonCmd, venvRoot } = useVirtualEnv(workPath, env, systemPython);
5638
5729
  if (venvRoot) {
5639
- (0, import_build_utils9.debug)(`Using existing virtualenv at ${venvRoot} for multi-service dev`);
5730
+ (0, import_build_utils10.debug)(`Using existing virtualenv at ${venvRoot} for multi-service dev`);
5640
5731
  return { command: pythonCmd, args: [] };
5641
5732
  }
5642
5733
  const venvPath = (0, import_path9.join)(workPath, ".venv");
5643
- await ensureVenv({ pythonPath: systemPython, venvPath, uvPath, quiet: true });
5644
- (0, import_build_utils9.debug)(`Created virtualenv at ${venvPath} for multi-service dev`);
5734
+ await ensureVenv({
5735
+ pythonVersion: { pythonPath: systemPython },
5736
+ venvPath,
5737
+ uvPath,
5738
+ quiet: true
5739
+ });
5740
+ (0, import_build_utils10.debug)(`Created virtualenv at ${venvPath} for multi-service dev`);
5645
5741
  const pythonBin = getVenvPythonBin(venvPath);
5646
5742
  const binDir = getVenvBinDir(venvPath);
5647
5743
  env.VIRTUAL_ENV = venvPath;
@@ -5654,11 +5750,12 @@ var startDevServer = async (opts) => {
5654
5750
  workPath,
5655
5751
  meta = {},
5656
5752
  config,
5753
+ service,
5657
5754
  onStdout,
5658
5755
  onStderr
5659
5756
  } = opts;
5660
5757
  const framework = config?.framework;
5661
- const serviceName = typeof meta.serviceName === "string" ? meta.serviceName : void 0;
5758
+ const serviceName = service?.name ?? (typeof meta.serviceName === "string" ? meta.serviceName : void 0);
5662
5759
  const serverKey = serviceName ? `${workPath}::${framework}::${serviceName}` : `${workPath}::${framework}`;
5663
5760
  const existing = PERSISTENT_SERVERS.get(serverKey);
5664
5761
  if (existing) {
@@ -5688,37 +5785,34 @@ var startDevServer = async (opts) => {
5688
5785
  restoreWarnings = silenceNodeWarnings();
5689
5786
  installGlobalCleanupHandlers();
5690
5787
  const env = { ...process.env, ...meta.env || {} };
5691
- const serviceType = env.VERCEL_SERVICE_TYPE;
5788
+ const entrypoint = rawEntrypoint === "<detect>" ? void 0 : rawEntrypoint;
5692
5789
  let resolved;
5693
- if ((serviceType === "cron" || serviceType === "worker") && rawEntrypoint?.endsWith(".py")) {
5694
- resolved = { entrypoint: rawEntrypoint, variableName: "app" };
5790
+ const detected = await detectPythonEntrypoint(
5791
+ framework,
5792
+ workPath,
5793
+ entrypoint,
5794
+ service
5795
+ );
5796
+ if (detected?.entrypoint) {
5797
+ resolved = detected.entrypoint;
5695
5798
  } else {
5696
- const detected = await detectPythonEntrypoint(
5697
- framework,
5799
+ const hookResult = await runFrameworkHook(framework, {
5800
+ pythonEnv: env,
5801
+ projectDir: (0, import_path9.join)(workPath, detected?.baseDir ?? ""),
5698
5802
  workPath,
5699
- rawEntrypoint
5700
- );
5701
- if (detected?.entrypoint) {
5702
- resolved = detected.entrypoint;
5703
- } else {
5704
- const hookResult = await runFrameworkHook(framework, {
5705
- pythonEnv: env,
5706
- projectDir: (0, import_path9.join)(workPath, detected?.baseDir ?? ""),
5707
- workPath,
5708
- entrypoint: rawEntrypoint,
5709
- detected: detected ?? void 0
5710
- });
5711
- resolved = hookResult?.entrypoint;
5712
- }
5713
- if (!resolved) {
5714
- const searched = PYTHON_CANDIDATE_ENTRYPOINTS.join(", ");
5715
- throw new import_build_utils9.NowBuildError({
5716
- code: "PYTHON_ENTRYPOINT_NOT_FOUND",
5717
- message: `No ${framework} entrypoint found. Add an 'app' script in pyproject.toml or define an entrypoint in one of: ${searched}.`,
5718
- link: `https://vercel.com/docs/frameworks/backend/${framework?.toLowerCase()}#exporting-the-${framework?.toLowerCase()}-application`,
5719
- action: "Learn More"
5720
- });
5803
+ entrypoint,
5804
+ detected: detected ?? void 0
5805
+ });
5806
+ resolved = hookResult?.entrypoint;
5807
+ }
5808
+ if (!resolved) {
5809
+ if (detected?.error) {
5810
+ throw detected.error;
5721
5811
  }
5812
+ throw new import_build_utils10.NowBuildError({
5813
+ code: "PYTHON_ENTRYPOINT_NOT_FOUND",
5814
+ message: "No Python entrypoint could be detected. Please specify an entrypoint file."
5815
+ });
5722
5816
  }
5723
5817
  const { entrypoint: entry, variableName } = resolved;
5724
5818
  const modulePath = entry.replace(/\.py$/i, "").replace(/[\\/]/g, ".");
@@ -5744,7 +5838,7 @@ var startDevServer = async (opts) => {
5744
5838
  const yellow = "\x1B[33m";
5745
5839
  const white = "\x1B[1m";
5746
5840
  const reset = "\x1B[0m";
5747
- throw new import_build_utils9.NowBuildError({
5841
+ throw new import_build_utils10.NowBuildError({
5748
5842
  code: "PYTHON_EXTERNAL_VENV_DETECTED",
5749
5843
  message: `Detected activated venv at ${yellow}${venv}${reset}, ${white}vercel dev${reset} manages virtual environments automatically.
5750
5844
  Run ${white}deactivate${reset} and try again.`
@@ -5761,11 +5855,11 @@ Run ${white}deactivate${reset} and try again.`
5761
5855
  );
5762
5856
  spawnCommand = runner.command;
5763
5857
  spawnArgsPrefix = runner.args;
5764
- (0, import_build_utils9.debug)(
5858
+ (0, import_build_utils10.debug)(
5765
5859
  `Multi-service Python runner: ${spawnCommand} ${spawnArgsPrefix.join(" ")}`
5766
5860
  );
5767
5861
  } else if (venv) {
5768
- (0, import_build_utils9.debug)(`Running in virtualenv at ${venv}`);
5862
+ (0, import_build_utils10.debug)(`Running in virtualenv at ${venv}`);
5769
5863
  } else {
5770
5864
  const { pythonCmd: venvPythonCmd, venvRoot } = useVirtualEnv(
5771
5865
  workPath,
@@ -5774,9 +5868,9 @@ Run ${white}deactivate${reset} and try again.`
5774
5868
  );
5775
5869
  spawnCommand = venvPythonCmd;
5776
5870
  if (venvRoot) {
5777
- (0, import_build_utils9.debug)(`Using virtualenv at ${venvRoot}`);
5871
+ (0, import_build_utils10.debug)(`Using virtualenv at ${venvRoot}`);
5778
5872
  } else {
5779
- (0, import_build_utils9.debug)("No virtualenv found");
5873
+ (0, import_build_utils10.debug)("No virtualenv found");
5780
5874
  try {
5781
5875
  const yellow = "\x1B[33m";
5782
5876
  const reset = "\x1B[0m";
@@ -5857,7 +5951,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
5857
5951
  const moduleToRun = devShim?.module || modulePath;
5858
5952
  const pythonArgs = ["-u", "-m", moduleToRun];
5859
5953
  const argv = [...spawnArgsPrefix, ...pythonArgs];
5860
- (0, import_build_utils9.debug)(
5954
+ (0, import_build_utils10.debug)(
5861
5955
  `Starting Python dev server (${framework}): ${spawnCommand} ${argv.join(" ")} [PORT=${port}]`
5862
5956
  );
5863
5957
  if (process.stdout.columns) {
@@ -5911,7 +6005,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
5911
6005
  };
5912
6006
 
5913
6007
  // src/quirks/index.ts
5914
- var import_build_utils12 = require("@vercel/build-utils");
6008
+ var import_build_utils13 = require("@vercel/build-utils");
5915
6009
  var import_python_analysis8 = require("@vercel/python-analysis");
5916
6010
 
5917
6011
  // src/quirks/matplotlib.ts
@@ -5927,7 +6021,7 @@ var matplotlibQuirk = {
5927
6021
  // src/quirks/litellm.ts
5928
6022
  var import_fs8 = __toESM(require("fs"));
5929
6023
  var import_path10 = require("path");
5930
- var import_build_utils10 = require("@vercel/build-utils");
6024
+ var import_build_utils11 = require("@vercel/build-utils");
5931
6025
  var LAMBDA_ROOT = "/var/task";
5932
6026
  var CONFIG_CANDIDATES = [
5933
6027
  "litellm_config.yaml",
@@ -5962,24 +6056,24 @@ var litellmQuirk = {
5962
6056
  );
5963
6057
  try {
5964
6058
  await import_fs8.default.promises.access(schemaPath);
5965
- (0, import_build_utils10.debug)(`LiteLLM quirk: found schema at ${schemaPath}`);
6059
+ (0, import_build_utils11.debug)(`LiteLLM quirk: found schema at ${schemaPath}`);
5966
6060
  buildEnv.PRISMA_SCHEMA_PATH = schemaPath;
5967
6061
  break;
5968
6062
  } catch {
5969
6063
  }
5970
6064
  }
5971
6065
  if (!buildEnv.PRISMA_SCHEMA_PATH) {
5972
- (0, import_build_utils10.debug)("LiteLLM quirk: schema.prisma not found in any site-packages");
6066
+ (0, import_build_utils11.debug)("LiteLLM quirk: schema.prisma not found in any site-packages");
5973
6067
  }
5974
6068
  if (!process.env.CONFIG_FILE_PATH) {
5975
6069
  const configName = await findConfigFile(ctx.workPath);
5976
6070
  if (configName) {
5977
- (0, import_build_utils10.debug)(`LiteLLM quirk: found config at ${configName}`);
6071
+ (0, import_build_utils11.debug)(`LiteLLM quirk: found config at ${configName}`);
5978
6072
  buildEnv.CONFIG_FILE_PATH = (0, import_path10.join)(ctx.workPath, configName);
5979
6073
  env.CONFIG_FILE_PATH = (0, import_path10.join)(LAMBDA_ROOT, configName);
5980
6074
  }
5981
6075
  } else {
5982
- (0, import_build_utils10.debug)(
6076
+ (0, import_build_utils11.debug)(
5983
6077
  `LiteLLM quirk: CONFIG_FILE_PATH already set to ${process.env.CONFIG_FILE_PATH}`
5984
6078
  );
5985
6079
  }
@@ -5991,7 +6085,7 @@ var litellmQuirk = {
5991
6085
  var import_fs9 = __toESM(require("fs"));
5992
6086
  var import_path11 = require("path");
5993
6087
  var import_execa4 = __toESM(require_execa());
5994
- var import_build_utils11 = require("@vercel/build-utils");
6088
+ var import_build_utils12 = require("@vercel/build-utils");
5995
6089
  var import_python_analysis7 = require("@vercel/python-analysis");
5996
6090
  function execErrorMessage(err) {
5997
6091
  if (err != null && typeof err === "object" && "stderr" in err) {
@@ -6034,7 +6128,7 @@ async function findUserSchema(workPath) {
6034
6128
  await import_fs9.default.promises.access(resolved);
6035
6129
  return resolved;
6036
6130
  } catch {
6037
- (0, import_build_utils11.debug)(`PRISMA_SCHEMA_PATH=${envPath} not found at ${resolved}`);
6131
+ (0, import_build_utils12.debug)(`PRISMA_SCHEMA_PATH=${envPath} not found at ${resolved}`);
6038
6132
  return null;
6039
6133
  }
6040
6134
  }
@@ -6138,7 +6232,7 @@ var prismaQuirk = {
6138
6232
  dummySchemaPath,
6139
6233
  buildDummySchema(generatedDir)
6140
6234
  );
6141
- (0, import_build_utils11.debug)(`Running prisma generate (dummy) with cache dir: ${cacheDir}`);
6235
+ (0, import_build_utils12.debug)(`Running prisma generate (dummy) with cache dir: ${cacheDir}`);
6142
6236
  try {
6143
6237
  const dummyResult = await (0, import_execa4.default)(
6144
6238
  pythonPath,
@@ -6150,11 +6244,11 @@ var prismaQuirk = {
6150
6244
  }
6151
6245
  );
6152
6246
  if (dummyResult.stdout)
6153
- (0, import_build_utils11.debug)(`prisma generate (dummy) stdout: ${dummyResult.stdout}`);
6247
+ (0, import_build_utils12.debug)(`prisma generate (dummy) stdout: ${dummyResult.stdout}`);
6154
6248
  if (dummyResult.stderr)
6155
- (0, import_build_utils11.debug)(`prisma generate (dummy) stderr: ${dummyResult.stderr}`);
6249
+ (0, import_build_utils12.debug)(`prisma generate (dummy) stderr: ${dummyResult.stderr}`);
6156
6250
  } catch (err) {
6157
- throw new import_build_utils11.NowBuildError({
6251
+ throw new import_build_utils12.NowBuildError({
6158
6252
  code: "PRISMA_GENERATE_FAILED",
6159
6253
  message: `Prisma engine download failed during \`prisma generate\`. Check that your prisma version is compatible with this Python version.
6160
6254
  ` + execErrorMessage(err)
@@ -6173,22 +6267,22 @@ var prismaQuirk = {
6173
6267
  const destPath = (0, import_path11.join)(cacheDir, runtimeName);
6174
6268
  try {
6175
6269
  await import_fs9.default.promises.access(destPath);
6176
- (0, import_build_utils11.debug)(`Engine binary: ${runtimeName} already exists, skipping`);
6270
+ (0, import_build_utils12.debug)(`Engine binary: ${runtimeName} already exists, skipping`);
6177
6271
  } catch {
6178
- (0, import_build_utils11.debug)(`Engine binary: copying ${entry} -> ${runtimeName}`);
6272
+ (0, import_build_utils12.debug)(`Engine binary: copying ${entry} -> ${runtimeName}`);
6179
6273
  await import_fs9.default.promises.copyFile(srcPath, destPath);
6180
6274
  }
6181
6275
  engineCopied = true;
6182
6276
  }
6183
6277
  } catch (err) {
6184
- throw new import_build_utils11.NowBuildError({
6278
+ throw new import_build_utils12.NowBuildError({
6185
6279
  code: "PRISMA_ENGINE_NOT_FOUND",
6186
6280
  message: `could not read Prisma engine directory "${nodeModulesDir}". This may indicate an incompatible prisma version.
6187
6281
  ` + (err instanceof Error ? err.message : String(err))
6188
6282
  });
6189
6283
  }
6190
6284
  if (!engineCopied) {
6191
- throw new import_build_utils11.NowBuildError({
6285
+ throw new import_build_utils12.NowBuildError({
6192
6286
  code: "PRISMA_ENGINE_NOT_FOUND",
6193
6287
  message: `could not find engine binary matching "${srcBinaryPrefix}*" in "${nodeModulesDir}". This may indicate an incompatible prisma version or an unsupported platform (${process.arch}).`
6194
6288
  });
@@ -6215,14 +6309,14 @@ echo "OpenSSL ${RUNTIME_OPENSSL_VERSION}.0 1 Jan 2024 (Library: OpenSSL ${RUNTIM
6215
6309
  pythonEnv
6216
6310
  );
6217
6311
  if (clientAlreadyGenerated) {
6218
- (0, import_build_utils11.debug)(
6312
+ (0, import_build_utils12.debug)(
6219
6313
  "Prisma quirk: client already generated, skipping user schema generate"
6220
6314
  );
6221
6315
  shouldGenerate = false;
6222
6316
  }
6223
6317
  }
6224
6318
  if (shouldGenerate) {
6225
- (0, import_build_utils11.debug)(`Running prisma generate with user schema: ${userSchema}`);
6319
+ (0, import_build_utils12.debug)(`Running prisma generate with user schema: ${userSchema}`);
6226
6320
  try {
6227
6321
  const userResult = await (0, import_execa4.default)(
6228
6322
  pythonPath,
@@ -6234,11 +6328,11 @@ echo "OpenSSL ${RUNTIME_OPENSSL_VERSION}.0 1 Jan 2024 (Library: OpenSSL ${RUNTIM
6234
6328
  }
6235
6329
  );
6236
6330
  if (userResult.stdout)
6237
- (0, import_build_utils11.debug)(`prisma generate stdout: ${userResult.stdout}`);
6331
+ (0, import_build_utils12.debug)(`prisma generate stdout: ${userResult.stdout}`);
6238
6332
  if (userResult.stderr)
6239
- (0, import_build_utils11.debug)(`prisma generate stderr: ${userResult.stderr}`);
6333
+ (0, import_build_utils12.debug)(`prisma generate stderr: ${userResult.stderr}`);
6240
6334
  } catch (err) {
6241
- throw new import_build_utils11.NowBuildError({
6335
+ throw new import_build_utils12.NowBuildError({
6242
6336
  code: "PRISMA_GENERATE_FAILED",
6243
6337
  message: `\`prisma generate\` failed for schema "${userSchema}".
6244
6338
  ` + execErrorMessage(err)
@@ -6254,7 +6348,7 @@ echo "OpenSSL ${RUNTIME_OPENSSL_VERSION}.0 1 Jan 2024 (Library: OpenSSL ${RUNTIM
6254
6348
  );
6255
6349
  const count = await (0, import_python_analysis7.extendDistRecord)(sitePackages, "prisma", allFiles);
6256
6350
  if (count > 0) {
6257
- (0, import_build_utils11.debug)(`Appended ${count} entries to prisma RECORD`);
6351
+ (0, import_build_utils12.debug)(`Appended ${count} entries to prisma RECORD`);
6258
6352
  }
6259
6353
  } catch (err) {
6260
6354
  console.warn(
@@ -6348,13 +6442,13 @@ async function runQuirks(ctx) {
6348
6442
  (0, import_python_analysis8.normalizePackageName)(quirk.dependency)
6349
6443
  );
6350
6444
  if (!installed) {
6351
- (0, import_build_utils12.debug)(`Quirk "${quirk.dependency}": not installed, skipping`);
6445
+ (0, import_build_utils13.debug)(`Quirk "${quirk.dependency}": not installed, skipping`);
6352
6446
  }
6353
6447
  return installed;
6354
6448
  });
6355
6449
  const sorted = toposortQuirks(activated);
6356
6450
  for (const quirk of sorted) {
6357
- (0, import_build_utils12.debug)(`Quirk "${quirk.dependency}": detected, running fix-up`);
6451
+ (0, import_build_utils13.debug)(`Quirk "${quirk.dependency}": detected, running fix-up`);
6358
6452
  const result = await quirk.run(ctx);
6359
6453
  if (result.env) {
6360
6454
  Object.assign(mergedEnv, result.env);
@@ -6378,31 +6472,30 @@ async function runQuirks(ctx) {
6378
6472
  var import_fs10 = __toESM(require("fs"));
6379
6473
  var import_path12 = require("path");
6380
6474
  var import_execa5 = __toESM(require_execa());
6381
- var import_build_utils13 = require("@vercel/build-utils");
6475
+ var import_build_utils14 = require("@vercel/build-utils");
6382
6476
  var scriptPath = (0, import_path12.join)(__dirname, "..", "templates", "vc_django_settings.py");
6383
6477
  var script = import_fs10.default.readFileSync(scriptPath, "utf-8");
6384
6478
  async function getDjangoSettings(projectDir, env) {
6385
- try {
6386
- const { stdout } = await (0, import_execa5.default)("python", ["-c", script], {
6387
- env,
6388
- cwd: projectDir
6389
- });
6390
- const parsed = JSON.parse(stdout);
6391
- if (!parsed)
6392
- return null;
6393
- return {
6394
- settingsModule: parsed.settings_module,
6395
- djangoSettings: parsed.django_settings
6396
- };
6397
- } catch (err) {
6398
- (0, import_build_utils13.debug)(`Django hook: failed to discover settings from manage.py: ${err}`);
6399
- return null;
6479
+ const { stdout } = await (0, import_execa5.default)("python", ["-c", script], {
6480
+ env,
6481
+ cwd: projectDir
6482
+ });
6483
+ const parsed = JSON.parse(stdout);
6484
+ if (!parsed) {
6485
+ throw new Error("manage.py did not return any settings");
6400
6486
  }
6487
+ return {
6488
+ settingsModule: parsed.settings_module,
6489
+ djangoSettings: parsed.django_settings,
6490
+ djangoVersion: parsed.django_version ?? null
6491
+ };
6401
6492
  }
6402
- async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir, settingsModule, djangoSettings) {
6493
+ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir, settingsModule, djangoSettings, djangoVersion) {
6403
6494
  const pythonPath = getVenvPythonBin(venvPath);
6404
6495
  const storages = djangoSettings["STORAGES"];
6405
- const storageBackend = storages?.staticfiles?.BACKEND ?? djangoSettings["STATICFILES_STORAGE"] ?? "django.contrib.staticfiles.storage.StaticFilesStorage";
6496
+ const useLegacySetting = !djangoVersion || djangoVersion[0] < 5 || djangoVersion[0] === 5 && djangoVersion[1] < 1;
6497
+ const legacyBackend = useLegacySetting ? djangoSettings["STATICFILES_STORAGE"] : void 0;
6498
+ const storageBackend = storages?.staticfiles?.BACKEND ?? legacyBackend ?? "django.contrib.staticfiles.storage.StaticFilesStorage";
6406
6499
  const staticUrl = djangoSettings["STATIC_URL"] ?? "/static/";
6407
6500
  const staticRoot = djangoSettings["STATIC_ROOT"] != null ? String(djangoSettings["STATIC_ROOT"]) : null;
6408
6501
  const whitenoiseUseFinders = djangoSettings["WHITENOISE_USE_FINDERS"] === true;
@@ -6471,7 +6564,7 @@ async function runDjangoCollectStatic(venvPath, workPath, env, outputStaticDir,
6471
6564
  await import_fs10.default.promises.mkdir(resolvedStaticRoot, { recursive: true });
6472
6565
  await import_fs10.default.promises.copyFile(manifestSrc, manifestDest);
6473
6566
  manifestRelPath = (0, import_path12.relative)(workPath, manifestDest);
6474
- (0, import_build_utils13.debug)(`Copied staticfiles.json to ${manifestDest} for Lambda bundle`);
6567
+ (0, import_build_utils14.debug)(`Copied staticfiles.json to ${manifestDest} for Lambda bundle`);
6475
6568
  }
6476
6569
  return {
6477
6570
  staticSourceDirs,
@@ -6498,14 +6591,31 @@ var frameworkHooks = {
6498
6591
  detected
6499
6592
  }) => {
6500
6593
  if (detected?.baseDir === void 0) {
6501
- (0, import_build_utils14.debug)("Django hook: no manage.py detected, skipping");
6594
+ (0, import_build_utils15.debug)("Django hook: no manage.py detected, skipping");
6502
6595
  return;
6503
6596
  }
6504
- const settingsResult = await getDjangoSettings(projectDir, pythonEnv);
6505
- (0, import_build_utils14.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
6506
- if (!settingsResult)
6507
- return;
6508
- const { djangoSettings, settingsModule } = settingsResult;
6597
+ let settingsResult;
6598
+ try {
6599
+ settingsResult = await getDjangoSettings(projectDir, pythonEnv);
6600
+ } catch (err) {
6601
+ let detail;
6602
+ if (err?.code === "ENOENT") {
6603
+ detail = `command not found: python
6604
+ Hint: activate a venv or run with \`uv run vercel dev\``;
6605
+ } else {
6606
+ detail = err?.stderr || err?.message || String(err);
6607
+ }
6608
+ throw new import_build_utils15.NowBuildError({
6609
+ code: "DJANGO_SETTINGS_FAILED",
6610
+ message: `Failed to read Django application settings from ${projectDir}/manage.py:
6611
+ ${detail}`
6612
+ });
6613
+ }
6614
+ (0, import_build_utils15.debug)(`Django settings: ${JSON.stringify(settingsResult)}`);
6615
+ const { djangoSettings, settingsModule, djangoVersion } = settingsResult;
6616
+ if (djangoVersion) {
6617
+ console.log(`Django ${djangoVersion.join(".")} detected`);
6618
+ }
6509
6619
  let resolvedEntrypoint;
6510
6620
  const baseDir = detected?.baseDir ?? "";
6511
6621
  const asgiApp = djangoSettings["ASGI_APPLICATION"];
@@ -6514,7 +6624,7 @@ var frameworkHooks = {
6514
6624
  const variableName = parts.at(-1);
6515
6625
  const rel = `${parts.slice(0, -1).join("/")}.py`;
6516
6626
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
6517
- (0, import_build_utils14.debug)(`Django hook: ASGI entrypoint: ${ep} (variable: ${variableName})`);
6627
+ (0, import_build_utils15.debug)(`Django hook: ASGI entrypoint: ${ep} (variable: ${variableName})`);
6518
6628
  resolvedEntrypoint = { entrypoint: ep, variableName };
6519
6629
  } else {
6520
6630
  const wsgiApp = djangoSettings["WSGI_APPLICATION"];
@@ -6523,7 +6633,7 @@ var frameworkHooks = {
6523
6633
  const variableName = parts.at(-1);
6524
6634
  const rel = `${parts.slice(0, -1).join("/")}.py`;
6525
6635
  const ep = baseDir ? `${baseDir}/${rel}` : rel;
6526
- (0, import_build_utils14.debug)(
6636
+ (0, import_build_utils15.debug)(
6527
6637
  `Django hook: WSGI entrypoint: ${ep} (variable: ${variableName})`
6528
6638
  );
6529
6639
  resolvedEntrypoint = { entrypoint: ep, variableName };
@@ -6538,7 +6648,8 @@ var frameworkHooks = {
6538
6648
  pythonEnv,
6539
6649
  outputStaticDir,
6540
6650
  settingsModule,
6541
- djangoSettings
6651
+ djangoSettings,
6652
+ djangoVersion
6542
6653
  );
6543
6654
  }
6544
6655
  return { entrypoint: resolvedEntrypoint, djangoStatic };
@@ -6550,14 +6661,14 @@ async function downloadFilesInWorkPath({
6550
6661
  files,
6551
6662
  meta = {}
6552
6663
  }) {
6553
- (0, import_build_utils14.debug)("Downloading user files...");
6554
- let downloadedFiles = await (0, import_build_utils14.download)(files, workPath, meta);
6555
- if (meta.isDev) {
6664
+ (0, import_build_utils15.debug)("Downloading user files...");
6665
+ let downloadedFiles = await (0, import_build_utils15.download)(files, workPath, meta);
6666
+ if (meta.isDev && entrypoint) {
6556
6667
  const { devCacheDir = (0, import_path13.join)(workPath, ".now", "cache") } = meta;
6557
6668
  const cacheKey = (0, import_path13.basename)(entrypoint).replace(/\./g, "_");
6558
6669
  const destCache = (0, import_path13.join)(devCacheDir, cacheKey);
6559
- await (0, import_build_utils14.download)(downloadedFiles, destCache);
6560
- downloadedFiles = await (0, import_build_utils14.glob)("**", destCache);
6670
+ await (0, import_build_utils15.download)(downloadedFiles, destCache);
6671
+ downloadedFiles = await (0, import_build_utils15.glob)("**", destCache);
6561
6672
  workPath = destCache;
6562
6673
  }
6563
6674
  return workPath;
@@ -6566,19 +6677,20 @@ var build = async ({
6566
6677
  workPath,
6567
6678
  repoRootPath,
6568
6679
  files: originalFiles,
6569
- entrypoint,
6680
+ entrypoint: rawEntrypoint,
6570
6681
  meta = {},
6571
6682
  config,
6572
6683
  span: parentSpan,
6573
6684
  service
6574
6685
  }) => {
6575
- const builderSpan = parentSpan ?? new import_build_utils14.Span({ name: "vc.builder" });
6686
+ let entrypoint = rawEntrypoint === "<detect>" ? void 0 : rawEntrypoint;
6687
+ const builderSpan = parentSpan ?? new import_build_utils15.Span({ name: "vc.builder" });
6576
6688
  const framework = config?.framework;
6577
6689
  const shouldInstallVercelWorkers = config?.hasWorkerServices === true;
6578
6690
  let spawnEnv;
6579
6691
  let projectInstallCommand;
6580
6692
  let hasCustomCommand = false;
6581
- (0, import_build_utils14.debug)(`workPath: ${workPath}`);
6693
+ (0, import_build_utils15.debug)(`workPath: ${workPath}`);
6582
6694
  workPath = await downloadFilesInWorkPath({
6583
6695
  workPath,
6584
6696
  files: originalFiles,
@@ -6594,56 +6706,17 @@ var build = async ({
6594
6706
  console.log('Failed to create "setup.cfg" file');
6595
6707
  throw err;
6596
6708
  }
6597
- let fsFiles = await (0, import_build_utils14.glob)("**", workPath);
6598
6709
  let detected;
6599
- let entrypointNotFound;
6600
- if ((0, import_build_utils14.isPythonFramework)(framework) && // XXX: we might want to detect anyway for django!
6601
- (!fsFiles[entrypoint] || !entrypoint.endsWith(".py"))) {
6602
- detected = await detectPythonEntrypoint(
6603
- config.framework,
6604
- workPath,
6605
- entrypoint
6606
- ) ?? void 0;
6607
- if (detected?.entrypoint) {
6608
- (0, import_build_utils14.debug)(
6609
- `Resolved Python entrypoint to "${detected.entrypoint.entrypoint}" (configured "${entrypoint}" not found).`
6610
- );
6611
- entrypoint = detected.entrypoint.entrypoint;
6612
- } else {
6613
- const searchedList = PYTHON_CANDIDATE_ENTRYPOINTS.join(", ");
6614
- entrypointNotFound = new import_build_utils14.NowBuildError({
6615
- code: `${framework.toUpperCase()}_ENTRYPOINT_NOT_FOUND`,
6616
- message: `No ${framework} entrypoint found. Add an 'app' script in pyproject.toml or define an entrypoint in one of: ${searchedList}.`,
6617
- link: `https://vercel.com/docs/frameworks/backend/${framework}#exporting-the-${framework}-application`,
6618
- action: "Learn More"
6619
- });
6620
- }
6621
- }
6622
- if (!detected?.entrypoint && entrypoint.endsWith(".py") && fsFiles[entrypoint]) {
6623
- const content = await import_fs11.default.promises.readFile(
6624
- (0, import_path13.join)(workPath, entrypoint),
6625
- "utf-8"
6626
- );
6627
- let varName = await (0, import_python_analysis9.findAppOrHandler)(content);
6628
- if (!varName) {
6629
- const isSpecialService = service?.type === "cron" || service?.type === "worker";
6630
- if (isSpecialService) {
6631
- varName = "app";
6632
- } else if (!varName) {
6633
- throw new import_build_utils14.NowBuildError({
6634
- code: "PYTHON_ENTRYPOINT_NOT_FOUND",
6635
- message: `Could not find a top-level "app", "application", or "handler" in "${entrypoint}".`,
6636
- link: "https://vercel.com/docs/functions/serverless-functions/runtimes/python",
6637
- action: "Learn More"
6638
- });
6639
- }
6640
- }
6641
- detected = { entrypoint: { entrypoint, variableName: varName } };
6642
- }
6643
- if (entrypointNotFound && detected?.baseDir === void 0) {
6644
- throw entrypointNotFound;
6710
+ detected = await detectPythonEntrypoint(
6711
+ config.framework,
6712
+ workPath,
6713
+ entrypoint,
6714
+ service
6715
+ ) ?? void 0;
6716
+ if (detected?.error && detected?.baseDir === void 0) {
6717
+ throw detected?.error;
6645
6718
  }
6646
- const entryDirectory = detected?.baseDir ?? (0, import_path13.dirname)(entrypoint);
6719
+ const entryDirectory = detected?.baseDir ?? (entrypoint ? (0, import_path13.dirname)(entrypoint) : ".");
6647
6720
  const entrypointAbsDir = (0, import_path13.join)(workPath, entryDirectory);
6648
6721
  const rootDir = repoRootPath ?? workPath;
6649
6722
  const pythonPackage = await builderSpan.child("vc.builder.python.discover").trace(
@@ -6674,22 +6747,30 @@ var build = async ({
6674
6747
  `
6675
6748
  );
6676
6749
  }
6677
- fsFiles = await (0, import_build_utils14.glob)("**", workPath);
6678
6750
  const venvPath = service?.name ? (0, import_path13.join)(workPath, ".vercel", "python", "services", service.name, ".venv") : (0, import_path13.join)(workPath, ".vercel", "python", ".venv");
6751
+ const uvCacheDir = getUvCacheDir(workPath);
6752
+ const hasCachedVenv = import_fs11.default.existsSync((0, import_path13.join)(venvPath, "pyvenv.cfg"));
6753
+ const hasCachedUv = import_fs11.default.existsSync(uvCacheDir);
6754
+ if (hasCachedVenv || hasCachedUv) {
6755
+ (0, import_build_utils15.debug)(
6756
+ `Build cache detected: venv=${hasCachedVenv}, uv-cache=${hasCachedUv}`
6757
+ );
6758
+ }
6679
6759
  await builderSpan.child("vc.builder.python.venv").trace(async () => {
6680
6760
  await ensureVenv({
6681
- pythonPath: pythonVersion.pythonPath,
6682
- venvPath
6761
+ pythonVersion,
6762
+ venvPath,
6763
+ uvCacheDir
6683
6764
  });
6684
6765
  });
6685
- if ((0, import_build_utils14.isPythonFramework)(framework)) {
6766
+ if ((0, import_build_utils15.isPythonFramework)(framework)) {
6686
6767
  const {
6687
6768
  cliType,
6688
6769
  lockfileVersion,
6689
6770
  packageJsonPackageManager,
6690
6771
  turboSupportsCorepackHome
6691
- } = await (0, import_build_utils14.scanParentDirs)(workPath, true);
6692
- spawnEnv = (0, import_build_utils14.getEnvForPackageManager)({
6772
+ } = await (0, import_build_utils15.scanParentDirs)(workPath, true);
6773
+ spawnEnv = (0, import_build_utils15.getEnvForPackageManager)({
6693
6774
  cliType,
6694
6775
  lockfileVersion,
6695
6776
  packageJsonPackageManager,
@@ -6708,13 +6789,13 @@ var build = async ({
6708
6789
  }
6709
6790
  }
6710
6791
  const baseEnv = spawnEnv || process.env;
6711
- const pythonEnv = createVenvEnv(venvPath, baseEnv);
6792
+ const pythonEnv = createVenvEnv(venvPath, baseEnv, uvCacheDir);
6712
6793
  pythonEnv.VERCEL_PYTHON_VENV_PATH = venvPath;
6713
6794
  let assumeDepsInstalled = false;
6714
6795
  let uv;
6715
6796
  try {
6716
6797
  const uvPath = await getUvBinaryOrInstall(pythonVersion.pythonPath);
6717
- uv = new UvRunner(uvPath);
6798
+ uv = new UvRunner(uvPath, uvCacheDir);
6718
6799
  const uvVersionOutput = (0, import_child_process3.execSync)(`${uvPath} --version`, {
6719
6800
  encoding: "utf8"
6720
6801
  }).trim();
@@ -6728,32 +6809,42 @@ var build = async ({
6728
6809
  let uvLockPath = null;
6729
6810
  let uvProjectDir = null;
6730
6811
  let projectName;
6731
- await builderSpan.child(import_build_utils14.BUILDER_INSTALLER_STEP, {
6812
+ await builderSpan.child(import_build_utils15.BUILDER_INSTALLER_STEP, {
6732
6813
  installCommand: projectInstallCommand || void 0
6733
6814
  }).trace(async () => {
6734
6815
  if (projectInstallCommand) {
6816
+ await import_fs11.default.promises.rm(venvPath, { recursive: true, force: true });
6817
+ await ensureVenv({
6818
+ pythonVersion,
6819
+ venvPath,
6820
+ uvCacheDir,
6821
+ quiet: true
6822
+ });
6735
6823
  console.log(
6736
6824
  `Running "install" command: \`${projectInstallCommand}\`...`
6737
6825
  );
6738
- await (0, import_build_utils14.execCommand)(projectInstallCommand, {
6826
+ await (0, import_build_utils15.execCommand)(projectInstallCommand, {
6739
6827
  env: pythonEnv,
6740
6828
  cwd: workPath
6741
6829
  });
6742
6830
  assumeDepsInstalled = true;
6743
6831
  hasCustomCommand = true;
6744
6832
  } else {
6745
- assumeDepsInstalled = await runPyprojectScript(
6833
+ const hasCustomScript = await runPyprojectScript(
6746
6834
  workPath,
6747
6835
  ["vercel-install", "now-install", "install"],
6748
6836
  pythonEnv,
6749
6837
  /* useUserVirtualEnv */
6750
6838
  false
6751
6839
  );
6752
- if (assumeDepsInstalled) {
6840
+ if (hasCustomScript) {
6841
+ assumeDepsInstalled = true;
6753
6842
  hasCustomCommand = true;
6754
6843
  }
6755
6844
  }
6756
6845
  if (!assumeDepsInstalled) {
6846
+ const lockCacheKey = service?.name ? `uv.lock.${service.name}` : "uv.lock";
6847
+ const cachedLockPath = (0, import_path13.join)(uvCacheDir, lockCacheKey);
6757
6848
  const { projectDir, lockPath, lockFileProvidedByUser } = await ensureUvProject({
6758
6849
  workPath,
6759
6850
  rootDir,
@@ -6761,8 +6852,8 @@ var build = async ({
6761
6852
  pythonPackage,
6762
6853
  pythonVersion: pythonVersionString(pythonVersion),
6763
6854
  uv,
6764
- generateLockFile: true,
6765
- requireBinaryWheels: false
6855
+ requireBinaryWheels: false,
6856
+ cachedLockPath
6766
6857
  });
6767
6858
  uvLockPath = lockPath;
6768
6859
  uvProjectDir = projectDir;
@@ -6773,17 +6864,21 @@ var build = async ({
6773
6864
  frozen: lockFileProvidedByUser,
6774
6865
  locked: !lockFileProvidedByUser
6775
6866
  });
6867
+ if (lockPath && import_fs11.default.existsSync(lockPath)) {
6868
+ await import_fs11.default.promises.mkdir(uvCacheDir, { recursive: true });
6869
+ await import_fs11.default.promises.copyFile(lockPath, cachedLockPath);
6870
+ }
6776
6871
  }
6777
6872
  });
6778
- if ((0, import_build_utils14.isPythonFramework)(framework)) {
6873
+ if ((0, import_build_utils15.isPythonFramework)(framework)) {
6779
6874
  const projectBuildCommand = config?.projectSettings?.buildCommand ?? // fallback if provided directly on config (some callers set this)
6780
6875
  config?.buildCommand;
6781
- await builderSpan.child(import_build_utils14.BUILDER_COMPILE_STEP, {
6876
+ await builderSpan.child(import_build_utils15.BUILDER_COMPILE_STEP, {
6782
6877
  buildCommand: projectBuildCommand || void 0
6783
6878
  }).trace(async () => {
6784
6879
  if (projectBuildCommand) {
6785
6880
  console.log(`Running "${projectBuildCommand}"`);
6786
- await (0, import_build_utils14.execCommand)(projectBuildCommand, {
6881
+ await (0, import_build_utils15.execCommand)(projectBuildCommand, {
6787
6882
  env: pythonEnv,
6788
6883
  cwd: workPath
6789
6884
  });
@@ -6805,16 +6900,19 @@ var build = async ({
6805
6900
  detected
6806
6901
  });
6807
6902
  const resolved = hookResult?.entrypoint ?? detected?.entrypoint;
6808
- if (entrypointNotFound && resolved) {
6809
- entrypoint = resolved.entrypoint;
6810
- entrypointNotFound = void 0;
6811
- }
6812
- if (entrypointNotFound) {
6813
- throw entrypointNotFound;
6903
+ if (!resolved && detected?.error) {
6904
+ throw detected?.error;
6905
+ }
6906
+ entrypoint = resolved?.entrypoint;
6907
+ if (!entrypoint) {
6908
+ throw new import_build_utils15.NowBuildError({
6909
+ code: "PYTHON_ENTRYPOINT_NOT_FOUND",
6910
+ message: "No Python entrypoint could be detected. Please specify an entrypoint file."
6911
+ });
6814
6912
  }
6815
6913
  const djangoStatic = hookResult?.djangoStatic ?? null;
6816
6914
  const runtimeDep = baseEnv.VERCEL_RUNTIME_PYTHON || `vercel-runtime==${VERCEL_RUNTIME_VERSION}`;
6817
- (0, import_build_utils14.debug)(`Installing ${runtimeDep}`);
6915
+ (0, import_build_utils15.debug)(`Installing ${runtimeDep}`);
6818
6916
  await uv.pip({
6819
6917
  venvPath,
6820
6918
  projectDir: (0, import_path13.join)(workPath, entryDirectory),
@@ -6822,7 +6920,7 @@ var build = async ({
6822
6920
  });
6823
6921
  if (shouldInstallVercelWorkers) {
6824
6922
  const workersDep = baseEnv.VERCEL_WORKERS_PYTHON || `vercel-workers==${VERCEL_WORKERS_VERSION}`;
6825
- (0, import_build_utils14.debug)(`Installing ${workersDep}`);
6923
+ (0, import_build_utils15.debug)(`Installing ${workersDep}`);
6826
6924
  await uv.pip({
6827
6925
  venvPath,
6828
6926
  projectDir: (0, import_path13.join)(workPath, entryDirectory),
@@ -6833,7 +6931,7 @@ var build = async ({
6833
6931
  if (quirksResult.buildEnv) {
6834
6932
  Object.assign(pythonEnv, quirksResult.buildEnv);
6835
6933
  }
6836
- (0, import_build_utils14.debug)("Entrypoint is", entrypoint);
6934
+ (0, import_build_utils15.debug)("Entrypoint is", entrypoint);
6837
6935
  const moduleName = entrypoint.replace(/\//g, ".").replace(/\.py$/i, "");
6838
6936
  const handlerFunction = typeof config?.handlerFunction === "string" ? config.handlerFunction : void 0;
6839
6937
  if (handlerFunction) {
@@ -6841,7 +6939,7 @@ var build = async ({
6841
6939
  const source = await import_fs11.default.promises.readFile(entrypointPath, "utf-8");
6842
6940
  const found = await (0, import_python_analysis9.containsTopLevelCallable)(source, handlerFunction);
6843
6941
  if (!found) {
6844
- throw new import_build_utils14.NowBuildError({
6942
+ throw new import_build_utils15.NowBuildError({
6845
6943
  code: "PYTHON_HANDLER_NOT_FOUND",
6846
6944
  message: `Handler function "${handlerFunction}" not found in ${entrypoint}. Ensure it is defined at the module's top level.`
6847
6945
  });
@@ -6850,7 +6948,7 @@ var build = async ({
6850
6948
  const vendorDir = resolveVendorDir();
6851
6949
  const suffix = meta.isDev && !entrypoint.endsWith(".py") ? ".py" : "";
6852
6950
  const entrypointWithSuffix = `${entrypoint}${suffix}`;
6853
- (0, import_build_utils14.debug)("Entrypoint with suffix is", entrypointWithSuffix);
6951
+ (0, import_build_utils15.debug)("Entrypoint with suffix is", entrypointWithSuffix);
6854
6952
  const handlerFuncEnvLine = handlerFunction ? `
6855
6953
  "__VC_HANDLER_FUNC_NAME": "${handlerFunction}",` : "";
6856
6954
  const variableName = resolved?.variableName ?? "";
@@ -6911,18 +7009,6 @@ from vercel_runtime.vc_init import vc_handler
6911
7009
  "**/yarn.lock",
6912
7010
  "**/package-lock.json"
6913
7011
  ];
6914
- if (djangoStatic) {
6915
- const dirsToExclude = [
6916
- ...djangoStatic.staticSourceDirs,
6917
- ...djangoStatic.staticRoot ? [djangoStatic.staticRoot] : []
6918
- ];
6919
- for (const absDir of dirsToExclude) {
6920
- const rel = (0, import_path13.relative)(workPath, absDir);
6921
- if (!rel.startsWith("..")) {
6922
- predefinedExcludes.push(`${rel}/**`);
6923
- }
6924
- }
6925
- }
6926
7012
  const lambdaEnv = {};
6927
7013
  lambdaEnv.PYTHONPATH = vendorDir;
6928
7014
  Object.assign(lambdaEnv, quirksResult.env);
@@ -6933,9 +7019,9 @@ from vercel_runtime.vc_init import vc_handler
6933
7019
  cwd: workPath,
6934
7020
  ignore: config && typeof config.excludeFiles === "string" ? [...predefinedExcludes, config.excludeFiles] : predefinedExcludes
6935
7021
  };
6936
- const files = await (0, import_build_utils14.glob)("**", globOptions);
7022
+ const files = await (0, import_build_utils15.glob)("**", globOptions);
6937
7023
  if (djangoStatic?.manifestRelPath) {
6938
- files[djangoStatic.manifestRelPath] = new import_build_utils14.FileFsRef({
7024
+ files[djangoStatic.manifestRelPath] = new import_build_utils15.FileFsRef({
6939
7025
  fsPath: (0, import_path13.join)(workPath, djangoStatic.manifestRelPath)
6940
7026
  });
6941
7027
  }
@@ -6972,12 +7058,12 @@ from vercel_runtime.vc_init import vc_handler
6972
7058
  }
6973
7059
  });
6974
7060
  const handlerPyFilename = "vc__handler__python";
6975
- files[`${handlerPyFilename}.py`] = new import_build_utils14.FileBlob({ data: runtimeTrampoline });
7061
+ files[`${handlerPyFilename}.py`] = new import_build_utils15.FileBlob({ data: runtimeTrampoline });
6976
7062
  if (config.framework === "fasthtml") {
6977
7063
  const { SESSKEY = "" } = process.env;
6978
- files[".sesskey"] = new import_build_utils14.FileBlob({ data: `"${SESSKEY}"` });
7064
+ files[".sesskey"] = new import_build_utils15.FileBlob({ data: `"${SESSKEY}"` });
6979
7065
  }
6980
- const output = new import_build_utils14.Lambda({
7066
+ const output = new import_build_utils15.Lambda({
6981
7067
  files,
6982
7068
  handler: `${handlerPyFilename}.vc_handler`,
6983
7069
  runtime: pythonVersion.runtime,
@@ -6993,33 +7079,56 @@ from vercel_runtime.vc_init import vc_handler
6993
7079
  uvLockPath
6994
7080
  });
6995
7081
  } catch (err) {
6996
- (0, import_build_utils14.debug)(
7082
+ (0, import_build_utils15.debug)(
6997
7083
  `Failed to write project manifest: ${err instanceof Error ? err.message : String(err)}`
6998
7084
  );
6999
7085
  }
7000
7086
  }
7001
- if (djangoStatic?.cdnOutputDir) {
7002
- const lambdaPath = entrypoint.replace(/\.py$/, "");
7003
- const staticFiles = await (0, import_build_utils14.glob)("**", { cwd: djangoStatic.cdnOutputDir });
7004
- return {
7005
- resultVersion: 2,
7006
- result: {
7007
- output: {
7008
- [lambdaPath]: output,
7009
- ...staticFiles
7010
- },
7011
- routes: [
7012
- { handle: "filesystem" },
7013
- { src: "/(.*)", dest: `/${lambdaPath}` }
7014
- ]
7015
- }
7016
- };
7087
+ if (!(0, import_build_utils15.isPythonFramework)(framework)) {
7088
+ return { resultVersion: 3, result: { output } };
7089
+ }
7090
+ const lambdaPath = service?.name ? `_svc/${service.name}/index` : "index";
7091
+ const staticFiles = djangoStatic?.cdnOutputDir ? await (0, import_build_utils15.glob)("**", { cwd: djangoStatic.cdnOutputDir }) : {};
7092
+ return {
7093
+ resultVersion: 2,
7094
+ result: {
7095
+ output: {
7096
+ [lambdaPath]: output,
7097
+ ...staticFiles
7098
+ },
7099
+ routes: [
7100
+ { handle: "filesystem" },
7101
+ { src: "/(.*)", dest: `/${lambdaPath}` }
7102
+ ]
7103
+ }
7104
+ };
7105
+ };
7106
+ var prepareCache = async ({
7107
+ repoRootPath,
7108
+ workPath
7109
+ }) => {
7110
+ if (process.env.VERCEL_PYTHON_PREPARE_CACHE !== "1") {
7111
+ return {};
7017
7112
  }
7018
- return { resultVersion: 3, result: { output } };
7113
+ const root = repoRootPath || workPath;
7114
+ const ignore = ["**/*.pyc", "**/__pycache__/**"];
7115
+ const uvCacheDir = getUvCacheDir(workPath);
7116
+ try {
7117
+ const uvPath = findUvInPath();
7118
+ if (uvPath) {
7119
+ const uv = new UvRunner(uvPath, uvCacheDir);
7120
+ await uv.cachePrune();
7121
+ }
7122
+ } catch {
7123
+ }
7124
+ return (0, import_build_utils15.glob)("**/.vercel/python/{.venv,services/*/.venv,cache/uv}/**", {
7125
+ cwd: root,
7126
+ ignore
7127
+ });
7019
7128
  };
7020
7129
  var shouldServe = (opts) => {
7021
7130
  const framework = opts.config.framework;
7022
- if ((0, import_build_utils14.isPythonFramework)(framework)) {
7131
+ if ((0, import_build_utils15.isPythonFramework)(framework)) {
7023
7132
  const requestPath = opts.requestPath.replace(/\/$/, "");
7024
7133
  if (requestPath.startsWith("api") && opts.hasMatched) {
7025
7134
  return false;
@@ -7055,6 +7164,7 @@ function hasProp(obj, key) {
7055
7164
  downloadFilesInWorkPath,
7056
7165
  installRequirement,
7057
7166
  installRequirementsFile,
7167
+ prepareCache,
7058
7168
  runFrameworkHook,
7059
7169
  shouldServe,
7060
7170
  startDevServer,