diff --git a/lib/manager/npm/update/locked-dependency/index.spec.ts b/lib/manager/npm/update/locked-dependency/index.spec.ts index 9909f810f159b4bb962fbd0519773060c32ba66e..db50a395d846de6679ec10ae9073aaf42f6ef1c8 100644 --- a/lib/manager/npm/update/locked-dependency/index.spec.ts +++ b/lib/manager/npm/update/locked-dependency/index.spec.ts @@ -120,13 +120,37 @@ describe('manager/npm/update/locked-dependency/index', () => { const packageLock = JSON.parse(res.files['package-lock.json']); expect(packageLock.dependencies.express.version).toBe('4.1.0'); }); - it('returns if already remediated', async () => { + it('returns already-updated if already remediated exactly', async () => { config.depName = 'mime'; config.currentVersion = '1.2.10'; config.newVersion = '1.2.11'; const res = await updateLockedDependency(config); expect(res.status).toBe('already-updated'); }); + it('returns already-updated if already remediated higher', async () => { + config.depName = 'mime'; + config.currentVersion = '1.2.9'; + config.newVersion = '1.2.10'; + config.allowHigherOrRemoved = true; + const res = await updateLockedDependency(config); + expect(res.status).toBe('already-updated'); + }); + it('returns already-updated if not found', async () => { + config.depName = 'notfound'; + config.currentVersion = '1.2.9'; + config.newVersion = '1.2.10'; + config.allowHigherOrRemoved = true; + const res = await updateLockedDependency(config); + expect(res.status).toBe('already-updated'); + }); + it('returns update-failed if other, lower version found', async () => { + config.depName = 'mime'; + config.currentVersion = '1.2.5'; + config.newVersion = '1.2.15'; + config.allowHigherOrRemoved = true; + const res = await updateLockedDependency(config); + expect(res.status).toBe('update-failed'); + }); it('remediates mime', async () => { config.depName = 'mime'; config.currentVersion = '1.2.11'; diff --git a/lib/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts b/lib/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts index 619975e72429dec22bdef1caf12776293d970c5b..3c3e3c303665ca36c8c1641485768d8e093246ae 100644 --- a/lib/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts +++ b/lib/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts @@ -36,6 +36,11 @@ describe('manager/npm/update/locked-dependency/package-lock/get-locked', () => { }, ]); }); + it('finds any version', () => { + expect(getLockedDependencies(packageLockJson, 'send', null)).toHaveLength( + 2 + ); + }); it('finds bundled dependency', () => { expect( getLockedDependencies(bundledPackageLockJson, 'ansi-regex', '3.0.0') diff --git a/lib/manager/npm/update/locked-dependency/package-lock/get-locked.ts b/lib/manager/npm/update/locked-dependency/package-lock/get-locked.ts index d726174ad7e752db3e054fe19bd93051e2c5ccf7..82acfdea1a36da458f142d0a7b3e3d29e4d3fe93 100644 --- a/lib/manager/npm/update/locked-dependency/package-lock/get-locked.ts +++ b/lib/manager/npm/update/locked-dependency/package-lock/get-locked.ts @@ -5,7 +5,7 @@ import type { PackageLockDependency, PackageLockOrEntry } from './types'; export function getLockedDependencies( entry: PackageLockOrEntry, depName: string, - currentVersion: string, + currentVersion: string | null, bundled = false ): PackageLockDependency[] { let res: PackageLockDependency[] = []; @@ -15,7 +15,7 @@ export function getLockedDependencies( return []; } const dep = dependencies[depName]; - if (dep?.version === currentVersion) { + if (dep && (currentVersion === null || dep?.version === currentVersion)) { if (bundled || entry.bundled) { dep.bundled = true; } diff --git a/lib/manager/npm/update/locked-dependency/package-lock/index.ts b/lib/manager/npm/update/locked-dependency/package-lock/index.ts index d910f84b00141d9c6b29e7d6dc15104d1de5f5dd..d54dae102306f6845d1174776bf4021c924238b7 100644 --- a/lib/manager/npm/update/locked-dependency/package-lock/index.ts +++ b/lib/manager/npm/update/locked-dependency/package-lock/index.ts @@ -22,6 +22,7 @@ export async function updateLockedDependency( lockFile, lockFileContent, allowParentUpdates = true, + allowHigherOrRemoved = false, } = config; logger.debug( `npm.updateLockedDependency: ${depName}@${currentVersion} -> ${newVersion} [${lockFile}]` @@ -66,10 +67,42 @@ export async function updateLockedDependency( ); status = 'already-updated'; } else { - logger.debug( - `${depName}@${currentVersion} not found in ${lockFile} - cannot update` - ); - status = 'update-failed'; + if (allowHigherOrRemoved) { + // it's acceptable if the package is no longer present + const anyVersionLocked = getLockedDependencies( + packageLockJson, + depName, + null + ); + if (anyVersionLocked.length) { + if ( + anyVersionLocked.every((dep) => + semver.isGreaterThan(dep.version, newVersion) + ) + ) { + logger.debug( + `${depName} found in ${lockFile} with higher version - looks like it's already updated` + ); + status = 'already-updated'; + } else { + logger.debug( + { anyVersionLocked }, + `Found alternative versions of qs` + ); + status = 'update-failed'; + } + } else { + logger.debug( + `${depName} not found in ${lockFile} - looks like it's already removed` + ); + status = 'already-updated'; + } + } else { + logger.debug( + `${depName}@${currentVersion} not found in ${lockFile} - cannot update` + ); + status = 'update-failed'; + } } // Don't return {} if we're a parent update or else the whole update will fail // istanbul ignore if: too hard to replicate diff --git a/lib/manager/types.ts b/lib/manager/types.ts index 05857baa5ef0387971a2e33620c0acc5ba0edad6..29bf3e04ceccb7ff4130ae016f098a9568f9491e 100644 --- a/lib/manager/types.ts +++ b/lib/manager/types.ts @@ -229,6 +229,7 @@ export interface UpdateLockedConfig { currentVersion?: string; newVersion?: string; allowParentUpdates?: boolean; + allowHigherOrRemoved?: boolean; } export interface UpdateLockedResult { diff --git a/lib/workers/branch/get-updated.ts b/lib/workers/branch/get-updated.ts index 2a596f47588de9b3c0a8f9745034398ea746e0ac..2eebdd09b0d49d34024ef6e4b44b07110a2e8417 100644 --- a/lib/workers/branch/get-updated.ts +++ b/lib/workers/branch/get-updated.ts @@ -75,6 +75,7 @@ export async function getUpdatedPackageFiles( packageFileContent, lockFileContent, allowParentUpdates: true, + allowHigherOrRemoved: true, }); if (reuseExistingBranch && status !== 'already-updated') { logger.debug(