Skip to content

Commit cbb9ca1

Browse files
authored
Add basic support for yarn and pnpm workspaces (#83)
* Extract helper for monorepo package ordering * Add handling for yarn workspaces * Add minimal handling for pnpm workspaces * Check for lerna.json last * Store and reuse monorepo ordering * Handle yarn2 workspace syntax * Harden against malformed package.json files * Harden against some unusual repos seen in the wild
1 parent fd23f0c commit cbb9ca1

File tree

8 files changed

+221
-92
lines changed

8 files changed

+221
-92
lines changed

package-lock.json

Lines changed: 71 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
"@typescript/server-harness": "^0.3.4",
1717
"@typescript/server-replay": "^0.2.11",
1818
"glob": "^7.2.3",
19+
"js-yaml": "^4.1.0",
1920
"json5": "^2.2.1",
2021
"random-seed": "^0.3.0",
2122
"simple-git": "^3.10.0"
2223
},
2324
"devDependencies": {
2425
"@types/glob": "^7.2.0",
2526
"@types/jest": "^27.5.2",
27+
"@types/js-yaml": "^4.0.5",
2628
"@types/node": "^14.18.21",
2729
"@types/random-seed": "^0.3.3",
2830
"jest": "^27.4.4",

src/main.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,21 @@ async function cloneRepo(
124124
}
125125
}
126126

