feat!: check the packageManager field (#7635)

* feat!: check the packageManager field

* test: fix

* refactor: update error message to match what corepack prints

* feat: add pm-strict option

* refactor: pm-strict=>package-manager-strict

* fix: remove packageManager field from package.json on publish
This commit is contained in:
Zoltan Kochan
2024-02-11 01:07:23 +01:00
committed by GitHub
parent 2b891558a8
commit 3477ee5ee8
11 changed files with 93 additions and 2 deletions

View File

@@ -0,0 +1,9 @@
---
"@pnpm/cli-utils": major
"pnpm": major
"@pnpm/exportable-manifest": major
---
pnpm will now check the `package.json` file for a `packageManager` field. If this field is present and specifies a different package manager or a different version of pnpm than the one you're currently using, pnpm will not proceed. This ensures that you're always using the correct package manager and version that the project requires.
To disable this behaviour, set the `package-manager-strict` setting to `false` or the `COREPACK_ENABLE_STRICT` env variable to `0`.

View File

@@ -1,17 +1,20 @@
import { PnpmError } from '@pnpm/error'
import { packageManager } from '@pnpm/cli-meta'
import { logger } from '@pnpm/logger'
import { logger, globalWarn } from '@pnpm/logger'
import { checkPackage, UnsupportedEngineError, type WantedEngine } from '@pnpm/package-is-installable'
import { type SupportedArchitectures } from '@pnpm/types'
export function packageIsInstallable (
pkgPath: string,
pkg: {
packageManager?: string
engines?: WantedEngine
cpu?: string[]
os?: string[]
libc?: string[]
},
opts: {
packageManagerStrict?: boolean
engineStrict?: boolean
nodeVersion?: string
supportedArchitectures?: SupportedArchitectures
@@ -20,6 +23,24 @@ export function packageIsInstallable (
const pnpmVersion = packageManager.name === 'pnpm'
? packageManager.stableVersion
: undefined
if (pkg.packageManager) {
const [pmName, pmVersion] = pkg.packageManager.split('@')
if (pmName && pmName !== 'pnpm') {
const msg = `This project is configured to use ${pmName}`
if (opts.packageManagerStrict) {
throw new PnpmError('OTHER_PM_EXPECTED', msg)
} else {
globalWarn(msg)
}
} else if (pmVersion && pnpmVersion && pmVersion !== pnpmVersion) {
const msg = `This project is configured to use v${pmVersion} of pnpm. Your current pnpm is v${pnpmVersion}`
if (opts.packageManagerStrict) {
throw new PnpmError('BAD_PM_VERSION', msg)
} else {
globalWarn(msg)
}
}
}
const err = checkPackage(pkgPath, pkg, {
nodeVersion: opts.nodeVersion,
pnpmVersion,

View File

@@ -4,6 +4,7 @@ import { packageIsInstallable } from './packageIsInstallable'
export interface ReadProjectManifestOpts {
engineStrict?: boolean
packageManagerStrict?: boolean
nodeVersion?: string
supportedArchitectures?: SupportedArchitectures
}

View File

@@ -188,6 +188,7 @@ export interface Config {
lockfile?: boolean
dedupeInjectedDeps?: boolean
nodeOptions?: string
packageManagerStrict?: boolean
}
export interface ConfigWithDeprecatedSettings extends Config {

View File

@@ -103,6 +103,7 @@ export const types = Object.assign({
'package-import-method': ['auto', 'hardlink', 'clone', 'copy'],
'patches-dir': String,
pnpmfile: String,
'package-manager-strict': Boolean,
'prefer-frozen-lockfile': Boolean,
'prefer-offline': Boolean,
'prefer-symlinked-executables': Boolean,
@@ -240,6 +241,7 @@ export async function getConfig (
'node-linker': 'isolated',
'package-lock': npmDefaults['package-lock'],
pending: false,
'package-manager-strict': process.env.COREPACK_ENABLE_STRICT !== '0',
'prefer-workspace-packages': false,
'public-hoist-pattern': [
'*eslint*',

View File

@@ -16,6 +16,7 @@
"autozoom",
"babek",
"badheaders",
"behaviour",
"blabla",
"brasileiro",
"bryntum",

View File

@@ -122,6 +122,7 @@ export interface PeerDependencyRules {
export type AllowedDeprecatedVersions = Record<string, string>
export type ProjectManifest = BaseManifest & {
packageManager?: string
workspaces?: string[]
pnpm?: {
neverBuiltDependencies?: string[]

View File

@@ -25,7 +25,7 @@ export async function createExportableManifest (
originalManifest: ProjectManifest,
opts?: MakePublishManifestOptions
) {
const publishManifest: ProjectManifest = omit(['pnpm', 'scripts'], originalManifest)
const publishManifest: ProjectManifest = omit(['pnpm', 'scripts', 'packageManager'], originalManifest)
if (originalManifest.scripts != null) {
publishManifest.scripts = omit(PREPUBLISH_SCRIPTS, originalManifest.scripts)
}

View File

@@ -29,6 +29,23 @@ test('the pnpm options are removed', async () => {
})
})
test('the packageManager field is removed', async () => {
expect(await createExportableManifest(process.cwd(), {
name: 'foo',
version: '1.0.0',
dependencies: {
qar: '2',
},
packageManager: 'pnpm@8.0.0',
})).toStrictEqual({
name: 'foo',
version: '1.0.0',
dependencies: {
qar: '2',
},
})
})
test('publish lifecycle scripts are removed', async () => {
expect(await createExportableManifest(process.cwd(), {
name: 'foo',

View File

@@ -260,6 +260,43 @@ test('install should fail if the used pnpm version does not satisfy the pnpm ver
expect(stdout.toString()).toContain('Your pnpm version is incompatible with')
})
test('install should fail if the used pnpm version does not satisfy the pnpm version specified in packageManager', async () => {
prepare({
name: 'project',
version: '1.0.0',
packageManager: 'pnpm@0.0.0',
})
const { status, stdout } = execPnpmSync(['install'])
expect(status).toBe(1)
expect(stdout.toString()).toContain('This project is configured to use v0.0.0 of pnpm. Your current pnpm is')
expect(execPnpmSync(['install', '--config.package-manager-strict=false']).status).toBe(0)
expect(execPnpmSync(['install'], {
env: {
COREPACK_ENABLE_STRICT: '0',
},
}).status).toBe(0)
})
test('install should fail if the project requires a different package manager', async () => {
prepare({
name: 'project',
version: '1.0.0',
packageManager: 'yarn@4.0.0',
})
const { status, stdout } = execPnpmSync(['install'])
expect(status).toBe(1)
expect(stdout.toString()).toContain('This project is configured to use yarn')
expect(execPnpmSync(['install', '--config.package-manager-strict=false']).status).toBe(0)
})
test('engine-strict=false: install should not fail if the used Node version does not satisfy the Node version specified in engines', async () => {
prepare({
name: 'project',

View File

@@ -11,6 +11,7 @@ export async function findWorkspacePackages (
workspaceRoot: string,
opts?: {
engineStrict?: boolean
packageManagerStrict?: boolean
nodeVersion?: string
patterns?: string[]
sharedWorkspaceLockfile?: boolean