diff --git a/lib/workers/repository/update/branch/auto-replace.spec.ts b/lib/workers/repository/update/branch/auto-replace.spec.ts index 7233a185640..72ebf9f7fd7 100644 --- a/lib/workers/repository/update/branch/auto-replace.spec.ts +++ b/lib/workers/repository/update/branch/auto-replace.spec.ts @@ -263,7 +263,7 @@ describe('workers/repository/update/branch/auto-replace', () => { await expect(res).rejects.toThrow(WORKER_FILE_UPDATE_FAILED); }); - it('fails with digest mismatch', async () => { + it('updates digest when only digest changes and no replaceString is set', async () => { const dockerfile = codeBlock` FROM java:11@sha256-1234 as build `; @@ -277,8 +277,8 @@ describe('workers/repository/update/branch/auto-replace', () => { upgrade.newValue = '11'; upgrade.newDigest = 'sha256-5678'; upgrade.packageFile = 'Dockerfile'; - const res = doAutoReplace(upgrade, dockerfile, reuseExistingBranch); - await expect(res).rejects.toThrow(WORKER_FILE_UPDATE_FAILED); + const res = await doAutoReplace(upgrade, dockerfile, reuseExistingBranch); + expect(res).toBe('FROM java:11@sha256-5678 as build'); }); it('updates with docker replacement', async () => { @@ -1430,6 +1430,30 @@ describe('workers/repository/update/branch/auto-replace', () => { ); }); + it('jsonata: update currentDigest with currentValue captured', async () => { + const source = + '[ { "version": "1.2.3", "digest": "abcdef", "package": "foo" } ]'; + upgrade.manager = 'jsonata'; + upgrade.depName = 'foo'; + upgrade.currentValue = '1.2.3'; + upgrade.currentDigest = 'abcdef'; + upgrade.newDigest = 'badbeef'; + upgrade.depIndex = 0; + upgrade.packageFile = 'deps.json'; + // @ts-expect-error -- TODO: improve typing + upgrade.fileFormat = 'json'; + // @ts-expect-error -- TODO: improve typing + upgrade.datasourceTemplate = 'github-releases'; + // @ts-expect-error -- TODO: improve typing + upgrade.matchStrings = [ + '*.{"depName": package, "currentDigest": digest, "currentValue": version }', + ]; + const res = await doAutoReplace(upgrade, source, reuseExistingBranch); + expect(res).toBe( + '[ { "version": "1.2.3", "digest": "badbeef", "package": "foo" } ]', + ); + }); + it('github-actions: updates with newValue only', async () => { const githubAction = codeBlock` jobs: diff --git a/lib/workers/repository/update/branch/auto-replace.ts b/lib/workers/repository/update/branch/auto-replace.ts index ff55b41f805..d967142e192 100644 --- a/lib/workers/repository/update/branch/auto-replace.ts +++ b/lib/workers/repository/update/branch/auto-replace.ts @@ -197,28 +197,6 @@ async function checkExistingBranch( return existingContent; } -/** - * Check if an update from `current` to `newString` should be performed and return 1 if so. - * - * @remarks - * Useful for counting the number of updates to do. - * - * @param current The current value (if undefined then no update is required) - * @param newString The new value (if undefined then no update is required) - * - * @returns 1 if `current !== newString` and 0 if they are equal or at least one is undefined. - */ -function updatedToInt( - current: string | undefined | null, - newString: string | undefined | null, -): number { - if (current && newString && newString !== current) { - return 1; - } else { - return 0; - } -} - export async function doAutoReplace( upgrade: BranchUpgradeConfig, existingContent: string, @@ -247,27 +225,31 @@ export async function doAutoReplace( if (reuseExistingBranch) { return await checkExistingBranch(upgrade, existingContent); } - - // count how many strings need to be updated - const changedCount = - updatedToInt(depName, newName) + - updatedToInt(currentValue, newValue) + - updatedToInt(currentDigest, newDigest); - if (changedCount > 1) { - logger.debug( - { packageFile, depName, changedCount }, - 'Multiple changed values, might need special handling (#36461)', - ); + const valueChanging = + isString(currentValue) && isString(newValue) && currentValue !== newValue; + const digestChanging = + isString(currentDigest) && + isString(newDigest) && + currentDigest !== newDigest; + let replaceWithoutReplaceString = + isString(newName) && + newName !== depName && + (isUndefined(upgrade.replaceString) || + !upgrade.replaceString?.includes(depName!)); + // fallback must contain the field being updated, else the replacement is + // a no-op for managers where value and digest live in separate tokens + let replaceString = upgrade.replaceString; + if (isUndefined(replaceString)) { + if (valueChanging && digestChanging) { + // no single fallback covers both — use the per-field path + replaceWithoutReplaceString = true; + replaceString = currentValue; + } else if (digestChanging) { + replaceString = currentDigest; + } else { + replaceString = currentValue ?? currentDigest; + } } - - const replaceWithoutReplaceString = - (isString(newName) && - newName !== depName && - (isUndefined(upgrade.replaceString) || - !upgrade.replaceString?.includes(depName!))) || - // for jsonata manager, fixes #36461 - (isUndefined(upgrade.replaceString) && changedCount > 1); - const replaceString = upgrade.replaceString ?? currentValue ?? currentDigest; logger.trace({ depName, replaceString }, 'autoReplace replaceString'); let searchIndex: number; if (replaceWithoutReplaceString) {