@vercel/fs-detectors 6.7.7 → 6.8.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.
@@ -1,5 +1,5 @@
1
1
  import type { Route } from '@vercel/routing-utils';
2
- import type { PackageJson, Builder, BuilderFunctions, ExperimentalServices, ProjectSettings, Service } from '@vercel/build-utils';
2
+ import type { PackageJson, Builder, BuilderFunctions, ExperimentalServices, ExperimentalServicesV2, ProjectSettings, Service } from '@vercel/build-utils';
3
3
  /**
4
4
  * Pattern for finding all supported middleware files.
5
5
  */
@@ -23,6 +23,7 @@ export interface Options {
23
23
  tag?: string;
24
24
  functions?: BuilderFunctions;
25
25
  experimentalServices?: ExperimentalServices;
26
+ experimentalServicesV2?: ExperimentalServicesV2;
26
27
  ignoreBuildScript?: boolean;
27
28
  projectSettings?: ProjectSettings;
28
29
  cleanUrls?: boolean;
@@ -92,18 +92,28 @@ function detectOutputDirectory(builders) {
92
92
  return publicBuilder ? publicBuilder.src.replace("/**/*", "") : null;
93
93
  }
94
94
  async function detectBuilders(files, pkg, options = {}) {
95
- const { experimentalServices, projectSettings = {} } = options;
95
+ const {
96
+ experimentalServices: experimentalServicesV1,
97
+ experimentalServicesV2,
98
+ projectSettings = {}
99
+ } = options;
96
100
  const { framework } = projectSettings;
97
- const configuredServices = experimentalServices;
98
- const configuredServicesType = "experimentalServices";
101
+ const configuredServices = experimentalServicesV2 ?? experimentalServicesV1;
102
+ const configuredServicesType = experimentalServicesV2 ? "experimentalServicesV2" : "experimentalServices";
99
103
  const hasServicesConfig = configuredServices != null && typeof configuredServices === "object";
100
104
  if (hasServicesConfig || framework === "services") {
101
- return (0, import_get_services_builders.getServicesBuilders)({
105
+ const result = await (0, import_get_services_builders.getServicesBuilders)({
102
106
  workPath: options.workPath,
103
107
  configuredServices,
104
108
  configuredServicesType,
105
109
  projectFramework: framework
106
110
  });
111
+ if (configuredServices != null) {
112
+ result.warnings.push(
113
+ ...(0, import_get_services_builders.warnIgnoredDirectories)(files, configuredServices)
114
+ );
115
+ }
116
+ return result;
107
117
  }
108
118
  const errors = [];
109
119
  const warnings = [];
@@ -486,10 +496,11 @@ function validateFunctions({ functions = {} }) {
486
496
  message: "Function must contain at least one property."
487
497
  };
488
498
  }
489
- if (func.maxDuration !== void 0 && func.maxDuration !== "max" && (func.maxDuration < 1 || func.maxDuration > 900 || !Number.isInteger(func.maxDuration))) {
499
+ const maxDurationLimit = (0, import_build_utils.getMaxDurationLimit)();
500
+ if (func.maxDuration !== void 0 && func.maxDuration !== "max" && (func.maxDuration < 1 || maxDurationLimit !== void 0 && func.maxDuration > maxDurationLimit || !Number.isInteger(func.maxDuration))) {
490
501
  return {
491
502
  code: "invalid_function_duration",
492
- message: 'Functions must have a maxDuration between 1 and 900, or "max".'
503
+ message: maxDurationLimit !== void 0 ? `Functions must have a maxDuration between 1 and ${maxDurationLimit}, or "max".` : 'Functions must have a positive integer maxDuration, or "max".'
493
504
  };
494
505
  }
495
506
  if (func.memory !== void 0 && (func.memory < 128 || func.memory > 10240)) {
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export { detectBuilders, detectOutputDirectory, detectApiDirectory, detectApiExtensions, type Options as DetectBuildersOptions, } from './detect-builders';
2
2
  export { detectServices, generateServicesRoutes, } from './services/detect-services';
3
+ export { resolveAllConfiguredServicesV2, resolveConfiguredServiceV2, validateServiceConfigV2, } from './services/resolve-v2';
4
+ export { isExperimentalService, isExperimentalServiceV2, } from '@vercel/build-utils';
3
5
  export { autoDetectServices } from './services/auto-detect';
4
6
  export type { AutoDetectOptions, AutoDetectResult, } from './services/auto-detect';
5
7
  export { isStaticBuild, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPath, getInternalServiceCronPathPrefix, getInternalServiceWorkerPath, getInternalServiceWorkerPathPrefix, } from './services/utils';
6
8
  export { getServicesBuilders } from './services/get-services-builders';
7
- export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, InferredServicesConfig, ResolvedServicesResult, InferredServicesResult, ResolvedService, Service, ServicesRoutes, ServiceDetectionError, } from './services/types';
9
+ export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, InferredServicesConfig, ResolvedServicesResult, InferredServicesResult, ResolvedService, Service, ExperimentalService, ExperimentalServiceV2, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServicesRoutes, ServiceDetectionError, } from './services/types';
8
10
  export { detectFileSystemAPI } from './detect-file-system-api';
9
11
  export { detectFramework, detectFrameworks, detectFrameworkRecord, detectFrameworkVersion, } from './detect-framework';
10
12
  export { getProjectPaths } from './get-project-paths';
package/dist/index.js CHANGED
@@ -49,17 +49,24 @@ __export(src_exports, {
49
49
  getServicesBuilders: () => import_get_services_builders.getServicesBuilders,
50
50
  getWorkspacePackagePaths: () => import_get_workspace_package_paths.getWorkspacePackagePaths,
51
51
  getWorkspaces: () => import_get_workspaces.getWorkspaces,
52
+ isExperimentalService: () => import_build_utils.isExperimentalService,
53
+ isExperimentalServiceV2: () => import_build_utils.isExperimentalServiceV2,
52
54
  isOfficialRuntime: () => import_is_official_runtime.isOfficialRuntime,
53
55
  isRouteOwningBuilder: () => import_utils.isRouteOwningBuilder,
54
56
  isStaticBuild: () => import_utils.isStaticBuild,
55
57
  isStaticRuntime: () => import_is_official_runtime.isStaticRuntime,
56
58
  monorepoManagers: () => import_monorepo_managers.monorepoManagers,
57
59
  packageManagers: () => import_package_managers.packageManagers,
60
+ resolveAllConfiguredServicesV2: () => import_resolve_v2.resolveAllConfiguredServicesV2,
61
+ resolveConfiguredServiceV2: () => import_resolve_v2.resolveConfiguredServiceV2,
62
+ validateServiceConfigV2: () => import_resolve_v2.validateServiceConfigV2,
58
63
  workspaceManagers: () => import_workspace_managers.workspaceManagers
59
64
  });
60
65
  module.exports = __toCommonJS(src_exports);
61
66
  var import_detect_builders = require("./detect-builders");
62
67
  var import_detect_services = require("./services/detect-services");
68
+ var import_resolve_v2 = require("./services/resolve-v2");
69
+ var import_build_utils = require("@vercel/build-utils");
63
70
  var import_auto_detect = require("./services/auto-detect");
64
71
  var import_utils = require("./services/utils");
65
72
  var import_get_services_builders = require("./services/get-services-builders");
@@ -109,12 +116,17 @@ var import_detect_instrumentation = require("./detect-instrumentation");
109
116
  getServicesBuilders,
110
117
  getWorkspacePackagePaths,
111
118
  getWorkspaces,
119
+ isExperimentalService,
120
+ isExperimentalServiceV2,
112
121
  isOfficialRuntime,
113
122
  isRouteOwningBuilder,
114
123
  isStaticBuild,
115
124
  isStaticRuntime,
116
125
  monorepoManagers,
117
126
  packageManagers,
127
+ resolveAllConfiguredServicesV2,
128
+ resolveConfiguredServiceV2,
129
+ validateServiceConfigV2,
118
130
  workspaceManagers,
119
131
  ...require("./monorepos/get-monorepo-default-settings")
120
132
  });
@@ -33,4 +33,4 @@ export declare function detectServices(options: DetectServicesOptions): Promise<
33
33
  * Internal cron callback routes under `/_svc/{serviceName}/crons/{entry}/{handler}`
34
34
  * that rewrite to `/_svc/{serviceName}/index`.
35
35
  */
36
- export declare function generateServicesRoutes(services: Service[]): ServicesRoutes;
36
+ export declare function generateServicesRoutes(allServices: Service[]): ServicesRoutes;
@@ -26,6 +26,7 @@ var import_build_utils = require("@vercel/build-utils");
26
26
  var import_routing_utils = require("@vercel/routing-utils");
27
27
  var import_utils = require("./utils");
28
28
  var import_resolve = require("./resolve");
29
+ var import_resolve_v2 = require("./resolve-v2");
29
30
  var import_auto_detect = require("./auto-detect");
30
31
  var import_detect_railway = require("./detect-railway");
31
32
  var import_detect_render = require("./detect-render");
@@ -90,7 +91,8 @@ async function detectServices(options) {
90
91
  fs,
91
92
  workPath,
92
93
  detectEntrypoint,
93
- configuredServices: providedConfiguredServices
94
+ configuredServices: providedConfiguredServices,
95
+ configuredServicesType: providedConfiguredServicesType
94
96
  } = options;
95
97
  const scopedFs = workPath ? fs.chdir(workPath) : fs;
96
98
  const { config: vercelConfig, error: configError } = await (0, import_utils.readVercelConfig)(scopedFs);
@@ -105,9 +107,26 @@ async function detectServices(options) {
105
107
  });
