@vercel/fs-detectors 6.10.3 → 6.11.1
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 +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/services/auto-detect.d.ts +2 -2
- package/dist/services/auto-detect.js +9 -6
- package/dist/services/detect-procfile.d.ts +2 -2
- package/dist/services/detect-procfile.js +8 -7
- package/dist/services/detect-railway.d.ts +2 -2
- package/dist/services/detect-railway.js +12 -13
- package/dist/services/detect-render.d.ts +2 -2
- package/dist/services/detect-render.js +14 -15
- package/dist/services/detect-services.d.ts +13 -1
- package/dist/services/detect-services.js +119 -67
- package/dist/services/get-services-builders.d.ts +6 -2
- package/dist/services/get-services-builders.js +2 -0
- package/dist/services/resolve-v2.js +52 -23
- package/dist/services/types.d.ts +35 -3
- package/dist/services/utils.d.ts +12 -6
- package/dist/services/utils.js +26 -6
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { autoDetectServices } from './services/auto-detect';
|
|
|
6
6
|
export type { AutoDetectOptions, AutoDetectResult, } from './services/auto-detect';
|
|
7
7
|
export { isStaticBuild, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPath, getInternalServiceCronPathPrefix, getInternalServiceWorkerPath, getInternalServiceWorkerPathPrefix, } from './services/utils';
|
|
8
8
|
export { getServicesBuilders } from './services/get-services-builders';
|
|
9
|
-
export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, InferredServicesConfig, ResolvedServicesResult, InferredServicesResult, ResolvedService, Service, ExperimentalService, ExperimentalServiceV2, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServiceBinding, ServiceConfig, Services, ServicesRoutes, ServiceDetectionError, } from './services/types';
|
|
9
|
+
export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, InferredServiceConfig, InferredServicesConfig, ResolvedServicesResult, InferredServicesResult, ResolvedService, Service, ExperimentalService, ExperimentalServiceV2, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServiceBinding, ServiceConfig, Services, ServicesRoutes, ServiceDetectionError, } from './services/types';
|
|
10
10
|
export { detectFileSystemAPI } from './detect-file-system-api';
|
|
11
11
|
export { detectFramework, detectFrameworks, detectFrameworkRecord, detectFrameworkVersion, } from './detect-framework';
|
|
12
12
|
export { getProjectPaths } from './get-project-paths';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DetectEntrypointFn } from '@vercel/build-utils';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
-
import type {
|
|
3
|
+
import type { InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
4
4
|
export interface AutoDetectOptions {
|
|
5
5
|
fs: DetectorFilesystem;
|
|
6
6
|
/**
|
|
@@ -10,7 +10,7 @@ export interface AutoDetectOptions {
|
|
|
10
10
|
detectEntrypoint?: DetectEntrypointFn;
|
|
11
11
|
}
|
|
12
12
|
export interface AutoDetectResult {
|
|
13
|
-
services:
|
|
13
|
+
services: InferredServicesConfig | null;
|
|
14
14
|
errors: ServiceDetectionError[];
|
|
15
15
|
warnings: ServiceDetectionWarning[];
|
|
16
16
|
}
|
|
@@ -97,8 +97,9 @@ async function autoDetectServices(options) {
|
|
|
97
97
|
async function detectServicesAtRoot(fs, rootFramework, detectEntrypoint) {
|
|
98
98
|
const services = {};
|
|
99
99
|
services.frontend = {
|
|
100
|
+
root: ".",
|
|
100
101
|
framework: rootFramework.slug ?? void 0,
|
|
101
|
-
|
|
102
|
+
mountPath: "/"
|
|
102
103
|
};
|
|
103
104
|
const backendResult = await detectBackendServices(fs, detectEntrypoint);
|
|
104
105
|
if (backendResult.error) {
|
|
@@ -116,9 +117,10 @@ async function detectServicesAtRoot(fs, rootFramework, detectEntrypoint) {
|
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
Object.assign(services, backendResult.services);
|
|
120
|
+
const mountWarnings = (0, import_utils.assignMountPaths)(services);
|
|
119
121
|
return {
|
|
120
122
|
services,
|
|
121
|
-
warnings:
|
|
123
|
+
warnings: mountWarnings,
|
|
122
124
|
errors: []
|
|
123
125
|
};
|
|
124
126
|
}
|
|
@@ -128,7 +130,7 @@ async function detectServicesFrontendSubdir(fs, frontendFramework, frontendLocat
|
|
|
128
130
|
services[serviceName] = {
|
|
129
131
|
framework: frontendFramework.slug ?? void 0,
|
|
130
132
|
root: frontendLocation,
|
|
131
|
-
|
|
133
|
+
mountPath: "/"
|
|
132
134
|
};
|
|
133
135
|
const backendResult = await detectBackendServices(fs, detectEntrypoint);
|
|
134
136
|
if (backendResult.error) {
|
|
@@ -151,9 +153,10 @@ async function detectServicesFrontendSubdir(fs, frontendFramework, frontendLocat
|
|
|
151
153
|
};
|
|
152
154
|
}
|
|
153
155
|
Object.assign(services, backendResult.services);
|
|
156
|
+
const mountWarnings = (0, import_utils.assignMountPaths)(services);
|
|
154
157
|
return {
|
|
155
158
|
services,
|
|
156
|
-
warnings:
|
|
159
|
+
warnings: mountWarnings,
|
|
157
160
|
errors: []
|
|
158
161
|
};
|
|
159
162
|
}
|
|
@@ -248,14 +251,14 @@ async function detectServiceInDir(fs, dirPath, serviceName, detectEntrypoint) {
|
|
|
248
251
|
}
|
|
249
252
|
const framework = frameworks[0];
|
|
250
253
|
const slug = framework.slug ?? void 0;
|
|
251
|
-
const
|
|
254
|
+
const mountPath = `/${serviceName}`;
|
|
252
255
|
const detected = detectEntrypoint && !(0, import_utils.isFrontendFramework)(slug) ? await detectEntrypoint({ workPath: dirPath, framework: slug }) : null;
|
|
253
256
|
return {
|
|
254
257
|
service: {
|
|
255
258
|
framework: slug,
|
|
256
259
|
root: dirPath,
|
|
257
260
|
...detected ? { entrypoint: detected.entrypoint } : {},
|
|
258
|
-
|
|
261
|
+
mountPath
|
|
259
262
|
}
|
|
260
263
|
};
|
|
261
264
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
2
|
-
import type {
|
|
2
|
+
import type { InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
3
3
|
export interface ProcfileDetectResult {
|
|
4
|
-
services:
|
|
4
|
+
services: InferredServicesConfig | null;
|
|
5
5
|
errors: ServiceDetectionError[];
|
|
6
6
|
warnings: ServiceDetectionWarning[];
|
|
7
7
|
}
|
|
@@ -115,6 +115,7 @@ async function detectProcfileServices(options) {
|
|
|
115
115
|
if (isWorkerLikeProcess) {
|
|
116
116
|
if (hasSupportedWorkerCommand(tokens) && entrypoint?.endsWith(".py")) {
|
|
117
117
|
services[processType] = {
|
|
118
|
+
root: ".",
|
|
118
119
|
type: "worker",
|
|
119
120
|
entrypoint,
|
|
120
121
|
runtime: "python"
|
|
@@ -131,12 +132,12 @@ async function detectProcfileServices(options) {
|
|
|
131
132
|
});
|
|
132
133
|
continue;
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
services[processType] = {
|
|
136
|
+
root: ".",
|
|
137
|
+
type: "web",
|
|
138
|
+
...detectedFramework?.slug ? { framework: detectedFramework.slug } : {},
|
|
139
|
+
entrypoint: entrypoint ?? "."
|
|
140
|
+
};
|
|
140
141
|
}
|
|
141
142
|
if (errors.length > 0) {
|
|
142
143
|
return { services: null, errors, warnings };
|
|
@@ -152,7 +153,7 @@ async function detectProcfileServices(options) {
|
|
|
152
153
|
firstService.buildCommand = releaseCommand;
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
|
-
warnings.push(...(0, import_utils.
|
|
156
|
+
warnings.push(...(0, import_utils.assignMountPaths)(services));
|
|
156
157
|
return { services, errors: [], warnings };
|
|
157
158
|
}
|
|
158
159
|
function parseProcfile(content) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { DetectEntrypointFn } from '@vercel/build-utils';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
-
import type {
|
|
3
|
+
import type { InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
4
4
|
export interface RailwayDetectResult {
|
|
5
|
-
services:
|
|
5
|
+
services: InferredServicesConfig | null;
|
|
6
6
|
errors: ServiceDetectionError[];
|
|
7
7
|
warnings: ServiceDetectionWarning[];
|
|
8
8
|
}
|
|
@@ -120,18 +120,17 @@ async function detectRailwayServices(options) {
|
|
|
120
120
|
}
|
|
121
121
|
const framework = frameworks[0];
|
|
122
122
|
const slug = framework.slug ?? void 0;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
123
|
+
const serviceConfig = {
|
|
124
|
+
root: cf.dirPath,
|
|
125
|
+
framework: slug
|
|
126
|
+
};
|
|
127
|
+
if (cf.dirPath !== "." && detectEntrypoint && !(0, import_utils.isFrontendFramework)(slug)) {
|
|
128
|
+
const detected = await detectEntrypoint({
|
|
129
|
+
workPath: cf.dirPath,
|
|
130
|
+
framework: slug
|
|
131
|
+
});
|
|
132
|
+
if (detected) {
|
|
133
|
+
serviceConfig.entrypoint = detected.entrypoint;
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
136
|
if (cf.config.build?.buildCommand) {
|
|
@@ -150,7 +149,7 @@ async function detectRailwayServices(options) {
|
|
|
150
149
|
if (serviceNames.length === 0) {
|
|
151
150
|
return { services: null, errors: [], warnings };
|
|
152
151
|
}
|
|
153
|
-
warnings.push(...(0, import_utils.
|
|
152
|
+
warnings.push(...(0, import_utils.assignMountPaths)(services));
|
|
154
153
|
return { services, errors: [], warnings };
|
|
155
154
|
}
|
|
156
155
|
async function findRailwayConfigs(fs, dirPath = ".", depth = 0) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
2
|
-
import type { DetectEntrypointFn,
|
|
2
|
+
import type { DetectEntrypointFn, InferredServicesConfig, ServiceDetectionError, ServiceDetectionWarning } from './types';
|
|
3
3
|
export interface RenderDetectResult {
|
|
4
|
-
services:
|
|
4
|
+
services: InferredServicesConfig | null;
|
|
5
5
|
errors: ServiceDetectionError[];
|
|
6
6
|
warnings: ServiceDetectionWarning[];
|
|
7
7
|
}
|
|
@@ -107,7 +107,7 @@ async function detectRenderServices(options) {
|
|
|
107
107
|
const name = rs.name ?? "unnamed";
|
|
108
108
|
const hint = {
|
|
109
109
|
entrypoint: rs.rootDir ?? "<path-to-entrypoint>",
|
|
110
|
-
|
|
110
|
+
mountPath: `/api/${name}`
|
|
111
111
|
};
|
|
112
112
|
warnings.push({
|
|
113
113
|
code: "RENDER_PSERV_HINT",
|
|
@@ -161,19 +161,18 @@ async function detectRenderServices(options) {
|
|
|
161
161
|
}
|
|
162
162
|
const framework = frameworks[0];
|
|
163
163
|
const vercelType = SERVICE_TYPE_MAP[serviceType];
|
|
164
|
-
const serviceConfig = {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
164
|
+
const serviceConfig = {
|
|
165
|
+
root: rootDir,
|
|
166
|
+
type: vercelType,
|
|
167
|
+
framework: framework.slug ?? void 0
|
|
168
|
+
};
|
|
169
|
+
if (rootDir !== "." && detectEntrypoint && !(0, import_utils.isFrontendFramework)(serviceConfig.framework)) {
|
|
170
|
+
const detected = await detectEntrypoint({
|
|
171
|
+
workPath: rootDir,
|
|
172
|
+
framework: serviceConfig.framework
|
|
173
|
+
});
|
|
174
|
+
if (detected) {
|
|
175
|
+
serviceConfig.entrypoint = detected.entrypoint;
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
178
|
const buildCommand = (0, import_utils.combineBuildCommand)(
|
|
@@ -191,7 +190,7 @@ async function detectRenderServices(options) {
|
|
|
191
190
|
if (Object.keys(services).length === 0) {
|
|
192
191
|
return { services: null, errors: [], warnings };
|
|
193
192
|
}
|
|
194
|
-
warnings.push(...(0, import_utils.
|
|
193
|
+
warnings.push(...(0, import_utils.assignMountPaths)(services));
|
|
195
194
|
return { services, errors: [], warnings };
|
|
196
195
|
}
|
|
197
196
|
async function readRenderYaml(fs) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Rewrite } from '@vercel/routing-utils';
|
|
2
|
+
import type { DetectServicesOptions, DetectServicesResult, InferredServicesConfig, Service, ServicesRoutes } from './types';
|
|
2
3
|
/**
|
|
3
4
|
* Detect and resolve services within a project.
|
|
4
5
|
*
|
|
@@ -6,6 +7,17 @@ import type { DetectServicesOptions, DetectServicesResult, Service, ServicesRout
|
|
|
6
7
|
* Returns an error if no services are configured.
|
|
7
8
|
*/
|
|
8
9
|
export declare function detectServices(options: DetectServicesOptions): Promise<DetectServicesResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Generate top-level service-targeted rewrites from inferred mount paths.
|
|
12
|
+
*
|
|
13
|
+
* Produces `Rewrite` objects (same format as vercel.json `rewrites`) that
|
|
14
|
+
* delegate public traffic into services based on their `mountPath`.
|
|
15
|
+
*
|
|
16
|
+
* Rewrites are ordered by mount path length (longest first) so more
|
|
17
|
+
* specific paths match before broader ones. The root service (`/`) is
|
|
18
|
+
* always last as a catch-all.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generateServiceRewrites(services: InferredServicesConfig): Rewrite[];
|
|
9
21
|
/**
|
|
10
22
|
* Generate routing rules for services.
|
|
11
23
|
*
|
|
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var detect_services_exports = {};
|
|
20
20
|
__export(detect_services_exports, {
|
|
21
21
|
detectServices: () => detectServices,
|
|
22
|
+
generateServiceRewrites: () => generateServiceRewrites,
|
|
22
23
|
generateServicesRoutes: () => generateServicesRoutes
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(detect_services_exports);
|
|
@@ -51,6 +52,7 @@ function withResolvedResult(resolved, inferred = null) {
|
|
|
51
52
|
source: resolved.source,
|
|
52
53
|
useImplicitEnvInjection: resolved.useImplicitEnvInjection,
|
|
53
54
|
routes: resolved.routes,
|
|
55
|
+
rewrites: resolved.rewrites,
|
|
54
56
|
errors: resolved.errors,
|
|
55
57
|
warnings: resolved.warnings,
|
|
56
58
|
resolved,
|
|
@@ -60,18 +62,17 @@ function withResolvedResult(resolved, inferred = null) {
|
|
|
60
62
|
function toInferredLayoutConfig(services) {
|
|
61
63
|
const inferredConfig = {};
|
|
62
64
|
for (const [name, service] of Object.entries(services)) {
|
|
63
|
-
const serviceConfig = {
|
|
65
|
+
const serviceConfig = {
|
|
66
|
+
root: service.root
|
|
67
|
+
};
|
|
64
68
|
if (service.type) {
|
|
65
69
|
serviceConfig.type = service.type;
|
|
66
70
|
}
|
|
67
|
-
if (typeof service.root === "string") {
|
|
68
|
-
serviceConfig.root = service.root;
|
|
69
|
-
}
|
|
70
71
|
if (typeof service.entrypoint === "string") {
|
|
71
72
|
serviceConfig.entrypoint = service.entrypoint;
|
|
72
73
|
}
|
|
73
|
-
if (typeof service.
|
|
74
|
-
serviceConfig.
|
|
74
|
+
if (typeof service.mountPath === "string") {
|
|
75
|
+
serviceConfig.mountPath = service.mountPath;
|
|
75
76
|
}
|
|
76
77
|
if ((0, import_utils.isFrontendFramework)(service.framework)) {
|
|
77
78
|
serviceConfig.framework = service.framework;
|
|
@@ -102,6 +103,7 @@ async function detectServices(options) {
|
|
|
102
103
|
source: "configured",
|
|
103
104
|
useImplicitEnvInjection: true,
|
|
104
105
|
routes: emptyRoutes(),
|
|
106
|
+
rewrites: [],
|
|
105
107
|
errors: [configError],
|
|
106
108
|
warnings: []
|
|
107
109
|
});
|
|
@@ -112,6 +114,7 @@ async function detectServices(options) {
|
|
|
112
114
|
source: "configured",
|
|
113
115
|
useImplicitEnvInjection: false,
|
|
114
116
|
routes: emptyRoutes(),
|
|
117
|
+
rewrites: [],
|
|
115
118
|
errors: [
|
|
116
119
|
{
|
|
117
120
|
code: "SERVICES_AND_EXPERIMENTAL_SERVICES_V2",
|
|
@@ -124,63 +127,66 @@ async function detectServices(options) {
|
|
|
124
127
|
const hasProvidedConfiguredServices = providedConfiguredServices && Object.keys(providedConfiguredServices).length > 0;
|
|
125
128
|
const experimentalServicesV2 = hasProvidedConfiguredServices && (providedConfiguredServicesType === "services" || providedConfiguredServicesType === "experimentalServicesV2") ? providedConfiguredServices : hasProvidedConfiguredServices ? void 0 : vercelConfig?.services ?? vercelConfig?.experimentalServicesV2;
|
|
126
129
|
if (experimentalServicesV2 && Object.keys(experimentalServicesV2).length > 0) {
|
|
127
|
-
const
|
|
130
|
+
const result = await (0, import_resolve_v2.resolveAllConfiguredServicesV2)(
|
|
128
131
|
experimentalServicesV2,
|
|
129
132
|
scopedFs
|
|
130
133
|
);
|
|
131
134
|
return withResolvedResult({
|
|
132
|
-
services:
|
|
135
|
+
services: result.services,
|
|
133
136
|
source: "configured",
|
|
134
137
|
// V2 uses explicit `bindings`, so no implicit `{NAME}_URL` injection.
|
|
135
138
|
useImplicitEnvInjection: false,
|
|
136
139
|
// V2 routes are explicitly carried per-service to output them separately.
|
|
137
140
|
routes: emptyRoutes(),
|
|
138
|
-
|
|
141
|
+
rewrites: [],
|
|
142
|
+
errors: result.errors,
|
|
139
143
|
warnings: []
|
|
140
144
|
});
|
|
141
145
|
}
|
|
142
146
|
const experimentalServicesV1 = hasProvidedConfiguredServices ? providedConfiguredServices : vercelConfig?.experimentalServices;
|
|
143
147
|
const hasExperimentalServicesV1 = experimentalServicesV1 && Object.keys(experimentalServicesV1).length > 0;
|
|
144
|
-
if (
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
for (const { detect, source } of detectors) {
|
|
152
|
-
const detectResult = await detect({ fs: scopedFs, detectEntrypoint });
|
|
153
|
-
const match = await tryResolveInferred(detectResult, source, scopedFs);
|
|
154
|
-
if (match)
|
|
155
|
-
return match;
|
|
156
|
-
}
|
|
148
|
+
if (hasExperimentalServicesV1) {
|
|
149
|
+
const result = await (0, import_resolve.resolveAllConfiguredServices)(
|
|
150
|
+
experimentalServicesV1,
|
|
151
|
+
scopedFs,
|
|
152
|
+
"configured"
|
|
153
|
+
);
|
|
154
|
+
const routes = generateServicesRoutes(result.services);
|
|
157
155
|
return withResolvedResult({
|
|
158
|
-
services:
|
|
159
|
-
source: "
|
|
156
|
+
services: result.services,
|
|
157
|
+
source: "configured",
|
|
158
|
+
// experimentalServices uses the legacy `{NAME}_URL` injection.
|
|
160
159
|
useImplicitEnvInjection: true,
|
|
161
|
-
routes
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
code: "NO_EXPERIMENTAL_SERVICES_CONFIGURED",
|
|
165
|
-
message: "No services configured. Add `experimentalServices` to vercel.json."
|
|
166
|
-
}
|
|
167
|
-
],
|
|
160
|
+
routes,
|
|
161
|
+
rewrites: [],
|
|
162
|
+
errors: result.errors,
|
|
168
163
|
warnings: []
|
|
169
164
|
});
|
|
170
165
|
}
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
"
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
const detectors = [
|
|
167
|
+
{ detect: import_detect_railway.detectRailwayServices, source: "railway" },
|
|
168
|
+
{ detect: import_detect_render.detectRenderServices, source: "render" },
|
|
169
|
+
{ detect: import_detect_procfile.detectProcfileServices, source: "procfile" },
|
|
170
|
+
{ detect: import_auto_detect.autoDetectServices, source: "layout" }
|
|
171
|
+
];
|
|
172
|
+
for (const { detect, source } of detectors) {
|
|
173
|
+
const detectResult = await detect({ fs: scopedFs, detectEntrypoint });
|
|
174
|
+
const match = await tryResolveInferred(detectResult, source, scopedFs);
|
|
175
|
+
if (match)
|
|
176
|
+
return match;
|
|
177
|
+
}
|
|
177
178
|
return withResolvedResult({
|
|
178
|
-
services:
|
|
179
|
-
source: "
|
|
180
|
-
// experimentalServices uses the legacy `{NAME}_URL` injection.
|
|
179
|
+
services: [],
|
|
180
|
+
source: "auto-detected",
|
|
181
181
|
useImplicitEnvInjection: true,
|
|
182
|
-
routes,
|
|
183
|
-
|
|
182
|
+
routes: emptyRoutes(),
|
|
183
|
+
rewrites: [],
|
|
184
|
+
errors: [
|
|
185
|
+
{
|
|
186
|
+
code: "NO_EXPERIMENTAL_SERVICES_CONFIGURED",
|
|
187
|
+
message: "No services configured. Add `experimentalServices` to vercel.json."
|
|
188
|
+
}
|
|
189
|
+
],
|
|
184
190
|
warnings: []
|
|
185
191
|
});
|
|
186
192
|
}
|
|
@@ -189,8 +195,9 @@ async function tryResolveInferred(detectResult, source, scopedFs) {
|
|
|
189
195
|
return withResolvedResult({
|
|
190
196
|
services: [],
|
|
191
197
|
source: "auto-detected",
|
|
192
|
-
useImplicitEnvInjection:
|
|
198
|
+
useImplicitEnvInjection: source !== "layout",
|
|
193
199
|
routes: emptyRoutes(),
|
|
200
|
+
rewrites: [],
|
|
194
201
|
errors: detectResult.errors,
|
|
195
202
|
warnings: detectResult.warnings
|
|
196
203
|
});
|
|
@@ -198,52 +205,96 @@ async function tryResolveInferred(detectResult, source, scopedFs) {
|
|
|
198
205
|
if (!detectResult.services) {
|
|
199
206
|
return null;
|
|
200
207
|
}
|
|
208
|
+
if (source === "layout") {
|
|
209
|
+
const v2Services = {};
|
|
210
|
+
for (const [name, svc] of Object.entries(detectResult.services)) {
|
|
211
|
+
v2Services[name] = {
|
|
212
|
+
root: svc.root,
|
|
213
|
+
...svc.framework ? { framework: svc.framework } : {},
|
|
214
|
+
...svc.entrypoint ? { entrypoint: svc.entrypoint } : {}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const result2 = await (0, import_resolve_v2.resolveAllConfiguredServicesV2)(v2Services, scopedFs);
|
|
218
|
+
const rootServices = Object.values(detectResult.services).filter(
|
|
219
|
+
(svc) => svc.mountPath === "/" && typeof svc.framework === "string"
|
|
220
|
+
);
|
|
221
|
+
const shouldInfer2 = result2.errors.length === 0 && rootServices.length === 1 && result2.services.length > 1;
|
|
222
|
+
const inferred2 = shouldInfer2 ? {
|
|
223
|
+
source,
|
|
224
|
+
config: toInferredLayoutConfig(detectResult.services),
|
|
225
|
+
services: result2.services,
|
|
226
|
+
warnings: detectResult.warnings
|
|
227
|
+
} : null;
|
|
228
|
+
return withResolvedResult(
|
|
229
|
+
{
|
|
230
|
+
services: shouldInfer2 ? result2.services : [],
|
|
231
|
+
source: "auto-detected",
|
|
232
|
+
useImplicitEnvInjection: false,
|
|
233
|
+
routes: emptyRoutes(),
|
|
234
|
+
rewrites: shouldInfer2 ? generateServiceRewrites(detectResult.services) : [],
|
|
235
|
+
experimentalServicesV2: shouldInfer2 ? v2Services : void 0,
|
|
236
|
+
errors: result2.errors,
|
|
237
|
+
warnings: detectResult.warnings
|
|
238
|
+
},
|
|
239
|
+
inferred2
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const v1Services = {};
|
|
243
|
+
for (const [name, svc] of Object.entries(detectResult.services)) {
|
|
244
|
+
v1Services[name] = {
|
|
245
|
+
root: svc.root === "." ? void 0 : svc.root,
|
|
246
|
+
...svc.framework ? { framework: svc.framework } : {},
|
|
247
|
+
...svc.entrypoint ? { entrypoint: svc.entrypoint } : {},
|
|
248
|
+
...svc.type ? { type: svc.type } : {},
|
|
249
|
+
...svc.buildCommand ? { buildCommand: svc.buildCommand } : {},
|
|
250
|
+
...svc.preDeployCommand ? { preDeployCommand: svc.preDeployCommand } : {},
|
|
251
|
+
...svc.mountPath ? { routePrefix: svc.mountPath } : {}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
201
254
|
const result = await (0, import_resolve.resolveAllConfiguredServices)(
|
|
202
|
-
|
|
255
|
+
v1Services,
|
|
203
256
|
scopedFs,
|
|
204
257
|
"generated"
|
|
205
258
|
);
|
|
206
|
-
|
|
207
|
-
if (source === "layout") {
|
|
208
|
-
const rootWebFrameworkServices = result.services.filter(
|
|
209
|
-
(service) => service.type === "web" && service.routePrefix === "/" && typeof service.framework === "string"
|
|
210
|
-
);
|
|
211
|
-
shouldInfer = result.errors.length === 0 && rootWebFrameworkServices.length === 1 && result.services.length > 1;
|
|
212
|
-
} else {
|
|
213
|
-
shouldInfer = result.errors.length === 0 && result.services.length > 0;
|
|
214
|
-
}
|
|
259
|
+
const shouldInfer = result.errors.length === 0 && result.services.length > 0;
|
|
215
260
|
const inferred = shouldInfer ? {
|
|
216
261
|
source,
|
|
217
262
|
config: toInferredLayoutConfig(detectResult.services),
|
|
218
263
|
services: result.services,
|
|
219
264
|
warnings: detectResult.warnings
|
|
220
265
|
} : null;
|
|
221
|
-
if (source === "layout" && shouldInfer) {
|
|
222
|
-
const routes = generateServicesRoutes(result.services);
|
|
223
|
-
return withResolvedResult(
|
|
224
|
-
{
|
|
225
|
-
services: result.services,
|
|
226
|
-
source: "auto-detected",
|
|
227
|
-
useImplicitEnvInjection: true,
|
|
228
|
-
routes,
|
|
229
|
-
errors: result.errors,
|
|
230
|
-
warnings: detectResult.warnings
|
|
231
|
-
},
|
|
232
|
-
inferred
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
266
|
return withResolvedResult(
|
|
236
267
|
{
|
|
237
268
|
services: [],
|
|
238
269
|
source: "auto-detected",
|
|
239
270
|
useImplicitEnvInjection: true,
|
|
240
271
|
routes: emptyRoutes(),
|
|
272
|
+
rewrites: [],
|
|
241
273
|
errors: result.errors,
|
|
242
274
|
warnings: detectResult.warnings
|
|
243
275
|
},
|
|
244
276
|
inferred
|
|
245
277
|
);
|
|
246
278
|
}
|
|
279
|
+
function generateServiceRewrites(services) {
|
|
280
|
+
const entries = Object.entries(services).filter(
|
|
281
|
+
([, svc]) => typeof svc.mountPath === "string" && (!svc.type || svc.type === "web")
|
|
282
|
+
).sort(([, a], [, b]) => b.mountPath.length - a.mountPath.length);
|
|
283
|
+
return entries.map(([name, svc]) => {
|
|
284
|
+
const mountPath = svc.mountPath;
|
|
285
|
+
if (mountPath === "/") {
|
|
286
|
+
return {
|
|
287
|
+
source: "/(.*)",
|
|
288
|
+
destination: { type: "service", service: name }
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const prefix = mountPath.startsWith("/") ? mountPath.slice(1) : mountPath;
|
|
292
|
+
return {
|
|
293
|
+
source: `/${prefix}(/.*)?`,
|
|
294
|
+
destination: { type: "service", service: name }
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
}
|
|
247
298
|
function generateServicesRoutes(allServices) {
|
|
248
299
|
const services = allServices.filter(import_build_utils.isExperimentalService);
|
|
249
300
|
const hostRewrites = [];
|
|
@@ -367,5 +418,6 @@ function getHostCondition(service) {
|
|
|
367
418
|
// Annotate the CommonJS export names for ESM import in node:
|
|
368
419
|
0 && (module.exports = {
|
|
369
420
|
detectServices,
|
|
421
|
+
generateServiceRewrites,
|
|
370
422
|
generateServicesRoutes
|
|
371
423
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Route } from '@vercel/routing-utils';
|
|
2
|
-
import type { Builder } from '@vercel/build-utils';
|
|
1
|
+
import type { Rewrite, Route } from '@vercel/routing-utils';
|
|
2
|
+
import type { Builder, Services } from '@vercel/build-utils';
|
|
3
3
|
import type { ConfiguredServices, ConfiguredServicesType, Service } from './types';
|
|
4
4
|
export interface ErrorResponse {
|
|
5
5
|
code: string;
|
|
@@ -23,6 +23,10 @@ export interface ServicesBuildersResult {
|
|
|
23
23
|
redirectRoutes: Route[] | null;
|
|
24
24
|
rewriteRoutes: Route[] | null;
|
|
25
25
|
errorRoutes: Route[] | null;
|
|
26
|
+
/** Top-level service-targeted rewrites generated by auto-detection. */
|
|
27
|
+
serviceRewrites?: Rewrite[];
|
|
28
|
+
/** V2 services config so the platform activates V2 routing. */
|
|
29
|
+
experimentalServicesV2?: Services;
|
|
26
30
|
services?: Service[];
|
|
27
31
|
useImplicitEnvInjection?: boolean;
|
|
28
32
|
}
|
|
@@ -131,6 +131,8 @@ async function getServicesBuilders(options) {
|
|
|
131
131
|
...result.routes.crons
|
|
132
132
|
] : null,
|
|
133
133
|
errorRoutes: [],
|
|
134
|
+
serviceRewrites: result.rewrites.length > 0 ? result.rewrites : void 0,
|
|
135
|
+
experimentalServicesV2: result.experimentalServicesV2,
|
|
134
136
|
services: result.services,
|
|
135
137
|
useImplicitEnvInjection: result.useImplicitEnvInjection
|
|
136
138
|
};
|
|
@@ -31,9 +31,27 @@ var import_resolve = require("./resolve");
|
|
|
31
31
|
var import_utils = require("./utils");
|
|
32
32
|
const frameworksBySlug = new Map(import_frameworks.frameworkList.map((f) => [f.slug, f]));
|
|
33
33
|
const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
34
|
+
const CONTAINER_ENTRYPOINT_CANDIDATES = [
|
|
35
|
+
"Dockerfile.vercel",
|
|
36
|
+
"Containerfile.vercel",
|
|
37
|
+
"Dockerfile",
|
|
38
|
+
"Containerfile"
|
|
39
|
+
];
|
|
40
|
+
const CONTAINER_ENTRYPOINT_BASENAMES = new Set(
|
|
41
|
+
CONTAINER_ENTRYPOINT_CANDIDATES.map((name) => name.toLowerCase())
|
|
42
|
+
);
|
|
34
43
|
function isDockerfileEntrypoint(entrypoint) {
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
return CONTAINER_ENTRYPOINT_BASENAMES.has(
|
|
45
|
+
import_path.posix.basename(entrypoint).toLowerCase()
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
async function detectContainerEntrypoint(serviceFs) {
|
|
49
|
+
for (const candidate of CONTAINER_ENTRYPOINT_CANDIDATES) {
|
|
50
|
+
if (await serviceFs.hasPath(candidate)) {
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return void 0;
|
|
37
55
|
}
|
|
38
56
|
function normalizeContainerCommand(command) {
|
|
39
57
|
if (command === void 0) {
|
|
@@ -41,29 +59,40 @@ function normalizeContainerCommand(command) {
|
|
|
41
59
|
}
|
|
42
60
|
return Array.isArray(command) ? command : [command];
|
|
43
61
|
}
|
|
44
|
-
function resolveContainerServiceV2(name, config, normalizedRoot) {
|
|
62
|
+
async function resolveContainerServiceV2(name, config, normalizedRoot, serviceFs) {
|
|
45
63
|
const isRoot = normalizedRoot === ".";
|
|
46
64
|
const entrypoint = config.entrypoint;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
let dockerfile;
|
|
66
|
+
if (typeof entrypoint === "string") {
|
|
67
|
+
if (!isDockerfileEntrypoint(entrypoint)) {
|
|
68
|
+
return {
|
|
69
|
+
error: {
|
|
70
|
+
code: "INVALID_SERVICE_CONFIG",
|
|
71
|
+
message: `Container service "${name}" has invalid "entrypoint" "${entrypoint}". It must name a Dockerfile or Containerfile.`,
|
|
72
|
+
serviceName: name
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
dockerfile = import_path.posix.normalize(entrypoint);
|
|
77
|
+
} else {
|
|
78
|
+
dockerfile = await detectContainerEntrypoint(serviceFs);
|
|
79
|
+
if (!dockerfile) {
|
|
80
|
+
return {
|
|
81
|
+
error: {
|
|
82
|
+
code: "MISSING_SERVICE_CONFIG",
|
|
83
|
+
message: `Container service "${name}" has no "entrypoint" and no ${CONTAINER_ENTRYPOINT_CANDIDATES.join(
|
|
84
|
+
", "
|
|
85
|
+
)} was found in "${normalizedRoot}".`,
|
|
86
|
+
serviceName: name
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
57
90
|
}
|
|
58
|
-
const
|
|
59
|
-
const builderSrc = isRoot ? localSrc : import_path.posix.join(normalizedRoot, localSrc);
|
|
91
|
+
const builderSrc = isRoot ? dockerfile : import_path.posix.join(normalizedRoot, dockerfile);
|
|
60
92
|
const builderConfig = { zeroConfig: true };
|
|
61
93
|
if (!isRoot) {
|
|
62
94
|
builderConfig.workspace = normalizedRoot;
|
|
63
95
|
}
|
|
64
|
-
if (image) {
|
|
65
|
-
builderConfig.handler = image;
|
|
66
|
-
}
|
|
67
96
|
const command = normalizeContainerCommand(config.command);
|
|
68
97
|
if (command) {
|
|
69
98
|
builderConfig.command = command;
|
|
@@ -74,7 +103,7 @@ function resolveContainerServiceV2(name, config, normalizedRoot) {
|
|
|
74
103
|
name,
|
|
75
104
|
root: normalizedRoot,
|
|
76
105
|
runtime: "container",
|
|
77
|
-
entrypoint:
|
|
106
|
+
entrypoint: dockerfile,
|
|
78
107
|
command,
|
|
79
108
|
builder: {
|
|
80
109
|
src: builderSrc,
|
|
@@ -165,15 +194,15 @@ function validateServiceConfigV2(name, config) {
|
|
|
165
194
|
}
|
|
166
195
|
async function resolveConfiguredServiceV2(name, config, fs) {
|
|
167
196
|
const normalizedRoot = (0, import_utils.stripTrailingSlash)(import_path.posix.normalize(config.root));
|
|
168
|
-
const isContainer = config.runtime === "container" || typeof config.entrypoint === "string" && isDockerfileEntrypoint(config.entrypoint);
|
|
169
|
-
if (isContainer) {
|
|
170
|
-
return resolveContainerServiceV2(name, config, normalizedRoot);
|
|
171
|
-
}
|
|
172
197
|
const serviceFsResult = normalizedRoot === "." ? { fs } : await (0, import_resolve.getServiceFs)(fs, name, normalizedRoot);
|
|
173
198
|
if (serviceFsResult.error) {
|
|
174
199
|
return { error: serviceFsResult.error };
|
|
175
200
|
}
|
|
176
201
|
const serviceFs = serviceFsResult.fs;
|
|
202
|
+
const isContainer = config.runtime === "container" || typeof config.entrypoint === "string" && isDockerfileEntrypoint(config.entrypoint);
|
|
203
|
+
if (isContainer) {
|
|
204
|
+
return resolveContainerServiceV2(name, config, normalizedRoot, serviceFs);
|
|
205
|
+
}
|
|
177
206
|
const rawEntrypoint = config.entrypoint;
|
|
178
207
|
const moduleAttr = typeof rawEntrypoint === "string" ? (0, import_resolve.parsePyModuleAttrEntrypoint)(rawEntrypoint) : null;
|
|
179
208
|
let normalizedEntrypoint;
|
package/dist/services/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Route } from '@vercel/routing-utils';
|
|
1
|
+
import type { Rewrite, Route } from '@vercel/routing-utils';
|
|
2
2
|
import type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceV2Config, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServiceBinding, ServiceConfig, Services, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder } from '@vercel/build-utils';
|
|
3
3
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
4
4
|
export type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServiceBinding, ServiceConfig, Services, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder, };
|
|
@@ -44,19 +44,51 @@ export interface ServicesRoutes {
|
|
|
44
44
|
}
|
|
45
45
|
export type ConfiguredServicesType = 'experimentalServices' | 'services' | 'experimentalServicesV2';
|
|
46
46
|
export type ConfiguredServices = ExperimentalServices | Services;
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* A single service entry inferred from project structure.
|
|
49
|
+
*
|
|
50
|
+
* This is an intermediate format produced by auto-detection — it carries
|
|
51
|
+
* the detection results (including `mountPath`, the inferred route mount
|
|
52
|
+
* point) before they are converted into a concrete config format (V1 or V2).
|
|
53
|
+
*/
|
|
54
|
+
export interface InferredServiceConfig {
|
|
55
|
+
/** Service root directory relative to the project root. */
|
|
56
|
+
root: string;
|
|
57
|
+
/** Framework slug, if detected. */
|
|
58
|
+
framework?: string;
|
|
59
|
+
/** Service entrypoint (file path or `module:attr` reference). */
|
|
60
|
+
entrypoint?: string;
|
|
61
|
+
/** Runtime identifier (e.g. "python", "node"). */
|
|
62
|
+
runtime?: string;
|
|
63
|
+
/** Service type (e.g. "web", "cron", "worker"). */
|
|
64
|
+
type?: ServiceType;
|
|
65
|
+
/** Build command override. */
|
|
66
|
+
buildCommand?: string;
|
|
67
|
+
/** Pre-deploy command override. */
|
|
68
|
+
preDeployCommand?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Inferred route mount path for this service.
|
|
71
|
+
* For example, `"/"` for the root frontend, `"/_/backend"` for a backend.
|
|
72
|
+
*/
|
|
73
|
+
mountPath?: string;
|
|
74
|
+
}
|
|
75
|
+
export type InferredServicesConfig = Record<string, InferredServiceConfig>;
|
|
48
76
|
export interface ResolvedServicesResult {
|
|
49
77
|
services: Service[];
|
|
50
78
|
source: DetectServicesSource;
|
|
51
79
|
useImplicitEnvInjection: boolean;
|
|
52
80
|
routes: ServicesRoutes;
|
|
81
|
+
/** Top-level service-targeted rewrites (V2). */
|
|
82
|
+
rewrites: Rewrite[];
|
|
83
|
+
/** V2 services config for the build output, so the platform activates V2 routing. */
|
|
84
|
+
experimentalServicesV2?: Services;
|
|
53
85
|
errors: ServiceDetectionError[];
|
|
54
86
|
warnings: ServiceDetectionWarning[];
|
|
55
87
|
}
|
|
56
88
|
export interface InferredServicesResult {
|
|
57
89
|
source: 'layout' | 'procfile' | 'railway' | 'render';
|
|
58
90
|
config: InferredServicesConfig;
|
|
59
|
-
services:
|
|
91
|
+
services: Service[];
|
|
60
92
|
warnings: ServiceDetectionWarning[];
|
|
61
93
|
}
|
|
62
94
|
export interface DetectServicesResult extends ResolvedServicesResult {
|
package/dist/services/utils.d.ts
CHANGED
|
@@ -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, ExperimentalServicesV2, Services, ServiceDetectionError, ServiceDetectionWarning, ResolvedService } from './types';
|
|
4
|
+
import type { ServiceRuntime, ExperimentalServices, ExperimentalServicesV2, InferredServicesConfig, Services, ServiceDetectionError, ServiceDetectionWarning, ResolvedService } from './types';
|
|
5
5
|
export declare const DETECTION_FRAMEWORKS: Framework[];
|
|
6
6
|
export { INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceCronPath, };
|
|
7
7
|
/**
|
|
@@ -42,6 +42,7 @@ export declare function isRouteOwningBuilder(service: ResolvedService): boolean;
|
|
|
42
42
|
*/
|
|
43
43
|
export declare function inferRuntimeFromFramework(framework: string | null | undefined): ServiceRuntime | undefined;
|
|
44
44
|
export declare function isFrontendFramework(framework: string | null | undefined): boolean;
|
|
45
|
+
export declare function isBFFFramework(framework: string | null | undefined): boolean;
|
|
45
46
|
export declare function filterFrameworksByRuntime<T extends {
|
|
46
47
|
slug?: string | null;
|
|
47
48
|
}>(frameworks: readonly T[], runtime?: ServiceRuntime): T[];
|
|
@@ -77,13 +78,18 @@ export interface ReadVercelConfigResult {
|
|
|
77
78
|
*/
|
|
78
79
|
export declare function readVercelConfig(fs: DetectorFilesystem): Promise<ReadVercelConfigResult>;
|
|
79
80
|
/**
|
|
80
|
-
* Assign
|
|
81
|
+
* Assign mount paths to inferred services.
|
|
81
82
|
*
|
|
82
|
-
* A frontend service gets `/`,
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
* A frontend service gets `/`, backend services get `/api/...`:
|
|
84
|
+
* - If the frontend is a BFF (e.g. Next.js, has its own API routes):
|
|
85
|
+
* backends get `/api/{name}/(.*)` to avoid shadowing the frontend's API routes.
|
|
86
|
+
* - If the frontend is client-only (e.g. Vite):
|
|
87
|
+
* backends get `/api/(.*)`.
|
|
88
|
+
*
|
|
89
|
+
* A single non-frontend service gets `/`.
|
|
90
|
+
* If no frontend service found, multiple services get `/api/{name}`.
|
|
85
91
|
*
|
|
86
92
|
* Priority for `/`: single service or frontend > name "frontend" or "web" > alphabetical.
|
|
87
93
|
*/
|
|
88
|
-
export declare function
|
|
94
|
+
export declare function assignMountPaths(services: InferredServicesConfig): ServiceDetectionWarning[];
|
|
89
95
|
export declare function combineBuildCommand(buildCommand: string | undefined, preDeployCommand: string | string[] | undefined): string | undefined;
|
package/dist/services/utils.js
CHANGED
|
@@ -31,7 +31,7 @@ __export(utils_exports, {
|
|
|
31
31
|
DETECTION_FRAMEWORKS: () => DETECTION_FRAMEWORKS,
|
|
32
32
|
INTERNAL_QUEUES_PREFIX: () => INTERNAL_QUEUES_PREFIX,
|
|
33
33
|
INTERNAL_SERVICE_PREFIX: () => import_build_utils.INTERNAL_SERVICE_PREFIX,
|
|
34
|
-
|
|
34
|
+
assignMountPaths: () => assignMountPaths,
|
|
35
35
|
combineBuildCommand: () => combineBuildCommand,
|
|
36
36
|
filterFrameworksByRuntime: () => filterFrameworksByRuntime,
|
|
37
37
|
getBuilderForRuntime: () => getBuilderForRuntime,
|
|
@@ -43,6 +43,7 @@ __export(utils_exports, {
|
|
|
43
43
|
hasFile: () => hasFile,
|
|
44
44
|
inferRuntimeFromFramework: () => inferRuntimeFromFramework,
|
|
45
45
|
inferServiceRuntime: () => inferServiceRuntime,
|
|
46
|
+
isBFFFramework: () => isBFFFramework,
|
|
46
47
|
isFrontendFramework: () => isFrontendFramework,
|
|
47
48
|
isRouteOwningBuilder: () => isRouteOwningBuilder,
|
|
48
49
|
isStaticBuild: () => isStaticBuild,
|
|
@@ -114,6 +115,16 @@ function isFrontendFramework(framework) {
|
|
|
114
115
|
}
|
|
115
116
|
return !inferRuntimeFromFramework(framework);
|
|
116
117
|
}
|
|
118
|
+
const BFF_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
119
|
+
"nextjs",
|
|
120
|
+
"nuxtjs",
|
|
121
|
+
"sveltekit",
|
|
122
|
+
"remix",
|
|
123
|
+
"solidstart"
|
|
124
|
+
]);
|
|
125
|
+
function isBFFFramework(framework) {
|
|
126
|
+
return !!framework && BFF_FRAMEWORKS.has(framework);
|
|
127
|
+
}
|
|
117
128
|
function filterFrameworksByRuntime(frameworks, runtime) {
|
|
118
129
|
if (!runtime) {
|
|
119
130
|
return [...frameworks];
|
|
@@ -182,11 +193,11 @@ async function readVercelConfig(fs) {
|
|
|
182
193
|
}
|
|
183
194
|
return { config: null, error: null };
|
|
184
195
|
}
|
|
185
|
-
function
|
|
196
|
+
function assignMountPaths(services) {
|
|
186
197
|
const warnings = [];
|
|
187
198
|
const names = Object.keys(services);
|
|
188
199
|
if (names.length === 1) {
|
|
189
|
-
services[names[0]].
|
|
200
|
+
services[names[0]].mountPath = "/";
|
|
190
201
|
return warnings;
|
|
191
202
|
}
|
|
192
203
|
const frontendNames = names.filter(
|
|
@@ -199,11 +210,19 @@ function assignRoutePrefixes(services) {
|
|
|
199
210
|
rootName = frontendNames.find((n) => n === "frontend" || n === "web") ?? frontendNames.sort()[0];
|
|
200
211
|
warnings.push({
|
|
201
212
|
code: "MULTIPLE_FRONTENDS",
|
|
202
|
-
message: `Multiple frontend services detected (${frontendNames.join(", ")}). "${rootName}" was assigned
|
|
213
|
+
message: `Multiple frontend services detected (${frontendNames.join(", ")}). "${rootName}" was assigned mount path "/". Adjust manually if a different service should be the root.`
|
|
203
214
|
});
|
|
204
215
|
}
|
|
216
|
+
const rootFramework = rootName ? services[rootName].framework : void 0;
|
|
217
|
+
const isBFF = rootFramework ? isBFFFramework(rootFramework) : false;
|
|
218
|
+
const nonRootNames = names.filter((n) => n !== rootName);
|
|
219
|
+
const needsNamespace = isBFF || nonRootNames.length > 1;
|
|
205
220
|
for (const name of names) {
|
|
206
|
-
|
|
221
|
+
if (name === rootName) {
|
|
222
|
+
services[name].mountPath = "/";
|
|
223
|
+
} else {
|
|
224
|
+
services[name].mountPath = needsNamespace ? `/api/${name}` : "/api";
|
|
225
|
+
}
|
|
207
226
|
}
|
|
208
227
|
return warnings;
|
|
209
228
|
}
|
|
@@ -222,7 +241,7 @@ function combineBuildCommand(buildCommand, preDeployCommand) {
|
|
|
222
241
|
DETECTION_FRAMEWORKS,
|
|
223
242
|
INTERNAL_QUEUES_PREFIX,
|
|
224
243
|
INTERNAL_SERVICE_PREFIX,
|
|
225
|
-
|
|
244
|
+
assignMountPaths,
|
|
226
245
|
combineBuildCommand,
|
|
227
246
|
filterFrameworksByRuntime,
|
|
228
247
|
getBuilderForRuntime,
|
|
@@ -234,6 +253,7 @@ function combineBuildCommand(buildCommand, preDeployCommand) {
|
|
|
234
253
|
hasFile,
|
|
235
254
|
inferRuntimeFromFramework,
|
|
236
255
|
inferServiceRuntime,
|
|
256
|
+
isBFFFramework,
|
|
237
257
|
isFrontendFramework,
|
|
238
258
|
isRouteOwningBuilder,
|
|
239
259
|
isStaticBuild,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/fs-detectors",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.1",
|
|
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.32.1",
|
|
24
23
|
"@vercel/error-utils": "2.2.0",
|
|
25
|
-
"@vercel/
|
|
26
|
-
"@vercel/frameworks": "3.30.
|
|
24
|
+
"@vercel/build-utils": "13.32.2",
|
|
25
|
+
"@vercel/frameworks": "3.30.1",
|
|
26
|
+
"@vercel/routing-utils": "6.4.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/glob": "7.2.0",
|