@vercel/build-utils 13.27.0 → 13.27.2

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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Vercel Functions have historically had a hard timeout limit of 900 seconds
3
+ * (15 minutes), which has long doubled as the client-side upper bound for
4
+ * `maxDuration` in `vercel.json` and zero-config function detection.
5
+ *
6
+ * The authoritative limit is enforced by server-side validation at deploy time.
7
+ * The CLI and `@vercel/build-utils` cannot know that limit at build time, so
8
+ * this client-side bound is intentionally coarse — it exists only to give fast
9
+ * local feedback on obviously-invalid values.
10
+ */
11
+ export declare const DEFAULT_MAX_DURATION_LIMIT = 900;
12
+ /**
13
+ * Internal env var used to skip the client-side `maxDuration` upper-bound check.
14
+ * When set to `'1'`, the client-side maximum ({@link DEFAULT_MAX_DURATION_LIMIT})
15
+ * is not applied and validation defers to the server, so the limit can be
16
+ * adjusted centrally without requiring users to upgrade their CLI. The lower
17
+ * bound and integer checks are always enforced regardless of this flag.
18
+ */
19
+ export declare const SKIP_MAX_DURATION_LIMIT_ENV = "VERCEL_CLI_SKIP_MAX_DURATION_LIMIT";
20
+ /**
21
+ * Returns the client-side upper bound for `maxDuration` in seconds, or
22
+ * `undefined` when the bound is skipped via {@link SKIP_MAX_DURATION_LIMIT_ENV}
23
+ * (server-side validation governs the real limit in that case). The lower bound
24
+ * and integer checks are always enforced by callers regardless of this flag.
25
+ */
26
+ export declare function getMaxDurationLimit(): number | undefined;
27
+ /**
28
+ * Returns the JSON Schema fragment used to validate a function's `maxDuration`
29
+ * in `vercel.json`. When the client-side upper bound is active the integer
30
+ * branch includes a `maximum`; when skipped via
31
+ * {@link SKIP_MAX_DURATION_LIMIT_ENV} the `maximum` is omitted so any positive
32
+ * integer passes schema validation and the server enforces the limit.
33
+ */
34
+ export declare function getMaxDurationSchema(): {
35
+ oneOf: ({
36
+ maximum?: number | undefined;
37
+ type: string;
38
+ minimum: number;
39
+ enum?: undefined;
40
+ } | {
41
+ type: string;
42
+ enum: string[];
43
+ })[];
44
+ };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var max_duration_exports = {};
20
+ __export(max_duration_exports, {
21
+ DEFAULT_MAX_DURATION_LIMIT: () => DEFAULT_MAX_DURATION_LIMIT,
22
+ SKIP_MAX_DURATION_LIMIT_ENV: () => SKIP_MAX_DURATION_LIMIT_ENV,
23
+ getMaxDurationLimit: () => getMaxDurationLimit,
24
+ getMaxDurationSchema: () => getMaxDurationSchema
25
+ });
26
+ module.exports = __toCommonJS(max_duration_exports);
27
+ const DEFAULT_MAX_DURATION_LIMIT = 900;
28
+ const SKIP_MAX_DURATION_LIMIT_ENV = "VERCEL_CLI_SKIP_MAX_DURATION_LIMIT";
29
+ function getMaxDurationLimit() {
30
+ if (process.env[SKIP_MAX_DURATION_LIMIT_ENV] === "1") {
31
+ return void 0;
32
+ }
33
+ return DEFAULT_MAX_DURATION_LIMIT;
34
+ }
35
+ function getMaxDurationSchema() {
36
+ const limit = getMaxDurationLimit();
37
+ return {
38
+ oneOf: [
39
+ {
40
+ type: "integer",
41
+ minimum: 1,
42
+ ...limit !== void 0 ? { maximum: limit } : {}
43
+ },
44
+ { type: "string", enum: ["max"] }
45
+ ]
46
+ };
47
+ }
48
+ // Annotate the CommonJS export names for ESM import in node:
49
+ 0 && (module.exports = {
50
+ DEFAULT_MAX_DURATION_LIMIT,
51
+ SKIP_MAX_DURATION_LIMIT_ENV,
52
+ getMaxDurationLimit,
53
+ getMaxDurationSchema
54
+ });
@@ -1,14 +1,15 @@
1
1
  import type FileFsRef from './file-fs-ref';