106
108
  }
107
109
  const hasProvidedConfiguredServices = providedConfiguredServices && Object.keys(providedConfiguredServices).length > 0;
108
- const configuredServices = hasProvidedConfiguredServices ? providedConfiguredServices : vercelConfig?.experimentalServices;
109
- const hasConfiguredServices = configuredServices && Object.keys(configuredServices).length > 0;
110
- if (!hasConfiguredServices) {
110
+ const experimentalServicesV2 = hasProvidedConfiguredServices && providedConfiguredServicesType === "experimentalServicesV2" ? providedConfiguredServices : hasProvidedConfiguredServices ? void 0 : vercelConfig?.experimentalServicesV2;
111
+ if (experimentalServicesV2 && Object.keys(experimentalServicesV2).length > 0) {
112
+ const result2 = await (0, import_resolve_v2.resolveAllConfiguredServicesV2)(
113
+ experimentalServicesV2,
114
+ scopedFs
115
+ );
116
+ return withResolvedResult({
117
+ services: result2.services,
118
+ source: "configured",
119
+ // V2 uses explicit `bindings`, so no implicit `{NAME}_URL` injection.
120
+ useImplicitEnvInjection: false,
121
+ // V2 routes are explicitly carried per-service to output them separately.
122
+ routes: emptyRoutes(),
123
+ errors: result2.errors,
124
+ warnings: []
125
+ });
126
+ }
127
+ const experimentalServicesV1 = hasProvidedConfiguredServices ? providedConfiguredServices : vercelConfig?.experimentalServices;
128
+ const hasExperimentalServicesV1 = experimentalServicesV1 && Object.keys(experimentalServicesV1).length > 0;
129
+ if (!hasExperimentalServicesV1) {
111
130
  const detectors = [
112
131
  { detect: import_detect_railway.detectRailwayServices, source: "railway" },
113
132
  { detect: import_detect_render.detectRenderServices, source: "render" },
@@ -135,7 +154,7 @@ async function detectServices(options) {
135
154
  });
136
155
  }
137
156
  const result = await (0, import_resolve.resolveAllConfiguredServices)(
138
- configuredServices,
157
+ experimentalServicesV1,
139
158
  scopedFs,
140
159
  "configured"
141
160
  );
@@ -210,7 +229,8 @@ async function tryResolveInferred(detectResult, source, scopedFs) {
210
229
  inferred
211
230
  );
212
231
  }
