Compare commits

...

5 Commits

Author SHA1 Message Date
Erika Rowland
2fa8af9136 minor change to test failure 2024-05-29 13:25:38 -07:00
Sean Massa
2090719c39 Merge branch 'endangeredmassa/reject-mismatch-corepack' of https://github.com/vercel/vercel into endangeredmassa/reject-mismatch-corepack 2024-05-15 18:34:39 -05:00
Sean Massa
11813250ce fix value used in error 2024-05-15 18:33:55 -05:00
Sean Massa
58bec9aa01 Create lemon-bears-talk.md 2024-05-15 17:08:35 -05:00
Sean Massa
dd199896ab reject mismatched corepack and detected package managers 2024-05-15 17:06:18 -05:00
3 changed files with 155 additions and 52 deletions

View File

@@ -0,0 +1,5 @@
---
"@vercel/build-utils": patch
---
reject mismatched corepack and detected package managers

View File

@@ -20,6 +20,12 @@ import { cloneEnv } from '../clone-env';
// Only allow one `runNpmInstall()` invocation to run concurrently
const runNpmInstallSema = new Sema(1);
const NO_OVERRIDE = {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
};
export type CliType = 'yarn' | 'npm' | 'pnpm' | 'bun';
export interface ScanParentDirsResult {
@@ -557,9 +563,6 @@ export function getEnvForPackageManager({
nodeVersion: NodeVersion | undefined;
env: { [x: string]: string | undefined };
}) {
const corepackFlagged = env.ENABLE_EXPERIMENTAL_COREPACK === '1';
const corepackEnabled = corepackFlagged && Boolean(packageJsonPackageManager);
const {
detectedLockfile,
detectedPackageManager,
@@ -567,10 +570,12 @@ export function getEnvForPackageManager({
} = getPathOverrideForPackageManager({
cliType,
lockfileVersion,
corepackEnabled,
corepackPackageManager: packageJsonPackageManager,
nodeVersion,
});
const corepackFlagged = env.ENABLE_EXPERIMENTAL_COREPACK === '1';
const corepackEnabled = corepackFlagged && Boolean(packageJsonPackageManager);
if (corepackEnabled) {
debug(
`Detected corepack use for "${packageJsonPackageManager}". Not overriding package manager version.`
@@ -629,12 +634,9 @@ type DetectedPnpmVersion =
| 'corepack_enabled';
function detectPnpmVersion(
lockfileVersion: number | undefined,
corepackEnabled: boolean
lockfileVersion: number | undefined
): DetectedPnpmVersion {
switch (true) {
case corepackEnabled:
return 'corepack_enabled';
case lockfileVersion === undefined:
return 'not found';
case lockfileVersion === 5.3:
@@ -665,12 +667,12 @@ function shouldUseNpm7(
export function getPathOverrideForPackageManager({
cliType,
lockfileVersion,
corepackEnabled,
corepackPackageManager,
nodeVersion,
}: {
cliType: CliType;
lockfileVersion: number | undefined;
corepackEnabled: boolean;
corepackPackageManager: string | undefined;
nodeVersion: NodeVersion | undefined;
}): {
/**
@@ -683,73 +685,164 @@ export function getPathOverrideForPackageManager({
detectedPackageManager: string | undefined;
/**
* Value of $PATH that includes the binaries for the detected package manager.
* Undefined if no $PATH are necessary.
* `undefined` if no $PATH are necessary.
*/
path: string | undefined;
} {
const no_override = {
detectedLockfile: undefined,
detectedPackageManager: undefined,
path: undefined,
};
const detectedPackageManger = detectPackageManager(
cliType,
lockfileVersion,
nodeVersion
);
if (!detectedPackageManger) {
return NO_OVERRIDE;
}
if (!corepackPackageManager) {
return detectedPackageManger;
}
if (
validateVersionOverlap(
detectedPackageManger.detectedPackageManager,
corepackPackageManager
)
) {
// corepack is going to take care of it; do nothing special
return NO_OVERRIDE;
}
throw new Error(
`Detected package manager (by lockfile) "${detectedPackageManger.detectedPackageManager}" does not match intended corepack package manager "${corepackPackageManager}". Update your lockfile or "package.json#packageManager" values to match.`
);
}
function validateVersionOverlap(
detectedPackageManger: string,
corepackPackageManager: string
) {
const validatedDetectedPackageManger = validateVersionSpecifier(
detectedPackageManger
);
if (!validatedDetectedPackageManger) {
throw new Error(
`Detected package manager "${detectedPackageManger}" is not a valid semver value.`
);
}
const validatedCorepackPackageManager = validateVersionSpecifier(
corepackPackageManager
);
if (!validatedCorepackPackageManager) {
throw new Error(
`Intended corepack defined package manager "${corepackPackageManager}" is not a valid semver value.`
);
}
if (
validatedDetectedPackageManger.packageName !==
validatedCorepackPackageManager.packageName
) {
throw new Error(
`Detected package manager "${validatedDetectedPackageManger.packageName}" does not match intended corepack defined package manager "${validatedCorepackPackageManager.packageName}". Change your lockfile or "package.json#packageManager" value to match.`
);
}
return intersects(
validatedDetectedPackageManger.packageVersionRange,
validatedCorepackPackageManager.packageVersionRange
);
}
function validateVersionSpecifier(version: string) {
if (!version) {
return undefined;
}
const [before, after, ...extra] = version.split('@');
if (extra.length) {
// should not have more than one `@`
return undefined;
}
if (!before) {
// should have a package before the `@`
return undefined;
}
if (!after) {
// should have a version after the `@`
return undefined;
}
if (!validRange(after)) {
// the version after the `@` should be a valid semver value
return undefined;
}
return {
packageName: before,
packageVersionRange: after,
};
}
function detectPackageManager(
cliType: CliType,
lockfileVersion: number | undefined,
nodeVersion: NodeVersion | undefined
) {
switch (cliType) {
case 'npm':
switch (true) {
case corepackEnabled:
return no_override;
case shouldUseNpm7(lockfileVersion, nodeVersion):
return {
path: '/node16/bin-npm7',
detectedLockfile: 'package-lock.json',
detectedPackageManager: 'npm 7+',
detectedPackageManager: 'npm@7.x',
};
default:
return no_override;
return undefined;
}
case 'pnpm':
switch (detectPnpmVersion(lockfileVersion, corepackEnabled)) {
case 'corepack_enabled':
return no_override;
switch (detectPnpmVersion(lockfileVersion)) {
case 'pnpm 7':
// pnpm 7
return {
path: '/pnpm7/node_modules/.bin',
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 7',
detectedPackageManager: 'pnpm@7.x',
};
case 'pnpm 8':
// pnpm 8
return {
path: '/pnpm8/node_modules/.bin',
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 8',
detectedPackageManager: 'pnpm@8.x',
};
case 'pnpm 9':
// pnpm 9
return {
path: '/pnpm9/node_modules/.bin',
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 9',
detectedPackageManager: 'pnpm@9.x',
};
case 'pnpm 6':
default:
return no_override;
return undefined;
}
case 'bun':
switch (true) {
case corepackEnabled:
return no_override;
default:
// Bun 1
return {
path: '/bun1',
detectedLockfile: 'bun.lockb',
detectedPackageManager: 'Bun',
detectedPackageManager: 'bun@1.x',
};
}
case 'yarn':
return no_override;
return undefined;
}
}
@@ -793,13 +886,19 @@ export function getPathForPackageManager({
// broken behavior.
const corepackEnabled = env.ENABLE_EXPERIMENTAL_COREPACK === '1';
const overrides = getPathOverrideForPackageManager({
let overrides = getPathOverrideForPackageManager({
cliType,
lockfileVersion,
corepackEnabled,
corepackPackageManager: undefined,
nodeVersion,
});
if (corepackEnabled) {
// this is essentially always overriding the value of `override`, but that's what was happening
// in this deprecated function before
overrides = NO_OVERRIDE;
}
const alreadyInPath = (newPath: string) => {
const oldPath = env.PATH ?? '';
return oldPath.split(path.delimiter).includes(newPath);

View File

@@ -63,7 +63,7 @@ describe('Test `getEnvForPackageManager()`', () => {
FOO: 'bar',
PATH: `/node16/bin-npm7${delimiter}foo`,
},
consoleLogOutput: 'Detected `package-lock.json` generated by npm 7+',
consoleLogOutput: 'Detected `package-lock.json` generated by npm@7.x',
consoleWarnOutput: null,
},
{
@@ -71,7 +71,7 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: 'pnpm@latest',
packageJsonPackageManager: 'npm@*',
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -177,7 +177,7 @@ describe('Test `getEnvForPackageManager()`', () => {
PATH: `/pnpm7/node_modules/.bin${delimiter}foo`,
},
consoleLogOutput:
'Detected `pnpm-lock.yaml` version 5.4 generated by pnpm 7',
'Detected `pnpm-lock.yaml` version 5.4 generated by pnpm@7.x',
consoleWarnOutput: null,
},
{
@@ -197,7 +197,7 @@ describe('Test `getEnvForPackageManager()`', () => {
PATH: `/pnpm8/node_modules/.bin${delimiter}foo`,
},
consoleLogOutput:
'Detected `pnpm-lock.yaml` version 6 generated by pnpm 8',
'Detected `pnpm-lock.yaml` version 6 generated by pnpm@8.x',
consoleWarnOutput: null,
},
{
@@ -217,7 +217,7 @@ describe('Test `getEnvForPackageManager()`', () => {
PATH: `/pnpm9/node_modules/.bin${delimiter}foo`,
},
consoleLogOutput:
'Detected `pnpm-lock.yaml` version 9 generated by pnpm 9',
'Detected `pnpm-lock.yaml` version 9 generated by pnpm@9.x',
consoleWarnOutput: null,
},
{
@@ -236,7 +236,7 @@ describe('Test `getEnvForPackageManager()`', () => {
FOO: 'bar',
PATH: `/bun1${delimiter}/usr/local/bin`,
},
consoleLogOutput: 'Detected `bun.lockb` generated by Bun',
consoleLogOutput: 'Detected `bun.lockb` generated by bun@1.x',
consoleWarnOutput:
'Warning: Bun is used as a package manager at build time only, not at runtime with Functions',
},
@@ -245,7 +245,7 @@ describe('Test `getEnvForPackageManager()`', () => {
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: 'npm@latest',
packageJsonPackageManager: 'pnpm@*',
lockfileVersion: 5.4,
env: {
FOO: 'bar',
@@ -342,7 +342,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
},
want: {
detectedLockfile: 'package-lock.json',
detectedPackageManager: 'npm 7+',
detectedPackageManager: 'npm@7.x',
path: '/node16/bin-npm7',
},
},
@@ -351,7 +351,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
args: {
cliType: 'npm',
nodeVersion: { major: 14, range: '14.x', runtime: 'nodejs14.x' },
packageJsonPackageManager: 'pnpm@latest',
packageJsonPackageManager: 'npm@*',
lockfileVersion: 2,
env: {
FOO: 'bar',
@@ -415,7 +415,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 7',
detectedPackageManager: 'pnpm@7.x',
path: '/pnpm7/node_modules/.bin',
},
},
@@ -433,7 +433,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 8',
detectedPackageManager: 'pnpm@8.x',
path: '/pnpm8/node_modules/.bin',
},
},
@@ -451,7 +451,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 9',
detectedPackageManager: 'pnpm@9.x',
path: '/pnpm9/node_modules/.bin',
},
},
@@ -469,7 +469,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
},
want: {
detectedLockfile: 'bun.lockb',
detectedPackageManager: 'Bun',
detectedPackageManager: 'bun@1.x',
path: '/bun1',
},
},
@@ -478,7 +478,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
args: {
cliType: 'pnpm',
nodeVersion: { major: 16, range: '16.x', runtime: 'nodejs16.x' },
packageJsonPackageManager: 'npm@latest',
packageJsonPackageManager: 'pnpm@*',
lockfileVersion: 5.4,
env: {
FOO: 'bar',
@@ -496,8 +496,7 @@ describe('Test `getPathOverrideForPackageManager()`', () => {
getPathOverrideForPackageManager({
cliType: args.cliType,
lockfileVersion: args.lockfileVersion,
// naive assumption that enabling corepack as a feature means it's used, but this is fine for tests
corepackEnabled: Boolean(args.env.ENABLE_EXPERIMENTAL_COREPACK),
corepackPackageManager: args.packageJsonPackageManager,
nodeVersion: args.nodeVersion,
})
).toStrictEqual(want);
@@ -540,7 +539,7 @@ describe('Test `getPathForPackageManager()`', () => {
},
want: {
detectedLockfile: 'package-lock.json',
detectedPackageManager: 'npm 7+',
detectedPackageManager: 'npm@7.x',
path: '/node16/bin-npm7',
yarnNodeLinker: undefined,
},
@@ -647,7 +646,7 @@ describe('Test `getPathForPackageManager()`', () => {
},
want: {
detectedLockfile: 'pnpm-lock.yaml',
detectedPackageManager: 'pnpm 7',
detectedPackageManager: 'pnpm@7.x',
path: '/pnpm7/node_modules/.bin',
yarnNodeLinker: undefined,
},
@@ -665,7 +664,7 @@ describe('Test `getPathForPackageManager()`', () => {
},
want: {
detectedLockfile: 'bun.lockb',
detectedPackageManager: 'Bun',
detectedPackageManager: 'bun@1.x',
path: '/bun1',
yarnNodeLinker: undefined,
},