Skip to content

Commit

Permalink
fix: a broken lockfile should be fixed
Browse files Browse the repository at this point in the history
Unless frozenLockfile is true, a broken lockfile should
be fixed and installation should succeed.

PR #2440
  • Loading branch information
zkochan committed Mar 24, 2020
1 parent 9890f37 commit 6a9edfe
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 53 deletions.
12 changes: 12 additions & 0 deletions packages/filter-lockfile/src/LockfileMissingDependencyError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { WANTED_LOCKFILE } from '@pnpm/constants'
import PnpmError from '@pnpm/error'

export default class LockfileMissingDependencyError extends PnpmError {
constructor (relDepPath: string) {
const message = `Broken lockfile: no entry for '${relDepPath}' in ${WANTED_LOCKFILE}`
super('LOCKFILE_MISSING_DEPENDENCY', message, {
hint: 'This issue is probably caused by a badly resolved merge conflict.\n' +
'To fix the lockfile, run \'pnpm install --no-frozen-lockfile\'.',
})
}
}
7 changes: 3 additions & 4 deletions packages/filter-lockfile/src/filterLockfileByImporters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { WANTED_LOCKFILE } from '@pnpm/constants'
import PnpmError from '@pnpm/error'
import {
Lockfile,
PackageSnapshots,
Expand All @@ -9,6 +8,7 @@ import pnpmLogger from '@pnpm/logger'
import { DependenciesField, Registries } from '@pnpm/types'
import R = require('ramda')
import filterImporter from './filterImporter'
import LockfileMissingDependencyError from './LockfileMissingDependencyError'

const logger = pnpmLogger('lockfile')

Expand Down Expand Up @@ -61,10 +61,9 @@ function pkgAllDeps (
pkgAllDeps(next(), pickedPackages, opts)
}
for (const relDepPath of step.missing) {
const message = `No entry for "${relDepPath}" in ${WANTED_LOCKFILE}`
if (opts.failOnMissingDependencies) {
throw new PnpmError('LOCKFILE_MISSING_DEPENDENCY', message)
throw new LockfileMissingDependencyError(relDepPath)
}
logger.debug(message)
logger.debug(`No entry for "${relDepPath}" in ${WANTED_LOCKFILE}`)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { WANTED_LOCKFILE } from '@pnpm/constants'
import PnpmError from '@pnpm/error'
import {
Lockfile,
PackageSnapshots,
Expand All @@ -11,6 +10,7 @@ import { DependenciesField, Registries } from '@pnpm/types'
import * as dp from 'dependency-path'
import R = require('ramda')
import filterImporter from './filterImporter'
import LockfileMissingDependencyError from './LockfileMissingDependencyError'

const logger = pnpmLogger('lockfile')

Expand Down Expand Up @@ -122,11 +122,10 @@ function pkgAllDeps (
if (ctx.pickedPackages[relDepPath]) continue
const pkgSnapshot = ctx.pkgSnapshots[relDepPath]
if (!pkgSnapshot && !relDepPath.startsWith('link:')) {
const message = `No entry for "${relDepPath}" in ${WANTED_LOCKFILE}`
if (opts.failOnMissingDependencies) {
throw new PnpmError('LOCKFILE_MISSING_DEPENDENCY', message)
throw new LockfileMissingDependencyError(relDepPath)
}
logger.debug(message)
logger.debug(`No entry for "${relDepPath}" in ${WANTED_LOCKFILE}`)
continue
}
let installable!: boolean
Expand Down
2 changes: 1 addition & 1 deletion packages/filter-lockfile/test/filterByImporters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ test('filterByImporters(): fail on missing packages when failOnMissingDependenci
err = _
}
t.ok(err)
t.equal(err.message, `No entry for "/prod-dep-dep/1.0.0" in ${WANTED_LOCKFILE}`)
t.equal(err.message, `Broken lockfile: no entry for '/prod-dep-dep/1.0.0' in ${WANTED_LOCKFILE}`)
t.end()
})

Expand Down
99 changes: 55 additions & 44 deletions packages/supi/src/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,14 @@ export async function mutateModules (
return result

async function _install (): Promise<Array<{ rootDir: string, manifest: ProjectManifest }>> {
const frozenLockfile = opts.frozenLockfile ||
opts.frozenLockfileIfExists && ctx.existsWantedLockfile
if (
!opts.lockfileOnly &&
!opts.update &&
installsOnly &&
(
opts.frozenLockfile ||
opts.frozenLockfileIfExists && ctx.existsWantedLockfile ||
frozenLockfile ||
opts.preferFrozenLockfile &&
(!opts.pruneLockfileImporters || Object.keys(ctx.wantedLockfile.importers).length === ctx.projects.length) &&
ctx.existsWantedLockfile &&
Expand All @@ -206,48 +207,58 @@ export async function mutateModules (
}
} else {
logger.info({ message: 'Lockfile is up-to-date, resolution step is skipped', prefix: opts.lockfileDir })
await headless({
currentEngine: {
nodeVersion: opts.nodeVersion,
pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
},
currentLockfile: ctx.currentLockfile,
engineStrict: opts.engineStrict,
extraBinPaths: opts.extraBinPaths,
force: opts.force,
hoistedAliases: ctx.hoistedAliases,
hoistPattern: ctx.hoistPattern,
ignoreScripts: opts.ignoreScripts,
include: opts.include,
independentLeaves: opts.independentLeaves,
lockfileDir: ctx.lockfileDir,
ownLifecycleHooksStdio: opts.ownLifecycleHooksStdio,
packageManager: opts.packageManager,
pendingBuilds: ctx.pendingBuilds,
projects: ctx.projects as Array<{
binsDir: string,
buildIndex: number,
id: string,
manifest: ProjectManifest,
modulesDir: string,
rootDir: string,
pruneDirectDependencies?: boolean,
}>,
pruneStore: opts.pruneStore,
rawConfig: opts.rawConfig,
registries: opts.registries,
shamefullyHoist: ctx.shamefullyHoist,
sideEffectsCacheRead: opts.sideEffectsCacheRead,
sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
skipped: ctx.skipped,
storeController: opts.storeController,
storeDir: opts.storeDir,
unsafePerm: opts.unsafePerm,
userAgent: opts.userAgent,
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: ctx.wantedLockfile,
})
return projects
try {
await headless({
currentEngine: {
nodeVersion: opts.nodeVersion,
pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
},
currentLockfile: ctx.currentLockfile,
engineStrict: opts.engineStrict,
extraBinPaths: opts.extraBinPaths,
force: opts.force,
hoistedAliases: ctx.hoistedAliases,
hoistPattern: ctx.hoistPattern,
ignoreScripts: opts.ignoreScripts,
include: opts.include,
independentLeaves: opts.independentLeaves,
lockfileDir: ctx.lockfileDir,
ownLifecycleHooksStdio: opts.ownLifecycleHooksStdio,
packageManager: opts.packageManager,
pendingBuilds: ctx.pendingBuilds,
projects: ctx.projects as Array<{
binsDir: string,
buildIndex: number,
id: string,
manifest: ProjectManifest,
modulesDir: string,
rootDir: string,
pruneDirectDependencies?: boolean,
}>,
pruneStore: opts.pruneStore,
rawConfig: opts.rawConfig,
registries: opts.registries,
shamefullyHoist: ctx.shamefullyHoist,
sideEffectsCacheRead: opts.sideEffectsCacheRead,
sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
skipped: ctx.skipped,
storeController: opts.storeController,
storeDir: opts.storeDir,
unsafePerm: opts.unsafePerm,
userAgent: opts.userAgent,
virtualStoreDir: ctx.virtualStoreDir,
wantedLockfile: ctx.wantedLockfile,
})
return projects
} catch (error) {
if (frozenLockfile || error.code !== 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY') throw error
// 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.',
prefix: ctx.lockfileDir,
})
}
}
}

Expand Down
41 changes: 41 additions & 0 deletions packages/supi/test/lockfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1087,3 +1087,44 @@ test('lockfile is not getting broken if the used registry changes', async (t: ta
],
)
})

test('broken lockfile is fixed even if it seems like up-to-date at first. Unless frozenLockfile option is set to true', async (t: tape.Test) => {
const project = prepareEmpty(t)
await addDistTag('dep-of-pkg-with-1-dep', '100.0.0', 'latest')

const manifest = await addDependenciesToPackage({}, ['pkg-with-1-dep'], await testDefaults({ lockfileOnly: true }))
{
const lockfile = await project.readLockfile()
t.ok(lockfile.packages['/dep-of-pkg-with-1-dep/100.0.0'])
delete lockfile.packages['/dep-of-pkg-with-1-dep/100.0.0']
await writeYamlFile(WANTED_LOCKFILE, lockfile)
}

let err!: PnpmError
try {
await mutateModules([
{
buildIndex: 0,
manifest,
mutation: 'install',
rootDir: process.cwd(),
},
], await testDefaults({ frozenLockfile: true }))
} catch (_err) {
err = _err
}
t.equal(err.code, 'ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY')

await mutateModules([
{
buildIndex: 0,
manifest,
mutation: 'install',
rootDir: process.cwd(),
},
], await testDefaults({ preferFrozenLockfile: true }))

project.has('pkg-with-1-dep')
const lockfile = await project.readLockfile()
t.ok(lockfile.packages['/dep-of-pkg-with-1-dep/100.0.0'])
})

0 comments on commit 6a9edfe

Please sign in to comment.