@vercel/fs-detectors 5.8.6 → 5.8.7

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.
@@ -51,7 +51,10 @@ async function detectServices(options) {
51
51
  };
52
52
  }
53
53
  if (autoResult.services) {
54
- const result2 = (0, import_resolve.resolveAllConfiguredServices)(autoResult.services);
54
+ const result2 = await (0, import_resolve.resolveAllConfiguredServices)(
55
+ autoResult.services,
56
+ scopedFs
57
+ );
55
58
  const routes2 = generateServicesRoutes(result2.services);
56
59
  return {
57
60
  services: result2.services,
@@ -72,7 +75,10 @@ async function detectServices(options) {
72
75
  warnings: []
73
76
  };
74
77
  }
75
- const result = (0, import_resolve.resolveAllConfiguredServices)(configuredServices);
78
+ const result = await (0, import_resolve.resolveAllConfiguredServices)(
79
+ configuredServices,
80
+ scopedFs
81
+ );
76
82
  const routes = generateServicesRoutes(result.services);
77
83
  return {
78
84
  services: result.services,
@@ -1,4 +1,5 @@
1
- import type { ResolvedService, ExperimentalServiceConfig, ExperimentalServices, ServiceDetectionError } from './types';
1
+ import type { Service, ExperimentalServiceConfig, ExperimentalServices, ServiceDetectionError } from './types';
2
+ import type { DetectorFilesystem } from '../detectors/filesystem';
2
3
  /**
3
4
  * Validate a service configuration from vercel.json experimentalServices.
4
5
  */
@@ -6,12 +7,12 @@ export declare function validateServiceConfig(name: string, config: Experimental
6
7
  /**
7
8
  * Resolve a single service from user configuration.
8
9
  */
9
- export declare function resolveConfiguredService(name: string, config: ExperimentalServiceConfig, group?: string): ResolvedService;
10
+ export declare function resolveConfiguredService(name: string, config: ExperimentalServiceConfig, fs: DetectorFilesystem, group?: string): Promise<Service>;
10
11
  /**
11
12
  * Resolve all services from vercel.json experimentalServices.
12
13
  * Validates each service configuration.
13
14
  */
14
- export declare function resolveAllConfiguredServices(services: ExperimentalServices): {
15
- services: ResolvedService[];
15
+ export declare function resolveAllConfiguredServices(services: ExperimentalServices, fs: DetectorFilesystem): Promise<{
16
+ services: Service[];
16
17
  errors: ServiceDetectionError[];
17
- };
18
+ }>;
@@ -40,6 +40,61 @@ var import_frameworks = __toESM(require("@vercel/frameworks"));
40
40
  var import_routing_utils = require("@vercel/routing-utils");
41
41
  const frameworksBySlug = new Map(import_frameworks.default.map((f) => [f.slug, f]));
42
42
  const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
43
+ function toWorkspaceRelativeEntrypoint(entrypoint, workspace) {
44
+ const normalizedEntrypoint = import_path.posix.normalize(entrypoint);
45
+ if (workspace === ".") {
46
+ return normalizedEntrypoint;
47
+ }
48
+ const workspacePrefix = `${workspace}/`;
49
+ if (normalizedEntrypoint.startsWith(workspacePrefix)) {
50
+ return normalizedEntrypoint.slice(workspacePrefix.length);
51
+ }
52
+ const relativeEntrypoint = import_path.posix.relative(
53
+ workspace,
54
+ normalizedEntrypoint
55
+ );
56
+ if (relativeEntrypoint === "" || relativeEntrypoint.startsWith("..")) {
57
+ return normalizedEntrypoint;
58
+ }
59
+ return relativeEntrypoint;
60
+ }
61
+ async function inferWorkspaceFromNearestManifest({
62
+ fs,
63
+ entrypoint,
64
+ runtime
65
+ }) {
66
+ if (!entrypoint || !runtime) {
67
+ return void 0;
68
+ }
69
+ const manifests = import_types.RUNTIME_MANIFESTS[runtime];
70
+ if (!manifests || manifests.length === 0) {
71
+ return void 0;
72
+ }
73
+ let dir = import_path.posix.dirname(import_path.posix.normalize(entrypoint)) || ".";
74
+ if (dir === "") {
75
+ dir = ".";
76
+ }
77
+ let reachedRoot = false;
78
+ while (!reachedRoot) {
79
+ for (const manifest of manifests) {
80
+ const manifestPath = dir === "." ? manifest : import_path.posix.join(dir, manifest);
81
+ if (await (0, import_utils.hasFile)(fs, manifestPath)) {
82
+ return dir;
83
+ }
84
+ }
85
+ if (dir === "." || dir === "/") {
86
+ reachedRoot = true;
87
+ } else {
88
+ const parent = import_path.posix.dirname(dir);
89
+ if (!parent || parent === dir) {
90
+ reachedRoot = true;
91
+ } else {
92
+ dir = parent;
93
+ }
94
+ }
95
+ }
96
+ return void 0;
97
+ }
43
98
  function isReservedServiceRoutePrefix(routePrefix) {
44
99
  const normalized = (0, import_routing_utils.normalizeRoutePrefix)(routePrefix);
45
100
  return normalized === import_utils.INTERNAL_SERVICE_PREFIX || normalized.startsWith(`${import_utils.INTERNAL_SERVICE_PREFIX}/`);
@@ -132,24 +187,41 @@ function validateServiceConfig(name, config) {
132
187
  }
133
188
  return null;
134
189
  }
135
- function resolveConfiguredService(name, config, group) {
190
+ async function resolveConfiguredService(name, config, fs, group) {
136
191
  const type = config.type || "web";
137
- const workspace = config.workspace || ".";
192
+ const inferredRuntime = (0, import_utils.inferServiceRuntime)(config);
193
+ let workspace = config.workspace || ".";
194
+ let resolvedEntrypoint = config.entrypoint;
195
+ if (!config.workspace) {
196
+ const inferredWorkspace = await inferWorkspaceFromNearestManifest({
197
+ fs,
198
+ entrypoint: resolvedEntrypoint,
199
+ runtime: inferredRuntime
200
+ });
201
+ if (inferredWorkspace) {
202
+ workspace = inferredWorkspace;
203
+ if (resolvedEntrypoint) {
204
+ resolvedEntrypoint = toWorkspaceRelativeEntrypoint(
205
+ resolvedEntrypoint,
206
+ inferredWorkspace
207
+ );
208
+ }
209
+ }
210
+ }
138
211
  const topic = type === "worker" ? config.topic || "default" : config.topic;
139
212
  const consumer = type === "worker" ? config.consumer || "default" : config.consumer;
140
- const inferredRuntime = (0, import_utils.inferServiceRuntime)(config);
141
213
  let builderUse;
142
214
  let builderSrc;
143
215
  if (config.framework) {
144
216
  const framework = frameworksBySlug.get(config.framework);
145
217
  builderUse = framework?.useRuntime?.use || "@vercel/static-build";
146
- builderSrc = config.entrypoint || framework?.useRuntime?.src || "package.json";
218
+ builderSrc = resolvedEntrypoint || framework?.useRuntime?.src || "package.json";
147
219
  } else if (config.builder) {
148
220
  builderUse = config.builder;
149
- builderSrc = config.entrypoint;
221
+ builderSrc = resolvedEntrypoint;
150
222
  } else {
151
223
  builderUse = (0, import_utils.getBuilderForRuntime)(inferredRuntime);
152
- builderSrc = config.entrypoint;
224
+ builderSrc = resolvedEntrypoint;
153
225
  }
154
226
  const routePrefix = type === "web" && config.routePrefix ? config.routePrefix.startsWith("/") ? config.routePrefix : `/${config.routePrefix}` : void 0;
155
227
  const isRoot = workspace === ".";
@@ -182,7 +254,7 @@ function resolveConfiguredService(name, config, group) {
182
254
  type,
183
255
  group,
184
256
  workspace,
185
- entrypoint: config.entrypoint,
257
+ entrypoint: resolvedEntrypoint,
186
258
  routePrefix,
187
259
  framework: config.framework,
188
260
  builder: {
@@ -198,7 +270,7 @@ function resolveConfiguredService(name, config, group) {
198
270
  consumer
199
271
  };
200
272
  }
201
- function resolveAllConfiguredServices(services) {
273
+ async function resolveAllConfiguredServices(services, fs) {
202
274
  const resolved = [];
203
275
  const errors = [];
204
276
  const webServicesByRoutePrefix = /* @__PURE__ */ new Map();
@@ -209,7 +281,7 @@ function resolveAllConfiguredServices(services) {
209
281
  errors.push(validationError);
210
282
  continue;
211
283
  }
212
- const service = resolveConfiguredService(name, serviceConfig);
284
+ const service = await resolveConfiguredService(name, serviceConfig, fs);
213
285
  if (service.type === "web" && typeof service.routePrefix === "string") {
214
286
  const normalizedRoutePrefix = (0, import_routing_utils.normalizeRoutePrefix)(service.routePrefix);
215
287
  const existingServiceName = webServicesByRoutePrefix.get(
@@ -33,7 +33,7 @@ export interface ServicesRoutes {
33
33
  workers: Route[];
34
34
  }
35
35
  export interface DetectServicesResult {
36
- services: ResolvedService[];
36
+ services: Service[];
37
37
  /** Routing rules derived from services */
38
38
  routes: ServicesRoutes;
39
39
  errors: ServiceDetectionError[];
@@ -50,6 +50,7 @@ export interface ServiceDetectionError {
50
50
  serviceName?: string;
51
51
  }
52
52
  export declare const RUNTIME_BUILDERS: Record<ServiceRuntime, string>;
53
+ export declare const RUNTIME_MANIFESTS: Partial<Record<ServiceRuntime, string[]>>;
53
54
  export declare const ENTRYPOINT_EXTENSIONS: Record<string, ServiceRuntime>;
54
55
  /**
55
56
  * Builders that produce static output (SPAs, static sites).
@@ -21,6 +21,7 @@ __export(types_exports, {
21
21
  ENTRYPOINT_EXTENSIONS: () => ENTRYPOINT_EXTENSIONS,
22
22
  ROUTE_OWNING_BUILDERS: () => ROUTE_OWNING_BUILDERS,
23
23
  RUNTIME_BUILDERS: () => RUNTIME_BUILDERS,
24
+ RUNTIME_MANIFESTS: () => RUNTIME_MANIFESTS,
24
25
  STATIC_BUILDERS: () => STATIC_BUILDERS
25
26
  });
26
27
  module.exports = __toCommonJS(types_exports);
@@ -31,6 +32,20 @@ const RUNTIME_BUILDERS = {
31
32
  rust: "@vercel/rust",
32
33
  ruby: "@vercel/ruby"
33
34
  };
35
+ const RUNTIME_MANIFESTS = {
36
+ node: ["package.json"],
37
+ python: [
38
+ "pyproject.toml",
39
+ "requirements.txt",
40
+ "Pipfile",
41
+ "pylock.yml",
42
+ "uv.lock",
43
+ "setup.py"
44
+ ],
45
+ go: ["go.mod"],
46
+ ruby: ["Gemfile"],
47
+ rust: ["Cargo.toml"]
48
+ };
34
49
  const ENTRYPOINT_EXTENSIONS = {
35
50
  ".ts": "node",
36
51
  ".mts": "node",
@@ -56,5 +71,6 @@ const ROUTE_OWNING_BUILDERS = /* @__PURE__ */ new Set([
56
71
  ENTRYPOINT_EXTENSIONS,
57
72
  ROUTE_OWNING_BUILDERS,
58
73
  RUNTIME_BUILDERS,
74
+ RUNTIME_MANIFESTS,
59
75
  STATIC_BUILDERS
60
76
  });
@@ -1,5 +1,6 @@
1
1
  import type { DetectorFilesystem } from '../detectors/filesystem';
2
2
  import type { ServiceRuntime, ExperimentalServices, ServiceDetectionError, ResolvedService } from './types';
3
+ export declare function hasFile(fs: DetectorFilesystem, filePath: string): Promise<boolean>;
3
4
  /**
4
5
  * Reserved internal namespace used by services routing/runtime plumbing.
5
6
  */
@@ -21,6 +21,7 @@ __export(utils_exports, {
21
21
  INTERNAL_SERVICE_PREFIX: () => INTERNAL_SERVICE_PREFIX,
22
22
  getBuilderForRuntime: () => getBuilderForRuntime,
23
23
  getInternalServiceFunctionPath: () => getInternalServiceFunctionPath,
24
+ hasFile: () => hasFile,
24
25
  inferServiceRuntime: () => inferServiceRuntime,
25
26
  isRouteOwningBuilder: () => isRouteOwningBuilder,
26
27
  isStaticBuild: () => isStaticBuild,
@@ -29,6 +30,13 @@ __export(utils_exports, {
29
30
  module.exports = __toCommonJS(utils_exports);
30
31
  var import_framework_helpers = require("@vercel/build-utils/dist/framework-helpers");
31
32
  var import_types = require("./types");
33
+ async function hasFile(fs, filePath) {
34
+ try {
35
+ return await fs.isFile(filePath);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
32
40
  const INTERNAL_SERVICE_PREFIX = "/_svc";
33
41
  function getInternalServiceFunctionPath(serviceName) {
34
42
  return `${INTERNAL_SERVICE_PREFIX}/${serviceName}/index`;
@@ -96,6 +104,7 @@ async function readVercelConfig(fs) {
96
104
  INTERNAL_SERVICE_PREFIX,
97
105
  getBuilderForRuntime,
98
106
  getInternalServiceFunctionPath,
107
+ hasFile,
99
108
  inferServiceRuntime,
100
109
  isRouteOwningBuilder,
101
110
  isStaticBuild,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.8.6",
3
+ "version": "5.8.7",
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": "5.3.3",
22
+ "@vercel/error-utils": "2.0.3",
23
23
  "@vercel/frameworks": "3.17.1",
24
- "@vercel/error-utils": "2.0.3"
24
+ "@vercel/routing-utils": "5.3.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/glob": "7.2.0",