@vercel/fs-detectors 5.15.0 → 5.15.2

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.
@@ -310,6 +310,14 @@ async function maybeGetApiBuilder(fileName, apiMatches, options) {
310
310
  return null;
311
311
  }
312
312
  }
313
+ const nodeExtensions = [".js", ".mjs", ".ts", ".tsx"];
314
+ if (process.env.VERCEL_NODE_FILTER_ENTRYPOINTS === "1" && nodeExtensions.some((ext) => fileName.endsWith(ext)) && options.workPath) {
315
+ const fsPath = (0, import_path.join)(options.workPath, fileName);
316
+ const isEntrypoint = await (0, import_build_utils.isNodeEntrypoint)({ fsPath });
317
+ if (!isEntrypoint) {
318
+ return null;
319
+ }
320
+ }
313
321
  const match = apiMatches.find(({ src = "**" }) => {
314
322
  return src === fileName || (0, import_minimatch.default)(fileName, src);
315
323
  });
@@ -160,6 +160,84 @@ function isReservedServiceRoutePrefix(routePrefix) {
160
160
  const normalized = (0, import_routing_utils.normalizeRoutePrefix)(routePrefix);
161
161
  return normalized === import_utils.INTERNAL_SERVICE_PREFIX || normalized.startsWith(`${import_utils.INTERNAL_SERVICE_PREFIX}/`);
162
162
  }
