diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts index a9997153188c79..114ad5d8995d2f 100644 --- a/lib/modules/manager/bundler/artifacts.spec.ts +++ b/lib/modules/manager/bundler/artifacts.spec.ts @@ -881,6 +881,38 @@ describe('modules/manager/bundler/artifacts', () => { { cmd: 'bundler lock --update foo bar' }, ]); }); + + it('handles failure of strict updating', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: '', + stderr: 'version solving has failed', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); + const execSnapshots = mockExecSequence([ + execError, + { stdout: '', stderr: '' }, + ]); + git.getRepoStatus.mockResolvedValueOnce( + partial({ + modified: ['Gemfile.lock'], + }), + ); + + const res = await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo', updateType: 'minor' }], + newPackageFileContent: '{}', + config, + }); + + expect(res).toMatchObject([{ file: { path: 'Gemfile.lock' } }]); + expect(execSnapshots).toMatchObject([ + { cmd: 'bundler lock --minor --strict --update foo' }, + { cmd: 'bundler lock --minor --conservative --update foo' }, + ]); + }); }); }); }); diff --git a/lib/modules/manager/bundler/artifacts.ts b/lib/modules/manager/bundler/artifacts.ts index 94fc4ad54c506c..e7c5f7dd43d3a2 100644 --- a/lib/modules/manager/bundler/artifacts.ts +++ b/lib/modules/manager/bundler/artifacts.ts @@ -245,6 +245,25 @@ export async function updateArtifacts( memCache.set('bundlerArtifactsError', BUNDLER_INVALID_CREDENTIALS); throw new Error(BUNDLER_INVALID_CREDENTIALS); } + if (recursionLimit > 0 && output.includes('version solving has failed')) { + logger.debug('Failed to lock strictly, retrying non-strict'); + const newConfig = { + ...config, + postUpdateOptions: [ + ...(config.postUpdateOptions ?? []), + 'bundlerConservative', + ], + }; + return updateArtifacts( + { + packageFileName, + updatedDeps, + newPackageFileContent, + config: newConfig, + }, + recursionLimit - 1, + ); + } const resolveMatches: string[] = getResolvedPackages(output).filter( (depName) => !updatedDepNames.includes(depName), );