@vercel/build-utils 13.2.12 → 13.2.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @vercel/build-utils
2
2
 
3
+ ## 13.2.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Add `experimentalServices` to `vercel.json` ([#14612](https://github.com/vercel/vercel/pull/14612))
8
+
9
+ ## 13.2.13
10
+
11
+ ### Patch Changes
12
+
13
+ - [python] experimental python runtime framework preset ([#14646](https://github.com/vercel/vercel/pull/14646))
14
+
3
15
  ## 13.2.12
4
16
 
5
17
  ### Patch Changes
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { FileBase } from './types';
3
4
  interface FileBlobOptions {
4
5
  mode?: number;
@@ -3,12 +3,16 @@ import { Builder } from '.';
3
3
  * List of backend frameworks supported by the experimental backends feature
4
4
  */
5
5
  export declare const BACKEND_FRAMEWORKS: readonly ["express", "hono", "h3", "koa", "nestjs", "fastify", "elysia"];
6
+ export declare const PYTHON_FRAMEWORKS: readonly ["fastapi", "flask", "python"];
7
+ export declare const RUNTIME_FRAMEWORKS: readonly ["python"];
6
8
  export declare const BACKEND_BUILDERS: readonly ["@vercel/express", "@vercel/hono", "@vercel/h3", "@vercel/koa", "@vercel/nestjs", "@vercel/fastify", "@vercel/elysia"];
7
9
  export type BackendFramework = (typeof BACKEND_FRAMEWORKS)[number];
10
+ export type PythonFramework = (typeof PYTHON_FRAMEWORKS)[number];
8
11
  /**
9
12
  * Checks if the given framework is a backend framework
10
13
  */
11
14
  export declare function isBackendFramework(framework: string | null | undefined): framework is BackendFramework;
15
+ export declare function isPythonFramework(framework: string | null | undefined): framework is (typeof PYTHON_FRAMEWORKS)[number];
12
16
  export declare function isExperimentalBackendsWithoutIntrospectionEnabled(): boolean;
13
17
  export declare function isExperimentalBackendsEnabled(): boolean;
14
18
  export declare function isBackendBuilder(builder: Builder | null | undefined): boolean;
@@ -20,10 +20,13 @@ var framework_helpers_exports = {};
20
20
  __export(framework_helpers_exports, {
21
21
  BACKEND_BUILDERS: () => BACKEND_BUILDERS,
22
22
  BACKEND_FRAMEWORKS: () => BACKEND_FRAMEWORKS,
23
+ PYTHON_FRAMEWORKS: () => PYTHON_FRAMEWORKS,
24
+ RUNTIME_FRAMEWORKS: () => RUNTIME_FRAMEWORKS,
23
25
  isBackendBuilder: () => isBackendBuilder,
24
26
  isBackendFramework: () => isBackendFramework,
25
27
  isExperimentalBackendsEnabled: () => isExperimentalBackendsEnabled,
26
28
  isExperimentalBackendsWithoutIntrospectionEnabled: () => isExperimentalBackendsWithoutIntrospectionEnabled,
29
+ isPythonFramework: () => isPythonFramework,
27
30
  shouldUseExperimentalBackends: () => shouldUseExperimentalBackends
28
31
  });
29
32
  module.exports = __toCommonJS(framework_helpers_exports);
@@ -36,6 +39,13 @@ const BACKEND_FRAMEWORKS = [
36
39
  "fastify",
37
40
  "elysia"
38
41
  ];
42
+ const PYTHON_FRAMEWORKS = [
43
+ "fastapi",
44
+ "flask",
45
+ "python"
46
+ // Generic Python framework preset
47
+ ];
48
+ const RUNTIME_FRAMEWORKS = ["python"];
39
49
  const BACKEND_BUILDERS = [
40
50
  "@vercel/express",
41
51
  "@vercel/hono",
@@ -50,6 +60,11 @@ function isBackendFramework(framework) {
50
60
  return false;
51
61
  return BACKEND_FRAMEWORKS.includes(framework);
52
62
  }
63
+ function isPythonFramework(framework) {
64
+ if (!framework)
65
+ return false;
66
+ return PYTHON_FRAMEWORKS.includes(framework);
67
+ }
53
68
  function isExperimentalBackendsWithoutIntrospectionEnabled() {
54
69
  return process.env.VERCEL_BACKENDS_BUILDS === "1";
55
70
  }
@@ -70,9 +85,12 @@ function shouldUseExperimentalBackends(framework) {
70
85
  0 && (module.exports = {
71
86
  BACKEND_BUILDERS,
72
87
  BACKEND_FRAMEWORKS,
88
+ PYTHON_FRAMEWORKS,
89
+ RUNTIME_FRAMEWORKS,
73
90
  isBackendBuilder,
74
91
  isBackendFramework,
75
92
  isExperimentalBackendsEnabled,
76
93
  isExperimentalBackendsWithoutIntrospectionEnabled,
94
+ isPythonFramework,
77
95
  shouldUseExperimentalBackends
78
96
  });
@@ -211,7 +211,17 @@ export declare function runCustomInstallCommand({ destPath, installCommand, spaw
211
211
  }): Promise<boolean>;
212
212
  export declare function runPackageJsonScript(destPath: string, scriptNames: string | Iterable<string>, spawnOpts?: SpawnOptions, projectCreatedAt?: number): Promise<boolean>;
213
213
  export declare function runBundleInstall(destPath: string, args?: string[], spawnOpts?: SpawnOptions, meta?: Meta): Promise<void>;
214
- export declare function runPipInstall(destPath: string, args?: string[], spawnOpts?: SpawnOptions, meta?: Meta): Promise<void>;
214
+ export type PipInstallResult = {
215
+ installed: false;
216
+ } | {
217
+ installed: true;
218
+ /**
219
+ * The directory where packages were installed.
220
+ * Add this to PYTHONPATH when running Python commands.
221
+ */
222
+ targetDir: string;
223
+ };
224
+ export declare function runPipInstall(destPath: string, args?: string[], spawnOpts?: SpawnOptions, meta?: Meta): Promise<PipInstallResult>;
215
225
  export declare function getScriptName(pkg: Pick<PackageJson, 'scripts'> | null | undefined, possibleNames: Iterable<string>): string | undefined;
216
226
  /**
217
227
  * @deprecate installDependencies() is deprecated.
@@ -1073,15 +1073,21 @@ async function runBundleInstall(destPath, args = [], spawnOpts, meta) {
1073
1073
  async function runPipInstall(destPath, args = [], spawnOpts, meta) {
1074
1074
  if (meta && meta.isDev) {
1075
1075
  (0, import_debug.default)("Skipping dependency installation because dev mode is enabled");
1076
- return;
1076
+ return { installed: false };
1077
1077
  }
1078
1078
  (0, import_assert.default)(import_path.default.isAbsolute(destPath));
1079
- const opts = { ...spawnOpts, cwd: destPath, prettyCommand: "pip3 install" };
1079
+ const targetDir = import_path.default.join(destPath, ".vercel_python_packages");
1080
+ const opts = {
1081
+ ...spawnOpts,
1082
+ cwd: destPath,
1083
+ prettyCommand: "uv pip install"
1084
+ };
1080
1085
  await spawnAsync(
1081
- "pip3",
1082
- ["install", "--disable-pip-version-check", ...args],
1086
+ "uv",
1087
+ ["pip", "install", "--target", targetDir, ...args],
1083
1088
  opts
1084
1089
  );
1090
+ return { installed: true, targetDir };
1085
1091
  }
1086
1092
  function getScriptName(pkg, possibleNames) {
1087
1093
  if (pkg?.scripts) {
@@ -1,3 +1,4 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  export default function streamToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer>;
3
4
  export declare function streamToBufferChunks(stream: NodeJS.ReadableStream, chunkSize?: number): Promise<Buffer[]>;
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ import download, { downloadFile, DownloadedFiles, isSymbolicLink, isDirectory }
8
8
  import getWriteableDirectory from './fs/get-writable-directory';
9
9
  import glob, { GlobOptions } from './fs/glob';
10
10
  import rename from './fs/rename';
11
- import { spawnAsync, execCommand, spawnCommand, walkParentDirs, getScriptName, installDependencies, runPackageJsonScript, runNpmInstall, runBundleInstall, runPipInstall, runShellScript, runCustomInstallCommand, resetCustomInstallCommandSet, getEnvForPackageManager, getNodeVersion, getPathForPackageManager, detectPackageManager, getSpawnOptions, getNodeBinPath, getNodeBinPaths, scanParentDirs, findPackageJson, traverseUpDirectories } from './fs/run-user-scripts';
11
+ import { spawnAsync, execCommand, spawnCommand, walkParentDirs, getScriptName, installDependencies, runPackageJsonScript, runNpmInstall, runBundleInstall, runPipInstall, runShellScript, runCustomInstallCommand, resetCustomInstallCommandSet, getEnvForPackageManager, getNodeVersion, getPathForPackageManager, detectPackageManager, getSpawnOptions, getNodeBinPath, getNodeBinPaths, scanParentDirs, findPackageJson, traverseUpDirectories, PipInstallResult } from './fs/run-user-scripts';
12
12
  import { getLatestNodeVersion, getDiscontinuedNodeVersions, getSupportedNodeVersion, isBunVersion, getSupportedBunVersion } from './fs/node-version';
13
13
  import streamToBuffer, { streamToBufferChunks } from './fs/stream-to-buffer';
14
14
  import debug from './debug';
@@ -18,7 +18,7 @@ import { getPrefixedEnvVars } from './get-prefixed-env-vars';
18
18
  import { cloneEnv } from './clone-env';
19
19
  import { hardLinkDir } from './hard-link-dir';
20
20
  import { validateNpmrc } from './validate-npmrc';
21
- export { FileBlob, FileFsRef, FileRef, Lambda, NodejsLambda, createLambda, Prerender, download, downloadFile, DownloadedFiles, getWriteableDirectory, glob, GlobOptions, rename, spawnAsync, getScriptName, installDependencies, runPackageJsonScript, execCommand, spawnCommand, walkParentDirs, getNodeBinPath, getNodeBinPaths, getSupportedNodeVersion, isBunVersion, getSupportedBunVersion, detectPackageManager, runNpmInstall, runBundleInstall, runPipInstall, runShellScript, runCustomInstallCommand, resetCustomInstallCommandSet, getEnvForPackageManager, getNodeVersion, getPathForPackageManager, getLatestNodeVersion, getDiscontinuedNodeVersions, getSpawnOptions, getPlatformEnv, getPrefixedEnvVars, streamToBuffer, streamToBufferChunks, debug, isSymbolicLink, isDirectory, getLambdaOptionsFromFunction, scanParentDirs, findPackageJson, getIgnoreFilter, cloneEnv, hardLinkDir, traverseUpDirectories, validateNpmrc, };
21
+ export { FileBlob, FileFsRef, FileRef, Lambda, NodejsLambda, createLambda, Prerender, download, downloadFile, DownloadedFiles, getWriteableDirectory, glob, GlobOptions, rename, spawnAsync, getScriptName, installDependencies, runPackageJsonScript, execCommand, spawnCommand, walkParentDirs, getNodeBinPath, getNodeBinPaths, getSupportedNodeVersion, isBunVersion, getSupportedBunVersion, detectPackageManager, runNpmInstall, runBundleInstall, runPipInstall, PipInstallResult, runShellScript, runCustomInstallCommand, resetCustomInstallCommandSet, getEnvForPackageManager, getNodeVersion, getPathForPackageManager, getLatestNodeVersion, getDiscontinuedNodeVersions, getSpawnOptions, getPlatformEnv, getPrefixedEnvVars, streamToBuffer, streamToBufferChunks, debug, isSymbolicLink, isDirectory, getLambdaOptionsFromFunction, scanParentDirs, findPackageJson, getIgnoreFilter, cloneEnv, hardLinkDir, traverseUpDirectories, validateNpmrc, };
22
22
  export { EdgeFunction } from './edge-function';
23
23
  export { readConfigFile, getPackageJson } from './fs/read-config-file';
24
24
  export { normalizePath } from './fs/normalize-path';
@@ -32,5 +32,5 @@ export { NODE_VERSIONS } from './fs/node-version';
32
32
  export { getInstalledPackageVersion } from './get-installed-package-version';
33
33
  export { defaultCachePathGlob } from './default-cache-path-glob';
34
34
  export { generateNodeBuilderFunctions } from './generate-node-builder-functions';
35
- export { BACKEND_FRAMEWORKS, BackendFramework, isBackendFramework, isBackendBuilder, isExperimentalBackendsEnabled, isExperimentalBackendsWithoutIntrospectionEnabled, shouldUseExperimentalBackends, } from './framework-helpers';
35
+ export { BACKEND_FRAMEWORKS, BackendFramework, isBackendFramework, isBackendBuilder, isExperimentalBackendsEnabled, isExperimentalBackendsWithoutIntrospectionEnabled, shouldUseExperimentalBackends, PYTHON_FRAMEWORKS, PythonFramework, isPythonFramework, } from './framework-helpers';
36
36
  export * from './python';
package/dist/index.js CHANGED
@@ -23728,6 +23728,7 @@ __export(src_exports, {
23728
23728
  NodeVersion: () => NodeVersion,
23729
23729
  NodejsLambda: () => NodejsLambda,
23730
23730
  NowBuildError: () => NowBuildError,
23731
+ PYTHON_FRAMEWORKS: () => PYTHON_FRAMEWORKS,
23731
23732
  Prerender: () => Prerender,
23732
23733
  Span: () => Span,
23733
23734
  Version: () => Version,
@@ -23774,6 +23775,7 @@ __export(src_exports, {
23774
23775
  isExperimentalBackendsEnabled: () => isExperimentalBackendsEnabled,
23775
23776
  isExperimentalBackendsWithoutIntrospectionEnabled: () => isExperimentalBackendsWithoutIntrospectionEnabled,
23776
23777
  isPythonEntrypoint: () => isPythonEntrypoint,
23778
+ isPythonFramework: () => isPythonFramework,
23777
23779
  isSymbolicLink: () => isSymbolicLink,
23778
23780
  normalizePath: () => normalizePath,
23779
23781
  readConfigFile: () => readConfigFile,
@@ -26112,15 +26114,21 @@ async function runBundleInstall(destPath, args = [], spawnOpts, meta) {
26112
26114
  async function runPipInstall(destPath, args = [], spawnOpts, meta) {
26113
26115
  if (meta && meta.isDev) {
26114
26116
  debug("Skipping dependency installation because dev mode is enabled");
26115
- return;
26117
+ return { installed: false };
26116
26118
  }
26117
26119
  (0, import_assert6.default)(import_path6.default.isAbsolute(destPath));
26118
- const opts = { ...spawnOpts, cwd: destPath, prettyCommand: "pip3 install" };
26120
+ const targetDir = import_path6.default.join(destPath, ".vercel_python_packages");
26121
+ const opts = {
26122
+ ...spawnOpts,
26123
+ cwd: destPath,
26124
+ prettyCommand: "uv pip install"
26125
+ };
26119
26126
  await spawnAsync(
26120
- "pip3",
26121
- ["install", "--disable-pip-version-check", ...args],
26127
+ "uv",
26128
+ ["pip", "install", "--target", targetDir, ...args],
26122
26129
  opts
26123
26130
  );
26131
+ return { installed: true, targetDir };
26124
26132
  }
26125
26133
  function getScriptName(pkg, possibleNames) {
26126
26134
  if (pkg?.scripts) {
@@ -26745,6 +26753,12 @@ var BACKEND_FRAMEWORKS = [
26745
26753
  "fastify",
26746
26754
  "elysia"
26747
26755
  ];
26756
+ var PYTHON_FRAMEWORKS = [
26757
+ "fastapi",
26758
+ "flask",
26759
+ "python"
26760
+ // Generic Python framework preset
26761
+ ];
26748
26762
  var BACKEND_BUILDERS = [
26749
26763
  "@vercel/express",
26750
26764
  "@vercel/hono",
@@ -26759,6 +26773,11 @@ function isBackendFramework(framework) {
26759
26773
  return false;
26760
26774
  return BACKEND_FRAMEWORKS.includes(framework);
26761
26775
  }
26776
+ function isPythonFramework(framework) {
26777
+ if (!framework)
26778
+ return false;
26779
+ return PYTHON_FRAMEWORKS.includes(framework);
26780
+ }
26762
26781
  function isExperimentalBackendsWithoutIntrospectionEnabled() {
26763
26782
  return process.env.VERCEL_BACKENDS_BUILDS === "1";
26764
26783
  }
@@ -26837,6 +26856,7 @@ async function isPythonEntrypoint(file) {
26837
26856
  NodeVersion,
26838
26857
  NodejsLambda,
26839
26858
  NowBuildError,
26859
+ PYTHON_FRAMEWORKS,
26840
26860
  Prerender,
26841
26861
  Span,
26842
26862
  Version,
@@ -26883,6 +26903,7 @@ async function isPythonEntrypoint(file) {
26883
26903
  isExperimentalBackendsEnabled,
26884
26904
  isExperimentalBackendsWithoutIntrospectionEnabled,
26885
26905
  isPythonEntrypoint,
26906
+ isPythonFramework,
26886
26907
  isSymbolicLink,
26887
26908
  normalizePath,
26888
26909
  readConfigFile,
package/dist/types.d.ts CHANGED
@@ -549,3 +549,57 @@ export interface TriggerEvent {
549
549
  */
550
550
  initialDelaySeconds?: number;
551
551
  }
552
+ export type ServiceRuntime = 'node' | 'python' | 'go' | 'rust' | 'ruby';
553
+ export type ServiceType = 'web' | 'cron' | 'worker';
554
+ /**
555
+ * Configuration for a service in vercel.json.
556
+ * @experimental This feature is experimental and may change.
557
+ */
558
+ export interface ExperimentalServiceConfig {
559
+ type?: ServiceType;
560
+ /**
561
+ * Entry file for the service, relative to the workspace directory.
562
+ * @example "src/index.ts", "main.py", "api/server.go"
563
+ */
564
+ entrypoint?: string;
565
+ /**
566
+ * Path to the directory containing the service's manifest file
567
+ * (package.json, pyproject.toml, etc.).
568
+ * Defaults to "." (project root) if not specified.
569
+ */
570
+ workspace?: string;
571
+ /** Framework to use */
572
+ framework?: string;
573
+ /** Builder to use, e.g. @vercel/node, @vercel/python */
574
+ builder?: string;
575
+ /** Specific lambda runtime to use, e.g. nodejs24.x, python3.14 */
576
+ runtime?: string;
577
+ buildCommand?: string;
578
+ installCommand?: string;
579
+ /** Lambda config */
580
+ memory?: number;
581
+ maxDuration?: number;
582
+ includeFiles?: string | string[];
583
+ excludeFiles?: string | string[];
584
+ /** URL prefix for routing */
585
+ routePrefix?: string;
586
+ /** Cron schedule expression (e.g., "0 0 * * *") */
587
+ schedule?: string;
588
+ topic?: string;
589
+ consumer?: string;
590
+ }
591
+ /**
592
+ * Map of service name to service configuration.
593
+ * @experimental This feature is experimental and may change.
594
+ */
595
+ export type ExperimentalServices = Record<string, ExperimentalServiceConfig>;
596
+ /**
597
+ * Map of service group name to array of service names belonging to that group.
598
+ * @experimental This feature is experimental and may change.
599
+ * @example
600
+ * {
601
+ * "app": ["site", "backend"],
602
+ * "admin": ["admin", "backend"]
603
+ * }
604
+ */
605
+ export type ExperimentalServiceGroups = Record<string, string[]>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/build-utils",
3
- "version": "13.2.12",
3
+ "version": "13.2.14",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.js",
@@ -22,7 +22,7 @@
22
22
  "@types/minimatch": "^5.1.2",
23
23
  "@types/ms": "0.7.31",
24
24
  "@types/multistream": "2.1.1",
25
- "@types/node": "14.18.33",
25
+ "@types/node": "20.11.0",
26
26
  "@types/node-fetch": "^2.1.6",
27
27
  "@types/semver": "6.0.0",
28
28
  "@types/yazl": "2.4.2",
@@ -47,8 +47,8 @@
47
47
  "yazl": "2.5.1",
48
48
  "vitest": "2.0.1",
49
49
  "json5": "2.2.3",
50
- "@vercel/error-utils": "2.0.3",
51
- "@vercel/routing-utils": "5.3.2"
50
+ "@vercel/routing-utils": "5.3.2",
51
+ "@vercel/error-utils": "2.0.3"
52
52
  },
53
53
  "scripts": {
54
54
  "build": "node build.mjs",