diff --git a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap index dc29fe05009aff26c874e94858cdae404c2ad4db..48cbabcbbfda162881e21fbe7ce3a2f1d914a58d 100644 --- a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap @@ -112,10 +112,13 @@ Object { "releases": Array [ Object { "gitRef": "v1.0.0", + "releaseTimestamp": "1970-01-01T00:00:00.000Z", "version": "v1.0.0", }, Object { "gitRef": "v1.1.0", + "isStable": false, + "releaseTimestamp": "1970-01-01T00:00:00.001Z", "version": "v1.1.0", }, ], @@ -136,6 +139,17 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/some/dep2/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/dep2/releases?per_page=100", + }, ] `; @@ -168,5 +182,16 @@ Array [ "method": "GET", "url": "https://git.enterprise.com/api/v3/repos/some/dep2/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "git.enterprise.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://git.enterprise.com/api/v3/repos/some/dep2/releases?per_page=100", + }, ] -`; \ No newline at end of file +`; diff --git a/lib/datasource/github-tags/index.spec.ts b/lib/datasource/github-tags/index.spec.ts index e6050460334b5955c3e7f6d5d43e8f532bfe6543..106718400554a999a743b353956fe1a58c8208d4 100644 --- a/lib/datasource/github-tags/index.spec.ts +++ b/lib/datasource/github-tags/index.spec.ts @@ -11,25 +11,19 @@ const githubEnterpriseApiHost = 'https://git.enterprise.com'; describe('datasource/github-tags', () => { beforeEach(() => { - httpMock.setup(); - }); - - afterEach(() => { httpMock.reset(); + httpMock.setup(); + jest.resetAllMocks(); + hostRules.hosts = jest.fn(() => []); + hostRules.find.mockReturnValue({ + token: 'some-token', + }); }); describe('getDigest', () => { const lookupName = 'some/dep'; const tag = 'v1.2.0'; - beforeEach(() => { - jest.resetAllMocks(); - hostRules.hosts = jest.fn(() => []); - hostRules.find.mockReturnValue({ - token: 'some-token', - }); - }); - it('returns null if no token', async () => { httpMock .scope(githubApiHost) @@ -90,14 +84,31 @@ describe('datasource/github-tags', () => { }); }); describe('getReleases', () => { + beforeEach(() => { + httpMock.reset(); + httpMock.setup(); + jest.resetAllMocks(); + hostRules.hosts = jest.fn(() => []); + hostRules.find.mockReturnValue({ + token: 'some-token', + }); + }); + const depName = 'some/dep2'; it('returns tags', async () => { - const body = [{ name: 'v1.0.0' }, { name: 'v1.1.0' }]; + const tags = [{ name: 'v1.0.0' }, { name: 'v1.1.0' }]; + const releases = tags.map((item, idx) => ({ + tag_name: item.name, + published_at: new Date(idx), + prerelease: !!idx, + })); httpMock .scope(githubApiHost) .get(`/repos/${depName}/tags?per_page=100`) - .reply(200, body); + .reply(200, tags) + .get(`/repos/${depName}/releases?per_page=100`) + .reply(200, releases); const res = await getPkgReleases({ datasource: github.id, depName }); expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(2); @@ -109,7 +120,10 @@ describe('datasource/github-tags', () => { httpMock .scope(githubEnterpriseApiHost) .get(`/api/v3/repos/${depName}/tags?per_page=100`) - .reply(200, body); + .reply(200, body) + .get(`/api/v3/repos/${depName}/releases?per_page=100`) + .reply(404); + const res = await github.getReleases({ registryUrl: 'https://git.enterprise.com', lookupName: depName, diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts index faa80f6f4253488122a0b7c5decc9090b651c40d..050b0a7613cdae3694f84f11a97309f54d48728b 100644 --- a/lib/datasource/github-tags/index.ts +++ b/lib/datasource/github-tags/index.ts @@ -3,6 +3,7 @@ import { logger } from '../../logger'; import * as packageCache from '../../util/cache/package'; import { GithubHttp } from '../../util/http/github'; import { DigestConfig, GetReleasesConfig, ReleaseResult } from '../common'; +import * as githubReleases from '../github-releases'; export const id = 'github-tags'; @@ -109,17 +110,7 @@ export async function getDigest( return digest; } -/** - * github.getReleases - * - * This function can be used to fetch releases with a customisable versioning (e.g. semver) and with either tags or releases. - * - * This function will: - * - Fetch all tags or releases (depending on configuration) - * - Sanitize the versions if desired (e.g. strip out leading 'v') - * - Return a dependency object containing sourceUrl string and releases array - */ -export async function getReleases({ +async function getTags({ registryUrl: depHost, lookupName: repo, }: GetReleasesConfig): Promise<ReleaseResult | null> { @@ -166,3 +157,35 @@ export async function getReleases({ ); return dependency; } + +export async function getReleases( + config: GetReleasesConfig +): Promise<ReleaseResult | null> { + const tagsResult = await getTags(config); + + try { + if (tagsResult?.releases) { + // Fetch additional data from releases endpoint when possible + const releasesResult = await githubReleases.getReleases(config); + const releaseByVersion = {}; + releasesResult?.releases?.forEach((release) => { + const key = release.version; + const value = { ...release }; + delete value.version; + releaseByVersion[key] = value; + }); + + const mergedReleases = []; + tagsResult.releases.forEach((tag) => { + const release = releaseByVersion[tag.version]; + mergedReleases.push({ ...release, ...tag }); + }); + + tagsResult.releases = mergedReleases; + } + } catch (e) { + // no-op + } + + return tagsResult; +} diff --git a/lib/datasource/go/__snapshots__/index.spec.ts.snap b/lib/datasource/go/__snapshots__/index.spec.ts.snap index c053968a8c565063b4036ba6d5ca00202bda922a..585f8e427fd8a4e9bee8cdd221f01ced13a96789 100644 --- a/lib/datasource/go/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/go/__snapshots__/index.spec.ts.snap @@ -64,6 +64,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/x/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/x/text/releases?per_page=100", + }, ] `; @@ -79,6 +89,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/x/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/x/text/releases?per_page=100", + }, ] `; @@ -119,6 +139,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/golang/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/golang/text/releases?per_page=100", + }, ] `; @@ -243,6 +273,16 @@ Array [ "method": "GET", "url": "https://git.enterprise.com/api/v3/repos/example/module/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "git.enterprise.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://git.enterprise.com/api/v3/repos/example/module/releases?per_page=100", + }, ] `; @@ -298,6 +338,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/x/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/x/text/releases?per_page=100", + }, Object { "headers": Object { "accept": "application/vnd.github.v3+json", @@ -308,6 +358,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/x/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/x/text/releases?per_page=100", + }, Object { "headers": Object { "accept": "application/vnd.github.v3+json", @@ -318,6 +378,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/go-x/x/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/go-x/x/releases?per_page=100", + }, ] `; @@ -333,6 +403,16 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/x/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/x/text/releases?per_page=100", + }, ] `; @@ -348,5 +428,15 @@ Array [ "method": "GET", "url": "https://api.github.com/repos/x/text/tags?per_page=100", }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "host": "api.github.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.github.com/repos/x/text/releases?per_page=100", + }, ] `; diff --git a/lib/datasource/go/index.spec.ts b/lib/datasource/go/index.spec.ts index fd751f04cabd2b5f8408cde51c4db3b6331ec232..0d1b16549f68a76a9b5a1aa7382981c54bb7e3ec 100644 --- a/lib/datasource/go/index.spec.ts +++ b/lib/datasource/go/index.spec.ts @@ -116,7 +116,9 @@ describe('datasource/go', () => { httpMock .scope('https://api.github.com/') .get('/repos/golang/text/tags?per_page=100') - .reply(200, [{ name: 'v1.0.0' }, { name: 'v2.0.0' }]); + .reply(200, [{ name: 'v1.0.0' }, { name: 'v2.0.0' }]) + .get('/repos/golang/text/releases?per_page=100') + .reply(200, []); const res = await getPkgReleases({ datasource, depName: 'golang.org/x/text', @@ -158,7 +160,9 @@ describe('datasource/go', () => { httpMock .scope('https://git.enterprise.com/') .get('/api/v3/repos/example/module/tags?per_page=100') - .reply(200, [{ name: 'v1.0.0' }, { name: 'v2.0.0' }]); + .reply(200, [{ name: 'v1.0.0' }, { name: 'v2.0.0' }]) + .get('/api/v3/repos/example/module/releases?per_page=100') + .reply(200, []); const res = await getPkgReleases({ datasource, depName: 'git.enterprise.com/example/module', @@ -221,9 +225,15 @@ describe('datasource/go', () => { .scope('https://api.github.com/') .get('/repos/x/text/tags?per_page=100') .reply(200, []) + .get('/repos/x/text/releases?per_page=100') + .reply(200, []) .get('/repos/x/text/tags?per_page=100') .reply(200, []) + .get('/repos/x/text/releases?per_page=100') + .reply(200, []) .get('/repos/go-x/x/tags?per_page=100') + .reply(200, []) + .get('/repos/go-x/x/releases?per_page=100') .reply(200, []); const packages = [ { datasource, depName: 'github.com/x/text' }, @@ -235,7 +245,7 @@ describe('datasource/go', () => { expect(res.releases).toBeEmpty(); } const httpCalls = httpMock.getTrace(); - expect(httpCalls).toHaveLength(3); + expect(httpCalls).toHaveLength(6); expect(httpCalls).toMatchSnapshot(); }); it('works for nested modules on github', async () => { @@ -250,7 +260,9 @@ describe('datasource/go', () => { httpMock .scope('https://api.github.com/') .get('/repos/x/text/tags?per_page=100') - .reply(200, tags); + .reply(200, tags) + .get('/repos/x/text/releases?per_page=100') + .reply(200, []); const prefix = pkg.depName.split('/')[3]; const result = await getPkgReleases(pkg); @@ -274,7 +286,9 @@ describe('datasource/go', () => { httpMock .scope('https://api.github.com/') .get('/repos/x/text/tags?per_page=100') - .reply(200, tags); + .reply(200, tags) + .get('/repos/x/text/releases?per_page=100') + .reply(200, []); const result = await getPkgReleases(pkg); expect(result.releases).toHaveLength(2);