feat: a new option strict-store-pkg-content-check (#8212)

ref #4724
This commit is contained in:
Zoltan Kochan
2024-06-16 21:37:58 +02:00
committed by GitHub
parent 398472cc11
commit 7c6c923a3d
12 changed files with 70 additions and 6 deletions

View File

@@ -0,0 +1,10 @@
---
"@pnpm/store-connection-manager": minor
"@pnpm/package-requester": minor
"@pnpm/plugin-commands-rebuild": minor
"@pnpm/package-store": minor
"@pnpm/config": minor
"pnpm": minor
---
Some registries allow the exact same content to be published under different package names and/or versions. This breaks the validity checks of packages in the store. To avoid errors when verifying the names and versions of such packages in the store, you may now set the `strict-store-pkg-content-check` setting to `false` [#4724](https://github.com/pnpm/pnpm/issues/4724).

View File

@@ -195,6 +195,7 @@ export interface Config {
packageManagerStrictVersion?: boolean
virtualStoreDirMaxLength: number
peersSuffixMaxLength?: number
strictStorePkgContentCheck: boolean
}
export interface ConfigWithDeprecatedSettings extends Config {

View File

@@ -133,6 +133,7 @@ export const types = Object.assign({
'state-dir': String,
'store-dir': String,
stream: Boolean,
'strict-store-pkg-content-check': Boolean,
'strict-peer-dependencies': Boolean,
'use-beta-cli': Boolean,
'use-node-version': String,
@@ -264,6 +265,7 @@ export async function getConfig (
symlink: true,
'shared-workspace-lockfile': true,
'shell-emulator': false,
'strict-store-pkg-content-check': true,
reverse: false,
sort: true,
'strict-peer-dependencies': false,

View File

@@ -49,6 +49,7 @@ export type StrictRebuildOptions = {
onlyBuiltDependencies?: string[]
virtualStoreDirMaxLength: number
peersSuffixMaxLength: number
strictStorePkgContentCheck: boolean
} & Pick<Config, 'sslConfigs'>
export type RebuildOptions = Partial<StrictRebuildOptions> &

View File

@@ -18,7 +18,7 @@ import {
} from '@pnpm/fetcher-base'
import { type Cafs } from '@pnpm/cafs-types'
import gfs from '@pnpm/graceful-fs'
import { logger } from '@pnpm/logger'
import { globalWarn, logger } from '@pnpm/logger'
import { packageIsInstallable } from '@pnpm/package-is-installable'
import { readPackageJson } from '@pnpm/read-package-json'
import {
@@ -91,6 +91,7 @@ export function createPackageRequester (
storeDir: string
verifyStoreIntegrity: boolean
virtualStoreDirMaxLength: number
strictStorePkgContentCheck?: boolean
}
): RequestPackageFunction & {
fetchPackageToStore: FetchPackageToStoreFunction
@@ -122,6 +123,7 @@ export function createPackageRequester (
}),
storeDir: opts.storeDir,
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
strictStorePkgContentCheck: opts.strictStorePkgContentCheck,
})
const requestPackage = resolveAndFetch.bind(null, {
engineStrict: opts.engineStrict,
@@ -342,6 +344,7 @@ function fetchToStore (
}
storeDir: string
virtualStoreDirMaxLength: number
strictStorePkgContentCheck?: boolean
},
opts: FetchPackageToStoreOptions
): {
@@ -484,10 +487,17 @@ function fetchToStore (
!equalOrSemverEqual(pkgFilesIndex.version, opts.expectedPkg.version)
)
) {
throw new PnpmError('UNEXPECTED_PKG_CONTENT_IN_STORE', `\
Package name mismatch found while reading ${JSON.stringify(opts.pkg.resolution)} from the store. \
This means that the lockfile is broken. Expected package: ${opts.expectedPkg.name}@${opts.expectedPkg.version}. \
Actual package in the store by the given integrity: ${pkgFilesIndex.name}@${pkgFilesIndex.version}.`)
const msg = `Package name mismatch found while reading ${JSON.stringify(opts.pkg.resolution)} from the store.`
const hint = `This means that either the lockfile is broken or the package metadata (name and version) inside the package's package.json file doesn't match the metadata in the registry. \
Expected package: ${opts.expectedPkg.name}@${opts.expectedPkg.version}. \
Actual package in the store with the given integrity: ${pkgFilesIndex.name}@${pkgFilesIndex.version}.`
if (ctx.strictStorePkgContentCheck ?? true) {
throw new PnpmError('UNEXPECTED_PKG_CONTENT_IN_STORE', msg, {
hint: `${hint}\n\nIf you want to ignore this issue, set the strict-store-pkg-content-check to false.`,
})
} else {
globalWarn(`${msg} ${hint}`)
}
}
fetching.resolve({
files: {

3
pnpm-lock.yaml generated
View File

@@ -4687,6 +4687,9 @@ importers:
'@pnpm/run-npm':
specifier: workspace:*
version: link:../exec/run-npm
'@pnpm/store.cafs':
specifier: workspace:*
version: link:../store/cafs
'@pnpm/tabtab':
specifier: ^0.5.3
version: 0.5.3

View File

@@ -67,6 +67,7 @@
"@pnpm/read-project-manifest": "workspace:*",
"@pnpm/registry-mock": "3.31.0",
"@pnpm/run-npm": "workspace:*",
"@pnpm/store.cafs": "workspace:*",
"@pnpm/tabtab": "^0.5.3",
"@pnpm/test-fixtures": "workspace:*",
"@pnpm/test-ipc-server": "workspace:*",

View File

@@ -5,12 +5,15 @@ import { type Lockfile } from '@pnpm/lockfile-types'
import { prepare, prepareEmpty, preparePackages } from '@pnpm/prepare'
import { readPackageJsonFromDir } from '@pnpm/read-package-json'
import { readProjectManifest } from '@pnpm/read-project-manifest'
import { getIntegrity } from '@pnpm/registry-mock'
import { getFilePathInCafs } from '@pnpm/store.cafs'
import { writeProjectManifest } from '@pnpm/write-project-manifest'
import dirIsCaseSensitive from 'dir-is-case-sensitive'
import { sync as readYamlFile } from 'read-yaml-file'
import { sync as rimraf } from '@zkochan/rimraf'
import isWindows from 'is-windows'
import loadJsonFile from 'load-json-file'
import writeJsonFile from 'write-json-file'
import crossSpawn from 'cross-spawn'
import {
execPnpm,
@@ -507,3 +510,27 @@ test('installation fails with a timeout error', async () => {
execPnpm(['add', 'typescript@2.4.2', '--fetch-timeout=1', '--fetch-retries=0'])
).rejects.toThrow()
})
test('installation fails when the stored package name and version do not match the meta of the installed package', async () => {
prepare()
const storeDir = path.resolve('store')
const settings = [`--config.store-dir=${storeDir}`]
await execPnpm(['add', '@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0', ...settings])
const cafsDir = path.join(storeDir, 'v3/files')
const cacheIntegrityPath = getFilePathInCafs(cafsDir, getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0'), 'index')
const cacheIntegrity = loadJsonFile.sync<any>(cacheIntegrityPath) // eslint-disable-line @typescript-eslint/no-explicit-any
cacheIntegrity.name = 'foo'
writeJsonFile.sync(cacheIntegrityPath, {
...cacheIntegrity,
name: 'foo',
})
rimraf('node_modules')
await expect(
execPnpm(['install', ...settings])
).rejects.toThrow()
await execPnpm(['install', '--config.strict-store-pkg-content-check=false', ...settings])
})

View File

@@ -130,6 +130,9 @@
{
"path": "../../reviewing/plugin-commands-outdated"
},
{
"path": "../../store/cafs"
},
{
"path": "../../store/plugin-commands-server"
},

View File

@@ -133,6 +133,9 @@
{
"path": "../reviewing/plugin-commands-outdated"
},
{
"path": "../store/cafs"
},
{
"path": "../store/plugin-commands-server"
},

View File

@@ -28,6 +28,7 @@ export function createPackageStore (
packageImportMethod?: 'auto' | 'hardlink' | 'copy' | 'clone' | 'clone-or-copy'
verifyStoreIntegrity: boolean
virtualStoreDirMaxLength: number
strictStorePkgContentCheck?: boolean
clearResolutionCache: () => void
}
): StoreController {
@@ -49,6 +50,7 @@ export function createPackageStore (
storeDir: initOpts.storeDir,
verifyStoreIntegrity: initOpts.verifyStoreIntegrity,
virtualStoreDirMaxLength: initOpts.virtualStoreDirMaxLength,
strictStorePkgContentCheck: initOpts.strictStorePkgContentCheck,
})
return {

View File

@@ -45,7 +45,7 @@ export type CreateNewStoreControllerOptions = CreateResolverOptions & Pick<Confi
> & {
cafsLocker?: CafsLocker
ignoreFile?: (filename: string) => boolean
} & Partial<Pick<Config, 'userConfig' | 'deployAllFiles' | 'sslConfigs'>> & Pick<ClientOptions, 'resolveSymlinksInInjectedDirs'>
} & Partial<Pick<Config, 'userConfig' | 'deployAllFiles' | 'sslConfigs' | 'strictStorePkgContentCheck'>> & Pick<ClientOptions, 'resolveSymlinksInInjectedDirs'>
export async function createNewStoreController (
opts: CreateNewStoreControllerOptions
@@ -107,6 +107,7 @@ export async function createNewStoreController (
? opts.verifyStoreIntegrity
: true,
virtualStoreDirMaxLength: opts.virtualStoreDirMaxLength,
strictStorePkgContentCheck: opts.strictStorePkgContentCheck,
clearResolutionCache,
}),
dir: opts.storeDir,