@vercel/build-utils 13.23.0 → 13.25.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @vercel/build-utils
2
2
 
3
+ ## 13.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fb0cb8d: Add normalized entrypoint detector for runtime builders.
8
+ - 4fc110b: [services] add preDeployCommand for experimentalServices
9
+
10
+ ## 13.24.0
11
+
12
+ ### Minor Changes
13
+
14
+ - d874af6: Add support for env vars injection that reference other services in `services` with an explicit `env` configuration.
15
+
3
16
  ## 13.23.0
4
17
 
5
18
  ### Minor Changes
@@ -1,4 +1,4 @@
1
- import type { Service } from './types';
1
+ import type { EnvVars, Service } from './types';
2
2
  type Envs = {
3
3
  [key: string]: string | undefined;
4
4
  };
@@ -7,27 +7,49 @@ interface FrameworkInfo {
7
7
  envPrefix?: string;
8
8
  }
9
9
  export interface GetServiceUrlEnvVarsOptions {
10
+ requestedEnv: EnvVars;
11
+ consumerService?: Service;
12
+ services: Service[];
13
+ frameworkList: readonly FrameworkInfo[];
14
+ currentEnv?: Envs;
15
+ deploymentUrl?: string;
16
+ origin?: string;
17
+ }
18
+ export interface GetExperimentalServiceUrlEnvVarsOptions {
10
19
  services: Service[];
11
20
  frameworkList: readonly FrameworkInfo[];
12
21
  currentEnv?: Envs;
13
22
  deploymentUrl?: string;
14
23
  origin?: string;
15
- envPrefix?: string;
16
24
  }
17
25
  /**
18
- * Generate environment variables for service URLs.
26
+ * Resolve a map of declared env-var refs into concrete URL values.
19
27
  *
20
- * For each web service, generates:
21
- * 1. A base env var with the full absolute URL (e.g., BACKEND_URL=https://deploy.vercel.app/api)
22
- * for server-side use.
23
- * 2. Framework-prefixed versions with only the route prefix path
24
- * (e.g., NEXT_PUBLIC_BACKEND_URL=/api, VITE_BACKEND_URL=/api) for client-side use.
25
- * Using relative paths avoids CORS issues since the browser resolves them against
26
- * the current origin, which works correctly across production domains, preview
27
- * deployments, and custom domains.
28
+ * By default the value is the absolute URL of the referenced web service,
29
+ * while if a consumer's framework has an `envPrefix`
30
+ * (e.g. `NEXT_PUBLIC_` or `VITE_`) and the declared name starts with that prefix
31
+ * then the target's route prefix (e.g. `/api`) is used,
32
+ * which useful for client bundles where same-origin requests avoid CORS.
28
33
  *
29
34
  * Environment variables that are already set in `currentEnv` will NOT be overwritten,
30
35
  * allowing user-defined values to take precedence.
31
36
  */
32
37
  export declare function getServiceUrlEnvVars(options: GetServiceUrlEnvVarsOptions): Record<string, string>;
38
+ /**
39
+ * Legacy implicit URL injection used for `experimentalServices` (and
40
+ * auto-detected services that map to the experimentalServices shape).
41
+ *
42
+ * For each web service, generates:
43
+ * 1. `{NAME}_URL` with the absolute URL (server-side use).
44
+ * 2. `{PREFIX}{NAME}_URL` for every framework prefix in `frameworkList` that
45
+ * matches a service in the deployment, with the relative route prefix
46
+ * (client-side use; relative paths avoid CORS).
47
+ *
48
+ * Entries already present in `currentEnv` are not overwritten — user-defined
49
+ * values win.
50
+ *
51
+ * The GA `services` field replaces this with explicit `env` declarations
52
+ * handled by `getServiceUrlEnvVars`.
53
+ */
54
+ export declare function getExperimentalServiceUrlEnvVars(options: GetExperimentalServiceUrlEnvVarsOptions): Record<string, string>;
33
55
  export {};
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var get_service_url_env_vars_exports = {};
20
20
  __export(get_service_url_env_vars_exports, {
21
+ getExperimentalServiceUrlEnvVars: () => getExperimentalServiceUrlEnvVars,
21
22
  getServiceUrlEnvVars: () => getServiceUrlEnvVars
22
23
  });