2
2
  /**
3
3
  * Check if a Node.js/TypeScript file is a valid API entrypoint by detecting
4
- * export patterns that correspond to supported handler shapes:
5
- * - Default function export (req, res handler)
6
- * - Named HTTP method exports (GET, POST, etc.)
7
- * - Fetch export
8
- * - module.exports / exports assignments
4
+ * the handler export shapes supported by `@vercel/node`:
5
+ * - Default export (`(req, res)` handler, Web handler, or object of handlers)
6
+ * - Named HTTP method exports (`GET`, `POST`, ) or a `fetch` export
7
+ * - `module.exports = <fn>`
8
+ * - A server that calls `.listen()`
9
9
  *
10
- * Returns `true` on error as a safe default — if we can't read the file,
11
- * let the existing build pipeline handle it.
10
+ * Returns `true` on error as a safe default — if we can't read or confidently
11
+ * analyze the file, let the existing build pipeline handle it rather than risk
12
+ * dropping a real Vercel Function.
12
13
  */
13
14
  export declare function isNodeEntrypoint(file: FileFsRef | {
14
15
  fsPath?: string;
@@ -32,26 +32,48 @@ __export(node_entrypoint_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(node_entrypoint_exports);
34
34
  var import_fs = __toESM(require("fs"));
35
+ var import_es_module_lexer = require("es-module-lexer");
36
+ var import_cjs_module_lexer = require("cjs-module-lexer");
35
37
  var import_debug = __toESM(require("./debug"));
36
- const HTTP_METHODS = "GET|HEAD|OPTIONS|POST|PUT|DELETE|PATCH";
37
- const VALID_EXPORT_PATTERNS = [
38
- // ESM default export: export default function handler() {}
39
- /export\s+default\b/,
40
- // CJS default export: module.exports = (req, res) => {}
41
- /module\.exports\s*=/,
42
- // ESM named HTTP method or fetch exports: export function GET() {}
43
- new RegExp(
44
- `export\\s+(?:async\\s+)?(?:function|const|let|var)\\s+(?:${HTTP_METHODS}|fetch)\\b`
45
- ),
46
- // ESM re-exports: export { GET } or export { handler as default }
47
- new RegExp(`export\\s*\\{[^}]*\\b(?:${HTTP_METHODS}|fetch|default)\\b`),
48
- // CJS named exports: exports.GET = ... or module.exports.GET = ...
49
- new RegExp(`(?:module\\.)?exports\\.(?:${HTTP_METHODS}|fetch|default)\\s*=`),
50
- // Server handler: http.createServer(...).listen() with no exports
51
- /http\.createServer\s*\(/
38
+ var import_strip_comments_and_literals = require("./strip-comments-and-literals");
39
+ const HANDLER_EXPORTS = /* @__PURE__ */ new Set([
40
+ "GET",
41
+ "HEAD",
42
+ "OPTIONS",
43
+ "POST",
44
+ "PUT",
45
+ "DELETE",
46
+ "PATCH",
47
+ "fetch",
48
+ "default"
49
+ ]);
50
+ const EXTRA_HANDLER_PATTERNS = [
51
+ /\bmodule\.exports\s*=(?!=)/,
52
+ /\.listen\s*\(/,
53
+ /\bexport\s*=(?!=)/,
54
+ /\bexport\s*\*\s*from\b/
52
55
  ];
53
- function stripComments(content) {
54
- return content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
56
+ async function getHandlerExportNames(content) {
57
+ const names = /* @__PURE__ */ new Set();
58
+ let esmUnparsed = false;
59
+ await import_es_module_lexer.init;
60
+ try {
61
+ const [, exports] = (0, import_es_module_lexer.parse)(content);
62
+ for (const { n } of exports) {
63
+ if (n)
64
+ names.add(n);
65
+ }
66
+ } catch {
67
+ esmUnparsed = true;
68
+ }
69
+ await (0, import_cjs_module_lexer.init)();
70
+ try {
71
+ for (const name of (0, import_cjs_module_lexer.parse)(content).exports) {
72
+ names.add(name);
73
+ }
74
+ } catch {
75
+ }
76
+ return { names, esmUnparsed };
55
77
  }
56
78
  async function isNodeEntrypoint(file) {
57
79
  try {
@@ -61,8 +83,15 @@ async function isNodeEntrypoint(file) {
61
83
  const content = await import_fs.default.promises.readFile(fsPath, "utf-8");
62
84
  if (!content.trim())
63
85
  return false;
64
- const stripped = stripComments(content);
65
- return VALID_EXPORT_PATTERNS.some((pattern) => pattern.test(stripped));
86
+ const { names, esmUnparsed } = await getHandlerExportNames(content);
87
+ for (const name of names) {
88
+ if (HANDLER_EXPORTS.has(name))
89
+ return true;
90
+ }
91
+ if (esmUnparsed)
92
+ return true;
93
+ const code = (0, import_strip_comments_and_literals.stripCommentsAndLiterals)(content);
94
+ return EXTRA_HANDLER_PATTERNS.some((pattern) => pattern.test(code));
66
95
  } catch (err) {
67
96
  (0, import_debug.default)(`Failed to check Node.js entrypoint: ${err}`);
68
97
  return true;
package/dist/schemas.d.ts CHANGED
@@ -22,15 +22,13 @@ export declare const functionsSchema: {
22
22
  };
23
23
  maxDuration: {
24
24
  oneOf: ({
25
+ maximum?: number | undefined;
25
26
  type: string;
26
27
  minimum: number;
27
- maximum: number;
28
28
  enum?: undefined;
29
29
  } | {
30
30
  type: string;
31
31
  enum: string[];
32
- minimum?: undefined;
33
- maximum?: undefined;
34
32
  })[];
35
33
  };
36
34
  regions: {
package/dist/schemas.js CHANGED
@@ -23,6 +23,7 @@ __export(schemas_exports, {
23
23
  packageManifestSchema: () => packageManifestSchema
24
24
  });
25
25
  module.exports = __toCommonJS(schemas_exports);
26
+ var import_max_duration = require("./max-duration");
26
27
  const triggerEventSchemaV1 = {
27
28
  type: "object",
28
29
  properties: {
@@ -114,12 +115,7 @@ const functionsSchema = {
114
115
  minimum: 128,
115
116
  maximum: 10240
116
117
  },
117
- maxDuration: {
118
- oneOf: [
119
- { type: "integer", minimum: 1, maximum: 900 },
120
- { type: "string", enum: ["max"] }
121
- ]
122
- },
118
+ maxDuration: (0, import_max_duration.getMaxDurationSchema)(),
123
119
  regions: {
124
120
  type: "array",
125
121
  items: {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Replace comments and string / template literals with single spaces, leaving
3
+ * only the code structure behind.
4
+ *
5
+ * Comments and literals are matched by one alternation. Because a literal is
6
+ * matched as a single token, the engine consumes it whole before it can look
7
+ * inside — so a comment-like sequence that appears inside a string (for
8
+ * example an `Accept` header value containing a wildcard media range, or a URL
9
+ * with `//`) is never treated as the start of a comment. Literals are blanked
10
+ * rather than preserved so their contents cannot be misread as code either.
11
+ *
12
+ * This is intentionally not aware of regex literals: an unescaped `/` cannot
13
+ * appear inside a regex body, so a comment-opening slash-star sequence cannot
14
+ * occur there, and callers only test the result for plain code-level tokens.
15
+ */
16
+ export declare function stripCommentsAndLiterals(content: string): string;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var strip_comments_and_literals_exports = {};
20
+ __export(strip_comments_and_literals_exports, {
21
+ stripCommentsAndLiterals: () => stripCommentsAndLiterals
22
+ });
23
+ module.exports = __toCommonJS(strip_comments_and_literals_exports);
24
+ const BLOCK_COMMENT = /\/\*[\s\S]*?(?:\*\/|$)/;
25
+ const LINE_COMMENT = /\/\/[^\n]*/;
26
+ const DOUBLE_QUOTED = /"(?:\\[\s\S]|[^"\\])*"?/;
27
+ const SINGLE_QUOTED = /'(?:\\[\s\S]|[^'\\])*'?/;
28
+ const TEMPLATE_LITERAL = /`(?:\\[\s\S]|[^`\\])*`?/;
29
+ const COMMENT_OR_LITERAL = new RegExp(
30
+ [BLOCK_COMMENT, LINE_COMMENT, DOUBLE_QUOTED, SINGLE_QUOTED, TEMPLATE_LITERAL].map((pattern) => pattern.source).join("|"),
31
+ "g"
32
+ );
33
+ function stripCommentsAndLiterals(content) {
34
+ return content.replace(COMMENT_OR_LITERAL, " ");
35
+ }
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ stripCommentsAndLiterals
39
+ });
package/dist/types.d.ts CHANGED
@@ -513,7 +513,8 @@ export interface ServiceRefEnvVar {
513
513
  }
514
514
  export type EnvVar = ServiceRefEnvVar;
515
515
  export type EnvVars = Record<string, EnvVar>;
516
- export interface Service {
516
+ export interface ExperimentalService {
517
+ schema: 'experimentalServices';
517
518
  name: string;
518
519
  type: ServiceType;
519
520
  trigger?: JobTrigger;
@@ -534,6 +535,36 @@ export interface Service {
534
535
  topics?: ServiceTopics;
535
536
  env?: EnvVars;
536
537
  }
538
+ export interface ExperimentalServiceV2 {
539
+ schema: 'experimentalServicesV2';
540
+ name: string;
541
+ /** Path to the service root, relative to the project root. */
542
+ root: string;
543
+ framework?: string;
544
+ runtime?: string;
545
+ /** Resolved entrypoint, relative to the service root. */
546
+ entrypoint?: string;
547
+ /** Builder selected by the resolver. */
548
+ builder: Builder;
549
+ installCommand?: string;
550
+ buildCommand?: string;
551
+ devCommand?: string;
552
+ ignoreCommand?: string;
553
+ outputDirectory?: string;
554
+ /** Caller-side bindings to other services. */
555
+ bindings?: ExperimentalServiceV2Binding[];
556
+ /** Function configuration scoped to this service. */
557
+ functions?: BuilderFunctions;
558
+ headers?: Header[];
559
+ redirects?: Redirect[];
560
+ rewrites?: Rewrite[];
561
+ routes?: Route[];
562
+ cleanUrls?: boolean;
563
+ trailingSlash?: boolean;
564
+ }
565
+ export type Service = ExperimentalService | ExperimentalServiceV2;
566
+ export declare function isExperimentalService(service: Service): service is ExperimentalService;
567
+ export declare function isExperimentalServiceV2(service: Service): service is ExperimentalServiceV2;
537
568
  export declare function getServiceQueueTopicConfigs(config: {
538
569
  type?: ServiceType;
539
570
  topics?: ServiceTopics;
package/dist/types.js CHANGED
@@ -25,6 +25,8 @@ __export(types_exports, {
25
25
  getReportedServiceType: () => getReportedServiceType,
26
26
  getServiceQueueTopicConfigs: () => getServiceQueueTopicConfigs,
27
27
  getServiceQueueTopics: () => getServiceQueueTopics,
28
+ isExperimentalService: () => isExperimentalService,
29
+ isExperimentalServiceV2: () => isExperimentalServiceV2,
28
30
  isQueueBackedService: () => isQueueBackedService,
29
31
  isQueueTriggeredService: () => isQueueTriggeredService,
30
32
  isScheduleTriggeredService: () => isScheduleTriggeredService,
@@ -56,6 +58,12 @@ class NodeVersion extends Version {
56
58
  class BunVersion extends Version {
57
59
  }
58
60
  const JOB_TRIGGERS = ["queue", "schedule", "workflow"];
61
+ function isExperimentalService(service) {
62
+ return service.schema === "experimentalServices";
63
+ }
64
+ function isExperimentalServiceV2(service) {
65
+ return service.schema === "experimentalServicesV2";
66
+ }
59
67
  function getServiceQueueTopicConfigs(config) {
60
68
  if (Array.isArray(config.topics) && config.topics.length > 0) {
61
69
  return typeof config.topics[0] === "string" ? config.topics.map((topic) => ({ topic })) : config.topics;
@@ -106,6 +114,8 @@ function getReportedServiceType(service) {
106
114
  getReportedServiceType,
107
115
  getServiceQueueTopicConfigs,
108
116
  getServiceQueueTopics,
117
+ isExperimentalService,
118
+ isExperimentalServiceV2,
109
119
  isQueueBackedService,
110
120
  isQueueTriggeredService,
111
121
  isScheduleTriggeredService,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/build-utils",
3
- "version": "13.27.0",
3
+ "version": "13.27.2",
4
4
  "license": "Apache-2.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.js",