163
+ function resolveServiceRoutingConfig(name, config) {
164
+ const hasLegacyRoutePrefix = typeof config.routePrefix === "string";
165
+ const hasLegacySubdomain = typeof config.subdomain === "string";
166
+ if (config.mount === void 0) {
167
+ return {
168
+ routing: {
169
+ routePrefix: config.routePrefix,
170
+ subdomain: config.subdomain,
171
+ routePrefixConfigured: hasLegacyRoutePrefix
172
+ }
173
+ };
174
+ }
175
+ if (hasLegacyRoutePrefix || hasLegacySubdomain) {
176
+ return {
177
+ error: {
178
+ code: "CONFLICTING_MOUNT_CONFIG",
179
+ message: `Service "${name}" cannot mix "mount" with "routePrefix" or "subdomain". Use only one routing configuration style.`,
180
+ serviceName: name
181
+ }
182
+ };
183
+ }
184
+ if (typeof config.mount === "string") {
185
+ return {
186
+ routing: {
187
+ routePrefix: config.mount,
188
+ routePrefixConfigured: true
189
+ }
190
+ };
191
+ }
192
+ if (!config.mount || typeof config.mount !== "object" || Array.isArray(config.mount)) {
193
+ return {
194
+ error: {
195
+ code: "INVALID_MOUNT",
196
+ message: `Service "${name}" has invalid "mount" config. Use a string path such as "/api" or an object like { path: "/api", subdomain: "api" }.`,
197
+ serviceName: name
198
+ }
199
+ };
200
+ }
201
+ const hasInvalidMountKeys = Object.keys(config.mount).some(
202
+ (key) => key !== "path" && key !== "subdomain"
203
+ );
204
+ if (hasInvalidMountKeys) {
205
+ return {
206
+ error: {
207
+ code: "INVALID_MOUNT",
208
+ message: `Service "${name}" has invalid "mount" config. Only "path" and "subdomain" are supported.`,
209
+ serviceName: name
210
+ }
211
+ };
212
+ }
213
+ const mountPath = config.mount.path;
214
+ const mountSubdomain = config.mount.subdomain;
215
+ if (mountPath !== void 0 && typeof mountPath !== "string" || mountSubdomain !== void 0 && typeof mountSubdomain !== "string") {
216
+ return {
217
+ error: {
218
+ code: "INVALID_MOUNT",
219
+ message: `Service "${name}" has invalid "mount" config. "path" and "subdomain" must be strings when provided.`,
220
+ serviceName: name
221
+ }
222
+ };
223
+ }
224
+ if (typeof mountPath !== "string" && typeof mountSubdomain !== "string") {
225
+ return {
226
+ error: {
227
+ code: "INVALID_MOUNT",
228
+ message: `Service "${name}" has invalid "mount" config. Specify at least one of "mount.path" or "mount.subdomain".`,
229
+ serviceName: name
230
+ }
231
+ };
232
+ }
233
+ return {
234
+ routing: {
235
+ routePrefix: mountPath,
236
+ subdomain: mountSubdomain,
237
+ routePrefixConfigured: typeof mountPath === "string"
238
+ }
239
+ };
240
+ }
163
241
  function validateServiceConfig(name, config) {
164
242
  if (!SERVICE_NAME_REGEX.test(name)) {
165
243
  return {
@@ -176,40 +254,46 @@ function validateServiceConfig(name, config) {
176
254
  };
177
255
  }
178
256
  const serviceType = config.type || "web";
179
- const hasRoutePrefix = typeof config.routePrefix === "string";
180
- const hasSubdomain = typeof config.subdomain === "string";
181
- if (hasSubdomain && !DNS_LABEL_RE.test(config.subdomain)) {
257
+ const routingResult = resolveServiceRoutingConfig(name, config);
258
+ if (routingResult.error) {
259
+ return routingResult.error;
260
+ }
261
+ const configuredRoutePrefix = routingResult.routing?.routePrefix;
262
+ const configuredSubdomain = routingResult.routing?.subdomain;
263
+ const hasRoutePrefix = typeof configuredRoutePrefix === "string";
264
+ const hasSubdomain = typeof configuredSubdomain === "string";
265
+ if (hasSubdomain && !DNS_LABEL_RE.test(configuredSubdomain)) {
182
266
  return {
183
267
  code: "INVALID_SUBDOMAIN",
184
- message: `Web service "${name}" has invalid subdomain "${config.subdomain}". Use a single DNS label such as "api".`,
268
+ message: `Web service "${name}" has invalid subdomain "${configuredSubdomain}". Use a single DNS label such as "api".`,
185
269
  serviceName: name
186
270
  };
187
271
  }
188
272
  if (serviceType === "web" && !hasRoutePrefix && !hasSubdomain) {
189
273
  return {
190
274
  code: "MISSING_ROUTE_PREFIX",
191
- message: `Web service "${name}" must specify at least one of "routePrefix" or "subdomain".`,
275
+ message: `Web service "${name}" must specify at least one of "mount", "routePrefix", or "subdomain".`,
192
276
  serviceName: name
193
277
  };
194
278
  }
195
- if (serviceType === "web" && config.routePrefix && isReservedServiceRoutePrefix(config.routePrefix)) {
279
+ if (serviceType === "web" && configuredRoutePrefix && isReservedServiceRoutePrefix(configuredRoutePrefix)) {
196
280
  return {
197
281
  code: "RESERVED_ROUTE_PREFIX",
198
- message: `Web service "${name}" cannot use routePrefix "${config.routePrefix}". The "${import_utils.INTERNAL_SERVICE_PREFIX}" prefix is reserved for internal services routing.`,
282
+ message: `Web service "${name}" cannot use routePrefix "${configuredRoutePrefix}". The "${import_utils.INTERNAL_SERVICE_PREFIX}" prefix is reserved for internal services routing.`,
199
283
  serviceName: name
200
284
  };
201
285
  }
202
- if ((serviceType === "worker" || serviceType === "cron") && config.routePrefix) {
286
+ if ((serviceType === "worker" || serviceType === "cron") && configuredRoutePrefix) {
203
287
  return {
204
288
  code: "INVALID_ROUTE_PREFIX",
205
- message: `${serviceType === "worker" ? "Worker" : "Cron"} service "${name}" cannot have "routePrefix". Only web services should specify "routePrefix".`,
289
+ message: `${serviceType === "worker" ? "Worker" : "Cron"} service "${name}" cannot have "routePrefix" or "mount". Only web services should specify path-based routing.`,
206
290
  serviceName: name
207
291
  };
208
292
  }
209
293
  if ((serviceType === "worker" || serviceType === "cron") && hasSubdomain) {
210
294
  return {
211
295
  code: "INVALID_HOST_ROUTING_CONFIG",
212
- message: `${serviceType === "worker" ? "Worker" : "Cron"} service "${name}" cannot have "subdomain". Only web services should specify subdomain routing.`,
296
+ message: `${serviceType === "worker" ? "Worker" : "Cron"} service "${name}" cannot have "subdomain" or "mount.subdomain". Only web services should specify subdomain routing.`,
213
297
  serviceName: name
214
298
  };
215
299
  }
@@ -301,6 +385,13 @@ async function resolveConfiguredService(options) {
301
385
  const type = config.type || "web";
302
386
  const rawEntrypoint = config.entrypoint;
303
387
  const moduleAttrParsed = typeof rawEntrypoint === "string" && type === "cron" ? parsePyModuleAttrEntrypoint(rawEntrypoint) : null;
388
+ const routingResult = resolveServiceRoutingConfig(name, config);
389
+ if (routingResult.error) {
390
+ throw new Error(routingResult.error.message);
391
+ }
392
+ const configuredRoutePrefix = routingResult.routing?.routePrefix;
393
+ const configuredSubdomain = routingResult.routing?.subdomain;
394
+ const routePrefixWasConfigured = routingResult.routing?.routePrefixConfigured ?? false;
304
395
  let resolvedEntrypointPath = resolvedEntrypoint;
305
396
  if (!resolvedEntrypointPath && typeof rawEntrypoint === "string") {
306
397
  const entrypointToResolve = moduleAttrParsed ? moduleAttrParsed.filePath : rawEntrypoint;
@@ -370,10 +461,10 @@ async function resolveConfiguredService(options) {
370
461
  }
371
462
  builderSrc = resolvedEntrypointFile;
372
463
  }
373
- const normalizedSubdomain = type === "web" && typeof config.subdomain === "string" ? config.subdomain.toLowerCase() : void 0;
464
+ const normalizedSubdomain = type === "web" && typeof configuredSubdomain === "string" ? configuredSubdomain.toLowerCase() : void 0;
374
465
  const defaultRoutePrefix = type === "web" && normalizedSubdomain ? `/_/${name}` : void 0;
375
- const routePrefix = type === "web" && (config.routePrefix || defaultRoutePrefix) ? (config.routePrefix || defaultRoutePrefix).startsWith("/") ? config.routePrefix || defaultRoutePrefix : `/${config.routePrefix || defaultRoutePrefix}` : void 0;
376
- const resolvedRoutePrefixSource = type === "web" && typeof routePrefix === "string" ? config.routePrefix ? routePrefixSource : "generated" : void 0;
466
+ const routePrefix = type === "web" && (configuredRoutePrefix || defaultRoutePrefix) ? (configuredRoutePrefix || defaultRoutePrefix).startsWith("/") ? configuredRoutePrefix || defaultRoutePrefix : `/${configuredRoutePrefix || defaultRoutePrefix}` : void 0;
467
+ const resolvedRoutePrefixSource = type === "web" && typeof routePrefix === "string" ? routePrefixWasConfigured ? routePrefixSource : "generated" : void 0;
377
468
  const isRoot = workspace === ".";
378
469
  if (!isRoot) {
379
470
  builderSrc = import_path.posix.join(workspace, builderSrc);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.15.0",
3
+ "version": "5.15.2",
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.14.0",
24
- "@vercel/error-utils": "2.0.3",
25
23
  "@vercel/frameworks": "3.24.0",
26
- "@vercel/routing-utils": "6.1.1"
24
+ "@vercel/build-utils": "13.14.2",
25
+ "@vercel/routing-utils": "6.1.1",
26
+ "@vercel/error-utils": "2.0.3"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/glob": "7.2.0",