@vercel/fs-detectors 7.6.1 → 7.7.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.
@@ -27,6 +27,13 @@ export interface ProxyConfig {
27
27
  export interface Options {
28
28
  tag?: string;
29
29
  functions?: BuilderFunctions;
30
+ /**
31
+ * Source files built by a builder that detection does not create itself
32
+ * (e.g. schedule entrypoints resolved by the CLI). Detection neither builds
33
+ * them as `api/` functions nor serves them as static files, and `functions`
34
+ * patterns that match them count as used.
35
+ */
36
+ reservedFunctionFiles?: string[];
30
37
  ignoreBuildScript?: boolean;
31
38
  projectSettings?: ProjectSettings;
32
39
  cleanUrls?: boolean;
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -26,6 +29,272 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
29
  mod
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../../internals/runtimes/dist/constants.js
34
+ var require_constants = __commonJS({
35
+ "../../internals/runtimes/dist/constants.js"(exports, module2) {
36
+ "use strict";
37
+ var __defProp2 = Object.defineProperty;
38
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
39
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
40
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
41
+ var __export2 = (target, all) => {
42
+ for (var name in all)
43
+ __defProp2(target, name, { get: all[name], enumerable: true });
44
+ };
45
+ var __copyProps2 = (to, from, except, desc) => {
46
+ if (from && typeof from === "object" || typeof from === "function") {
47
+ for (let key of __getOwnPropNames2(from))
48
+ if (!__hasOwnProp2.call(to, key) && key !== except)
49
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
50
+ }
51
+ return to;
52
+ };
53
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
54
+ var constants_exports = {};
55
+ __export2(constants_exports, {
56
+ BUILDER_RUNTIMES: () => BUILDER_RUNTIMES,
57
+ CONTAINER_ENTRYPOINT_CANDIDATES: () => CONTAINER_ENTRYPOINT_CANDIDATES,
58
+ ENTRYPOINT_EXTENSIONS: () => ENTRYPOINT_EXTENSIONS2,
59
+ ROUTE_OWNING_BUILDERS: () => ROUTE_OWNING_BUILDERS,
60
+ RUNTIME_BUILDERS: () => RUNTIME_BUILDERS,
61
+ RUNTIME_MANIFESTS: () => RUNTIME_MANIFESTS,
62
+ STATIC_BUILDERS: () => STATIC_BUILDERS
63
+ });
64
+ module2.exports = __toCommonJS2(constants_exports);
65
+ var RUNTIME_BUILDERS = {
66
+ node: "@vercel/backends",
67
+ python: "@vercel/python",
68
+ go: "@vercel/go",
69
+ rust: "@vercel/rust",
70
+ ruby: "@vercel/ruby",
71
+ container: "@vercel/container"
72
+ };
73
+ var RUNTIME_MANIFESTS = {
74
+ node: ["package.json"],
75
+ python: [
76
+ "pyproject.toml",
77
+ "requirements.txt",
78
+ "Pipfile",
79
+ "pylock.yml",
80
+ "uv.lock",
81
+ "setup.py"
82
+ ],
83
+ go: ["go.mod"],
84
+ ruby: ["Gemfile"],
85
+ rust: ["Cargo.toml"]
86
+ };
87
+ var ENTRYPOINT_EXTENSIONS2 = {
88
+ ".ts": "node",
89
+ ".mts": "node",
90
+ ".js": "node",
91
+ ".mjs": "node",
92
+ ".cjs": "node",
93
+ ".py": "python",
94
+ ".go": "go",
95
+ ".rs": "rust",
96
+ ".rb": "ruby",
97
+ ".ru": "ruby"
98
+ };
99
+ var BUILDER_RUNTIMES = Object.fromEntries(
100
+ Object.entries(RUNTIME_BUILDERS).map(([runtime, builder]) => [
101
+ builder,
102
+ runtime
103
+ ])
104
+ );
105
+ var STATIC_BUILDERS = /* @__PURE__ */ new Set([
106
+ "@vercel/static-build",
107
+ "@vercel/static"
108
+ ]);
109
+ var ROUTE_OWNING_BUILDERS = /* @__PURE__ */ new Set([
110
+ "@vercel/next",
111
+ "@vercel/backends"
112
+ ]);
113
+ var CONTAINER_ENTRYPOINT_CANDIDATES = [
114
+ "Dockerfile.vercel",
115
+ "Containerfile.vercel",
116
+ "Dockerfile",
117
+ "Containerfile"
118
+ ];
119
+ }
120
+ });
121
+
122
+ // ../../internals/runtimes/dist/entrypoint.js
123
+ var require_entrypoint = __commonJS({
124
+ "../../internals/runtimes/dist/entrypoint.js"(exports, module2) {
125
+ "use strict";
126
+ var __defProp2 = Object.defineProperty;
127
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
128
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
129
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
130
+ var __export2 = (target, all) => {
131
+ for (var name in all)
132
+ __defProp2(target, name, { get: all[name], enumerable: true });
133
+ };
134
+ var __copyProps2 = (to, from, except, desc) => {
135
+ if (from && typeof from === "object" || typeof from === "function") {
136
+ for (let key of __getOwnPropNames2(from))
137
+ if (!__hasOwnProp2.call(to, key) && key !== except)
138
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
139
+ }
140
+ return to;
141
+ };
142
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
143
+ var entrypoint_exports = {};
144
+ __export2(entrypoint_exports, {
145
+ SCHEDULE_ENTRYPOINT_FORMS_HINT: () => SCHEDULE_ENTRYPOINT_FORMS_HINT,
146
+ isDockerfileEntrypoint: () => isDockerfileEntrypoint,
147
+ parsePyModuleAttrEntrypoint: () => parsePyModuleAttrEntrypoint2,
148
+ parseScheduleEntrypoint: () => parseScheduleEntrypoint,
149
+ stripTrailingSlash: () => stripTrailingSlash
150
+ });
151
+ module2.exports = __toCommonJS2(entrypoint_exports);
152
+ var import_path2 = require("path");
153
+ var import_constants2 = require_constants();
154
+ var PYTHON_MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
155
+ var NODE_FILE_ENTRYPOINT_RE = /^(?!\/)(?!.*(?:^|\/)\.\.?(?:\/|$))(?:[\w@+.-]+\/)*[\w@+.-]+\.[cm]?[jt]s$/;
156
+ var NODE_DECLARATION_ENTRYPOINT_RE = /\.d\.[cm]?ts$/;
157
+ function parsePyModuleAttrEntrypoint2(entrypoint) {
158
+ const match = PYTHON_MODULE_ATTR_RE.exec(entrypoint);
159
+ if (!match)
160
+ return null;
161
+ const [, modulePart, attrName] = match;
162
+ if (!modulePart || !attrName)
163
+ return null;
164
+ return {
165
+ attrName,
166
+ filePath: modulePart.replace(/\./g, "/") + ".py",
167
+ moduleName: modulePart
168
+ };
169
+ }
170
+ var SCHEDULE_ENTRYPOINT_FORMS_HINT = 'Use a source file path like "jobs/nightly.ts" or a Python "module.path:function" reference.';
171
+ function parseScheduleEntrypoint(entrypoint) {
172
+ const python = parsePyModuleAttrEntrypoint2(entrypoint);
173
+ if (python) {
174
+ return { runtime: "python", ...python };
175
+ }
176
+ if (NODE_FILE_ENTRYPOINT_RE.test(entrypoint) && !NODE_DECLARATION_ENTRYPOINT_RE.test(entrypoint)) {
177
+ return { runtime: "node", filePath: entrypoint };
178
+ }
179
+ return null;
180
+ }
181
+ var CONTAINER_ENTRYPOINT_BASENAMES = new Set(
182
+ import_constants2.CONTAINER_ENTRYPOINT_CANDIDATES.map((name) => name.toLowerCase())
183
+ );
184
+ function isDockerfileEntrypoint(entrypoint) {
185
+ return CONTAINER_ENTRYPOINT_BASENAMES.has(
186
+ import_path2.posix.basename(entrypoint).toLowerCase()
187
+ );
188
+ }
189
+ function stripTrailingSlash(p) {
190
+ const stripped = p.replace(/\/+$/, "");
191
+ return stripped === "" ? "." : stripped;
192
+ }
193
+ }
194
+ });
195
+
196
+ // ../../internals/runtimes/dist/framework.js
197
+ var require_framework = __commonJS({
198
+ "../../internals/runtimes/dist/framework.js"(exports, module2) {
199
+ "use strict";
200
+ var __defProp2 = Object.defineProperty;
201
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
202
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
203
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
204
+ var __export2 = (target, all) => {
205
+ for (var name in all)
206
+ __defProp2(target, name, { get: all[name], enumerable: true });
207
+ };
208
+ var __copyProps2 = (to, from, except, desc) => {
209
+ if (from && typeof from === "object" || typeof from === "function") {
210
+ for (let key of __getOwnPropNames2(from))
211
+ if (!__hasOwnProp2.call(to, key) && key !== except)
212
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
213
+ }
214
+ return to;
215
+ };
216
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
217
+ var framework_exports = {};
218
+ __export2(framework_exports, {
219
+ DETECTION_FRAMEWORKS: () => DETECTION_FRAMEWORKS,
220
+ filterFrameworksByRuntime: () => filterFrameworksByRuntime,
221
+ frameworksBySlug: () => frameworksBySlug,
222
+ inferBuilderRuntimeFromFramework: () => inferBuilderRuntimeFromFramework2,
223
+ inferRuntimeFromFramework: () => inferRuntimeFromFramework,
224
+ inferServerRuntimeFromFramework: () => inferServerRuntimeFromFramework,
225
+ isBFFFramework: () => isBFFFramework,
226
+ isFrontendFramework: () => isFrontendFramework
227
+ });
228
+ module2.exports = __toCommonJS2(framework_exports);
229
+ var import_build_utils3 = require("@vercel/build-utils");
230
+ var import_frameworks2 = require("@vercel/frameworks");
231
+ var import_constants2 = require_constants();
232
+ var frameworksBySlug = new Map(
233
+ import_frameworks2.frameworkList.flatMap((f) => typeof f.slug === "string" ? [[f.slug, f]] : [])
234
+ );
235
+ var DETECTION_FRAMEWORKS = import_frameworks2.frameworkList.filter(
236
+ (framework) => !framework.experimental || framework.runtimeFramework
237
+ );
238
+ function inferRuntimeFromFramework(framework) {
239
+ if (!framework)
240
+ return void 0;
241
+ if (framework in import_constants2.RUNTIME_BUILDERS)
242
+ return framework;
243
+ if ((0, import_build_utils3.isPythonFramework)(framework))
244
+ return "python";
245
+ if ((0, import_build_utils3.isBackendFramework)(framework))
246
+ return "node";
247
+ return void 0;
248
+ }
249
+ var STATIC_BUILDER_RUNTIMES = {
250
+ hugo: "go",
251
+ jekyll: "ruby",
252
+ middleman: "ruby",
253
+ zola: "rust"
254
+ };
255
+ function inferServerRuntimeFromFramework(framework) {
256
+ if (!framework || framework === "services")
257
+ return void 0;
258
+ const builder = frameworksBySlug.get(framework)?.useRuntime?.use;
259
+ if (!builder)
260
+ return void 0;
261
+ return import_constants2.BUILDER_RUNTIMES[builder] ?? "node";
262
+ }
263
+ function inferBuilderRuntimeFromFramework2(framework) {
264
+ if (!framework || framework === "container" || framework === "services")
265
+ return void 0;
266
+ const f = frameworksBySlug.get(framework);
267
+ const declaredBuilder = f?.useRuntime?.use;
268
+ if (declaredBuilder)
269
+ return import_constants2.BUILDER_RUNTIMES[declaredBuilder];
270
+ const detectorItems = [
271
+ ...f?.detectors?.every ?? [],
272
+ ...f?.detectors?.some ?? []
273
+ ];
274
+ return STATIC_BUILDER_RUNTIMES[framework] ?? (detectorItems.some((d) => d.matchPackage !== void 0) ? "node" : void 0);
275
+ }
276
+ function isFrontendFramework(framework) {
277
+ return !!framework && !inferRuntimeFromFramework(framework);
278
+ }
279
+ var BFF_FRAMEWORKS = /* @__PURE__ */ new Set([
280
+ "nextjs",
281
+ "nuxtjs",
282
+ "sveltekit",
283
+ "remix",
284
+ "solidstart"
285
+ ]);
286
+ function isBFFFramework(framework) {
287
+ return !!framework && BFF_FRAMEWORKS.has(framework);
288
+ }
289
+ function filterFrameworksByRuntime(frameworks, runtime) {
290
+ if (!runtime)
291
+ return [...frameworks];
292
+ return frameworks.filter((f) => inferRuntimeFromFramework(f.slug) === runtime);
293
+ }
294
+ }
295
+ });
296
+
297
+ // src/detect-builders.ts
29
298
  var detect_builders_exports = {};
30
299
  __export(detect_builders_exports, {
31
300
  REGEX_MIDDLEWARE_FILES: () => REGEX_MIDDLEWARE_FILES,
@@ -43,17 +312,53 @@ __export(detect_builders_exports, {
43
312
  });
44
313
  module.exports = __toCommonJS(detect_builders_exports);
45
314
  var import_minimatch = __toESM(require("minimatch"));
315
+ var import_entrypoint = __toESM(require_entrypoint());
46
316
  var import_semver = require("semver");
47
317
  var import_path = require("path");
48
318
  var import_frameworks = require("@vercel/frameworks");
49
- var import_framework = require("@vercel-internals/runtimes/framework");
50
- var import_constants = require("@vercel-internals/runtimes/constants");
51
- var import_is_official_runtime = require("./is-official-runtime");
319
+ var import_framework = __toESM(require_framework());
320
+ var import_constants = __toESM(require_constants());
321
+
322
+ // src/is-official-runtime.ts
323
+ var isOfficialRuntime = (desired, name) => {
324
+ if (typeof name !== "string") {
325
+ return false;
326
+ }
327
+ return name === `@vercel/${desired}` || name === `@now/${desired}` || name.startsWith(`@vercel/${desired}@`) || name.startsWith(`@now/${desired}@`);
328
+ };
329
+
330
+ // src/detect-builders.ts
331
+ var import_build_utils2 = require("@vercel/build-utils");
332
+
333
+ // src/python.ts
334
+ var import_fs = __toESM(require("fs"));
52
335
  var import_build_utils = require("@vercel/build-utils");
53
- var import_python = require("./python");
54
- var import_buildpack_runtimes = require("./buildpack-runtimes");
55
- const REGEX_MIDDLEWARE_FILES = "middleware.[jt]s";
56
- const VERCEL_PLATFORM_FILES = [
336
+ var import_python_analysis = require("@vercel/python-analysis");
337
+ async function isPythonEntrypoint(fsPath) {
338
+ try {
339
+ const content = await import_fs.default.promises.readFile(fsPath, "utf-8");
340
+ return await (0, import_python_analysis.findAppOrHandler)(content) !== null;
341
+ } catch (err) {
342
+ (0, import_build_utils.debug)(`Failed to check Python entrypoint: ${err}`);
343
+ return false;
344
+ }
345
+ }
346
+
347
+ // src/buildpack-runtimes.ts
348
+ var BUILDPACK_PROJECT_MARKERS = /* @__PURE__ */ new Map([["ruby", ["Gemfile"]]]);
349
+ function isBuildpackRuntimeEnabled(runtime) {
350
+ return process.env[`VERCEL_EXPERIMENTAL_BUILDPACK_${runtime.toUpperCase()}`] === "1";
351
+ }
352
+ function toBuildpackRuntime(runtime) {
353
+ if (!runtime || !BUILDPACK_PROJECT_MARKERS.has(runtime) || !isBuildpackRuntimeEnabled(runtime)) {
354
+ return void 0;
355
+ }
356
+ return runtime;
357
+ }
358
+
359
+ // src/detect-builders.ts
360
+ var REGEX_MIDDLEWARE_FILES = "middleware.[jt]s";
361
+ var VERCEL_PLATFORM_FILES = [
57
362
  "api/**",
58
363
  "node_modules/**",
59
364
  REGEX_MIDDLEWARE_FILES,
@@ -66,7 +371,7 @@ const VERCEL_PLATFORM_FILES = [
66
371
  ".gitignore",
67
372
  "README.md"
68
373
  ];
69
- const REGEX_VERCEL_PLATFORM_FILES = VERCEL_PLATFORM_FILES.join(",");
374
+ var REGEX_VERCEL_PLATFORM_FILES = VERCEL_PLATFORM_FILES.join(",");
70
375
  function escapeMinimatchPath(path) {
71
376
  return path.replace(/([\\,*?[\]{}()!+@])/g, "\\$1");
72
377
  }
@@ -76,8 +381,8 @@ function getStaticFilesPattern(additionalExclusions = []) {
76
381
  ...additionalExclusions.map(escapeMinimatchPath)
77
382
  ].join(",")}}`;
78
383
  }
79
- const REGEX_NON_VERCEL_PLATFORM_FILES = getStaticFilesPattern();
80
- const builderToFrameworks = (() => {
384
+ var REGEX_NON_VERCEL_PLATFORM_FILES = getStaticFilesPattern();
385
+ var builderToFrameworks = (() => {
81
386
  const map = /* @__PURE__ */ new Map();
82
387
  for (const f of import_frameworks.frameworkList) {
83
388
  if (!f.useRuntime?.use)
@@ -110,7 +415,7 @@ function detectApiDirectory(builders) {
110
415
  }
111
416
  function getPublicBuilder(builders) {
112
417
  for (const builder of builders) {
113
- if (typeof builder.src === "string" && (0, import_is_official_runtime.isOfficialRuntime)("static", builder.use) && /^.*\/\*\*\/\*$/.test(builder.src) && builder.config?.zeroConfig === true) {
418
+ if (typeof builder.src === "string" && isOfficialRuntime("static", builder.use) && /^.*\/\*\*\/\*$/.test(builder.src) && builder.config?.zeroConfig === true) {
114
419
  return builder;
115
420
  }
116
421
  }
@@ -160,6 +465,13 @@ async function detectBuilders(files, pkg, options = {}) {
160
465
  usedFunctions.add(key);
161
466
  };
162
467
  const absolutePathCache = /* @__PURE__ */ new Map();
468
+ const proxyFile = options.proxy ? parseProxyEntrypoint(options.proxy.entrypoint).file : null;
469
+ const reservedFunctionFiles = options.reservedFunctionFiles ?? [];
470
+ const claimedFiles = /* @__PURE__ */ new Set([
471
+ ...proxyFile !== null ? [proxyFile] : [],
472
+ ...reservedFunctionFiles
473
+ ]);
474
+ const hasReservedFunctions = reservedFunctionFiles.length > 0;
163
475
  const { buildCommand, outputDirectory } = projectSettings;
164
476
  const frameworkConfig = import_framework.frameworksBySlug.get(framework || "");
165
477
  const rustStaticOutput = isRustStaticOutput(framework, projectSettings);
@@ -186,7 +498,7 @@ async function detectBuilders(files, pkg, options = {}) {
186
498
  const apiRoutes = [];
187
499
  const dynamicRoutes = [];
188
500
  for (const fileName of sortedFiles) {
189
- if (fileName === options.proxy?.entrypoint) {
501
+ if (claimedFiles.has(fileName)) {
190
502
  continue;
191
503
  }
192
504
  const apiBuilder = await maybeGetApiBuilder(fileName, apiMatches, options);
@@ -250,7 +562,7 @@ async function detectBuilders(files, pkg, options = {}) {
250
562
  options
251
563
  );
252
564
  } else {
253
- if (pkg && !makeFrontendStatic && !apiBuilders.length && !options.ignoreBuildScript) {
565
+ if (pkg && !makeFrontendStatic && !apiBuilders.length && !hasReservedFunctions && !options.ignoreBuildScript) {
254
566
  errors.push(getMissingBuildScriptError());
255
567
  return {
256
568
  errors,
@@ -271,19 +583,17 @@ async function detectBuilders(files, pkg, options = {}) {
271
583
  outputDirectory: usedOutputDirectory
272
584
  }
273
585
  };
274
- } else if (apiBuilders.length && hasNoneApiFiles) {
586
+ } else if ((apiBuilders.length || hasReservedFunctions) && hasNoneApiFiles) {
275
587
  frontendBuilder = {
276
588
  use: "@vercel/static",
277
- src: getStaticFilesPattern(
278
- options.proxy ? [options.proxy.entrypoint] : []
279
- ),
589
+ src: getStaticFilesPattern([...claimedFiles]),
280
590
  config: {
281
591
  zeroConfig: true
282
592
  }
283
593
  };
284
594
  }
285
595
  }
286
- if (options.proxy && frontendBuilder && (0, import_is_official_runtime.isOfficialRuntime)("next", frontendBuilder.use)) {
596
+ if (options.proxy && frontendBuilder && isOfficialRuntime("next", frontendBuilder.use)) {
287
597
  return {
288
598
  builders: null,
289
599
  errors: [
@@ -302,6 +612,7 @@ async function detectBuilders(files, pkg, options = {}) {
302
612
  const unusedFunctionError = checkUnusedFunctions(
303
613
  frontendBuilder,
304
614
  usedFunctions,
615
+ claimedFiles,
305
616
  options
306
617
  );
307
618
  if (unusedFunctionError) {
@@ -326,7 +637,7 @@ async function detectBuilders(files, pkg, options = {}) {
326
637
  builders.push(...apiBuilders);
327
638
  }
328
639
  if (frontendBuilder) {
329
- if ((0, import_is_official_runtime.isOfficialRuntime)("express", frontendBuilder?.use) || (0, import_is_official_runtime.isOfficialRuntime)("hono", frontendBuilder?.use) || (0, import_is_official_runtime.isOfficialRuntime)("python", frontendBuilder?.use) || (0, import_is_official_runtime.isOfficialRuntime)("go", frontendBuilder?.use) || (0, import_is_official_runtime.isOfficialRuntime)("backends", frontendBuilder?.use)) {
640
+ if (isOfficialRuntime("express", frontendBuilder?.use) || isOfficialRuntime("hono", frontendBuilder?.use) || isOfficialRuntime("python", frontendBuilder?.use) || isOfficialRuntime("go", frontendBuilder?.use) || isOfficialRuntime("backends", frontendBuilder?.use)) {
330
641
  builders.push({
331
642
  src: "public/**/*",
332
643
  use: "@vercel/static",
@@ -337,7 +648,7 @@ async function detectBuilders(files, pkg, options = {}) {
337
648
  });
338
649
  }
339
650
  builders.push(frontendBuilder);
340
- if (hasNextApiFiles && apiBuilders.some((b) => (0, import_is_official_runtime.isOfficialRuntime)("node", b.use))) {
651
+ if (hasNextApiFiles && apiBuilders.some((b) => isOfficialRuntime("node", b.use))) {
341
652
  warnings.push({
342
653
  code: "conflicting_files",
343
654
  message: "When using Next.js, it is recommended to place JavaScript Functions inside of the `pages/api` (provided by Next.js) directory instead of `api` (provided by Vercel). Other languages (Python, Go, etc) should still go in the `api` directory.",
@@ -386,7 +697,7 @@ async function maybeGetApiBuilder(fileName, apiMatches, options) {
386
697
  }
387
698
  if (fileName.endsWith(".py") && options.workPath) {
388
699
  const fsPath = (0, import_path.join)(options.workPath, fileName);
389
- const isEntrypoint = await (0, import_python.isPythonEntrypoint)(fsPath);
700
+ const isEntrypoint = await isPythonEntrypoint(fsPath);
390
701
  if (!isEntrypoint) {
391
702
  return null;
392
703
  }
@@ -394,7 +705,7 @@ async function maybeGetApiBuilder(fileName, apiMatches, options) {
394
705
  const nodeExtensions = [".js", ".mjs", ".ts", ".tsx"];
395
706
  if (fileName.startsWith("api/") && process.env.VERCEL_NODE_FILTER_ENTRYPOINTS === "1" && nodeExtensions.some((ext) => fileName.endsWith(ext)) && options.workPath) {
396
707
  const fsPath = (0, import_path.join)(options.workPath, fileName);
397
- const isEntrypoint = await (0, import_build_utils.isNodeEntrypoint)({ fsPath });
708
+ const isEntrypoint = await (0, import_build_utils2.isNodeEntrypoint)({ fsPath });
398
709
  if (!isEntrypoint) {
399
710
  return null;
400
711
  }
@@ -438,14 +749,25 @@ function getFunction(fileName, { functions = {} }) {
438
749
  const func = keys.find((key) => key === fileName || (0, import_minimatch.default)(fileName, key));
439
750
  return func ? { fnPattern: func, func: functions[func] } : { fnPattern: null, func: null };
440
751
  }
752
+ function parseProxyEntrypoint(entrypoint) {
753
+ if (process.env.VERCEL_PYTHON_PROXY) {
754
+ const parsed = (0, import_entrypoint.parsePyModuleAttrEntrypoint)(entrypoint);
755
+ if (parsed)
756
+ return { file: parsed.filePath, variable: parsed.attrName };
757
+ }
758
+ return { file: entrypoint };
759
+ }
441
760
  function getProxyBuilder(proxy, tag, functions = {}) {
442
- const { fnPattern, func } = getFunction(proxy.entrypoint, { functions });
761
+ const { file, variable } = parseProxyEntrypoint(proxy.entrypoint);
762
+ const isPython = (0, import_path.extname)(file) === ".py";
763
+ const { fnPattern, func } = getFunction(file, { functions });
443
764
  const runtime = func?.runtime;
444
765
  const config = {
445
766
  zeroConfig: true,
446
767
  middleware: true,
447
- ...!runtime ? { middlewareRuntime: "nodejs" } : {},
448
- ...proxy.matcher ? { middlewareMatcher: proxy.matcher } : {}
768
+ ...!runtime && !isPython ? { middlewareRuntime: "nodejs" } : {},
769
+ ...proxy.matcher ? { middlewareMatcher: proxy.matcher } : {},
770
+ ...variable ? { handlerFunction: variable } : {}
449
771
  };
450
772
  if (fnPattern && func) {
451
773
  config.functions = { [fnPattern]: func };
@@ -456,9 +778,10 @@ function getProxyBuilder(proxy, tag, functions = {}) {
456
778
  config.excludeFiles = func.excludeFiles;
457
779
  }
458
780
  }
781
+ const defaultRuntime = isPython ? `@vercel/python${tag ? `@${tag}` : ""}` : `@vercel/node${tag ? `@${tag}` : ""}`;
459
782
  return {
460
- src: proxy.entrypoint,
461
- use: runtime || `@vercel/node${tag ? `@${tag}` : ""}`,
783
+ src: file,
784
+ use: runtime || defaultRuntime,
462
785
  config
463
786
  };
464
787
  }
@@ -481,21 +804,29 @@ function validateProxyConfig(proxy) {
481
804
  if (typeof proxy !== "object" || typeof proxy.entrypoint !== "string" || !proxy.entrypoint) {
482
805
  return {
483
806
  code: "invalid_proxy",
484
- message: "The `proxy` property must contain an `entrypoint` string that references a `.js` or `.ts` file."
807
+ message: "The `proxy` property must contain an `entrypoint` string."
485
808
  };
486
809
  }
487
- const entrypoint = proxy.entrypoint;
488
- const segments = entrypoint.split("/");
489
- if (entrypoint.startsWith("/") || entrypoint.includes("\\") || segments.includes(".") || segments.includes("..") || /[?#\u0000-\u001f]/.test(entrypoint)) {
810
+ const { file, variable } = parseProxyEntrypoint(proxy.entrypoint);
811
+ const isPythonForm = (0, import_path.extname)(file) === ".py";
812
+ const isJsForm = /\.(?:js|ts)$/.test(file) && !file.endsWith(".d.ts");
813
+ const segments = file.split("/");
814
+ if (file.startsWith("/") || file.includes("\\") || segments.includes(".") || segments.includes("..") || /[?#\u0000-\u001f]/.test(file)) {
490
815
  return {
491
816
  code: "invalid_proxy_entrypoint",
492
817
  message: "The `proxy.entrypoint` path must be relative to the project root and cannot contain traversal, query, fragment, or control characters."
493
818
  };
494
819
  }
495
- if (!/\.(?:js|ts)$/.test(entrypoint) || entrypoint.endsWith(".d.ts")) {
820
+ if (!isPythonForm && !isJsForm) {
496
821
  return {
497
822
  code: "invalid_proxy_entrypoint",
498
- message: "The `proxy.entrypoint` path must end in `.js` or `.ts` and reference an executable file."
823
+ message: "The `proxy.entrypoint` must be a `.js` or `.ts` file path, or a Python `module:attr`."
824
+ };
825
+ }
826
+ if (isPythonForm && !variable) {
827
+ return {
828
+ code: "invalid_proxy_entrypoint",
829
+ message: "Python proxy entrypoints must use `module:attr` syntax, not a plain `.py` path."
499
830
  };
500
831
  }
501
832
  if (proxy.matcher !== void 0) {
@@ -520,10 +851,11 @@ function validateProxy(options, files, framework) {
520
851
  if (configError) {
521
852
  return configError;
522
853
  }
523
- if (!files.includes(proxy.entrypoint)) {
854
+ const { file } = parseProxyEntrypoint(proxy.entrypoint);
855
+ if (!files.includes(file)) {
524
856
  return {
525
857
  code: "proxy_entrypoint_not_found",
526
- message: `The proxy entrypoint \`${proxy.entrypoint}\` does not exist. Set \`proxy.entrypoint\` to an existing \`.js\` or \`.ts\` file.`
858
+ message: `The proxy entrypoint \`${proxy.entrypoint}\` does not exist.`
527
859
  };
528
860
  }
529
861
  if (framework === "nextjs" || framework === "astro") {
@@ -533,7 +865,8 @@ function validateProxy(options, files, framework) {
533
865
  };
534
866
  }
535
867
  const appRuntime = (0, import_framework.inferBuilderRuntimeFromFramework)(framework);
536
- const proxyRuntime = import_constants.ENTRYPOINT_EXTENSIONS[(0, import_path.extname)(proxy.entrypoint)];
868
+ const { file: proxyFile } = parseProxyEntrypoint(proxy.entrypoint);
869
+ const proxyRuntime = import_constants.ENTRYPOINT_EXTENSIONS[(0, import_path.extname)(proxyFile)];
537
870
  if (appRuntime !== void 0 && proxyRuntime !== void 0 && appRuntime !== proxyRuntime) {
538
871
  return {
539
872
  code: "proxy_runtime_mismatch",
@@ -596,8 +929,8 @@ function detectFrontBuilder(pkg, files, usedFunctions, fallbackEntrypoint, optio
596
929
  }
597
930
  const f = import_framework.frameworksBySlug.get(framework || "");
598
931
  if (f && f.useRuntime && !isRustStaticOutput(framework, projectSettings)) {
599
- const buildpackRuntime = (0, import_buildpack_runtimes.toBuildpackRuntime)(framework);
600
- if (buildpackRuntime && (import_buildpack_runtimes.BUILDPACK_PROJECT_MARKERS.get(buildpackRuntime) ?? []).some(
932
+ const buildpackRuntime = toBuildpackRuntime(framework);
933
+ if (buildpackRuntime && (BUILDPACK_PROJECT_MARKERS.get(buildpackRuntime) ?? []).some(
601
934
  (marker) => files.includes(marker)
602
935
  )) {
603
936
  config.buildpack = buildpackRuntime;
@@ -608,8 +941,8 @@ function detectFrontBuilder(pkg, files, usedFunctions, fallbackEntrypoint, optio
608
941
  };
609
942
  }
610
943
  const { src, use } = f.useRuntime;
611
- const shouldUseUnifiedBackend = (0, import_build_utils.isExperimentalBackendsEnabled)() && import_build_utils.BACKEND_BUILDERS.includes(use);
612
- const finalUse = shouldUseUnifiedBackend ? import_build_utils.UNIFIED_BACKEND_BUILDER : use;
944
+ const shouldUseUnifiedBackend = (0, import_build_utils2.isExperimentalBackendsEnabled)() && import_build_utils2.BACKEND_BUILDERS.includes(use);
945
+ const finalUse = shouldUseUnifiedBackend ? import_build_utils2.UNIFIED_BACKEND_BUILDER : use;
613
946
  return { src, use: `${finalUse}${withTag}`, config };
614
947
  }
615
948
  const entrypoints = /* @__PURE__ */ new Set([
@@ -654,7 +987,7 @@ function validateFunctions({ functions = {} }) {
654
987
  message: "Function must contain at least one property."
655
988
  };
656
989
  }
657
- const maxDurationLimit = (0, import_build_utils.getMaxDurationLimit)();
990
+ const maxDurationLimit = (0, import_build_utils2.getMaxDurationLimit)();
658
991
  if (func.maxDuration !== void 0 && func.maxDuration !== "max" && (func.maxDuration < 1 || maxDurationLimit !== void 0 && func.maxDuration > maxDurationLimit || !Number.isInteger(func.maxDuration))) {
659
992
  return {
660
993
  code: "invalid_function_duration",
@@ -701,14 +1034,22 @@ function validateFunctions({ functions = {} }) {
701
1034
  }
702
1035
  return null;
703
1036
  }
704
- function checkUnusedFunctions(frontendBuilder, usedFunctions, options) {
1037
+ function checkUnusedFunctions(frontendBuilder, usedFunctions, claimedFiles, options) {
705
1038
  const unusedFunctions = new Set(
706
1039
  Object.keys(options.functions || {}).filter((key) => !usedFunctions.has(key))
707
1040
  );
1041
+ for (const fnKey of unusedFunctions) {
1042
+ for (const filePath of claimedFiles) {
1043
+ if (filePath === fnKey || (0, import_minimatch.default)(filePath, fnKey)) {
1044
+ unusedFunctions.delete(fnKey);
1045
+ break;
1046
+ }
1047
+ }
1048
+ }
708
1049
  if (!unusedFunctions.size) {
709
1050
  return null;
710
1051
  }
711
- if (frontendBuilder && (0, import_is_official_runtime.isOfficialRuntime)("next", frontendBuilder.use)) {
1052
+ if (frontendBuilder && isOfficialRuntime("next", frontendBuilder.use)) {
712
1053
  for (const fnKey of unusedFunctions.values()) {
713
1054
  if (fnKey.startsWith("pages/") || fnKey.startsWith("src/pages") || fnKey.startsWith("app/") || fnKey.startsWith("src/app/") || fnKey.startsWith("middleware") || fnKey.startsWith("src/middleware")) {
714
1055
  unusedFunctions.delete(fnKey);
@@ -722,7 +1063,7 @@ function checkUnusedFunctions(frontendBuilder, usedFunctions, options) {
722
1063
  }
723
1064
  }
724
1065
  }
725
- if (frontendBuilder && ((0, import_is_official_runtime.isOfficialRuntime)("express", frontendBuilder.use) || (0, import_is_official_runtime.isOfficialRuntime)("hono", frontendBuilder.use) || (0, import_is_official_runtime.isOfficialRuntime)("python", frontendBuilder.use) || (0, import_is_official_runtime.isOfficialRuntime)("backends", frontendBuilder.use))) {
1066
+ if (frontendBuilder && (isOfficialRuntime("express", frontendBuilder.use) || isOfficialRuntime("hono", frontendBuilder.use) || isOfficialRuntime("python", frontendBuilder.use) || isOfficialRuntime("backends", frontendBuilder.use))) {
726
1067
  return null;
727
1068
  }
728
1069
  if (unusedFunctions.size) {
@@ -912,7 +1253,7 @@ function getRouteResult(apiRoutes, dynamicRoutes, outputDirectory, apiBuilders,
912
1253
  const errorRoutes = [];
913
1254
  const framework = frontendBuilder?.config?.framework || "";
914
1255
  const isGatsby = framework === "gatsby";
915
- const isNextjs = framework === "nextjs" || (0, import_is_official_runtime.isOfficialRuntime)("next", frontendBuilder?.use);
1256
+ const isNextjs = framework === "nextjs" || isOfficialRuntime("next", frontendBuilder?.use);
916
1257
  const ignoreRuntimes = import_framework.frameworksBySlug.get(framework)?.ignoreRuntimes;
917
1258
  if (apiRoutes && apiRoutes.length > 0) {
918
1259
  if (options.featHandleMiss) {
@@ -961,7 +1302,7 @@ function getRouteResult(apiRoutes, dynamicRoutes, outputDirectory, apiBuilders,
961
1302
  }
962
1303
  }
963
1304
  }
964
- if (outputDirectory && frontendBuilder && !options.featHandleMiss && (0, import_is_official_runtime.isOfficialRuntime)("static", frontendBuilder.use)) {
1305
+ if (outputDirectory && frontendBuilder && !options.featHandleMiss && isOfficialRuntime("static", frontendBuilder.use)) {
965
1306
  defaultRoutes.push({
966
1307
  src: "/(.*)",
967
1308
  dest: `/${outputDirectory}/$1`
@@ -248,7 +248,7 @@ async function resolveConfiguredServiceV2(name, config, fs) {
248
248
  }
249
249
  };
250
250
  }
251
- const buildpackRuntime = flaggedBuildpackRuntime && !entrypointFile && !(0, import_framework.isFrontendFramework)(framework) && await (0, import_buildpack_runtimes.hasBuildpackProjectMarker)(
251
+ const buildpackRuntime = flaggedBuildpackRuntime && (!entrypointFile || flaggedBuildpackRuntime === "ruby" && entrypointFile === "config.ru") && !(0, import_framework.isFrontendFramework)(framework) && await (0, import_buildpack_runtimes.hasBuildpackProjectMarker)(
252
252
  flaggedBuildpackRuntime,
253
253
  (marker) => (0, import_utils.hasFile)(serviceFs, marker)
254
254
  ) ? flaggedBuildpackRuntime : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "7.6.1",
3
+ "version": "7.7.0",
4
4
  "description": "Vercel filesystem detectors",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,11 +20,11 @@
20
20
  "minimatch": "3.1.2",
21
21
  "semver": "6.3.1",
22
22
  "smol-toml": "1.5.2",
23
- "@vercel/build-utils": "14.10.2",
23
+ "@vercel/error-utils": "2.2.1",
24
+ "@vercel/build-utils": "14.11.0",
24
25
  "@vercel/frameworks": "3.34.0",
25
- "@vercel/python-analysis": "0.14.0",
26
26
  "@vercel/routing-utils": "6.6.0",
27
- "@vercel/error-utils": "2.2.1"
27
+ "@vercel/python-analysis": "0.14.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/glob": "7.2.0",
@@ -33,10 +33,10 @@
33
33
  "@types/node": "20.11.0",
34
34
  "@types/semver": "7.3.10",
35
35
  "vitest": "4.1.10",
36
- "@vercel-internals/runtimes": "1.1.1"
36
+ "@vercel-internals/runtimes": "1.1.2"
37
37
  },
38
38
  "scripts": {
39
- "build": "node ../../utils/build.mjs",
39
+ "build": "node build.mjs",
40
40
  "test": "vitest run --config ../../vitest.config.mts",
41
41
  "test-unit": "vitest run --config ../../vitest.config.mts test/unit.",
42
42
  "type-check": "tsc --noEmit"