Skip to content

Commit e312d61

Browse files
onsclomtrek
andauthored
[build-utils] Fix corepack packageManager detection on monorepos (#12219)
Reverting #12099 This change was originally reverted because Turborepo did not handle corepack correctly. Now #12211 mitigates this problem. --------- Co-authored-by: Trek Glowacki <[email protected]>
1 parent 582193a commit e312d61

File tree

16 files changed

+207
-47
lines changed

16 files changed

+207
-47
lines changed

.changeset/weak-gifts-leave.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@vercel/static-build': patch
3+
'@vercel/build-utils': patch
4+
'@vercel/hydrogen': patch
5+
'@vercel/redwood': patch
6+
'@vercel/remix-builder': patch
7+
'@vercel/next': patch
8+
---
9+
10+
Fix corepack `packageManager` detection on monorepos

packages/build-utils/src/fs/run-user-scripts.ts

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export interface ScanParentDirsResult {
6161
* or `undefined` if not found.
6262
*/
6363
lockfileVersion?: number;
64+
/**
65+
* The contents of the `packageManager` field from `package.json` if found.
66+
* The value may come from a different `package.json` file than the one
67+
* specified by `packageJsonPath`, in the case of a monorepo.
68+
*/
69+
packageJsonPackageManager?: string;
6470
/**
6571
* Whether Turborepo supports the `COREPACK_HOME` environment variable.
6672
* `undefined` if not a Turborepo project.
@@ -348,17 +354,19 @@ export async function scanParentDirs(
348354
readPackageJson && pkgJsonPath
349355
? JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'))
350356
: undefined;
351-
const [yarnLockPath, npmLockPath, pnpmLockPath, bunLockPath] =
352-
await walkParentDirsMulti({
353-
base,
354-
start: destPath,
355-
filenames: [
356-
'yarn.lock',
357-
'package-lock.json',
358-
'pnpm-lock.yaml',
359-
'bun.lockb',
360-
],
361-
});
357+
const {
358+
paths: [yarnLockPath, npmLockPath, pnpmLockPath, bunLockPath],
359+
packageJsonPackageManager,
360+
} = await walkParentDirsMulti({
361+
base,
362+
start: destPath,
363+
filenames: [
364+
'yarn.lock',
365+
'package-lock.json',
366+
'pnpm-lock.yaml',
367+
'bun.lockb',
368+
],
369+
});
362370
let lockfilePath: string | undefined;
363371
let lockfileVersion: number | undefined;
364372
let cliType: CliType;
@@ -413,19 +421,17 @@ export async function scanParentDirs(
413421
// TODO: read "bun-lockfile-format-v0"
414422
lockfileVersion = 0;
415423
} else {
416-
cliType =
417-
packageJson && rootProjectInfo
418-
? detectPackageManagerNameWithoutLockfile(
419-
packageJson,
420-
turboSupportsCorepackHome
421-
)
422-
: 'npm';
424+
cliType = detectPackageManagerNameWithoutLockfile(
425+
packageJsonPackageManager,
426+
turboSupportsCorepackHome
427+
);
423428
}
424429

425430
const packageJsonPath = pkgJsonPath || undefined;
426431
return {
427432
cliType,
428433
packageJson,
434+
packageJsonPackageManager,
429435
lockfilePath,
430436
lockfileVersion,
431437
packageJsonPath,
@@ -462,10 +468,9 @@ function turboRangeSupportsCorepack(turboVersionRange: string) {
462468
}
463469

464470
function detectPackageManagerNameWithoutLockfile(
465-
packageJson: PackageJson,
471+
packageJsonPackageManager: string | undefined,
466472
turboSupportsCorepackHome: boolean | undefined
467473
) {
468-
const packageJsonPackageManager = packageJson.packageManager;
469474
if (
470475
usingCorepack(
471476
process.env,
@@ -537,20 +542,35 @@ async function walkParentDirsMulti({
537542
base,
538543
start,
539544
filenames,
540-
}: WalkParentDirsMultiProps): Promise<(string | undefined)[]> {
545+
}: WalkParentDirsMultiProps): Promise<{
546+
paths: (string | undefined)[];
547+
packageJsonPackageManager?: string;
548+
}> {
549+
let packageManager: string | undefined;
550+
541551
for (const dir of traverseUpDirectories({ start, base })) {
542552
const fullPaths = filenames.map(f => path.join(dir, f));
543553
const existResults = await Promise.all(
544554
fullPaths.map(f => fs.pathExists(f))
545555
);
546556
const foundOneOrMore = existResults.some(b => b);
557+
const packageJsonPath = path.join(dir, 'package.json');
558+
const packageJson: PackageJson | null = await fs
559+
.readJSON(packageJsonPath)
560+
.catch(() => null);
561+
if (packageJson?.packageManager) {
562+
packageManager = packageJson.packageManager;
563+
}
547564

548565
if (foundOneOrMore) {
549-
return fullPaths.map((f, i) => (existResults[i] ? f : undefined));
566+
return {
567+
paths: fullPaths.map((f, i) => (existResults[i] ? f : undefined)),
568+
packageJsonPackageManager: packageManager,
569+
};
550570
}
551571
}
552572

553-
return [];
573+
return { paths: [], packageJsonPackageManager: packageManager };
554574
}
555575

556576
function isSet<T>(v: any): v is Set<T> {
@@ -578,6 +598,7 @@ export async function runNpmInstall(
578598
packageJsonPath,
579599
packageJson,
580600
lockfileVersion,
601+
packageJsonPackageManager,
581602
turboSupportsCorepackHome,
582603
} = await scanParentDirs(destPath, true);
583604

@@ -614,7 +635,7 @@ export async function runNpmInstall(
614635
opts.env = getEnvForPackageManager({
615636
cliType,
616637
lockfileVersion,
617-
packageJsonPackageManager: packageJson?.packageManager,
638+
packageJsonPackageManager,
618639
nodeVersion,
619640
env,
620641
packageJsonEngines: packageJson?.engines,
@@ -1154,12 +1175,17 @@ export async function runCustomInstallCommand({
11541175
spawnOpts?: SpawnOptions;
11551176
}) {
11561177
console.log(`Running "install" command: \`${installCommand}\`...`);
1157-
const { cliType, lockfileVersion, packageJson, turboSupportsCorepackHome } =
1158-
await scanParentDirs(destPath, true);
1178+
const {
1179+
cliType,
1180+
lockfileVersion,
1181+
packageJson,
1182+
packageJsonPackageManager,
1183+
turboSupportsCorepackHome,
1184+
} = await scanParentDirs(destPath, true);
11591185
const env = getEnvForPackageManager({
11601186
cliType,
11611187
lockfileVersion,
1162-
packageJsonPackageManager: packageJson?.packageManager,
1188+
packageJsonPackageManager,
11631189
nodeVersion,
11641190
env: spawnOpts?.env || {},
11651191
packageJsonEngines: packageJson?.engines,
@@ -1180,8 +1206,13 @@ export async function runPackageJsonScript(
11801206
) {
11811207
assert(path.isAbsolute(destPath));
11821208

1183-
const { packageJson, cliType, lockfileVersion, turboSupportsCorepackHome } =
1184-
await scanParentDirs(destPath, true);
1209+
const {
1210+
packageJson,
1211+
cliType,
1212+
lockfileVersion,
1213+
packageJsonPackageManager,
1214+
turboSupportsCorepackHome,
1215+
} = await scanParentDirs(destPath, true);
11851216
const scriptName = getScriptName(
11861217
packageJson,
11871218
typeof scriptNames === 'string' ? [scriptNames] : scriptNames
@@ -1197,7 +1228,7 @@ export async function runPackageJsonScript(
11971228
env: getEnvForPackageManager({
11981229
cliType,
11991230
lockfileVersion,
1200-
packageJsonPackageManager: packageJson?.packageManager,
1231+
packageJsonPackageManager,
12011232
nodeVersion: undefined,
12021233
env: cloneEnv(process.env, spawnOpts?.env),
12031234
packageJsonEngines: packageJson?.engines,
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "a21",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"debug": "^4.3.2"
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "b21",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"cowsay": "^1.5.0"
14+
}
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"private": true,
3+
"name": "41-npm-workspaces-corepack",
4+
"version": "1.0.0",
5+
"packageManager": "[email protected]",
6+
"workspaces": [
7+
"a",
8+
"b"
9+
]
10+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "build-c23",
3+
"license": "MIT",
4+
"version": "0.1.0"
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "build-d23",
3+
"license": "MIT",
4+
"version": "0.1.0",
5+
"devDependencies": {
6+
"once": "1.4.0"
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"private": true,
3+
"name": "42-pnpm-workspaces-corepack",
4+
"packageManager": "[email protected]",
5+
"license": "MIT",
6+
"version": "1.0.0"
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
packages:
2+
- 'c'
3+
- 'd'

packages/build-utils/test/unit.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,41 @@ it('should detect non-turborepo monorepo', async () => {
836836
expect(result.turboSupportsCorepackHome).toEqual(undefined);
837837
});
838838

839+
it('should detect `packageManager` in npm monorepo', async () => {
840+
try {
841+
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
842+
843+
const base = path.join(__dirname, 'fixtures', '41-npm-workspaces-corepack');
844+
const fixture = path.join(base, 'a');
845+
const result = await scanParentDirs(fixture, false, base);
846+
expect(result.cliType).toEqual('npm');
847+
expect(result.packageJsonPackageManager).toEqual('[email protected]');
848+
expect(result.lockfileVersion).toEqual(undefined);
849+
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
850+
} finally {
851+
delete process.env.ENABLE_EXPERIMENTAL_COREPACK;
852+
}
853+
});
854+
855+
it('should detect `packageManager` in pnpm monorepo', async () => {
856+
try {
857+
process.env.ENABLE_EXPERIMENTAL_COREPACK = '1';
858+
const base = path.join(
859+
__dirname,
860+
'fixtures',
861+
'42-pnpm-workspaces-corepack'
862+
);
863+
const fixture = path.join(base, 'c');
864+
const result = await scanParentDirs(fixture, false, base);
865+
expect(result.cliType).toEqual('pnpm');
866+
expect(result.packageJsonPackageManager).toEqual('[email protected]');
867+
expect(result.lockfileVersion).toEqual(undefined);
868+
expect(result.packageJsonPath).toEqual(path.join(fixture, 'package.json'));
869+
} finally {
870+
delete process.env.ENABLE_EXPERIMENTAL_COREPACK;
871+
}
872+
});
873+
839874
it('should retry npm install when peer deps invalid and npm@8 on node@16', async () => {
840875
const nodeMajor = Number(process.versions.node.split('.')[0]);
841876
if (nodeMajor !== 16) {

0 commit comments

Comments
 (0)