@vercel/fs-detectors 5.8.15 → 5.8.17

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, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, } from './services/utils';
5
+ export { isStaticBuild, isRouteOwningBuilder, INTERNAL_SERVICE_PREFIX, getInternalServiceFunctionPath, getInternalServiceCronPathPrefix, getInternalServiceWorkerPath, getInternalServiceWorkerPathPrefix, } from './services/utils';
6
6
  export { getServicesBuilders } from './services/get-services-builders';
7
7
  export type { DetectServicesOptions, DetectServicesResult, DetectServicesSource, ResolvedService, Service, ServicesRoutes, ServiceDetectionError, } from './services/types';
8
8
  export { detectFileSystemAPI } from './detect-file-system-api';
package/dist/index.js CHANGED
@@ -40,7 +40,10 @@ __export(src_exports, {
40
40
  detectOutputDirectory: () => import_detect_builders.detectOutputDirectory,
41
41
  detectServices: () => import_detect_services.detectServices,
42
42
  generateServicesRoutes: () => import_detect_services.generateServicesRoutes,
43
+ getInternalServiceCronPathPrefix: () => import_utils.getInternalServiceCronPathPrefix,
43
44
  getInternalServiceFunctionPath: () => import_utils.getInternalServiceFunctionPath,
45
+ getInternalServiceWorkerPath: () => import_utils.getInternalServiceWorkerPath,
46
+ getInternalServiceWorkerPathPrefix: () => import_utils.getInternalServiceWorkerPathPrefix,
44
47
  getProjectPaths: () => import_get_project_paths.getProjectPaths,
45
48
  getServicesBuilders: () => import_get_services_builders.getServicesBuilders,
46
49
  getWorkspacePackagePaths: () => import_get_workspace_package_paths.getWorkspacePackagePaths,
@@ -96,7 +99,10 @@ var import_detect_instrumentation = require("./detect-instrumentation");
96
99
  detectOutputDirectory,
97
100
  detectServices,
98
101
  generateServicesRoutes,
102
+ getInternalServiceCronPathPrefix,
99
103
  getInternalServiceFunctionPath,
104
+ getInternalServiceWorkerPath,
105
+ getInternalServiceWorkerPathPrefix,
100
106
  getProjectPaths,
101
107
  getServicesBuilders,
102
108
  getWorkspacePackagePaths,
@@ -112,6 +112,9 @@ async function detectServicesAtRoot(fs, rootFramework) {
112
112
  if (backendResult.error) {
113
113
  return { services: null, errors: [backendResult.error] };
114
114
  }
115
+ if (Object.keys(backendResult.services).length === 0) {
116
+ return { services: null, errors: [] };
117
+ }
115
118
  Object.assign(services, backendResult.services);
116
119
  return { services, errors: [] };
117
120
  }
@@ -26,6 +26,10 @@ export declare function detectServices(options: DetectServicesOptions): Promise<
26
26
  * Builders that provide their own routing (`@vercel/next`, `@vercel/backends`,
27
27
  * Build Output API builders, etc.) are not given synthetic routes here.
28
28
  *
29
- * - Cron/Worker services: TODO - internal routes under `/_svc/`
29
+ * - Worker services:
30
+ * Internal queue callback routes under `/_svc/{serviceName}/workers/{entry}/{handler}`
31
+ * that rewrite to `/_svc/{serviceName}/index`.
32
+ *
33
+ * - Cron services: TODO - internal routes under `/_svc/`
30
34
  */
31
35
  export declare function generateServicesRoutes(services: ResolvedService[]): ServicesRoutes;
@@ -148,8 +148,25 @@ function generateServicesRoutes(services) {
148
148
  continue;
149
149
  }
150
150
  }
151
+ const workerServices = services.filter((s) => s.type === "worker");
152
+ for (const service of workerServices) {
153
+ const workerEntrypoint = service.entrypoint || service.builder.src || "index";
154
+ const workerPath = (0, import_utils.getInternalServiceWorkerPath)(
155
+ service.name,
156
+ workerEntrypoint
157
+ );
158
+ const functionPath = (0, import_utils.getInternalServiceFunctionPath)(service.name);
159
+ workers.push({
160
+ src: `^${escapeRegex(workerPath)}$`,
161
+ dest: functionPath,
162
+ check: true
163
+ });
164
+ }
151
165
  return { rewrites, defaults, crons, workers };
152
166
  }
