-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtsbuild.mjs
171 lines (152 loc) · 4.75 KB
/
tsbuild.mjs
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* This script loads the packages in the npm workspace by inspecting the
* "workspaces" field in the root package.json. It finds cross-references
* between each of the workspace package.json, and then updates each package's
* tsconfig.json with a project reference for each cross reference. Finally, all
* discovered TypeScript projects are added to the root tsconfig.json.
*
* The tsconfig project references are used by the tsc compiler to build the
* projects in the correct order.
*
* @see {@link https://www.typescriptlang.org/docs/handbook/project-references.html}
*/
import { spawnSync } from "child_process";
import { readFile, writeFile } from "fs/promises";
import * as glob from "glob";
import { dirname, join, relative, resolve } from "path";
import Prettier from "prettier";
import { fileURLToPath } from "url";
const WORKSPACE_ROOT = dirname(fileURLToPath(import.meta.url));
const PACKAGE_JSON = "package.json";
const TS_CONFIG = "tsconfig.json";
// load the prettier config from the workspace root
const prettierConfig = await Prettier.resolveConfig(
join(WORKSPACE_ROOT, TS_CONFIG),
);
// read the root package.json
const rootPkg = JSON.parse(
await readFile(join(WORKSPACE_ROOT, PACKAGE_JSON), "utf-8"),
);
// we expect there to be workspaces defined in the root package.
if (
!rootPkg.workspaces ||
!Array.isArray(rootPkg.workspaces) ||
!rootPkg.workspaces.length
) {
console.error(`ERROR: no workspaces defined`);
process.exit(1);
}
// the workspaces array might contain globs, so expand them
// see https://docs.npmjs.com/cli/v7/using-npm/workspaces
const packages = rootPkg.workspaces.flatMap((x) =>
glob.sync(x, { onlyDirectories: true }).map((p) => resolve(p)),
);
// bad workspace definition, no projects actually found
if (!packages.length) {
console.error(
`ERROR: no packages found in workspaces [${rootPkg.workspaces}]`,
);
process.exit(1);
}
const refs = {};
// look through each package we found
for (const pkg of packages) {
const packageJsonPath = join(pkg, PACKAGE_JSON);
let packageJson;
try {
// read/parse the package.json
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
} catch (err) {
// couldn't parse the package.json
console.warn(`WARN: skipping ${relative(WORKSPACE_ROOT, pkg)}: %o`, err);
continue;
}
// collect all candidate dependencies
const deps = [];
if (packageJson.dependencies) {
deps.push(...Object.keys(packageJson.dependencies));
}
if (packageJson.devDependencies) {
deps.push(...Object.keys(packageJson.devDependencies));
}
let tsConfig;
const packageTsConfigPath = join(pkg, TS_CONFIG);
try {
// now load the tsconfig.json
tsConfig = JSON.parse(await readFile(packageTsConfigPath));
if (!tsConfig.compilerOptions?.composite) {
// composite option is required for build mode to work
// https://www.typescriptlang.org/tsconfig#composite
console.warn(
`skipping ${packageJson.name} because composite is not turned on`,
);
continue;
}
} catch {
// couldn't parse the tsconfig, or couldn't find it
continue;
}
refs[packageJson.name] = {
name: packageJson.name,
path: pkg,
deps,
tsConfig,
};
}
// for each package we found, update the tsconfig with the cross-refs
for (const pkg of Object.values(refs)) {
await addRefsToTsConfig(
join(pkg.path, TS_CONFIG),
// filter the candidate refs to only include cross-refs
pkg.deps.filter((x) => x in refs).map((x) => refs[x].path),
pkg.tsConfig,
);
}
// save all the packages we found to the root tsconfig
const rootTsConfigPath = join(WORKSPACE_ROOT, TS_CONFIG);
await addRefsToTsConfig(
rootTsConfigPath,
Object.values(refs).map((x) => x.path),
);
// run tsc and forward any arguments from this script
const result = spawnSync(
join(WORKSPACE_ROOT, "node_modules/.bin/tsc"),
["-b", rootTsConfigPath, ...process.argv.slice(2)],
{
stdio: "inherit",
shell: process.platform === "win32",
},
);
process.exit(result.status);
async function addRefsToTsConfig(configPath, references, contents) {
const dir = dirname(resolve(configPath));
if (!contents) {
try {
contents = JSON.parse(await readFile(configPath, "utf-8"));
} catch (err) {
// file wasn't found, nothing to add then
if (err?.code !== "ENOENT") {
throw err;
}
}
}
// save the project references for each cross-ref
const output = JSON.stringify(
{
...contents,
references: references.map((x) => ({
path: relative(dir, x).replace(/\\/g, "/"),
})),
},
null,
2,
);
// format the output with prettier to avoid lint problems
await writeFile(
configPath,
await Prettier.format(output, {
filepath: configPath,
...prettierConfig,
}),
);
}