@vercel/python 6.9.0 → 6.11.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
@@ -137,12 +137,12 @@ var require_isexe = __commonJS({
137
137
  if (typeof Promise !== "function") {
138
138
  throw new TypeError("callback not provided");
139
139
  }
140
- return new Promise(function(resolve2, reject) {
140
+ return new Promise(function(resolve, reject) {
141
141
  isexe(path, options || {}, function(er, is) {
142
142
  if (er) {
143
143
  reject(er);
144
144
  } else {
145
- resolve2(is);
145
+ resolve(is);
146
146
  }
147
147
  });
148
148
  });
@@ -1498,7 +1498,7 @@ var require_parse = __commonJS({
1498
1498
  var escape = require_escape();
1499
1499
  var readShebang = require_readShebang();
1500
1500
  var semver = require_semver();
1501
- var isWin4 = process.platform === "win32";
1501
+ var isWin3 = process.platform === "win32";
1502
1502
  var isExecutableRegExp = /\.(?:com|exe)$/i;
1503
1503
  var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
1504
1504
  var supportsShellOption = niceTry(() => semver.satisfies(process.version, "^4.8.0 || ^5.7.0 || >= 6.0.0", true)) || false;
@@ -1513,7 +1513,7 @@ var require_parse = __commonJS({
1513
1513
  return parsed.file;
1514
1514
  }
1515
1515
  function parseNonShell(parsed) {
1516
- if (!isWin4) {
1516
+ if (!isWin3) {
1517
1517
  return parsed;
1518
1518
  }
1519
1519
  const commandFile = detectShebang(parsed);
@@ -1535,7 +1535,7 @@ var require_parse = __commonJS({
1535
1535
  return parsed;
1536
1536
  }
1537
1537
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
1538
- if (isWin4) {
1538
+ if (isWin3) {
1539
1539
  parsed.command = typeof parsed.options.shell === "string" ? parsed.options.shell : process.env.comspec || "cmd.exe";
1540
1540
  parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
1541
1541
  parsed.options.windowsVerbatimArguments = true;
@@ -1578,7 +1578,7 @@ var require_parse = __commonJS({
1578
1578
  var require_enoent = __commonJS({
1579
1579
  "../../node_modules/.pnpm/cross-spawn@6.0.5/node_modules/cross-spawn/lib/enoent.js"(exports, module2) {
1580
1580
  "use strict";
1581
- var isWin4 = process.platform === "win32";
1581
+ var isWin3 = process.platform === "win32";
1582
1582
  function notFoundError(original, syscall) {
1583
1583
  return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
1584
1584
  code: "ENOENT",
@@ -1589,7 +1589,7 @@ var require_enoent = __commonJS({
1589
1589
  });
1590
1590
  }
1591
1591
  function hookChildProcess(cp, parsed) {
1592
- if (!isWin4) {
1592
+ if (!isWin3) {
1593
1593
  return;
1594
1594
  }
1595
1595
  const originalEmit = cp.emit;
@@ -1604,13 +1604,13 @@ var require_enoent = __commonJS({
1604
1604
  };
1605
1605
  }
1606
1606
  function verifyENOENT(status, parsed) {
1607
- if (isWin4 && status === 1 && !parsed.file) {
1607
+ if (isWin3 && status === 1 && !parsed.file) {
1608
1608
  return notFoundError(parsed.original, "spawn");
1609
1609
  }
1610
1610
  return null;
1611
1611
  }
1612
1612
  function verifyENOENTSync(status, parsed) {
1613
- if (isWin4 && status === 1 && !parsed.file) {
1613
+ if (isWin3 && status === 1 && !parsed.file) {
1614
1614
  return notFoundError(parsed.original, "spawnSync");
1615
1615
  }
1616
1616
  return null;
@@ -2042,7 +2042,7 @@ var require_get_stream = __commonJS({
2042
2042
  options = Object.assign({ maxBuffer: Infinity }, options);
2043
2043
  const { maxBuffer } = options;
2044
2044
  let stream;
2045
- return new Promise((resolve2, reject) => {
2045
+ return new Promise((resolve, reject) => {
2046
2046
  const rejectPromise = (error) => {
2047
2047
  if (error) {
2048
2048
  error.bufferedData = stream.getBufferedValue();
@@ -2054,7 +2054,7 @@ var require_get_stream = __commonJS({
2054
2054
  rejectPromise(error);
2055
2055
  return;
2056
2056
  }
2057
- resolve2();
2057
+ resolve();
2058
2058
  });
2059
2059
  stream.on("data", () => {
2060
2060
  if (stream.getBufferedLength() > maxBuffer) {
@@ -2078,11 +2078,11 @@ var require_p_finally = __commonJS({
2078
2078
  onFinally = onFinally || (() => {
2079
2079
  });
2080
2080
  return promise.then(
2081
- (val) => new Promise((resolve2) => {
2082
- resolve2(onFinally());
2081
+ (val) => new Promise((resolve) => {
2082
+ resolve(onFinally());
2083
2083
  }).then(() => val),
2084
- (err) => new Promise((resolve2) => {
2085
- resolve2(onFinally());
2084
+ (err) => new Promise((resolve) => {
2085
+ resolve(onFinally());
2086
2086
  }).then(() => {
2087
2087
  throw err;
2088
2088
  })
@@ -2143,7 +2143,7 @@ var require_signal_exit = __commonJS({
2143
2143
  } else {
2144
2144
  assert = require("assert");
2145
2145
  signals = require_signals();
2146
- isWin4 = /^win/i.test(process2.platform);
2146
+ isWin3 = /^win/i.test(process2.platform);
2147
2147
  EE = require("events");
2148
2148
  if (typeof EE !== "function") {
2149
2149
  EE = EE.EventEmitter;
@@ -2215,7 +2215,7 @@ var require_signal_exit = __commonJS({
2215
2215
  unload();
2216
2216
  emit("exit", null, sig);
2217
2217
  emit("afterexit", null, sig);
2218
- if (isWin4 && sig === "SIGHUP") {
2218
+ if (isWin3 && sig === "SIGHUP") {
2219
2219
  sig = "SIGINT";
2220
2220
  }
2221
2221
  process2.kill(process2.pid, sig);
@@ -2272,7 +2272,7 @@ var require_signal_exit = __commonJS({
2272
2272
  }
2273
2273
  var assert;
2274
2274
  var signals;
2275
- var isWin4;
2275
+ var isWin3;
2276
2276
  var EE;
2277
2277
  var emitter;
2278
2278
  var unload;
@@ -2461,8 +2461,8 @@ var require_execa = __commonJS({
2461
2461
  }
2462
2462
  let ret;
2463
2463
  if (!buffer) {
2464
- ret = new Promise((resolve2, reject) => {
2465
- process2[stream].once("end", resolve2).once("error", reject);
2464
+ ret = new Promise((resolve, reject) => {
2465
+ process2[stream].once("end", resolve).once("error", reject);
2466
2466
  });
2467
2467
  } else if (encoding) {
2468
2468
  ret = _getStream(process2[stream], {
@@ -2551,19 +2551,19 @@ ${stderr}${stdout}`;
2551
2551
  spawned.kill(parsed.opts.killSignal);
2552
2552
  }, parsed.opts.timeout);
2553
2553
  }
2554
- const processDone = new Promise((resolve2) => {
2554
+ const processDone = new Promise((resolve) => {
2555
2555
  spawned.on("exit", (code, signal) => {
2556
2556
  cleanup();
2557
- resolve2({ code, signal });
2557
+ resolve({ code, signal });
2558
2558
  });
2559
2559
  spawned.on("error", (err) => {
2560
2560
  cleanup();
2561
- resolve2({ error: err });
2561
+ resolve({ error: err });
2562
2562
  });
2563
2563
  if (spawned.stdin) {
2564
2564
  spawned.stdin.on("error", (err) => {
2565
2565
  cleanup();
2566
- resolve2({ error: err });
2566
+ resolve({ error: err });
2567
2567
  });
2568
2568
  }
2569
2569
  });
@@ -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: join7, delimiter, sep, posix } = require("path");
2654
+ var { join: join7, delimiter: delimiter2, sep, posix } = require("path");
2655
2655
  var isWindows = process.platform === "win32";
2656
2656
  var rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? "" : sep}]`.replace(/(\\)/g, "\\$1"));
2657
2657
  var rRel = new RegExp(`^\\.${rSlash.source}`);
@@ -2659,7 +2659,7 @@ var require_lib = __commonJS({
2659
2659
  var getPathInfo = (cmd, {
2660
2660
  path: optPath = process.env.PATH,
2661
2661
  pathExt: optPathExt = process.env.PATHEXT,
2662
- delimiter: optDelimiter = delimiter
2662
+ delimiter: optDelimiter = delimiter2
2663
2663
  }) => {
2664
2664
  const pathEnv = cmd.match(rSlash) ? [""] : [
2665
2665
  // windows always checks the cwd first
@@ -2761,9 +2761,9 @@ var import_build_utils8 = require("@vercel/build-utils");
2761
2761
  // src/install.ts
2762
2762
  var import_execa3 = __toESM(require_execa());
2763
2763
  var import_fs3 = __toESM(require("fs"));
2764
- var import_os2 = __toESM(require("os"));
2765
2764
  var import_path4 = require("path");
2766
2765
  var import_build_utils4 = require("@vercel/build-utils");
2766
+ var import_python_analysis = require("@vercel/python-analysis");
2767
2767
 
2768
2768
  // src/utils.ts
2769
2769
  var import_fs2 = __toESM(require("fs"));
@@ -3061,7 +3061,8 @@ function createVenvEnv(venvPath, baseEnv = process.env) {
3061
3061
  }
3062
3062
  async function ensureVenv({
3063
3063
  pythonPath,
3064
- venvPath
3064
+ venvPath,
3065
+ uvPath
3065
3066
  }) {
3066
3067
  const marker = (0, import_path3.join)(venvPath, "pyvenv.cfg");
3067
3068
  try {
@@ -3071,7 +3072,11 @@ async function ensureVenv({
3071
3072
  }
3072
3073
  await import_fs2.default.promises.mkdir(venvPath, { recursive: true });
3073
3074
  console.log(`Creating virtual environment at "${venvPath}"...`);
3074
- await (0, import_execa2.default)(pythonPath, ["-m", "venv", venvPath]);
3075
+ if (uvPath) {
3076
+ await (0, import_execa2.default)(uvPath, ["venv", venvPath]);
3077
+ } else {
3078
+ await (0, import_execa2.default)(pythonPath, ["-m", "venv", venvPath]);
3079
+ }
3075
3080
  }
3076
3081
  function getVenvPythonBin(venvPath) {
3077
3082
  return (0, import_path3.join)(getVenvBinDir(venvPath), isWin2 ? "python.exe" : "python");
@@ -3379,7 +3384,6 @@ function isInstalled({ version: version2 }) {
3379
3384
  }
3380
3385
 
3381
3386
  // src/install.ts
3382
- var isWin3 = process.platform === "win32";
3383
3387
  var makeDependencyCheckCode = (dependency) => `
3384
3388
  from importlib import util
3385
3389
  dep = '${dependency}'.replace('-', '_')
@@ -3455,225 +3459,130 @@ function resolveVendorDir() {
3455
3459
  const vendorDir = process.env.VERCEL_PYTHON_VENDOR_DIR || "_vendor";
3456
3460
  return vendorDir;
3457
3461
  }
3462
+ function toBuildError(error) {
3463
+ return new import_build_utils4.NowBuildError({
3464
+ code: error.code,
3465
+ message: error.message,
3466
+ link: error.link,
3467
+ action: error.action
3468
+ });
3469
+ }
3458
3470
  async function detectInstallSource({
3459
3471
  workPath,
3460
3472
  entryDirectory,
3461
- fsFiles
3473
+ repoRootPath
3462
3474
  }) {
3463
- const uvLockDir = findDir({
3464
- file: "uv.lock",
3465
- entryDirectory,
3466
- workPath,
3467
- fsFiles
3468
- });
3469
- const pyprojectDir = findDir({
3470
- file: "pyproject.toml",
3471
- entryDirectory,
3472
- workPath,
3473
- fsFiles
3474
- });
3475
- const pipfileLockDir = findDir({
3476
- file: "Pipfile.lock",
3477
- entryDirectory,
3478
- workPath,
3479
- fsFiles
3480
- });
3481
- const pipfileDir = findDir({
3482
- file: "Pipfile",
3483
- entryDirectory,
3484
- workPath,
3485
- fsFiles
3486
- });
3487
- const requirementsDir = findDir({
3488
- file: "requirements.txt",
3489
- entryDirectory,
3490
- workPath,
3491
- fsFiles
3492
- });
3493
- let manifestPath = null;
3494
- let manifestType = null;
3495
- if (uvLockDir && pyprojectDir) {
3496
- manifestType = "uv.lock";
3497
- manifestPath = (0, import_path4.join)(uvLockDir, "uv.lock");
3498
- } else if (pyprojectDir) {
3499
- manifestType = "pyproject.toml";
3500
- manifestPath = (0, import_path4.join)(pyprojectDir, "pyproject.toml");
3501
- } else if (pipfileLockDir) {
3502
- manifestType = "Pipfile.lock";
3503
- manifestPath = (0, import_path4.join)(pipfileLockDir, "Pipfile.lock");
3504
- } else if (pipfileDir) {
3505
- manifestType = "Pipfile";
3506
- manifestPath = (0, import_path4.join)(pipfileDir, "Pipfile");
3507
- } else if (requirementsDir) {
3508
- manifestType = "requirements.txt";
3509
- manifestPath = (0, import_path4.join)(requirementsDir, "requirements.txt");
3510
- }
3511
- let manifestContent;
3512
- if (manifestPath) {
3513
- try {
3514
- manifestContent = await import_fs3.default.promises.readFile(manifestPath, "utf8");
3515
- } catch (err) {
3516
- (0, import_build_utils4.debug)("Failed to read install manifest contents", err);
3475
+ const entrypointDir = (0, import_path4.join)(workPath, entryDirectory);
3476
+ const rootDir = repoRootPath ?? workPath;
3477
+ let pythonPackage;
3478
+ try {
3479
+ pythonPackage = await (0, import_python_analysis.discoverPythonPackage)({
3480
+ entrypointDir,
3481
+ rootDir
3482
+ });
3483
+ } catch (error) {
3484
+ if (error instanceof import_python_analysis.PythonAnalysisError) {
3485
+ throw toBuildError(error);
3517
3486
  }
3487
+ throw error;
3518
3488
  }
3519
- return { manifestPath, manifestType, manifestContent };
3520
- }
3521
- async function createPyprojectToml({
3522
- projectName,
3523
- pyprojectPath,
3524
- dependencies,
3525
- pythonVersion
3526
- }) {
3527
- const version2 = pythonVersion ?? DEFAULT_PYTHON_VERSION;
3528
- const requiresPython = `~=${version2}.0`;
3529
- const depsToml = dependencies.length > 0 ? [
3530
- "dependencies = [",
3531
- ...dependencies.map((dep) => ` "${dep}",`),
3532
- "]"
3533
- ].join("\n") : "dependencies = []";
3534
- const content = [
3535
- "[project]",
3536
- `name = "${projectName}"`,
3537
- 'version = "0.1.0"',
3538
- `requires-python = "${requiresPython}"`,
3539
- "classifiers = [",
3540
- ' "Private :: Do Not Upload",',
3541
- "]",
3542
- depsToml,
3543
- ""
3544
- ].join("\n");
3545
- await import_fs3.default.promises.writeFile(pyprojectPath, content);
3546
- }
3547
- function findUvLockUpwards(startDir, repoRootPath) {
3548
- const start = (0, import_path4.resolve)(startDir);
3549
- const base = repoRootPath ? (0, import_path4.resolve)(repoRootPath) : void 0;
3550
- for (const dir of (0, import_build_utils4.traverseUpDirectories)({ start, base })) {
3551
- const lockPath = (0, import_path4.join)(dir, "uv.lock");
3552
- const pyprojectPath = (0, import_path4.join)(dir, "pyproject.toml");
3553
- if (import_fs3.default.existsSync(lockPath) && import_fs3.default.existsSync(pyprojectPath)) {
3554
- return lockPath;
3555
- }
3489
+ let manifestType = null;
3490
+ let manifestPath = null;
3491
+ const lockFile = pythonPackage.manifest?.lockFile ?? pythonPackage.workspaceLockFile;
3492
+ if (lockFile) {
3493
+ if (lockFile.kind === import_python_analysis.PythonLockFileKind.UvLock) {
3494
+ manifestType = "uv.lock";
3495
+ manifestPath = (0, import_path4.join)(rootDir, lockFile.path);
3496
+ } else if (lockFile.kind === import_python_analysis.PythonLockFileKind.PylockToml) {
3497
+ manifestType = "pylock.toml";
3498
+ manifestPath = (0, import_path4.join)(rootDir, lockFile.path);
3499
+ }
3500
+ } else if (pythonPackage.manifest) {
3501
+ manifestType = "pyproject.toml";
3502
+ manifestPath = (0, import_path4.join)(rootDir, pythonPackage.manifest.path);
3556
3503
  }
3557
- return null;
3504
+ return { manifestPath, manifestType, pythonPackage };
3558
3505
  }
3559
3506
  async function ensureUvProject({
3560
3507
  workPath,
3561
3508
  entryDirectory,
3562
- fsFiles,
3563
3509
  repoRootPath,
3564
- pythonPath,
3565
- pipPath,
3566
3510
  pythonVersion,
3567
- uv,
3568
- venvPath,
3569
- meta
3511
+ uv
3570
3512
  }) {
3571
- const uvPath = uv.getPath();
3513
+ const rootDir = repoRootPath ?? workPath;
3572
3514
  const installInfo = await detectInstallSource({
3573
3515
  workPath,
3574
3516
  entryDirectory,
3575
- fsFiles
3517
+ repoRootPath
3576
3518
  });
3577
- const { manifestType, manifestPath } = installInfo;
3519
+ const { manifestType, pythonPackage } = installInfo;
3520
+ const manifest = pythonPackage?.manifest;
3578
3521
  let projectDir;
3579
3522
  let pyprojectPath;
3580
3523
  let lockPath = null;
3581
- if (manifestType === "uv.lock") {
3582
- if (!manifestPath) {
3583
- throw new Error("Expected uv.lock path to be resolved, but it was null");
3524
+ if (manifestType === "uv.lock" || manifestType === "pylock.toml") {
3525
+ const lockFile = pythonPackage?.manifest?.lockFile ?? pythonPackage?.workspaceLockFile;
3526
+ if (!lockFile) {
3527
+ throw new Error(
3528
+ `Expected lock file path to be resolved, but it was null`
3529
+ );
3584
3530
  }
3585
- projectDir = (0, import_path4.dirname)(manifestPath);
3531
+ lockPath = (0, import_path4.join)(rootDir, lockFile.path);
3532
+ projectDir = (0, import_path4.dirname)(lockPath);
3586
3533
  pyprojectPath = (0, import_path4.join)(projectDir, "pyproject.toml");
3587
3534
  if (!import_fs3.default.existsSync(pyprojectPath)) {
3588
3535
  throw new Error(
3589
- `Expected "pyproject.toml" next to "uv.lock" in "${projectDir}"`
3536
+ `Expected "pyproject.toml" next to "${lockFile.kind}" in "${projectDir}"`
3590
3537
  );
3591
3538
  }
3592
- lockPath = manifestPath;
3593
- console.log("Installing required dependencies from uv.lock...");
3594
- } else if (manifestType === "pyproject.toml") {
3595
- if (!manifestPath) {
3596
- throw new Error(
3597
- "Expected pyproject.toml path to be resolved, but it was null"
3539
+ console.log(`Installing required dependencies from ${lockFile.kind}...`);
3540
+ } else if (manifest) {
3541
+ projectDir = (0, import_path4.join)(rootDir, (0, import_path4.dirname)(manifest.path));
3542
+ pyprojectPath = (0, import_path4.join)(rootDir, manifest.path);
3543
+ const originKind = manifest.origin?.kind;
3544
+ if (originKind === import_python_analysis.PythonManifestConvertedKind.Pipfile) {
3545
+ console.log("Installing required dependencies from Pipfile...");
3546
+ } else if (originKind === import_python_analysis.PythonManifestConvertedKind.PipfileLock) {
3547
+ console.log("Installing required dependencies from Pipfile.lock...");
3548
+ } else if (originKind === import_python_analysis.PythonManifestConvertedKind.RequirementsTxt || originKind === import_python_analysis.PythonManifestConvertedKind.RequirementsIn) {
3549
+ console.log(
3550
+ `Installing required dependencies from ${manifest.origin?.path ?? "requirements.txt"}...`
3598
3551
  );
3599
- }
3600
- projectDir = (0, import_path4.dirname)(manifestPath);
3601
- pyprojectPath = manifestPath;
3602
- console.log("Installing required dependencies from pyproject.toml...");
3603
- const workspaceLock = findUvLockUpwards(projectDir, repoRootPath);
3604
- if (workspaceLock) {
3605
- lockPath = workspaceLock;
3606
3552
  } else {
3607
- await uv.lock(projectDir);
3553
+ console.log("Installing required dependencies from pyproject.toml...");
3608
3554
  }
3609
- } else if (manifestType === "Pipfile.lock" || manifestType === "Pipfile") {
3610
- if (!manifestPath) {
3611
- throw new Error(
3612
- "Expected Pipfile/Pipfile.lock path to be resolved, but it was null"
3613
- );
3614
- }
3615
- projectDir = (0, import_path4.dirname)(manifestPath);
3616
- console.log(`Installing required dependencies from ${manifestType}...`);
3617
- const exportedReq = await exportRequirementsFromPipfile({
3618
- pythonPath,
3619
- pipPath,
3620
- uvPath,
3621
- projectDir,
3622
- meta
3623
- });
3624
- pyprojectPath = (0, import_path4.join)(projectDir, "pyproject.toml");
3625
- if (!import_fs3.default.existsSync(pyprojectPath)) {
3626
- await createPyprojectToml({
3627
- projectName: "app",
3628
- pyprojectPath,
3629
- dependencies: [],
3630
- pythonVersion
3631
- });
3632
- }
3633
- await uv.addFromFile({
3634
- venvPath,
3635
- projectDir,
3636
- requirementsPath: exportedReq
3637
- });
3638
- } else if (manifestType === "requirements.txt") {
3639
- if (!manifestPath) {
3640
- throw new Error(
3641
- "Expected requirements.txt path to be resolved, but it was null"
3642
- );
3555
+ if (manifest.origin) {
3556
+ if (manifest.data.project && !manifest.data.project["requires-python"]) {
3557
+ manifest.data.project["requires-python"] = `~=${pythonVersion}.0`;
3558
+ }
3559
+ const content = (0, import_python_analysis.stringifyManifest)(manifest.data);
3560
+ pyprojectPath = (0, import_path4.join)(projectDir, "pyproject.toml");
3561
+ await import_fs3.default.promises.writeFile(pyprojectPath, content);
3643
3562
  }
3644
- projectDir = (0, import_path4.dirname)(manifestPath);
3645
- pyprojectPath = (0, import_path4.join)(projectDir, "pyproject.toml");
3646
- console.log(
3647
- "Installing required dependencies from requirements.txt with uv..."
3648
- );
3649
- if (!import_fs3.default.existsSync(pyprojectPath)) {
3650
- await createPyprojectToml({
3651
- projectName: "app",
3652
- pyprojectPath,
3653
- dependencies: [],
3654
- pythonVersion
3655
- });
3563
+ const workspaceLockFile = pythonPackage?.workspaceLockFile;
3564
+ if (workspaceLockFile) {
3565
+ lockPath = (0, import_path4.join)(rootDir, workspaceLockFile.path);
3566
+ } else {
3567
+ await uv.lock(projectDir);
3656
3568
  }
3657
- await uv.addFromFile({
3658
- venvPath,
3659
- projectDir,
3660
- requirementsPath: manifestPath
3661
- });
3662
3569
  } else {
3663
3570
  projectDir = workPath;
3664
3571
  pyprojectPath = (0, import_path4.join)(projectDir, "pyproject.toml");
3665
3572
  console.log(
3666
3573
  "No Python manifest found; creating an empty pyproject.toml and uv.lock..."
3667
3574
  );
3668
- await createPyprojectToml({
3669
- projectName: "app",
3670
- pyprojectPath,
3671
- dependencies: [],
3672
- pythonVersion
3575
+ const requiresPython = `~=${pythonVersion}.0`;
3576
+ const minimalManifest = (0, import_python_analysis.createMinimalManifest)({
3577
+ name: "app",
3578
+ requiresPython,
3579
+ dependencies: []
3673
3580
  });
3581
+ const content = (0, import_python_analysis.stringifyManifest)(minimalManifest);
3582
+ await import_fs3.default.promises.writeFile(pyprojectPath, content);
3674
3583
  await uv.lock(projectDir);
3675
3584
  }
3676
- const resolvedLockPath = lockPath && import_fs3.default.existsSync(lockPath) ? lockPath : findUvLockUpwards(projectDir, repoRootPath) || (0, import_path4.join)(projectDir, "uv.lock");
3585
+ const resolvedLockPath = lockPath && import_fs3.default.existsSync(lockPath) ? lockPath : (0, import_path4.join)(projectDir, "uv.lock");
3677
3586
  return { projectDir, pyprojectPath, lockPath: resolvedLockPath };
3678
3587
  }
3679
3588
  async function pipInstall(pipPath, uvPath, workPath, args, targetDir) {
@@ -3767,46 +3676,6 @@ async function installRequirementsFile({
3767
3676
  targetDir
3768
3677
  );
3769
3678
  }
3770
- async function exportRequirementsFromPipfile({
3771
- pythonPath,
3772
- pipPath,
3773
- uvPath,
3774
- projectDir,
3775
- meta
3776
- }) {
3777
- const tempDir = await import_fs3.default.promises.mkdtemp(
3778
- (0, import_path4.join)(import_os2.default.tmpdir(), "vercel-pipenv-")
3779
- );
3780
- await installRequirement({
3781
- pythonPath,
3782
- pipPath,
3783
- dependency: "pipfile-requirements",
3784
- version: "0.3.0",
3785
- workPath: tempDir,
3786
- meta,
3787
- args: ["--no-warn-script-location"],
3788
- uvPath
3789
- });
3790
- const tempVendorDir = (0, import_path4.join)(tempDir, resolveVendorDir());
3791
- const convertCmd = isWin3 ? (0, import_path4.join)(tempVendorDir, "Scripts", "pipfile2req.exe") : (0, import_path4.join)(tempVendorDir, "bin", "pipfile2req");
3792
- (0, import_build_utils4.debug)(`Running "${convertCmd}" in ${projectDir}...`);
3793
- let stdout;
3794
- try {
3795
- const { stdout: out } = await (0, import_execa3.default)(convertCmd, [], {
3796
- cwd: projectDir,
3797
- env: { ...process.env, PYTHONPATH: tempVendorDir }
3798
- });
3799
- stdout = out;
3800
- } catch (err) {
3801
- throw new Error(
3802
- `Failed to run "${convertCmd}": ${err instanceof Error ? err.message : String(err)}`
3803
- );
3804
- }
3805
- const outPath = (0, import_path4.join)(tempDir, "requirements.pipenv.txt");
3806
- await import_fs3.default.promises.writeFile(outPath, stdout);
3807
- (0, import_build_utils4.debug)(`Exported pipfile requirements to ${outPath}`);
3808
- return outPath;
3809
- }
3810
3679
  async function mirrorSitePackagesIntoVendor({
3811
3680
  venvPath,
3812
3681
  vendorDirName
@@ -4044,6 +3913,21 @@ function createDevWsgiShim(workPath, modulePath) {
4044
3913
  return null;
4045
3914
  }
4046
3915
  }
3916
+ async function getMultiServicePythonRunner(workPath, env, systemPython, uvPath) {
3917
+ const { pythonCmd, venvRoot } = useVirtualEnv(workPath, env, systemPython);
3918
+ if (venvRoot) {
3919
+ (0, import_build_utils7.debug)(`Using existing virtualenv at ${venvRoot} for multi-service dev`);
3920
+ return { command: pythonCmd, args: [] };
3921
+ }
3922
+ const venvPath = (0, import_path6.join)(workPath, ".venv");
3923
+ await ensureVenv({ pythonPath: systemPython, venvPath, uvPath });
3924
+ (0, import_build_utils7.debug)(`Created virtualenv at ${venvPath} for multi-service dev`);
3925
+ const pythonBin = getVenvPythonBin(venvPath);
3926
+ const binDir = getVenvBinDir(venvPath);
3927
+ env.VIRTUAL_ENV = venvPath;
3928
+ env.PATH = `${binDir}${import_path6.delimiter}${env.PATH || ""}`;
3929
+ return { command: pythonBin, args: [] };
3930
+ }
4047
3931
  var startDevServer = async (opts) => {
4048
3932
  const {
4049
3933
  entrypoint: rawEntrypoint,
@@ -4104,44 +3988,70 @@ var startDevServer = async (opts) => {
4104
3988
  let resolveChildReady;
4105
3989
  let rejectChildReady;
4106
3990
  const childReady = new Promise(
4107
- (resolve2, reject) => {
4108
- resolveChildReady = resolve2;
3991
+ (resolve, reject) => {
3992
+ resolveChildReady = resolve;
4109
3993
  rejectChildReady = reject;
4110
3994
  }
4111
3995
  );
4112
3996
  PENDING_STARTS.set(serverKey, childReady);
4113
3997
  try {
4114
- await new Promise((resolve2, reject) => {
4115
- let resolved = false;
4116
- const { pythonPath: systemPython } = getDefaultPythonVersion(meta);
4117
- let pythonCmd = systemPython;
4118
- const venv = isInVirtualEnv();
4119
- if (venv) {
4120
- (0, import_build_utils7.debug)(`Running in virtualenv at ${venv}`);
3998
+ const { pythonPath: systemPython } = getDefaultPythonVersion(meta);
3999
+ const uvPath = await findUvBinary(systemPython);
4000
+ const venv = isInVirtualEnv();
4001
+ const serviceCount = meta.serviceCount ?? 0;
4002
+ const pythonServiceCount = meta.pythonServiceCount ?? 1;
4003
+ if (venv && pythonServiceCount > 1) {
4004
+ const yellow = "\x1B[33m";
4005
+ const white = "\x1B[1m";
4006
+ const reset = "\x1B[0m";
4007
+ throw new import_build_utils7.NowBuildError({
4008
+ code: "PYTHON_EXTERNAL_VENV_DETECTED",
4009
+ message: `Detected activated venv at ${yellow}${venv}${reset}, ${white}vercel dev${reset} manages virtual environments automatically.
4010
+ Run ${white}deactivate${reset} and try again.`
4011
+ });
4012
+ }
4013
+ let spawnCommand = systemPython;
4014
+ let spawnArgsPrefix = [];
4015
+ if (serviceCount > 0) {
4016
+ const runner = await getMultiServicePythonRunner(
4017
+ workPath,
4018
+ env,
4019
+ systemPython,
4020
+ uvPath
4021
+ );
4022
+ spawnCommand = runner.command;
4023
+ spawnArgsPrefix = runner.args;
4024
+ (0, import_build_utils7.debug)(
4025
+ `Multi-service Python runner: ${spawnCommand} ${spawnArgsPrefix.join(" ")}`
4026
+ );
4027
+ } else if (venv) {
4028
+ (0, import_build_utils7.debug)(`Running in virtualenv at ${venv}`);
4029
+ } else {
4030
+ const { pythonCmd: venvPythonCmd, venvRoot } = useVirtualEnv(
4031
+ workPath,
4032
+ env,
4033
+ systemPython
4034
+ );
4035
+ spawnCommand = venvPythonCmd;
4036
+ if (venvRoot) {
4037
+ (0, import_build_utils7.debug)(`Using virtualenv at ${venvRoot}`);
4121
4038
  } else {
4122
- const { pythonCmd: venvPythonCmd, venvRoot } = useVirtualEnv(
4123
- workPath,
4124
- env,
4125
- systemPython
4126
- );
4127
- pythonCmd = venvPythonCmd;
4128
- if (venvRoot) {
4129
- (0, import_build_utils7.debug)(`Using virtualenv at ${venvRoot}`);
4130
- } else {
4131
- (0, import_build_utils7.debug)("No virtualenv found");
4132
- try {
4133
- const yellow = "\x1B[33m";
4134
- const reset = "\x1B[0m";
4135
- const venvCmd = process.platform === "win32" ? "python -m venv .venv && .venv\\Scripts\\activate" : "python -m venv .venv && source .venv/bin/activate";
4136
- process.stderr.write(
4137
- `${yellow}Warning: no virtual environment detected in ${workPath}. Using system Python: ${pythonCmd}.${reset}
4039
+ (0, import_build_utils7.debug)("No virtualenv found");
4040
+ try {
4041
+ const yellow = "\x1B[33m";
4042
+ const reset = "\x1B[0m";
4043
+ const venvCmd = process.platform === "win32" ? "python -m venv .venv && .venv\\Scripts\\activate" : "python -m venv .venv && source .venv/bin/activate";
4044
+ process.stderr.write(
4045
+ `${yellow}Warning: no virtual environment detected in ${workPath}. Using system Python: ${systemPython}.${reset}
4138
4046
  If you are using a virtual environment, activate it before running "vercel dev", or create one: ${venvCmd}
4139
4047
  `
4140
- );
4141
- } catch (_) {
4142
- }
4048
+ );
4049
+ } catch (_) {
4143
4050
  }
4144
4051
  }
4052
+ }
4053
+ await new Promise((resolve, reject) => {
4054
+ let resolved = false;
4145
4055
  if (framework !== "flask") {
4146
4056
  const devShimModule = createDevAsgiShim(workPath, modulePath);
4147
4057
  if (devShimModule) {
@@ -4150,11 +4060,12 @@ If you are using a virtual environment, activate it before running "vercel dev",
4150
4060
  env.PYTHONPATH = existingPythonPath ? `${vercelPythonDir}:${existingPythonPath}` : vercelPythonDir;
4151
4061
  }
4152
4062
  const moduleToRun = devShimModule || modulePath;
4153
- const argv = ["-u", "-m", moduleToRun];
4063
+ const pythonArgs = ["-u", "-m", moduleToRun];
4064
+ const argv = [...spawnArgsPrefix, ...pythonArgs];
4154
4065
  (0, import_build_utils7.debug)(
4155
- `Starting ASGI dev server (${framework}): ${pythonCmd} ${argv.join(" ")}`
4066
+ `Starting ASGI dev server (${framework}): ${spawnCommand} ${argv.join(" ")}`
4156
4067
  );
4157
- const child = (0, import_child_process2.spawn)(pythonCmd, argv, {
4068
+ const child = (0, import_child_process2.spawn)(spawnCommand, argv, {
4158
4069
  cwd: workPath,
4159
4070
  env,
4160
4071
  stdio: ["inherit", "pipe", "pipe"]
@@ -4187,7 +4098,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
4187
4098
  child.stderr?.removeListener("data", onDetect);
4188
4099
  const port2 = Number(portMatch[1]);
4189
4100
  resolveChildReady({ port: port2, pid: child.pid });
4190
- resolve2();
4101
+ resolve();
4191
4102
  }
4192
4103
  }
4193
4104
  };
@@ -4216,9 +4127,10 @@ If you are using a virtual environment, activate it before running "vercel dev",
4216
4127
  env.PYTHONPATH = existingPythonPath ? `${vercelPythonDir}:${existingPythonPath}` : vercelPythonDir;
4217
4128
  }
4218
4129
  const moduleToRun = devShimModule || modulePath;
4219
- const argv = ["-u", "-m", moduleToRun];
4220
- (0, import_build_utils7.debug)(`Starting Flask dev server: ${pythonCmd} ${argv.join(" ")}`);
4221
- const child = (0, import_child_process2.spawn)(pythonCmd, argv, {
4130
+ const pythonArgs = ["-u", "-m", moduleToRun];
4131
+ const argv = [...spawnArgsPrefix, ...pythonArgs];
4132
+ (0, import_build_utils7.debug)(`Starting Flask dev server: ${spawnCommand} ${argv.join(" ")}`);
4133
+ const child = (0, import_child_process2.spawn)(spawnCommand, argv, {
4222
4134
  cwd: workPath,
4223
4135
  env,
4224
4136
  stdio: ["inherit", "pipe", "pipe"]
@@ -4250,7 +4162,7 @@ If you are using a virtual environment, activate it before running "vercel dev",
4250
4162
  child.stderr?.removeListener("data", onDetect);
4251
4163
  const port2 = Number(portMatch[1]);
4252
4164
  resolveChildReady({ port: port2, pid: child.pid });
4253
- resolve2();
4165
+ resolve();
4254
4166
  }
4255
4167
  }
4256
4168
  };
@@ -4522,14 +4434,9 @@ var build = async ({
4522
4434
  const { projectDir } = await ensureUvProject({
4523
4435
  workPath,
4524
4436
  entryDirectory,
4525
- fsFiles,
4526
4437
  repoRootPath,
4527
- pythonPath: pythonVersion.pythonPath,
4528
- pipPath: pythonVersion.pipPath,
4529
4438
  pythonVersion: pythonVersion.version,
4530
- uv,
4531
- venvPath,
4532
- meta
4439
+ uv
4533
4440
  });
4534
4441
  await uv.sync({
4535
4442
  venvPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.9.0",
3
+ "version": "6.11.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -15,6 +15,9 @@
15
15
  "url": "https://github.com/vercel/vercel.git",
16
16
  "directory": "packages/python"
17
17
  },
18
+ "dependencies": {
19
+ "@vercel/python-analysis": "0.4.0"
20
+ },
18
21
  "devDependencies": {
19
22
  "@renovatebot/pep440": "4.2.1",
20
23
  "@types/execa": "^0.9.0",
@@ -31,7 +34,7 @@
31
34
  "smol-toml": "1.5.2",
32
35
  "vitest": "2.1.4",
33
36
  "which": "3.0.0",
34
- "@vercel/build-utils": "13.3.3",
37
+ "@vercel/build-utils": "13.3.5",
35
38
  "@vercel/error-utils": "2.0.3"
36
39
  },
37
40
  "scripts": {
@@ -75,13 +75,23 @@ async def app(scope, receive, send):
75
75
 
76
76
 
77
77
  if __name__ == '__main__':
78
- # Development runner for ASGI: prefer uvicorn, then hypercorn.
78
+ # Development runner for ASGI: prefer fastapi dev, then uvicorn, then hypercorn.
79
79
  # Bind to localhost on an ephemeral port and emit a recognizable log line
80
80
  # so the caller can detect the bound port.
81
81
  host = '127.0.0.1'
82
+
83
+ try:
84
+ from fastapi_cli.cli import dev
85
+ except ImportError:
86
+ dev = None
87
+
88
+ if dev is not None:
89
+ dev(entrypoint='vc_init_dev_asgi:app', host=host, port=0, reload=True)
90
+ sys.exit(0)
91
+
82
92
  try:
83
93
  import uvicorn
84
- uvicorn.run('vc_init_dev_asgi:app', host=host, port=0, use_colors=True)
94
+ uvicorn.run('vc_init_dev_asgi:app', host=host, port=0, use_colors=True, reload=True)
85
95
  except Exception:
86
96
  try:
87
97
  import asyncio
@@ -112,7 +112,7 @@ if __name__ == "__main__":
112
112
  host = "127.0.0.1"
113
113
  try:
114
114
  from werkzeug.serving import run_simple
115
- run_simple(host, 0, app, use_reloader=False)
115
+ run_simple(host, 0, app, use_reloader=True)
116
116
  except Exception:
117
117
  import sys
118
118
  print(_color("Werkzeug not installed; falling back to wsgiref (no reloader).", _YELLOW), file=sys.stderr)