From 19e839fc5add84356715760e88885de5f0b4ff28 Mon Sep 17 00:00:00 2001 From: Tanuel <tanuel.mategi@gmail.com> Date: Wed, 17 Jul 2019 16:53:20 +0200 Subject: [PATCH] feat(composer): Add support for custom git repositories (#4055) --- lib/manager/composer/extract.js | 130 ++++++++++++------ .../__snapshots__/extract.spec.js.snap | 74 ++++++++++ .../manager/composer/_fixtures/composer4.json | 30 ++++ .../manager/composer/_fixtures/composer5.json | 28 ++++ .../manager/composer/_fixtures/composer5.lock | 28 ++++ test/manager/composer/extract.spec.js | 23 ++++ 6 files changed, 270 insertions(+), 43 deletions(-) create mode 100644 test/manager/composer/_fixtures/composer4.json create mode 100644 test/manager/composer/_fixtures/composer5.json create mode 100644 test/manager/composer/_fixtures/composer5.lock diff --git a/lib/manager/composer/extract.js b/lib/manager/composer/extract.js index b67da65a06..81d320d473 100644 --- a/lib/manager/composer/extract.js +++ b/lib/manager/composer/extract.js @@ -5,6 +5,38 @@ const semverComposer = require('../../versioning/composer'); export { extractPackageFile }; +/** + * Parse the repositories field from a composer.json + * + * Entries with type vcs or git will be added to repositories, + * other entries will be added to registryUrls + * + * @param repoJson + * @param repositories + * @param registryUrls + */ +function parseRepositories(repoJson, repositories, registryUrls) { + try { + Object.entries(repoJson).forEach(([key, repo]) => { + const name = is.array(repoJson) ? repo.name : key; + switch (repo.type) { + case 'vcs': + case 'git': + // eslint-disable-next-line no-param-reassign + repositories[name] = repo; + break; + default: + registryUrls.push(repo); + } + }); + } catch (e) /* istanbul ignore next */ { + logger.info( + { repositories: repoJson }, + 'Error parsing composer.json repositories config' + ); + } +} + async function extractPackageFile(content, fileName) { logger.trace(`composer.extractPackageFile(${fileName})`); let composerJson; @@ -14,6 +46,33 @@ async function extractPackageFile(content, fileName) { logger.info({ fileName }, 'Invalid JSON'); return null; } + const repositories = {}; + const registryUrls = []; + const res = {}; + + // handle lockfile + const lockfilePath = fileName.replace(/\.json$/, '.lock'); + const lockContents = await platform.getFile(lockfilePath); + let lockParsed; + if (lockContents) { + logger.debug({ packageFile: fileName }, 'Found composer lock file'); + res.composerLock = lockfilePath; + try { + lockParsed = JSON.parse(lockContents); + } catch (err) /* istanbul ignore next */ { + logger.warn({ err }, 'Error processing composer.lock'); + } + } else { + res.composerLock = false; + } + + // handle composer.json repositories + if (composerJson.repositories) { + parseRepositories(composerJson.repositories, repositories, registryUrls); + } + if (registryUrls.length !== 0) { + res.registryUrls = registryUrls; + } const deps = []; const depTypes = ['require', 'require-dev']; for (const depType of depTypes) { @@ -23,12 +82,30 @@ async function extractPackageFile(content, fileName) { composerJson[depType] )) { const currentValue = version.trim(); + // Default datasource and lookupName + let datasource = 'packagist'; + let lookupName = depName; + + // Check custom repositories by type + if (repositories[depName]) { + // eslint-disable-next-line default-case + switch (repositories[depName].type) { + case 'vcs': + case 'git': + datasource = 'gitTags'; + lookupName = repositories[depName].url; + break; + } + } const dep = { depType, depName, currentValue, - datasource: 'packagist', + datasource, }; + if (depName !== lookupName) { + dep.lookupName = lookupName; + } if (!depName.includes('/')) { dep.skipReason = 'unsupported'; } @@ -38,6 +115,14 @@ async function extractPackageFile(content, fileName) { if (currentValue === '*') { dep.skipReason = 'any-version'; } + if (lockParsed) { + const lockedDep = lockParsed.packages.find( + item => item.name === dep.depName + ); + if (lockedDep && semverComposer.isVersion(lockedDep.version)) { + dep.lockedVersion = lockedDep.version.replace(/^v/i, ''); + } + } deps.push(dep); } } catch (err) /* istanbul ignore next */ { @@ -49,48 +134,7 @@ async function extractPackageFile(content, fileName) { if (!deps.length) { return null; } - const res = { deps }; - const filePath = fileName.replace(/\.json$/, '.lock'); - const lockContents = await platform.getFile(filePath); - // istanbul ignore if - if (lockContents) { - logger.debug({ packageFile: fileName }, 'Found composer lock file'); - res.composerLock = filePath; - try { - const lockParsed = JSON.parse(lockContents); - for (const dep of res.deps) { - const lockedDep = lockParsed.packages.find( - item => item.name === dep.depName - ); - if (lockedDep && semverComposer.isVersion(lockedDep.version)) { - dep.lockedVersion = lockedDep.version.replace(/^v/i, ''); - } - } - } catch (err) { - logger.warn({ err }, 'Error processing composer.lock'); - } - } else { - res.composerLock = false; - } - if (composerJson.repositories) { - if (is.array(composerJson.repositories)) { - res.registryUrls = composerJson.repositories; - } else if (is.object(composerJson.repositories)) { - try { - res.registryUrls = []; - for (const repository of Object.values(composerJson.repositories)) { - res.registryUrls.push(repository); - } - } catch (err) /* istanbul ignore next */ { - logger.warn({ err }, 'Error extracting composer repositories'); - } - } /* istanbul ignore next */ else { - logger.info( - { repositories: composerJson.repositories }, - 'Unknown composer repositories' - ); - } - } + res.deps = deps; if (composerJson.type) { res.composerJsonType = composerJson.type; } diff --git a/test/manager/composer/__snapshots__/extract.spec.js.snap b/test/manager/composer/__snapshots__/extract.spec.js.snap index b944859152..30cd5c6dfe 100644 --- a/test/manager/composer/__snapshots__/extract.spec.js.snap +++ b/test/manager/composer/__snapshots__/extract.spec.js.snap @@ -565,6 +565,44 @@ Object { } `; +exports[`lib/manager/composer/extract extractPackageFile() extracts object repositories and registryUrls with lock file 1`] = ` +Object { + "composerLock": "composer.lock", + "deps": Array [ + Object { + "currentValue": "*", + "datasource": "packagist", + "depName": "aws/aws-sdk-php", + "depType": "require", + "skipReason": "any-version", + }, + Object { + "currentValue": "dev-trunk", + "datasource": "gitTags", + "depName": "awesome/vcs", + "depType": "require", + "lockedVersion": "1.1.0", + "lookupName": "https://my-vcs.example/my-vcs-repo", + "skipReason": "unsupported-constraint", + }, + Object { + "currentValue": ">=7.0.2", + "datasource": "gitTags", + "depName": "awesome/git", + "depType": "require", + "lockedVersion": "1.2.0", + "lookupName": "git@my-git.example:my-git-repo", + }, + ], + "registryUrls": Array [ + Object { + "type": "composer", + "url": "https://wpackagist.org", + }, + ], +} +`; + exports[`lib/manager/composer/extract extractPackageFile() extracts registryUrls 1`] = ` Object { "composerLock": false, @@ -605,3 +643,39 @@ Object { ], } `; + +exports[`lib/manager/composer/extract extractPackageFile() extracts repositories and registryUrls 1`] = ` +Object { + "composerLock": false, + "deps": Array [ + Object { + "currentValue": "*", + "datasource": "packagist", + "depName": "aws/aws-sdk-php", + "depType": "require", + "skipReason": "any-version", + }, + Object { + "currentValue": "dev-trunk", + "datasource": "gitTags", + "depName": "awesome/vcs", + "depType": "require", + "lookupName": "https://my-vcs.example/my-vcs-repo", + "skipReason": "unsupported-constraint", + }, + Object { + "currentValue": ">=7.0.2", + "datasource": "gitTags", + "depName": "awesome/git", + "depType": "require", + "lookupName": "https://my-git.example/my-git-repo", + }, + ], + "registryUrls": Array [ + Object { + "type": "composer", + "url": "https://wpackagist.org", + }, + ], +} +`; diff --git a/test/manager/composer/_fixtures/composer4.json b/test/manager/composer/_fixtures/composer4.json new file mode 100644 index 0000000000..2b9e63280c --- /dev/null +++ b/test/manager/composer/_fixtures/composer4.json @@ -0,0 +1,30 @@ +{ + "name": "acme/git-sources", + "description": "Fetch Packages via git", + "repositories":[ + { + "name": "awesome/vcs", + "type":"vcs", + "url":"https://my-vcs.example/my-vcs-repo" + }, + { + "name": "awesome/git", + "type":"git", + "url":"https://my-git.example/my-git-repo" + }, + { + "type": "composer", + "url": "https://wpackagist.org" + } + ], + "require": { + "aws/aws-sdk-php":"*", + "awesome/vcs":"dev-trunk", + "awesome/git":">=7.0.2" + }, + "autoload": { + "psr-0": { + "Acme": "src/" + } + } +} diff --git a/test/manager/composer/_fixtures/composer5.json b/test/manager/composer/_fixtures/composer5.json new file mode 100644 index 0000000000..17238d84a8 --- /dev/null +++ b/test/manager/composer/_fixtures/composer5.json @@ -0,0 +1,28 @@ +{ + "name": "acme/git-sources", + "description": "Fetch Packages via git", + "repositories":{ + "awesome/vcs": { + "type":"vcs", + "url":"https://my-vcs.example/my-vcs-repo" + }, + "awesome/git": { + "type":"git", + "url":"git@my-git.example:my-git-repo" + }, + "wpackagist": { + "type": "composer", + "url": "https://wpackagist.org" + } + }, + "require": { + "aws/aws-sdk-php":"*", + "awesome/vcs":"dev-trunk", + "awesome/git":">=7.0.2" + }, + "autoload": { + "psr-0": { + "Acme": "src/" + } + } +} diff --git a/test/manager/composer/_fixtures/composer5.lock b/test/manager/composer/_fixtures/composer5.lock new file mode 100644 index 0000000000..53e834ba47 --- /dev/null +++ b/test/manager/composer/_fixtures/composer5.lock @@ -0,0 +1,28 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "h34j5h3p3g4ug34u34543j5h34j53h5j", + "packages": [ + { + "name": "awesome/vcs", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://my-vcs.example/my-vcs-repo", + "reference": "3j5b345j3b45j345j345h34j5h345j34h5j34h5j" + } + }, + { + "name": "awesome/git", + "version": "1.2.0", + "source": { + "type": "git", + "url": "git@my-git.example/awesome.git", + "reference": "3j5b345j3b45j345j345h34j5h345j34h5j34h5j" + } + } + ] +} diff --git a/test/manager/composer/extract.spec.js b/test/manager/composer/extract.spec.js index e8420dac96..c01ce51f07 100644 --- a/test/manager/composer/extract.spec.js +++ b/test/manager/composer/extract.spec.js @@ -16,6 +16,18 @@ const requirements3 = fs.readFileSync( 'test/manager/composer/_fixtures/composer3.json', 'utf8' ); +const requirements4 = fs.readFileSync( + 'test/manager/composer/_fixtures/composer4.json', + 'utf8' +); +const requirements5 = fs.readFileSync( + 'test/manager/composer/_fixtures/composer5.json', + 'utf8' +); +const requirements5Lock = fs.readFileSync( + 'test/manager/composer/_fixtures/composer5.lock', + 'utf8' +); describe('lib/manager/composer/extract', () => { describe('extractPackageFile()', () => { @@ -43,6 +55,17 @@ describe('lib/manager/composer/extract', () => { expect(res).toMatchSnapshot(); expect(res.registryUrls).toHaveLength(3); }); + it('extracts repositories and registryUrls', async () => { + const res = await extractPackageFile(requirements4, packageFile); + expect(res).toMatchSnapshot(); + expect(res.registryUrls).toHaveLength(1); + }); + it('extracts object repositories and registryUrls with lock file', async () => { + platform.getFile.mockReturnValueOnce(requirements5Lock); + const res = await extractPackageFile(requirements5, packageFile); + expect(res).toMatchSnapshot(); + expect(res.registryUrls).toHaveLength(1); + }); it('extracts dependencies with lock file', async () => { platform.getFile.mockReturnValueOnce('some content'); const res = await extractPackageFile(requirements1, packageFile); -- GitLab