@vercel/fs-detectors 5.16.0 → 5.17.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.
@@ -311,7 +311,7 @@ async function maybeGetApiBuilder(fileName, apiMatches, options) {
311
311
  }
312
312
  }
313
313
  const nodeExtensions = [".js", ".mjs", ".ts", ".tsx"];
314
- if (process.env.VERCEL_NODE_FILTER_ENTRYPOINTS === "1" && nodeExtensions.some((ext) => fileName.endsWith(ext)) && options.workPath) {
314
+ if (fileName.startsWith("api/") && process.env.VERCEL_NODE_FILTER_ENTRYPOINTS === "1" && nodeExtensions.some((ext) => fileName.endsWith(ext)) && options.workPath) {
315
315
  const fsPath = (0, import_path.join)(options.workPath, fileName);
316
316
  const isEntrypoint = await (0, import_build_utils.isNodeEntrypoint)({ fsPath });
317
317
  if (!isEntrypoint) {
@@ -8,7 +8,9 @@ type RoutePrefixSource = 'configured' | 'generated';
8
8
  interface ResolveConfiguredServiceOptions {
9
9
  name: string;
10
10
  config: ExperimentalServiceConfig;
11
- fs: DetectorFilesystem;
11
+ /** Filesystem scoped to the service root (via chdir) when root is set, otherwise the project-level fs. */
12
+ serviceFs: DetectorFilesystem;
13
+ root?: string;
12
14
  group?: string;
13
15
  resolvedEntrypoint?: ResolvedEntrypointPath;
14
16
  routePrefixSource?: RoutePrefixSource;
@@ -46,6 +46,33 @@ function parsePyModuleAttrEntrypoint(entrypoint) {
46
46
  const SERVICE_NAME_REGEX = /^[a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
47
47
  const DNS_LABEL_RE = /^(?!-)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
48
48
  const ENV_PREFIX_RE = /^[A-Z][A-Z0-9_]*_$/;
49
+ async function getServiceFs(fs, serviceName, root) {
50
+ if (!root) {
51
+ return { fs };
52
+ }
53
+ const normalizedRoot = import_path.posix.normalize(root);
54
+ if (!await fs.hasPath(normalizedRoot)) {
55
+ return {
56
+ fs,
57
+ error: {
58
+ code: "ROOT_NOT_FOUND",
59
+ message: `Service "${serviceName}" has root "${root}" but that directory does not exist.`,
60
+ serviceName
61
+ }
62
+ };
63
+ }
64
+ if (await fs.isFile(normalizedRoot)) {
65
+ return {
66
+ fs,
67
+ error: {
68
+ code: "ROOT_NOT_DIRECTORY",
69
+ message: `Service "${serviceName}" has root "${root}" but that path is a file, not a directory.`,
70
+ serviceName
71
+ }
72
+ };
73
+ }
74
+ return { fs: fs.chdir(normalizedRoot) };
75
+ }
49
76
  function normalizeServiceEntrypoint(entrypoint) {
50
77
  const normalized = import_path.posix.normalize(entrypoint);
51
78
  return normalized === "" ? "." : normalized;
@@ -304,6 +331,23 @@ function validateServiceConfig(name, config) {
304
331
  serviceName: name
305
332
  };
306
333
  }
334
+ if (config.root !== void 0) {
335
+ const normalizedRoot = import_path.posix.normalize(config.root);
336
+ if (normalizedRoot.startsWith("/")) {
337
+ return {
338
+ code: "INVALID_ROOT",
339
+ message: `Service "${name}" has invalid "root" "${config.root}". Must be a relative path.`,
340
+ serviceName: name
341
+ };
342
+ }
343
+ if (normalizedRoot === ".." || normalizedRoot.startsWith("../")) {
344
+ return {
345
+ code: "INVALID_ROOT",
346
+ message: `Service "${name}" has invalid "root" "${config.root}". Must not escape the project root.`,
347
+ serviceName: name
348
+ };
349
+ }
350
+ }
307
351
  if (config.envPrefix !== void 0) {
308
352
  if (!ENV_PREFIX_RE.test(config.envPrefix)) {
309
353
  return {
@@ -377,7 +421,8 @@ async function resolveConfiguredService(options) {
377
421
  const {
378
422
  name,
379
423
  config,
380
- fs,
424
+ serviceFs,
425
+ root,
381
426
  group,
382
427
  resolvedEntrypoint,
383
428
  routePrefixSource = "configured"
@@ -396,7 +441,7 @@ async function resolveConfiguredService(options) {
396
441
  if (!resolvedEntrypointPath && typeof rawEntrypoint === "string") {
397
442
  const entrypointToResolve = moduleAttrParsed ? moduleAttrParsed.filePath : rawEntrypoint;
398
443
  const resolved = await resolveEntrypointPath({
399
- fs,
444
+ fs: serviceFs,
400
445
  serviceName: name,
401
446
  entrypoint: entrypointToResolve
402
447
  });
@@ -419,7 +464,7 @@ async function resolveConfiguredService(options) {
419
464
  workspace = normalizedEntrypoint;
420
465
  } else {
421
466
  const inferredWorkspace = await inferWorkspaceFromNearestManifest({
422
- fs,
467
+ fs: serviceFs,
423
468
  entrypoint: resolvedEntrypointFile,
424
469
  runtime: inferredRuntime
425
470
  });
@@ -433,6 +478,12 @@ async function resolveConfiguredService(options) {
433
478
  }
434
479
  }
435
480
  }
481
+ if (root) {
482
+ const normalizedRoot = import_path.posix.normalize(root);
483
+ if (normalizedRoot !== ".") {
484
+ workspace = workspace === "." ? normalizedRoot : import_path.posix.join(normalizedRoot, workspace);
485
+ }
486
+ }
436
487
  const topics = type === "worker" ? (0, import_build_utils.getWorkerTopics)(config) : config.topics;
437
488
  const consumer = type === "worker" ? config.consumer || "default" : config.consumer;
438
489
  let builderUse;
@@ -532,12 +583,19 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
532
583
  errors.push(validationError);
533
584
  continue;
534
585
  }
586
+ const root = serviceConfig.root;
587
+ const serviceFsResult = await getServiceFs(fs, name, root);
588
+ if (serviceFsResult.error) {
589
+ errors.push(serviceFsResult.error);
590
+ continue;
591
+ }
592
+ const serviceFs = serviceFsResult.fs;
535
593
  let resolvedEntrypoint;
536
594
  if (typeof serviceConfig.entrypoint === "string") {
537
595
  const moduleAttr = parsePyModuleAttrEntrypoint(serviceConfig.entrypoint);
538
596
  const entrypointToResolve = moduleAttr?.filePath ?? serviceConfig.entrypoint;
539
597
  const resolvedPath = await resolveEntrypointPath({
540
- fs,
598
+ fs: serviceFs,
541
599
  serviceName: name,
542
600
  entrypoint: entrypointToResolve
543
601
  });
@@ -566,7 +624,7 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
566
624
  });
567
625
  const workspace = resolvedEntrypoint.normalized;
568
626
  const { framework, error } = await detectFrameworkFromWorkspace({
569
- fs,
627
+ fs: serviceFs,
570
628
  workspace,
571
629
  runtime: inferredRuntime,
572
630
  serviceName: name
@@ -594,13 +652,13 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
594
652
  });
595
653
  if (inferredRuntime) {
596
654
  const inferredWorkspace = await inferWorkspaceFromNearestManifest({
597
- fs,
655
+ fs: serviceFs,
598
656
  entrypoint: resolvedEntrypoint.normalized,
599
657
  runtime: inferredRuntime
600
658
  });
601
659
  const workspace = inferredWorkspace ?? import_path.posix.dirname(resolvedEntrypoint.normalized);
602
660
  const detection = await detectFrameworkFromWorkspace({
603
- fs,
661
+ fs: serviceFs,
604
662
  workspace,
605
663
  serviceName: name,
606
664
  runtime: inferredRuntime
@@ -617,7 +675,8 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
617
675
  const service = await resolveConfiguredService({
618
676
  name,
619
677
  config: resolvedConfig,
620
- fs,
678
+ serviceFs,
679
+ root,
621
680
  resolvedEntrypoint,
622
681
  routePrefixSource
623
682
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.16.0",
3
+ "version": "5.17.0",
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.15.0",
23
+ "@vercel/build-utils": "13.16.0",
24
+ "@vercel/routing-utils": "6.1.1",
24
25
  "@vercel/error-utils": "2.0.3",
25
- "@vercel/frameworks": "3.24.0",
26
- "@vercel/routing-utils": "6.1.1"
26
+ "@vercel/frameworks": "3.24.1"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/glob": "7.2.0",