213
- function generateServicesRoutes(services) {
232
+ function generateServicesRoutes(allServices) {
233
+ const services = allServices.filter(import_build_utils.isExperimentalService);
214
234
  const hostRewrites = [];
215
235
  const rewrites = [];
216
236
  const defaults = [];
@@ -1,6 +1,6 @@
1
1
  import type { Route } from '@vercel/routing-utils';
2
2
  import type { Builder } from '@vercel/build-utils';
3
- import type { ConfiguredServices, ResolvedService } from './types';
3
+ import type { ConfiguredServices, ConfiguredServicesType, Service } from './types';
4
4
  export interface ErrorResponse {
5
5
  code: string;
6
6
  message: string;
@@ -10,7 +10,7 @@ export interface ErrorResponse {
10
10
  export interface GetServicesBuildersOptions {
11
11
  workPath?: string;
12
12
  configuredServices?: ConfiguredServices;
13
- configuredServicesType?: 'experimentalServices';
13
+ configuredServicesType?: ConfiguredServicesType;
14
14
  projectFramework?: string | null;
15
15
  }
16
16
  export interface ServicesBuildersResult {
@@ -23,7 +23,7 @@ export interface ServicesBuildersResult {
23
23
  redirectRoutes: Route[] | null;
24
24
  rewriteRoutes: Route[] | null;
25
25
  errorRoutes: Route[] | null;
26
- services?: ResolvedService[];
26
+ services?: Service[];
27
27
  useImplicitEnvInjection?: boolean;
28
28
  }
29
29
  /**
@@ -33,3 +33,7 @@ export interface ServicesBuildersResult {
33
33
  * the shape expected by `detectBuilders` when `framework === 'services'`.
34
34
  */
35
35
  export declare function getServicesBuilders(options: GetServicesBuildersOptions): Promise<ServicesBuildersResult>;
36
+ /**
37
+ * Returns warnings for ignored directories that are not covered by services
38
+ */
39
+ export declare function warnIgnoredDirectories(files: string[], configuredServices: ConfiguredServices): ErrorResponse[];
@@ -18,7 +18,8 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var get_services_builders_exports = {};
20
20
  __export(get_services_builders_exports, {
21
- getServicesBuilders: () => getServicesBuilders
21
+ getServicesBuilders: () => getServicesBuilders,
22
+ warnIgnoredDirectories: () => warnIgnoredDirectories
22
23
  });
23
24
  module.exports = __toCommonJS(get_services_builders_exports);
24
25
  var import_detect_services = require("./detect-services");
@@ -134,7 +135,25 @@ async function getServicesBuilders(options) {
134
135
  useImplicitEnvInjection: result.useImplicitEnvInjection
135
136
  };
136
137
  }
138
+ function warnIgnoredDirectories(files, configuredServices) {
139
+ const warnings = [];
140
+ if (files.some((f) => f.startsWith("api/"))) {
141
+ const serviceCoversApi = Object.values(configuredServices).some((service) => {
142
+ const root = service.root ?? ".";
143
+ const entrypoint = service.entrypoint ?? "";
144
+ return root === "api" || root.startsWith("api/") || root === "." && entrypoint.startsWith("api/");
145
+ });
146
+ if (!serviceCoversApi) {
147
+ warnings.push({
148
+ code: "api_dir_ignored",
149
+ message: "The `api/` directory will not be built because `experimentalServices` is configured. To serve these files, declare them as a service in your `vercel.json`."
150
+ });
151
+ }
152
+ }
153
+ return warnings;
154
+ }
137
155
  // Annotate the CommonJS export names for ESM import in node:
138
156
  0 && (module.exports = {
139
- getServicesBuilders
157
+ getServicesBuilders,
158
+ warnIgnoredDirectories
140
159
  });
@@ -0,0 +1,11 @@
1
+ import type { ExperimentalServiceV2, ExperimentalServiceV2Config, ExperimentalServicesV2, ServiceDetectionError } from './types';
2
+ import type { DetectorFilesystem } from '../detectors/filesystem';
3
+ export declare function validateServiceConfigV2(name: string, config: ExperimentalServiceV2Config): ServiceDetectionError | null;
4
+ export declare function resolveConfiguredServiceV2(name: string, config: ExperimentalServiceV2Config, fs: DetectorFilesystem): Promise<{
5
+ service?: ExperimentalServiceV2;
6
+ error?: ServiceDetectionError;
7
+ }>;
8
+ export declare function resolveAllConfiguredServicesV2(services: ExperimentalServicesV2, fs: DetectorFilesystem): Promise<{
9
+ services: ExperimentalServiceV2[];
10
+ errors: ServiceDetectionError[];
11
+ }>;
@@ -0,0 +1,264 @@
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 resolve_v2_exports = {};
20
+ __export(resolve_v2_exports, {
21
+ resolveAllConfiguredServicesV2: () => resolveAllConfiguredServicesV2,
22
+ resolveConfiguredServiceV2: () => resolveConfiguredServiceV2,
23
+ validateServiceConfigV2: () => validateServiceConfigV2
24
+ });
25
+ module.exports = __toCommonJS(resolve_v2_exports);
26
+ var import_path = require("path");
27
+ var import_build_utils = require("@vercel/build-utils");
28
+ var import_frameworks = require("@vercel/frameworks");
29
+ var import_types = require("./types");
30
+ var import_resolve = require("./resolve");
31
+ var import_utils = require("./utils");
32
+ const frameworksBySlug = new Map(import_frameworks.frameworkList.map((f) => [f.slug, f]));
33
+ const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
34
+ function validateServiceConfigV2(name, config) {
35
+ if (!SERVICE_NAME_REGEX.test(name)) {
36
+ return {
37
+ code: "INVALID_SERVICE_NAME",
38
+ message: `Service name "${name}" is invalid. Names must start with a letter, end with an alphanumeric character, and contain only alphanumeric characters, hyphens, and underscores.`,
39
+ serviceName: name
40
+ };
41
+ }
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
+ if (typeof config.root !== "string" || config.root.length === 0) {
50
+ return {
51
+ code: "MISSING_ROOT",
52
+ message: `Service "${name}" must specify a "root".`,
53
+ serviceName: name
54
+ };
55
+ }
56
+ const normalizedRoot = import_path.posix.normalize(config.root);
57
+ if (normalizedRoot.startsWith("/")) {
58
+ return {
59
+ code: "INVALID_ROOT",
60
+ message: `Service "${name}" has invalid "root" "${config.root}". Must be a relative path.`,
61
+ serviceName: name
62
+ };
63
+ }
64
+ if (normalizedRoot === ".." || normalizedRoot.startsWith("../")) {
65
+ return {
66
+ code: "INVALID_ROOT",
67
+ message: `Service "${name}" has invalid "root" "${config.root}". Must not escape the project root.`,
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}".`,
75
+ serviceName: name
76
+ };
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
+ if (config.runtime && config.framework) {
86
+ const frameworkRuntime = (0, import_utils.inferRuntimeFromFramework)(config.framework);
87
+ if (frameworkRuntime && frameworkRuntime !== config.runtime) {
88
+ return {
89
+ code: "RUNTIME_FRAMEWORK_MISMATCH",
90
+ message: `Service "${name}" has conflicting runtime/framework: runtime "${config.runtime}" is incompatible with framework "${config.framework}" (runtime "${frameworkRuntime}").`,
91
+ serviceName: name
92
+ };
93
+ }
94
+ }
95
+ if (!config.framework && !config.entrypoint) {
96
+ return {
97
+ code: "MISSING_SERVICE_CONFIG",
98
+ message: `Service "${name}" must specify "framework" or "entrypoint".`,
99
+ serviceName: name
100
+ };
101
+ }
102
+ return null;
103
+ }
104
+ async function resolveConfiguredServiceV2(name, config, fs) {
105
+ const root = config.root;
106
+ const normalizedRoot = import_path.posix.normalize(root);
107
+ const serviceFsResult = normalizedRoot === "." ? { fs } : await (0, import_resolve.getServiceFs)(fs, name, root);
108
+ if (serviceFsResult.error) {
109
+ return { error: serviceFsResult.error };
110
+ }
111
+ const serviceFs = serviceFsResult.fs;
112
+ const rawEntrypoint = config.entrypoint;
113
+ const moduleAttr = typeof rawEntrypoint === "string" ? (0, import_resolve.parsePyModuleAttrEntrypoint)(rawEntrypoint) : null;
114
+ let normalizedEntrypoint;
115
+ let entrypointIsDirectory = false;
116
+ if (typeof rawEntrypoint === "string") {
117
+ const entrypointToResolve = moduleAttr ? moduleAttr.filePath : rawEntrypoint;
118
+ const resolved = await (0, import_resolve.resolveEntrypointPath)({
119
+ fs: serviceFs,
120
+ serviceName: name,
121
+ entrypoint: entrypointToResolve
122
+ });
123
+ if (resolved.error) {
124
+ return { error: resolved.error };
125
+ }
126
+ normalizedEntrypoint = resolved.entrypoint?.normalized;
127
+ entrypointIsDirectory = Boolean(resolved.entrypoint?.isDirectory);
128
+ }
129
+ const entrypointFile = entrypointIsDirectory || !normalizedEntrypoint ? void 0 : normalizedEntrypoint;
130
+ const inferredRuntime = (0, import_utils.inferServiceRuntime)({
131
+ runtime: config.runtime,
132
+ framework: config.framework,
133
+ entrypoint: entrypointFile
134
+ });
135
+ let framework = config.framework;
136
+ if (!framework && normalizedEntrypoint) {
137
+ const workspace = entrypointIsDirectory ? normalizedEntrypoint : import_path.posix.dirname(normalizedEntrypoint) || ".";
138
+ const detection = await (0, import_resolve.detectFrameworkFromWorkspace)({
139
+ fs: serviceFs,
140
+ workspace,
141
+ serviceName: name,
142
+ runtime: inferredRuntime
143
+ });
144
+ if (detection.error) {
145
+ return { error: detection.error };
146
+ }
147
+ framework = detection.framework;
148
+ }
149
+ if (entrypointIsDirectory && !framework) {
150
+ return {
151
+ error: {
152
+ code: "MISSING_SERVICE_FRAMEWORK",
153
+ message: `Service "${name}" uses directory entrypoint "${config.entrypoint}" but no framework could be detected. Specify "framework" explicitly or use a file entrypoint.`,
154
+ serviceName: name
155
+ }
156
+ };
157
+ }
158
+ const frameworkDefinition = framework ? frameworksBySlug.get(framework) : void 0;
159
+ let builderUse;
160
+ let builderSrc;
161
+ if (framework) {
162
+ builderUse = (0, import_build_utils.isNodeBackendFramework)(framework) ? "@vercel/backends" : frameworkDefinition?.useRuntime?.use || "@vercel/static-build";
163
+ builderSrc = entrypointFile || frameworkDefinition?.useRuntime?.src || "package.json";
164
+ } else {
165
+ if (!inferredRuntime) {
166
+ return {
167
+ error: {
168
+ code: "MISSING_SERVICE_CONFIG",
169
+ message: `Service "${name}" must specify "framework" or a runtime-resolvable "entrypoint".`,
170
+ serviceName: name
171
+ }
172
+ };
173
+ }
174
+ builderUse = inferredRuntime === "node" ? "@vercel/backends" : (0, import_utils.getBuilderForRuntime)(inferredRuntime);
175
+ builderSrc = entrypointFile;
176
+ }
177
+ const isRoot = normalizedRoot === ".";
178
+ const projectRelativeSrc = isRoot ? builderSrc : import_path.posix.join(normalizedRoot, builderSrc);
179
+ const builderConfig = { zeroConfig: true };
180
+ if (builderUse === "@vercel/backends") {
181
+ builderConfig.serviceName = name;
182
+ }
183
+ if (framework) {
184
+ builderConfig.framework = framework;
185
+ }
186
+ if (!isRoot) {
187
+ builderConfig.workspace = normalizedRoot;
188
+ }
189
+ if (moduleAttr) {
190
+ builderConfig.handlerFunction = moduleAttr.attrName;
191
+ }
192
+ const runtime = import_types.STATIC_BUILDERS.has(builderUse) ? void 0 : inferredRuntime;
193
+ return {
194
+ service: {
195
+ schema: "experimentalServicesV2",
196
+ name,
197
+ root,
198
+ framework,
199
+ runtime,
200
+ entrypoint: entrypointFile,
201
+ builder: {
202
+ src: projectRelativeSrc,
203
+ use: builderUse,
204
+ config: builderConfig
205
+ },
206
+ installCommand: config.installCommand,
207
+ buildCommand: config.buildCommand,
208
+ devCommand: config.devCommand,
209
+ ignoreCommand: config.ignoreCommand,
210
+ outputDirectory: config.outputDirectory,
211
+ bindings: config.bindings,
212
+ functions: config.functions,
213
+ headers: config.headers,
214
+ redirects: config.redirects,
215
+ rewrites: config.rewrites,
216
+ routes: config.routes,
217
+ cleanUrls: config.cleanUrls,
218
+ trailingSlash: config.trailingSlash
219
+ }
220
+ };
221
+ }
222
+ async function resolveAllConfiguredServicesV2(services, fs) {
223
+ const resolved = [];
224
+ const errors = [];
225
+ for (const name of Object.keys(services)) {
226
+ const config = services[name];
227
+ const validationError = validateServiceConfigV2(name, config);
228
+ if (validationError) {
229
+ errors.push(validationError);
230
+ continue;
231
+ }
232
+ const { service, error } = await resolveConfiguredServiceV2(
233
+ name,
234
+ config,
235
+ fs
236
+ );
237
+ if (error) {
238
+ errors.push(error);
239
+ continue;
240
+ }
241
+ if (service) {
242
+ resolved.push(service);
243
+ }
244
+ }
245
+ const serviceNames = new Set(Object.keys(services));
246
+ for (const service of resolved) {
247
+ for (const binding of service.bindings ?? []) {
248
+ if (!serviceNames.has(binding.service)) {
249
+ errors.push({
250
+ code: "UNKNOWN_SERVICE_BINDING",
251
+ message: `Service "${service.name}" declares a binding to unknown service "${binding.service}".`,
252
+ serviceName: service.name
253
+ });
254
+ }
255
+ }
256
+ }
257
+ return { services: resolved, errors };
258
+ }
259
+ // Annotate the CommonJS export names for ESM import in node:
260
+ 0 && (module.exports = {
261
+ resolveAllConfiguredServicesV2,
262
+ resolveConfiguredServiceV2,
263
+ validateServiceConfigV2
264
+ });
@@ -1,10 +1,26 @@
1
- import type { Service, ConfiguredServices, ExperimentalServiceConfig, ServiceDetectionError } from './types';
1
+ import type { ExperimentalService, ConfiguredServices, ExperimentalServiceConfig, ServiceDetectionError, ServiceRuntime } from './types';
2
2
  import type { DetectorFilesystem } from '../detectors/filesystem';
3
+ export declare function parsePyModuleAttrEntrypoint(entrypoint: string): {
4
+ attrName: string;
5
+ filePath: string;
6
+ } | null;
3
7
  type ConfiguredServiceConfig = ExperimentalServiceConfig;
4
8
  interface ResolvedEntrypointPath {
5
9
  normalized: string;
6
10
  isDirectory: boolean;
7
11
  }
12
+ export declare function getServiceFs(fs: DetectorFilesystem, serviceName: string, root?: string): Promise<{
13
+ fs: DetectorFilesystem;
14
+ error?: ServiceDetectionError;
15
+ }>;
16
+ export declare function resolveEntrypointPath({ fs, serviceName, entrypoint, }: {
17
+ fs: DetectorFilesystem;
18
+ serviceName: string;
19
+ entrypoint: string;
20
+ }): Promise<{
21
+ entrypoint?: ResolvedEntrypointPath;
22
+ error?: ServiceDetectionError;
23
+ }>;
8
24
  type RoutePrefixSource = 'configured' | 'generated';
9
25
  interface ResolveConfiguredServiceOptions {
10
26
  name: string;
@@ -19,6 +35,20 @@ interface ResolveConfiguredServiceOptions {
19
35
  interface ResolveAllConfiguredServicesOptions {
20
36
  requireFileEntrypointForBackendRuntimes?: boolean;
21
37
  }
38
+ export declare function inferWorkspaceFromNearestManifest({ fs, entrypoint, runtime, }: {
39
+ fs: DetectorFilesystem;
40
+ entrypoint?: string;
41
+ runtime?: ServiceRuntime;
42
+ }): Promise<string | undefined>;
43
+ export declare function detectFrameworkFromWorkspace({ fs, workspace, serviceName, runtime, }: {
44
+ fs: DetectorFilesystem;
45
+ workspace: string;
46
+ serviceName: string;
47
+ runtime?: ServiceRuntime;
48
+ }): Promise<{
49
+ framework?: string;
50
+ error?: ServiceDetectionError;
51
+ }>;
22
52
  /**
23
53
  * Validate a service configuration from vercel.json services.
24
54
  */
@@ -27,13 +57,13 @@ export declare function validateServiceEntrypoint(name: string, config: Configur
27
57
  /**
28
58
  * Resolve a single service from user configuration.
29
59
  */
30
- export declare function resolveConfiguredService(options: ResolveConfiguredServiceOptions): Promise<Service>;
60
+ export declare function resolveConfiguredService(options: ResolveConfiguredServiceOptions): Promise<ExperimentalService>;
31
61
  /**
32
62
  * Resolve all services from vercel.json services.
33
63
  * Validates each service configuration.
34
64
  */
35
65
  export declare function resolveAllConfiguredServices(services: ConfiguredServices, fs: DetectorFilesystem, routePrefixSource?: RoutePrefixSource, options?: ResolveAllConfiguredServicesOptions): Promise<{
36
- services: Service[];
66
+ services: ExperimentalService[];
37
67
  errors: ServiceDetectionError[];
38
68
  }>;
39
69
  export {};
@@ -18,8 +18,13 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var resolve_exports = {};
20
20
  __export(resolve_exports, {
21
+ detectFrameworkFromWorkspace: () => detectFrameworkFromWorkspace,
22
+ getServiceFs: () => getServiceFs,
23
+ inferWorkspaceFromNearestManifest: () => inferWorkspaceFromNearestManifest,
24
+ parsePyModuleAttrEntrypoint: () => parsePyModuleAttrEntrypoint,
21
25
  resolveAllConfiguredServices: () => resolveAllConfiguredServices,
22
26
  resolveConfiguredService: () => resolveConfiguredService,
27
+ resolveEntrypointPath: () => resolveEntrypointPath,
23
28
  validateServiceConfig: () => validateServiceConfig,
24
29
  validateServiceEntrypoint: () => validateServiceEntrypoint
25
30
  });
@@ -666,6 +671,7 @@ async function resolveConfiguredService(options) {
666
671
  builderConfig.handlerFunction = moduleAttrParsed.attrName;
667
672
  }
668
673
  return {
674
+ schema: "experimentalServices",
669
675
  name,
670
676
  type,
671
677
  trigger,
@@ -870,8 +876,13 @@ function validateEnvRefs(env, serviceName, servicesByName, errors) {
870
876
  }
871
877
  // Annotate the CommonJS export names for ESM import in node:
872
878
  0 && (module.exports = {
879
+ detectFrameworkFromWorkspace,
880
+ getServiceFs,
881
+ inferWorkspaceFromNearestManifest,
882
+ parsePyModuleAttrEntrypoint,
873
883
  resolveAllConfiguredServices,
874
884
  resolveConfiguredService,
885
+ resolveEntrypointPath,
875
886
  validateServiceConfig,
876
887
  validateServiceEntrypoint
877
888
  });
@@ -1,7 +1,7 @@
1
1
  import type { Route } from '@vercel/routing-utils';
2
- import type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder } from '@vercel/build-utils';
2
+ import type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceV2Config, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServicesV2, ExperimentalServiceV2Binding, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder } from '@vercel/build-utils';
3
3
  import type { DetectorFilesystem } from '../detectors/filesystem';
4
- export type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder, };
4
+ export type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder, };
5
5
  /**
6
6
  * @deprecated Use `Service` instead
7
7
  */
@@ -9,7 +9,7 @@ export type ResolvedService = Service;
9
9
  export interface DetectServicesOptions {
10
10
  fs: DetectorFilesystem;
11
11
  configuredServices?: ConfiguredServices;
12
- configuredServicesType?: 'experimentalServices';
12
+ configuredServicesType?: ConfiguredServicesType;
13
13
  /**
14
14
  * Working directory path (relative to fs root).
15
15
  * If provided, vercel.json is read from this path.
@@ -42,7 +42,8 @@ export interface ServicesRoutes {
42
42
  */
43
43
  workers: Route[];
44
44
  }
45
- export type ConfiguredServices = ExperimentalServices;
45
+ export type ConfiguredServicesType = 'experimentalServices' | 'experimentalServicesV2';
46
+ export type ConfiguredServices = ExperimentalServices | ExperimentalServicesV2;
46
47
  export type InferredServicesConfig = ExperimentalServices;
47
48
  export interface ResolvedServicesResult {
48
49
  services: Service[];
@@ -55,13 +56,13 @@ export interface ResolvedServicesResult {
55
56
  export interface InferredServicesResult {
56
57
  source: 'layout' | 'procfile' | 'railway' | 'render';
57
58
  config: InferredServicesConfig;
58
- services: Service[];
59
+ services: ExperimentalService[];
59
60
  warnings: ServiceDetectionWarning[];
60
61
  }
61
62
  export interface DetectServicesResult extends ResolvedServicesResult {
62
63
  /**
63
64
  * Source of service definitions:
64
- * - `configured`: loaded from explicit project configuration (`vercel.json#experimentalServices`)
65
+ * - `configured`: loaded from explicit project configuration (`vercel.json#experimentalServices` or `vercel.json#experimentalServicesV2`)
65
66
  * - `auto-detected`: inferred from project structure
66
67
  */
67
68
  resolved: ResolvedServicesResult;
@@ -1,7 +1,7 @@
1
1
  import { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath } from '@vercel/build-utils';
2
2
  import type { Framework } from '@vercel/frameworks';
3
3
  import type { DetectorFilesystem } from '../detectors/filesystem';
4
- import type { ServiceRuntime, ExperimentalServices, ServiceDetectionError, ServiceDetectionWarning, ResolvedService } from './types';
4
+ import type { ServiceRuntime, ExperimentalServices, ExperimentalServicesV2, ServiceDetectionError, ServiceDetectionWarning, ResolvedService } from './types';
5
5
  export declare const DETECTION_FRAMEWORKS: Framework[];
6
6
  export { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath, };
7
7
  export declare function hasFile(fs: DetectorFilesystem, filePath: string): Promise<boolean>;
@@ -56,6 +56,7 @@ export declare function inferServiceRuntime(config: {
56
56
  export interface ReadVercelConfigResult {
57
57
  config: {
58
58
  experimentalServices?: ExperimentalServices;
59
+ experimentalServicesV2?: ExperimentalServicesV2;
59
60
  } | null;
60
61
  error: ServiceDetectionError | null;
61
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "6.7.7",
3
+ "version": "6.8.0",
4
4
  "description": "Vercel filesystem detectors",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,10 +20,10 @@
20
20
  "minimatch": "3.1.2",
21
21
  "semver": "6.3.1",
22
22
  "smol-toml": "1.5.2",
23
- "@vercel/build-utils": "13.27.0",
24
- "@vercel/routing-utils": "6.2.0",
25
- "@vercel/frameworks": "3.27.0",
26
- "@vercel/error-utils": "2.2.0"
23
+ "@vercel/error-utils": "2.2.0",
24
+ "@vercel/build-utils": "13.27.2",
25
+ "@vercel/frameworks": "3.28.0",
26
+ "@vercel/routing-utils": "6.2.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/glob": "7.2.0",