23
24
  module.exports = __toCommonJS(get_service_url_env_vars_exports);
@@ -44,12 +45,46 @@ function getFrameworkEnvPrefix(frameworkSlug, frameworkList) {
44
45
  }
45
46
  function getServiceUrlEnvVars(options) {
46
47
  const {
48
+ requestedEnv,
49
+ consumerService,
47
50
  services,
48
51
  frameworkList,
49
52
  currentEnv = {},
50
53
  deploymentUrl,
51
- origin,
52
- envPrefix
54
+ origin
55
+ } = options;
56
+ const baseUrl = origin || deploymentUrl;
57
+ if (!baseUrl)
58
+ return {};
59
+ const servicesByName = new Map(services.map((s) => [s.name, s]));
60
+ const consumerEnvPrefix = getFrameworkEnvPrefix(
61
+ consumerService?.framework,
62
+ frameworkList
63
+ );
64
+ const result = {};
65
+ for (const [name, envVar] of Object.entries(requestedEnv)) {
66
+ if (name in currentEnv) {
67
+ continue;
68
+ }
69
+ if (envVar.type !== "service-ref") {
70
+ continue;
71
+ }
72
+ const target = servicesByName.get(envVar.service);
73
+ if (!target || target.type !== "web" || !target.routePrefix) {
74
+ continue;
75
+ }
76
+ const isClientSide = !!consumerEnvPrefix && name.startsWith(consumerEnvPrefix) && name.length > consumerEnvPrefix.length;
77
+ result[name] = isClientSide ? target.routePrefix : computeServiceUrl(baseUrl, target.routePrefix, !!origin);
78
+ }
79
+ return result;
80
+ }
81
+ function getExperimentalServiceUrlEnvVars(options) {
82
+ const {
83
+ services,
84
+ frameworkList,
85
+ currentEnv = {},
86
+ deploymentUrl,
87
+ origin
53
88
  } = options;
54
89
  const baseUrl = origin || deploymentUrl;
55
90
  if (!baseUrl || !services || services.length === 0) {
@@ -73,12 +108,11 @@ function getServiceUrlEnvVars(options) {
73
108
  service.routePrefix,
74
109
  !!origin
75
110
  );
76
- const effectiveBaseEnvVarName = envPrefix ? `${envPrefix}${baseEnvVarName}` : baseEnvVarName;
77
- if (!(effectiveBaseEnvVarName in currentEnv)) {
78
- envVars[effectiveBaseEnvVarName] = absoluteUrl;
111
+ if (!(baseEnvVarName in currentEnv)) {
112
+ envVars[baseEnvVarName] = absoluteUrl;
79
113
  }
80
114
  for (const prefix of frameworkPrefixes) {
81
- const prefixedEnvVarName = envPrefix ? `${prefix}${envPrefix}${baseEnvVarName}` : `${prefix}${baseEnvVarName}`;
115
+ const prefixedEnvVarName = `${prefix}${baseEnvVarName}`;
82
116
  if (!(prefixedEnvVarName in currentEnv)) {
83
117
  envVars[prefixedEnvVarName] = service.routePrefix;
84
118
  }
@@ -88,5 +122,6 @@ function getServiceUrlEnvVars(options) {
88
122
  }
89
123
  // Annotate the CommonJS export names for ESM import in node:
90
124
  0 && (module.exports = {
125
+ getExperimentalServiceUrlEnvVars,
91
126
  getServiceUrlEnvVars
92
127
  });
package/dist/index.d.ts CHANGED
@@ -15,12 +15,12 @@ import debug from './debug';
15
15
  import getIgnoreFilter from './get-ignore-filter';
16
16
  import { getPlatformEnv } from './get-platform-env';
17
17
  import { getPrefixedEnvVars } from './get-prefixed-env-vars';
18
- import { getServiceUrlEnvVars } from './get-service-url-env-vars';
18
+ import { getServiceUrlEnvVars, getExperimentalServiceUrlEnvVars } from './get-service-url-env-vars';
19
19
  import { cloneEnv } from './clone-env';
20
20
  import { hardLinkDir } from './hard-link-dir';
21
21
  import { validateNpmrc } from './validate-npmrc';
22
22
  export type { NodejsLambdaOptions };
23
- 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, NpmInstallOutput, runBundleInstall, runPipInstall, PipInstallResult, runShellScript, runCustomInstallCommand, resetCustomInstallCommandSet, getEnvForPackageManager, getNodeVersion, getPathForPackageManager, getLatestNodeVersion, getDiscontinuedNodeVersions, getSpawnOptions, getPlatformEnv, getPrefixedEnvVars, getServiceUrlEnvVars, streamToBuffer, streamToBufferChunks, debug, isSymbolicLink, isDirectory, getLambdaOptionsFromFunction, sanitizeConsumerName, scanParentDirs, findPackageJson, getIgnoreFilter, cloneEnv, hardLinkDir, traverseUpDirectories, validateNpmrc, };
23
+ 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, NpmInstallOutput, runBundleInstall, runPipInstall, PipInstallResult, runShellScript, runCustomInstallCommand, resetCustomInstallCommandSet, getEnvForPackageManager, getNodeVersion, getPathForPackageManager, getLatestNodeVersion, getDiscontinuedNodeVersions, getSpawnOptions, getPlatformEnv, getPrefixedEnvVars, getServiceUrlEnvVars, getExperimentalServiceUrlEnvVars, streamToBuffer, streamToBufferChunks, debug, isSymbolicLink, isDirectory, getLambdaOptionsFromFunction, sanitizeConsumerName, scanParentDirs, findPackageJson, getIgnoreFilter, cloneEnv, hardLinkDir, traverseUpDirectories, validateNpmrc, };
24
24
  export { EdgeFunction } from './edge-function';
25
25
  export { readConfigFile, getPackageJson } from './fs/read-config-file';
26
26
  export { normalizePath } from './fs/normalize-path';
package/dist/index.js CHANGED
@@ -34489,6 +34489,7 @@ __export(src_exports, {
34489
34489
  BACKEND_FRAMEWORKS: () => BACKEND_FRAMEWORKS,
34490
34490
  BUILDER_COMPILE_STEP: () => BUILDER_COMPILE_STEP,
34491
34491
  BUILDER_INSTALLER_STEP: () => BUILDER_INSTALLER_STEP,
34492
+ BUILDER_PRE_DEPLOY_STEP: () => BUILDER_PRE_DEPLOY_STEP,
34492
34493
  BunVersion: () => BunVersion,
34493
34494
  ENV_WRAPPER_SUPPORTED_FAMILIES: () => ENV_WRAPPER_SUPPORTED_FAMILIES,
34494
34495
  EdgeFunction: () => EdgeFunction,
@@ -34539,6 +34540,7 @@ __export(src_exports, {
34539
34540
  getDiscontinuedNodeVersions: () => getDiscontinuedNodeVersions,
34540
34541
  getEncryptedEnv: () => getEncryptedEnv,
34541
34542
  getEnvForPackageManager: () => getEnvForPackageManager,
34543
+ getExperimentalServiceUrlEnvVars: () => getExperimentalServiceUrlEnvVars,
34542
34544
  getIgnoreFilter: () => get_ignore_filter_default,
34543
34545
  getInstalledPackageVersion: () => getInstalledPackageVersion,
34544
34546
  getInternalServiceCronPath: () => getInternalServiceCronPath,
@@ -37949,12 +37951,46 @@ function getFrameworkEnvPrefix(frameworkSlug, frameworkList) {
37949
37951
  }
37950
37952
  function getServiceUrlEnvVars(options) {
37951
37953
  const {
37954
+ requestedEnv,
37955
+ consumerService,
37952
37956
  services,
37953
37957
  frameworkList,
37954
37958
  currentEnv = {},
37955
37959
  deploymentUrl,
37956
- origin,
37957
- envPrefix
37960
+ origin
37961
+ } = options;
37962
+ const baseUrl = origin || deploymentUrl;
37963
+ if (!baseUrl)
37964
+ return {};
37965
+ const servicesByName = new Map(services.map((s) => [s.name, s]));
37966
+ const consumerEnvPrefix = getFrameworkEnvPrefix(
37967
+ consumerService?.framework,
37968
+ frameworkList
37969
+ );
37970
+ const result = {};
37971
+ for (const [name, envVar] of Object.entries(requestedEnv)) {
37972
+ if (name in currentEnv) {
37973
+ continue;
37974
+ }
37975
+ if (envVar.type !== "service-ref") {
37976
+ continue;
37977
+ }
37978
+ const target = servicesByName.get(envVar.service);
37979
+ if (!target || target.type !== "web" || !target.routePrefix) {
37980
+ continue;
37981
+ }
37982
+ const isClientSide = !!consumerEnvPrefix && name.startsWith(consumerEnvPrefix) && name.length > consumerEnvPrefix.length;
37983
+ result[name] = isClientSide ? target.routePrefix : computeServiceUrl(baseUrl, target.routePrefix, !!origin);
37984
+ }
37985
+ return result;
37986
+ }
37987
+ function getExperimentalServiceUrlEnvVars(options) {
37988
+ const {
37989
+ services,
37990
+ frameworkList,
37991
+ currentEnv = {},
37992
+ deploymentUrl,
37993
+ origin
37958
37994
  } = options;
37959
37995
  const baseUrl = origin || deploymentUrl;
37960
37996
  if (!baseUrl || !services || services.length === 0) {
@@ -37978,12 +38014,11 @@ function getServiceUrlEnvVars(options) {
37978
38014
  service.routePrefix,
37979
38015
  !!origin
37980
38016
  );
37981
- const effectiveBaseEnvVarName = envPrefix ? `${envPrefix}${baseEnvVarName}` : baseEnvVarName;
37982
- if (!(effectiveBaseEnvVarName in currentEnv)) {
37983
- envVars[effectiveBaseEnvVarName] = absoluteUrl;
38017
+ if (!(baseEnvVarName in currentEnv)) {
38018
+ envVars[baseEnvVarName] = absoluteUrl;
37984
38019
  }
37985
38020
  for (const prefix of frameworkPrefixes) {
37986
- const prefixedEnvVarName = envPrefix ? `${prefix}${envPrefix}${baseEnvVarName}` : `${prefix}${baseEnvVarName}`;
38021
+ const prefixedEnvVarName = `${prefix}${baseEnvVarName}`;
37987
38022
  if (!(prefixedEnvVarName in currentEnv)) {
37988
38023
  envVars[prefixedEnvVarName] = service.routePrefix;
37989
38024
  }
@@ -38910,6 +38945,7 @@ var Span = class _Span {
38910
38945
  // src/trace/constants.ts
38911
38946
  var BUILDER_INSTALLER_STEP = "vc.builder.install";
38912
38947
  var BUILDER_COMPILE_STEP = "vc.builder.build";
38948
+ var BUILDER_PRE_DEPLOY_STEP = "vc.builder.preDeploy";
38913
38949
 
38914
38950
  // src/get-installed-package-version.ts
38915
38951
  async function getInstalledPackageVersion(packageName, path8) {
@@ -40248,6 +40284,7 @@ function getExtendedPayload({
40248
40284
  BACKEND_FRAMEWORKS,
40249
40285
  BUILDER_COMPILE_STEP,
40250
40286
  BUILDER_INSTALLER_STEP,
40287
+ BUILDER_PRE_DEPLOY_STEP,
40251
40288
  BunVersion,
40252
40289
  ENV_WRAPPER_SUPPORTED_FAMILIES,
40253
40290
  EdgeFunction,
@@ -40298,6 +40335,7 @@ function getExtendedPayload({
40298
40335
  getDiscontinuedNodeVersions,
40299
40336
  getEncryptedEnv,
40300
40337
  getEnvForPackageManager,
40338
+ getExperimentalServiceUrlEnvVars,
40301
40339
  getIgnoreFilter,
40302
40340
  getInstalledPackageVersion,
40303
40341
  getInternalServiceCronPath,
@@ -1,2 +1,3 @@
1
1
  export declare const BUILDER_INSTALLER_STEP = "vc.builder.install";
2
2
  export declare const BUILDER_COMPILE_STEP = "vc.builder.build";
3
+ export declare const BUILDER_PRE_DEPLOY_STEP = "vc.builder.preDeploy";
@@ -19,13 +19,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var constants_exports = {};
20
20
  __export(constants_exports, {
21
21
  BUILDER_COMPILE_STEP: () => BUILDER_COMPILE_STEP,
22
- BUILDER_INSTALLER_STEP: () => BUILDER_INSTALLER_STEP
22
+ BUILDER_INSTALLER_STEP: () => BUILDER_INSTALLER_STEP,
23
+ BUILDER_PRE_DEPLOY_STEP: () => BUILDER_PRE_DEPLOY_STEP
23
24
  });
24
25
  module.exports = __toCommonJS(constants_exports);
25
26
  const BUILDER_INSTALLER_STEP = "vc.builder.install";
26
27
  const BUILDER_COMPILE_STEP = "vc.builder.build";
28
+ const BUILDER_PRE_DEPLOY_STEP = "vc.builder.preDeploy";
27
29
  // Annotate the CommonJS export names for ESM import in node:
28
30
  0 && (module.exports = {
29
31
  BUILDER_COMPILE_STEP,
30
- BUILDER_INSTALLER_STEP
32
+ BUILDER_INSTALLER_STEP,
33
+ BUILDER_PRE_DEPLOY_STEP
31
34
  });
@@ -1,3 +1,3 @@
1
1
  export { Span } from './trace';
2
- export { BUILDER_COMPILE_STEP, BUILDER_INSTALLER_STEP } from './constants';
2
+ export { BUILDER_COMPILE_STEP, BUILDER_INSTALLER_STEP, BUILDER_PRE_DEPLOY_STEP, } from './constants';
3
3
  export type { SpanId, TraceEvent, Reporter } from './trace';
@@ -20,6 +20,7 @@ var trace_exports = {};
20
20
  __export(trace_exports, {
21
21
  BUILDER_COMPILE_STEP: () => import_constants.BUILDER_COMPILE_STEP,
22
22
  BUILDER_INSTALLER_STEP: () => import_constants.BUILDER_INSTALLER_STEP,
23
+ BUILDER_PRE_DEPLOY_STEP: () => import_constants.BUILDER_PRE_DEPLOY_STEP,
23
24
  Span: () => import_trace.Span
24
25
  });
25
26
  module.exports = __toCommonJS(trace_exports);
@@ -29,5 +30,6 @@ var import_constants = require("./constants");
29
30
  0 && (module.exports = {
30
31
  BUILDER_COMPILE_STEP,
31
32
  BUILDER_INSTALLER_STEP,
33
+ BUILDER_PRE_DEPLOY_STEP,
32
34
  Span
33
35
  });
package/dist/types.d.ts CHANGED
@@ -97,6 +97,12 @@ export interface BuildOptions {
97
97
  * fully processed
98
98
  */
99
99
  buildCallback?: (opts: Omit<BuildOptions, 'buildCallback'>) => Promise<void>;
100
+ /**
101
+ * Called by the builder to register a callback that will execute the
102
+ * service's pre-deploy command. The CLI collects these and invokes
103
+ * them only after every builder has succeeded.
104
+ */
105
+ registerPreDeploy?: (callback: () => Promise<void>) => void;
100
106
  /**
101
107
  * The current trace state from the internal vc tracing
102
108
  */
@@ -500,6 +506,12 @@ export interface ServiceQueueTopic {
500
506
  export type ServiceTopics = string[] | ServiceQueueTopic[];
501
507
  export declare const JOB_TRIGGERS: readonly ["queue", "schedule", "workflow"];
502
508
  export type JobTrigger = (typeof JOB_TRIGGERS)[number];
509
+ export interface ServiceRefEnvVar {
510
+ type: 'service-ref';
511
+ service: string;
512
+ }
513
+ export type EnvVar = ServiceRefEnvVar;
514
+ export type EnvVars = Record<string, EnvVar>;
503
515
  export interface Service {
504
516
  name: string;
505
517
  type: ServiceType;
@@ -512,14 +524,14 @@ export interface Service {
512
524
  runtime?: string;
513
525
  buildCommand?: string;
514
526
  installCommand?: string;
527
+ preDeployCommand?: string;
515
528
  routePrefix?: string;
516
529
  routePrefixSource?: 'configured' | 'generated';
517
530
  subdomain?: string;
518
531
  schedule?: string | string[];
519
532
  handlerFunction?: string;
520
533
  topics?: ServiceTopics;
521
- /** custom prefix to inject service URL env vars */
522
- envPrefix?: string;
534
+ env?: EnvVars;
523
535
  }
524
536
  export declare function getServiceQueueTopicConfigs(config: {
525
537
  type?: ServiceType;
@@ -744,8 +756,7 @@ export interface ExperimentalServiceConfig {
744
756
  /** Cron schedule expression(s) (e.g., "0 0 * * *") */
745
757
  schedule?: string | string[];
746
758
  topics?: ServiceTopics;
747
- /** Custom prefix to use to inject service URL env vars */
748
- envPrefix?: string;
759
+ env?: EnvVars;
749
760
  }
750
761
  /**
751
762
  * Map of service name to service configuration.
@@ -801,3 +812,37 @@ export type Services = Record<string, ServiceConfig>;
801
812
  * }
802
813
  */
803
814
  export type ExperimentalServiceGroups = Record<string, string[]>;
815
+ /**
816
+ * Result of a runtime builder's normalized entrypoint detection.
817
+ *
818
+ * - `kind: 'file'` — `entrypoint` is a path relative to the scanned `workPath`
819
+ * (e.g. `"src/index.ts"`, `"main.go"`, `"main.py"`).
820
+ * - `kind: 'py-module:attr'` — `entrypoint` is a Python `module:attr` reference
821
+ * where the module is dot-separated and resolved relative to the scanned
822
+ * `workPath` (e.g. `"main:app"`, `"src.main:app"`).
823
+ *
824
+ * @experimental This feature is experimental and may change.
825
+ */
826
+ export type DetectedEntrypoint = {
827
+ kind: 'file';
828
+ entrypoint: string;
829
+ } | {
830
+ kind: 'py-module:attr';
831
+ entrypoint: string;
832
+ } | null;
833
+ /**
834
+ * Input to a runtime builder's normalized entrypoint detector.
835
+ * @experimental This feature is experimental and may change.
836
+ */
837
+ export interface DetectEntrypointOptions {
838
+ /** Path to the candidate service directory relative to project root. */
839
+ workPath: string;
840
+ /** Framework slug detected for this directory, if any. */
841
+ framework?: string;
842
+ }
843
+ /**
844
+ * Normalized entrypoint detector signature, implemented by each runtime builder
845
+ * and consumed by services auto-detection to populate suggested service configs.
846
+ * @experimental This feature is experimental and may change.
847
+ */
848
+ export type DetectEntrypointFn = (opts: DetectEntrypointOptions) => Promise<DetectedEntrypoint>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/build-utils",
3
- "version": "13.23.0",
3
+ "version": "13.25.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.js",