diff --git a/docs/usage/ruby.md b/docs/usage/ruby.md index 1848b217af112edf40f9fd07c49407c45e9f4195..aa35baf1497da8de60485a75b044838204d3c53b 100644 --- a/docs/usage/ruby.md +++ b/docs/usage/ruby.md @@ -17,7 +17,9 @@ Renovate supports upgrading dependencies in Bundler's Gemfiles and their accompa ## Warnings -Renovate doesn't update dependencies without a version constraint. +When using `"rangeStrategy": "update-lockfile"`, all gems listed in the `Gemfile` will be updated, even if they do not have a version specified. + +When using other `rangeStrategy` options, Renovate doesn't update dependencies without a version constraint. Example: `gem 'some-gem', '~> 1.2.3'` will update `some-gem` if a new version matching the constraint is available, but `gem 'some-gem'` won't. If you always want to have the latest available version, consider specifying `gem 'some-gem', '> 0'`. diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts index b0e47017b197c57f9b8736c9346601af87162e00..19c640a7b20ec563348a6dec996a0a790ef65df0 100644 --- a/lib/modules/manager/bundler/artifacts.spec.ts +++ b/lib/modules/manager/bundler/artifacts.spec.ts @@ -16,6 +16,7 @@ import { ExecError } from '../../../util/exec/exec-error'; import type { StatusResult } from '../../../util/git/types'; import * as _datasource from '../../datasource'; import type { UpdateArtifactsConfig } from '../types'; +import { buildArgs } from './artifacts'; import * as _bundlerHostRules from './host-rules'; import { updateArtifacts } from '.'; @@ -50,230 +51,74 @@ const updatedGemfileLock = { }; describe('modules/manager/bundler/artifacts', () => { - beforeEach(() => { - jest.resetAllMocks(); - jest.resetModules(); - - delete process.env.GEM_HOME; - - env.getChildProcessEnv.mockReturnValue(envMock.basic); - bundlerHostRules.findAllAuthenticatable.mockReturnValue([]); - docker.resetPrefetchedImages(); - - GlobalConfig.set(adminConfig); - fs.ensureCacheDir.mockResolvedValue('/tmp/cache/others/gem'); - }); + describe('buildArgs', () => { + it('returns only --update arg when no config is specified', () => { + const config: UpdateArtifactsConfig = {}; + expect(buildArgs(config)).toStrictEqual(['--update']); + }); - afterEach(() => { - GlobalConfig.reset(); - }); + it('adds --conservative when bundlerConservative is set as postUpdateOption', () => { + const config: UpdateArtifactsConfig = { + postUpdateOptions: ['bundlerConservative'], + }; + expect(buildArgs(config)).toStrictEqual(['--conservative', '--update']); + }); - it('returns null by default', async () => { - expect( - await updateArtifacts({ - packageFileName: '', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: '', - config, - }) - ).toBeNull(); - }); + it('adds --patch and --strict when update type is patch', () => { + const config: UpdateArtifactsConfig = { updateType: 'patch' }; + expect(buildArgs(config)).toStrictEqual([ + '--patch', + '--strict', + '--update', + ]); + }); - it('returns null if Gemfile.lock was not changed', async () => { - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: [] as string[], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', - config, - }) - ).toBeNull(); - expect(execSnapshots).toMatchObject([ - { cmd: 'bundler lock --update foo bar' }, - ]); + it('adds --minor and --strict when update type is minor', () => { + const config: UpdateArtifactsConfig = { updateType: 'minor' }; + expect(buildArgs(config)).toStrictEqual([ + '--minor', + '--strict', + '--update', + ]); + }); }); - it('works for default binarySource', async () => { - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce(null); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', - config, - }) - ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchObject([ - { cmd: 'bundler lock --update foo bar' }, - ]); - }); + describe('updateArtifacts', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.resetModules(); - it('works explicit global binarySource', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce(null); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', - config, - }) - ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchObject([ - { cmd: 'bundler lock --update foo bar' }, - ]); - }); + delete process.env.GEM_HOME; - it('supports conservative mode', async () => { - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce(null); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', - config: { - ...config, - postUpdateOptions: [ - ...(config.postUpdateOptions ?? []), - 'bundlerConservative', - ], - }, - }) - ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchObject([ - expect.objectContaining({ - cmd: 'bundler lock --conservative --update foo bar', - }), - ]); - }); + env.getChildProcessEnv.mockReturnValue(envMock.basic); + bundlerHostRules.findAllAuthenticatable.mockReturnValue([]); + docker.resetPrefetchedImages(); - it('supports install mode', async () => { - GlobalConfig.set({ - ...adminConfig, - binarySource: 'install', + GlobalConfig.set(adminConfig); + fs.ensureCacheDir.mockResolvedValue('/tmp/cache/others/gem'); }); - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce('1.2.0'); - // bundler - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [{ version: '1.17.2' }, { version: '2.3.5' }], - }); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', - config, - }) - ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchObject([ - { cmd: 'install-tool ruby 1.2.0' }, - { cmd: 'install-tool bundler 2.3.5' }, - { cmd: 'ruby --version' }, - { cmd: 'bundler lock --update foo bar' }, - ]); - }); - describe('Docker', () => { - beforeEach(() => { - GlobalConfig.set({ - ...adminConfig, - binarySource: 'docker', - }); + afterEach(() => { + GlobalConfig.reset(); }); - it('.ruby-version', async () => { - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce('1.2.0'); - // bundler - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [{ version: '1.17.2' }, { version: '2.3.5' }], - }); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + it('returns null by default', async () => { expect( await updateArtifacts({ - packageFileName: 'Gemfile', + packageFileName: '', updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', + newPackageFileContent: '', config, }) - ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.2.0' + - ' && ' + - 'install-tool bundler 2.3.5' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, - ]); + ).toBeNull(); }); - it('constraints options', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + it('returns null if Gemfile.lock was not changed', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [{ version: '1.17.2' }, { version: '2.3.5' }], - }); - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [ - { version: '1.0.0' }, - { version: '1.2.0' }, - { version: '1.3.0' }, - ], - }); + fs.writeLocalFile.mockResolvedValueOnce(); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], + modified: [] as string[], } as StatusResult); fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( @@ -281,56 +126,17 @@ describe('modules/manager/bundler/artifacts', () => { packageFileName: 'Gemfile', updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], newPackageFileContent: 'Updated Gemfile content', - config: { - ...config, - constraints: { - ruby: '1.2.5', - bundler: '3.2.1', - }, - }, + config, }) - ).toEqual([updatedGemfileLock]); + ).toBeNull(); expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.2.5' + - ' && ' + - 'install-tool bundler 3.2.1' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, + { cmd: 'bundler lock --update foo bar' }, ]); }); - it('invalid constraints options', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + it('works for default binarySource', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - // ruby - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [ - { version: '1.0.0' }, - { version: '1.2.0' }, - { version: '1.3.0' }, - ], - }); - // bundler - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [{ version: '1.17.2' }, { version: '2.3.5' }], - }); + fs.readLocalFile.mockResolvedValueOnce(null); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], @@ -341,61 +147,18 @@ describe('modules/manager/bundler/artifacts', () => { packageFileName: 'Gemfile', updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], newPackageFileContent: 'Updated Gemfile content', - config: { - ...config, - constraints: { - ruby: 'foo', - bundler: 'bar', - }, - }, + config, }) ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.3.0' + - ' && ' + - 'install-tool bundler 2.3.5' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, + { cmd: 'bundler lock --update foo bar' }, ]); }); - it('injects bundler host configuration environment variables', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + it('works explicit global binarySource', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce('1.2.0'); - // bundler - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [{ version: '1.17.2' }, { version: '2.3.5' }], - }); - bundlerHostRules.findAllAuthenticatable.mockReturnValue([ - { - hostType: 'bundler', - matchHost: 'gems.private.com', - resolvedHost: 'gems.private.com', - username: 'some-user', - password: 'some-password', - }, - ]); - bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( - 'some-user:some-password' - ); + fs.readLocalFile.mockResolvedValueOnce(null); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], @@ -410,56 +173,13 @@ describe('modules/manager/bundler/artifacts', () => { }) ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e BUNDLE_GEMS__PRIVATE__COM ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.2.0' + - ' && ' + - 'install-tool bundler 2.3.5' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, + { cmd: 'bundler lock --update foo bar' }, ]); }); - it('injects bundler host configuration as command with bundler < 2', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + it('supports conservative mode and updateType option', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce('1.2.0'); - // ruby - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [ - { version: '1.0.0' }, - { version: '1.2.0' }, - { version: '1.3.0' }, - ], - }); - bundlerHostRules.findAllAuthenticatable.mockReturnValue([ - { - hostType: 'bundler', - matchHost: 'gems-private.com', - resolvedHost: 'gems-private.com', - username: 'some-user', - password: 'some-password', - }, - ]); - bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( - 'some-user:some-password' - ); + fs.readLocalFile.mockResolvedValueOnce(null); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], @@ -472,138 +192,32 @@ describe('modules/manager/bundler/artifacts', () => { newPackageFileContent: 'Updated Gemfile content', config: { ...config, - constraints: { - bundler: '1.2', - }, + updateType: 'patch', + postUpdateOptions: [ + ...(config.postUpdateOptions ?? []), + 'bundlerConservative', + ], }, }) ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.2.0' + - ' && ' + - 'install-tool bundler 1.2' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler config --local gems-private.com some-user:some-password' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, + expect.objectContaining({ + cmd: 'bundler lock --patch --strict --conservative --update foo bar', + }), ]); }); - it('injects bundler host configuration as command with bundler >= 2', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.readLocalFile.mockResolvedValueOnce('1.2.0'); - // ruby - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [ - { version: '1.0.0' }, - { version: '1.2.0' }, - { version: '1.3.0' }, - ], + it('supports install mode', async () => { + GlobalConfig.set({ + ...adminConfig, + binarySource: 'install', }); - bundlerHostRules.findAllAuthenticatable.mockReturnValue([ - { - hostType: 'bundler', - matchHost: 'gems-private.com', - resolvedHost: 'gems-private.com', - username: 'some-user', - password: 'some-password', - }, - ]); - bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( - 'some-user:some-password' - ); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], - newPackageFileContent: 'Updated Gemfile content', - config: { - ...config, - constraints: { - bundler: '2.1', - }, - }, - }) - ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.2.0' + - ' && ' + - 'install-tool bundler 2.1' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler config set --local gems-private.com some-user:some-password' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, - ]); - }); - - it('injects bundler host configuration as command with bundler == latest', async () => { - GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); - // ruby - datasource.getPkgReleases.mockResolvedValueOnce({ - releases: [ - { version: '1.0.0' }, - { version: '1.2.0' }, - { version: '1.3.0' }, - ], - }); // bundler datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '1.17.2' }, { version: '2.3.5' }], }); - bundlerHostRules.findAllAuthenticatable.mockReturnValue([ - { - hostType: 'bundler', - matchHost: 'gems-private.com', - resolvedHost: 'gems-private.com', - username: 'some-user', - password: 'some-password', - }, - ]); - bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( - 'some-user:some-password' - ); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], @@ -618,84 +232,455 @@ describe('modules/manager/bundler/artifacts', () => { }) ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchObject([ - { cmd: 'docker pull renovate/sidecar' }, - { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, - { - cmd: - 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + - '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + - '-v "/tmp/cache":"/tmp/cache" ' + - '-e GEM_HOME ' + - '-e BUILDPACK_CACHE_DIR ' + - '-e CONTAINERBASE_CACHE_DIR ' + - '-w "/tmp/github/some/repo" ' + - 'renovate/sidecar' + - ' bash -l -c "' + - 'install-tool ruby 1.2.0' + - ' && ' + - 'install-tool bundler 1.3.0' + - ' && ' + - 'ruby --version' + - ' && ' + - 'bundler config set --local gems-private.com some-user:some-password' + - ' && ' + - 'bundler lock --update foo bar' + - '"', - }, + { cmd: 'install-tool ruby 1.2.0' }, + { cmd: 'install-tool bundler 2.3.5' }, + { cmd: 'ruby --version' }, + { cmd: 'bundler lock --update foo bar' }, ]); }); - }); - it('returns error when failing in lockFileMaintenance true', async () => { - const execError = new ExecError('Exec error', { - cmd: '', - stdout: ' foo was resolved to', - stderr: '', - options: { encoding: 'utf8' }, - }); - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); - const execSnapshots = mockExecAll(execError); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [], - newPackageFileContent: '{}', - config: { - ...config, - isLockFileMaintenance: true, - }, - }) - ).toMatchObject([{ artifactError: { lockFile: 'Gemfile.lock' } }]); - expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); - }); + describe('Docker', () => { + beforeEach(() => { + GlobalConfig.set({ + ...adminConfig, + binarySource: 'docker', + }); + }); - it('performs lockFileMaintenance', async () => { - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(); - const execSnapshots = mockExecAll(); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); - expect( - await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [], - newPackageFileContent: '{}', - config: { - ...config, - isLockFileMaintenance: true, - }, - }) - ).not.toBeNull(); - expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); - }); + it('.ruby-version', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + fs.readLocalFile.mockResolvedValueOnce('1.2.0'); + // bundler + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.17.2' }, { version: '2.3.5' }], + }); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.2.0' + + ' && ' + + 'install-tool bundler 2.3.5' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + + it('constraints options', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.17.2' }, { version: '2.3.5' }], + }); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.2.0' }, + { version: '1.3.0' }, + ], + }); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config: { + ...config, + constraints: { + ruby: '1.2.5', + bundler: '3.2.1', + }, + }, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.2.5' + + ' && ' + + 'install-tool bundler 3.2.1' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + + it('invalid constraints options', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + // ruby + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.2.0' }, + { version: '1.3.0' }, + ], + }); + // bundler + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.17.2' }, { version: '2.3.5' }], + }); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config: { + ...config, + constraints: { + ruby: 'foo', + bundler: 'bar', + }, + }, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.3.0' + + ' && ' + + 'install-tool bundler 2.3.5' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + + it('injects bundler host configuration environment variables', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + fs.readLocalFile.mockResolvedValueOnce('1.2.0'); + // bundler + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.17.2' }, { version: '2.3.5' }], + }); + bundlerHostRules.findAllAuthenticatable.mockReturnValue([ + { + hostType: 'bundler', + matchHost: 'gems.private.com', + resolvedHost: 'gems.private.com', + username: 'some-user', + password: 'some-password', + }, + ]); + bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( + 'some-user:some-password' + ); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e BUNDLE_GEMS__PRIVATE__COM ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.2.0' + + ' && ' + + 'install-tool bundler 2.3.5' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + + it('injects bundler host configuration as command with bundler < 2', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + fs.readLocalFile.mockResolvedValueOnce('1.2.0'); + // ruby + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.2.0' }, + { version: '1.3.0' }, + ], + }); + bundlerHostRules.findAllAuthenticatable.mockReturnValue([ + { + hostType: 'bundler', + matchHost: 'gems-private.com', + resolvedHost: 'gems-private.com', + username: 'some-user', + password: 'some-password', + }, + ]); + bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( + 'some-user:some-password' + ); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config: { + ...config, + constraints: { + bundler: '1.2', + }, + }, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.2.0' + + ' && ' + + 'install-tool bundler 1.2' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler config --local gems-private.com some-user:some-password' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + + it('injects bundler host configuration as command with bundler >= 2', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + fs.readLocalFile.mockResolvedValueOnce('1.2.0'); + // ruby + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.2.0' }, + { version: '1.3.0' }, + ], + }); + bundlerHostRules.findAllAuthenticatable.mockReturnValue([ + { + hostType: 'bundler', + matchHost: 'gems-private.com', + resolvedHost: 'gems-private.com', + username: 'some-user', + password: 'some-password', + }, + ]); + bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( + 'some-user:some-password' + ); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config: { + ...config, + constraints: { + bundler: '2.1', + }, + }, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.2.0' + + ' && ' + + 'install-tool bundler 2.1' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler config set --local gems-private.com some-user:some-password' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + + it('injects bundler host configuration as command with bundler == latest', async () => { + GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + fs.readLocalFile.mockResolvedValueOnce('1.2.0'); + // ruby + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.2.0' }, + { version: '1.3.0' }, + ], + }); + // bundler + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.17.2' }, { version: '2.3.5' }], + }); + bundlerHostRules.findAllAuthenticatable.mockReturnValue([ + { + hostType: 'bundler', + matchHost: 'gems-private.com', + resolvedHost: 'gems-private.com', + username: 'some-user', + password: 'some-password', + }, + ]); + bundlerHostRules.getAuthenticationHeaderValue.mockReturnValue( + 'some-user:some-password' + ); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }], + newPackageFileContent: 'Updated Gemfile content', + config, + }) + ).toEqual([updatedGemfileLock]); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/sidecar' }, + { cmd: 'docker ps --filter name=renovate_sidecar -aq' }, + { + cmd: + 'docker run --rm --name=renovate_sidecar --label=renovate_child ' + + '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' + + '-v "/tmp/cache":"/tmp/cache" ' + + '-e GEM_HOME ' + + '-e BUILDPACK_CACHE_DIR ' + + '-e CONTAINERBASE_CACHE_DIR ' + + '-w "/tmp/github/some/repo" ' + + 'renovate/sidecar' + + ' bash -l -c "' + + 'install-tool ruby 1.2.0' + + ' && ' + + 'install-tool bundler 1.3.0' + + ' && ' + + 'ruby --version' + + ' && ' + + 'bundler config set --local gems-private.com some-user:some-password' + + ' && ' + + 'bundler lock --update foo bar' + + '"', + }, + ]); + }); + }); - describe('Error handling', () => { it('returns error when failing in lockFileMaintenance true', async () => { const execError = new ExecError('Exec error', { cmd: '', @@ -704,6 +689,7 @@ describe('modules/manager/bundler/artifacts', () => { options: { encoding: 'utf8' }, }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + fs.writeLocalFile.mockResolvedValueOnce(null as never); const execSnapshots = mockExecAll(execError); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], @@ -718,47 +704,18 @@ describe('modules/manager/bundler/artifacts', () => { isLockFileMaintenance: true, }, }) - ).toMatchObject([ - { - artifactError: { - lockFile: 'Gemfile.lock', - }, - }, - ]); + ).toMatchObject([{ artifactError: { lockFile: 'Gemfile.lock' } }]); expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); }); - it('rethrows for temporary error', async () => { - const execError = new ExecError(TEMPORARY_ERROR, { - cmd: '', - stdout: '', - stderr: '', - options: { encoding: 'utf8' }, - }); - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - mockExecAll(execError); - await expect( - updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [], - newPackageFileContent: '{}', - config: { - ...config, - isLockFileMaintenance: true, - }, - }) - ).rejects.toThrow(TEMPORARY_ERROR); - }); - - it('handles "Could not parse object" error', async () => { - const execError = new ExecError('fatal: Could not parse object', { - cmd: '', - stdout: 'but that version could not be found', - stderr: '', - options: { encoding: 'utf8' }, - }); + it('performs lockFileMaintenance', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - mockExecAll(execError); + fs.writeLocalFile.mockResolvedValueOnce(); + const execSnapshots = mockExecAll(); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -767,64 +724,144 @@ describe('modules/manager/bundler/artifacts', () => { config: { ...config, isLockFileMaintenance: true, + updateType: 'patch', // This will have no effect together with isLockFileMaintenance }, }) - ).toMatchObject([{ artifactError: { lockFile: 'Gemfile.lock' } }]); + ).not.toBeNull(); + expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); }); - it('throws on authentication errors', async () => { - const execError = new ExecError('Exec error', { - cmd: '', - stdout: 'Please supply credentials for this source', - stderr: 'Please make sure you have the correct access rights', - options: { encoding: 'utf8' }, + describe('Error handling', () => { + it('returns error when failing in lockFileMaintenance true', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: ' foo was resolved to', + stderr: '', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + const execSnapshots = mockExecAll(execError); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchObject([ + { + artifactError: { + lockFile: 'Gemfile.lock', + }, + }, + ]); + expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); }); - fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - mockExecAll(execError); - await expect( - updateArtifacts({ + + it('rethrows for temporary error', async () => { + const execError = new ExecError(TEMPORARY_ERROR, { + cmd: '', + stdout: '', + stderr: '', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + mockExecAll(execError); + await expect( + updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).rejects.toThrow(TEMPORARY_ERROR); + }); + + it('handles "Could not parse object" error', async () => { + const execError = new ExecError('fatal: Could not parse object', { + cmd: '', + stdout: 'but that version could not be found', + stderr: '', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + mockExecAll(execError); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchObject([{ artifactError: { lockFile: 'Gemfile.lock' } }]); + }); + + it('throws on authentication errors', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: 'Please supply credentials for this source', + stderr: 'Please make sure you have the correct access rights', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + mockExecAll(execError); + await expect( + updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).rejects.toThrow(BUNDLER_INVALID_CREDENTIALS); + }); + + it('handles recursive resolved dependencies', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: 'foo was resolved to foo', + stderr: 'bar was resolved to bar', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); + const execSnapshots = mockExecSequence([ + execError, + { stdout: '', stderr: '' }, + ]); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + + const res = await updateArtifacts({ packageFileName: 'Gemfile', - updatedDeps: [], + updatedDeps: [{ depName: 'foo' }], newPackageFileContent: '{}', config: { ...config, - isLockFileMaintenance: true, + isLockFileMaintenance: false, }, - }) - ).rejects.toThrow(BUNDLER_INVALID_CREDENTIALS); - }); + }); - it('handles recursive resolved dependencies', async () => { - const execError = new ExecError('Exec error', { - cmd: '', - stdout: 'foo was resolved to foo', - stderr: 'bar was resolved to bar', - options: { encoding: 'utf8' }, + expect(res).toMatchObject([{ file: { path: 'Gemfile.lock' } }]); + expect(execSnapshots).toMatchObject([ + { cmd: 'bundler lock --update foo' }, + { cmd: 'bundler lock --update foo bar' }, + ]); }); - fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); - const execSnapshots = mockExecSequence([ - execError, - { stdout: '', stderr: '' }, - ]); - git.getRepoStatus.mockResolvedValueOnce({ - modified: ['Gemfile.lock'], - } as StatusResult); - - const res = await updateArtifacts({ - packageFileName: 'Gemfile', - updatedDeps: [{ depName: 'foo' }], - newPackageFileContent: '{}', - config: { - ...config, - isLockFileMaintenance: false, - }, - }); - - expect(res).toMatchObject([{ file: { path: 'Gemfile.lock' } }]); - expect(execSnapshots).toMatchObject([ - { cmd: 'bundler lock --update foo' }, - { cmd: 'bundler lock --update foo bar' }, - ]); }); }); }); diff --git a/lib/modules/manager/bundler/artifacts.ts b/lib/modules/manager/bundler/artifacts.ts index 46aeed9087b742bd4c7e28a98eb8565f7af2610f..317cc4e4b10690e8f34e7aa5c6836606706de441 100644 --- a/lib/modules/manager/bundler/artifacts.ts +++ b/lib/modules/manager/bundler/artifacts.ts @@ -19,7 +19,11 @@ import { getRepoStatus } from '../../../util/git'; import { newlineRegex, regEx } from '../../../util/regex'; import { addSecretForSanitizing } from '../../../util/sanitize'; import { isValid } from '../../versioning/ruby'; -import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; +import type { + UpdateArtifact, + UpdateArtifactsConfig, + UpdateArtifactsResult, +} from '../types'; import { getBundlerConstraint, getRubyConstraint } from './common'; import { findAllAuthenticatable, @@ -28,6 +32,26 @@ import { const hostConfigVariablePrefix = 'BUNDLE_'; +export function buildArgs(config: UpdateArtifactsConfig): string[] { + const args: string[] = []; + // --major is the default and does not need to be handled separately. + switch (config.updateType) { + case 'patch': + args.push('--patch', '--strict'); + break; + case 'minor': + args.push('--minor', '--strict'); + break; + } + + if (config.postUpdateOptions?.includes('bundlerConservative')) { + args.push('--conservative'); + } + + args.push('--update'); + return args; +} + function buildBundleHostVariable(hostRule: HostRule): Record<string, string> { if (!hostRule.resolvedHost || hostRule.resolvedHost.includes('-')) { return {}; @@ -81,11 +105,7 @@ export async function updateArtifacts( return null; } - const args = [ - config.postUpdateOptions?.includes('bundlerConservative') && - '--conservative', - '--update', - ].filter(is.nonEmptyString); + const args = buildArgs(config); const updatedDepNames = updatedDeps .map(({ depName }) => depName)