167
+ function escapeRegex(str) {
168
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
169
+ }
153
170
  function getWebRoutePrefixes(services) {
154
171
  const unique = /* @__PURE__ */ new Set();
155
172
  for (const service of services) {
@@ -84,7 +84,11 @@ async function getServicesBuilders(options) {
84
84
  warnings: warningResponses,
85
85
  defaultRoutes: result.routes.defaults.length > 0 ? result.routes.defaults : null,
86
86
  redirectRoutes: [],
87
- rewriteRoutes: result.routes.rewrites.length > 0 ? result.routes.rewrites : null,
87
+ rewriteRoutes: result.routes.rewrites.length > 0 || result.routes.workers.length > 0 || result.routes.crons.length > 0 ? [
88
+ ...result.routes.rewrites,
89
+ ...result.routes.workers,
90
+ ...result.routes.crons
91
+ ] : null,
88
92
  errorRoutes: [],
89
93
  services: result.services
90
94
  };
@@ -126,12 +126,14 @@ async function inferWorkspaceFromNearestManifest({
126
126
  async function detectFrameworkFromWorkspace({
127
127
  fs,
128
128
  workspace,
129
- serviceName
129
+ serviceName,
130
+ runtime
130
131
  }) {
131
132
  const serviceFs = workspace === "." ? fs : fs.chdir(workspace);
133
+ const frameworkCandidates = (0, import_utils.filterFrameworksByRuntime)(import_frameworks.default, runtime);
132
134
  const frameworks = await (0, import_detect_framework.detectFrameworks)({
133
135
  fs: serviceFs,
134
- frameworkList: import_frameworks.default
136
+ frameworkList: frameworkCandidates
135
137
  });
136
138
  if (frameworks.length > 1) {
137
139
  const frameworkNames = frameworks.map((f) => f.name).join(", ");
@@ -212,6 +214,16 @@ function validateServiceConfig(name, config) {
212
214
  serviceName: name
213
215
  };
214
216
  }
217
+ if (config.runtime && config.framework) {
218
+ const frameworkRuntime = (0, import_utils.inferRuntimeFromFramework)(config.framework);
219
+ if (frameworkRuntime && frameworkRuntime !== config.runtime) {
220
+ return {
221
+ code: "RUNTIME_FRAMEWORK_MISMATCH",
222
+ message: `Service "${name}" has conflicting runtime/framework: runtime "${config.runtime}" is incompatible with framework "${config.framework}" (runtime "${frameworkRuntime}").`,
223
+ serviceName: name
224
+ };
225
+ }
226
+ }
215
227
  const hasFramework = Boolean(config.framework);
216
228
  const hasBuilderOrRuntime = Boolean(config.builder || config.runtime);
217
229
  const hasEntrypoint = Boolean(config.entrypoint);
@@ -395,30 +407,56 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
395
407
  }
396
408
  }
397
409
  let resolvedConfig = serviceConfig;
398
- const shouldDetectFramework = !serviceConfig.framework && Boolean(resolvedEntrypoint?.isDirectory);
399
- if (shouldDetectFramework) {
400
- const workspace = resolvedEntrypoint.normalized;
401
- const { framework, error } = await detectFrameworkFromWorkspace({
402
- fs,
403
- workspace,
404
- serviceName: name
405
- });
406
- if (error) {
407
- errors.push(error);
408
- continue;
409
- }
410
- if (!framework) {
411
- errors.push({
412
- code: "MISSING_SERVICE_FRAMEWORK",
413
- message: `Service "${name}" uses directory entrypoint "${serviceConfig.entrypoint}" but no framework could be detected in "${workspace}". Specify "framework" explicitly or use a file entrypoint.`,
410
+ if (!serviceConfig.framework && resolvedEntrypoint) {
411
+ if (resolvedEntrypoint.isDirectory) {
412
+ const workspace = resolvedEntrypoint.normalized;
413
+ const { framework, error } = await detectFrameworkFromWorkspace({
414
+ fs,
415
+ workspace,
414
416
  serviceName: name
415
417
  });
416
- continue;
418
+ if (error) {
419
+ errors.push(error);
420
+ continue;
421
+ }
422
+ if (!framework) {
423
+ errors.push({
424
+ code: "MISSING_SERVICE_FRAMEWORK",
425
+ message: `Service "${name}" uses directory entrypoint "${serviceConfig.entrypoint}" but no framework could be detected in "${workspace}". Specify "framework" explicitly or use a file entrypoint.`,
426
+ serviceName: name
427
+ });
428
+ continue;
429
+ }
430
+ resolvedConfig = {
431
+ ...resolvedConfig,
432
+ framework
433
+ };
434
+ } else {
435
+ const inferredRuntime = (0, import_utils.inferServiceRuntime)({
436
+ ...serviceConfig,
437
+ entrypoint: resolvedEntrypoint.normalized
438
+ });
439
+ if (inferredRuntime) {
440
+ const inferredWorkspace = await inferWorkspaceFromNearestManifest({
441
+ fs,
442
+ entrypoint: resolvedEntrypoint.normalized,
443
+ runtime: inferredRuntime
444
+ });
445
+ const workspace = inferredWorkspace ?? import_path.posix.dirname(resolvedEntrypoint.normalized);
446
+ const detection = await detectFrameworkFromWorkspace({
447
+ fs,
448
+ workspace,
449
+ serviceName: name,
450
+ runtime: inferredRuntime
451
+ });
452
+ if (detection.framework) {
453
+ resolvedConfig = {
454
+ ...resolvedConfig,
455
+ framework: detection.framework
456
+ };
457
+ }
458
+ }
417
459
  }
418
- resolvedConfig = {
419
- ...serviceConfig,
420
- framework
421
- };
422
460
  }
423
461
  const service = await resolveConfiguredService({
424
462
  name,
@@ -28,7 +28,6 @@ export interface ServicesRoutes {
28
28
  /**
29
29
  * Internal routes for worker services.
30
30
  * These route `/_svc/{serviceName}/workers/{entry}/{handler}` to the worker function.
31
- * TODO: Implement
32
31
  */
33
32
  workers: Route[];
34
33
  }
@@ -6,6 +6,9 @@ export declare function hasFile(fs: DetectorFilesystem, filePath: string): Promi
6
6
  */
7
7
  export declare const INTERNAL_SERVICE_PREFIX = "/_svc";
8
8
  export declare function getInternalServiceFunctionPath(serviceName: string): string;
9
+ export declare function getInternalServiceWorkerPathPrefix(serviceName: string): string;
10
+ export declare function getInternalServiceCronPathPrefix(serviceName: string): string;
11
+ export declare function getInternalServiceWorkerPath(serviceName: string, entrypoint: string, handler?: string): string;
9
12
  export declare function getBuilderForRuntime(runtime: ServiceRuntime): string;
10
13
  export declare function isStaticBuild(service: ResolvedService): boolean;
11
14
  /**
@@ -17,6 +20,18 @@ export declare function isStaticBuild(service: ResolvedService): boolean;
17
20
  * rewrites for them — instead, we rely on the builder's own `routes[]`.
18
21
  */
19
22
  export declare function isRouteOwningBuilder(service: ResolvedService): boolean;
23
+ /**
24
+ * Infer runtime from a framework slug.
25
+ *
26
+ * Examples:
27
+ * - `python` -> `python`
28
+ * - `fastapi` -> `python`
29
+ * - `express` -> `node`
30
+ */
31
+ export declare function inferRuntimeFromFramework(framework: string | null | undefined): ServiceRuntime | undefined;
32
+ export declare function filterFrameworksByRuntime<T extends {
33
+ slug?: string | null;
34
+ }>(frameworks: readonly T[], runtime?: ServiceRuntime): T[];
20
35
  /**
21
36
  * Infer runtime from available service configuration.
22
37
  *
@@ -19,9 +19,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var utils_exports = {};
20
20
  __export(utils_exports, {
21
21
  INTERNAL_SERVICE_PREFIX: () => INTERNAL_SERVICE_PREFIX,
22
+ filterFrameworksByRuntime: () => filterFrameworksByRuntime,
22
23
  getBuilderForRuntime: () => getBuilderForRuntime,
24
+ getInternalServiceCronPathPrefix: () => getInternalServiceCronPathPrefix,
23
25
  getInternalServiceFunctionPath: () => getInternalServiceFunctionPath,
26
+ getInternalServiceWorkerPath: () => getInternalServiceWorkerPath,
27
+ getInternalServiceWorkerPathPrefix: () => getInternalServiceWorkerPathPrefix,
24
28
  hasFile: () => hasFile,
29
+ inferRuntimeFromFramework: () => inferRuntimeFromFramework,
25
30
  inferServiceRuntime: () => inferServiceRuntime,
26
31
  isRouteOwningBuilder: () => isRouteOwningBuilder,
27
32
  isStaticBuild: () => isStaticBuild,
@@ -41,6 +46,20 @@ const INTERNAL_SERVICE_PREFIX = "/_svc";
41
46
  function getInternalServiceFunctionPath(serviceName) {
42
47
  return `${INTERNAL_SERVICE_PREFIX}/${serviceName}/index`;
43
48
  }
49
+ function normalizeInternalServiceEntrypoint(entrypoint) {
50
+ const normalized = entrypoint.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\.[^/.]+$/, "");
51
+ return normalized || "index";
52
+ }
53
+ function getInternalServiceWorkerPathPrefix(serviceName) {
54
+ return `${INTERNAL_SERVICE_PREFIX}/${serviceName}/workers`;
55
+ }
56
+ function getInternalServiceCronPathPrefix(serviceName) {
57
+ return `${INTERNAL_SERVICE_PREFIX}/${serviceName}/crons`;
58
+ }
59
+ function getInternalServiceWorkerPath(serviceName, entrypoint, handler = "worker") {
60
+ const normalizedEntrypoint = normalizeInternalServiceEntrypoint(entrypoint);
61
+ return `${getInternalServiceWorkerPathPrefix(serviceName)}/${normalizedEntrypoint}/${handler}`;
62
+ }
44
63
  function getBuilderForRuntime(runtime) {
45
64
  const builder = import_types.RUNTIME_BUILDERS[runtime];
46
65
  if (!builder) {
@@ -54,19 +73,37 @@ function isStaticBuild(service) {
54
73
  function isRouteOwningBuilder(service) {
55
74
  return import_types.ROUTE_OWNING_BUILDERS.has(service.builder.use);
56
75
  }
57
- function inferServiceRuntime(config) {
58
- if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
59
- return config.runtime;
76
+ function inferRuntimeFromFramework(framework) {
77
+ if (!framework) {
78
+ return void 0;
60
79
  }
61
- if (config.framework && config.framework in import_types.RUNTIME_BUILDERS) {
62
- return config.framework;
80
+ if (framework in import_types.RUNTIME_BUILDERS) {
81
+ return framework;
63
82
  }
64
- if ((0, import_framework_helpers.isPythonFramework)(config.framework)) {
83
+ if ((0, import_framework_helpers.isPythonFramework)(framework)) {
65
84
  return "python";
66
85
  }
67
- if ((0, import_framework_helpers.isBackendFramework)(config.framework)) {
86
+ if ((0, import_framework_helpers.isBackendFramework)(framework)) {
68
87
  return "node";
69
88
  }
89
+ return void 0;
90
+ }
91
+ function filterFrameworksByRuntime(frameworks, runtime) {
92
+ if (!runtime) {
93
+ return [...frameworks];
94
+ }
95
+ return frameworks.filter(
96
+ (framework) => inferRuntimeFromFramework(framework.slug) === runtime
97
+ );
98
+ }
99
+ function inferServiceRuntime(config) {
100
+ if (config.runtime && config.runtime in import_types.RUNTIME_BUILDERS) {
101
+ return config.runtime;
102
+ }
103
+ const frameworkRuntime = inferRuntimeFromFramework(config.framework);
104
+ if (frameworkRuntime) {
105
+ return frameworkRuntime;
106
+ }
70
107
  if (config.builder) {
71
108
  for (const [runtime, builderName] of Object.entries(import_types.RUNTIME_BUILDERS)) {
72
109
  if (config.builder === builderName) {
@@ -105,9 +142,14 @@ async function readVercelConfig(fs) {
105
142
  // Annotate the CommonJS export names for ESM import in node:
106
143
  0 && (module.exports = {
107
144
  INTERNAL_SERVICE_PREFIX,
145
+ filterFrameworksByRuntime,
108
146
  getBuilderForRuntime,
147
+ getInternalServiceCronPathPrefix,
109
148
  getInternalServiceFunctionPath,
149
+ getInternalServiceWorkerPath,
150
+ getInternalServiceWorkerPathPrefix,
110
151
  hasFile,
152
+ inferRuntimeFromFramework,
111
153
  inferServiceRuntime,
112
154
  isRouteOwningBuilder,
113
155
  isStaticBuild,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.8.15",
3
+ "version": "5.8.17",
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/routing-utils": "6.0.2",
22
23
  "@vercel/error-utils": "2.0.3",
23
- "@vercel/frameworks": "3.19.1",
24
- "@vercel/routing-utils": "6.0.1"
24
+ "@vercel/frameworks": "3.20.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/glob": "7.2.0",