aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/turbo-utils/src/getTurboConfigs.ts
blob: df15a566c325f5fa48b1d8d712cfbf2fbb3e9241 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import fs from "fs";
import path from "path";
import getTurboRoot from "./getTurboRoot";
import yaml from "js-yaml";
import { sync } from "fast-glob";
import { Schema } from "@turbo/types";
import JSON5 from "json5";

const ROOT_GLOB = "turbo.json";

export type TurboConfigs = Array<{
  config: Schema;
  turboConfigPath: string;
  workspacePath: string;
  isRootConfig: boolean;
}>;

interface Options {
  cache?: boolean;
}

const configsCache: Record<string, TurboConfigs> = {};

// A quick and dirty workspace parser
// TODO: after @turbo/workspace-convert is merged, we can leverage those utils here
function getWorkspaceGlobs(root: string): Array<string> {
  try {
    if (fs.existsSync(path.join(root, "pnpm-workspace.yaml"))) {
      const workspaceConfig = yaml.load(
        fs.readFileSync(path.join(root, "pnpm-workspace.yaml"), "utf8")
      ) as Record<"packages", Array<string>>;

      return workspaceConfig?.packages || [];
    } else {
      const packageJson = JSON.parse(
        fs.readFileSync(path.join(root, "package.json"), "utf8")
      );
      return packageJson?.workspaces || [];
    }
  } catch (e) {
    return [];
  }
}

function getTurboConfigs(cwd?: string, opts?: Options): TurboConfigs {
  const turboRoot = getTurboRoot(cwd, opts);
  const configs: TurboConfigs = [];

  const cacheEnabled = opts?.cache ?? true;
  if (cacheEnabled && cwd && configsCache[cwd]) {
    return configsCache[cwd];
  }

  // parse workspaces
  if (turboRoot) {
    const workspaceGlobs = getWorkspaceGlobs(turboRoot);
    const workspaceConfigGlobs = workspaceGlobs.map(
      (glob) => `${glob}/turbo.json`
    );

    const configPaths = sync([ROOT_GLOB, ...workspaceConfigGlobs], {
      cwd: turboRoot,
      onlyFiles: true,
      followSymbolicLinks: false,
      // avoid throwing when encountering permission errors or unreadable paths
      suppressErrors: true,
    }).map((configPath) => path.join(turboRoot, configPath));

    configPaths.forEach((configPath) => {
      try {
        const raw = fs.readFileSync(configPath, "utf8");
        const turboJsonContent: Schema = JSON5.parse(raw);
        // basic config validation
        let isRootConfig = path.dirname(configPath) === turboRoot;
        if (isRootConfig) {
          // invalid - root config with extends
          if ("extends" in turboJsonContent) {
            return;
          }
        } else {
          // invalid - workspace config with no extends
          if (!("extends" in turboJsonContent)) {
            return;
          }
        }
        configs.push({
          config: turboJsonContent,
          turboConfigPath: configPath,
          workspacePath: path.dirname(configPath),
          isRootConfig,
        });
      } catch (e) {
        // if we can't read or parse the config, just ignore it with a warning
        console.warn(e);
      }
    });
  }

  if (cacheEnabled && cwd) {
    configsCache[cwd] = configs;
  }

  return configs;
}

export default getTurboConfigs;