diff --git a/lib/datasource/adoptium-java/__fixtures__/page.json b/lib/datasource/adoptium-java/__fixtures__/page.json new file mode 100644 index 0000000000000000000000000000000000000000..f5537643b3a5e11f37d592c15dd6e92e525e0780 --- /dev/null +++ b/lib/datasource/adoptium-java/__fixtures__/page.json @@ -0,0 +1,28 @@ +{ + "versions": [ + { + "build": 7, + "major": 16, + "minor": 0, + "openjdk_version": "16.0.2+7", + "security": 2, + "semver": "16.0.2+7" + }, + { + "build": 7, + "major": 11, + "minor": 0, + "openjdk_version": "11.0.12+7", + "security": 12, + "semver": "11.0.12+7" + }, + { + "build": 8, + "major": 8, + "minor": 0, + "openjdk_version": "1.8.0_302-b08", + "security": 302, + "semver": "8.0.302+8" + } + ] +} diff --git a/lib/datasource/adoptium-java/__snapshots__/index.spec.ts.snap b/lib/datasource/adoptium-java/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..5b273483414707f2e3e4ace22be39f20945a4974 --- /dev/null +++ b/lib/datasource/adoptium-java/__snapshots__/index.spec.ts.snap @@ -0,0 +1,278 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`datasource/adoptium-java/index getReleases pages 1`] = ` +Object { + "homepage": "https://adoptium.net", + "registryUrl": "https://api.adoptium.net/", + "releases": Array [ + Object { + "version": "1.1.0", + }, + Object { + "version": "1.2.0", + }, + Object { + "version": "1.3.0", + }, + Object { + "version": "1.4.0", + }, + Object { + "version": "1.5.0", + }, + Object { + "version": "1.6.0", + }, + Object { + "version": "1.7.0", + }, + Object { + "version": "1.8.0", + }, + Object { + "version": "1.9.0", + }, + Object { + "version": "1.10.0", + }, + Object { + "version": "1.11.0", + }, + Object { + "version": "1.12.0", + }, + Object { + "version": "1.13.0", + }, + Object { + "version": "1.14.0", + }, + Object { + "version": "1.15.0", + }, + Object { + "version": "1.16.0", + }, + Object { + "version": "1.17.0", + }, + Object { + "version": "1.18.0", + }, + Object { + "version": "1.19.0", + }, + Object { + "version": "1.20.0", + }, + Object { + "version": "1.21.0", + }, + Object { + "version": "1.22.0", + }, + Object { + "version": "1.23.0", + }, + Object { + "version": "1.24.0", + }, + Object { + "version": "1.25.0", + }, + Object { + "version": "1.26.0", + }, + Object { + "version": "1.27.0", + }, + Object { + "version": "1.28.0", + }, + Object { + "version": "1.29.0", + }, + Object { + "version": "1.30.0", + }, + Object { + "version": "1.31.0", + }, + Object { + "version": "1.32.0", + }, + Object { + "version": "1.33.0", + }, + Object { + "version": "1.34.0", + }, + Object { + "version": "1.35.0", + }, + Object { + "version": "1.36.0", + }, + Object { + "version": "1.37.0", + }, + Object { + "version": "1.38.0", + }, + Object { + "version": "1.39.0", + }, + Object { + "version": "1.40.0", + }, + Object { + "version": "1.41.0", + }, + Object { + "version": "1.42.0", + }, + Object { + "version": "1.43.0", + }, + Object { + "version": "1.44.0", + }, + Object { + "version": "1.45.0", + }, + Object { + "version": "1.46.0", + }, + Object { + "version": "1.47.0", + }, + Object { + "version": "1.48.0", + }, + Object { + "version": "1.49.0", + }, + Object { + "version": "1.50.0", + }, + ], +} +`; + +exports[`datasource/adoptium-java/index getReleases pages 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=0", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=1", + }, +] +`; + +exports[`datasource/adoptium-java/index getReleases processes real data 1`] = ` +Object { + "homepage": "https://adoptium.net", + "registryUrl": "https://api.adoptium.net/", + "releases": Array [ + Object { + "version": "8.0.302+8", + }, + Object { + "version": "11.0.12+7", + }, + Object { + "version": "16.0.2+7", + }, + ], +} +`; + +exports[`datasource/adoptium-java/index getReleases processes real data 2`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=0", + }, +] +`; + +exports[`datasource/adoptium-java/index getReleases returns null for 404 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=0", + }, +] +`; + +exports[`datasource/adoptium-java/index getReleases returns null for empty 200 OK 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=0", + }, +] +`; + +exports[`datasource/adoptium-java/index getReleases throws for 5xx 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=0", + }, +] +`; + +exports[`datasource/adoptium-java/index getReleases throws for error 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "host": "api.adoptium.net", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.adoptium.net/v3/release_versions?page_size=50&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=0", + }, +] +`; diff --git a/lib/datasource/adoptium-java/common.ts b/lib/datasource/adoptium-java/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..eabaeacd871a0c5e82cfdd8c38df191363a7adf3 --- /dev/null +++ b/lib/datasource/adoptium-java/common.ts @@ -0,0 +1,6 @@ +// Api page size limit 50 +export const pageSize = 50; + +export const defaultRegistryUrl = 'https://api.adoptium.net/'; + +export const datasource = 'adoptium-java'; diff --git a/lib/datasource/adoptium-java/index.spec.ts b/lib/datasource/adoptium-java/index.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2960089109338d0561ef695c1cdd18365a1779f6 --- /dev/null +++ b/lib/datasource/adoptium-java/index.spec.ts @@ -0,0 +1,101 @@ +import { getPkgReleases } from '..'; +import * as httpMock from '../../../test/http-mock'; +import { getName, loadFixture } from '../../../test/util'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; +import { datasource, defaultRegistryUrl, pageSize } from './common'; + +const res1 = loadFixture('page.json'); + +function getPath(page: number): string { + return `/v3/release_versions?page_size=${pageSize}&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium&page=${page}`; +} +function* range(start: number, end: number): Generator<number, number, number> { + yield start; + if (start === end) { + return; + } + yield* range(start + 1, end); +} + +describe(getName(), () => { + describe('getReleases', () => { + it('throws for error', async () => { + httpMock + .scope(defaultRegistryUrl) + .get(getPath(0)) + .replyWithError('error'); + await expect( + getPkgReleases({ + datasource, + depName: 'adoptium-java', + }) + ).rejects.toThrow(EXTERNAL_HOST_ERROR); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns null for 404', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(0)).reply(404); + expect( + await getPkgReleases({ + datasource, + depName: 'adoptium-java', + }) + ).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('returns null for empty 200 OK', async () => { + httpMock + .scope(defaultRegistryUrl) + .get(getPath(0)) + .reply(200, { versions: [] }); + expect( + await getPkgReleases({ + datasource, + depName: 'adoptium-java', + }) + ).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('throws for 5xx', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(0)).reply(502); + await expect( + getPkgReleases({ + datasource, + depName: 'adoptium-java', + }) + ).rejects.toThrow(EXTERNAL_HOST_ERROR); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('processes real data', async () => { + httpMock.scope(defaultRegistryUrl).get(getPath(0)).reply(200, res1); + const res = await getPkgReleases({ + datasource, + depName: 'adoptium-java', + }); + expect(res).toMatchSnapshot(); + expect(res.releases).toHaveLength(3); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('pages', async () => { + httpMock + .scope(defaultRegistryUrl) + .get(getPath(0)) + .reply(200, { + versions: [...range(1, 50)].map((v) => ({ semver: `1.${v}.0` })), + }) + .get(getPath(1)) + .reply(404); + const res = await getPkgReleases({ + datasource, + depName: 'adoptium-java', + }); + expect(res).toMatchSnapshot(); + expect(res.releases).toHaveLength(50); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + }); +}); diff --git a/lib/datasource/adoptium-java/index.ts b/lib/datasource/adoptium-java/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5213b3a218280e79cd175df6e37397a75dabc295 --- /dev/null +++ b/lib/datasource/adoptium-java/index.ts @@ -0,0 +1,58 @@ +import { ExternalHostError } from '../../types/errors/external-host-error'; +import { HttpError } from '../../util/http/types'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, ReleaseResult } from '../types'; +import { datasource, pageSize } from './common'; +import type { AdoptiumJavaResponse } from './types'; + +export class AdoptiumJavaDatasource extends Datasource { + static readonly id = datasource; + + constructor() { + super(datasource); + } + + customRegistrySupport = false; + + defaultRegistryUrls = ['https://api.adoptium.net/']; + + caching = true; + + async getReleases({ + registryUrl, + }: GetReleasesConfig): Promise<ReleaseResult | null> { + let page = 0; + const url = `${registryUrl}v3/release_versions?page_size=${pageSize}&project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium`; + + const result: ReleaseResult = { + homepage: 'https://adoptium.net', + releases: [], + }; + let resp: AdoptiumJavaResponse; + try { + do { + resp = ( + await this.http.getJson<AdoptiumJavaResponse>(`${url}&page=${page}`) + ).body; + result.releases.push( + ...resp.versions.map(({ semver }) => ({ version: semver })) + ); + page += 1; + } while (page < 50 && resp.versions.length === pageSize); + } catch (err) { + // istanbul ignore else: not testable with nock + if (err instanceof HttpError) { + if (err.response?.statusCode === 404 && page > 0) { + // no more pages + return result; + } + if (err.response?.statusCode !== 404) { + throw new ExternalHostError(err); + } + } + this.handleGenericErrors(err); + } + + return result.releases.length ? result : null; + } +} diff --git a/lib/datasource/adoptium-java/readme.md b/lib/datasource/adoptium-java/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..841a211f0ccdbd90cebbb9ad28b76dc09daaecc4 --- /dev/null +++ b/lib/datasource/adoptium-java/readme.md @@ -0,0 +1,5 @@ +This datasource returns releases from [Adoptium](https://adoptium.net/) API. + +It uses `project=jdk&release_type=ga&sort_method=DATE&sort_order=DESC&vendor=adoptium` as filter parameters. + +It only uses the first 50 pages with 50 items per page. diff --git a/lib/datasource/adoptium-java/types.ts b/lib/datasource/adoptium-java/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..578ef99d93f2a15dd5a43843678bbdfb423740e1 --- /dev/null +++ b/lib/datasource/adoptium-java/types.ts @@ -0,0 +1,7 @@ +export interface AdoptiumJavaVersion { + semver: string; +} + +export interface AdoptiumJavaResponse { + versions?: AdoptiumJavaVersion[]; +} diff --git a/lib/datasource/api.ts b/lib/datasource/api.ts index 5dde26a33af18b9ae005b6a57ffceb3396f9fcfd..9ad37f8fa7a5e9898745938bb8cd1f9250dda552 100644 --- a/lib/datasource/api.ts +++ b/lib/datasource/api.ts @@ -1,3 +1,4 @@ +import { AdoptiumJavaDatasource } from './adoptium-java'; import { BitBucketTagsDatasource } from './bitbucket-tags'; import { CdnJsDatasource } from './cdnjs'; import { ClojureDatasource } from './clojure'; @@ -35,6 +36,7 @@ import type { DatasourceApi } from './types'; const api = new Map<string, DatasourceApi>(); export default api; +api.set(AdoptiumJavaDatasource.id, new AdoptiumJavaDatasource()); api.set('bitbucket-tags', new BitBucketTagsDatasource()); api.set('cdnjs', new CdnJsDatasource()); api.set('clojure', new ClojureDatasource());