@vercel/build-utils 13.3.5 → 13.4.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,20 @@
1
1
  # @vercel/build-utils
2
2
 
3
+ ## 13.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [services] synchronize dependencies in dev mode for JS/TS and Python services ([#14987](https://github.com/vercel/vercel/pull/14987))
8
+
9
+ - [services] inject service URLs into web services as local paths ([#15024](https://github.com/vercel/vercel/pull/15024))
10
+
11
+ ### Patch Changes
12
+
13
+ - Add new expirementalTrigger format for queues v2beta ([#14970](https://github.com/vercel/vercel/pull/14970))
14
+
15
+ - Updated dependencies [[`a960cf23a42ff1a570c808ee9567670c24422f98`](https://github.com/vercel/vercel/commit/a960cf23a42ff1a570c808ee9567670c24422f98)]:
16
+ - @vercel/python-analysis@0.4.1
17
+
3
18
  ## 13.3.5
4
19
 
5
20
  ### Patch Changes
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { SpawnOptions } from 'child_process';
3
4
  import { Meta, PackageJson, NodeVersion, Config, BunVersion } from '../types';
4
5
  export type CliType = 'yarn' | 'npm' | 'pnpm' | 'bun' | 'vlt';
@@ -73,6 +74,26 @@ export interface SpawnOptionsExtended extends SpawnOptions {
73
74
  * the error code, stdout and stderr.
74
75
  */
75
76
  ignoreNon0Exit?: boolean;
77
+ /**
78
+ * Writable stream to pipe stdout to (e.g., for prefixing output in multi-service mode).
79
+ * When provided, stdio is automatically set to 'pipe'.
80
+ */
81
+ outputStream?: NodeJS.WritableStream;
82
+ /**
83
+ * Writable stream to pipe stderr to (e.g., for prefixing output in multi-service mode).
84
+ * When provided, stdio is automatically set to 'pipe'.
85
+ */
86
+ errorStream?: NodeJS.WritableStream;
87
+ }
88
+ export interface NpmInstallOutput {
89
+ /**
90
+ * Writable stream for stdout (e.g., for prefixing output in multi-service mode)
91
+ */
92
+ stdout?: NodeJS.WritableStream;
93
+ /**
94
+ * Writable stream for stderr (e.g., for prefixing output in multi-service mode)
95
+ */
96
+ stderr?: NodeJS.WritableStream;
76
97
  }
77
98
  export declare function spawnAsync(command: string, args: string[], opts?: SpawnOptionsExtended): Promise<void>;
78
99
  export declare function spawnCommand(command: string, options?: SpawnOptions): import("child_process").ChildProcess;
@@ -111,7 +132,7 @@ export declare function walkParentDirs({ base, start, filename, }: WalkParentDir
111
132
  * across multiple builds in the same Node process (e.g., in unit tests).
112
133
  */
113
134
  export declare function resetCustomInstallCommandSet(): void;
114
- export declare function runNpmInstall(destPath: string, args?: string[], spawnOpts?: SpawnOptions, meta?: Meta, projectCreatedAt?: number): Promise<boolean>;
135
+ export declare function runNpmInstall(destPath: string, args?: string[], spawnOpts?: SpawnOptions, meta?: Meta, projectCreatedAt?: number, output?: NpmInstallOutput): Promise<boolean>;
115
136
  /**
116
137
  * Prepares the input environment based on the used package manager and lockfile
117
138
  * versions.
@@ -79,9 +79,21 @@ const NO_OVERRIDE = {
79
79
  function spawnAsync(command, args, opts = {}) {
80
80
  return new Promise((resolve, reject) => {
81
81
  const stderrLogs = [];
82
- opts = { stdio: "inherit", ...opts };
82
+ const hasCustomStreams = opts.outputStream || opts.errorStream;
83
+ if (hasCustomStreams) {
84
+ opts = { ...opts, stdio: ["inherit", "pipe", "pipe"] };
85
+ } else {
86
+ opts = { stdio: "inherit", ...opts };
87
+ }
83
88
  const child = (0, import_cross_spawn.default)(command, args, opts);
84
- if (opts.stdio === "pipe" && child.stderr) {
89
+ if (hasCustomStreams) {
90
+ if (child.stdout && opts.outputStream) {
91
+ child.stdout.pipe(opts.outputStream);
92
+ }
93
+ if (child.stderr && opts.errorStream) {
94
+ child.stderr.pipe(opts.errorStream);
95
+ }
96
+ } else if (opts.stdio === "pipe" && child.stderr) {
85
97
  child.stderr.on("data", (data) => stderrLogs.push(data));
86
98
  }
87
99
  child.on("error", reject);
@@ -93,7 +105,7 @@ function spawnAsync(command, args, opts = {}) {
93
105
  reject(
94
106
  new import_errors.NowBuildError({
95
107
  code: `BUILD_UTILS_SPAWN_${code || signal}`,
96
- message: opts.stdio === "inherit" ? `${cmd} exited with ${code || signal}` : stderrLogs.map((line) => line.toString()).join("")
108
+ message: opts.stdio === "inherit" || hasCustomStreams ? `${cmd} exited with ${code || signal}` : stderrLogs.map((line) => line.toString()).join("")
97
109
  })
98
110
  );
99
111
  });
@@ -518,13 +530,16 @@ function getInstallCommandForPackageManager(packageManager, args) {
518
530
  async function runInstallCommand({
519
531
  packageManager,
520
532
  args,
521
- opts
533
+ opts,
534
+ output
522
535
  }) {
523
536
  const { commandArguments, prettyCommand } = getInstallCommandForPackageManager(packageManager, args);
524
537
  opts.prettyCommand = prettyCommand;
525
538
  if (process.env.NPM_ONLY_PRODUCTION) {
526
539
  commandArguments.push("--production");
527
540
  }
541
+ opts.outputStream = output?.stdout;
542
+ opts.errorStream = output?.stderr;
528
543
  await spawnAsync(packageManager, commandArguments, opts);
529
544
  }
530
545
  function initializeSet(set) {
@@ -544,7 +559,7 @@ let customInstallCommandSet;
544
559
  function resetCustomInstallCommandSet() {
545
560
  customInstallCommandSet = void 0;
546
561
  }
547
- async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreatedAt) {
562
+ async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreatedAt, output) {
548
563
  if (meta?.isDev) {
549
564
  (0, import_debug.default)("Skipping dependency installation because dev mode is enabled");
550
565
  return false;
@@ -596,7 +611,11 @@ async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreate
596
611
  }
597
612
  }
598
613
  const installTime = Date.now();
599
- console.log("Installing dependencies...");
614
+ if (output?.stdout) {
615
+ output.stdout.write("Installing dependencies...\n");
616
+ } else {
617
+ console.log("Installing dependencies...");
618
+ }
600
619
  (0, import_debug.default)(`Installing to ${destPath}`);
601
620
  const opts = { cwd: destPath, ...spawnOpts };
602
621
  const env = (0, import_clone_env.cloneEnv)(opts.env || process.env);
@@ -619,7 +638,8 @@ async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreate
619
638
  await runInstallCommand({
620
639
  packageManager: cliType,
621
640
  args,
622
- opts
641
+ opts,
642
+ output
623
643
  });
624
644
  (0, import_debug.default)(`Install complete [${Date.now() - installTime}ms]`);
625
645
  return true;
@@ -11,6 +11,7 @@ export interface GetServiceUrlEnvVarsOptions {
11
11
  frameworkList: readonly FrameworkInfo[];
12
12
  currentEnv?: Envs;
13
13
  deploymentUrl?: string;
14
+ origin?: string;
14
15
  }
15
16
  /**
16
17
  * Generate environment variables for service URLs.
@@ -24,12 +24,15 @@ module.exports = __toCommonJS(get_service_url_env_vars_exports);
24
24
  function serviceNameToEnvVar(name) {
25
25
  return `${name.replace(/-/g, "_").toUpperCase()}_URL`;
26
26
  }
27
- function computeServiceUrl(deploymentUrl, routePrefix) {
27
+ function computeServiceUrl(baseUrl, routePrefix, isOrigin) {
28
+ if (!isOrigin) {
29
+ baseUrl = `https://${baseUrl}`;
30
+ }
28
31
  if (routePrefix === "/") {
29
- return `https://${deploymentUrl}`;
32
+ return baseUrl;
30
33
  }
31
34
  const normalizedPrefix = routePrefix.startsWith("/") ? routePrefix.slice(1) : routePrefix;
32
- return `https://${deploymentUrl}/${normalizedPrefix}`;
35
+ return `${baseUrl}/${normalizedPrefix}`;
33
36
  }
34
37
  function getFrameworkEnvPrefix(frameworkSlug, frameworkList) {
35
38
  if (!frameworkSlug)
@@ -40,8 +43,15 @@ function getFrameworkEnvPrefix(frameworkSlug, frameworkList) {
40
43
  return framework?.envPrefix;
41
44
  }
42
45
  function getServiceUrlEnvVars(options) {
43
- const { services, frameworkList, currentEnv = {}, deploymentUrl } = options;
44
- if (!deploymentUrl || !services || services.length === 0) {
46
+ const {
47
+ services,
48
+ frameworkList,
49
+ currentEnv = {},
50
+ deploymentUrl,
51
+ origin
52
+ } = options;
53
+ const baseUrl = origin || deploymentUrl;
54
+ if (!baseUrl || !services || services.length === 0) {
45
55
  return {};
46
56
  }
47
57
  const envVars = {};
@@ -57,7 +67,11 @@ function getServiceUrlEnvVars(options) {
57
67
  continue;
58
68
  }
59
69
  const baseEnvVarName = serviceNameToEnvVar(service.name);
60
- const absoluteUrl = computeServiceUrl(deploymentUrl, service.routePrefix);
70
+ const absoluteUrl = computeServiceUrl(
71
+ baseUrl,
72
+ service.routePrefix,
73
+ !!origin
74
+ );
61
75
  if (!(baseEnvVarName in currentEnv)) {
62
76
  envVars[baseEnvVarName] = absoluteUrl;
63
77
  }
package/dist/index.d.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import FileBlob from './file-blob';
2
2
  import FileFsRef from './file-fs-ref';
3
3
  import FileRef from './file-ref';
4
- import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
4
+ import { Lambda, createLambda, getLambdaOptionsFromFunction, sanitizeConsumerName } from './lambda';
5
5
  import { NodejsLambda } from './nodejs-lambda';
6
6
  import { Prerender } from './prerender';
7
7
  import download, { downloadFile, DownloadedFiles, isSymbolicLink, isDirectory } from './fs/download';
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, PipInstallResult } 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, NpmInstallOutput } 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';
@@ -19,7 +19,7 @@ import { getServiceUrlEnvVars } 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
- 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, getServiceUrlEnvVars, streamToBuffer, streamToBufferChunks, debug, isSymbolicLink, isDirectory, getLambdaOptionsFromFunction, scanParentDirs, findPackageJson, getIgnoreFilter, cloneEnv, hardLinkDir, traverseUpDirectories, validateNpmrc, };
22
+ 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
23
  export { EdgeFunction } from './edge-function';
24
24
  export { readConfigFile, getPackageJson } from './fs/read-config-file';
25
25
  export { normalizePath } from './fs/normalize-path';
package/dist/index.js CHANGED
@@ -21892,6 +21892,7 @@ __export(src_exports, {
21892
21892
  runPackageJsonScript: () => runPackageJsonScript,
21893
21893
  runPipInstall: () => runPipInstall,
21894
21894
  runShellScript: () => runShellScript,
21895
+ sanitizeConsumerName: () => sanitizeConsumerName,
21895
21896
  scanParentDirs: () => scanParentDirs,
21896
21897
  shouldServe: () => shouldServe,
21897
21898
  shouldUseExperimentalBackends: () => shouldUseExperimentalBackends,
@@ -22398,6 +22399,23 @@ async function download(files, basePath, meta) {
22398
22399
  }
22399
22400
 
22400
22401
  // src/lambda.ts
22402
+ function sanitizeConsumerName(functionPath) {
22403
+ let result = "";
22404
+ for (const char of functionPath) {
22405
+ if (char === "_") {
22406
+ result += "__";
22407
+ } else if (char === "/") {
22408
+ result += "_S";
22409
+ } else if (char === ".") {
22410
+ result += "_D";
22411
+ } else if (/[A-Za-z0-9-]/.test(char)) {
22412
+ result += char;
22413
+ } else {
22414
+ result += "_" + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0");
22415
+ }
22416
+ }
22417
+ return result;
22418
+ }
22401
22419
  function getDefaultLambdaArchitecture(architecture) {
22402
22420
  if (architecture) {
22403
22421
  return architecture;
@@ -22519,8 +22537,8 @@ var Lambda = class {
22519
22537
  `${prefix} is not an object`
22520
22538
  );
22521
22539
  (0, import_assert4.default)(
22522
- trigger.type === "queue/v1beta",
22523
- `${prefix}.type must be "queue/v1beta"`
22540
+ trigger.type === "queue/v1beta" || trigger.type === "queue/v2beta",
22541
+ `${prefix}.type must be "queue/v1beta" or "queue/v2beta"`
22524
22542
  );
22525
22543
  (0, import_assert4.default)(
22526
22544
  typeof trigger.topic === "string",
@@ -22535,6 +22553,12 @@ var Lambda = class {
22535
22553
  trigger.consumer.length > 0,
22536
22554
  `${prefix}.consumer cannot be empty`
22537
22555
  );
22556
+ if (trigger.type === "queue/v2beta") {
22557
+ (0, import_assert4.default)(
22558
+ experimentalTriggers.length === 1,
22559
+ '"experimentalTriggers" can only have one item for queue/v2beta'
22560
+ );
22561
+ }
22538
22562
  if (trigger.maxDeliveries !== void 0) {
22539
22563
  (0, import_assert4.default)(
22540
22564
  typeof trigger.maxDeliveries === "number",
@@ -22675,12 +22699,23 @@ async function getLambdaOptionsFromFunction({
22675
22699
  if (config?.functions) {
22676
22700
  for (const [pattern, fn] of Object.entries(config.functions)) {
22677
22701
  if (sourceFile === pattern || (0, import_minimatch.default)(sourceFile, pattern)) {
22702
+ const experimentalTriggers = fn.experimentalTriggers?.map(
22703
+ (trigger) => {
22704
+ if (trigger.type === "queue/v2beta") {
22705
+ return {
22706
+ ...trigger,
22707
+ consumer: sanitizeConsumerName(pattern)
22708
+ };
22709
+ }
22710
+ return trigger;
22711
+ }
22712
+ );
22678
22713
  return {
22679
22714
  architecture: fn.architecture,
22680
22715
  memory: fn.memory,
22681
22716
  maxDuration: fn.maxDuration,
22682
22717
  regions: fn.regions,
22683
- experimentalTriggers: fn.experimentalTriggers,
22718
+ experimentalTriggers,
22684
22719
  supportsCancellation: fn.supportsCancellation
22685
22720
  };
22686
22721
  }
@@ -23247,9 +23282,21 @@ var NO_OVERRIDE = {
23247
23282
  function spawnAsync(command, args, opts = {}) {
23248
23283
  return new Promise((resolve, reject) => {
23249
23284
  const stderrLogs = [];
23250
- opts = { stdio: "inherit", ...opts };
23285
+ const hasCustomStreams = opts.outputStream || opts.errorStream;
23286
+ if (hasCustomStreams) {
23287
+ opts = { ...opts, stdio: ["inherit", "pipe", "pipe"] };
23288
+ } else {
23289
+ opts = { stdio: "inherit", ...opts };
23290
+ }
23251
23291
  const child = (0, import_cross_spawn.default)(command, args, opts);
23252
- if (opts.stdio === "pipe" && child.stderr) {
23292
+ if (hasCustomStreams) {
23293
+ if (child.stdout && opts.outputStream) {
23294
+ child.stdout.pipe(opts.outputStream);
23295
+ }
23296
+ if (child.stderr && opts.errorStream) {
23297
+ child.stderr.pipe(opts.errorStream);
23298
+ }
23299
+ } else if (opts.stdio === "pipe" && child.stderr) {
23253
23300
  child.stderr.on("data", (data) => stderrLogs.push(data));
23254
23301
  }
23255
23302
  child.on("error", reject);
@@ -23261,7 +23308,7 @@ function spawnAsync(command, args, opts = {}) {
23261
23308
  reject(
23262
23309
  new NowBuildError({
23263
23310
  code: `BUILD_UTILS_SPAWN_${code || signal}`,
23264
- message: opts.stdio === "inherit" ? `${cmd} exited with ${code || signal}` : stderrLogs.map((line) => line.toString()).join("")
23311
+ message: opts.stdio === "inherit" || hasCustomStreams ? `${cmd} exited with ${code || signal}` : stderrLogs.map((line) => line.toString()).join("")
23265
23312
  })
23266
23313
  );
23267
23314
  });
@@ -23686,13 +23733,16 @@ function getInstallCommandForPackageManager(packageManager, args) {
23686
23733
  async function runInstallCommand({
23687
23734
  packageManager,
23688
23735
  args,
23689
- opts
23736
+ opts,
23737
+ output
23690
23738
  }) {
23691
23739
  const { commandArguments, prettyCommand } = getInstallCommandForPackageManager(packageManager, args);
23692
23740
  opts.prettyCommand = prettyCommand;
23693
23741
  if (process.env.NPM_ONLY_PRODUCTION) {
23694
23742
  commandArguments.push("--production");
23695
23743
  }
23744
+ opts.outputStream = output?.stdout;
23745
+ opts.errorStream = output?.stderr;
23696
23746
  await spawnAsync(packageManager, commandArguments, opts);
23697
23747
  }
23698
23748
  function initializeSet(set) {
@@ -23712,7 +23762,7 @@ var customInstallCommandSet;
23712
23762
  function resetCustomInstallCommandSet() {
23713
23763
  customInstallCommandSet = void 0;
23714
23764
  }
23715
- async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreatedAt) {
23765
+ async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreatedAt, output) {
23716
23766
  if (meta?.isDev) {
23717
23767
  debug("Skipping dependency installation because dev mode is enabled");
23718
23768
  return false;
@@ -23764,7 +23814,11 @@ async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreate
23764
23814
  }
23765
23815
  }
23766
23816
  const installTime = Date.now();
23767
- console.log("Installing dependencies...");
23817
+ if (output?.stdout) {
23818
+ output.stdout.write("Installing dependencies...\n");
23819
+ } else {
23820
+ console.log("Installing dependencies...");
23821
+ }
23768
23822
  debug(`Installing to ${destPath}`);
23769
23823
  const opts = { cwd: destPath, ...spawnOpts };
23770
23824
  const env = cloneEnv(opts.env || process.env);
@@ -23787,7 +23841,8 @@ async function runNpmInstall(destPath, args = [], spawnOpts, meta, projectCreate
23787
23841
  await runInstallCommand({
23788
23842
  packageManager: cliType,
23789
23843
  args,
23790
- opts
23844
+ opts,
23845
+ output
23791
23846
  });
23792
23847
  debug(`Install complete [${Date.now() - installTime}ms]`);
23793
23848
  return true;
@@ -24364,12 +24419,15 @@ function getPrefixedEnvVars({
24364
24419
  function serviceNameToEnvVar(name) {
24365
24420
  return `${name.replace(/-/g, "_").toUpperCase()}_URL`;
24366
24421
  }
24367
- function computeServiceUrl(deploymentUrl, routePrefix) {
24422
+ function computeServiceUrl(baseUrl, routePrefix, isOrigin) {
24423
+ if (!isOrigin) {
24424
+ baseUrl = `https://${baseUrl}`;
24425
+ }
24368
24426
  if (routePrefix === "/") {
24369
- return `https://${deploymentUrl}`;
24427
+ return baseUrl;
24370
24428
  }
24371
24429
  const normalizedPrefix = routePrefix.startsWith("/") ? routePrefix.slice(1) : routePrefix;
24372
- return `https://${deploymentUrl}/${normalizedPrefix}`;
24430
+ return `${baseUrl}/${normalizedPrefix}`;
24373
24431
  }
24374
24432
  function getFrameworkEnvPrefix(frameworkSlug, frameworkList) {
24375
24433
  if (!frameworkSlug)
@@ -24380,8 +24438,15 @@ function getFrameworkEnvPrefix(frameworkSlug, frameworkList) {
24380
24438
  return framework?.envPrefix;
24381
24439
  }
24382
24440
  function getServiceUrlEnvVars(options) {
24383
- const { services, frameworkList, currentEnv = {}, deploymentUrl } = options;
24384
- if (!deploymentUrl || !services || services.length === 0) {
24441
+ const {
24442
+ services,
24443
+ frameworkList,
24444
+ currentEnv = {},
24445
+ deploymentUrl,
24446
+ origin
24447
+ } = options;
24448
+ const baseUrl = origin || deploymentUrl;
24449
+ if (!baseUrl || !services || services.length === 0) {
24385
24450
  return {};
24386
24451
  }
24387
24452
  const envVars = {};
@@ -24397,7 +24462,11 @@ function getServiceUrlEnvVars(options) {
24397
24462
  continue;
24398
24463
  }
24399
24464
  const baseEnvVarName = serviceNameToEnvVar(service.name);
24400
- const absoluteUrl = computeServiceUrl(deploymentUrl, service.routePrefix);
24465
+ const absoluteUrl = computeServiceUrl(
24466
+ baseUrl,
24467
+ service.routePrefix,
24468
+ !!origin
24469
+ );
24401
24470
  if (!(baseEnvVarName in currentEnv)) {
24402
24471
  envVars[baseEnvVarName] = absoluteUrl;
24403
24472
  }
@@ -24567,7 +24636,7 @@ function hasProp(obj, key) {
24567
24636
  }
24568
24637
 
24569
24638
  // src/schemas.ts
24570
- var triggerEventSchema = {
24639
+ var triggerEventSchemaV1 = {
24571
24640
  type: "object",
24572
24641
  properties: {
24573
24642
  type: {
@@ -24602,6 +24671,40 @@ var triggerEventSchema = {
24602
24671
  required: ["type", "topic", "consumer"],
24603
24672
  additionalProperties: false
24604
24673
  };
24674
+ var triggerEventSchemaV2 = {
24675
+ type: "object",
24676
+ properties: {
24677
+ type: {
24678
+ type: "string",
24679
+ const: "queue/v2beta"
24680
+ },
24681
+ topic: {
24682
+ type: "string",
24683
+ minLength: 1
24684
+ },
24685
+ maxDeliveries: {
24686
+ type: "number",
24687
+ minimum: 1
24688
+ },
24689
+ retryAfterSeconds: {
24690
+ type: "number",
24691
+ exclusiveMinimum: 0
24692
+ },
24693
+ initialDelaySeconds: {
24694
+ type: "number",
24695
+ minimum: 0
24696
+ },
24697
+ maxConcurrency: {
24698
+ type: "number",
24699
+ minimum: 1
24700
+ }
24701
+ },
24702
+ required: ["type", "topic"],
24703
+ additionalProperties: false
24704
+ };
24705
+ var triggerEventSchema = {
24706
+ oneOf: [triggerEventSchemaV1, triggerEventSchemaV2]
24707
+ };
24605
24708
  var functionsSchema = {
24606
24709
  type: "object",
24607
24710
  minProperties: 1,
@@ -25078,6 +25181,7 @@ async function isPythonEntrypoint(file) {
25078
25181
  runPackageJsonScript,
25079
25182
  runPipInstall,
25080
25183
  runShellScript,
25184
+ sanitizeConsumerName,
25081
25185
  scanParentDirs,
25082
25186
  shouldServe,
25083
25187
  shouldUseExperimentalBackends,
package/dist/lambda.d.ts CHANGED
@@ -1,6 +1,23 @@
1
1
  /// <reference types="node" />
2
- import type { Config, Env, Files, FunctionFramework, TriggerEvent } from './types';
3
- export type { TriggerEvent };
2
+ import type { Config, Env, Files, FunctionFramework, TriggerEvent, TriggerEventInput } from './types';
3
+ export type { TriggerEvent, TriggerEventInput };
4
+ /**
5
+ * Encodes a function path into a valid consumer name using mnemonic escapes.
6
+ * For queue/v2beta triggers, the consumer name is derived from the function path.
7
+ * This encoding is collision-free (bijective/reversible).
8
+ *
9
+ * Encoding scheme:
10
+ * - `_` → `__` (escape character itself)
11
+ * - `/` → `_S` (slash)
12
+ * - `.` → `_D` (dot)
13
+ * - Other invalid chars → `_XX` (hex code)
14
+ *
15
+ * @example
16
+ * sanitizeConsumerName('api/test.js') // => 'api_Stest_Djs'
17
+ * sanitizeConsumerName('api/users/handler.ts') // => 'api_Susers_Shandler_Dts'
18
+ * sanitizeConsumerName('my_func.ts') // => 'my__func_Dts'
19
+ */
20
+ export declare function sanitizeConsumerName(functionPath: string): string;
4
21
  export type LambdaOptions = LambdaOptionsWithFiles | LambdaOptionsWithZipBuffer;
5
22
  export type LambdaExecutableRuntimeLanguages = 'rust' | 'go';
6
23
  export type LambdaArchitecture = 'x86_64' | 'arm64';
package/dist/lambda.js CHANGED
@@ -31,7 +31,8 @@ __export(lambda_exports, {
31
31
  Lambda: () => Lambda,
32
32
  createLambda: () => createLambda,
33
33
  createZip: () => createZip,
34
- getLambdaOptionsFromFunction: () => getLambdaOptionsFromFunction
34
+ getLambdaOptionsFromFunction: () => getLambdaOptionsFromFunction,
35
+ sanitizeConsumerName: () => sanitizeConsumerName
35
36
  });
36
37
  module.exports = __toCommonJS(lambda_exports);
37
38
  var import_assert = __toESM(require("assert"));
@@ -41,6 +42,23 @@ var import_minimatch = __toESM(require("minimatch"));
41
42
  var import_fs_extra = require("fs-extra");
42
43
  var import_download = require("./fs/download");
43
44
  var import_stream_to_buffer = __toESM(require("./fs/stream-to-buffer"));
45
+ function sanitizeConsumerName(functionPath) {
46
+ let result = "";
47
+ for (const char of functionPath) {
48
+ if (char === "_") {
49
+ result += "__";
50
+ } else if (char === "/") {
51
+ result += "_S";
52
+ } else if (char === ".") {
53
+ result += "_D";
54
+ } else if (/[A-Za-z0-9-]/.test(char)) {
55
+ result += char;
56
+ } else {
57
+ result += "_" + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0");
58
+ }
59
+ }
60
+ return result;
61
+ }
44
62
  function getDefaultLambdaArchitecture(architecture) {
45
63
  if (architecture) {
46
64
  return architecture;
@@ -162,8 +180,8 @@ class Lambda {
162
180
  `${prefix} is not an object`
163
181
  );
164
182
  (0, import_assert.default)(
165
- trigger.type === "queue/v1beta",
166
- `${prefix}.type must be "queue/v1beta"`
183
+ trigger.type === "queue/v1beta" || trigger.type === "queue/v2beta",
184
+ `${prefix}.type must be "queue/v1beta" or "queue/v2beta"`
167
185
  );
168
186
  (0, import_assert.default)(
169
187
  typeof trigger.topic === "string",
@@ -178,6 +196,12 @@ class Lambda {
178
196
  trigger.consumer.length > 0,
179
197
  `${prefix}.consumer cannot be empty`
180
198
  );
199
+ if (trigger.type === "queue/v2beta") {
200
+ (0, import_assert.default)(
201
+ experimentalTriggers.length === 1,
202
+ '"experimentalTriggers" can only have one item for queue/v2beta'
203
+ );
204
+ }
181
205
  if (trigger.maxDeliveries !== void 0) {
182
206
  (0, import_assert.default)(
183
207
  typeof trigger.maxDeliveries === "number",
@@ -318,12 +342,23 @@ async function getLambdaOptionsFromFunction({
318
342
  if (config?.functions) {
319
343
  for (const [pattern, fn] of Object.entries(config.functions)) {
320
344
  if (sourceFile === pattern || (0, import_minimatch.default)(sourceFile, pattern)) {
345
+ const experimentalTriggers = fn.experimentalTriggers?.map(
346
+ (trigger) => {
347
+ if (trigger.type === "queue/v2beta") {
348
+ return {
349
+ ...trigger,
350
+ consumer: sanitizeConsumerName(pattern)
351
+ };
352
+ }
353
+ return trigger;
354
+ }
355
+ );
321
356
  return {
322
357
  architecture: fn.architecture,
323
358
  memory: fn.memory,
324
359
  maxDuration: fn.maxDuration,
325
360
  regions: fn.regions,
326
- experimentalTriggers: fn.experimentalTriggers,
361
+ experimentalTriggers,
327
362
  supportsCancellation: fn.supportsCancellation
328
363
  };
329
364
  }
@@ -336,5 +371,6 @@ async function getLambdaOptionsFromFunction({
336
371
  Lambda,
337
372
  createLambda,
338
373
  createZip,
339
- getLambdaOptionsFromFunction
374
+ getLambdaOptionsFromFunction,
375
+ sanitizeConsumerName
340
376
  });
package/dist/schemas.d.ts CHANGED
@@ -42,39 +42,37 @@ export declare const functionsSchema: {
42
42
  experimentalTriggers: {
43
43
  type: string;
44
44
  items: {
45
- type: string;
46
- properties: {
47
- type: {
48
- type: string;
49
- const: string;
50
- };
51
- topic: {
52
- type: string;
53
- minLength: number;
54
- };
55
- consumer: {
56
- type: string;
57
- minLength: number;
58
- };
59
- maxDeliveries: {
60
- type: string;
61
- minimum: number;
62
- };
63
- retryAfterSeconds: {
64
- type: string;
65
- exclusiveMinimum: number;
66
- };
67
- initialDelaySeconds: {
68
- type: string;
69
- minimum: number;
70
- };
71
- maxConcurrency: {
72
- type: string;
73
- minimum: number;
45
+ oneOf: {
46
+ type: string;
47
+ properties: {
48
+ type: {
49
+ type: string;
50
+ const: string;
51
+ };
52
+ topic: {
53
+ type: string;
54
+ minLength: number;
55
+ };
56
+ maxDeliveries: {
57
+ type: string;
58
+ minimum: number;
59
+ };
60
+ retryAfterSeconds: {
61
+ type: string;
62
+ exclusiveMinimum: number;
63
+ };
64
+ initialDelaySeconds: {
65
+ type: string;
66
+ minimum: number;
67
+ };
68
+ maxConcurrency: {
69
+ type: string;
70
+ minimum: number;
71
+ };
74
72
  };
75
- };
76
- required: string[];
77
- additionalProperties: boolean;
73
+ required: string[];
74
+ additionalProperties: boolean;
75
+ }[];
78
76
  };
79
77
  };
80
78
  supportsCancellation: {
package/dist/schemas.js CHANGED
@@ -22,7 +22,7 @@ __export(schemas_exports, {
22
22
  functionsSchema: () => functionsSchema
23
23
  });
24
24
  module.exports = __toCommonJS(schemas_exports);
25
- const triggerEventSchema = {
25
+ const triggerEventSchemaV1 = {
26
26
  type: "object",
27
27
  properties: {
28
28
  type: {
@@ -57,6 +57,40 @@ const triggerEventSchema = {
57
57
  required: ["type", "topic", "consumer"],
58
58
  additionalProperties: false
59
59
  };
60
+ const triggerEventSchemaV2 = {
61
+ type: "object",
62
+ properties: {
63
+ type: {
64
+ type: "string",
65
+ const: "queue/v2beta"
66
+ },
67
+ topic: {
68
+ type: "string",
69
+ minLength: 1
70
+ },
71
+ maxDeliveries: {
72
+ type: "number",
73
+ minimum: 1
74
+ },
75
+ retryAfterSeconds: {
76
+ type: "number",
77
+ exclusiveMinimum: 0
78
+ },
79
+ initialDelaySeconds: {
80
+ type: "number",
81
+ minimum: 0
82
+ },
83
+ maxConcurrency: {
84
+ type: "number",
85
+ minimum: 1
86
+ }
87
+ },
88
+ required: ["type", "topic"],
89
+ additionalProperties: false
90
+ };
91
+ const triggerEventSchema = {
92
+ oneOf: [triggerEventSchemaV1, triggerEventSchemaV2]
93
+ };
60
94
  const functionsSchema = {
61
95
  type: "object",
62
96
  minProperties: 1,
package/dist/types.d.ts CHANGED
@@ -358,7 +358,7 @@ export interface BuilderFunctions {
358
358
  runtime?: string;
359
359
  includeFiles?: string;
360
360
  excludeFiles?: string;
361
- experimentalTriggers?: TriggerEvent[];
361
+ experimentalTriggers?: TriggerEventInput[];
362
362
  supportsCancellation?: boolean;
363
363
  };
364
364
  }
@@ -559,17 +559,9 @@ export interface Chain {
559
559
  */
560
560
  headers: Record<string, string>;
561
561
  }
562
- /**
563
- * Queue trigger event for Vercel's queue system.
564
- * Handles "queue/v1beta" events with queue-specific configuration.
565
- */
566
- export interface TriggerEvent {
567
- /** Event type - must be "queue/v1beta" (REQUIRED) */
568
- type: 'queue/v1beta';
562
+ interface TriggerEventBase {
569
563
  /** Name of the queue topic to consume from (REQUIRED) */
570
564
  topic: string;
571
- /** Name of the consumer group for this trigger (REQUIRED) */
572
- consumer: string;
573
565
  /**
574
566
  * Maximum number of delivery attempts for message processing (OPTIONAL)
575
567
  * This represents the total number of times a message can be delivered,
@@ -595,6 +587,40 @@ export interface TriggerEvent {
595
587
  */
596
588
  maxConcurrency?: number;
597
589
  }
590
+ /**
591
+ * Queue trigger input event for v1beta (from vercel.json config).
592
+ * Requires explicit consumer name.
593
+ */
594
+ export interface TriggerEventInputV1 extends TriggerEventBase {
595
+ /** Event type - must be "queue/v1beta" (REQUIRED) */
596
+ type: 'queue/v1beta';
597
+ /** Name of the consumer group for this trigger (REQUIRED) */
598
+ consumer: string;
599
+ }
600
+ /**
601
+ * Queue trigger input event for v2beta (from vercel.json config).
602
+ * Consumer name is implicitly derived from the function path.
603
+ * Only one trigger per function is allowed.
604
+ */
605
+ export interface TriggerEventInputV2 extends TriggerEventBase {
606
+ /** Event type - must be "queue/v2beta" (REQUIRED) */
607
+ type: 'queue/v2beta';
608
+ }
609
+ /**
610
+ * Queue trigger input event from vercel.json config.
611
+ * v1beta requires explicit consumer, v2beta derives consumer from function path.
612
+ */
613
+ export type TriggerEventInput = TriggerEventInputV1 | TriggerEventInputV2;
614
+ /**
615
+ * Processed queue trigger event for Lambda.
616
+ * Consumer is always present (explicitly provided for v1beta, derived for v2beta).
617
+ */
618
+ export interface TriggerEvent extends TriggerEventBase {
619
+ /** Event type */
620
+ type: 'queue/v1beta' | 'queue/v2beta';
621
+ /** Name of the consumer group for this trigger (always present in processed output) */
622
+ consumer: string;
623
+ }
598
624
  export type ServiceRuntime = 'node' | 'python' | 'go' | 'rust' | 'ruby';
599
625
  export type ServiceType = 'web' | 'cron' | 'worker';
600
626
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/build-utils",
3
- "version": "13.3.5",
3
+ "version": "13.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.js",
@@ -11,7 +11,7 @@
11
11
  "directory": "packages/now-build-utils"
12
12
  },
13
13
  "dependencies": {
14
- "@vercel/python-analysis": "0.4.0"
14
+ "@vercel/python-analysis": "0.4.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@iarna/toml": "2.2.3",
@@ -50,8 +50,8 @@
50
50
  "yazl": "2.5.1",
51
51
  "vitest": "2.0.1",
52
52
  "json5": "2.2.3",
53
- "@vercel/error-utils": "2.0.3",
54
- "@vercel/routing-utils": "5.3.2"
53
+ "@vercel/routing-utils": "5.3.2",
54
+ "@vercel/error-utils": "2.0.3"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "node build.mjs",