diff --git a/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap b/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap index 78bdf20c5043e9ba5cac76634d4f66647aaa1d09..7257baf1a935aa7f43fc6b706968cb32b0db7933 100644 --- a/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/composer/__snapshots__/artifacts.spec.ts.snap @@ -121,19 +121,19 @@ Array [ exports[`.updateArtifacts() supports docker mode 1`] = ` Array [ Object { - "cmd": "docker pull renovate/composer:1.10.17", + "cmd": "docker pull renovate/php:7.3", "options": Object { "encoding": "utf-8", }, }, Object { - "cmd": "docker ps --filter name=renovate_composer -aq", + "cmd": "docker ps --filter name=renovate_php -aq", "options": Object { "encoding": "utf-8", }, }, Object { - "cmd": "docker run --rm --name=renovate_composer --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e COMPOSER_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/composer:1.10.17 bash -l -c \\"composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins\\"", + "cmd": "docker run --rm --name=renovate_php --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -e COMPOSER_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/php:7.3 bash -l -c \\"install-tool composer 1.10.17 && composer update --with-dependencies --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins\\"", "options": Object { "cwd": "/tmp/github/some/repo", "encoding": "utf-8", diff --git a/lib/manager/composer/artifacts.spec.ts b/lib/manager/composer/artifacts.spec.ts index 012e7c345854e8b35e2b90e8d51015a4c7a6074a..25c3b7a56f198789106c163daa3f9ed7173eabc7 100644 --- a/lib/manager/composer/artifacts.spec.ts +++ b/lib/manager/composer/artifacts.spec.ts @@ -52,6 +52,18 @@ describe('.updateArtifacts()', () => { fs.ensureCacheDir.mockResolvedValue( join(adminConfig.cacheDir, './others/composer') ); + + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.3.0' }, + { version: '1.10.0' }, + { version: '1.10.17' }, + { version: '2.0.14' }, + { version: '2.1.0' }, + ], + }); }); afterEach(() => { @@ -214,12 +226,14 @@ describe('.updateArtifacts()', () => { const execSnapshots = mockExecAll(exec); fs.readLocalFile.mockResolvedValueOnce('{ }'); + datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ - { version: '1.10.0' }, - { version: '1.10.17' }, - { version: '2.0.0' }, - { version: '2.0.7' }, + { version: '7.2.34' }, + { version: '7.3' }, // composer versioning bug + { version: '7.3.29' }, + { version: '7.4.22' }, + { version: '8.0.6' }, ], }); @@ -233,10 +247,11 @@ describe('.updateArtifacts()', () => { packageFileName: 'composer.json', updatedDeps: [], newPackageFileContent: '{}', - config: { ...config, constraints: { composer: '^1.10.0' } }, + config: { ...config, constraints: { composer: '^1.10.0', php: '7.3' } }, }) ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toHaveLength(3); }); it('supports global mode', async () => { diff --git a/lib/manager/composer/artifacts.ts b/lib/manager/composer/artifacts.ts index 9beef4f7de72780e71bbc6472e684a963e9c596c..6f452b726e21ace97ce4909434acc9ba674b1501 100644 --- a/lib/manager/composer/artifacts.ts +++ b/lib/manager/composer/artifacts.ts @@ -28,7 +28,8 @@ import type { AuthJson } from './types'; import { composerVersioningId, extractContraints, - getConstraint, + getComposerConstraint, + getPhpConstraint, } from './utils'; function getAuthJson(): string | null { @@ -110,6 +111,10 @@ export async function updateArtifacts({ await deleteLocalFile(lockFileName); } + const preCommands: string[] = [ + `install-tool composer ${await getComposerConstraint(constraints)}`, + ]; + const execOptions: ExecOptions = { cwdFile: packageFileName, extraEnv: { @@ -117,8 +122,9 @@ export async function updateArtifacts({ COMPOSER_AUTH: getAuthJson(), }, docker: { - image: 'composer', - tagConstraint: getConstraint(constraints), + preCommands, + image: 'php', + tagConstraint: getPhpConstraint(constraints), tagScheme: composerVersioningId, }, }; diff --git a/lib/manager/composer/utils.spec.ts b/lib/manager/composer/utils.spec.ts index 6c488a0d0c85cb92cb55b070e42838a42aa4f7e0..8102e71fe88819af80332fc94f2e06eadce7212a 100644 --- a/lib/manager/composer/utils.spec.ts +++ b/lib/manager/composer/utils.spec.ts @@ -1,14 +1,52 @@ -import { getName } from '../../../test/util'; -import { extractContraints, getConstraint } from './utils'; +import { getName, mocked } from '../../../test/util'; +import * as _datasource from '../../datasource'; +import { extractContraints, getComposerConstraint } from './utils'; + +jest.mock('../../../lib/datasource'); + +const datasource = mocked(_datasource); describe(getName(), () => { - describe('getConstraint', () => { - it('returns from config', () => { - expect(getConstraint({ composer: '1.1.0' })).toEqual('1.1.0'); + describe('getComposerConstraint', () => { + beforeEach(() => { + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.3.0' }, + { version: '2.0.14' }, + { version: '2.1.0' }, + ], + }); + }); + it('returns from config', async () => { + expect(await getComposerConstraint({ composer: '1.1.0' })).toEqual( + '1.1.0' + ); + }); + + it('returns from latest', async () => { + expect(await getComposerConstraint({})).toEqual('2.1.0'); }); - it('returns from null', () => { - expect(getConstraint({})).toBeNull(); + it('throws no releases', async () => { + datasource.getPkgReleases.mockReset(); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [], + }); + await expect(getComposerConstraint({})).rejects.toThrow( + 'No composer releases found.' + ); + }); + + it('throws no compatible releases', async () => { + datasource.getPkgReleases.mockReset(); + datasource.getPkgReleases.mockResolvedValueOnce({ + releases: [{ version: '1.2.3' }], + }); + await expect( + getComposerConstraint({ composer: '^3.1.0' }) + ).rejects.toThrow('No compatible composer releases found.'); }); }); diff --git a/lib/manager/composer/utils.ts b/lib/manager/composer/utils.ts index 8a77b5c75fc367512b38ec5becc5e7b59638ab72..7f84615fc91e2a83c03bd0dcd525603973cdfac8 100644 --- a/lib/manager/composer/utils.ts +++ b/lib/manager/composer/utils.ts @@ -1,17 +1,57 @@ +import { getPkgReleases } from '../../datasource'; import { logger } from '../../logger'; import { api, id as composerVersioningId } from '../../versioning/composer'; import type { ComposerConfig, ComposerLock } from './types'; export { composerVersioningId }; -export function getConstraint(constraints: Record<string, string>): string { +export async function getComposerConstraint( + constraints: Record<string, string> +): Promise<string> { const { composer } = constraints; - if (composer) { - logger.debug('Using composer constraint from config'); + if (api.isSingleVersion(composer)) { + logger.debug( + { version: composer }, + 'Using composer constraint from config' + ); return composer; } + const release = await getPkgReleases({ + depName: 'composer/composer', + datasource: 'github-releases', + versioning: composerVersioningId, + }); + + if (!release?.releases?.length) { + throw new Error('No composer releases found.'); + } + let versions = release.releases.map((r) => r.version); + + if (composer) { + versions = versions.filter( + (v) => api.isValid(v) && api.matches(v, composer) + ); + } + + if (!versions.length) { + throw new Error('No compatible composer releases found.'); + } + + const version = versions.pop(); + logger.debug({ range: composer, version }, 'Using composer constraint'); + return version; +} + +export function getPhpConstraint(constraints: Record<string, string>): string { + const { php } = constraints; + + if (php) { + logger.debug('Using php constraint from config'); + return php; + } + return null; }