@vercel/fs-detectors 5.7.16 → 5.7.18

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.
@@ -44,6 +44,7 @@ var import_path = require("path");
44
44
  var import_frameworks = __toESM(require("@vercel/frameworks"));
45
45
  var import_is_official_runtime = require("./is-official-runtime");
46
46
  var import_build_utils = require("@vercel/build-utils");
47
+ var import_get_services_builders = require("./services/get-services-builders");
47
48
  const REGEX_MIDDLEWARE_FILES = "middleware.[jt]s";
48
49
  const REGEX_VERCEL_PLATFORM_FILES = `api/**,package.json,${REGEX_MIDDLEWARE_FILES}`;
49
50
  const REGEX_NON_VERCEL_PLATFORM_FILES = `!{${REGEX_VERCEL_PLATFORM_FILES}}`;
@@ -79,6 +80,13 @@ function detectOutputDirectory(builders) {
79
80
  return publicBuilder ? publicBuilder.src.replace("/**/*", "") : null;
80
81
  }
81
82
  async function detectBuilders(files, pkg, options = {}) {
83
+ const { projectSettings = {} } = options;
84
+ const { framework } = projectSettings;
85
+ if (framework === "services") {
86
+ return (0, import_get_services_builders.getServicesBuilders)({
87
+ workPath: options.workPath
88
+ });
89
+ }
82
90
  const errors = [];
83
91
  const warnings = [];
84
92
  let apiBuilders = [];
@@ -104,8 +112,7 @@ async function detectBuilders(files, pkg, options = {}) {
104
112
  usedFunctions.add(key);
105
113
  };
106
114
  const absolutePathCache = /* @__PURE__ */ new Map();
107
- const { projectSettings = {} } = options;
108
- const { buildCommand, outputDirectory, framework } = projectSettings;
115
+ const { buildCommand, outputDirectory } = projectSettings;
109
116
  const frameworkConfig = slugToFramework.get(framework || "");
110
117
  const ignoreRuntimes = new Set(frameworkConfig?.ignoreRuntimes);
111
118
  const withTag = options.tag ? `@${options.tag}` : "";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export { detectBuilders, detectOutputDirectory, detectApiDirectory, detectApiExtensions, } from './detect-builders';
2
- export { detectServices, type DetectServicesOptions, type DetectServicesResult, type ResolvedService, } from './services';
1
+ export { detectBuilders, detectOutputDirectory, detectApiDirectory, detectApiExtensions, type Options as DetectBuildersOptions, } from './detect-builders';
2
+ export { detectServices, generateServicesRoutes, } from './services/detect-services';
3
+ export { getServicesBuilders } from './services/get-services-builders';
4
+ export type { DetectServicesOptions, DetectServicesResult, ResolvedService, ServicesRoutes, } from './services/types';
3
5
  export { detectFileSystemAPI } from './detect-file-system-api';
4
6
  export { detectFramework, detectFrameworks, detectFrameworkRecord, detectFrameworkVersion, } from './detect-framework';
5
7
  export { getProjectPaths } from './get-project-paths';
package/dist/index.js CHANGED
@@ -36,8 +36,10 @@ __export(src_exports, {
36
36
  detectFrameworks: () => import_detect_framework.detectFrameworks,
37
37
  detectInstrumentation: () => import_detect_instrumentation.detectInstrumentation,
38
38
  detectOutputDirectory: () => import_detect_builders.detectOutputDirectory,
39
- detectServices: () => import_services.detectServices,
39
+ detectServices: () => import_detect_services.detectServices,
40
+ generateServicesRoutes: () => import_detect_services.generateServicesRoutes,
40
41
  getProjectPaths: () => import_get_project_paths.getProjectPaths,
42
+ getServicesBuilders: () => import_get_services_builders.getServicesBuilders,
41
43
  getWorkspacePackagePaths: () => import_get_workspace_package_paths.getWorkspacePackagePaths,
42
44
  getWorkspaces: () => import_get_workspaces.getWorkspaces,
43
45
  isOfficialRuntime: () => import_is_official_runtime.isOfficialRuntime,
@@ -48,7 +50,8 @@ __export(src_exports, {
48
50
  });
49
51
  module.exports = __toCommonJS(src_exports);
50
52
  var import_detect_builders = require("./detect-builders");
51
- var import_services = require("./services");
53
+ var import_detect_services = require("./services/detect-services");
54
+ var import_get_services_builders = require("./services/get-services-builders");
52
55
  var import_detect_file_system_api = require("./detect-file-system-api");
53
56
  var import_detect_framework = require("./detect-framework");
54
57
  var import_get_project_paths = require("./get-project-paths");
@@ -83,7 +86,9 @@ var import_detect_instrumentation = require("./detect-instrumentation");
83
86
  detectInstrumentation,
84
87
  detectOutputDirectory,
85
88
  detectServices,
89
+ generateServicesRoutes,
86
90
  getProjectPaths,
91
+ getServicesBuilders,
87
92
  getWorkspacePackagePaths,
88
93
  getWorkspaces,
89
94
  isOfficialRuntime,
@@ -0,0 +1,19 @@
1
+ import type { DetectServicesOptions, DetectServicesResult, ResolvedService, ServicesRoutes } from './types';
2
+ /**
3
+ * Detect and resolve services within a project.
4
+ *
5
+ * Reads vercel.json and resolves `experimentalServices` into ResolvedService objects.
6
+ * Returns an error if no services are configured.
7
+ */
8
+ export declare function detectServices(options: DetectServicesOptions): Promise<DetectServicesResult>;
9
+ /**
10
+ * Generate routing rules for services.
11
+ *
12
+ * Web services: Routes are ordered by prefix length (longest first) to ensure
13
+ * more specific routes match before broader ones. For example, `/api/users`
14
+ * must be checked before `/api`, which must be checked before the catch-all `/`.
15
+ *
16
+ * Cron/Worker services: TODO
17
+ * Use internal routes under `/_svc/crons` and `/_svc/workers`
18
+ */
19
+ export declare function generateServicesRoutes(services: ResolvedService[]): ServicesRoutes;
@@ -0,0 +1,103 @@
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 detect_services_exports = {};
20
+ __export(detect_services_exports, {
21
+ detectServices: () => detectServices,
22
+ generateServicesRoutes: () => generateServicesRoutes
23
+ });
24
+ module.exports = __toCommonJS(detect_services_exports);
25
+ var import_utils = require("./utils");
26
+ var import_resolve = require("./resolve");
27
+ async function detectServices(options) {
28
+ const { fs, workPath } = options;
29
+ const scopedFs = workPath ? fs.chdir(workPath) : fs;
30
+ const { config: vercelConfig, error: configError } = await (0, import_utils.readVercelConfig)(scopedFs);
31
+ if (configError) {
32
+ return {
33
+ services: [],
34
+ routes: { rewrites: [], defaults: [], crons: [], workers: [] },
35
+ errors: [configError],
36
+ warnings: []
37
+ };
38
+ }
39
+ const configuredServices = vercelConfig?.experimentalServices;
40
+ const hasConfiguredServices = configuredServices && Object.keys(configuredServices).length > 0;
41
+ if (!hasConfiguredServices) {
42
+ return {
43
+ services: [],
44
+ routes: { rewrites: [], defaults: [], crons: [], workers: [] },
45
+ errors: [
46
+ {
47
+ code: "NO_SERVICES_CONFIGURED",
48
+ message: "No services configured. Add `experimentalServices` to vercel.json."
49
+ }
50
+ ],
51
+ warnings: []
52
+ };
53
+ }
54
+ const result = (0, import_resolve.resolveAllConfiguredServices)(configuredServices);
55
+ const routes = generateServicesRoutes(result.services);
56
+ return {
57
+ services: result.services,
58
+ routes,
59
+ errors: result.errors,
60
+ warnings: []
61
+ };
62
+ }
63
+ function generateServicesRoutes(services) {
64
+ const rewrites = [];
65
+ const defaults = [];
66
+ const crons = [];
67
+ const workers = [];
68
+ const webServices = services.filter(
69
+ (s) => s.type === "web" && typeof s.routePrefix === "string"
70
+ );
71
+ const sortedWebServices = [...webServices].sort((a, b) => {
72
+ if (a.routePrefix === "/")
73
+ return 1;
74
+ if (b.routePrefix === "/")
75
+ return -1;
76
+ return b.routePrefix.length - a.routePrefix.length;
77
+ });
78
+ for (const service of sortedWebServices) {
79
+ const { routePrefix, builder } = service;
80
+ const builderSrc = builder.src || routePrefix;
81
+ const functionPath = builderSrc.startsWith("/") ? builderSrc : `/${builderSrc}`;
82
+ if (routePrefix === "/") {
83
+ defaults.push({
84
+ src: "^/(.*)$",
85
+ dest: functionPath,
86
+ check: true
87
+ });
88
+ } else {
89
+ const normalizedPrefix = routePrefix.startsWith("/") ? routePrefix.slice(1) : routePrefix;
90
+ rewrites.push({
91
+ src: `^/${normalizedPrefix}(?:/.*)?$`,
92
+ dest: functionPath,
93
+ check: true
94
+ });
95
+ }
96
+ }
97
+ return { rewrites, defaults, crons, workers };
98
+ }
99
+ // Annotate the CommonJS export names for ESM import in node:
100
+ 0 && (module.exports = {
101
+ detectServices,
102
+ generateServicesRoutes
103
+ });
@@ -0,0 +1,29 @@
1
+ import type { Route } from '@vercel/routing-utils';
2
+ import type { Builder } from '@vercel/build-utils';
3
+ import type { ResolvedService } from './types';
4
+ export interface ErrorResponse {
5
+ code: string;
6
+ message: string;
7
+ action?: string;
8
+ link?: string;
9
+ }
10
+ export interface GetServicesBuildersOptions {
11
+ workPath?: string;
12
+ }
13
+ export interface ServicesBuildersResult {
14
+ builders: Builder[] | null;
15
+ errors: ErrorResponse[] | null;
16
+ warnings: ErrorResponse[];
17
+ defaultRoutes: Route[] | null;
18
+ redirectRoutes: Route[] | null;
19
+ rewriteRoutes: Route[] | null;
20
+ errorRoutes: Route[] | null;
21
+ services?: ResolvedService[];
22
+ }
23
+ /**
24
+ * Get builders for services - adapter for detectBuilders.
25
+ *
26
+ * This function wraps `detectServices` and transforms the result into
27
+ * the shape expected by `detectBuilders` when `framework === 'services'`.
28
+ */
29
+ export declare function getServicesBuilders(options: GetServicesBuildersOptions): Promise<ServicesBuildersResult>;
@@ -0,0 +1,95 @@
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 get_services_builders_exports = {};
20
+ __export(get_services_builders_exports, {
21
+ getServicesBuilders: () => getServicesBuilders
22
+ });
23
+ module.exports = __toCommonJS(get_services_builders_exports);
24
+ var import_detect_services = require("./detect-services");
25
+ var import_local_file_system_detector = require("../detectors/local-file-system-detector");
26
+ async function getServicesBuilders(options) {
27
+ const { workPath } = options;
28
+ if (!workPath) {
29
+ return {
30
+ builders: null,
31
+ errors: [
32
+ {
33
+ code: "MISSING_WORK_PATH",
34
+ message: "workPath is required for services detection."
35
+ }
36
+ ],
37
+ warnings: [],
38
+ defaultRoutes: null,
39
+ redirectRoutes: null,
40
+ rewriteRoutes: null,
41
+ errorRoutes: null
42
+ };
43
+ }
44
+ const fs = new import_local_file_system_detector.LocalFileSystemDetector(workPath);
45
+ const result = await (0, import_detect_services.detectServices)({ fs });
46
+ const warningResponses = result.warnings.map((w) => ({
47
+ code: w.code,
48
+ message: w.message
49
+ }));
50
+ if (result.errors.length > 0) {
51
+ return {
52
+ builders: null,
53
+ errors: result.errors.map((e) => ({
54
+ code: e.code,
55
+ message: e.message
56
+ })),
57
+ warnings: warningResponses,
58
+ defaultRoutes: null,
59
+ redirectRoutes: null,
60
+ rewriteRoutes: null,
61
+ errorRoutes: null
62
+ };
63
+ }
64
+ if (result.services.length === 0) {
65
+ return {
66
+ builders: null,
67
+ errors: [
68
+ {
69
+ code: "NO_SERVICES_CONFIGURED",
70
+ message: "No services configured. Add `experimentalServices` to vercel.json."
71
+ }
72
+ ],
73
+ warnings: warningResponses,
74
+ defaultRoutes: null,
75
+ redirectRoutes: null,
76
+ rewriteRoutes: null,
77
+ errorRoutes: null
78
+ };
79
+ }
80
+ const builders = result.services.map((service) => service.builder);
81
+ return {
82
+ builders: builders.length > 0 ? builders : null,
83
+ errors: null,
84
+ warnings: warningResponses,
85
+ defaultRoutes: result.routes.defaults.length > 0 ? result.routes.defaults : null,
86
+ redirectRoutes: [],
87
+ rewriteRoutes: result.routes.rewrites.length > 0 ? result.routes.rewrites : null,
88
+ errorRoutes: [],
89
+ services: result.services
90
+ };
91
+ }
92
+ // Annotate the CommonJS export names for ESM import in node:
93
+ 0 && (module.exports = {
94
+ getServicesBuilders
95
+ });
@@ -1,3 +1,17 @@
1
- import type { ResolvedService, ExperimentalServiceConfig, ServiceDetectionError } from './types';
1
+ import type { ResolvedService, ExperimentalServiceConfig, ExperimentalServices, ServiceDetectionError } from './types';
2
+ /**
3
+ * Validate a service configuration from vercel.json experimentalServices.
4
+ */
2
5
  export declare function validateServiceConfig(name: string, config: ExperimentalServiceConfig): ServiceDetectionError | null;
3
- export declare function resolveService(name: string, config: ExperimentalServiceConfig, group?: string): ResolvedService;
6
+ /**
7
+ * Resolve a single service from user configuration.
8
+ */
9
+ export declare function resolveConfiguredService(name: string, config: ExperimentalServiceConfig, group?: string): ResolvedService;
10
+ /**
11
+ * Resolve all services from vercel.json experimentalServices.
12
+ * Validates each service configuration.
13
+ */
14
+ export declare function resolveAllConfiguredServices(services: ExperimentalServices): {
15
+ services: ResolvedService[];
16
+ errors: ServiceDetectionError[];
17
+ };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,51 +17,175 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var resolve_exports = {};
20
30
  __export(resolve_exports, {
21
- resolveService: () => resolveService,
31
+ resolveAllConfiguredServices: () => resolveAllConfiguredServices,
32
+ resolveConfiguredService: () => resolveConfiguredService,
22
33
  validateServiceConfig: () => validateServiceConfig
23
34
  });
24
35
  module.exports = __toCommonJS(resolve_exports);
36
+ var import_path = require("path");
37
+ var import_types = require("./types");
38
+ var import_utils = require("./utils");
39
+ var import_frameworks = __toESM(require("@vercel/frameworks"));
40
+ const frameworksBySlug = new Map(import_frameworks.default.map((f) => [f.slug, f]));
25
41
  function validateServiceConfig(name, config) {
26
- if (config.type === "cron" && !config.schedule) {
42
+ if (!config || typeof config !== "object") {
43
+ return {
44
+ code: "INVALID_SERVICE_CONFIG",
45
+ message: `Service "${name}" has an invalid configuration. Expected an object.`,
46
+ serviceName: name
47
+ };
48
+ }
49
+ const serviceType = config.type || "web";
50
+ if (serviceType === "web" && !config.routePrefix) {
51
+ return {
52
+ code: "MISSING_ROUTE_PREFIX",
53
+ message: `Web service "${name}" must specify "routePrefix".`,
54
+ serviceName: name
55
+ };
56
+ }
57
+ if ((serviceType === "worker" || serviceType === "cron") && config.routePrefix) {
58
+ return {
59
+ code: "INVALID_ROUTE_PREFIX",
60
+ message: `${serviceType === "worker" ? "Worker" : "Cron"} service "${name}" cannot have "routePrefix". Only web services should specify "routePrefix".`,
61
+ serviceName: name
62
+ };
63
+ }
64
+ if (serviceType === "cron" && !config.schedule) {
27
65
  return {
28
66
  code: "MISSING_CRON_SCHEDULE",
29
- message: `Cron service "${name}" is missing required "schedule" field`,
67
+ message: `Cron service "${name}" is missing required "schedule" field.`,
68
+ serviceName: name
69
+ };
70
+ }
71
+ if (config.runtime && !(config.runtime in import_types.RUNTIME_BUILDERS)) {
72
+ return {
73
+ code: "INVALID_RUNTIME",
74
+ message: `Service "${name}" has invalid runtime "${config.runtime}".`,
30
75
  serviceName: name
31
76
  };
32
77
  }
78
+ if (config.framework && !frameworksBySlug.has(config.framework)) {
79
+ return {
80
+ code: "INVALID_FRAMEWORK",
81
+ message: `Service "${name}" has invalid framework "${config.framework}".`,
82
+ serviceName: name
83
+ };
84
+ }
85
+ const hasFramework = Boolean(config.framework);
86
+ const hasBuilderOrRuntime = Boolean(config.builder || config.runtime);
87
+ const hasEntrypoint = Boolean(config.entrypoint);
88
+ if (!hasFramework && !hasBuilderOrRuntime && !hasEntrypoint) {
89
+ return {
90
+ code: "MISSING_SERVICE_CONFIG",
91
+ message: `Service "${name}" must specify "framework", "entrypoint", or both "builder"/"runtime" with "entrypoint".`,
92
+ serviceName: name
93
+ };
94
+ }
95
+ if (hasBuilderOrRuntime && !hasFramework && !hasEntrypoint) {
96
+ return {
97
+ code: "MISSING_ENTRYPOINT",
98
+ message: `Service "${name}" must specify "entrypoint" when using "${config.builder ? "builder" : "runtime"}".`,
99
+ serviceName: name
100
+ };
101
+ }
102
+ if (hasEntrypoint && !hasBuilderOrRuntime && !hasFramework) {
103
+ const runtime = (0, import_utils.inferServiceRuntime)({ entrypoint: config.entrypoint });
104
+ if (!runtime) {
105
+ const supported = Object.keys(import_types.ENTRYPOINT_EXTENSIONS).join(", ");
106
+ return {
107
+ code: "UNSUPPORTED_ENTRYPOINT",
108
+ message: `Service "${name}" has unsupported entrypoint "${config.entrypoint}". Use a supported extension (${supported}) or specify "builder", "framework", or "runtime".`,
109
+ serviceName: name
110
+ };
111
+ }
112
+ }
33
113
  return null;
34
114
  }
35
- function resolveService(name, config, group) {
115
+ function resolveConfiguredService(name, config, group) {
36
116
  const type = config.type || "web";
37
117
  const workspace = config.workspace || ".";
38
118
  const topic = type === "worker" ? config.topic || "default" : config.topic;
39
119
  const consumer = type === "worker" ? config.consumer || "default" : config.consumer;
120
+ const inferredRuntime = (0, import_utils.inferServiceRuntime)(config);
121
+ let builderUse;
122
+ let builderSrc;
123
+ if (config.framework) {
124
+ const framework = frameworksBySlug.get(config.framework);
125
+ builderUse = framework?.useRuntime?.use || "@vercel/static-build";
126
+ builderSrc = config.entrypoint || framework?.useRuntime?.src || "package.json";
127
+ } else if (config.builder) {
128
+ builderUse = config.builder;
129
+ builderSrc = config.entrypoint;
130
+ } else {
131
+ builderUse = (0, import_utils.getBuilderForRuntime)(inferredRuntime);
132
+ builderSrc = config.entrypoint;
133
+ }
134
+ const routePrefix = type === "web" ? config.routePrefix : void 0;
135
+ const isRoot = workspace === ".";
136
+ if (!isRoot && !builderSrc.startsWith(workspace + "/")) {
137
+ builderSrc = import_path.posix.join(workspace, builderSrc);
138
+ }
139
+ const builderConfig = {};
140
+ if (config.memory)
141
+ builderConfig.memory = config.memory;
142
+ if (config.maxDuration)
143
+ builderConfig.maxDuration = config.maxDuration;
144
+ if (config.includeFiles)
145
+ builderConfig.includeFiles = config.includeFiles;
146
+ if (config.excludeFiles)
147
+ builderConfig.excludeFiles = config.excludeFiles;
148
+ const isStaticBuild = import_types.STATIC_BUILDERS.has(builderUse);
149
+ const runtime = isStaticBuild ? void 0 : inferredRuntime;
40
150
  return {
41
151
  name,
42
152
  type,
43
153
  group,
44
154
  workspace,
45
155
  entrypoint: config.entrypoint,
46
- routePrefix: config.routePrefix,
156
+ routePrefix,
47
157
  framework: config.framework,
48
- builder: config.builder,
49
- runtime: config.runtime,
158
+ builder: {
159
+ src: builderSrc,
160
+ use: builderUse,
161
+ config: Object.keys(builderConfig).length > 0 ? builderConfig : void 0
162
+ },
163
+ runtime,
50
164
  buildCommand: config.buildCommand,
51
165
  installCommand: config.installCommand,
52
- memory: config.memory,
53
- maxDuration: config.maxDuration,
54
- includeFiles: config.includeFiles,
55
- excludeFiles: config.excludeFiles,
56
166
  schedule: config.schedule,
57
167
  topic,
58
168
  consumer
59
169
  };
60
170
  }
171
+ function resolveAllConfiguredServices(services) {
172
+ const resolved = [];
173
+ const errors = [];
174
+ for (const name of Object.keys(services)) {
175
+ const serviceConfig = services[name];
176
+ const validationError = validateServiceConfig(name, serviceConfig);
177
+ if (validationError) {
178
+ errors.push(validationError);
179
+ continue;
180
+ }
181
+ const service = resolveConfiguredService(name, serviceConfig);
182
+ resolved.push(service);
183
+ }
184
+ return { services: resolved, errors };
185
+ }
61
186
  // Annotate the CommonJS export names for ESM import in node:
62
187
  0 && (module.exports = {
63
- resolveService,
188
+ resolveAllConfiguredServices,
189
+ resolveConfiguredService,
64
190
  validateServiceConfig
65
191
  });
@@ -1,6 +1,7 @@
1
- import type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType } from '@vercel/build-utils';
1
+ import type { Route } from '@vercel/routing-utils';
2
+ import type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, Builder } from '@vercel/build-utils';
2
3
  import type { DetectorFilesystem } from '../detectors/filesystem';
3
- export type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, };
4
+ export type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, Builder, };
4
5
  export interface ResolvedService {
5
6
  name: string;
6
7
  type: ServiceType;
@@ -9,14 +10,15 @@ export interface ResolvedService {
9
10
  workspace: string;
10
11
  entrypoint?: string;
11
12
  framework?: string;
12
- builder?: string;
13
+ builder: Builder;
13
14
  buildCommand?: string;
14
15
  installCommand?: string;
15
16
  runtime?: string;
16
- memory?: number;
17
- maxDuration?: number;
18
- includeFiles?: string | string[];
19
- excludeFiles?: string | string[];
17
+ /**
18
+ * URL path prefix for routing requests to this service.
19
+ * Required for web services; requests matching this prefix are routed to this service.
20
+ * Root services use "/" as the catch-all.
21
+ */
20
22
  routePrefix?: string;
21
23
  schedule?: string;
22
24
  topic?: string;
@@ -24,11 +26,41 @@ export interface ResolvedService {
24
26
  }
25
27
  export interface DetectServicesOptions {
26
28
  fs: DetectorFilesystem;
29
+ /**
30
+ * Working directory path (relative to fs root).
31
+ * If provided, vercel.json is read from this path.
32
+ */
27
33
  workPath?: string;
28
34
  }
35
+ export interface ServicesRoutes {
36
+ /** Rewrite routes for non-root web services (prefix-based) */
37
+ rewrites: Route[];
38
+ /** Default routes (catch-all for root web service) */
39
+ defaults: Route[];
40
+ /**
41
+ * Internal routes for cron services.
42
+ * These route `/_svc/{serviceName}/crons/{entry}/{handler}` to the cron function.
43
+ * TODO: Implement
44
+ */
45
+ crons: Route[];
46
+ /**
47
+ * Internal routes for worker services.
48
+ * These route `/_svc/{serviceName}/workers/{entry}/{handler}` to the worker function.
49
+ * TODO: Implement
50
+ */
51
+ workers: Route[];
52
+ }
29
53
  export interface DetectServicesResult {
30
54
  services: ResolvedService[];
55
+ /** Routing rules derived from services */
56
+ routes: ServicesRoutes;
31
57
  errors: ServiceDetectionError[];
58
+ warnings: ServiceDetectionWarning[];
59
+ }
60
+ export interface ServiceDetectionWarning {
61
+ code: string;
62
+ message: string;
63
+ serviceName?: string;
32
64
  }
33
65
  export interface ServiceDetectionError {
34
66
  code: string;
@@ -36,3 +68,9 @@ export interface ServiceDetectionError {
36
68
  serviceName?: string;
37
69
  }
38
70
  export declare const RUNTIME_BUILDERS: Record<ServiceRuntime, string>;
71
+ export declare const ENTRYPOINT_EXTENSIONS: Record<string, ServiceRuntime>;
72
+ /**
73
+ * Builders that produce static output (SPAs, static sites).
74
+ * These don't have a "runtime" - they just build to static files.
75
+ */
76
+ export declare const STATIC_BUILDERS: Set<string>;
@@ -18,7 +18,9 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var types_exports = {};
20
20
  __export(types_exports, {
21
- RUNTIME_BUILDERS: () => RUNTIME_BUILDERS
21
+ ENTRYPOINT_EXTENSIONS: () => ENTRYPOINT_EXTENSIONS,
22
+ RUNTIME_BUILDERS: () => RUNTIME_BUILDERS,
23
+ STATIC_BUILDERS: () => STATIC_BUILDERS
22
24
  });
23
25
  module.exports = __toCommonJS(types_exports);
24
26
  const RUNTIME_BUILDERS = {
@@ -28,7 +30,25 @@ const RUNTIME_BUILDERS = {
28
30
  rust: "@vercel/rust",
29
31
  ruby: "@vercel/ruby"
30
32
  };
33
+ const ENTRYPOINT_EXTENSIONS = {
34
+ ".ts": "node",
35
+ ".mts": "node",
36
+ ".js": "node",
37
+ ".mjs": "node",
38
+ ".cjs": "node",
39
+ ".py": "python",
40
+ ".go": "go",
41
+ ".rs": "rust",
42
+ ".rb": "ruby",
43
+ ".ru": "ruby"
44
+ };
45
+ const STATIC_BUILDERS = /* @__PURE__ */ new Set([
46
+ "@vercel/static-build",
47
+ "@vercel/static"
48
+ ]);
31
49
  // Annotate the CommonJS export names for ESM import in node:
32
50
  0 && (module.exports = {
33
- RUNTIME_BUILDERS
51
+ ENTRYPOINT_EXTENSIONS,
52
+ RUNTIME_BUILDERS,
53
+ STATIC_BUILDERS
34
54
  });
@@ -0,0 +1,31 @@
1
+ import type { DetectorFilesystem } from '../detectors/filesystem';
2
+ import type { ServiceRuntime, ExperimentalServices, ServiceDetectionError } from './types';
3
+ export declare function getBuilderForRuntime(runtime: ServiceRuntime): string;
4
+ /**
5
+ * Infer runtime from available service configuration.
6
+ *
7
+ * Priority (highest to lowest):
8
+ * 1. Explicit runtime (user specified in config)
9
+ * 2. Framework detection (fastapi → python, express → node)
10
+ * 3. Builder detection (@vercel/python → python)
11
+ * 4. Entrypoint extension (.py → python, .ts → node)
12
+ *
13
+ * @returns The inferred runtime, or undefined if none can be determined.
14
+ */
15
+ export declare function inferServiceRuntime(config: {
16
+ runtime?: string;
17
+ framework?: string;
18
+ builder?: string;
19
+ entrypoint?: string;
20
+ }): ServiceRuntime | undefined;
21
+ export interface ReadVercelConfigResult {
22
+ config: {
23
+ experimentalServices?: ExperimentalServices;
24
+ } | null;
25
+ error: ServiceDetectionError | null;
26
+ }
27
+ /**
28
+ * Read and parse vercel.json from filesystem.
29
+ * Returns the parsed config or an error if the file exists but is invalid.
30
+ */
31
+ export declare function readVercelConfig(fs: DetectorFilesystem): Promise<ReadVercelConfigResult>;
@@ -0,0 +1,85 @@
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 utils_exports = {};
20
+ __export(utils_exports, {
21
+ getBuilderForRuntime: () => getBuilderForRuntime,
22
+ inferServiceRuntime: () => inferServiceRuntime,
23
+ readVercelConfig: () => readVercelConfig
24
+ });
25
+ module.exports = __toCommonJS(utils_exports);
26
+ var import_framework_helpers = require("@vercel/build-utils/dist/framework-helpers");
27
+ var import_types = require("./types");
28
+ function getBuilderForRuntime(runtime) {
29
+ const builder = import_types.RUNTIME_BUILDERS[runtime];
30
+ if (!builder) {
31
+ throw new Error(`Unknown runtime: ${runtime}`);
32
+ }
33
+ return builder;
34
+ }
35
+ function inferServiceRuntime(config) {
36
+ if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
37
+ return config.runtime;
38
+ }
39
+ if ((0, import_framework_helpers.isPythonFramework)(config.framework)) {
40
+ return "python";
41
+ }
42
+ if ((0, import_framework_helpers.isBackendFramework)(config.framework)) {
43
+ return "node";
44
+ }
45
+ if (config.builder) {
46
+ for (const [runtime, builderName] of Object.entries(import_types.RUNTIME_BUILDERS)) {
47
+ if (config.builder === builderName) {
48
+ return runtime;
49
+ }
50
+ }
51
+ }
52
+ if (config.entrypoint) {
53
+ for (const [ext, runtime] of Object.entries(import_types.ENTRYPOINT_EXTENSIONS)) {
54
+ if (config.entrypoint.endsWith(ext)) {
55
+ return runtime;
56
+ }
57
+ }
58
+ }
59
+ return void 0;
60
+ }
61
+ async function readVercelConfig(fs) {
62
+ const hasVercelJson = await fs.hasPath("vercel.json");
63
+ if (!hasVercelJson) {
64
+ return { config: null, error: null };
65
+ }
66
+ try {
67
+ const content = await fs.readFile("vercel.json");
68
+ const config = JSON.parse(content.toString());
69
+ return { config, error: null };
70
+ } catch {
71
+ return {
72
+ config: null,
73
+ error: {
74
+ code: "INVALID_VERCEL_JSON",
75
+ message: "Failed to parse vercel.json. Ensure it contains valid JSON."
76
+ }
77
+ };
78
+ }
79
+ }
80
+ // Annotate the CommonJS export names for ESM import in node:
81
+ 0 && (module.exports = {
82
+ getBuilderForRuntime,
83
+ inferServiceRuntime,
84
+ readVercelConfig
85
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.7.16",
3
+ "version": "5.7.18",
4
4
  "description": "Vercel filesystem detectors",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,8 +20,8 @@
20
20
  "minimatch": "3.1.2",
21
21
  "semver": "6.3.1",
22
22
  "@vercel/error-utils": "2.0.3",
23
- "@vercel/routing-utils": "5.3.2",
24
- "@vercel/frameworks": "3.15.7"
23
+ "@vercel/frameworks": "3.16.0",
24
+ "@vercel/routing-utils": "5.3.2"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/glob": "7.2.0",
@@ -32,7 +32,7 @@
32
32
  "@types/semver": "7.3.10",
33
33
  "jest-junit": "16.0.0",
34
34
  "typescript": "4.9.5",
35
- "@vercel/build-utils": "13.2.14"
35
+ "@vercel/build-utils": "13.2.16"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "node ../../utils/build.mjs",
@@ -1,10 +0,0 @@
1
- import type { DetectServicesOptions, DetectServicesResult } from './types';
2
- export * from './types';
3
- export * from './resolve';
4
- /**
5
- * Detect and resolve services within a project.
6
- *
7
- * Reads service configurations from vercel.json `experimentalServices`
8
- * and resolves them into ResolvedService objects.
9
- */
10
- export declare function detectServices(options: DetectServicesOptions): Promise<DetectServicesResult>;
@@ -1,60 +0,0 @@
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
19
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
- var services_exports = {};
21
- __export(services_exports, {
22
- detectServices: () => detectServices
23
- });
24
- module.exports = __toCommonJS(services_exports);
25
- var import_resolve = require("./resolve");
26
- __reExport(services_exports, require("./types"), module.exports);
27
- __reExport(services_exports, require("./resolve"), module.exports);
28
- async function detectServices(options) {
29
- const { fs, workPath = "" } = options;
30
- const services = [];
31
- const errors = [];
32
- const configPath = workPath ? `${workPath}/vercel.json` : "vercel.json";
33
- let experimentalServices;
34
- try {
35
- const configBuffer = await fs.readFile(configPath);
36
- const config = JSON.parse(configBuffer.toString("utf-8"));
37
- experimentalServices = config.experimentalServices;
38
- } catch {
39
- return { services, errors };
40
- }
41
- if (experimentalServices && typeof experimentalServices === "object") {
42
- for (const name of Object.keys(experimentalServices)) {
43
- const serviceConfig = experimentalServices[name];
44
- const validationError = (0, import_resolve.validateServiceConfig)(name, serviceConfig);
45
- if (validationError) {
46
- errors.push(validationError);
47
- continue;
48
- }
49
- const resolved = (0, import_resolve.resolveService)(name, serviceConfig);
50
- services.push(resolved);
51
- }
52
- }
53
- return { services, errors };
54
- }
55
- // Annotate the CommonJS export names for ESM import in node:
56
- 0 && (module.exports = {
57
- detectServices,
58
- ...require("./types"),
59
- ...require("./resolve")
60
- });