@vercel/fs-detectors 5.15.2 → 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,14 +421,15 @@ 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"
384
429
  } = options;
385
430
  const type = config.type || "web";
386
431
  const rawEntrypoint = config.entrypoint;
387
- const moduleAttrParsed = typeof rawEntrypoint === "string" && type === "cron" ? parsePyModuleAttrEntrypoint(rawEntrypoint) : null;
432
+ const moduleAttrParsed = typeof rawEntrypoint === "string" ? parsePyModuleAttrEntrypoint(rawEntrypoint) : null;
388
433
  const routingResult = resolveServiceRoutingConfig(name, config);
389
434
  if (routingResult.error) {
390
435
  throw new Error(routingResult.error.message);
@@ -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,13 +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
- const serviceType = serviceConfig.type || "web";
537
594
  if (typeof serviceConfig.entrypoint === "string") {
538
- const moduleAttr = serviceType === "cron" ? parsePyModuleAttrEntrypoint(serviceConfig.entrypoint) : null;
539
- const entrypointToResolve = moduleAttr ? moduleAttr.filePath : serviceConfig.entrypoint;
595
+ const moduleAttr = parsePyModuleAttrEntrypoint(serviceConfig.entrypoint);
596
+ const entrypointToResolve = moduleAttr?.filePath ?? serviceConfig.entrypoint;
540
597
  const resolvedPath = await resolveEntrypointPath({
541
- fs,
598
+ fs: serviceFs,
542
599
  serviceName: name,
543
600
  entrypoint: entrypointToResolve
544
601
  });
@@ -567,7 +624,7 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
567
624
  });
568
625
  const workspace = resolvedEntrypoint.normalized;
569
626
  const { framework, error } = await detectFrameworkFromWorkspace({
570
- fs,
627
+ fs: serviceFs,
571
628
  workspace,
572
629
  runtime: inferredRuntime,
573
630
  serviceName: name
@@ -595,13 +652,13 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
595
652
  });
596
653
  if (inferredRuntime) {
597
654
  const inferredWorkspace = await inferWorkspaceFromNearestManifest({
598
- fs,
655
+ fs: serviceFs,
599
656
  entrypoint: resolvedEntrypoint.normalized,
600
657
  runtime: inferredRuntime
601
658
  });
602
659
  const workspace = inferredWorkspace ?? import_path.posix.dirname(resolvedEntrypoint.normalized);
603
660
  const detection = await detectFrameworkFromWorkspace({
604
- fs,
661
+ fs: serviceFs,
605
662
  workspace,
606
663
  serviceName: name,
607
664
  runtime: inferredRuntime
@@ -618,7 +675,8 @@ async function resolveAllConfiguredServices(services, fs, routePrefixSource = "c
618
675
  const service = await resolveConfiguredService({
619
676
  name,
620
677
  config: resolvedConfig,
621
- fs,
678
+ serviceFs,
679
+ root,
622
680
  resolvedEntrypoint,
623
681
  routePrefixSource
624
682
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/fs-detectors",
3
- "version": "5.15.2",
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/frameworks": "3.24.0",
24
- "@vercel/build-utils": "13.14.2",
23
+ "@vercel/build-utils": "13.16.0",
25
24
  "@vercel/routing-utils": "6.1.1",
26
- "@vercel/error-utils": "2.0.3"
25
+ "@vercel/error-utils": "2.0.3",
26
+ "@vercel/frameworks": "3.24.1"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/glob": "7.2.0",