@vercel/fs-detectors 5.8.8 → 5.8.10
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.
|
@@ -38,6 +38,9 @@ const APPS_WEB_DIR = "apps/web";
|
|
|
38
38
|
const BACKEND_DIR = "backend";
|
|
39
39
|
const SERVICES_DIR = "services";
|
|
40
40
|
const FRONTEND_LOCATIONS = [FRONTEND_DIR, APPS_WEB_DIR];
|
|
41
|
+
const DETECTION_FRAMEWORKS = import_frameworks.default.filter(
|
|
42
|
+
(framework) => !framework.experimental || framework.runtimeFramework
|
|
43
|
+
);
|
|
41
44
|
async function autoDetectServices(options) {
|
|
42
45
|
const { fs } = options;
|
|
43
46
|
const rootFrameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
@@ -117,7 +120,7 @@ async function detectServicesFrontendSubdir(fs, frontendFramework, frontendLocat
|
|
|
117
120
|
const serviceName = frontendLocation.split("/").pop() || "frontend";
|
|
118
121
|
services[serviceName] = {
|
|
119
122
|
framework: frontendFramework.slug ?? void 0,
|
|
120
|
-
|
|
123
|
+
entrypoint: frontendLocation,
|
|
121
124
|
routePrefix: "/"
|
|
122
125
|
};
|
|
123
126
|
const backendResult = await detectBackendServices(fs);
|
|
@@ -198,7 +201,8 @@ async function detectServiceInDir(fs, dirPath, serviceName) {
|
|
|
198
201
|
const serviceFs = fs.chdir(dirPath);
|
|
199
202
|
const frameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
200
203
|
fs: serviceFs,
|
|
201
|
-
frameworkList:
|
|
204
|
+
frameworkList: DETECTION_FRAMEWORKS,
|
|
205
|
+
useExperimentalFrameworks: true
|
|
202
206
|
});
|
|
203
207
|
if (frameworks.length > 1) {
|
|
204
208
|
const frameworkNames = frameworks.map((f) => f.name).join(", ");
|
|
@@ -215,7 +219,7 @@ async function detectServiceInDir(fs, dirPath, serviceName) {
|
|
|
215
219
|
return {
|
|
216
220
|
service: {
|
|
217
221
|
framework: framework.slug ?? void 0,
|
|
218
|
-
|
|
222
|
+
entrypoint: dirPath,
|
|
219
223
|
routePrefix: `/_/${serviceName}`
|
|
220
224
|
}
|
|
221
225
|
};
|
|
@@ -53,7 +53,8 @@ async function detectServices(options) {
|
|
|
53
53
|
if (autoResult.services) {
|
|
54
54
|
const result2 = await (0, import_resolve.resolveAllConfiguredServices)(
|
|
55
55
|
autoResult.services,
|
|
56
|
-
scopedFs
|
|
56
|
+
scopedFs,
|
|
57
|
+
"generated"
|
|
57
58
|
);
|
|
58
59
|
const routes2 = generateServicesRoutes(result2.services);
|
|
59
60
|
return {
|
|
@@ -77,7 +78,8 @@ async function detectServices(options) {
|
|
|
77
78
|
}
|
|
78
79
|
const result = await (0, import_resolve.resolveAllConfiguredServices)(
|
|
79
80
|
configuredServices,
|
|
80
|
-
scopedFs
|
|
81
|
+
scopedFs,
|
|
82
|
+
"configured"
|
|
81
83
|
);
|
|
82
84
|
const routes = generateServicesRoutes(result.services);
|
|
83
85
|
return {
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
import type { Service, ExperimentalServiceConfig, ExperimentalServices, ServiceDetectionError } from './types';
|
|
2
2
|
import type { DetectorFilesystem } from '../detectors/filesystem';
|
|
3
|
+
interface ResolvedEntrypointPath {
|
|
4
|
+
normalized: string;
|
|
5
|
+
isDirectory: boolean;
|
|
6
|
+
}
|
|
7
|
+
type RoutePrefixSource = 'configured' | 'generated';
|
|
8
|
+
interface ResolveConfiguredServiceOptions {
|
|
9
|
+
name: string;
|
|
10
|
+
config: ExperimentalServiceConfig;
|
|
11
|
+
fs: DetectorFilesystem;
|
|
12
|
+
group?: string;
|
|
13
|
+
resolvedEntrypoint?: ResolvedEntrypointPath;
|
|
14
|
+
routePrefixSource?: RoutePrefixSource;
|
|
15
|
+
}
|
|
3
16
|
/**
|
|
4
17
|
* Validate a service configuration from vercel.json experimentalServices.
|
|
5
18
|
*/
|
|
6
19
|
export declare function validateServiceConfig(name: string, config: ExperimentalServiceConfig): ServiceDetectionError | null;
|
|
20
|
+
export declare function validateServiceEntrypoint(name: string, config: ExperimentalServiceConfig, resolvedEntrypoint: ResolvedEntrypointPath): ServiceDetectionError | null;
|
|
7
21
|
/**
|
|
8
22
|
* Resolve a single service from user configuration.
|
|
9
23
|
*/
|
|
10
|
-
export declare function resolveConfiguredService(
|
|
24
|
+
export declare function resolveConfiguredService(options: ResolveConfiguredServiceOptions): Promise<Service>;
|
|
11
25
|
/**
|
|
12
26
|
* Resolve all services from vercel.json experimentalServices.
|
|
13
27
|
* Validates each service configuration.
|
|
14
28
|
*/
|
|
15
|
-
export declare function resolveAllConfiguredServices(services: ExperimentalServices, fs: DetectorFilesystem): Promise<{
|
|
29
|
+
export declare function resolveAllConfiguredServices(services: ExperimentalServices, fs: DetectorFilesystem, routePrefixSource?: RoutePrefixSource): Promise<{
|
|
16
30
|
services: Service[];
|
|
17
31
|
errors: ServiceDetectionError[];
|
|
18
32
|
}>;
|
|
33
|
+
export {};
|
package/dist/services/resolve.js
CHANGED
|
@@ -30,16 +30,44 @@ var resolve_exports = {};
|
|
|
30
30
|
__export(resolve_exports, {
|
|
31
31
|
resolveAllConfiguredServices: () => resolveAllConfiguredServices,
|
|
32
32
|
resolveConfiguredService: () => resolveConfiguredService,
|
|
33
|
-
validateServiceConfig: () => validateServiceConfig
|
|
33
|
+
validateServiceConfig: () => validateServiceConfig,
|
|
34
|
+
validateServiceEntrypoint: () => validateServiceEntrypoint
|
|
34
35
|
});
|
|
35
36
|
module.exports = __toCommonJS(resolve_exports);
|
|
36
37
|
var import_path = require("path");
|
|
37
38
|
var import_types = require("./types");
|
|
38
39
|
var import_utils = require("./utils");
|
|
39
40
|
var import_frameworks = __toESM(require("@vercel/frameworks"));
|
|
41
|
+
var import_detect_framework = require("../detect-framework");
|
|
40
42
|
var import_routing_utils = require("@vercel/routing-utils");
|
|
41
43
|
const frameworksBySlug = new Map(import_frameworks.default.map((f) => [f.slug, f]));
|
|
42
44
|
const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
45
|
+
function normalizeServiceEntrypoint(entrypoint) {
|
|
46
|
+
const normalized = import_path.posix.normalize(entrypoint);
|
|
47
|
+
return normalized === "" ? "." : normalized;
|
|
48
|
+
}
|
|
49
|
+
async function resolveEntrypointPath({
|
|
50
|
+
fs,
|
|
51
|
+
serviceName,
|
|
52
|
+
entrypoint
|
|
53
|
+
}) {
|
|
54
|
+
const normalized = normalizeServiceEntrypoint(entrypoint);
|
|
55
|
+
if (!await fs.hasPath(normalized)) {
|
|
56
|
+
return {
|
|
57
|
+
error: {
|
|
58
|
+
code: "ENTRYPOINT_NOT_FOUND",
|
|
59
|
+
message: `Service "${serviceName}" has entrypoint "${entrypoint}" but that path does not exist.`,
|
|
60
|
+
serviceName
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
entrypoint: {
|
|
66
|
+
normalized,
|
|
67
|
+
isDirectory: !await fs.isFile(normalized)
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
43
71
|
function toWorkspaceRelativeEntrypoint(entrypoint, workspace) {
|
|
44
72
|
const normalizedEntrypoint = import_path.posix.normalize(entrypoint);
|
|
45
73
|
if (workspace === ".") {
|
|
@@ -95,6 +123,33 @@ async function inferWorkspaceFromNearestManifest({
|
|
|
95
123
|
}
|
|
96
124
|
return void 0;
|
|
97
125
|
}
|
|
126
|
+
async function detectFrameworkFromWorkspace({
|
|
127
|
+
fs,
|
|
128
|
+
workspace,
|
|
129
|
+
serviceName
|
|
130
|
+
}) {
|
|
131
|
+
const serviceFs = workspace === "." ? fs : fs.chdir(workspace);
|
|
132
|
+
const frameworks = await (0, import_detect_framework.detectFrameworks)({
|
|
133
|
+
fs: serviceFs,
|
|
134
|
+
frameworkList: import_frameworks.default
|
|
135
|
+
});
|
|
136
|
+
if (frameworks.length > 1) {
|
|
137
|
+
const frameworkNames = frameworks.map((f) => f.name).join(", ");
|
|
138
|
+
return {
|
|
139
|
+
error: {
|
|
140
|
+
code: "MULTIPLE_FRAMEWORKS_SERVICE",
|
|
141
|
+
message: `Multiple frameworks detected in ${workspace === "." ? "project root" : `${workspace}/`}: ${frameworkNames}. Specify "framework" explicitly in experimentalServices.`,
|
|
142
|
+
serviceName
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (frameworks.length === 1) {
|
|
147
|
+
return {
|
|
148
|
+
framework: frameworks[0].slug ?? void 0
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
98
153
|
function isReservedServiceRoutePrefix(routePrefix) {
|
|
99
154
|
const normalized = (0, import_routing_utils.normalizeRoutePrefix)(routePrefix);
|
|
100
155
|
return normalized === import_utils.INTERNAL_SERVICE_PREFIX || normalized.startsWith(`${import_utils.INTERNAL_SERVICE_PREFIX}/`);
|
|
@@ -174,7 +229,10 @@ function validateServiceConfig(name, config) {
|
|
|
174
229
|
serviceName: name
|
|
175
230
|
};
|
|
176
231
|
}
|
|
177
|
-
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
function validateServiceEntrypoint(name, config, resolvedEntrypoint) {
|
|
235
|
+
if (!resolvedEntrypoint.isDirectory && !config.builder && !config.runtime && !config.framework) {
|
|
178
236
|
const runtime = (0, import_utils.inferServiceRuntime)({ entrypoint: config.entrypoint });
|
|
179
237
|
if (!runtime) {
|
|
180
238
|
const supported = Object.keys(import_types.ENTRYPOINT_EXTENSIONS).join(", ");
|
|
@@ -187,22 +245,52 @@ function validateServiceConfig(name, config) {
|
|
|
187
245
|
}
|
|
188
246
|
return null;
|
|
189
247
|
}
|
|
190
|
-
async function resolveConfiguredService(
|
|
248
|
+
async function resolveConfiguredService(options) {
|
|
249
|
+
const {
|
|
250
|
+
name,
|
|
251
|
+
config,
|
|
252
|
+
fs,
|
|
253
|
+
group,
|
|
254
|
+
resolvedEntrypoint,
|
|
255
|
+
routePrefixSource = "configured"
|
|
256
|
+
} = options;
|
|
191
257
|
const type = config.type || "web";
|
|
192
|
-
const
|
|
193
|
-
let
|
|
194
|
-
|
|
195
|
-
|
|
258
|
+
const rawEntrypoint = config.entrypoint;
|
|
259
|
+
let resolvedEntrypointPath = resolvedEntrypoint;
|
|
260
|
+
if (!resolvedEntrypointPath && typeof rawEntrypoint === "string") {
|
|
261
|
+
const resolved = await resolveEntrypointPath({
|
|
262
|
+
fs,
|
|
263
|
+
serviceName: name,
|
|
264
|
+
entrypoint: rawEntrypoint
|
|
265
|
+
});
|
|
266
|
+
resolvedEntrypointPath = resolved.entrypoint;
|
|
267
|
+
}
|
|
268
|
+
if (typeof rawEntrypoint === "string" && !resolvedEntrypointPath) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Failed to resolve entrypoint "${rawEntrypoint}" for service "${name}".`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
const normalizedEntrypoint = resolvedEntrypointPath?.normalized;
|
|
274
|
+
const entrypointIsDirectory = Boolean(resolvedEntrypointPath?.isDirectory);
|
|
275
|
+
const inferredRuntime = (0, import_utils.inferServiceRuntime)({
|
|
276
|
+
...config,
|
|
277
|
+
entrypoint: entrypointIsDirectory ? void 0 : normalizedEntrypoint
|
|
278
|
+
});
|
|
279
|
+
let workspace = ".";
|
|
280
|
+
let resolvedEntrypointFile = entrypointIsDirectory || !normalizedEntrypoint ? void 0 : normalizedEntrypoint;
|
|
281
|
+
if (entrypointIsDirectory && normalizedEntrypoint) {
|
|
282
|
+
workspace = normalizedEntrypoint;
|
|
283
|
+
} else {
|
|
196
284
|
const inferredWorkspace = await inferWorkspaceFromNearestManifest({
|
|
197
285
|
fs,
|
|
198
|
-
entrypoint:
|
|
286
|
+
entrypoint: resolvedEntrypointFile,
|
|
199
287
|
runtime: inferredRuntime
|
|
200
288
|
});
|
|
201
289
|
if (inferredWorkspace) {
|
|
202
290
|
workspace = inferredWorkspace;
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
291
|
+
if (resolvedEntrypointFile) {
|
|
292
|
+
resolvedEntrypointFile = toWorkspaceRelativeEntrypoint(
|
|
293
|
+
resolvedEntrypointFile,
|
|
206
294
|
inferredWorkspace
|
|
207
295
|
);
|
|
208
296
|
}
|
|
@@ -215,13 +303,13 @@ async function resolveConfiguredService(name, config, fs, group) {
|
|
|
215
303
|
if (config.framework) {
|
|
216
304
|
const framework = frameworksBySlug.get(config.framework);
|
|
217
305
|
builderUse = framework?.useRuntime?.use || "@vercel/static-build";
|
|
218
|
-
builderSrc =
|
|
306
|
+
builderSrc = resolvedEntrypointFile || framework?.useRuntime?.src || "package.json";
|
|
219
307
|
} else if (config.builder) {
|
|
220
308
|
builderUse = config.builder;
|
|
221
|
-
builderSrc =
|
|
309
|
+
builderSrc = resolvedEntrypointFile;
|
|
222
310
|
} else {
|
|
223
311
|
builderUse = (0, import_utils.getBuilderForRuntime)(inferredRuntime);
|
|
224
|
-
builderSrc =
|
|
312
|
+
builderSrc = resolvedEntrypointFile;
|
|
225
313
|
}
|
|
226
314
|
const routePrefix = type === "web" && config.routePrefix ? config.routePrefix.startsWith("/") ? config.routePrefix : `/${config.routePrefix}` : void 0;
|
|
227
315
|
const isRoot = workspace === ".";
|
|
@@ -254,8 +342,9 @@ async function resolveConfiguredService(name, config, fs, group) {
|
|
|
254
342
|
type,
|
|
255
343
|
group,
|
|
256
344
|
workspace,
|
|
257
|
-
entrypoint:
|
|
345
|
+
entrypoint: resolvedEntrypointFile,
|
|
258
346
|
routePrefix,
|
|
347
|
+
routePrefixSource: type === "web" && typeof routePrefix === "string" ? routePrefixSource : void 0,
|
|
259
348
|
framework: config.framework,
|
|
260
349
|
builder: {
|
|
261
350
|
src: builderSrc,
|
|
@@ -270,7 +359,7 @@ async function resolveConfiguredService(name, config, fs, group) {
|
|
|
270
359
|
consumer
|
|
271
360
|
};
|
|
272
361
|
}
|
|
273
|
-
async function resolveAllConfiguredServices(services, fs) {
|
|
362
|
+
async function resolveAllConfiguredServices(services, fs, routePrefixSource = "configured") {
|
|
274
363
|
const resolved = [];
|
|
275
364
|
const errors = [];
|
|
276
365
|
const webServicesByRoutePrefix = /* @__PURE__ */ new Map();
|
|
@@ -281,7 +370,63 @@ async function resolveAllConfiguredServices(services, fs) {
|
|
|
281
370
|
errors.push(validationError);
|
|
282
371
|
continue;
|
|
283
372
|
}
|
|
284
|
-
|
|
373
|
+
let resolvedEntrypoint;
|
|
374
|
+
if (typeof serviceConfig.entrypoint === "string") {
|
|
375
|
+
const resolvedPath = await resolveEntrypointPath({
|
|
376
|
+
fs,
|
|
377
|
+
serviceName: name,
|
|
378
|
+
entrypoint: serviceConfig.entrypoint
|
|
379
|
+
});
|
|
380
|
+
if (resolvedPath.error) {
|
|
381
|
+
errors.push(resolvedPath.error);
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
resolvedEntrypoint = resolvedPath.entrypoint;
|
|
385
|
+
}
|
|
386
|
+
if (resolvedEntrypoint) {
|
|
387
|
+
const entrypointError = validateServiceEntrypoint(
|
|
388
|
+
name,
|
|
389
|
+
serviceConfig,
|
|
390
|
+
resolvedEntrypoint
|
|
391
|
+
);
|
|
392
|
+
if (entrypointError) {
|
|
393
|
+
errors.push(entrypointError);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
let resolvedConfig = serviceConfig;
|
|
398
|
+
const shouldDetectFramework = !serviceConfig.framework && Boolean(resolvedEntrypoint?.isDirectory);
|
|
399
|
+
if (shouldDetectFramework) {
|
|
400
|
+
const workspace = resolvedEntrypoint.normalized;
|
|
401
|
+
const { framework, error } = await detectFrameworkFromWorkspace({
|
|
402
|
+
fs,
|
|
403
|
+
workspace,
|
|
404
|
+
serviceName: name
|
|
405
|
+
});
|
|
406
|
+
if (error) {
|
|
407
|
+
errors.push(error);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (!framework) {
|
|
411
|
+
errors.push({
|
|
412
|
+
code: "MISSING_SERVICE_FRAMEWORK",
|
|
413
|
+
message: `Service "${name}" uses directory entrypoint "${serviceConfig.entrypoint}" but no framework could be detected in "${workspace}". Specify "framework" explicitly or use a file entrypoint.`,
|
|
414
|
+
serviceName: name
|
|
415
|
+
});
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
resolvedConfig = {
|
|
419
|
+
...serviceConfig,
|
|
420
|
+
framework
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const service = await resolveConfiguredService({
|
|
424
|
+
name,
|
|
425
|
+
config: resolvedConfig,
|
|
426
|
+
fs,
|
|
427
|
+
resolvedEntrypoint,
|
|
428
|
+
routePrefixSource
|
|
429
|
+
});
|
|
285
430
|
if (service.type === "web" && typeof service.routePrefix === "string") {
|
|
286
431
|
const normalizedRoutePrefix = (0, import_routing_utils.normalizeRoutePrefix)(service.routePrefix);
|
|
287
432
|
const existingServiceName = webServicesByRoutePrefix.get(
|
|
@@ -305,5 +450,6 @@ async function resolveAllConfiguredServices(services, fs) {
|
|
|
305
450
|
0 && (module.exports = {
|
|
306
451
|
resolveAllConfiguredServices,
|
|
307
452
|
resolveConfiguredService,
|
|
308
|
-
validateServiceConfig
|
|
453
|
+
validateServiceConfig,
|
|
454
|
+
validateServiceEntrypoint
|
|
309
455
|
});
|
package/dist/services/utils.d.ts
CHANGED
|
@@ -22,9 +22,10 @@ export declare function isRouteOwningBuilder(service: ResolvedService): boolean;
|
|
|
22
22
|
*
|
|
23
23
|
* Priority (highest to lowest):
|
|
24
24
|
* 1. Explicit runtime (user specified in config)
|
|
25
|
-
* 2.
|
|
26
|
-
* 3.
|
|
27
|
-
* 4.
|
|
25
|
+
* 2. Runtime framework slug (ruby → ruby, go → go)
|
|
26
|
+
* 3. Framework detection (fastapi → python, express → node)
|
|
27
|
+
* 4. Builder detection (@vercel/python → python)
|
|
28
|
+
* 5. Entrypoint extension (.py → python, .ts → node)
|
|
28
29
|
*
|
|
29
30
|
* @returns The inferred runtime, or undefined if none can be determined.
|
|
30
31
|
*/
|
package/dist/services/utils.js
CHANGED
|
@@ -58,6 +58,9 @@ function inferServiceRuntime(config) {
|
|
|
58
58
|
if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
|
|
59
59
|
return config.runtime;
|
|
60
60
|
}
|
|
61
|
+
if (config.framework && config.framework in import_types.RUNTIME_BUILDERS) {
|
|
62
|
+
return config.framework;
|
|
63
|
+
}
|
|
61
64
|
if ((0, import_framework_helpers.isPythonFramework)(config.framework)) {
|
|
62
65
|
return "python";
|
|
63
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/fs-detectors",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.10",
|
|
4
4
|
"description": "Vercel filesystem detectors",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"json5": "2.2.2",
|
|
20
20
|
"minimatch": "3.1.2",
|
|
21
21
|
"semver": "6.3.1",
|
|
22
|
-
"@vercel/frameworks": "3.18.0",
|
|
23
22
|
"@vercel/error-utils": "2.0.3",
|
|
23
|
+
"@vercel/frameworks": "3.19.0",
|
|
24
24
|
"@vercel/routing-utils": "5.3.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@types/semver": "7.3.10",
|
|
33
33
|
"jest-junit": "16.0.0",
|
|
34
34
|
"typescript": "4.9.5",
|
|
35
|
-
"@vercel/build-utils": "13.
|
|
35
|
+
"@vercel/build-utils": "13.5.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "node ../../utils/build.mjs",
|