@vercel/fs-detectors 5.8.4 → 5.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { detectBuilders, detectOutputDirectory, detectApiDirectory, detectApiExt
2
2
  export { detectServices, generateServicesRoutes, } from './services/detect-services';
3
3
  export { autoDetectServices } from './services/auto-detect';
4
4
  export type { AutoDetectOptions, AutoDetectResult, } from './services/auto-detect';
5
- export { isStaticBuild } from './services/utils';
5
+ export { isStaticBuild, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, } from './services/utils';
6
6
  export { getServicesBuilders } from './services/get-services-builders';
7
7
  export type { DetectServicesOptions, DetectServicesResult, ResolvedService, Service, ServicesRoutes, ServiceDetectionError, } from './services/types';
8
8
  export { detectFileSystemAPI } from './detect-file-system-api';
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ __export(src_exports, {
22
22
  DetectorFilesystem: () => import_filesystem.DetectorFilesystem,
23
23
  GetWorkspaceOptions: () => import_get_workspaces.GetWorkspaceOptions,
24
24
  GetWorkspacePackagePathsOptions: () => import_get_workspace_package_paths.GetWorkspacePackagePathsOptions,
25
+ INTERNAL_SERVICE_PREFIX: () => import_utils.INTERNAL_SERVICE_PREFIX,
25
26
  LocalFileSystemDetector: () => import_local_file_system_detector.LocalFileSystemDetector,
26
27
  REGEX_NON_VERCEL_PLATFORM_FILES: () => import_detect_builders2.REGEX_NON_VERCEL_PLATFORM_FILES,
27
28
  Workspace: () => import_get_workspaces.Workspace,
@@ -39,11 +40,13 @@ __export(src_exports, {
39
40
  detectOutputDirectory: () => import_detect_builders.detectOutputDirectory,
40
41
  detectServices: () => import_detect_services.detectServices,
41
42
  generateServicesRoutes: () => import_detect_services.generateServicesRoutes,
43
+ getInternalServiceFunctionPath: () => import_utils.getInternalServiceFunctionPath,
42
44
  getProjectPaths: () => import_get_project_paths.getProjectPaths,
43
45
  getServicesBuilders: () => import_get_services_builders.getServicesBuilders,
44
46
  getWorkspacePackagePaths: () => import_get_workspace_package_paths.getWorkspacePackagePaths,
45
47
  getWorkspaces: () => import_get_workspaces.getWorkspaces,
46
48
  isOfficialRuntime: () => import_is_official_runtime.isOfficialRuntime,
49
+ isRouteOwningBuilder: () => import_utils.isRouteOwningBuilder,
47
50
  isStaticBuild: () => import_utils.isStaticBuild,
48
51
  isStaticRuntime: () => import_is_official_runtime.isStaticRuntime,
49
52
  monorepoManagers: () => import_monorepo_managers.monorepoManagers,
@@ -75,6 +78,7 @@ var import_detect_instrumentation = require("./detect-instrumentation");
75
78
  DetectorFilesystem,
76
79
  GetWorkspaceOptions,
77
80
  GetWorkspacePackagePathsOptions,
81
+ INTERNAL_SERVICE_PREFIX,
78
82
  LocalFileSystemDetector,
79
83
  REGEX_NON_VERCEL_PLATFORM_FILES,
80
84
  Workspace,
@@ -92,11 +96,13 @@ var import_detect_instrumentation = require("./detect-instrumentation");
92
96
  detectOutputDirectory,
93
97
  detectServices,
94
98
  generateServicesRoutes,
99
+ getInternalServiceFunctionPath,
95
100
  getProjectPaths,
96
101
  getServicesBuilders,
97
102
  getWorkspacePackagePaths,
98
103
  getWorkspaces,
99
104
  isOfficialRuntime,
105
+ isRouteOwningBuilder,
100
106
  isStaticBuild,
101
107
  isStaticRuntime,
102
108
  monorepoManagers,
@@ -1,4 +1,4 @@
1
- import type { DetectServicesOptions, DetectServicesResult, ResolvedService, ServicesRoutes } from './types';
1
+ import { type DetectServicesOptions, type DetectServicesResult, type ResolvedService, type ServicesRoutes } from './types';
2
2
  /**
3
3
  * Detect and resolve services within a project.
4
4
  *
@@ -13,8 +13,19 @@ export declare function detectServices(options: DetectServicesOptions): Promise<
13
13
  * routes match before broader ones. For example, `/api/users` must be checked
14
14
  * before `/api`, which must be checked before the catch-all `/`.
15
15
  *
16
- * - Static/SPA services: SPA fallback routes to index.html
17
- * - Serverless services: Rewrite to the function entrypoint
16
+ * Services routing only generates *synthetic* routes for builders that do not
17
+ * provide their own route tables:
18
+ *
19
+ * - **Static/SPA services** (`@vercel/static-build`, `@vercel/static`):
20
+ * SPA fallback routes to index.html under the service prefix.
21
+ *
22
+ * - **Runtime services** (`@vercel/python`, `@vercel/go`, `@vercel/ruby`, etc.):
23
+ * Prefix rewrites to an internal runtime destination (`/_svc/{name}/index`)
24
+ * with `check: true`.
25
+ *
26
+ * Builders that provide their own routing (`@vercel/next`, `@vercel/backends`,
27
+ * Build Output API builders, etc.) are not given synthetic routes here.
28
+ *
18
29
  * - Cron/Worker services: TODO - internal routes under `/_svc/`
19
30
  */
20
31
  export declare function generateServicesRoutes(services: ResolvedService[]): ServicesRoutes;
@@ -22,6 +22,7 @@ __export(detect_services_exports, {
22
22
  generateServicesRoutes: () => generateServicesRoutes
23
23
  });
24
24
  module.exports = __toCommonJS(detect_services_exports);
25
+ var import_routing_utils = require("@vercel/routing-utils");
25
26
  var import_utils = require("./utils");
26
27
  var import_resolve = require("./resolve");
27
28
  var import_auto_detect = require("./auto-detect");
@@ -88,35 +89,64 @@ function generateServicesRoutes(services) {
88
89
  const sortedWebServices = services.filter(
89
90
  (s) => s.type === "web" && typeof s.routePrefix === "string"
90
91
  ).sort((a, b) => b.routePrefix.length - a.routePrefix.length);
92
+ const allWebPrefixes = getWebRoutePrefixes(sortedWebServices);
91
93
  for (const service of sortedWebServices) {
92
94
  const { routePrefix } = service;
93
95
  const normalizedPrefix = routePrefix.slice(1);
96
+ const ownershipGuard = (0, import_routing_utils.getOwnershipGuard)(routePrefix, allWebPrefixes);
97
+ if ((0, import_utils.isRouteOwningBuilder)(service)) {
98
+ continue;
99
+ }
94
100
  if ((0, import_utils.isStaticBuild)(service)) {
95
101
  if (routePrefix === "/") {
96
102
  defaults.push({ handle: "filesystem" });
97
- defaults.push({ src: "/(.*)", dest: "/index.html" });
103
+ defaults.push({
104
+ src: (0, import_routing_utils.scopeRouteSourceToOwnership)("/(.*)", ownershipGuard),
105
+ dest: "/index.html"
106
+ });
98
107
  } else {
99
108
  rewrites.push({
100
- src: `^/${normalizedPrefix}(?:/.*)?$`,
109
+ src: (0, import_routing_utils.scopeRouteSourceToOwnership)(
110
+ `^/${normalizedPrefix}(?:/.*)?$`,
111
+ ownershipGuard
112
+ ),
101
113
  dest: `/${normalizedPrefix}/index.html`
102
114
  });
103
115
  }
104
- } else {
105
- const builderSrc = service.builder.src || routePrefix;
106
- const functionPath = builderSrc.startsWith("/") ? builderSrc : `/${builderSrc}`;
116
+ } else if (service.runtime) {
117
+ const functionPath = (0, import_utils.getInternalServiceFunctionPath)(service.name);
107
118
  if (routePrefix === "/") {
108
- defaults.push({ src: "^/(.*)$", dest: functionPath, check: true });
119
+ defaults.push({
120
+ src: (0, import_routing_utils.scopeRouteSourceToOwnership)("^/(.*)$", ownershipGuard),
121
+ dest: functionPath,
122
+ check: true
123
+ });
109
124
  } else {
110
125
  rewrites.push({
111
- src: `^/${normalizedPrefix}(?:/.*)?$`,
126
+ src: (0, import_routing_utils.scopeRouteSourceToOwnership)(
127
+ `^/${normalizedPrefix}(?:/.*)?$`,
128
+ ownershipGuard
129
+ ),
112
130
  dest: functionPath,
113
131
  check: true
114
132
  });
115
133
  }
134
+ } else {
135
+ continue;
116
136
  }
117
137
  }
118
138
  return { rewrites, defaults, crons, workers };
119
139
  }
140
+ function getWebRoutePrefixes(services) {
141
+ const unique = /* @__PURE__ */ new Set();
142
+ for (const service of services) {
143
+ if (service.type !== "web" || typeof service.routePrefix !== "string") {
144
+ continue;
145
+ }
146
+ unique.add((0, import_routing_utils.normalizeRoutePrefix)(service.routePrefix));
147
+ }
148
+ return Array.from(unique);
149
+ }
120
150
  // Annotate the CommonJS export names for ESM import in node:
121
151
  0 && (module.exports = {
122
152
  detectServices,
@@ -37,8 +37,13 @@ var import_path = require("path");
37
37
  var import_types = require("./types");
38
38
  var import_utils = require("./utils");
39
39
  var import_frameworks = __toESM(require("@vercel/frameworks"));
40
+ var import_routing_utils = require("@vercel/routing-utils");
40
41
  const frameworksBySlug = new Map(import_frameworks.default.map((f) => [f.slug, f]));
41
42
  const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
43
+ function isReservedServiceRoutePrefix(routePrefix) {
44
+ const normalized = (0, import_routing_utils.normalizeRoutePrefix)(routePrefix);
45
+ return normalized === import_utils.INTERNAL_SERVICE_PREFIX || normalized.startsWith(`${import_utils.INTERNAL_SERVICE_PREFIX}/`);
46
+ }
42
47
  function validateServiceConfig(name, config) {
43
48
  if (!SERVICE_NAME_REGEX.test(name)) {
44
49
  return {
@@ -62,6 +67,13 @@ function validateServiceConfig(name, config) {
62
67
  serviceName: name
63
68
  };
64
69
  }
70
+ if (serviceType === "web" && config.routePrefix && isReservedServiceRoutePrefix(config.routePrefix)) {
71
+ return {
72
+ code: "RESERVED_ROUTE_PREFIX",
73
+ message: `Web service "${name}" cannot use routePrefix "${config.routePrefix}". The "${import_utils.INTERNAL_SERVICE_PREFIX}" prefix is reserved for internal services routing.`,
74
+ serviceName: name
75
+ };
76
+ }
65
77
  if ((serviceType === "worker" || serviceType === "cron") && config.routePrefix) {
66
78
  return {
67
79
  code: "INVALID_ROUTE_PREFIX",
@@ -141,10 +153,10 @@ function resolveConfiguredService(name, config, group) {
141
153
  }
142
154
  const routePrefix = type === "web" && config.routePrefix ? config.routePrefix.startsWith("/") ? config.routePrefix : `/${config.routePrefix}` : void 0;
143
155
  const isRoot = workspace === ".";
144
- if (!isRoot && !builderSrc.startsWith(workspace + "/")) {
156
+ if (!isRoot) {
145
157
  builderSrc = import_path.posix.join(workspace, builderSrc);
146
158
  }
147
- const builderConfig = {};
159
+ const builderConfig = { zeroConfig: true };
148
160
  if (config.memory)
149
161
  builderConfig.memory = config.memory;
150
162
  if (config.maxDuration)
@@ -159,6 +171,9 @@ function resolveConfiguredService(name, config, group) {
159
171
  const stripped = routePrefix.startsWith("/") ? routePrefix.slice(1) : routePrefix;
160
172
  builderConfig.routePrefix = stripped || ".";
161
173
  }
174
+ if (workspace && workspace !== ".") {
175
+ builderConfig.workspace = workspace;
176
+ }
162
177
  if (config.framework) {
163
178
  builderConfig.framework = config.framework;
164
179
  }
@@ -186,6 +201,7 @@ function resolveConfiguredService(name, config, group) {
186
201
  function resolveAllConfiguredServices(services) {
187
202
  const resolved = [];
188
203
  const errors = [];
204
+ const webServicesByRoutePrefix = /* @__PURE__ */ new Map();
189
205
  for (const name of Object.keys(services)) {
190
206
  const serviceConfig = services[name];
191
207
  const validationError = validateServiceConfig(name, serviceConfig);
@@ -194,6 +210,21 @@ function resolveAllConfiguredServices(services) {
194
210
  continue;
195
211
  }
196
212
  const service = resolveConfiguredService(name, serviceConfig);
213
+ if (service.type === "web" && typeof service.routePrefix === "string") {
214
+ const normalizedRoutePrefix = (0, import_routing_utils.normalizeRoutePrefix)(service.routePrefix);
215
+ const existingServiceName = webServicesByRoutePrefix.get(
216
+ normalizedRoutePrefix
217
+ );
218
+ if (existingServiceName) {
219
+ errors.push({
220
+ code: "DUPLICATE_ROUTE_PREFIX",
221
+ message: `Web services "${existingServiceName}" and "${name}" cannot share routePrefix "${normalizedRoutePrefix}".`,
222
+ serviceName: name
223
+ });
224
+ continue;
225
+ }
226
+ webServicesByRoutePrefix.set(normalizedRoutePrefix, name);
227
+ }
197
228
  resolved.push(service);
198
229
  }
199
230
  return { services: resolved, errors };
@@ -56,3 +56,16 @@ export declare const ENTRYPOINT_EXTENSIONS: Record<string, ServiceRuntime>;
56
56
  * These don't have a "runtime" - they just build to static files.
57
57
  */
58
58
  export declare const STATIC_BUILDERS: Set<string>;
59
+ /**
60
+ * Builders that produce their own full route table with handle phases
61
+ * (filesystem, miss, rewrite, hit, error).
62
+ *
63
+ * In services mode we generally avoid generating synthetic catch-all routes
64
+ * for builders that provide their own routing. At service-detection time we
65
+ * only have the builder "use" string (not the loaded module), so this is an
66
+ * explicit allow-list for known route-table builders.
67
+ *
68
+ * NOTE: This is an explicit positive set because we can't check
69
+ * `builder.version` at service detection time.
70
+ */
71
+ export declare const ROUTE_OWNING_BUILDERS: Set<string>;
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var types_exports = {};
20
20
  __export(types_exports, {
21
21
  ENTRYPOINT_EXTENSIONS: () => ENTRYPOINT_EXTENSIONS,
22
+ ROUTE_OWNING_BUILDERS: () => ROUTE_OWNING_BUILDERS,
22
23
  RUNTIME_BUILDERS: () => RUNTIME_BUILDERS,
23
24
  STATIC_BUILDERS: () => STATIC_BUILDERS
24
25
  });
@@ -46,9 +47,14 @@ const STATIC_BUILDERS = /* @__PURE__ */ new Set([
46
47
  "@vercel/static-build",
47
48
  "@vercel/static"
48
49
  ]);
50
+ const ROUTE_OWNING_BUILDERS = /* @__PURE__ */ new Set([
51
+ "@vercel/next",
52
+ "@vercel/backends"
53
+ ]);
49
54
  // Annotate the CommonJS export names for ESM import in node:
50
55
  0 && (module.exports = {
51
56
  ENTRYPOINT_EXTENSIONS,
57
+ ROUTE_OWNING_BUILDERS,
52
58
  RUNTIME_BUILDERS,
53
59
  STATIC_BUILDERS
54
60
  });
@@ -1,7 +1,21 @@
1
1
  import type { DetectorFilesystem } from '../detectors/filesystem';
2
2
  import type { ServiceRuntime, ExperimentalServices, ServiceDetectionError, ResolvedService } from './types';
3
+ /**
4
+ * Reserved internal namespace used by services routing/runtime plumbing.
5
+ */
6
+ export declare const INTERNAL_SERVICE_PREFIX = "/_svc";
7
+ export declare function getInternalServiceFunctionPath(serviceName: string): string;
3
8
  export declare function getBuilderForRuntime(runtime: ServiceRuntime): string;
4
9
  export declare function isStaticBuild(service: ResolvedService): boolean;
10
+ /**
11
+ * Determines if a service uses a "route-owning" builder.
12
+ *
13
+ * Route-owning builders (e.g., `@vercel/next`, `@vercel/backends`) produce
14
+ * their own full route table with handle phases (filesystem, miss, rewrite,
15
+ * hit, error). The services system should NOT generate synthetic catch-all
16
+ * rewrites for them — instead, we rely on the builder's own `routes[]`.
17
+ */
18
+ export declare function isRouteOwningBuilder(service: ResolvedService): boolean;
5
19
  /**
6
20
  * Infer runtime from available service configuration.
7
21
  *
@@ -18,14 +18,21 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var utils_exports = {};
20
20
  __export(utils_exports, {
21
+ INTERNAL_SERVICE_PREFIX: () => INTERNAL_SERVICE_PREFIX,
21
22
  getBuilderForRuntime: () => getBuilderForRuntime,
23
+ getInternalServiceFunctionPath: () => getInternalServiceFunctionPath,
22
24
  inferServiceRuntime: () => inferServiceRuntime,
25
+ isRouteOwningBuilder: () => isRouteOwningBuilder,
23
26
  isStaticBuild: () => isStaticBuild,
24
27
  readVercelConfig: () => readVercelConfig
25
28
  });
26
29
  module.exports = __toCommonJS(utils_exports);
27
30
  var import_framework_helpers = require("@vercel/build-utils/dist/framework-helpers");
28
31
  var import_types = require("./types");
32
+ const INTERNAL_SERVICE_PREFIX = "/_svc";
33
+ function getInternalServiceFunctionPath(serviceName) {
34
+ return `${INTERNAL_SERVICE_PREFIX}/${serviceName}/index`;
35
+ }
29
36
  function getBuilderForRuntime(runtime) {
30
37
  const builder = import_types.RUNTIME_BUILDERS[runtime];
31
38
  if (!builder) {
@@ -36,6 +43,9 @@ function getBuilderForRuntime(runtime) {
36
43
  function isStaticBuild(service) {
37
44
  return import_types.STATIC_BUILDERS.has(service.builder.use);
38
45
  }
46
+ function isRouteOwningBuilder(service) {
47
+ return import_types.ROUTE_OWNING_BUILDERS.has(service.builder.use);
48
+ }
39
49
  function inferServiceRuntime(config) {
40
50
  if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
41
51
  return config.runtime;
@@ -83,8 +93,11 @@ async function readVercelConfig(fs) {
83
93
  }
84
94
  // Annotate the CommonJS export names for ESM import in node:
85
95
  0 && (module.exports = {
96
+ INTERNAL_SERVICE_PREFIX,
86
97
  getBuilderForRuntime,
98
+ getInternalServiceFunctionPath,
87
99
  inferServiceRuntime,
100
+ isRouteOwningBuilder,
88
101
  isStaticBuild,
89
102
  readVercelConfig
90
103
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.8.4",
3
+ "version": "5.8.6",
4
4
  "description": "Vercel filesystem detectors",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -19,9 +19,9 @@
19
19
  "json5": "2.2.2",
20
20
  "minimatch": "3.1.2",
21
21
  "semver": "6.3.1",
22
- "@vercel/error-utils": "2.0.3",
23
- "@vercel/routing-utils": "5.3.2",
24
- "@vercel/frameworks": "3.17.1"
22
+ "@vercel/routing-utils": "5.3.3",
23
+ "@vercel/frameworks": "3.17.1",
24
+ "@vercel/error-utils": "2.0.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/glob": "7.2.0",
@@ -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.3.4"
35
+ "@vercel/build-utils": "13.4.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "node ../../utils/build.mjs",