@vercel/fs-detectors 5.9.0 → 5.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.
@@ -37,6 +37,7 @@ export declare function detectBuilders(files: string[], pkg?: PackageJson | unde
37
37
  builders: Builder[] | null;
38
38
  errors: ErrorResponse[] | null;
39
39
  warnings: ErrorResponse[];
40
+ hostRewriteRoutes?: Route[] | null;
40
41
  defaultRoutes: Route[] | null;
41
42
  redirectRoutes: Route[] | null;
42
43
  rewriteRoutes: Route[] | null;
@@ -460,10 +460,10 @@ function validateFunctions({ functions = {} }) {
460
460
  message: "Function must contain at least one property."
461
461
  };
462
462
  }
463
- if (func.maxDuration !== void 0 && (func.maxDuration < 1 || func.maxDuration > 900 || !Number.isInteger(func.maxDuration))) {
463
+ if (func.maxDuration !== void 0 && func.maxDuration !== "max" && (func.maxDuration < 1 || func.maxDuration > 900 || !Number.isInteger(func.maxDuration))) {
464
464
  return {
465
465
  code: "invalid_function_duration",
466
- message: "Functions must have a duration between 1 and 900."
466
+ message: 'Functions must have a maxDuration between 1 and 900, or "max".'
467
467
  };
468
468
  }
469
469
  if (func.memory !== void 0 && (func.memory < 128 || func.memory > 10240)) {
@@ -1,8 +1,8 @@
1
- import { type DetectServicesOptions, type DetectServicesResult, type ResolvedService, type ServicesRoutes } from './types';
1
+ import { type DetectServicesOptions, type DetectServicesResult, type Service, type ServicesRoutes } from './types';
2
2
  /**
3
3
  * Detect and resolve services within a project.
4
4
  *
5
- * Reads vercel.json and resolves `experimentalServices` into ResolvedService objects.
5
+ * Reads vercel.json and resolves `experimentalServices` into Service objects.
6
6
  * Returns an error if no services are configured.
7
7
  */
8
8
  export declare function detectServices(options: DetectServicesOptions): Promise<DetectServicesResult>;
@@ -34,4 +34,4 @@ export declare function detectServices(options: DetectServicesOptions): Promise<
34
34
  * Internal cron callback routes under `/_svc/{serviceName}/crons/{entry}/{handler}`
35
35
  * that rewrite to `/_svc/{serviceName}/index`.
36
36
  */
37
- export declare function generateServicesRoutes(services: ResolvedService[]): ServicesRoutes;
37
+ export declare function generateServicesRoutes(services: Service[]): ServicesRoutes;
@@ -26,6 +26,10 @@ var import_routing_utils = require("@vercel/routing-utils");
26
26
  var import_utils = require("./utils");
27
27
  var import_resolve = require("./resolve");
28
28
  var import_auto_detect = require("./auto-detect");
29
+ const PREVIEW_DOMAIN_MISSING = [
30
+ { type: "host", value: { suf: ".vercel.app" } },
31
+ { type: "host", value: { suf: ".vercel.dev" } }
32
+ ];
29
33
  async function detectServices(options) {
30
34
  const { fs, workPath } = options;
31
35
  const scopedFs = workPath ? fs.chdir(workPath) : fs;
@@ -34,7 +38,13 @@ async function detectServices(options) {
34
38
  return {
35
39
  services: [],
36
40
  source: "configured",
37
- routes: { rewrites: [], defaults: [], crons: [], workers: [] },
41
+ routes: {
42
+ hostRewrites: [],
43
+ rewrites: [],
44
+ defaults: [],
45
+ crons: [],
46
+ workers: []
47
+ },
38
48
  errors: [configError],
39
49
  warnings: []
40
50
  };
@@ -47,7 +57,13 @@ async function detectServices(options) {
47
57
  return {
48
58
  services: [],
49
59
  source: "auto-detected",
50
- routes: { rewrites: [], defaults: [], crons: [], workers: [] },
60
+ routes: {
61
+ hostRewrites: [],
62
+ rewrites: [],
63
+ defaults: [],
64
+ crons: [],
65
+ workers: []
66
+ },
51
67
  errors: autoResult.errors,
52
68
  warnings: []
53
69
  };
@@ -70,7 +86,13 @@ async function detectServices(options) {
70
86
  return {
71
87
  services: [],
72
88
  source: "auto-detected",
73
- routes: { rewrites: [], defaults: [], crons: [], workers: [] },
89
+ routes: {
90
+ hostRewrites: [],
91
+ rewrites: [],
92
+ defaults: [],
93
+ crons: [],
94
+ workers: []
95
+ },
74
96
  errors: [
75
97
  {
76
98
  code: "NO_SERVICES_CONFIGURED",
@@ -95,6 +117,7 @@ async function detectServices(options) {
95
117
  };
96
118
  }
97
119
  function generateServicesRoutes(services) {
120
+ const hostRewrites = [];
98
121
  const rewrites = [];
99
122
  const defaults = [];
100
123
  const crons = [];
@@ -107,6 +130,25 @@ function generateServicesRoutes(services) {
107
130
  const { routePrefix } = service;
108
131
  const normalizedPrefix = routePrefix.slice(1);
109
132
  const ownershipGuard = (0, import_routing_utils.getOwnershipGuard)(routePrefix, allWebPrefixes);
133
+ const hostCondition = getHostCondition(service);
134
+ if (hostCondition && routePrefix !== "/") {
135
+ const normalizedRoutePrefix = (0, import_routing_utils.normalizeRoutePrefix)(routePrefix);
136
+ const escapedPrefix = escapeRegex(normalizedRoutePrefix.slice(1));
137
+ hostRewrites.push({
138
+ src: "^/$",
139
+ dest: normalizedRoutePrefix,
140
+ has: hostCondition,
141
+ missing: PREVIEW_DOMAIN_MISSING,
142
+ check: true
143
+ });
144
+ hostRewrites.push({
145
+ src: `^/(?!${escapedPrefix}(?:/|$))(.*)$`,
146
+ dest: `${normalizedRoutePrefix}/$1`,
147
+ has: hostCondition,
148
+ missing: PREVIEW_DOMAIN_MISSING,
149
+ check: true
150
+ });
151
+ }
110
152
  if ((0, import_utils.isRouteOwningBuilder)(service)) {
111
153
  continue;
112
154
  }
@@ -176,7 +218,7 @@ function generateServicesRoutes(services) {
176
218
  check: true
177
219
  });
178
220
  }
179
- return { rewrites, defaults, crons, workers };
221
+ return { hostRewrites, rewrites, defaults, crons, workers };
180
222
  }
181
223
  function escapeRegex(str) {
182
224
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -191,6 +233,15 @@ function getWebRoutePrefixes(services) {
191
233
  }
192
234
  return Array.from(unique);
193
235
  }
236
+ function getHostCondition(service) {
237
+ if (service.type !== "web") {
238
+ return void 0;
239
+ }
240
+ if (typeof service.subdomain === "string" && service.subdomain.length > 0) {
241
+ return [{ type: "host", value: { pre: `${service.subdomain}.` } }];
242
+ }
243
+ return void 0;
244
+ }
194
245
  // Annotate the CommonJS export names for ESM import in node:
195
246
  0 && (module.exports = {
196
247
  detectServices,
@@ -14,6 +14,7 @@ export interface ServicesBuildersResult {
14
14
  builders: Builder[] | null;
15
15
  errors: ErrorResponse[] | null;
16
16
  warnings: ErrorResponse[];
17
+ hostRewriteRoutes: Route[] | null;
17
18
  defaultRoutes: Route[] | null;
18
19
  redirectRoutes: Route[] | null;
19
20
  rewriteRoutes: Route[] | null;
@@ -35,6 +35,7 @@ async function getServicesBuilders(options) {
35
35
  }
36
36
  ],
37
37
  warnings: [],
38
+ hostRewriteRoutes: null,
38
39
  defaultRoutes: null,
39
40
  redirectRoutes: null,
40
41
  rewriteRoutes: null,
@@ -55,6 +56,7 @@ async function getServicesBuilders(options) {
55
56
  message: e.message
56
57
  })),
57
58
  warnings: warningResponses,
59
+ hostRewriteRoutes: null,
58
60
  defaultRoutes: null,
59
61
  redirectRoutes: null,
60
62
  rewriteRoutes: null,
@@ -71,6 +73,7 @@ async function getServicesBuilders(options) {
71
73
  }
72
74
  ],
73
75
  warnings: warningResponses,
76
+ hostRewriteRoutes: null,
74
77
  defaultRoutes: null,
75
78
  redirectRoutes: null,
76
79
  rewriteRoutes: null,
@@ -82,6 +85,7 @@ async function getServicesBuilders(options) {
82
85
  builders: builders.length > 0 ? builders : null,
83
86
  errors: null,
84
87
  warnings: warningResponses,
88
+ hostRewriteRoutes: result.routes.hostRewrites.length > 0 ? result.routes.hostRewrites : null,
85
89
  defaultRoutes: result.routes.defaults.length > 0 ? result.routes.defaults : null,
86
90
  redirectRoutes: [],
87
91
  rewriteRoutes: result.routes.rewrites.length > 0 || result.routes.workers.length > 0 || result.routes.crons.length > 0 ? [
@@ -52,6 +52,7 @@ function parsePyModuleAttrEntrypoint(entrypoint) {
52
52
  };
53
53
  }
54
54
  const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
55
+ const DNS_LABEL_RE = /^(?!-)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
55
56
  function normalizeServiceEntrypoint(entrypoint) {
56
57
  const normalized = import_path.posix.normalize(entrypoint);
57
58
  return normalized === "" ? "." : normalized;
@@ -182,10 +183,19 @@ function validateServiceConfig(name, config) {
182
183
  };
183
184
  }
184
185
  const serviceType = config.type || "web";
185
- if (serviceType === "web" && !config.routePrefix) {
186
+ const hasRoutePrefix = typeof config.routePrefix === "string";
187
+ const hasSubdomain = typeof config.subdomain === "string";
188
+ if (hasSubdomain && !DNS_LABEL_RE.test(config.subdomain)) {
189
+ return {
190
+ code: "INVALID_SUBDOMAIN",
191
+ message: `Web service "${name}" has invalid subdomain "${config.subdomain}". Use a single DNS label such as "api".`,
192
+ serviceName: name
193
+ };
194
+ }
195
+ if (serviceType === "web" && !hasRoutePrefix && !hasSubdomain) {
186
196
  return {
187
197
  code: "MISSING_ROUTE_PREFIX",
188
- message: `Web service "${name}" must specify "routePrefix".`,
198
+ message: `Web service "${name}" must specify at least one of "routePrefix" or "subdomain".`,
189
199
  serviceName: name
190
200
  };
191
201
  }
@@ -203,6 +213,13 @@ function validateServiceConfig(name, config) {
203
213
  serviceName: name
204
214
  };
205
215
  }
216
+ if ((serviceType === "worker" || serviceType === "cron") && hasSubdomain) {
217
+ return {
218
+ code: "INVALID_HOST_ROUTING_CONFIG",
219
+ message: `${serviceType === "worker" ? "Worker" : "Cron"} service "${name}" cannot have "subdomain". Only web services should specify subdomain routing.`,
220
+ serviceName: name
221
+ };
222
+ }
206
223
  if (serviceType === "cron" && !config.schedule) {
207
224
  return {
208
225
  code: "MISSING_CRON_SCHEDULE",
@@ -337,7 +354,10 @@ async function resolveConfiguredService(options) {
337
354
  builderUse = (0, import_utils.getBuilderForRuntime)(inferredRuntime);
338
355
  builderSrc = resolvedEntrypointFile;
339
356
  }
340
- const routePrefix = type === "web" && config.routePrefix ? config.routePrefix.startsWith("/") ? config.routePrefix : `/${config.routePrefix}` : void 0;
357
+ const normalizedSubdomain = type === "web" && typeof config.subdomain === "string" ? config.subdomain.toLowerCase() : void 0;
358
+ const defaultRoutePrefix = type === "web" && normalizedSubdomain ? `/_/${name}` : void 0;
359
+ const routePrefix = type === "web" && (config.routePrefix || defaultRoutePrefix) ? (config.routePrefix || defaultRoutePrefix).startsWith("/") ? config.routePrefix || defaultRoutePrefix : `/${config.routePrefix || defaultRoutePrefix}` : void 0;
360
+ const resolvedRoutePrefixSource = type === "web" && typeof routePrefix === "string" ? config.routePrefix ? routePrefixSource : "generated" : void 0;
341
361
  const isRoot = workspace === ".";
342
362
  if (!isRoot) {
343
363
  builderSrc = import_path.posix.join(workspace, builderSrc);
@@ -373,7 +393,8 @@ async function resolveConfiguredService(options) {
373
393
  workspace,
374
394
  entrypoint: resolvedEntrypointFile,
375
395
  routePrefix,
376
- routePrefixSource: type === "web" && typeof routePrefix === "string" ? routePrefixSource : void 0,
396
+ routePrefixSource: resolvedRoutePrefixSource,
397
+ subdomain: normalizedSubdomain,
377
398
  framework: config.framework,
378
399
  builder: {
379
400
  src: builderSrc,
@@ -15,6 +15,8 @@ export interface DetectServicesOptions {
15
15
  workPath?: string;
16
16
  }
17
17
  export interface ServicesRoutes {
18
+ /** Host-based rewrite routes for subdomain-mounted web services */
19
+ hostRewrites: Route[];
18
20
  /** Rewrite routes for non-root web services (prefix-based) */
19
21
  rewrites: Route[];
20
22
  /** Default routes (catch-all for root web service) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.9.0",
3
+ "version": "5.10.0",
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/frameworks": "3.20.0",
23
22
  "@vercel/error-utils": "2.0.3",
24
- "@vercel/routing-utils": "6.0.2"
23
+ "@vercel/routing-utils": "6.0.2",
24
+ "@vercel/frameworks": "3.20.0"
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.7.0"
35
+ "@vercel/build-utils": "13.8.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "node ../../utils/build.mjs",