mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
fix: the lockfile should be autofixed if it has invalid checksums (#3228)
close #3137
This commit is contained in:
committed by
Zoltan Kochan
parent
8d1dfa89c6
commit
f008425cdb
5
.changeset/gorgeous-parents-melt.md
Normal file
5
.changeset/gorgeous-parents-melt.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"supi": patch
|
||||
---
|
||||
|
||||
Fix the lockfile if it contains invalid checksums.
|
||||
@@ -66,6 +66,11 @@ import pFilter = require('p-filter')
|
||||
import pLimit = require('p-limit')
|
||||
import R = require('ramda')
|
||||
|
||||
const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([
|
||||
'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE',
|
||||
'ERR_PNPM_TARBALL_INTEGRITY',
|
||||
])
|
||||
|
||||
export type DependenciesMutation = (
|
||||
{
|
||||
buildIndex: number
|
||||
@@ -257,9 +262,9 @@ export async function mutateModules (
|
||||
} catch (error) {
|
||||
if (
|
||||
frozenLockfile ||
|
||||
error.code !== 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY' && error.code !== 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE'
|
||||
error.code !== 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY' && !BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)
|
||||
) throw error
|
||||
if (error.code === 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE') {
|
||||
if (BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)) {
|
||||
needsFullResolution = true
|
||||
// Ideally, we would not update but currently there is no other way to redownload the integrity of the package
|
||||
opts.update = true
|
||||
@@ -267,9 +272,10 @@ export async function mutateModules (
|
||||
// A broken lockfile may be caused by a badly resolved Git conflict
|
||||
logger.warn({
|
||||
error,
|
||||
message: 'The lockfile is broken! Resolution step will be performed to fix it.',
|
||||
message: error.message,
|
||||
prefix: ctx.lockfileDir,
|
||||
})
|
||||
logger.error(new PnpmError(error.code, 'The lockfile is broken! Resolution step will be performed to fix it.'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -897,17 +903,16 @@ const installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
try {
|
||||
return await _installInContext(projects, ctx, opts)
|
||||
} catch (error) {
|
||||
if (
|
||||
error.code !== 'ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE'
|
||||
) throw error
|
||||
if (!BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)) throw error
|
||||
opts.needsFullResolution = true
|
||||
// Ideally, we would not update but currently there is no other way to redownload the integrity of the package
|
||||
opts.update = true
|
||||
logger.warn({
|
||||
error,
|
||||
message: 'The lockfile is broken! pnpm will attempt to fix it.',
|
||||
message: error.message,
|
||||
prefix: ctx.lockfileDir,
|
||||
})
|
||||
logger.error(new PnpmError(error.code, 'The lockfile is broken! A full installation will be performed in an attempt to fix it.'))
|
||||
return _installInContext(projects, ctx, opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import rimraf from '@zkochan/rimraf'
|
||||
import R from 'ramda'
|
||||
import {
|
||||
addDependenciesToPackage,
|
||||
mutateModules,
|
||||
@@ -22,10 +23,11 @@ test('installation breaks if the lockfile contains the wrong checksum', async ()
|
||||
await testDefaults({ lockfileOnly: true })
|
||||
)
|
||||
|
||||
const lockfile = await project.readLockfile()
|
||||
const corruptedLockfile = await project.readLockfile()
|
||||
const correctLockfile = R.clone(corruptedLockfile)
|
||||
// breaking the lockfile
|
||||
lockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity'] = lockfile.packages['/dep-of-pkg-with-1-dep/100.0.0'].resolution['integrity']
|
||||
await writeYamlFile(WANTED_LOCKFILE, lockfile, { lineWidth: 1000 })
|
||||
corruptedLockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity'] = corruptedLockfile.packages['/dep-of-pkg-with-1-dep/100.0.0'].resolution['integrity']
|
||||
await writeYamlFile(WANTED_LOCKFILE, corruptedLockfile, { lineWidth: 1000 })
|
||||
|
||||
await expect(mutateModules([
|
||||
{
|
||||
@@ -45,8 +47,10 @@ test('installation breaks if the lockfile contains the wrong checksum', async ()
|
||||
},
|
||||
], await testDefaults())
|
||||
|
||||
expect(await project.readLockfile()).toStrictEqual(correctLockfile)
|
||||
|
||||
// Breaking the lockfile again
|
||||
await writeYamlFile(WANTED_LOCKFILE, lockfile, { lineWidth: 1000 })
|
||||
await writeYamlFile(WANTED_LOCKFILE, corruptedLockfile, { lineWidth: 1000 })
|
||||
|
||||
await rimraf('node_modules')
|
||||
|
||||
@@ -58,4 +62,76 @@ test('installation breaks if the lockfile contains the wrong checksum', async ()
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults({ preferFrozenLockfile: false }))
|
||||
|
||||
expect(await project.readLockfile()).toStrictEqual(correctLockfile)
|
||||
})
|
||||
|
||||
test('installation breaks if the lockfile contains the wrong checksum and the store is clean', async () => {
|
||||
await addDistTag('dep-of-pkg-with-1-dep', '100.0.0', 'latest')
|
||||
const project = prepareEmpty()
|
||||
|
||||
const manifest = await addDependenciesToPackage({},
|
||||
[
|
||||
'pkg-with-1-dep@100.0.0',
|
||||
],
|
||||
await testDefaults({ lockfileOnly: true })
|
||||
)
|
||||
|
||||
const corruptedLockfile = await project.readLockfile()
|
||||
const correctIntegrity = corruptedLockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity']
|
||||
// breaking the lockfile
|
||||
corruptedLockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity'] = 'sha512-pl8WtlGAnoIQ7gPxT187/YwhKRnsFBR4h0YY+v0FPQjT5WPuZbI9dPRaKWgKBFOqWHylJ8EyPy34V5u9YArfng=='
|
||||
await writeYamlFile(WANTED_LOCKFILE, corruptedLockfile, { lineWidth: 1000 })
|
||||
|
||||
await expect(
|
||||
mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
],
|
||||
await testDefaults({ frozenLockfile: true }, { retry: { retries: 0 } }))
|
||||
).rejects.toThrowError(/Got unexpected checksum/)
|
||||
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults({}, { retry: { retries: 0 } }))
|
||||
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
expect(lockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity']).toBe(correctIntegrity)
|
||||
}
|
||||
|
||||
// Breaking the lockfile again
|
||||
await writeYamlFile(WANTED_LOCKFILE, corruptedLockfile, { lineWidth: 1000 })
|
||||
|
||||
await rimraf('node_modules')
|
||||
|
||||
const reporter = jest.fn()
|
||||
await mutateModules([
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest,
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd(),
|
||||
},
|
||||
], await testDefaults({ preferFrozenLockfile: false, reporter }, { retry: { retries: 0 } }))
|
||||
|
||||
expect(reporter).toBeCalledWith(expect.objectContaining({
|
||||
level: 'warn',
|
||||
name: 'pnpm',
|
||||
prefix: process.cwd(),
|
||||
message: expect.stringMatching(/Got unexpected checksum/),
|
||||
}))
|
||||
{
|
||||
const lockfile = await project.readLockfile()
|
||||
expect(lockfile.packages['/pkg-with-1-dep/100.0.0'].resolution['integrity']).toBe(correctIntegrity)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -116,39 +116,6 @@ test('lockfile with scoped package', async () => {
|
||||
}, await testDefaults({ frozenLockfile: true }))
|
||||
})
|
||||
|
||||
test('fail when shasum from lockfile does not match with the actual one', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
await writeYamlFile(WANTED_LOCKFILE, {
|
||||
dependencies: {
|
||||
'is-negative': '2.1.0',
|
||||
},
|
||||
lockfileVersion: LOCKFILE_VERSION,
|
||||
packages: {
|
||||
'/is-negative/2.1.0': {
|
||||
resolution: {
|
||||
integrity: 'sha1-uZnX2TX0P1IHsBsA094ghS9Mp10=',
|
||||
tarball: `http://localhost:${REGISTRY_MOCK_PORT}/is-negative/-/is-negative-2.1.0.tgz`,
|
||||
},
|
||||
},
|
||||
},
|
||||
specifiers: {
|
||||
'is-negative': '2.1.0',
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
await install({
|
||||
dependencies: {
|
||||
'is-negative': '2.1.0',
|
||||
},
|
||||
}, await testDefaults({}, {}, { fetchRetries: 0 }))
|
||||
throw new Error('installation should have failed')
|
||||
} catch (err) {
|
||||
expect(err.code).toBe('ERR_PNPM_TARBALL_INTEGRITY')
|
||||
}
|
||||
})
|
||||
|
||||
test("lockfile doesn't lock subdependencies that don't satisfy the new specs", async () => {
|
||||
const project = prepareEmpty()
|
||||
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -3190,6 +3190,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/assert-project': link:../assert-project
|
||||
'@pnpm/types': link:../../packages/types
|
||||
unique-string: 2.0.0
|
||||
write-json5-file: 3.1.0
|
||||
write-pkg: 4.0.0
|
||||
write-yaml-file: 4.2.0
|
||||
@@ -3203,6 +3204,7 @@ importers:
|
||||
'@types/node': ^14.14.22
|
||||
tslint-config-standard: 9.0.0
|
||||
tslint-eslint-rules: 5.4.0
|
||||
unique-string: ^2.0.0
|
||||
write-json5-file: ^3.0.1
|
||||
write-pkg: 4.0.0
|
||||
write-yaml-file: ^4.1.3
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"@pnpm/assert-project": "workspace:*",
|
||||
"@pnpm/types": "workspace:6.4.0",
|
||||
"unique-string": "^2.0.0",
|
||||
"write-json5-file": "^3.0.1",
|
||||
"write-pkg": "4.0.0",
|
||||
"write-yaml-file": "^4.1.3"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import assertProject, { Modules, Project } from '@pnpm/assert-project'
|
||||
import { ProjectManifest } from '@pnpm/types'
|
||||
import uniqueString from 'unique-string'
|
||||
import { sync as writeJson5File } from 'write-json5-file'
|
||||
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||
import fs = require('fs')
|
||||
@@ -11,7 +12,7 @@ export type ManifestFormat = 'JSON' | 'JSON5' | 'YAML'
|
||||
|
||||
// The testing folder should be outside of the project to avoid lookup in the project's node_modules
|
||||
// Not using the OS temp directory due to issues on Windows CI.
|
||||
const tmpPath = path.join(__dirname, `../../../../pnpm_tmp/${Math.random()}`)
|
||||
const tmpPath = path.join(__dirname, `../../../../pnpm_tmp/${uniqueString()}`)
|
||||
|
||||
let dirNumber = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user