@vercel/fs-detectors 6.2.1 → 6.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/detect-builders.d.ts +2 -1
- package/dist/detect-builders.js +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/services/auto-detect.d.ts +1 -1
- package/dist/services/auto-detect.js +5 -5
- package/dist/services/detect-railway.js +2 -2
- package/dist/services/detect-services.d.ts +1 -1
- package/dist/services/detect-services.js +9 -3
- package/dist/services/get-services-builders.js +1 -1
- package/dist/services/resolve.d.ts +11 -7
- package/dist/services/resolve.js +81 -14
- package/dist/services/types.d.ts +6 -5
- package/dist/services/utils.d.ts +6 -1
- package/dist/services/utils.js +26 -2
- package/package.json +4 -4
|
@@ -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, Services, ExperimentalServices, ProjectSettings, Service } from '@vercel/build-utils';
|
|
3
3
|
/**
|
|
4
4
|
* Pattern for finding all supported middleware files.
|
|
5
5
|
*/
|
|
@@ -22,6 +22,7 @@ export interface ErrorResponse {
|
|
|
22
22
|
export interface Options {
|
|
23
23
|
tag?: string;
|
|
24
24
|
functions?: BuilderFunctions;
|
|
25
|
+
services?: Services;
|
|
25
26
|
experimentalServices?: ExperimentalServices;
|
|
26
27
|
ignoreBuildScript?: boolean;
|
|
27
28
|
projectSettings?: ProjectSettings;
|
package/dist/detect-builders.js
CHANGED
|
@@ -92,13 +92,14 @@ function detectOutputDirectory(builders) {
|
|
|
92
92
|
return publicBuilder ? publicBuilder.src.replace("/**/*", "") : null;
|
|
93
93
|
}
|
|
94
94
|
async function detectBuilders(files, pkg, options = {}) {
|
|
95
|
-
const {
|
|
95
|
+
const { services, experimentalServices, projectSettings = {} } = options;
|
|
96
96
|
const { framework } = projectSettings;
|
|
97
|
-
const
|
|
97
|
+
const configuredServices = services ?? experimentalServices;
|
|
98
|
+
const hasServicesConfig = configuredServices != null && typeof configuredServices === "object";
|
|
98
99
|
if (hasServicesConfig || framework === "services") {
|
|
99
100
|
return (0, import_get_services_builders.getServicesBuilders)({
|
|
100
101
|
workPath: options.workPath,
|
|
101
|
-
configuredServices
|
|
102
|
+
configuredServices,
|
|
102
103
|
projectFramework: framework
|
|
103
104
|
});
|
|
104
105
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { autoDetectServices } from './services/auto-detect';
|
|
|
4
4
|
export type { AutoDetectOptions, AutoDetectResult, } from './services/auto-detect';
|
|
5
5
|
export { isStaticBuild, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPath, getInternalServiceCronPathPrefix, getInternalServiceWorkerPath, getInternalServiceWorkerPathPrefix, } from './services/utils';
|
|
6
6
|
export { getServicesBuilders } from './services/get-services-builders';
|
|
7
|
-
export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource,
|
|
7
|
+
export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, InferredServicesConfig, ResolvedServicesResult, InferredServicesResult, ResolvedService, Service, ServicesRoutes, ServiceDetectionError, } from './services/types';
|
|
8
8
|
export { detectFileSystemAPI } from './detect-file-system-api';
|
|
9
9
|
export { detectFramework, detectFrameworks, detectFrameworkRecord, detectFrameworkVersion, } from './detect-framework';
|
|
10
10
|
export { getProjectPaths } from './get-project-paths';
|
|
@@ -8,7 +8,7 @@ export interface AutoDetectResult {
|
|
|
8
8
|
errors: ServiceDetectionError[];
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Auto-detect services when
|
|
11
|
+
* Auto-detect services when services are not configured.
|
|
12
12
|
*
|
|
13
13
|
* Scans the project for frameworks, supporting multiple layouts:
|
|
14
14
|
*
|
|
@@ -44,7 +44,7 @@ async function autoDetectServices(options) {
|
|
|
44
44
|
errors: [
|
|
45
45
|
{
|
|
46
46
|
code: "MULTIPLE_FRAMEWORKS_ROOT",
|
|
47
|
-
message: `Multiple frameworks detected at root: ${frameworkNames}. Use explicit
|
|
47
|
+
message: `Multiple frameworks detected at root: ${frameworkNames}. Use explicit services config.`
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
50
|
};
|
|
@@ -69,7 +69,7 @@ async function autoDetectServices(options) {
|
|
|
69
69
|
errors: [
|
|
70
70
|
{
|
|
71
71
|
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
72
|
-
message: `Multiple frameworks detected in ${frontendLocation}/: ${frameworkNames}. Use explicit
|
|
72
|
+
message: `Multiple frameworks detected in ${frontendLocation}/: ${frameworkNames}. Use explicit services config.`
|
|
73
73
|
}
|
|
74
74
|
]
|
|
75
75
|
};
|
|
@@ -87,7 +87,7 @@ async function autoDetectServices(options) {
|
|
|
87
87
|
errors: [
|
|
88
88
|
{
|
|
89
89
|
code: "NO_SERVICES_CONFIGURED",
|
|
90
|
-
message: "No services detected. Configure
|
|
90
|
+
message: "No services detected. Configure services in vercel.json or ensure a framework exists at project root, frontend/, or apps/web/."
|
|
91
91
|
}
|
|
92
92
|
]
|
|
93
93
|
};
|
|
@@ -168,7 +168,7 @@ async function detectBackendServices(fs) {
|
|
|
168
168
|
services: {},
|
|
169
169
|
error: {
|
|
170
170
|
code: "SERVICE_NAME_CONFLICT",
|
|
171
|
-
message: `Service name conflict: "${serviceName}" exists in both ${BACKEND_DIR}/ and ${SERVICES_DIR}/${serviceName}/. Rename one of the directories or use explicit
|
|
171
|
+
message: `Service name conflict: "${serviceName}" exists in both ${BACKEND_DIR}/ and ${SERVICES_DIR}/${serviceName}/. Rename one of the directories or use explicit services config.`,
|
|
172
172
|
serviceName
|
|
173
173
|
}
|
|
174
174
|
};
|
|
@@ -217,7 +217,7 @@ async function detectServiceInDir(fs, dirPath, serviceName) {
|
|
|
217
217
|
return {
|
|
218
218
|
error: {
|
|
219
219
|
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
220
|
-
message: `Multiple frameworks detected in ${dirPath}/: ${frameworkNames}. Use explicit
|
|
220
|
+
message: `Multiple frameworks detected in ${dirPath}/: ${frameworkNames}. Use explicit services config.`,
|
|
221
221
|
serviceName
|
|
222
222
|
}
|
|
223
223
|
};
|
|
@@ -109,7 +109,7 @@ async function detectRailwayServices(options) {
|
|
|
109
109
|
if (frameworks.length === 0) {
|
|
110
110
|
warnings.push({
|
|
111
111
|
code: "SERVICE_SKIPPED",
|
|
112
|
-
message: `Skipped service in ${dirLabel}/: no framework detected. Configure it manually in
|
|
112
|
+
message: `Skipped service in ${dirLabel}/: no framework detected. Configure it manually in services.`
|
|
113
113
|
});
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
@@ -117,7 +117,7 @@ async function detectRailwayServices(options) {
|
|
|
117
117
|
const names = frameworks.map((f) => f.name).join(", ");
|
|
118
118
|
errors.push({
|
|
119
119
|
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
120
|
-
message: `Multiple frameworks detected in ${dirLabel}/: ${names}. Use explicit
|
|
120
|
+
message: `Multiple frameworks detected in ${dirLabel}/: ${names}. Use explicit services config.`,
|
|
121
121
|
serviceName
|
|
122
122
|
});
|
|
123
123
|
continue;
|
|
@@ -2,7 +2,7 @@ import { type DetectServicesOptions, type DetectServicesResult, type Service, ty
|
|
|
2
2
|
/**
|
|
3
3
|
* Detect and resolve services within a project.
|
|
4
4
|
*
|
|
5
|
-
* Reads vercel.json and resolves
|
|
5
|
+
* Reads vercel.json and resolves configured services into Service objects.
|
|
6
6
|
* Returns an error if no services are configured.
|
|
7
7
|
*/
|
|
8
8
|
export declare function detectServices(options: DetectServicesOptions): Promise<DetectServicesResult>;
|
|
@@ -86,7 +86,8 @@ async function detectServices(options) {
|
|
|
86
86
|
warnings: []
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
-
const
|
|
89
|
+
const hasNonEmptyPublicServicesConfig = vercelConfig?.services && Object.keys(vercelConfig.services).length > 0;
|
|
90
|
+
const configuredServices = hasNonEmptyPublicServicesConfig ? vercelConfig.services : vercelConfig?.experimentalServices;
|
|
90
91
|
const hasConfiguredServices = configuredServices && Object.keys(configuredServices).length > 0;
|
|
91
92
|
if (!hasConfiguredServices) {
|
|
92
93
|
const railwayResult = await (0, import_detect_railway.detectRailwayServices)({ fs: scopedFs });
|
|
@@ -163,7 +164,7 @@ async function detectServices(options) {
|
|
|
163
164
|
errors: [
|
|
164
165
|
{
|
|
165
166
|
code: "NO_SERVICES_CONFIGURED",
|
|
166
|
-
message: "No services configured. Add `
|
|
167
|
+
message: "No services configured. Add `services` to vercel.json."
|
|
167
168
|
}
|
|
168
169
|
],
|
|
169
170
|
warnings: []
|
|
@@ -172,7 +173,12 @@ async function detectServices(options) {
|
|
|
172
173
|
const result = await (0, import_resolve.resolveAllConfiguredServices)(
|
|
173
174
|
configuredServices,
|
|
174
175
|
scopedFs,
|
|
175
|
-
"configured"
|
|
176
|
+
"configured",
|
|
177
|
+
{
|
|
178
|
+
requireFileEntrypointForBackendRuntimes: Boolean(
|
|
179
|
+
hasNonEmptyPublicServicesConfig
|
|
180
|
+
)
|
|
181
|
+
}
|
|
176
182
|
);
|
|
177
183
|
const routes = generateServicesRoutes(result.services);
|
|
178
184
|
return withResolvedResult({
|
|
@@ -94,7 +94,7 @@ async function getServicesBuilders(options) {
|
|
|
94
94
|
errors: [
|
|
95
95
|
{
|
|
96
96
|
code: "NO_SERVICES_CONFIGURED",
|
|
97
|
-
message: "No services configured. Add `
|
|
97
|
+
message: "No services configured. Add `services` to vercel.json."
|
|
98
98
|
}
|
|
99
99
|
],
|
|
100
100
|
warnings: warningResponses,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Service, ExperimentalServiceConfig,
|
|
1
|
+
import type { Service, ConfiguredServices, ExperimentalServiceConfig, ServiceConfig, ServiceDetectionError } from './types';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
+
type ConfiguredServiceConfig = (ServiceConfig | ExperimentalServiceConfig) & Partial<ExperimentalServiceConfig>;
|
|
3
4
|
interface ResolvedEntrypointPath {
|
|
4
5
|
normalized: string;
|
|
5
6
|
isDirectory: boolean;
|
|
@@ -7,7 +8,7 @@ interface ResolvedEntrypointPath {
|
|
|
7
8
|
type RoutePrefixSource = 'configured' | 'generated';
|
|
8
9
|
interface ResolveConfiguredServiceOptions {
|
|
9
10
|
name: string;
|
|
10
|
-
config:
|
|
11
|
+
config: ConfiguredServiceConfig;
|
|
11
12
|
/** Filesystem scoped to the service root (via chdir) when root is set, otherwise the project-level fs. */
|
|
12
13
|
serviceFs: DetectorFilesystem;
|
|
13
14
|
root?: string;
|
|
@@ -15,20 +16,23 @@ interface ResolveConfiguredServiceOptions {
|
|
|
15
16
|
resolvedEntrypoint?: ResolvedEntrypointPath;
|
|
16
17
|
routePrefixSource?: RoutePrefixSource;
|
|
17
18
|
}
|
|
19
|
+
interface ResolveAllConfiguredServicesOptions {
|
|
20
|
+
requireFileEntrypointForBackendRuntimes?: boolean;
|
|
21
|
+
}
|
|
18
22
|
/**
|
|
19
|
-
* Validate a service configuration from vercel.json
|
|
23
|
+
* Validate a service configuration from vercel.json services.
|
|
20
24
|
*/
|
|
21
|
-
export declare function validateServiceConfig(name: string, config:
|
|
22
|
-
export declare function validateServiceEntrypoint(name: string, config:
|
|
25
|
+
export declare function validateServiceConfig(name: string, config: ConfiguredServiceConfig, options?: ResolveAllConfiguredServicesOptions): ServiceDetectionError | null;
|
|
26
|
+
export declare function validateServiceEntrypoint(name: string, config: ConfiguredServiceConfig, resolvedEntrypoint: ResolvedEntrypointPath): ServiceDetectionError | null;
|
|
23
27
|
/**
|
|
24
28
|
* Resolve a single service from user configuration.
|
|
25
29
|
*/
|
|
26
30
|
export declare function resolveConfiguredService(options: ResolveConfiguredServiceOptions): Promise<Service>;
|
|
27
31
|
/**
|
|
28
|
-
* Resolve all services from vercel.json
|
|
32
|
+
* Resolve all services from vercel.json services.
|
|
29
33
|
* Validates each service configuration.
|
|
30
34
|
*/
|
|
31
|
-
export declare function resolveAllConfiguredServices(services:
|
|
35
|
+
export declare function resolveAllConfiguredServices(services: ConfiguredServices, fs: DetectorFilesystem, routePrefixSource?: RoutePrefixSource, options?: ResolveAllConfiguredServicesOptions): Promise<{
|
|
32
36
|
services: Service[];
|
|
33
37
|
errors: ServiceDetectionError[];
|
|
34
38
|
}>;
|
package/dist/services/resolve.js
CHANGED
|
@@ -46,6 +46,11 @@ function parsePyModuleAttrEntrypoint(entrypoint) {
|
|
|
46
46
|
const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
47
47
|
const DNS_LABEL_RE = /^(?!-)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
48
48
|
const ENV_PREFIX_RE = /^[A-Z][A-Z0-9_]*_$/;
|
|
49
|
+
const ENTRYPOINT_REQUIRED_RUNTIMES = /* @__PURE__ */ new Set([
|
|
50
|
+
"node",
|
|
51
|
+
"python",
|
|
52
|
+
"go"
|
|
53
|
+
]);
|
|
49
54
|
async function getServiceFs(fs, serviceName, root) {
|
|
50
55
|
if (!root) {
|
|
51
56
|
return { fs };
|
|
@@ -77,6 +82,38 @@ function normalizeServiceEntrypoint(entrypoint) {
|
|
|
77
82
|
const normalized = import_path.posix.normalize(entrypoint);
|
|
78
83
|
return normalized === "" ? "." : normalized;
|
|
79
84
|
}
|
|
85
|
+
function getEffectiveServiceTrigger(config) {
|
|
86
|
+
if (config.type === "cron") {
|
|
87
|
+
return "schedule";
|
|
88
|
+
}
|
|
89
|
+
if (config.type === "worker") {
|
|
90
|
+
return "queue";
|
|
91
|
+
}
|
|
92
|
+
if (config.type !== "job") {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
return config.trigger;
|
|
96
|
+
}
|
|
97
|
+
function getEntrypointRequiredRuntime(config) {
|
|
98
|
+
if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
|
|
99
|
+
return config.runtime;
|
|
100
|
+
}
|
|
101
|
+
return (0, import_utils.inferRuntimeFromFramework)(config.framework);
|
|
102
|
+
}
|
|
103
|
+
function validateBackendFileEntrypoint(name, config, resolvedEntrypoint, options) {
|
|
104
|
+
if (!options.requireFileEntrypointForBackendRuntimes || !resolvedEntrypoint?.isDirectory) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const runtime = getEntrypointRequiredRuntime(config);
|
|
108
|
+
if (!runtime || !ENTRYPOINT_REQUIRED_RUNTIMES.has(runtime)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
code: "INVALID_ENTRYPOINT",
|
|
113
|
+
message: `Service "${name}" must specify a file "entrypoint" when using "${config.runtime ? "runtime" : "framework"}" "${config.runtime || config.framework}".`,
|
|
114
|
+
serviceName: name
|
|
115
|
+
};
|
|
116
|
+
}
|
|
80
117
|
async function resolveEntrypointPath({
|
|
81
118
|
fs,
|
|
82
119
|
serviceName,
|
|
@@ -171,7 +208,7 @@ async function detectFrameworkFromWorkspace({
|
|
|
171
208
|
return {
|
|
172
209
|
error: {
|
|
173
210
|
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
174
|
-
message: `Multiple frameworks detected in ${workspace === "." ? "project root" : `${workspace}/`}: ${frameworkNames}. Specify "framework" explicitly in
|
|
211
|
+
message: `Multiple frameworks detected in ${workspace === "." ? "project root" : `${workspace}/`}: ${frameworkNames}. Specify "framework" explicitly in services.`,
|
|
175
212
|
serviceName
|
|
176
213
|
}
|
|
177
214
|
};
|
|
@@ -265,7 +302,7 @@ function resolveServiceRoutingConfig(name, config) {
|
|
|
265
302
|
}
|
|
266
303
|
};
|
|
267
304
|
}
|
|
268
|
-
function validateServiceConfig(name, config) {
|
|
305
|
+
function validateServiceConfig(name, config, options = {}) {
|
|
269
306
|
if (!SERVICE_NAME_REGEX.test(name)) {
|
|
270
307
|
return {
|
|
271
308
|
code: "INVALID_SERVICE_NAME",
|
|
@@ -281,13 +318,15 @@ function validateServiceConfig(name, config) {
|
|
|
281
318
|
};
|
|
282
319
|
}
|
|
283
320
|
const serviceType = config.type || "web";
|
|
284
|
-
const
|
|
285
|
-
const
|
|
321
|
+
const effectiveTrigger = getEffectiveServiceTrigger(config);
|
|
322
|
+
const effectiveService = {
|
|
286
323
|
type: serviceType,
|
|
287
|
-
trigger:
|
|
288
|
-
}
|
|
289
|
-
const
|
|
290
|
-
const
|
|
324
|
+
trigger: effectiveTrigger
|
|
325
|
+
};
|
|
326
|
+
const isJobService = serviceType === "job" || serviceType === "cron";
|
|
327
|
+
const isScheduleJobService = (0, import_build_utils.isScheduleTriggeredService)(effectiveService);
|
|
328
|
+
const isQueueJobService = serviceType === "job" && (0, import_build_utils.isQueueTriggeredService)(effectiveService);
|
|
329
|
+
const isWorkflowService = serviceType === "job" && effectiveTrigger === "workflow";
|
|
291
330
|
const isNonWebService = serviceType === "worker" || isJobService;
|
|
292
331
|
const serviceTypeLabel = isJobService ? "Job" : serviceType === "worker" ? "Worker" : "Web";
|
|
293
332
|
const routingResult = resolveServiceRoutingConfig(name, config);
|
|
@@ -333,17 +372,17 @@ function validateServiceConfig(name, config) {
|
|
|
333
372
|
serviceName: name
|
|
334
373
|
};
|
|
335
374
|
}
|
|
336
|
-
if (serviceType === "job" &&
|
|
375
|
+
if (serviceType === "job" && effectiveTrigger === void 0) {
|
|
337
376
|
return {
|
|
338
377
|
code: "MISSING_JOB_TRIGGER",
|
|
339
378
|
message: `Job service "${name}" is missing required "trigger" field.`,
|
|
340
379
|
serviceName: name
|
|
341
380
|
};
|
|
342
381
|
}
|
|
343
|
-
if (serviceType === "job" &&
|
|
382
|
+
if (serviceType === "job" && effectiveTrigger && !import_build_utils.JOB_TRIGGERS.includes(effectiveTrigger)) {
|
|
344
383
|
return {
|
|
345
384
|
code: "INVALID_JOB_TRIGGER",
|
|
346
|
-
message: `Job service "${name}" has invalid trigger "${
|
|
385
|
+
message: `Job service "${name}" has invalid trigger "${effectiveTrigger}". Expected ${import_build_utils.JOB_TRIGGERS.map((t) => `"${t}"`).join(", ")}.`,
|
|
347
386
|
serviceName: name
|
|
348
387
|
};
|
|
349
388
|
}
|
|
@@ -421,6 +460,7 @@ function validateServiceConfig(name, config) {
|
|
|
421
460
|
const hasFramework = Boolean(config.framework);
|
|
422
461
|
const hasBuilderOrRuntime = Boolean(config.builder || config.runtime);
|
|
423
462
|
const hasEntrypoint = Boolean(config.entrypoint);
|
|
463
|
+
const entrypointRequiredRuntime = getEntrypointRequiredRuntime(config);
|
|
424
464
|
if (!hasFramework && !hasBuilderOrRuntime && !hasEntrypoint) {
|
|
425
465
|
return {
|
|
426
466
|
code: "MISSING_SERVICE_CONFIG",
|
|
@@ -428,6 +468,13 @@ function validateServiceConfig(name, config) {
|
|
|
428
468
|
serviceName: name
|
|
429
469
|
};
|
|
430
470
|
}
|
|
471
|
+
if (options.requireFileEntrypointForBackendRuntimes && !hasEntrypoint && entrypointRequiredRuntime && ENTRYPOINT_REQUIRED_RUNTIMES.has(entrypointRequiredRuntime)) {
|
|
472
|
+
return {
|
|
473
|
+
code: "MISSING_ENTRYPOINT",
|
|
474
|
+
message: `Service "${name}" must specify "entrypoint" when using "${config.runtime ? "runtime" : "framework"}" "${config.runtime || config.framework}".`,
|
|
475
|
+
serviceName: name
|
|
476
|
+
};
|
|
477
|
+
}
|
|
431
478
|
if (hasBuilderOrRuntime && !hasFramework && !hasEntrypoint) {
|
|
432
479
|
return {
|
|
433
480
|
code: "MISSING_ENTRYPOINT",
|
|
@@ -465,7 +512,7 @@ async function resolveConfiguredService(options) {
|
|
|
465
512
|
routePrefixSource = "configured"
|
|
466
513
|
} = options;
|
|
467
514
|
const type = config.type || "web";
|
|
468
|
-
const trigger =
|
|
515
|
+
const trigger = getEffectiveServiceTrigger(config);
|
|
469
516
|
const rawEntrypoint = config.entrypoint;
|
|
470
517
|
const moduleAttrParsed = typeof rawEntrypoint === "string" ? parsePyModuleAttrEntrypoint(rawEntrypoint) : null;
|
|
471
518
|
const routingResult = resolveServiceRoutingConfig(name, config);
|
|
@@ -611,13 +658,13 @@ async function resolveConfiguredService(options) {
|
|
|
611
658
|
envPrefix: config.envPrefix
|
|
612
659
|
};
|
|
613
660
|
}
|
|
614
|
-
async function resolveAllConfiguredServices(services, fs, routePrefixSource = "configured") {
|
|
661
|
+
async function resolveAllConfiguredServices(services, fs, routePrefixSource = "configured", options = {}) {
|
|
615
662
|
const resolved = [];
|
|
616
663
|
const errors = [];
|
|
617
664
|
const webServicesByRoutePrefix = /* @__PURE__ */ new Map();
|
|
618
665
|
for (const name of Object.keys(services)) {
|
|
619
666
|
const serviceConfig = services[name];
|
|
620
|
-
const validationError = validateServiceConfig(name, serviceConfig);
|
|
667
|
+
const validationError = validateServiceConfig(name, serviceConfig, options);
|
|
621
668
|
if (validationError) {
|
|
622
669
|
errors.push(validationError);
|
|
623
670
|
continue;
|
|
@@ -655,6 +702,16 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
|
|
|
655
702
|
continue;
|
|
656
703
|
}
|
|
657
704
|
}
|
|
705
|
+
const explicitBackendEntrypointError = validateBackendFileEntrypoint(
|
|
706
|
+
name,
|
|
707
|
+
serviceConfig,
|
|
708
|
+
resolvedEntrypoint,
|
|
709
|
+
options
|
|
710
|
+
);
|
|
711
|
+
if (explicitBackendEntrypointError) {
|
|
712
|
+
errors.push(explicitBackendEntrypointError);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
658
715
|
let resolvedConfig = serviceConfig;
|
|
659
716
|
if (!serviceConfig.framework && resolvedEntrypoint) {
|
|
660
717
|
if (resolvedEntrypoint.isDirectory) {
|
|
@@ -711,6 +768,16 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
|
|
|
711
768
|
}
|
|
712
769
|
}
|
|
713
770
|
}
|
|
771
|
+
const backendEntrypointError = validateBackendFileEntrypoint(
|
|
772
|
+
name,
|
|
773
|
+
resolvedConfig,
|
|
774
|
+
resolvedEntrypoint,
|
|
775
|
+
options
|
|
776
|
+
);
|
|
777
|
+
if (backendEntrypointError) {
|
|
778
|
+
errors.push(backendEntrypointError);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
714
781
|
const service = await resolveConfiguredService({
|
|
715
782
|
name,
|
|
716
783
|
config: resolvedConfig,
|
package/dist/services/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Route } from '@vercel/routing-utils';
|
|
2
|
-
import type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, Service, Builder } from '@vercel/build-utils';
|
|
2
|
+
import type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceConfig, Services, ServiceRuntime, ServiceType, Service, Builder } from '@vercel/build-utils';
|
|
3
3
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
4
|
-
export type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceRuntime, ServiceType, Service, Builder, };
|
|
4
|
+
export type { ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ServiceConfig, Services, ServiceRuntime, ServiceType, Service, Builder, };
|
|
5
5
|
/**
|
|
6
6
|
* @deprecated Use `Service` instead
|
|
7
7
|
*/
|
|
@@ -34,7 +34,8 @@ export interface ServicesRoutes {
|
|
|
34
34
|
*/
|
|
35
35
|
workers: Route[];
|
|
36
36
|
}
|
|
37
|
-
export type
|
|
37
|
+
export type ConfiguredServices = Services | ExperimentalServices;
|
|
38
|
+
export type InferredServicesConfig = ExperimentalServices;
|
|
38
39
|
export interface ResolvedServicesResult {
|
|
39
40
|
services: Service[];
|
|
40
41
|
source: DetectServicesSource;
|
|
@@ -44,14 +45,14 @@ export interface ResolvedServicesResult {
|
|
|
44
45
|
}
|
|
45
46
|
export interface InferredServicesResult {
|
|
46
47
|
source: 'layout' | 'procfile' | 'railway';
|
|
47
|
-
config:
|
|
48
|
+
config: InferredServicesConfig;
|
|
48
49
|
services: Service[];
|
|
49
50
|
warnings: ServiceDetectionWarning[];
|
|
50
51
|
}
|
|
51
52
|
export interface DetectServicesResult extends ResolvedServicesResult {
|
|
52
53
|
/**
|
|
53
54
|
* Source of service definitions:
|
|
54
|
-
* - `configured`: loaded from explicit project configuration (
|
|
55
|
+
* - `configured`: loaded from explicit project configuration (`vercel.json#services` or legacy `experimentalServices`)
|
|
55
56
|
* - `auto-detected`: inferred from project structure
|
|
56
57
|
*/
|
|
57
58
|
resolved: ResolvedServicesResult;
|
package/dist/services/utils.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath } from '@vercel/build-utils';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
-
import type { ServiceRuntime, ExperimentalServices, ServiceDetectionError, ResolvedService } from './types';
|
|
3
|
+
import type { ServiceRuntime, ExperimentalServices, Services, ServiceDetectionError, ResolvedService } from './types';
|
|
4
4
|
export { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath, };
|
|
5
5
|
export declare function hasFile(fs: DetectorFilesystem, filePath: string): Promise<boolean>;
|
|
6
|
+
export declare function isPublicServicesEnabled(): boolean;
|
|
7
|
+
export declare function validateServicesConfigGate(config: {
|
|
8
|
+
services?: Services;
|
|
9
|
+
} | null | undefined): ServiceDetectionError | null;
|
|
6
10
|
/**
|
|
7
11
|
* Reserved internal namespace used by the dev queue proxy.
|
|
8
12
|
*/
|
|
@@ -53,6 +57,7 @@ export declare function inferServiceRuntime(config: {
|
|
|
53
57
|
}): ServiceRuntime | undefined;
|
|
54
58
|
export interface ReadVercelConfigResult {
|
|
55
59
|
config: {
|
|
60
|
+
services?: Services;
|
|
56
61
|
experimentalServices?: ExperimentalServices;
|
|
57
62
|
} | null;
|
|
58
63
|
error: ServiceDetectionError | null;
|
package/dist/services/utils.js
CHANGED
|
@@ -41,9 +41,11 @@ __export(utils_exports, {
|
|
|
41
41
|
inferRuntimeFromFramework: () => inferRuntimeFromFramework,
|
|
42
42
|
inferServiceRuntime: () => inferServiceRuntime,
|
|
43
43
|
isFrontendFramework: () => isFrontendFramework,
|
|
44
|
+
isPublicServicesEnabled: () => isPublicServicesEnabled,
|
|
44
45
|
isRouteOwningBuilder: () => isRouteOwningBuilder,
|
|
45
46
|
isStaticBuild: () => isStaticBuild,
|
|
46
|
-
readVercelConfig: () => readVercelConfig
|
|
47
|
+
readVercelConfig: () => readVercelConfig,
|
|
48
|
+
validateServicesConfigGate: () => validateServicesConfigGate
|
|
47
49
|
});
|
|
48
50
|
module.exports = __toCommonJS(utils_exports);
|
|
49
51
|
var import_framework_helpers = require("@vercel/build-utils/dist/framework-helpers");
|
|
@@ -56,6 +58,18 @@ async function hasFile(fs, filePath) {
|
|
|
56
58
|
return false;
|
|
57
59
|
}
|
|
58
60
|
}
|
|
61
|
+
function isPublicServicesEnabled() {
|
|
62
|
+
return process.env.VERCEL_USE_SERVICES === "1" || process.env.VERCEL_USE_SERVICES?.toLowerCase() === "true";
|
|
63
|
+
}
|
|
64
|
+
function validateServicesConfigGate(config) {
|
|
65
|
+
if (config?.services !== void 0 && !isPublicServicesEnabled()) {
|
|
66
|
+
return {
|
|
67
|
+
code: "INVALID_VERCEL_CONFIG",
|
|
68
|
+
message: "Invalid vercel.json - should NOT have additional property `services`. Please remove it."
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
59
73
|
const INTERNAL_QUEUES_PREFIX = "/_svc/_queues";
|
|
60
74
|
function normalizeInternalServiceEntrypoint(entrypoint) {
|
|
61
75
|
const normalized = entrypoint.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.[^/.]+$/, "");
|
|
@@ -140,6 +154,10 @@ async function readVercelConfig(fs) {
|
|
|
140
154
|
try {
|
|
141
155
|
const content = await fs.readFile("vercel.json");
|
|
142
156
|
const config = JSON.parse(content.toString());
|
|
157
|
+
const gateError = validateServicesConfigGate(config);
|
|
158
|
+
if (gateError) {
|
|
159
|
+
return { config: null, error: gateError };
|
|
160
|
+
}
|
|
143
161
|
return { config, error: null };
|
|
144
162
|
} catch {
|
|
145
163
|
return {
|
|
@@ -157,6 +175,10 @@ async function readVercelConfig(fs) {
|
|
|
157
175
|
const { parse: tomlParse } = await import("smol-toml");
|
|
158
176
|
const content = await fs.readFile("vercel.toml");
|
|
159
177
|
const config = tomlParse(content.toString());
|
|
178
|
+
const gateError = validateServicesConfigGate(config);
|
|
179
|
+
if (gateError) {
|
|
180
|
+
return { config: null, error: gateError };
|
|
181
|
+
}
|
|
160
182
|
return { config, error: null };
|
|
161
183
|
} catch {
|
|
162
184
|
return {
|
|
@@ -185,7 +207,9 @@ async function readVercelConfig(fs) {
|
|
|
185
207
|
inferRuntimeFromFramework,
|
|
186
208
|
inferServiceRuntime,
|
|
187
209
|
isFrontendFramework,
|
|
210
|
+
isPublicServicesEnabled,
|
|
188
211
|
isRouteOwningBuilder,
|
|
189
212
|
isStaticBuild,
|
|
190
|
-
readVercelConfig
|
|
213
|
+
readVercelConfig,
|
|
214
|
+
validateServicesConfigGate
|
|
191
215
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/fs-detectors",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.2",
|
|
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/error-utils": "2.1.0",
|
|
24
|
-
"@vercel/frameworks": "3.25.1",
|
|
25
23
|
"@vercel/routing-utils": "6.2.0",
|
|
26
|
-
"@vercel/build-utils": "13.
|
|
24
|
+
"@vercel/build-utils": "13.23.0",
|
|
25
|
+
"@vercel/error-utils": "2.1.0",
|
|
26
|
+
"@vercel/frameworks": "3.26.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/glob": "7.2.0",
|