@vercel/fs-detectors 7.1.7 → 7.2.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.
@@ -0,0 +1,383 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var resolve_v2_exports = {};
20
+ __export(resolve_v2_exports, {
21
+ resolveAllConfiguredServicesV2: () => resolveAllConfiguredServicesV2,
22
+ resolveConfiguredServiceV2: () => resolveConfiguredServiceV2,
23
+ validateServiceConfigV2: () => validateServiceConfigV2
24
+ });
25
+ module.exports = __toCommonJS(resolve_v2_exports);
26
+ var import_path = require("path");
27
+ var import_build_utils = require("@vercel/build-utils");
28
+ var import_frameworks = require("@vercel/frameworks");
29
+ var import_constants = require("./runtimes/constants");
30
+ var import_framework = require("./runtimes/framework");
31
+ var import_runtime = require("./runtimes/runtime");
32
+ var import_entrypoint = require("./runtimes/entrypoint");
33
+ var import_resolve = require("./resolve");
34
+ const frameworksBySlug = new Map(import_frameworks.frameworkList.map((f) => [f.slug, f]));
35
+ const MAX_SERVICE_NAME_LENGTH = 64;
36
+ const SERVICE_NAME_REGEX = /^[a-z]([a-z_-]*[a-z])?$/;
37
+ function isValidServiceName(name) {
38
+ return name.length <= MAX_SERVICE_NAME_LENGTH && SERVICE_NAME_REGEX.test(name);
39
+ }
40
+ function getInvalidServiceNameMessage(name) {
41
+ return `Service name "${name}" is invalid. Names must be 1-${MAX_SERVICE_NAME_LENGTH} characters, start and end with a lowercase letter, and contain only lowercase letters, hyphens, and underscores.`;
42
+ }
43
+ async function detectContainerEntrypoint(serviceFs) {
44
+ for (const candidate of import_constants.CONTAINER_ENTRYPOINT_CANDIDATES) {
45
+ if (await serviceFs.hasPath(candidate)) {
46
+ return candidate;
47
+ }
48
+ }
49
+ return void 0;
50
+ }
51
+ function normalizeContainerCommand(command) {
52
+ if (command === void 0) {
53
+ return void 0;
54
+ }
55
+ return Array.isArray(command) ? command : [command];
56
+ }
57
+ async function resolveContainerServiceV2(name, config, normalizedRoot, serviceFs) {
58
+ const isRoot = normalizedRoot === ".";
59
+ const entrypoint = config.entrypoint;
60
+ let dockerfile;
61
+ if (typeof entrypoint === "string") {
62
+ if (!(0, import_entrypoint.isDockerfileEntrypoint)(entrypoint)) {
63
+ return {
64
+ error: {
65
+ code: "INVALID_SERVICE_CONFIG",
66
+ message: `Container service "${name}" has invalid "entrypoint" "${entrypoint}". It must name a Dockerfile or Containerfile.`,
67
+ serviceName: name
68
+ }
69
+ };
70
+ }
71
+ dockerfile = import_path.posix.normalize(entrypoint);
72
+ } else {
73
+ dockerfile = await detectContainerEntrypoint(serviceFs);
74
+ if (!dockerfile) {
75
+ return {
76
+ error: {
77
+ code: "MISSING_SERVICE_CONFIG",
78
+ message: `Container service "${name}" has no "entrypoint" and no ${import_constants.CONTAINER_ENTRYPOINT_CANDIDATES.join(
79
+ ", "
80
+ )} was found in "${normalizedRoot}".`,
81
+ serviceName: name
82
+ }
83
+ };
84
+ }
85
+ }
86
+ const builderSrc = isRoot ? dockerfile : import_path.posix.join(normalizedRoot, dockerfile);
87
+ const builderConfig = { zeroConfig: true };
88
+ if (!isRoot) {
89
+ builderConfig.workspace = normalizedRoot;
90
+ }
91
+ const command = normalizeContainerCommand(config.command);
92
+ if (command) {
93
+ builderConfig.command = command;
94
+ }
95
+ return {
96
+ service: {
97
+ schema: "experimentalServicesV2",
98
+ name,
99
+ root: normalizedRoot,
100
+ runtime: "container",
101
+ entrypoint: dockerfile,
102
+ command,
103
+ builder: {
104
+ src: builderSrc,
105
+ use: "@vercel/container",
106
+ config: builderConfig
107
+ },
108
+ bindings: config.bindings,
109
+ functions: config.functions,
110
+ headers: config.headers,
111
+ redirects: config.redirects,
112
+ rewrites: config.rewrites,
113
+ routes: config.routes,
114
+ cleanUrls: config.cleanUrls,
115
+ trailingSlash: config.trailingSlash
116
+ }
117
+ };
118
+ }
119
+ function validateServiceConfigV2(name, config) {
120
+ if (!isValidServiceName(name)) {
121
+ return {
122
+ code: "INVALID_SERVICE_NAME",
123
+ message: getInvalidServiceNameMessage(name),
124
+ serviceName: name
125
+ };
126
+ }
127
+ if (!config || typeof config !== "object") {
128
+ return {
129
+ code: "INVALID_SERVICE_CONFIG",
130
+ message: `Service "${name}" has an invalid configuration. Expected an object.`,
131
+ serviceName: name
132
+ };
133
+ }
134
+ if (typeof config.root !== "string" || config.root.length === 0) {
135
+ return {
136
+ code: "MISSING_ROOT",
137
+ message: `Service "${name}" must specify a "root".`,
138
+ serviceName: name
139
+ };
140
+ }
141
+ const normalizedRoot = import_path.posix.normalize(config.root);
142
+ if (normalizedRoot.startsWith("/")) {
143
+ return {
144
+ code: "INVALID_ROOT",
145
+ message: `Service "${name}" has invalid "root" "${config.root}". Must be a relative path.`,
146
+ serviceName: name
147
+ };
148
+ }
149
+ if (normalizedRoot === ".." || normalizedRoot.startsWith("../")) {
150
+ return {
151
+ code: "INVALID_ROOT",
152
+ message: `Service "${name}" has invalid "root" "${config.root}". Must not escape the project root.`,
153
+ serviceName: name
154
+ };
155
+ }
156
+ if (config.runtime && !(config.runtime in import_constants.RUNTIME_BUILDERS)) {
157
+ return {
158
+ code: "INVALID_RUNTIME",
159
+ message: `Service "${name}" has invalid runtime "${config.runtime}".`,
160
+ serviceName: name
161
+ };
162
+ }
163
+ if (config.framework && !frameworksBySlug.has(config.framework)) {
164
+ return {
165
+ code: "INVALID_FRAMEWORK",
166
+ message: `Service "${name}" has invalid framework "${config.framework}".`,
167
+ serviceName: name
168
+ };
169
+ }
170
+ if (config.runtime && config.framework) {
171
+ const frameworkRuntime = (0, import_framework.inferRuntimeFromFramework)(config.framework);
172
+ if (frameworkRuntime && frameworkRuntime !== config.runtime) {
173
+ return {
174
+ code: "RUNTIME_FRAMEWORK_MISMATCH",
175
+ message: `Service "${name}" has conflicting runtime/framework: runtime "${config.runtime}" is incompatible with framework "${config.framework}" (runtime "${frameworkRuntime}").`,
176
+ serviceName: name
177
+ };
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ async function resolveConfiguredServiceV2(name, config, fs) {
183
+ const normalizedRoot = (0, import_entrypoint.stripTrailingSlash)(import_path.posix.normalize(config.root));
184
+ const serviceFsResult = normalizedRoot === "." ? { fs } : await (0, import_resolve.getServiceFs)(fs, name, normalizedRoot);
185
+ if (serviceFsResult.error) {
186
+ return { error: serviceFsResult.error };
187
+ }
188
+ const serviceFs = serviceFsResult.fs;
189
+ const isContainer = config.runtime === "container" || typeof config.entrypoint === "string" && (0, import_entrypoint.isDockerfileEntrypoint)(config.entrypoint);
190
+ if (isContainer) {
191
+ return resolveContainerServiceV2(name, config, normalizedRoot, serviceFs);
192
+ }
193
+ const rawEntrypoint = config.entrypoint;
194
+ const moduleAttr = typeof rawEntrypoint === "string" ? (0, import_entrypoint.parsePyModuleAttrEntrypoint)(rawEntrypoint) : null;
195
+ let normalizedEntrypoint;
196
+ let entrypointIsDirectory = false;
197
+ if (typeof rawEntrypoint === "string") {
198
+ const entrypointToResolve = moduleAttr ? moduleAttr.filePath : rawEntrypoint;
199
+ const resolved = await (0, import_resolve.resolveEntrypointPath)({
200
+ fs: serviceFs,
201
+ serviceName: name,
202
+ entrypoint: entrypointToResolve
203
+ });
204
+ if (resolved.error) {
205
+ return { error: resolved.error };
206
+ }
207
+ normalizedEntrypoint = resolved.entrypoint?.normalized;
208
+ entrypointIsDirectory = Boolean(resolved.entrypoint?.isDirectory);
209
+ }
210
+ const entrypointFile = entrypointIsDirectory || !normalizedEntrypoint ? void 0 : normalizedEntrypoint;
211
+ let inferredRuntime = (0, import_runtime.inferRuntime)({
212
+ runtime: config.runtime,
213
+ framework: config.framework,
214
+ entrypoint: entrypointFile
215
+ });
216
+ let framework = config.framework;
217
+ let detectedFramework = false;
218
+ if (!framework) {
219
+ const workspace = entrypointIsDirectory && normalizedEntrypoint ? normalizedEntrypoint : ".";
220
+ const detection = await (0, import_resolve.detectFrameworkFromWorkspace)({
221
+ fs: serviceFs,
222
+ workspace,
223
+ serviceName: name,
224
+ runtime: inferredRuntime
225
+ });
226
+ if (detection.error) {
227
+ return { error: detection.error };
228
+ }
229
+ framework = detection.framework;
230
+ detectedFramework = Boolean(framework);
231
+ inferredRuntime = (0, import_runtime.inferRuntime)({
232
+ runtime: config.runtime,
233
+ framework,
234
+ entrypoint: entrypointFile
235
+ });
236
+ }
237
+ if (entrypointIsDirectory && !framework) {
238
+ return {
239
+ error: {
240
+ code: "MISSING_SERVICE_FRAMEWORK",
241
+ message: `Service "${name}" uses directory entrypoint "${config.entrypoint}" but no framework could be detected. Specify "framework" explicitly or use a file entrypoint.`,
242
+ serviceName: name
243
+ }
244
+ };
245
+ }
246
+ const frameworkRuntime = (0, import_framework.inferRuntimeFromFramework)(framework);
247
+ if (detectedFramework && frameworkRuntime && !entrypointFile) {
248
+ return {
249
+ error: {
250
+ code: "MISSING_SERVICE_CONFIG",
251
+ message: `Service "${name}" detected framework "${framework}" in "${normalizedRoot}" and must specify an "entrypoint" for runtime "${frameworkRuntime}".`,
252
+ serviceName: name
253
+ }
254
+ };
255
+ }
256
+ const frameworkDefinition = framework ? frameworksBySlug.get(framework) : void 0;
257
+ let builderUse;
258
+ let builderSrc;
259
+ if (framework) {
260
+ builderUse = (0, import_build_utils.isNodeBackendFramework)(framework) ? "@vercel/backends" : frameworkDefinition?.useRuntime?.use || "@vercel/static-build";
261
+ builderSrc = entrypointFile || frameworkDefinition?.useRuntime?.src || "package.json";
262
+ } else {
263
+ if (!inferredRuntime) {
264
+ if (config.buildCommand) {
265
+ builderUse = "@vercel/static-build";
266
+ builderSrc = "package.json";
267
+ } else {
268
+ builderUse = "@vercel/static";
269
+ builderSrc = config.outputDirectory ? import_path.posix.join(config.outputDirectory, "**") : "**";
270
+ }
271
+ } else {
272
+ if (!entrypointFile) {
273
+ return {
274
+ error: {
275
+ code: "MISSING_SERVICE_CONFIG",
276
+ message: `Service "${name}" must specify an "entrypoint" for runtime "${inferredRuntime}".`,
277
+ serviceName: name
278
+ }
279
+ };
280
+ }
281
+ builderUse = inferredRuntime === "node" ? "@vercel/backends" : (0, import_runtime.getBuilderForRuntime)(inferredRuntime);
282
+ builderSrc = entrypointFile;
283
+ }
284
+ }
285
+ const isRoot = normalizedRoot === ".";
286
+ const projectRelativeSrc = isRoot ? builderSrc : import_path.posix.join(normalizedRoot, builderSrc);
287
+ const builderConfig = { zeroConfig: true };
288
+ if (builderUse === "@vercel/backends") {
289
+ builderConfig.serviceName = name;
290
+ }
291
+ if (framework) {
292
+ builderConfig.framework = framework;
293
+ }
294
+ if (config.outputDirectory) {
295
+ builderConfig.outputDirectory = config.outputDirectory;
296
+ }
297
+ if (!isRoot) {
298
+ builderConfig.workspace = normalizedRoot;
299
+ }
300
+ if (moduleAttr) {
301
+ builderConfig.handlerFunction = moduleAttr.attrName;
302
+ }
303
+ const runtime = import_constants.STATIC_BUILDERS.has(builderUse) ? void 0 : inferredRuntime;
304
+ return {
305
+ service: {
306
+ schema: "experimentalServicesV2",
307
+ name,
308
+ root: normalizedRoot,
309
+ framework,
310
+ runtime,
311
+ entrypoint: entrypointFile,
312
+ builder: {
313
+ src: projectRelativeSrc,
314
+ use: builderUse,
315
+ config: builderConfig
316
+ },
317
+ installCommand: config.installCommand,
318
+ buildCommand: config.buildCommand,
319
+ devCommand: config.devCommand,
320
+ ignoreCommand: config.ignoreCommand,
321
+ outputDirectory: config.outputDirectory,
322
+ bindings: config.bindings,
323
+ functions: config.functions,
324
+ headers: config.headers,
325
+ redirects: config.redirects,
326
+ rewrites: config.rewrites,
327
+ routes: config.routes,
328
+ cleanUrls: config.cleanUrls,
329
+ trailingSlash: config.trailingSlash
330
+ }
331
+ };
332
+ }
333
+ async function resolveAllConfiguredServicesV2(services, fs) {
334
+ const resolved = [];
335
+ const errors = [];
336
+ for (const name of Object.keys(services)) {
337
+ const config = services[name];
338
+ const validationError = validateServiceConfigV2(name, config);
339
+ if (validationError) {
340
+ errors.push(validationError);
341
+ continue;
342
+ }
343
+ const { service, error } = await resolveConfiguredServiceV2(
344
+ name,
345
+ config,
346
+ fs
347
+ );
348
+ if (error) {
349
+ errors.push(error);
350
+ continue;
351
+ }
352
+ if (service) {
353
+ resolved.push(service);
354
+ }
355
+ }
356
+ const serviceNames = new Set(Object.keys(services));
357
+ for (const service of resolved) {
358
+ for (const binding of service.bindings ?? []) {
359
+ if (!isValidServiceName(binding.service)) {
360
+ errors.push({
361
+ code: "INVALID_SERVICE_BINDING_NAME",
362
+ message: `Service "${service.name}" declares an invalid binding service name "${binding.service}". ${getInvalidServiceNameMessage(binding.service)}`,
363
+ serviceName: service.name
364
+ });
365
+ continue;
366
+ }
367
+ if (!serviceNames.has(binding.service)) {
368
+ errors.push({
369
+ code: "UNKNOWN_SERVICE_BINDING",
370
+ message: `Service "${service.name}" declares a binding to unknown service "${binding.service}".`,
371
+ serviceName: service.name
372
+ });
373
+ }
374
+ }
375
+ }
376
+ return { services: resolved, errors };
377
+ }
378
+ // Annotate the CommonJS export names for ESM import in node:
379
+ 0 && (module.exports = {
380
+ resolveAllConfiguredServicesV2,
381
+ resolveConfiguredServiceV2,
382
+ validateServiceConfigV2
383
+ });
@@ -0,0 +1,65 @@
1
+ import type { ExperimentalService, ConfiguredServices, ExperimentalServiceConfig, ServiceDetectionError, ServiceRuntime } from './types';
2
+ import type { DetectorFilesystem } from '../detectors/filesystem';
3
+ type ConfiguredServiceConfig = ExperimentalServiceConfig;
4
+ interface ResolvedEntrypointPath {
5
+ normalized: string;
6
+ isDirectory: boolean;
7
+ }
8
+ export declare function getServiceFs(fs: DetectorFilesystem, serviceName: string, root?: string): Promise<{
9
+ fs: DetectorFilesystem;
10
+ error?: ServiceDetectionError;
11
+ }>;
12
+ export declare function resolveEntrypointPath({ fs, serviceName, entrypoint, }: {
13
+ fs: DetectorFilesystem;
14
+ serviceName: string;
15
+ entrypoint: string;
16
+ }): Promise<{
17
+ entrypoint?: ResolvedEntrypointPath;
18
+ error?: ServiceDetectionError;
19
+ }>;
20
+ type RoutePrefixSource = 'configured' | 'generated';
21
+ interface ResolveConfiguredServiceOptions {
22
+ name: string;
23
+ config: ConfiguredServiceConfig;
24
+ /** Filesystem scoped to the service root (via chdir) when root is set, otherwise the project-level fs. */
25
+ serviceFs: DetectorFilesystem;
26
+ root?: string;
27
+ group?: string;
28
+ resolvedEntrypoint?: ResolvedEntrypointPath;
29
+ routePrefixSource?: RoutePrefixSource;
30
+ }
31
+ interface ResolveAllConfiguredServicesOptions {
32
+ requireFileEntrypointForBackendRuntimes?: boolean;
33
+ }
34
+ export declare function inferWorkspaceFromNearestManifest({ fs, entrypoint, runtime, }: {
35
+ fs: DetectorFilesystem;
36
+ entrypoint?: string;
37
+ runtime?: ServiceRuntime;
38
+ }): Promise<string | undefined>;
39
+ export declare function detectFrameworkFromWorkspace({ fs, workspace, serviceName, runtime, }: {
40
+ fs: DetectorFilesystem;
41
+ workspace: string;
42
+ serviceName: string;
43
+ runtime?: ServiceRuntime;
44
+ }): Promise<{
45
+ framework?: string;
46
+ error?: ServiceDetectionError;
47
+ }>;
48
+ /**
49
+ * Validate a service configuration from vercel.json services.
50
+ */
51
+ export declare function validateServiceConfig(name: string, config: ConfiguredServiceConfig, options?: ResolveAllConfiguredServicesOptions): ServiceDetectionError | null;
52
+ export declare function validateServiceEntrypoint(name: string, config: ConfiguredServiceConfig, resolvedEntrypoint: ResolvedEntrypointPath): ServiceDetectionError | null;
53
+ /**
54
+ * Resolve a single service from user configuration.
55
+ */
56
+ export declare function resolveConfiguredService(options: ResolveConfiguredServiceOptions): Promise<ExperimentalService>;
57
+ /**
58
+ * Resolve all services from vercel.json services.
59
+ * Validates each service configuration.
60
+ */
61
+ export declare function resolveAllConfiguredServices(services: ConfiguredServices, fs: DetectorFilesystem, routePrefixSource?: RoutePrefixSource, options?: ResolveAllConfiguredServicesOptions): Promise<{
62
+ services: ExperimentalService[];
63
+ errors: ServiceDetectionError[];
64
+ }>;
65
+ export {};