127+
async function getMonorepoPackages(repoDir: string): Promise<readonly string[] | undefined> {
128+
try {
129+
return await pu.getMonorepoOrder(repoDir);
130+
}
131+
catch (e) {
132+
reportError(e, `Error identifying monorepo packages for ${repoDir} - treating as separate packages`);
133+
return undefined;
134+
}
135+
}
136+
127137
async function installPackagesAndGetCommands(
128138
repo: git.Repo,
129139
downloadDir: string,
130140
repoDir: string,
141+
monorepoPackages: readonly string[],
131142
cleanOnFailure: boolean,
132143
diagnosticOutput: boolean): Promise<ip.InstallCommand[] | undefined> {
133144
const packageInstallStart = performance.now();
@@ -139,7 +150,7 @@ async function installPackagesAndGetCommands(
139150
/*ignoreScripts*/ true,
140151
/*quietOutput*/ !diagnosticOutput,
141152
/*recursiveSearch*/ !isUserTestRepo,
142-
/*lernaPackages*/ undefined,
153+
/*monorepoPackages*/ monorepoPackages,
143154
repo.types);
144155
await installPackages(repoDir, commands, packageTimeout);
145156
return commands;
@@ -180,10 +191,11 @@ async function getTsServerRepoResult(
180191
}
181192

182193
const repoDir = path.join(downloadDir, repo.name);
194+
const monorepoPackages = await getMonorepoPackages(repoDir);
183195

184196
// Presumably, people occasionally browse repos without installing the packages first
185-
const installCommands = (prng.random() > 0.2)
186-
? (await installPackagesAndGetCommands(repo, downloadDir, repoDir, /*cleanOnFailure*/ true, diagnosticOutput))!
197+
const installCommands = (prng.random() > 0.2) && monorepoPackages
198+
? (await installPackagesAndGetCommands(repo, downloadDir, repoDir, monorepoPackages, /*cleanOnFailure*/ true, diagnosticOutput))!
187199
: [];
188200

189201
const isUserTestRepo = !repo.url;
@@ -374,8 +386,9 @@ export async function getTscRepoResult(
374386
}
375387

376388
const repoDir = path.join(downloadDir, repo.name);
389+
const monorepoPackages = await getMonorepoPackages(repoDir);
377390

378-
if (!await installPackagesAndGetCommands(repo, downloadDir, repoDir, /*cleanOnFailure*/ false, diagnosticOutput)) {
391+
if (!monorepoPackages || !await installPackagesAndGetCommands(repo, downloadDir, repoDir, monorepoPackages, /*cleanOnFailure*/ false, diagnosticOutput)) {
379392
return { status: "Package install failed" };
380393
}
381394

@@ -384,7 +397,7 @@ export async function getTscRepoResult(
384397
const buildStart = performance.now();
385398
try {
386399
console.log(`Building with ${oldTscPath} (old)`);
387-
const oldErrors = await ge.buildAndGetErrors(repoDir, isUserTestRepo, oldTscPath, executionTimeout, /*skipLibCheck*/ true);
400+
const oldErrors = await ge.buildAndGetErrors(repoDir, monorepoPackages, isUserTestRepo, oldTscPath, executionTimeout, /*skipLibCheck*/ true);
388401

389402
if (oldErrors.hasConfigFailure) {
390403
console.log("Unable to build project graph");
@@ -424,7 +437,7 @@ export async function getTscRepoResult(
424437
}
425438

426439
console.log(`Building with ${newTscPath} (new)`);
427-
const newErrors = await ge.buildAndGetErrors(repoDir, isUserTestRepo, newTscPath, executionTimeout, /*skipLibCheck*/ true);
440+
const newErrors = await ge.buildAndGetErrors(repoDir, monorepoPackages, isUserTestRepo, newTscPath, executionTimeout, /*skipLibCheck*/ true);
428441

429442
if (newErrors.hasConfigFailure) {
430443
console.log("Unable to build project graph");

src/utils/getTscErrors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function errorEquals(error1: Error, error2: Error) {
6464
* @param tscPath The path to tsc.js.
6565
* @param skipLibCheck True pass --skipLibCheck when building non-composite projects. (Defaults to true)
6666
*/
67-
export async function buildAndGetErrors(repoDir: string, isUserTestRepo: boolean, tscPath: string, timeoutMs: number, skipLibCheck: boolean = true): Promise<RepoErrors> {
67+
export async function buildAndGetErrors(repoDir: string, monorepoPackages: readonly string[], isUserTestRepo: boolean, tscPath: string, timeoutMs: number, skipLibCheck: boolean = true): Promise<RepoErrors> {
6868
const projectErrors: ProjectErrors[] = [];
6969

7070
// If it's a user test repo and there's a build.sh in the root, don't bother searching for projects
@@ -95,7 +95,7 @@ export async function buildAndGetErrors(repoDir: string, isUserTestRepo: boolean
9595
const simpleBuildArgs = ["--skipLibCheck", `${skipLibCheck}`, "--incremental", "false", "--pretty", "false", "-p"];
9696
const compositeBuildArgs = ["-b", "-f", "-v"]; // Build mode doesn't support --skipLibCheck or --pretty
9797

98-
const { simpleProjects, rootCompositeProjects, hasError: hasConfigFailure } = await projectGraph.getProjectsToBuild(repoDir);
98+
const { simpleProjects, rootCompositeProjects, hasError: hasConfigFailure } = await projectGraph.getProjectsToBuild(repoDir, monorepoPackages);
9999
// TODO: Move this inside getProjectsToBuild, separating them is pointless (originally separate to enable simple-only and composite-only runs)
100100
const projectsToBuild = simpleProjects.concat(rootCompositeProjects);
101101
if (!projectsToBuild.length) {

src/utils/installPackages.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export interface InstallCommand {
2222
* Traverses the given directory and returns a list of commands that can be used, in order, to install
2323
* the packages required for building.
2424
*/
25-
export async function installPackages(repoDir: string, ignoreScripts: boolean, quietOutput: boolean, recursiveSearch: boolean, lernaPackages?: readonly string[], types?: string[]): Promise<InstallCommand[]> {
26-
lernaPackages = lernaPackages ?? await utils.getLernaOrder(repoDir);
25+
export async function installPackages(repoDir: string, ignoreScripts: boolean, quietOutput: boolean, recursiveSearch: boolean, monorepoPackages?: readonly string[], types?: string[]): Promise<InstallCommand[]> {
26+
monorepoPackages = monorepoPackages ?? await utils.getMonorepoOrder(repoDir);
2727

2828
const isRepoYarn = await utils.exists(path.join(repoDir, "yarn.lock"));
2929
// The existence of .yarnrc.yml indicates that this repo uses yarn 2
@@ -36,12 +36,12 @@ export async function installPackages(repoDir: string, ignoreScripts: boolean, q
3636
const packageFiles = utils.glob(repoDir, globPattern);
3737

3838
for (const packageFile of packageFiles) {
39-
let inLernaPackageDir = false;
40-
for (const lernaPackage of lernaPackages) {
41-
if (inLernaPackageDir = packageFile.startsWith(lernaPackage)) break;
39+
let inMonorepoPackageDir = false;
40+
for (const monorepoPackage of monorepoPackages) {
41+
if (inMonorepoPackageDir = packageFile.startsWith(monorepoPackage)) break;
4242
}
43-
if (inLernaPackageDir) {
44-
// Skipping installation of lerna package
43+
if (inMonorepoPackageDir) {
44+
// Skipping installation of monorepo package
4545
continue;
4646
}
4747

0 commit comments

Comments
 (0)