@vercel/fs-detectors 6.15.8 → 7.0.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,914 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var resolve_exports = {};
20
- __export(resolve_exports, {
21
- detectFrameworkFromWorkspace: () => detectFrameworkFromWorkspace,
22
- getServiceFs: () => getServiceFs,
23
- inferWorkspaceFromNearestManifest: () => inferWorkspaceFromNearestManifest,
24
- parsePyModuleAttrEntrypoint: () => parsePyModuleAttrEntrypoint,
25
- resolveAllConfiguredServices: () => resolveAllConfiguredServices,
26
- resolveConfiguredService: () => resolveConfiguredService,
27
- resolveEntrypointPath: () => resolveEntrypointPath,
28
- validateServiceConfig: () => validateServiceConfig,
29
- validateServiceEntrypoint: () => validateServiceEntrypoint
30
- });
31
- module.exports = __toCommonJS(resolve_exports);
32
- var import_path = require("path");
33
- var import_build_utils = require("@vercel/build-utils");
34
- var import_types = require("./types");
35
- var import_utils = require("./utils");
36
- var import_frameworks = require("@vercel/frameworks");
37
- var import_detect_framework = require("../detect-framework");
38
- var import_routing_utils = require("@vercel/routing-utils");
39
- var import_build_utils2 = require("@vercel/build-utils");
40
- const frameworksBySlug = new Map(import_frameworks.frameworkList.map((f) => [f.slug, f]));
41
- const PYTHON_MODULE_ATTR_RE = /^([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*):([A-Za-z_][\w]*)$/;
42
- function parsePyModuleAttrEntrypoint(entrypoint) {
43
- const match = PYTHON_MODULE_ATTR_RE.exec(entrypoint);
44
- if (!match)
45
- return null;
46
- return {
47
- attrName: match[2],
48
- filePath: match[1].replace(/\./g, "/") + ".py"
49
- };
50
- }
51
- const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
52
- const DNS_LABEL_RE = /^(?!-)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
53
- const ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
54
- const ENTRYPOINT_REQUIRED_RUNTIMES = /* @__PURE__ */ new Set([
55
- "node",
56
- "python",
57
- "go"
58
- ]);
59
- function isContainerRuntime(config) {
60
- return config.runtime === "container";
61
- }
62
- function normalizeContainerCommand(command) {
63
- return Array.isArray(command) ? command : [command];
64
- }
65
- async function getServiceFs(fs, serviceName, root) {
66
- if (!root) {
67
- return { fs };
68
- }
69
- const normalizedRoot = (0, import_utils.stripTrailingSlash)(import_path.posix.normalize(root));
70
- if (!await fs.hasPath(normalizedRoot)) {
71
- return {
72
- fs,
73
- error: {
74
- code: "ROOT_NOT_FOUND",
75
- message: `Service "${serviceName}" has root "${root}" but that directory does not exist.`,
76
- serviceName
77
- }
78
- };
79
- }
80
- if (await fs.isFile(normalizedRoot)) {
81
- return {
82
- fs,
83
- error: {
84
- code: "ROOT_NOT_DIRECTORY",
85
- message: `Service "${serviceName}" has root "${root}" but that path is a file, not a directory.`,
86
- serviceName
87
- }
88
- };
89
- }
90
- return { fs: fs.chdir(normalizedRoot) };
91
- }
92
- function normalizeServiceEntrypoint(entrypoint) {
93
- const normalized = import_path.posix.normalize(entrypoint);
94
- return normalized === "" ? "." : normalized;
95
- }
96
- function getEffectiveServiceTrigger(config) {
97
- if (config.type === "cron") {
98
- return "schedule";
99
- }
100
- if (config.type === "worker") {
101
- return "queue";
102
- }
103
- if (config.type !== "job") {
104
- return void 0;
105
- }
106
- return config.trigger;
107
- }
108
- function getEntrypointRequiredRuntime(config) {
109
- if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
110
- return config.runtime;
111
- }
112
- return (0, import_utils.inferRuntimeFromFramework)(config.framework);
113
- }
114
- function validateBackendFileEntrypoint(name, config, resolvedEntrypoint, options) {
115
- if (!options.requireFileEntrypointForBackendRuntimes || !resolvedEntrypoint?.isDirectory) {
116
- return null;
117
- }
118
- const runtime = getEntrypointRequiredRuntime(config);
119
- if (!runtime || !ENTRYPOINT_REQUIRED_RUNTIMES.has(runtime)) {
120
- return null;
121
- }
122
- return {
123
- code: "INVALID_ENTRYPOINT",
124
- message: `Service "${name}" must specify a file "entrypoint" when using "${config.runtime ? "runtime" : "framework"}" "${config.runtime || config.framework}".`,
125
- serviceName: name
126
- };
127
- }
128
- async function resolveEntrypointPath({
129
- fs,
130
- serviceName,
131
- entrypoint
132
- }) {
133
- const normalized = normalizeServiceEntrypoint(entrypoint);
134
- if (!await fs.hasPath(normalized)) {
135
- return {
136
- error: {
137
- code: "ENTRYPOINT_NOT_FOUND",
138
- message: `Service "${serviceName}" has entrypoint "${entrypoint}" but that path does not exist.`,
139
- serviceName
140
- }
141
- };
142
- }
143
- return {
144
- entrypoint: {
145
- normalized,
146
- isDirectory: !await fs.isFile(normalized)
147
- }
148
- };
149
- }
150
- function isDockerfileEntrypoint(entrypoint) {
151
- const base = import_path.posix.basename(entrypoint).toLowerCase();
152
- return base === "dockerfile" || base === "containerfile" || base.endsWith(".dockerfile");
153
- }
154
- function toWorkspaceRelativeEntrypoint(entrypoint, workspace) {
155
- const normalizedEntrypoint = import_path.posix.normalize(entrypoint);
156
- if (workspace === ".") {
157
- return normalizedEntrypoint;
158
- }
159
- const workspacePrefix = `${workspace}/`;
160
- if (normalizedEntrypoint.startsWith(workspacePrefix)) {
161
- return normalizedEntrypoint.slice(workspacePrefix.length);
162
- }
163
- const relativeEntrypoint = import_path.posix.relative(
164
- workspace,
165
- normalizedEntrypoint
166
- );
167
- if (relativeEntrypoint === "" || relativeEntrypoint.startsWith("..")) {
168
- return normalizedEntrypoint;
169
- }
170
- return relativeEntrypoint;
171
- }
172
- async function inferWorkspaceFromNearestManifest({
173
- fs,
174
- entrypoint,
175
- runtime
176
- }) {
177
- if (!entrypoint || !runtime) {
178
- return void 0;
179
- }
180
- const manifests = import_types.RUNTIME_MANIFESTS[runtime];
181
- if (!manifests || manifests.length === 0) {
182
- return void 0;
183
- }
184
- let dir = import_path.posix.dirname(import_path.posix.normalize(entrypoint)) || ".";
185
- if (dir === "") {
186
- dir = ".";
187
- }
188
- let reachedRoot = false;
189
- while (!reachedRoot) {
190
- for (const manifest of manifests) {
191
- const manifestPath = dir === "." ? manifest : import_path.posix.join(dir, manifest);
192
- if (await (0, import_utils.hasFile)(fs, manifestPath)) {
193
- return dir;
194
- }
195
- }
196
- if (dir === "." || dir === "/") {
197
- reachedRoot = true;
198
- } else {
199
- const parent = import_path.posix.dirname(dir);
200
- if (!parent || parent === dir) {
201
- reachedRoot = true;
202
- } else {
203
- dir = parent;
204
- }
205
- }
206
- }
207
- return void 0;
208
- }
209
- async function detectFrameworkFromWorkspace({
210
- fs,
211
- workspace,
212
- serviceName,
213
- runtime
214
- }) {
215
- const serviceFs = workspace === "." ? fs : fs.chdir(workspace);
216
- const frameworkCandidates = (0, import_utils.filterFrameworksByRuntime)(import_frameworks.frameworkList, runtime);
217
- const frameworks = await (0, import_detect_framework.detectFrameworks)({
218
- fs: serviceFs,
219
- frameworkList: frameworkCandidates
220
- });
221
- if (frameworks.length > 1) {
222
- const frameworkNames = frameworks.map((f) => f.name).join(", ");
223
- return {
224
- error: {
225
- code: "MULTIPLE_FRAMEWORKS_SERVICE",
226
- message: `Multiple frameworks detected in ${workspace === "." ? "project root" : `${workspace}/`}: ${frameworkNames}. Specify "framework" explicitly in services.`,
227
- serviceName
228
- }
229
- };
230
- }
231
- if (frameworks.length === 1) {
232
- return {
233
- framework: frameworks[0].slug ?? void 0
234
- };
235
- }
236
- return {};
237
- }
238
- function isReservedServiceRoutePrefix(routePrefix) {
239
- const normalized = (0, import_routing_utils.normalizeRoutePrefix)(routePrefix);
240
- return normalized === import_utils.INTERNAL_SERVICE_PREFIX || normalized.startsWith(`${import_utils.INTERNAL_SERVICE_PREFIX}/`);
241
- }
242
- function resolveServiceRoutingConfig(name, config) {
243
- const hasLegacyRoutePrefix = typeof config.routePrefix === "string";
244
- const hasLegacySubdomain = typeof config.subdomain === "string";
245
- if (config.mount === void 0) {
246
- return {
247
- routing: {
248
- routePrefix: config.routePrefix,
249
- subdomain: config.subdomain,
250
- routePrefixConfigured: hasLegacyRoutePrefix
251
- }
252
- };
253
- }
254
- if (hasLegacyRoutePrefix || hasLegacySubdomain) {
255
- return {
256
- error: {
257
- code: "CONFLICTING_MOUNT_CONFIG",
258
- message: `Service "${name}" cannot mix "mount" with "routePrefix" or "subdomain". Use only one routing configuration style.`,
259
- serviceName: name
260
- }
261
- };
262
- }
263
- if (typeof config.mount === "string") {
264
- return {
265
- routing: {
266
- routePrefix: config.mount,
267
- routePrefixConfigured: true
268
- }
269
- };
270
- }
271
- if (!config.mount || typeof config.mount !== "object" || Array.isArray(config.mount)) {
272
- return {
273
- error: {
274
- code: "INVALID_MOUNT",
275
- message: `Service "${name}" has invalid "mount" config. Use a string path such as "/api" or an object like { path: "/api", subdomain: "api" }.`,
276
- serviceName: name
277
- }
278
- };
279
- }
280
- const hasInvalidMountKeys = Object.keys(config.mount).some(
281
- (key) => key !== "path" && key !== "subdomain"
282
- );
283
- if (hasInvalidMountKeys) {
284
- return {
285
- error: {
286
- code: "INVALID_MOUNT",
287
- message: `Service "${name}" has invalid "mount" config. Only "path" and "subdomain" are supported.`,
288
- serviceName: name
289
- }
290
- };
291
- }
292
- const mountPath = config.mount.path;
293
- const mountSubdomain = config.mount.subdomain;
294
- if (mountPath !== void 0 && typeof mountPath !== "string" || mountSubdomain !== void 0 && typeof mountSubdomain !== "string") {
295
- return {
296
- error: {
297
- code: "INVALID_MOUNT",
298
- message: `Service "${name}" has invalid "mount" config. "path" and "subdomain" must be strings when provided.`,
299
- serviceName: name
300
- }
301
- };
302
- }
303
- if (typeof mountPath !== "string" && typeof mountSubdomain !== "string") {
304
- return {
305
- error: {
306
- code: "INVALID_MOUNT",
307
- message: `Service "${name}" has invalid "mount" config. Specify at least one of "mount.path" or "mount.subdomain".`,
308
- serviceName: name
309
- }
310
- };
311
- }
312
- return {
313
- routing: {
314
- routePrefix: mountPath,
315
- subdomain: mountSubdomain,
316
- routePrefixConfigured: typeof mountPath === "string"
317
- }
318
- };
319
- }
320
- function validateServiceConfig(name, config, options = {}) {
321
- if (!SERVICE_NAME_REGEX.test(name)) {
322
- return {
323
- code: "INVALID_SERVICE_NAME",
324
- 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.`,
325
- serviceName: name
326
- };
327
- }
328
- if (!config || typeof config !== "object") {
329
- return {
330
- code: "INVALID_SERVICE_CONFIG",
331
- message: `Service "${name}" has an invalid configuration. Expected an object.`,
332
- serviceName: name
333
- };
334
- }
335
- const serviceType = config.type || "web";
336
- const effectiveTrigger = getEffectiveServiceTrigger(config);
337
- const effectiveService = {
338
- type: serviceType,
339
- trigger: effectiveTrigger
340
- };
341
- const isJobService = serviceType === "job" || serviceType === "cron";
342
- const isScheduleJobService = (0, import_build_utils.isScheduleTriggeredService)(effectiveService);
343
- const isQueueJobService = serviceType === "job" && (0, import_build_utils.isQueueTriggeredService)(effectiveService);
344
- const isWorkflowService = serviceType === "job" && effectiveTrigger === "workflow";
345
- const isNonWebService = serviceType === "worker" || isJobService;
346
- const serviceTypeLabel = isJobService ? "Job" : serviceType === "worker" ? "Worker" : "Web";
347
- const routingResult = resolveServiceRoutingConfig(name, config);
348
- if (routingResult.error) {
349
- return routingResult.error;
350
- }
351
- const configuredRoutePrefix = routingResult.routing?.routePrefix;
352
- const configuredSubdomain = routingResult.routing?.subdomain;
353
- const hasRoutePrefix = typeof configuredRoutePrefix === "string";
354
- const hasSubdomain = typeof configuredSubdomain === "string";
355
- if (hasSubdomain && !DNS_LABEL_RE.test(configuredSubdomain)) {
356
- return {
357
- code: "INVALID_SUBDOMAIN",
358
- message: `Web service "${name}" has invalid subdomain "${configuredSubdomain}". Use a single DNS label such as "api".`,
359
- serviceName: name
360
- };
361
- }
362
- if (serviceType === "web" && !hasRoutePrefix && !hasSubdomain) {
363
- return {
364
- code: "MISSING_ROUTE_PREFIX",
365
- message: `Web service "${name}" must specify at least one of "mount", "routePrefix", or "subdomain".`,
366
- serviceName: name
367
- };
368
- }
369
- if (serviceType === "web" && configuredRoutePrefix && isReservedServiceRoutePrefix(configuredRoutePrefix)) {
370
- return {
371
- code: "RESERVED_ROUTE_PREFIX",
372
- message: `Web service "${name}" cannot use routePrefix "${configuredRoutePrefix}". The "${import_utils.INTERNAL_SERVICE_PREFIX}" prefix is reserved for internal services routing.`,
373
- serviceName: name
374
- };
375
- }
376
- if (isNonWebService && configuredRoutePrefix) {
377
- return {
378
- code: "INVALID_ROUTE_PREFIX",
379
- message: `${serviceTypeLabel} service "${name}" cannot have "routePrefix" or "mount". Only web services should specify path-based routing.`,
380
- serviceName: name
381
- };
382
- }
383
- if (isNonWebService && hasSubdomain) {
384
- return {
385
- code: "INVALID_HOST_ROUTING_CONFIG",
386
- message: `${serviceTypeLabel} service "${name}" cannot have "subdomain" or "mount.subdomain". Only web services should specify subdomain routing.`,
387
- serviceName: name
388
- };
389
- }
390
- if (serviceType === "job" && effectiveTrigger === void 0) {
391
- return {
392
- code: "MISSING_JOB_TRIGGER",
393
- message: `Job service "${name}" is missing required "trigger" field.`,
394
- serviceName: name
395
- };
396
- }
397
- if (serviceType === "job" && effectiveTrigger && !import_build_utils.JOB_TRIGGERS.includes(effectiveTrigger)) {
398
- return {
399
- code: "INVALID_JOB_TRIGGER",
400
- message: `Job service "${name}" has invalid trigger "${effectiveTrigger}". Expected ${import_build_utils.JOB_TRIGGERS.map((t) => `"${t}"`).join(", ")}.`,
401
- serviceName: name
402
- };
403
- }
404
- if (isScheduleJobService && !config.schedule) {
405
- return {
406
- code: serviceType === "cron" ? "MISSING_CRON_SCHEDULE" : "MISSING_JOB_SCHEDULE",
407
- message: `${serviceTypeLabel} service "${name}" is missing required "schedule" field.`,
408
- serviceName: name
409
- };
410
- }
411
- if (isQueueJobService && (!Array.isArray(config.topics) || config.topics.length === 0)) {
412
- return {
413
- code: "MISSING_QUEUE_TOPICS",
414
- message: `${serviceTypeLabel} service "${name}" is missing required "topics" field.`,
415
- serviceName: name
416
- };
417
- }
418
- if (isWorkflowService && typeof config.entrypoint !== "string") {
419
- return {
420
- code: "MISSING_ENTRYPOINT",
421
- message: `Job service "${name}" with "workflow" trigger must specify "entrypoint".`,
422
- serviceName: name
423
- };
424
- }
425
- if (config.root !== void 0) {
426
- const normalizedRoot = import_path.posix.normalize(config.root);
427
- if (normalizedRoot.startsWith("/")) {
428
- return {
429
- code: "INVALID_ROOT",
430
- message: `Service "${name}" has invalid "root" "${config.root}". Must be a relative path.`,
431
- serviceName: name
432
- };
433
- }
434
- if (normalizedRoot === ".." || normalizedRoot.startsWith("../")) {
435
- return {
436
- code: "INVALID_ROOT",
437
- message: `Service "${name}" has invalid "root" "${config.root}". Must not escape the project root.`,
438
- serviceName: name
439
- };
440
- }
441
- }
442
- if (config.env !== void 0) {
443
- if (typeof config.env !== "object" || Array.isArray(config.env)) {
444
- return {
445
- code: "INVALID_ENV_VARS",
446
- message: `Service "${name}" has invalid "env". Must be an object keyed by environment variable name.`,
447
- serviceName: name
448
- };
449
- }
450
- for (const [envVarName, envVar] of Object.entries(config.env)) {
451
- if (!ENV_VAR_NAME_RE.test(envVarName)) {
452
- return {
453
- code: "INVALID_ENV_VAR_NAME",
454
- message: `Service "${name}" has invalid env key "${envVarName}". Must match /^[A-Za-z_][A-Za-z0-9_]*$/.`,
455
- serviceName: name
456
- };
457
- }
458
- if (!envVar || typeof envVar !== "object" || Array.isArray(envVar)) {
459
- return {
460
- code: "INVALID_ENV_VAR",
461
- message: `Service "${name}" has invalid env["${envVarName}"]. Must be an object with a "type" discriminator.`,
462
- serviceName: name
463
- };
464
- }
465
- const envVarType = envVar.type;
466
- if (envVarType !== "service-ref") {
467
- return {
468
- code: "INVALID_ENV_VAR_TYPE",
469
- message: `Service "${name}" env["${envVarName}"] has unknown type "${envVarType}".`,
470
- serviceName: name
471
- };
472
- }
473
- const refService = envVar.service;
474
- if (typeof refService !== "string" || refService.length === 0) {
475
- return {
476
- code: "INVALID_ENV_VAR_REF",
477
- message: `Service "${name}" env["${envVarName}"] must specify "service" as a non-empty string.`,
478
- serviceName: name
479
- };
480
- }
481
- }
482
- }
483
- if (config.runtime && !(config.runtime in import_types.RUNTIME_BUILDERS)) {
484
- return {
485
- code: "INVALID_RUNTIME",
486
- message: `Service "${name}" has invalid runtime "${config.runtime}".`,
487
- serviceName: name
488
- };
489
- }
490
- if (config.framework && !frameworksBySlug.has(config.framework)) {
491
- return {
492
- code: "INVALID_FRAMEWORK",
493
- message: `Service "${name}" has invalid framework "${config.framework}".`,
494
- serviceName: name
495
- };
496
- }
497
- if (config.runtime && config.framework) {
498
- const frameworkRuntime = (0, import_utils.inferRuntimeFromFramework)(config.framework);
499
- if (frameworkRuntime && frameworkRuntime !== config.runtime) {
500
- return {
501
- code: "RUNTIME_FRAMEWORK_MISMATCH",
502
- message: `Service "${name}" has conflicting runtime/framework: runtime "${config.runtime}" is incompatible with framework "${config.framework}" (runtime "${frameworkRuntime}").`,
503
- serviceName: name
504
- };
505
- }
506
- }
507
- const hasFramework = Boolean(config.framework);
508
- const hasBuilderOrRuntime = Boolean(config.builder || config.runtime);
509
- const hasEntrypoint = Boolean(config.entrypoint);
510
- const entrypointRequiredRuntime = getEntrypointRequiredRuntime(config);
511
- if (!hasFramework && !hasBuilderOrRuntime && !hasEntrypoint) {
512
- return {
513
- code: "MISSING_SERVICE_CONFIG",
514
- message: `Service "${name}" must specify "framework", "entrypoint", or both "builder"/"runtime" with "entrypoint".`,
515
- serviceName: name
516
- };
517
- }
518
- if (options.requireFileEntrypointForBackendRuntimes && !hasEntrypoint && entrypointRequiredRuntime && ENTRYPOINT_REQUIRED_RUNTIMES.has(entrypointRequiredRuntime)) {
519
- return {
520
- code: "MISSING_ENTRYPOINT",
521
- message: `Service "${name}" must specify "entrypoint" when using "${config.runtime ? "runtime" : "framework"}" "${config.runtime || config.framework}".`,
522
- serviceName: name
523
- };
524
- }
525
- if (hasBuilderOrRuntime && !hasFramework && !hasEntrypoint) {
526
- return {
527
- code: "MISSING_ENTRYPOINT",
528
- message: `Service "${name}" must specify "entrypoint" when using "${config.builder ? "builder" : "runtime"}".`,
529
- serviceName: name
530
- };
531
- }
532
- if (config.command !== void 0 && !isContainerRuntime(config)) {
533
- return {
534
- code: "INVALID_COMMAND",
535
- message: `Service "${name}" can only specify "command" when using runtime "container".`,
536
- serviceName: name
537
- };
538
- }
539
- return null;
540
- }
541
- function validateServiceEntrypoint(name, config, resolvedEntrypoint) {
542
- if (!resolvedEntrypoint.isDirectory && !config.builder && !config.runtime && !config.framework) {
543
- const runtime = (0, import_utils.inferServiceRuntime)({
544
- ...config,
545
- entrypoint: resolvedEntrypoint.normalized
546
- });
547
- if (!runtime) {
548
- const supported = Object.keys(import_types.ENTRYPOINT_EXTENSIONS).join(", ");
549
- return {
550
- code: "UNSUPPORTED_ENTRYPOINT",
551
- message: `Service "${name}" has unsupported entrypoint "${config.entrypoint}". Use a supported extension (${supported}) or specify "builder", "framework", or "runtime".`,
552
- serviceName: name
553
- };
554
- }
555
- }
556
- return null;
557
- }
558
- async function resolveConfiguredService(options) {
559
- const {
560
- name,
561
- config,
562
- serviceFs,
563
- root,
564
- group,
565
- resolvedEntrypoint,
566
- routePrefixSource = "configured"
567
- } = options;
568
- const type = config.type || "web";
569
- const trigger = getEffectiveServiceTrigger(config);
570
- const rawEntrypoint = config.entrypoint;
571
- const moduleAttrParsed = typeof rawEntrypoint === "string" ? parsePyModuleAttrEntrypoint(rawEntrypoint) : null;
572
- const routingResult = resolveServiceRoutingConfig(name, config);
573
- if (routingResult.error) {
574
- throw new Error(routingResult.error.message);
575
- }
576
- const configuredRoutePrefix = routingResult.routing?.routePrefix;
577
- const configuredSubdomain = routingResult.routing?.subdomain;
578
- const routePrefixWasConfigured = routingResult.routing?.routePrefixConfigured ?? false;
579
- const containerEntrypoint = isContainerRuntime(config) && typeof rawEntrypoint === "string" ? rawEntrypoint : void 0;
580
- const containerDockerfile = containerEntrypoint && isDockerfileEntrypoint(containerEntrypoint) ? import_path.posix.normalize(containerEntrypoint) : void 0;
581
- const containerImage = containerEntrypoint && !containerDockerfile ? containerEntrypoint : void 0;
582
- let resolvedEntrypointPath = resolvedEntrypoint;
583
- if (!containerEntrypoint && !resolvedEntrypointPath && typeof rawEntrypoint === "string") {
584
- const entrypointToResolve = moduleAttrParsed ? moduleAttrParsed.filePath : rawEntrypoint;
585
- const resolved = await resolveEntrypointPath({
586
- fs: serviceFs,
587
- serviceName: name,
588
- entrypoint: entrypointToResolve
589
- });
590
- resolvedEntrypointPath = resolved.entrypoint;
591
- }
592
- if (!containerEntrypoint && typeof rawEntrypoint === "string" && !resolvedEntrypointPath) {
593
- throw new Error(
594
- `Failed to resolve entrypoint "${rawEntrypoint}" for service "${name}".`
595
- );
596
- }
597
- const normalizedEntrypoint = resolvedEntrypointPath?.normalized;
598
- const entrypointIsDirectory = Boolean(resolvedEntrypointPath?.isDirectory);
599
- const inferredRuntime = (0, import_utils.inferServiceRuntime)({
600
- ...config,
601
- entrypoint: entrypointIsDirectory ? void 0 : normalizedEntrypoint
602
- });
603
- let workspace = ".";
604
- let resolvedEntrypointFile = entrypointIsDirectory || !normalizedEntrypoint ? void 0 : normalizedEntrypoint;
605
- if (entrypointIsDirectory && normalizedEntrypoint) {
606
- workspace = normalizedEntrypoint;
607
- } else {
608
- const inferredWorkspace = await inferWorkspaceFromNearestManifest({
609
- fs: serviceFs,
610
- entrypoint: resolvedEntrypointFile,
611
- runtime: inferredRuntime
612
- });
613
- if (inferredWorkspace) {
614
- workspace = inferredWorkspace;
615
- if (resolvedEntrypointFile) {
616
- resolvedEntrypointFile = toWorkspaceRelativeEntrypoint(
617
- resolvedEntrypointFile,
618
- inferredWorkspace
619
- );
620
- }
621
- }
622
- }
623
- if (root) {
624
- const normalizedRoot = import_path.posix.normalize(root);
625
- if (normalizedRoot !== ".") {
626
- workspace = workspace === "." ? normalizedRoot : import_path.posix.join(normalizedRoot, workspace);
627
- }
628
- }
629
- const topics = type === "worker" ? (0, import_build_utils.getServiceQueueTopics)({ type, topics: config.topics }) : trigger === "queue" ? config.topics : trigger === "workflow" ? ["__wkf_*"] : void 0;
630
- let builderUse;
631
- let builderSrc;
632
- const frameworkDefinition = config.framework ? frameworksBySlug.get(config.framework) : void 0;
633
- if (config.builder) {
634
- builderUse = config.builder;
635
- builderSrc = resolvedEntrypointFile || frameworkDefinition?.useRuntime?.src || "package.json";
636
- } else if (config.framework) {
637
- const isCronService = (0, import_build_utils.isScheduleTriggeredService)({ type, trigger });
638
- if ((0, import_build_utils2.isNodeBackendFramework)(config.framework) && (type === "web" || isCronService)) {
639
- builderUse = "@vercel/backends";
640
- } else {
641
- builderUse = frameworkDefinition?.useRuntime?.use || "@vercel/static-build";
642
- }
643
- builderSrc = resolvedEntrypointFile || frameworkDefinition?.useRuntime?.src || "package.json";
644
- } else {
645
- if (!inferredRuntime) {
646
- throw new Error(
647
- `Could not infer runtime for service "${name}" and no builder or framework were provided.`
648
- );
649
- }
650
- if (inferredRuntime === "node") {
651
- const isCronService = (0, import_build_utils.isScheduleTriggeredService)({ type, trigger });
652
- builderUse = type === "web" || isCronService ? "@vercel/backends" : "@vercel/node";
653
- } else {
654
- builderUse = (0, import_utils.getBuilderForRuntime)(inferredRuntime);
655
- }
656
- builderSrc = inferredRuntime === "container" && typeof containerEntrypoint === "string" ? containerEntrypoint : resolvedEntrypointFile;
657
- }
658
- const normalizedSubdomain = type === "web" && typeof configuredSubdomain === "string" ? configuredSubdomain.toLowerCase() : void 0;
659
- const defaultRoutePrefix = type === "web" && normalizedSubdomain ? `/_/${name}` : void 0;
660
- const routePrefix = type === "web" && (configuredRoutePrefix || defaultRoutePrefix) ? (configuredRoutePrefix || defaultRoutePrefix).startsWith("/") ? configuredRoutePrefix || defaultRoutePrefix : `/${configuredRoutePrefix || defaultRoutePrefix}` : void 0;
661
- const resolvedRoutePrefixSource = type === "web" && typeof routePrefix === "string" ? routePrefixWasConfigured ? routePrefixSource : "generated" : void 0;
662
- const isRoot = workspace === ".";
663
- if (!isRoot) {
664
- builderSrc = import_path.posix.join(workspace, builderSrc);
665
- }
666
- const builderConfig = { zeroConfig: true };
667
- if (builderUse === "@vercel/backends") {
668
- builderConfig.serviceName = name;
669
- }
670
- if (config.memory)
671
- builderConfig.memory = config.memory;
672
- if (config.maxDuration)
673
- builderConfig.maxDuration = config.maxDuration;
674
- if (config.includeFiles)
675
- builderConfig.includeFiles = config.includeFiles;
676
- if (config.excludeFiles)
677
- builderConfig.excludeFiles = config.excludeFiles;
678
- const isStaticBuild = import_types.STATIC_BUILDERS.has(builderUse);
679
- const runtime = isStaticBuild ? void 0 : inferredRuntime;
680
- if (routePrefix) {
681
- const stripped = routePrefix.startsWith("/") ? routePrefix.slice(1) : routePrefix;
682
- builderConfig.routePrefix = stripped || ".";
683
- }
684
- if (workspace && workspace !== ".") {
685
- builderConfig.workspace = workspace;
686
- }
687
- if (config.framework) {
688
- builderConfig.framework = config.framework;
689
- }
690
- if (containerImage) {
691
- builderConfig.handler = containerImage;
692
- }
693
- if (config.command !== void 0) {
694
- builderConfig.command = normalizeContainerCommand(config.command);
695
- }
696
- if (moduleAttrParsed) {
697
- builderConfig.handlerFunction = moduleAttrParsed.attrName;
698
- }
699
- return {
700
- schema: "experimentalServices",
701
- name,
702
- type,
703
- trigger,
704
- group,
705
- workspace,
706
- entrypoint: containerImage ?? containerDockerfile ?? resolvedEntrypointFile,
707
- routePrefix,
708
- routePrefixSource: resolvedRoutePrefixSource,
709
- subdomain: normalizedSubdomain,
710
- framework: config.framework,
711
- builder: {
712
- src: builderSrc,
713
- use: builderUse,
714
- config: Object.keys(builderConfig).length > 0 ? builderConfig : void 0
715
- },
716
- runtime,
717
- buildCommand: config.buildCommand,
718
- installCommand: config.installCommand,
719
- preDeployCommand: config.preDeployCommand,
720
- schedule: config.schedule,
721
- handlerFunction: moduleAttrParsed?.attrName,
722
- topics,
723
- env: config.env
724
- };
725
- }
726
- async function resolveAllConfiguredServices(services, fs, routePrefixSource = "configured", options = {}) {
727
- const resolved = [];
728
- const errors = [];
729
- const webServicesByRoutePrefix = /* @__PURE__ */ new Map();
730
- for (const name of Object.keys(services)) {
731
- const serviceConfig = services[name];
732
- const validationError = validateServiceConfig(name, serviceConfig, options);
733
- if (validationError) {
734
- errors.push(validationError);
735
- continue;
736
- }
737
- const root = serviceConfig.root;
738
- const serviceFsResult = await getServiceFs(fs, name, root);
739
- if (serviceFsResult.error) {
740
- errors.push(serviceFsResult.error);
741
- continue;
742
- }
743
- const serviceFs = serviceFsResult.fs;
744
- let resolvedEntrypoint;
745
- if (typeof serviceConfig.entrypoint === "string" && !isContainerRuntime(serviceConfig)) {
746
- const moduleAttr = parsePyModuleAttrEntrypoint(serviceConfig.entrypoint);
747
- const entrypointToResolve = moduleAttr?.filePath ?? serviceConfig.entrypoint;
748
- const resolvedPath = await resolveEntrypointPath({
749
- fs: serviceFs,
750
- serviceName: name,
751
- entrypoint: entrypointToResolve
752
- });
753
- if (resolvedPath.error) {
754
- errors.push(resolvedPath.error);
755
- continue;
756
- }
757
- resolvedEntrypoint = resolvedPath.entrypoint;
758
- }
759
- if (resolvedEntrypoint) {
760
- const entrypointError = validateServiceEntrypoint(
761
- name,
762
- serviceConfig,
763
- resolvedEntrypoint
764
- );
765
- if (entrypointError) {
766
- errors.push(entrypointError);
767
- continue;
768
- }
769
- }
770
- const explicitBackendEntrypointError = validateBackendFileEntrypoint(
771
- name,
772
- serviceConfig,
773
- resolvedEntrypoint,
774
- options
775
- );
776
- if (explicitBackendEntrypointError) {
777
- errors.push(explicitBackendEntrypointError);
778
- continue;
779
- }
780
- let resolvedConfig = serviceConfig;
781
- if (!serviceConfig.framework && resolvedEntrypoint) {
782
- if (resolvedEntrypoint.isDirectory) {
783
- const inferredRuntime = (0, import_utils.inferServiceRuntime)({
784
- ...serviceConfig
785
- });
786
- const workspace = resolvedEntrypoint.normalized;
787
- const { framework, error } = await detectFrameworkFromWorkspace({
788
- fs: serviceFs,
789
- workspace,
790
- runtime: inferredRuntime,
791
- serviceName: name
792
- });
793
- if (error) {
794
- errors.push(error);
795
- continue;
796
- }
797
- if (!framework) {
798
- errors.push({
799
- code: "MISSING_SERVICE_FRAMEWORK",
800
- message: `Service "${name}" uses directory entrypoint "${serviceConfig.entrypoint}" but no framework could be detected in "${workspace}". Specify "framework" explicitly or use a file entrypoint.`,
801
- serviceName: name
802
- });
803
- continue;
804
- }
805
- resolvedConfig = {
806
- ...resolvedConfig,
807
- framework
808
- };
809
- } else {
810
- const inferredRuntime = (0, import_utils.inferServiceRuntime)({
811
- ...serviceConfig,
812
- entrypoint: resolvedEntrypoint.normalized
813
- });
814
- if (inferredRuntime) {
815
- const inferredWorkspace = await inferWorkspaceFromNearestManifest({
816
- fs: serviceFs,
817
- entrypoint: resolvedEntrypoint.normalized,
818
- runtime: inferredRuntime
819
- });
820
- const workspace = inferredWorkspace ?? import_path.posix.dirname(resolvedEntrypoint.normalized);
821
- const detection = await detectFrameworkFromWorkspace({
822
- fs: serviceFs,
823
- workspace,
824
- serviceName: name,
825
- runtime: inferredRuntime
826
- });
827
- if (!detection.error && detection.framework) {
828
- resolvedConfig = {
829
- ...resolvedConfig,
830
- framework: detection.framework
831
- };
832
- }
833
- }
834
- }
835
- }
836
- const backendEntrypointError = validateBackendFileEntrypoint(
837
- name,
838
- resolvedConfig,
839
- resolvedEntrypoint,
840
- options
841
- );
842
- if (backendEntrypointError) {
843
- errors.push(backendEntrypointError);
844
- continue;
845
- }
846
- const service = await resolveConfiguredService({
847
- name,
848
- config: resolvedConfig,
849
- serviceFs,
850
- root,
851
- resolvedEntrypoint,
852
- routePrefixSource
853
- });
854
- if (service.type === "web" && typeof service.routePrefix === "string") {
855
- const normalizedRoutePrefix = (0, import_routing_utils.normalizeRoutePrefix)(service.routePrefix);
856
- const existingServiceName = webServicesByRoutePrefix.get(
857
- normalizedRoutePrefix
858
- );
859
- if (existingServiceName) {
860
- errors.push({
861
- code: "DUPLICATE_ROUTE_PREFIX",
862
- message: `Web services "${existingServiceName}" and "${name}" cannot share routePrefix "${normalizedRoutePrefix}".`,
863
- serviceName: name
864
- });
865
- continue;
866
- }
867
- webServicesByRoutePrefix.set(normalizedRoutePrefix, name);
868
- }
869
- resolved.push(service);
870
- }
871
- const servicesByName = new Map(resolved.map((s) => [s.name, s]));
872
- for (const service of resolved) {
873
- if (!service.env)
874
- continue;
875
- validateEnvRefs(service.env, service.name, servicesByName, errors);
876
- }
877
- return { services: resolved, errors };
878
- }
879
- function validateEnvRefs(env, serviceName, servicesByName, errors) {
880
- const pathPrefix = `Service "${serviceName}" env`;
881
- for (const [envVarName, envVar] of Object.entries(env)) {
882
- if (envVar.type !== "service-ref")
883
- continue;
884
- const refName = envVar.service;
885
- const target = servicesByName.get(refName);
886
- if (!target) {
887
- errors.push({
888
- code: "UNKNOWN_SERVICE_REF",
889
- message: `${pathPrefix}["${envVarName}"] references unknown service "${refName}".`,
890
- serviceName
891
- });
892
- continue;
893
- }
894
- if (target.type !== "web") {
895
- errors.push({
896
- code: "INVALID_SERVICE_REF_TYPE",
897
- message: `${pathPrefix}["${envVarName}"] references service "${refName}" which is a ${target.type} service and has no URL. Only web services can be referenced.`,
898
- serviceName
899
- });
900
- }
901
- }
902
- }
903
- // Annotate the CommonJS export names for ESM import in node:
904
- 0 && (module.exports = {
905
- detectFrameworkFromWorkspace,
906
- getServiceFs,
907
- inferWorkspaceFromNearestManifest,
908
- parsePyModuleAttrEntrypoint,
909
- resolveAllConfiguredServices,
910
- resolveConfiguredService,
911
- resolveEntrypointPath,
912
- validateServiceConfig,
913
- validateServiceEntrypoint
914
- });