mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
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:
9
.changeset/nasty-seals-run.md
Normal file
9
.changeset/nasty-seals-run.md
Normal 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`.
|
||||
@@ -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,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { packageIsInstallable } from './packageIsInstallable'
|
||||
|
||||
export interface ReadProjectManifestOpts {
|
||||
engineStrict?: boolean
|
||||
packageManagerStrict?: boolean
|
||||
nodeVersion?: string
|
||||
supportedArchitectures?: SupportedArchitectures
|
||||
}
|
||||
|
||||
@@ -188,6 +188,7 @@ export interface Config {
|
||||
lockfile?: boolean
|
||||
dedupeInjectedDeps?: boolean
|
||||
nodeOptions?: string
|
||||
packageManagerStrict?: boolean
|
||||
}
|
||||
|
||||
export interface ConfigWithDeprecatedSettings extends Config {
|
||||
|
||||
@@ -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*',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"autozoom",
|
||||
"babek",
|
||||
"badheaders",
|
||||
"behaviour",
|
||||
"blabla",
|
||||
"brasileiro",
|
||||
"bryntum",
|
||||
|
||||
@@ -122,6 +122,7 @@ export interface PeerDependencyRules {
|
||||
export type AllowedDeprecatedVersions = Record<string, string>
|
||||
|
||||
export type ProjectManifest = BaseManifest & {
|
||||
packageManager?: string
|
||||
workspaces?: string[]
|
||||
pnpm?: {
|
||||
neverBuiltDependencies?: string[]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function findWorkspacePackages (
|
||||
workspaceRoot: string,
|
||||
opts?: {
|
||||
engineStrict?: boolean
|
||||
packageManagerStrict?: boolean
|
||||
nodeVersion?: string
|
||||
patterns?: string[]
|
||||
sharedWorkspaceLockfile?: boolean
|
||||
|
||||
Reference in New Issue
Block a user