@vercel/fs-detectors 6.9.2 → 6.10.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,5 +1,5 @@
1
1
  import type { Route } from '@vercel/routing-utils';
2
- import type { PackageJson, Builder, BuilderFunctions, ExperimentalServices, ExperimentalServicesV2, ProjectSettings, Service } from '@vercel/build-utils';
2
+ import type { PackageJson, Builder, BuilderFunctions, ExperimentalServices, ExperimentalServicesV2, Services, ProjectSettings, Service } from '@vercel/build-utils';
3
3
  /**
4
4
  * Pattern for finding all supported middleware files.
5
5
  */
@@ -23,6 +23,7 @@ export interface Options {
23
23
  tag?: string;
24
24
  functions?: BuilderFunctions;
25
25
  experimentalServices?: ExperimentalServices;
26
+ services?: Services;
26
27
  experimentalServicesV2?: ExperimentalServicesV2;
27
28
  ignoreBuildScript?: boolean;
28
29
  projectSettings?: ProjectSettings;
@@ -94,12 +94,30 @@ function detectOutputDirectory(builders) {
94
94
  async function detectBuilders(files, pkg, options = {}) {
95
95
  const {
96
96
  experimentalServices: experimentalServicesV1,
97
+ services,
97
98
  experimentalServicesV2,
98
99
  projectSettings = {}
99
100
  } = options;
101
+ if (services != null && experimentalServicesV2 != null) {
102
+ return {
103
+ builders: null,
104
+ errors: [
105
+ {
106
+ code: "SERVICES_AND_EXPERIMENTAL_SERVICES_V2",
107
+ message: "The `services` option cannot be used in conjunction with its deprecated alias `experimentalServicesV2`. Please use only `services`."
108
+ }
109
+ ],
110
+ warnings: [],
111
+ defaultRoutes: null,
112
+ redirectRoutes: null,
113
+ rewriteRoutes: null,
114
+ errorRoutes: null
115
+ };
116
+ }
100
117
  const { framework } = projectSettings;
101
- const configuredServices = experimentalServicesV2 ?? experimentalServicesV1;
102
- const configuredServicesType = experimentalServicesV2 ? "experimentalServicesV2" : "experimentalServices";
118
+ const servicesConfig = services ?? experimentalServicesV2;
119
+ const configuredServices = servicesConfig ?? experimentalServicesV1;
120
+ const configuredServicesType = servicesConfig ? services ? "services" : "experimentalServicesV2" : "experimentalServices";
103
121
  const hasServicesConfig = configuredServices != null && typeof configuredServices === "object";
104
122
  if (hasServicesConfig || framework === "services") {
105
123
  const result = await (0, import_get_services_builders.getServicesBuilders)({
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, ServicesRoutes, ServiceDetectionError, } from './services/types';
9
+ export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, 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';
@@ -106,8 +106,23 @@ async function detectServices(options) {
106
106
  warnings: []
107
107
  });
108
108
  }
109
+ if (vercelConfig?.services != null && vercelConfig.experimentalServicesV2 != null) {
110
+ return withResolvedResult({
111
+ services: [],
112
+ source: "configured",
113
+ useImplicitEnvInjection: false,
114
+ routes: emptyRoutes(),
115
+ errors: [
116
+ {
117
+ code: "SERVICES_AND_EXPERIMENTAL_SERVICES_V2",
118
+ message: "The `services` property cannot be used in conjunction with its deprecated alias `experimentalServicesV2`. Please use only `services`."
119
+ }
120
+ ],
121
+ warnings: []
122
+ });
123
+ }
109
124
  const hasProvidedConfiguredServices = providedConfiguredServices && Object.keys(providedConfiguredServices).length > 0;
110
- const experimentalServicesV2 = hasProvidedConfiguredServices && providedConfiguredServicesType === "experimentalServicesV2" ? providedConfiguredServices : hasProvidedConfiguredServices ? void 0 : vercelConfig?.experimentalServicesV2;
125
+ const experimentalServicesV2 = hasProvidedConfiguredServices && (providedConfiguredServicesType === "services" || providedConfiguredServicesType === "experimentalServicesV2") ? providedConfiguredServices : hasProvidedConfiguredServices ? void 0 : vercelConfig?.services ?? vercelConfig?.experimentalServicesV2;
111
126
  if (experimentalServicesV2 && Object.keys(experimentalServicesV2).length > 0) {
112
127
  const result2 = await (0, import_resolve_v2.resolveAllConfiguredServicesV2)(
113
128
  experimentalServicesV2,
@@ -286,11 +301,12 @@ function generateServicesRoutes(allServices) {
286
301
  }
287
302
  } else if (service.runtime) {
288
303
  const functionPath = (0, import_utils.getInternalServiceFunctionPath)(service.name);
304
+ const check = service.runtime === "container" ? void 0 : true;
289
305
  if (routePrefix === "/") {
290
306
  defaults.push({
291
307
  src: (0, import_routing_utils.scopeRouteSourceToOwnership)("^/(.*)$", ownershipGuard),
292
308
  dest: functionPath,
293
- check: true
309
+ ...check ? { check } : {}
294
310
  });
295
311
  } else {
296
312
  rewrites.push({
@@ -299,7 +315,7 @@ function generateServicesRoutes(allServices) {
299
315
  ownershipGuard
300
316
  ),
301
317
  dest: functionPath,
302
- check: true
318
+ ...check ? { check } : {}
303
319
  });
304
320
  }
305
321
  }
@@ -31,6 +31,67 @@ 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
+ function isDockerfileEntrypoint(entrypoint) {
35
+ const base = import_path.posix.basename(entrypoint).toLowerCase();
36
+ return base === "dockerfile" || base === "containerfile" || base.endsWith(".dockerfile");
37
+ }
38
+ function normalizeContainerCommand(command) {
39
+ if (command === void 0) {
40
+ return void 0;
41
+ }
42
+ return Array.isArray(command) ? command : [command];
43
+ }
44
+ function resolveContainerServiceV2(name, config, normalizedRoot) {
45
+ const isRoot = normalizedRoot === ".";
46
+ const entrypoint = config.entrypoint;
47
+ const dockerfile = typeof entrypoint === "string" && isDockerfileEntrypoint(entrypoint) ? import_path.posix.normalize(entrypoint) : void 0;
48
+ const image = typeof entrypoint === "string" && !dockerfile ? entrypoint : void 0;
49
+ if (!dockerfile && !image) {
50
+ return {
51
+ error: {
52
+ code: "MISSING_SERVICE_CONFIG",
53
+ message: `Container service "${name}" must specify an "entrypoint": a Dockerfile path to build, or a prebuilt OCI image reference.`,
54
+ serviceName: name
55
+ }
56
+ };
57
+ }
58
+ const localSrc = dockerfile ?? image ?? "Dockerfile";
59
+ const builderSrc = isRoot ? localSrc : import_path.posix.join(normalizedRoot, localSrc);
60
+ const builderConfig = { zeroConfig: true };
61
+ if (!isRoot) {
62
+ builderConfig.workspace = normalizedRoot;
63
+ }
64
+ if (image) {
65
+ builderConfig.handler = image;
66
+ }
67
+ const command = normalizeContainerCommand(config.command);
68
+ if (command) {
69
+ builderConfig.command = command;
70
+ }
71
+ return {
72
+ service: {
73
+ schema: "experimentalServicesV2",
74
+ name,
75
+ root: normalizedRoot,
76
+ runtime: "container",
77
+ entrypoint: image ?? dockerfile,
78
+ command,
79
+ builder: {
80
+ src: builderSrc,
81
+ use: "@vercel/container",
82
+ config: builderConfig
83
+ },
84
+ bindings: config.bindings,
85
+ functions: config.functions,
86
+ headers: config.headers,
87
+ redirects: config.redirects,
88
+ rewrites: config.rewrites,
89
+ routes: config.routes,
90
+ cleanUrls: config.cleanUrls,
91
+ trailingSlash: config.trailingSlash
92
+ }
93
+ };
94
+ }
34
95
  function validateServiceConfigV2(name, config) {
35
96
  if (!SERVICE_NAME_REGEX.test(name)) {
36
97
  return {
@@ -92,7 +153,8 @@ function validateServiceConfigV2(name, config) {
92
153
  };
93
154
  }
94
155
  }
95
- if (!config.framework && !config.entrypoint) {
156
+ const isContainer = config.runtime === "container";
157
+ if (!config.framework && !config.entrypoint && !isContainer) {
96
158
  return {
97
159
  code: "MISSING_SERVICE_CONFIG",
98
160
  message: `Service "${name}" must specify "framework" or "entrypoint".`,
@@ -103,6 +165,10 @@ function validateServiceConfigV2(name, config) {
103
165
  }
104
166
  async function resolveConfiguredServiceV2(name, config, fs) {
105
167
  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
+ }
106
172
  const serviceFsResult = normalizedRoot === "." ? { fs } : await (0, import_resolve.getServiceFs)(fs, name, normalizedRoot);
107
173
  if (serviceFsResult.error) {
108
174
  return { error: serviceFsResult.error };
@@ -56,6 +56,12 @@ const ENTRYPOINT_REQUIRED_RUNTIMES = /* @__PURE__ */ new Set([
56
56
  "python",
57
57
  "go"
58
58
  ]);
59
+ function isContainerRuntime(config) {
60
+ return config.runtime === "container";
61
+ }
62
+ function normalizeContainerCommand(command) {
63
+ return Array.isArray(command) ? command : [command];
64
+ }
59
65
  async function getServiceFs(fs, serviceName, root) {
60
66
  if (!root) {
61
67
  return { fs };
@@ -141,6 +147,10 @@ async function resolveEntrypointPath({
141
147
  }
142
148
  };
143
149
  }
150
+ function isDockerfileEntrypoint(entrypoint) {
151
+ const base = import_path.posix.basename(entrypoint).toLowerCase();
152
+ return base === "dockerfile" || base === "containerfile" || base.endsWith(".dockerfile");
153
+ }
144
154
  function toWorkspaceRelativeEntrypoint(entrypoint, workspace) {
145
155
  const normalizedEntrypoint = import_path.posix.normalize(entrypoint);
146
156
  if (workspace === ".") {
@@ -519,6 +529,13 @@ function validateServiceConfig(name, config, options = {}) {
519
529
  serviceName: name
520
530
  };
521
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
+ }
522
539
  return null;
523
540
  }
524
541
  function validateServiceEntrypoint(name, config, resolvedEntrypoint) {
@@ -559,8 +576,11 @@ async function resolveConfiguredService(options) {
559
576
  const configuredRoutePrefix = routingResult.routing?.routePrefix;
560
577
  const configuredSubdomain = routingResult.routing?.subdomain;
561
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;
562
582
  let resolvedEntrypointPath = resolvedEntrypoint;
563
- if (!resolvedEntrypointPath && typeof rawEntrypoint === "string") {
583
+ if (!containerEntrypoint && !resolvedEntrypointPath && typeof rawEntrypoint === "string") {
564
584
  const entrypointToResolve = moduleAttrParsed ? moduleAttrParsed.filePath : rawEntrypoint;
565
585
  const resolved = await resolveEntrypointPath({
566
586
  fs: serviceFs,
@@ -569,7 +589,7 @@ async function resolveConfiguredService(options) {
569
589
  });
570
590
  resolvedEntrypointPath = resolved.entrypoint;
571
591
  }
572
- if (typeof rawEntrypoint === "string" && !resolvedEntrypointPath) {
592
+ if (!containerEntrypoint && typeof rawEntrypoint === "string" && !resolvedEntrypointPath) {
573
593
  throw new Error(
574
594
  `Failed to resolve entrypoint "${rawEntrypoint}" for service "${name}".`
575
595
  );
@@ -633,7 +653,7 @@ async function resolveConfiguredService(options) {
633
653
  } else {
634
654
  builderUse = (0, import_utils.getBuilderForRuntime)(inferredRuntime);
635
655
  }
636
- builderSrc = resolvedEntrypointFile;
656
+ builderSrc = inferredRuntime === "container" && typeof containerEntrypoint === "string" ? containerEntrypoint : resolvedEntrypointFile;
637
657
  }
638
658
  const normalizedSubdomain = type === "web" && typeof configuredSubdomain === "string" ? configuredSubdomain.toLowerCase() : void 0;
639
659
  const defaultRoutePrefix = type === "web" && normalizedSubdomain ? `/_/${name}` : void 0;
@@ -667,6 +687,12 @@ async function resolveConfiguredService(options) {
667
687
  if (config.framework) {
668
688
  builderConfig.framework = config.framework;
669
689
  }
690
+ if (containerImage) {
691
+ builderConfig.handler = containerImage;
692
+ }
693
+ if (config.command !== void 0) {
694
+ builderConfig.command = normalizeContainerCommand(config.command);
695
+ }
670
696
  if (moduleAttrParsed) {
671
697
  builderConfig.handlerFunction = moduleAttrParsed.attrName;
672
698
  }
@@ -677,7 +703,7 @@ async function resolveConfiguredService(options) {
677
703
  trigger,
678
704
  group,
679
705
  workspace,
680
- entrypoint: resolvedEntrypointFile,
706
+ entrypoint: containerImage ?? containerDockerfile ?? resolvedEntrypointFile,
681
707
  routePrefix,
682
708
  routePrefixSource: resolvedRoutePrefixSource,
683
709
  subdomain: normalizedSubdomain,
@@ -716,7 +742,7 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
716
742
  }
717
743
  const serviceFs = serviceFsResult.fs;
718
744
  let resolvedEntrypoint;
719
- if (typeof serviceConfig.entrypoint === "string") {
745
+ if (typeof serviceConfig.entrypoint === "string" && !isContainerRuntime(serviceConfig)) {
720
746
  const moduleAttr = parsePyModuleAttrEntrypoint(serviceConfig.entrypoint);
721
747
  const entrypointToResolve = moduleAttr?.filePath ?? serviceConfig.entrypoint;
722
748
  const resolvedPath = await resolveEntrypointPath({
@@ -1,7 +1,7 @@
1
1
  import type { Route } from '@vercel/routing-utils';
2
- import type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceV2Config, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServicesV2, ExperimentalServiceV2Binding, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder } from '@vercel/build-utils';
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
- export type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder, };
4
+ export type { DetectEntrypointFn, EnvVar, EnvVars, ExperimentalServiceConfig, ExperimentalServiceGroups, ExperimentalServices, ExperimentalServiceV2Config, ExperimentalServicesV2, ExperimentalServiceV2Binding, ServiceBinding, ServiceConfig, Services, ExperimentalService, ExperimentalServiceV2, ServiceRuntime, ServiceType, ServiceRefEnvVar, Service, Builder, };
5
5
  /**
6
6
  * @deprecated Use `Service` instead
7
7
  */
@@ -42,8 +42,8 @@ export interface ServicesRoutes {
42
42
  */
43
43
  workers: Route[];
44
44
  }
45
- export type ConfiguredServicesType = 'experimentalServices' | 'experimentalServicesV2';
46
- export type ConfiguredServices = ExperimentalServices | ExperimentalServicesV2;
45
+ export type ConfiguredServicesType = 'experimentalServices' | 'services' | 'experimentalServicesV2';
46
+ export type ConfiguredServices = ExperimentalServices | Services;
47
47
  export type InferredServicesConfig = ExperimentalServices;
48
48
  export interface ResolvedServicesResult {
49
49
  services: Service[];
@@ -62,7 +62,7 @@ export interface InferredServicesResult {
62
62
  export interface DetectServicesResult extends ResolvedServicesResult {
63
63
  /**
64
64
  * Source of service definitions:
65
- * - `configured`: loaded from explicit project configuration (`vercel.json#experimentalServices` or `vercel.json#experimentalServicesV2`)
65
+ * - `configured`: loaded from explicit project configuration (`vercel.json#experimentalServices`, `vercel.json#services`, or the deprecated `vercel.json#experimentalServicesV2` alias)
66
66
  * - `auto-detected`: inferred from project structure
67
67
  */
68
68
  resolved: ResolvedServicesResult;
@@ -30,7 +30,8 @@ const RUNTIME_BUILDERS = {
30
30
  python: "@vercel/python",
31
31
  go: "@vercel/go",
32
32
  rust: "@vercel/rust",
33
- ruby: "@vercel/ruby"
33
+ ruby: "@vercel/ruby",
34
+ container: "@vercel/container"
34
35
  };
35
36
  const RUNTIME_MANIFESTS = {
36
37
  node: ["package.json"],
@@ -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, ServiceDetectionError, ServiceDetectionWarning, ResolvedService } from './types';
4
+ import type { ServiceRuntime, ExperimentalServices, ExperimentalServicesV2, 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
  /**
@@ -66,6 +66,7 @@ export declare function inferServiceRuntime(config: {
66
66
  export interface ReadVercelConfigResult {
67
67
  config: {
68
68
  experimentalServices?: ExperimentalServices;
69
+ services?: Services;
69
70
  experimentalServicesV2?: ExperimentalServicesV2;
70
71
  } | null;
71
72
  error: ServiceDetectionError | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "6.9.2",
3
+ "version": "6.10.0",
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.31.0",
23
+ "@vercel/build-utils": "13.32.0",
24
24
  "@vercel/error-utils": "2.2.0",
25
- "@vercel/routing-utils": "6.3.1",
26
- "@vercel/frameworks": "3.29.1"
25
+ "@vercel/frameworks": "3.29.1",
26
+ "@vercel/routing-utils": "6.3.1"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/glob": "7.2.0",