From 0eb5c6d2cde9df9654c1d2478e64917f63728d75 Mon Sep 17 00:00:00 2001 From: Michael Kriese <michael.kriese@visualon.de> Date: Wed, 1 Sep 2021 13:07:55 +0200 Subject: [PATCH] fix(core/changelogs): pass though known project info (#11515) --- lib/datasource/github-releases/test/index.ts | 2 +- lib/datasource/github-releases/types.ts | 5 + lib/datasource/github-tags/index.ts | 7 +- lib/datasource/github-tags/types.ts | 4 + lib/datasource/gitlab-releases/types.ts | 1 + .../__snapshots__/err-serializer.spec.ts.snap | 1 + lib/logger/utils.ts | 1 + lib/util/http/index.ts | 6 +- .../__snapshots__/github.spec.ts.snap | 30 +- .../__snapshots__/gitlab.spec.ts.snap | 30 +- .../__snapshots__/index.spec.ts.snap | 35 +- .../__snapshots__/release-notes.spec.ts.snap | 75 +++-- lib/workers/pr/changelog/github/index.ts | 20 +- lib/workers/pr/changelog/gitlab/index.ts | 19 +- .../pr/changelog/release-notes.spec.ts | 310 +++++++++++------- lib/workers/pr/changelog/release-notes.ts | 175 ++++------ lib/workers/pr/changelog/source-github.ts | 10 +- lib/workers/pr/changelog/source-gitlab.ts | 5 +- lib/workers/pr/changelog/types.ts | 4 +- lib/workers/pr/index.spec.ts | 56 ++-- lib/workers/pr/index.ts | 4 +- 21 files changed, 446 insertions(+), 354 deletions(-) diff --git a/lib/datasource/github-releases/test/index.ts b/lib/datasource/github-releases/test/index.ts index 7c40f0962f..95c756a089 100644 --- a/lib/datasource/github-releases/test/index.ts +++ b/lib/datasource/github-releases/test/index.ts @@ -20,7 +20,7 @@ export class GitHubReleaseMocker { published_at: '2020-03-09T11:00:00Z', prerelease: false, assets: [], - }; + } as GithubRelease; for (const assetFn of Object.keys(assets)) { const assetPath = `/repos/${this.lookupName}/releases/download/${version}/${assetFn}`; const assetData = assets[assetFn]; diff --git a/lib/datasource/github-releases/types.ts b/lib/datasource/github-releases/types.ts index 78dd7af200..2989ddcaee 100644 --- a/lib/datasource/github-releases/types.ts +++ b/lib/datasource/github-releases/types.ts @@ -1,8 +1,13 @@ export type GithubRelease = { + id: number; tag_name: string; published_at: string; prerelease: boolean; assets: GithubReleaseAsset[]; + + html_url: string; + name: string; + body: string; }; export interface GithubReleaseAsset { diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts index 2300a58e29..9099448e61 100644 --- a/lib/datasource/github-tags/index.ts +++ b/lib/datasource/github-tags/index.ts @@ -4,7 +4,7 @@ import { GithubHttp } from '../../util/http/github'; import { ensureTrailingSlash } from '../../util/url'; import * as githubReleases from '../github-releases'; import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types'; -import type { TagResponse } from './types'; +import type { GitHubTag, TagResponse } from './types'; export const id = 'github-tags'; export const customRegistrySupport = true; @@ -148,12 +148,9 @@ async function getTags({ : `${sourceUrlBase}api/v3/`; // tag const url = `${apiBaseUrl}repos/${repo}/tags?per_page=100`; - type GitHubTag = { - name: string; - }[]; const versions = ( - await http.getJson<GitHubTag>(url, { + await http.getJson<GitHubTag[]>(url, { paginate: true, }) ).body.map((o) => o.name); diff --git a/lib/datasource/github-tags/types.ts b/lib/datasource/github-tags/types.ts index 0c570048a2..e8b5888166 100644 --- a/lib/datasource/github-tags/types.ts +++ b/lib/datasource/github-tags/types.ts @@ -5,3 +5,7 @@ export interface TagResponse { sha: string; }; } + +export interface GitHubTag { + name: string; +} diff --git a/lib/datasource/gitlab-releases/types.ts b/lib/datasource/gitlab-releases/types.ts index d6b4cab4fb..246ba0a9ed 100644 --- a/lib/datasource/gitlab-releases/types.ts +++ b/lib/datasource/gitlab-releases/types.ts @@ -1,4 +1,5 @@ export interface GitlabRelease { + description: string; name: string; tag_name: string; released_at: string; diff --git a/lib/logger/__snapshots__/err-serializer.spec.ts.snap b/lib/logger/__snapshots__/err-serializer.spec.ts.snap index d23c5513d6..6b2de4afa3 100644 --- a/lib/logger/__snapshots__/err-serializer.spec.ts.snap +++ b/lib/logger/__snapshots__/err-serializer.spec.ts.snap @@ -60,6 +60,7 @@ Object { "accept-encoding": "gzip, deflate, br", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, + "hostType": "any", "http2": false, "method": "POST", "password": "***********", diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index 36945b4eed..34e7085c10 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -66,6 +66,7 @@ export default function prepareError(err: Error): Record<string, unknown> { const options: Record<string, unknown> = { headers: clone(err.options.headers), url: err.options.url?.toString(), + hostType: err.options.context.hostType, }; response.options = options; diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts index a60501444f..a3530c9526 100644 --- a/lib/util/http/index.ts +++ b/lib/util/http/index.ts @@ -105,7 +105,11 @@ async function gotRoutine<T>( } export class Http<GetOptions = HttpOptions, PostOptions = HttpPostOptions> { - constructor(private hostType: string, private options?: HttpOptions) {} + private options?: GotOptions; + + constructor(private hostType: string, options?: HttpOptions) { + this.options = merge<GotOptions>(options, { context: { hostType } }); + } protected async request<T>( requestUrl: string | URL, diff --git a/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap index 23bcff4cd3..a97678ef16 100644 --- a/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap @@ -7,9 +7,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "@renovate/no", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -59,9 +60,10 @@ Object { "apiBaseUrl": "https://github-enterprise.example.com/api/v3/", "baseUrl": "https://github-enterprise.example.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github-enterprise.example.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github-enterprise.example.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -111,9 +113,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -163,9 +166,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -215,9 +219,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -267,9 +272,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { diff --git a/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap index 427d3e9199..cc229009fe 100644 --- a/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap @@ -7,8 +7,9 @@ Object { "apiBaseUrl": "https://gitlab.com/api/v4/", "baseUrl": "https://gitlab.com/", "depName": "renovate", - "gitlab": "meno/dropzone", - "repository": "https://gitlab.com/meno/dropzone/", + "repository": "meno/dropzone", + "sourceUrl": "https://gitlab.com/meno/dropzone/", + "type": "gitlab", }, "versions": Array [ Object { @@ -154,8 +155,9 @@ Object { "apiBaseUrl": "https://gitlab-enterprise.example.com/api/v4/", "baseUrl": "https://gitlab-enterprise.example.com/", "depName": "renovate", - "gitlab": "meno/dropzone", - "repository": "https://gitlab-enterprise.example.com/meno/dropzone/", + "repository": "meno/dropzone", + "sourceUrl": "https://gitlab-enterprise.example.com/meno/dropzone/", + "type": "gitlab", }, "versions": Array [ Object { @@ -197,8 +199,9 @@ Object { "apiBaseUrl": "https://git.test.com/api/v4/", "baseUrl": "https://git.test.com/", "depName": "renovate", - "gitlab": "meno/dropzone", - "repository": "https://git.test.com/meno/dropzone/", + "repository": "meno/dropzone", + "sourceUrl": "https://git.test.com/meno/dropzone/", + "type": "gitlab", }, "versions": Array [ Object { @@ -240,8 +243,9 @@ Object { "apiBaseUrl": "https://gitlab.com/api/v4/", "baseUrl": "https://gitlab.com/", "depName": "renovate", - "gitlab": "meno/dropzone", - "repository": "https://gitlab.com/meno/dropzone/", + "repository": "meno/dropzone", + "sourceUrl": "https://gitlab.com/meno/dropzone/", + "type": "gitlab", }, "versions": Array [ Object { @@ -403,8 +407,9 @@ Object { "apiBaseUrl": "https://gitlab.com/api/v4/", "baseUrl": "https://gitlab.com/", "depName": "renovate", - "gitlab": "meno/dropzone", - "repository": "https://gitlab.com/meno/dropzone/", + "repository": "meno/dropzone", + "sourceUrl": "https://gitlab.com/meno/dropzone/", + "type": "gitlab", }, "versions": Array [ Object { @@ -550,8 +555,9 @@ Object { "apiBaseUrl": "https://gitlab.com/api/v4/", "baseUrl": "https://gitlab.com/", "depName": "renovate", - "gitlab": "meno/dropzone", - "repository": "https://gitlab.com/meno/dropzone/", + "repository": "meno/dropzone", + "sourceUrl": "https://gitlab.com/meno/dropzone/", + "type": "gitlab", }, "versions": Array [ Object { diff --git a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap index 96be5e64cf..f57ff11e1b 100644 --- a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap @@ -7,9 +7,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "@renovate/no", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -163,9 +164,10 @@ Object { "apiBaseUrl": "https://github-enterprise.example.com/api/v3/", "baseUrl": "https://github-enterprise.example.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github-enterprise.example.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github-enterprise.example.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -363,9 +365,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -563,9 +566,10 @@ Object { "apiBaseUrl": "https://github-enterprise.example.com/api/v3/", "baseUrl": "https://github-enterprise.example.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github-enterprise.example.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github-enterprise.example.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -763,9 +767,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -815,9 +820,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { @@ -1019,9 +1025,10 @@ Object { "apiBaseUrl": "https://api.github.com/", "baseUrl": "https://github.com/", "depName": "renovate", - "github": "chalk/chalk", - "repository": "https://github.com/chalk/chalk", + "repository": "chalk/chalk", "sourceDirectory": undefined, + "sourceUrl": "https://github.com/chalk/chalk", + "type": "github", }, "versions": Array [ Object { diff --git a/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap index 53776c9742..976f87b5a5 100644 --- a/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap +++ b/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap @@ -5,7 +5,8 @@ Object { "a": 1, "hasReleaseNotes": false, "project": Object { - "github": "https://github.com/nodeca/js-yaml", + "repository": "https://github.com/nodeca/js-yaml", + "type": "github", }, "versions": Array [ Object { @@ -24,7 +25,8 @@ Object { "a": 1, "hasReleaseNotes": false, "project": Object { - "gitlab": "https://gitlab.com/gitlab-org/gitter/webapp/", + "repository": "https://gitlab.com/gitlab-org/gitter/webapp/", + "type": "gitlab", }, "versions": Array [ Object { @@ -137,7 +139,7 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 1`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "" 1`] = ` Object { "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124) ", @@ -148,7 +150,7 @@ Object { } `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 2`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "" 2`] = ` Array [ Object { "headers": Object { @@ -163,18 +165,18 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 3`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other@" 1`] = ` Object { "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124) ", "id": undefined, "name": undefined, - "tag": "v1.0.1", - "url": "https://github.com/some/other-repository/releases/v1.0.1", + "tag": "other@1.0.1", + "url": "https://github.com/some/other-repository/releases/other@1.0.1", } `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 4`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other@" 2`] = ` Array [ Object { "headers": Object { @@ -189,18 +191,18 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 5`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other_v" 1`] = ` Object { "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124) ", "id": undefined, "name": undefined, - "tag": "other-1.0.1", - "url": "https://github.com/some/other-repository/releases/other-1.0.1", + "tag": "other_v1.0.1", + "url": "https://github.com/some/other-repository/releases/other_v1.0.1", } `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 6`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other_v" 2`] = ` Array [ Object { "headers": Object { @@ -215,18 +217,18 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 7`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other-" 1`] = ` Object { "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124) ", "id": undefined, "name": undefined, - "tag": "other_v1.0.1", - "url": "https://github.com/some/other-repository/releases/other_v1.0.1", + "tag": "other-1.0.1", + "url": "https://github.com/some/other-repository/releases/other-1.0.1", } `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 8`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other-" 2`] = ` Array [ Object { "headers": Object { @@ -241,18 +243,18 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 9`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "v" 1`] = ` Object { "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124) ", "id": undefined, "name": undefined, - "tag": "other@1.0.1", - "url": "https://github.com/some/other-repository/releases/other@1.0.1", + "tag": "v1.0.1", + "url": "https://github.com/some/other-repository/releases/v1.0.1", } `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 10`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "v" 2`] = ` Array [ Object { "headers": Object { @@ -267,9 +269,16 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo 1`] = `null`; +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "" 1`] = ` +Object { + "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)", + "name": undefined, + "tag": "1.0.1", + "url": "https://gitlab.com/some/other-repository/tags/1.0.1", +} +`; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo 2`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "" 2`] = ` Array [ Object { "headers": Object { @@ -284,9 +293,16 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo other- 1`] = `null`; +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "other-" 1`] = ` +Object { + "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)", + "name": undefined, + "tag": "other-1.0.1", + "url": "https://gitlab.com/some/other-repository/tags/other-1.0.1", +} +`; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo other- 2`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "other-" 2`] = ` Array [ Object { "headers": Object { @@ -301,9 +317,16 @@ Array [ ] `; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo v 1`] = `null`; +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "v" 1`] = ` +Object { + "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)", + "name": undefined, + "tag": "v1.0.1", + "url": "https://gitlab.com/some/other-repository/tags/v1.0.1", +} +`; -exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo v 2`] = ` +exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "v" 2`] = ` Array [ Object { "headers": Object { diff --git a/lib/workers/pr/changelog/github/index.ts b/lib/workers/pr/changelog/github/index.ts index 4232aa7550..354e7da27d 100644 --- a/lib/workers/pr/changelog/github/index.ts +++ b/lib/workers/pr/changelog/github/index.ts @@ -1,4 +1,6 @@ import changelogFilenameRegex from 'changelog-filename-regex'; +import type { GithubRelease } from '../../../../datasource/github-releases/types'; +import type { GitHubTag } from '../../../../datasource/github-tags/types'; import { logger } from '../../../../logger'; import type { GithubGitBlob, @@ -18,7 +20,7 @@ export async function getTags( logger.trace('github.getTags()'); const url = `${endpoint}repos/${repository}/tags?per_page=100`; try { - const res = await http.getJson<{ name: string }[]>(url, { + const res = await http.getJson<GitHubTag[]>(url, { paginate: true, }); @@ -30,8 +32,10 @@ export async function getTags( return tags.map((tag) => tag.name).filter(Boolean); } catch (err) { - logger.debug({ sourceRepo: repository }, 'Failed to fetch Github tags'); - logger.debug({ err }); + logger.debug( + { sourceRepo: repository, err }, + 'Failed to fetch Github tags' + ); // istanbul ignore if if (err.message?.includes('Bad credentials')) { logger.warn('Bad credentials triggering tag fail lookup in changelog'); @@ -108,15 +112,7 @@ export async function getReleaseList( const url = `${ensureTrailingSlash( apiBaseUrl )}repos/${repository}/releases?per_page=100`; - const res = await http.getJson< - { - html_url: string; - id: number; - tag_name: string; - name: string; - body: string; - }[] - >(url, { paginate: true }); + const res = await http.getJson<GithubRelease[]>(url, { paginate: true }); return res.body.map((release) => ({ url: release.html_url, id: release.id, diff --git a/lib/workers/pr/changelog/gitlab/index.ts b/lib/workers/pr/changelog/gitlab/index.ts index d6a0b94fd5..ab7e71625d 100644 --- a/lib/workers/pr/changelog/gitlab/index.ts +++ b/lib/workers/pr/changelog/gitlab/index.ts @@ -1,4 +1,6 @@ import changelogFilenameRegex from 'changelog-filename-regex'; +import type { GitlabRelease } from '../../../../datasource/gitlab-releases/types'; +import type { GitlabTag } from '../../../../datasource/gitlab-tags/types'; import { logger } from '../../../../logger'; import type { GitlabTreeNode } from '../../../../types/platform/gitlab'; import { GitlabHttp } from '../../../../util/http/gitlab'; @@ -20,7 +22,7 @@ export async function getTags( repository )}/repository/tags?per_page=100`; try { - const res = await http.getJson<{ name: string }[]>(url, { + const res = await http.getJson<GitlabTag[]>(url, { paginate: true, }); @@ -32,7 +34,10 @@ export async function getTags( return tags.map((tag) => tag.name).filter(Boolean); } catch (err) { - logger.info({ sourceRepo: repository }, 'Failed to fetch Gitlab tags'); + logger.debug( + { sourceRepo: repository, err }, + 'Failed to fetch Gitlab tags' + ); // istanbul ignore if if (err.message?.includes('Bad credentials')) { logger.warn('Bad credentials triggering tag fail lookup in changelog'); @@ -101,14 +106,8 @@ export async function getReleaseList( const apiUrl = `${ensureTrailingSlash( apiBaseUrl )}projects/${repoId}/releases`; - const res = await http.getJson< - { - name: string; - release: string; - description: string; - tag_name: string; - }[] - >(`${apiUrl}?per_page=100`, { + + const res = await http.getJson<GitlabRelease[]>(`${apiUrl}?per_page=100`, { paginate: true, }); return res.body.map((release) => ({ diff --git a/lib/workers/pr/changelog/release-notes.spec.ts b/lib/workers/pr/changelog/release-notes.spec.ts index 526e23d190..3a86c3db88 100644 --- a/lib/workers/pr/changelog/release-notes.spec.ts +++ b/lib/workers/pr/changelog/release-notes.spec.ts @@ -10,7 +10,12 @@ import { getReleaseNotesMd, releaseNotesCacheMinutes, } from './release-notes'; -import type { ChangeLogNotes } from './types'; +import type { + ChangeLogNotes, + ChangeLogProject, + ChangeLogRelease, + ChangeLogResult, +} from './types'; jest.mock('../../../util/host-rules'); @@ -37,6 +42,18 @@ const gitlabTreeResponse = [ { path: 'README.md', type: 'blob' }, ]; +const githubProject = { + type: 'github', + apiBaseUrl: 'https://api.github.com/', + baseUrl: 'https://github.com/', +} as ChangeLogProject; + +const gitlabProject = { + type: 'gitlab', + apiBaseUrl: 'https://gitlab.com/api/v4/', + baseUrl: 'https://gitlab.com/', +} as ChangeLogProject; + describe('workers/pr/changelog/release-notes', () => { beforeEach(() => { hostRules.find.mockReturnValue({}); @@ -60,6 +77,7 @@ describe('workers/pr/changelog/release-notes', () => { it('handles date object', () => { expect(releaseNotesCacheMinutes(new Date())).toEqual(55); }); + it.each([null, undefined, 'fake', 123])('handles invalid: %s', (date) => { expect(releaseNotesCacheMinutes(date as never)).toEqual(55); }); @@ -69,31 +87,47 @@ describe('workers/pr/changelog/release-notes', () => { it('returns input if invalid', async () => { const input = { a: 1 }; expect(await addReleaseNotes(input as never)).toEqual(input); + expect(await addReleaseNotes(null)).toBeNull(); + expect(await addReleaseNotes({ versions: [] } as never)).toStrictEqual({ + versions: [], + }); }); + it('returns ChangeLogResult', async () => { const input = { a: 1, - project: { github: 'https://github.com/nodeca/js-yaml' }, + project: { + type: 'github', + repository: 'https://github.com/nodeca/js-yaml', + }, versions: [{ version: '3.10.0', compare: { url: '' } }], }; // FIXME: explicit assert condition expect(await addReleaseNotes(input as never)).toMatchSnapshot(); }); + it('returns ChangeLogResult without release notes', async () => { const input = { a: 1, - project: { gitlab: 'https://gitlab.com/gitlab-org/gitter/webapp/' }, - versions: [{ version: '20.26.0', compare: { url: '' } }], - }; + project: { + type: 'gitlab', + repository: 'https://gitlab.com/gitlab-org/gitter/webapp/', + } as ChangeLogProject, + versions: [ + { version: '20.26.0', compare: { url: '' } } as ChangeLogRelease, + ], + } as ChangeLogResult; // FIXME: explicit assert condition - expect(await addReleaseNotes(input as never)).toMatchSnapshot(); + expect(await addReleaseNotes(input)).toMatchSnapshot(); }); }); + describe('getReleaseList()', () => { it('should return empty array if no apiBaseUrl', async () => { - const res = await getReleaseList('', 'some/yet-other-repository'); + const res = await getReleaseList({} as ChangeLogProject); expect(res).toEqual([]); }); + it('should return release list for github repo', async () => { httpMock .scope('https://api.github.com/') @@ -106,14 +140,15 @@ describe('workers/pr/changelog/release-notes', () => { }, ]); - const res = await getReleaseList( - 'https://api.github.com/', - 'some/yet-other-repository' - ); + const res = await getReleaseList({ + ...githubProject, + repository: 'some/yet-other-repository', + }); // FIXME: explicit assert condition expect(res).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('should return release list for gitlab.com project', async () => { httpMock .scope('https://gitlab.com/') @@ -127,14 +162,15 @@ describe('workers/pr/changelog/release-notes', () => { body: 'some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)', }, ]); - const res = await getReleaseList( - 'https://gitlab.com/api/v4/', - 'some/yet-other-repository' - ); + const res = await getReleaseList({ + ...gitlabProject, + repository: 'some/yet-other-repository', + }); // FIXME: explicit assert condition expect(res).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('should return release list for self hosted gitlab project', async () => { hostRules.find.mockReturnValue({ token: 'some-token' }); httpMock @@ -149,15 +185,18 @@ describe('workers/pr/changelog/release-notes', () => { body: 'some body #123, [#124](https://my.custom.domain/some/yet-other-repository/issues/124)', }, ]); - const res = await getReleaseList( - 'https://my.custom.domain/api/v4/', - 'some/yet-other-repository' - ); + const res = await getReleaseList({ + ...gitlabProject, + repository: 'some/yet-other-repository', + apiBaseUrl: 'https://my.custom.domain/api/v4/', + baseUrl: 'https://my.custom.domain/', + }); // FIXME: explicit assert condition expect(res).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); }); }); + describe('getReleaseNotes()', () => { it('should return null for release notes without body', async () => { httpMock @@ -165,17 +204,19 @@ describe('workers/pr/changelog/release-notes', () => { .get('/repos/some/repository/releases?per_page=100') .reply(200, [{ tag_name: 'v1.0.0' }, { tag_name: 'v1.0.1' }]); const res = await getReleaseNotes( - 'some/repository', - '1.0.0', - 'some', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'some/repository', + depName: 'some', + }, + '1.0.0' ); expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it.each([[''], ['v'], ['other-'], ['other_v'], ['other@']])( - 'gets release notes with body', + 'gets release notes with body "%s"', async (prefix) => { httpMock .scope('https://api.github.com/') @@ -188,19 +229,21 @@ describe('workers/pr/changelog/release-notes', () => { }, ]); const res = await getReleaseNotes( - 'some/other-repository', - '1.0.1', - 'other', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'some/other-repository', + depName: 'other', + }, + '1.0.1' ); // FIXME: explicit assert condition expect(res).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); } ); + it.each([[''], ['v'], ['other-']])( - 'gets release notes with body from gitlab repo %s', + 'gets release notes with body from gitlab repo "%s"', async (prefix) => { httpMock .scope('https://api.gitlab.com/') @@ -209,52 +252,53 @@ describe('workers/pr/changelog/release-notes', () => { { tag_name: `${prefix}1.0.0` }, { tag_name: `${prefix}1.0.1`, - body: 'some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)', + description: + 'some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)', }, ]); const res = await getReleaseNotes( - 'some/other-repository', - '1.0.1', - 'other', - 'https://gitlab.com/', - 'https://api.gitlab.com/' + { + ...gitlabProject, + repository: 'some/other-repository', + depName: 'other', + apiBaseUrl: 'https://api.gitlab.com/', + }, + '1.0.1' ); // FIXME: explicit assert condition expect(res).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); } ); - it.each([[''], ['v'], ['other-']])( - 'gets null from repository without gitlab/github in domain %s', - async (prefix) => { - // FIXME: Should not call `api.lol.lol` ? - httpMock - .scope('https://api.lol.lol') - .get('/repos/some/other-repository/releases?per_page=100') - .reply(404); - const res = await getReleaseNotes( - 'some/other-repository', - '1.0.1', - 'other', - 'https://lol.lol/', - 'https://api.lol.lol/' - ); - expect(res).toBeNull(); - } - ); + + it('gets null from repository without gitlab/github in domain', async () => { + const res = await getReleaseNotes( + { + repository: 'some/repository', + depName: 'other', + apiBaseUrl: 'https://api.lol.lol/', + baseUrl: 'https://lol.lol/', + } as ChangeLogProject, + '1.0.1' + ); + expect(res).toBeNull(); + }); }); + describe('getReleaseNotesMd()', () => { it('handles not found', async () => { httpMock.scope('https://api.github.com').get('/repos/chalk').reply(404); const res = await getReleaseNotesMd( - 'chalk', - '2.0.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'chalk', + }, + '2.0.0' ); expect(res).toBeNull(); }); + it('handles files mismatch', async () => { httpMock .scope('https://api.github.com') @@ -268,14 +312,16 @@ describe('workers/pr/changelog/release-notes', () => { ], }); const res = await getReleaseNotesMd( - 'chalk', - '2.0.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'chalk', + }, + '2.0.0' ); expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('handles wrong format', async () => { httpMock .scope('https://api.github.com') @@ -288,14 +334,16 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from('not really markdown').toString('base64'), }); const res = await getReleaseNotesMd( - 'some/repository1', - '1.0.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'some/repository1', + }, + '1.0.0' ); expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('handles bad markdown', async () => { httpMock .scope('https://api.github.com') @@ -308,14 +356,16 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(`#\nha\nha\n#\nha\nha`).toString('base64'), }); const res = await getReleaseNotesMd( - 'some/repository2', - '1.0.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'some/repository2', + }, + '1.0.0' ); expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('parses angular.js', async () => { httpMock .scope('https://api.github.com') @@ -328,16 +378,18 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(angularJsChangelogMd).toString('base64'), }); const res = await getReleaseNotesMd( - 'angular/angular.js', - '1.6.9', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'angular/angular.js', + }, + '1.6.9' ); // FIXME: explicit assert condition expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('parses gitlab.com/gitlab-org/gitter/webapp', async () => { jest.setTimeout(0); httpMock @@ -349,16 +401,19 @@ describe('workers/pr/changelog/release-notes', () => { .get('/projects/gitlab-org%2fgitter%2fwebapp/repository/blobs/abcd/raw') .reply(200, gitterWebappChangelogMd); const res = await getReleaseNotesMd( - 'gitlab-org/gitter/webapp', - '20.26.0', - 'https://gitlab.com/', - 'https://api.gitlab.com/' + { + ...gitlabProject, + repository: 'gitlab-org/gitter/webapp', + apiBaseUrl: 'https://api.gitlab.com/', + }, + '20.26.0' ); expect(httpMock.getTrace()).toMatchSnapshot(); // FIXME: explicit assert condition expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('parses self hosted gitlab', async () => { hostRules.find.mockReturnValue({ token: 'some-token' }); jest.setTimeout(0); @@ -371,16 +426,20 @@ describe('workers/pr/changelog/release-notes', () => { .get('/projects/gitlab-org%2fgitter%2fwebapp/repository/blobs/abcd/raw') .reply(200, gitterWebappChangelogMd); const res = await getReleaseNotesMd( - 'gitlab-org/gitter/webapp', - '20.26.0', - 'https://my.custom.domain/', - 'https://my.custom.domain/' + { + ...gitlabProject, + repository: 'gitlab-org/gitter/webapp', + apiBaseUrl: 'https://my.custom.domain/', + baseUrl: 'https://my.custom.domain/', + }, + '20.26.0' ); expect(httpMock.getTrace()).toMatchSnapshot(); // FIXME: explicit assert condition expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('parses jest', async () => { httpMock .scope('https://api.github.com') @@ -393,16 +452,18 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(jestChangelogMd).toString('base64'), }); const res = await getReleaseNotesMd( - 'facebook/jest', - '22.0.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'facebook/jest', + }, + '22.0.0' ); expect(httpMock.getTrace()).toMatchSnapshot(); // FIXME: explicit assert condition expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('handles github sourceDirectory', async () => { const sourceDirectory = 'packages/foo'; const subdirTree = clone(githubTreeResponse); @@ -420,17 +481,19 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(jsYamlChangelogMd).toString('base64'), }); const res = await getReleaseNotesMd( - 'nodeca/js-yaml', - '3.10.0', - 'https://github.com/', - 'https://api.github.com/', - sourceDirectory + { + ...githubProject, + repository: 'nodeca/js-yaml', + sourceDirectory, + }, + '3.10.0' ); expect(httpMock.getTrace()).toMatchSnapshot(); // FIXME: explicit assert condition expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('parses js-yaml', async () => { httpMock .scope('https://api.github.com') @@ -443,16 +506,28 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(jsYamlChangelogMd).toString('base64'), }); const res = await getReleaseNotesMd( - 'nodeca/js-yaml', - '3.10.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'nodeca/js-yaml', + }, + '3.10.0' ); expect(httpMock.getTrace()).toMatchSnapshot(); // FIXME: explicit assert condition expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + + it('ignores invalid', async () => { + const res = await getReleaseNotesMd( + { + repository: 'nodeca/js-yaml', + } as ChangeLogProject, + '3.10.0' + ); + expect(res).toBeNull(); + }); + describe('ReleaseNotes Correctness', () => { let versionOneNotes: ChangeLogNotes; let versionTwoNotes: ChangeLogNotes; @@ -468,10 +543,11 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(yargsChangelogMd).toString('base64'), }); const res = await getReleaseNotesMd( - 'yargs/yargs', - '15.3.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'yargs/yargs', + }, + '15.3.0' ); versionOneNotes = res; expect(httpMock.getTrace()).toMatchSnapshot(); @@ -479,6 +555,7 @@ describe('workers/pr/changelog/release-notes', () => { expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('parses yargs 15.2.0', async () => { httpMock .scope('https://api.github.com') @@ -491,10 +568,11 @@ describe('workers/pr/changelog/release-notes', () => { content: Buffer.from(yargsChangelogMd).toString('base64'), }); const res = await getReleaseNotesMd( - 'yargs/yargs', - '15.2.0', - 'https://github.com/', - 'https://api.github.com/' + { + ...githubProject, + repository: 'yargs/yargs', + }, + '15.2.0' ); versionTwoNotes = res; expect(httpMock.getTrace()).toMatchSnapshot(); @@ -502,6 +580,7 @@ describe('workers/pr/changelog/release-notes', () => { expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('parses adapter-utils 4.33.0', async () => { httpMock .scope('https://gitlab.com/') @@ -514,10 +593,11 @@ describe('workers/pr/changelog/release-notes', () => { ) .reply(200, adapterutilsChangelogMd); const res = await getReleaseNotesMd( - 'itentialopensource/adapter-utils', - '4.33.0', - 'https://gitlab.com/', - 'https://gitlab.com/api/v4/' + { + ...gitlabProject, + repository: 'itentialopensource/adapter-utils', + }, + '4.33.0' ); versionTwoNotes = res; expect(httpMock.getTrace()).toMatchSnapshot(); @@ -525,6 +605,7 @@ describe('workers/pr/changelog/release-notes', () => { expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('handles gitlab sourceDirectory', async () => { const sourceDirectory = 'packages/foo'; const response = clone(gitlabTreeResponse).map((file) => ({ @@ -542,11 +623,12 @@ describe('workers/pr/changelog/release-notes', () => { ) .reply(200, adapterutilsChangelogMd); const res = await getReleaseNotesMd( - 'itentialopensource/adapter-utils', - '4.33.0', - 'https://gitlab.com/', - 'https://gitlab.com/api/v4/', - sourceDirectory + { + ...gitlabProject, + repository: 'itentialopensource/adapter-utils', + sourceDirectory, + }, + '4.33.0' ); versionTwoNotes = res; expect(httpMock.getTrace()).toMatchSnapshot(); @@ -554,9 +636,11 @@ describe('workers/pr/changelog/release-notes', () => { expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('isUrl', () => { expect(versionOneNotes).not.toMatchObject(versionTwoNotes); }); + it('15.3.0 is not equal to 15.2.0', () => { expect(versionOneNotes).not.toMatchObject(versionTwoNotes); }); diff --git a/lib/workers/pr/changelog/release-notes.ts b/lib/workers/pr/changelog/release-notes.ts index 51fba383c3..43067c9709 100644 --- a/lib/workers/pr/changelog/release-notes.ts +++ b/lib/workers/pr/changelog/release-notes.ts @@ -2,64 +2,61 @@ import * as URL from 'url'; import is from '@sindresorhus/is'; import { DateTime } from 'luxon'; import MarkdownIt from 'markdown-it'; -import { PLATFORM_TYPE_GITLAB } from '../../../constants/platforms'; import { logger } from '../../../logger'; import * as memCache from '../../../util/cache/memory'; import * as packageCache from '../../../util/cache/package'; -import * as hostRules from '../../../util/host-rules'; import { linkify } from '../../../util/markdown'; import * as github from './github'; import * as gitlab from './gitlab'; -import type { ChangeLogFile, ChangeLogNotes, ChangeLogResult } from './types'; +import type { + ChangeLogFile, + ChangeLogNotes, + ChangeLogProject, + ChangeLogResult, +} from './types'; const markdown = new MarkdownIt('zero'); markdown.enable(['heading', 'lheading']); export async function getReleaseList( - apiBaseUrl: string, - repository: string + project: ChangeLogProject ): Promise<ChangeLogNotes[]> { logger.trace('getReleaseList()'); - // istanbul ignore if - if (!apiBaseUrl) { - logger.debug('No apiBaseUrl'); - return []; - } + const { apiBaseUrl, repository, type } = project; try { - if (apiBaseUrl.includes('gitlab')) { - return await gitlab.getReleaseList(apiBaseUrl, repository); - } + switch (type) { + case 'gitlab': + return await gitlab.getReleaseList(apiBaseUrl, repository); + case 'github': + return await github.getReleaseList(apiBaseUrl, repository); - const opts = hostRules.find({ - hostType: PLATFORM_TYPE_GITLAB, - url: apiBaseUrl, - }); - if (opts.token) { - return await gitlab.getReleaseList(apiBaseUrl, repository); + default: + logger.warn({ apiBaseUrl, repository, type }, 'Invalid project type'); + return []; } - - return await github.getReleaseList(apiBaseUrl, repository); } catch (err) /* istanbul ignore next */ { if (err.statusCode === 404) { - logger.debug({ repository }, 'getReleaseList 404'); + logger.debug({ repository, type, apiBaseUrl }, 'getReleaseList 404'); } else { - logger.info({ repository, err }, 'getReleaseList error'); + logger.debug( + { repository, type, apiBaseUrl, err }, + 'getReleaseList error' + ); } - return []; } + return []; } export function getCachedReleaseList( - apiBaseUrl: string, - repository: string + project: ChangeLogProject ): Promise<ChangeLogNotes[]> { - const cacheKey = `getReleaseList-${apiBaseUrl}-${repository}`; + const cacheKey = `getReleaseList-${project.apiBaseUrl}-${project.repository}`; const cachedResult = memCache.get<Promise<ChangeLogNotes[]>>(cacheKey); // istanbul ignore if if (cachedResult !== undefined) { return cachedResult; } - const promisedRes = getReleaseList(apiBaseUrl, repository); + const promisedRes = getReleaseList(project); memCache.set(cacheKey, promisedRes); return promisedRes; } @@ -94,14 +91,12 @@ export function massageBody( } export async function getReleaseNotes( - repository: string, - version: string, - depName: string, - baseUrl: string, - apiBaseUrl: string + project: ChangeLogProject, + version: string ): Promise<ChangeLogNotes | null> { + const { baseUrl, depName, repository } = project; logger.trace(`getReleaseNotes(${repository}, ${version}, ${depName})`); - const releaseList = await getCachedReleaseList(apiBaseUrl, repository); + const releaseList = await getCachedReleaseList(project); logger.trace({ releaseList }, 'Release list from getReleaseList'); let releaseNotes: ChangeLogNotes | null = null; for (const release of releaseList) { @@ -172,84 +167,70 @@ function isUrl(url: string): boolean { } export async function getReleaseNotesMdFileInner( - repository: string, - apiBaseUrl: string, - sourceDirectory?: string + project: ChangeLogProject ): Promise<ChangeLogFile> | null { + const { apiBaseUrl, repository, sourceDirectory, type } = project; try { - if (apiBaseUrl.includes('gitlab')) { - return await gitlab.getReleaseNotesMd( - repository, - apiBaseUrl, - sourceDirectory - ); - } + switch (type) { + case 'gitlab': + return await gitlab.getReleaseNotesMd( + repository, + apiBaseUrl, + sourceDirectory + ); + case 'github': + return await github.getReleaseNotesMd( + repository, + apiBaseUrl, + sourceDirectory + ); - const opts = hostRules.find({ - hostType: PLATFORM_TYPE_GITLAB, - url: apiBaseUrl, - }); - if (opts.token) { - return await gitlab.getReleaseNotesMd( - repository, - apiBaseUrl, - sourceDirectory - ); + default: + logger.warn({ apiBaseUrl, repository, type }, 'Invalid project type'); + return null; } - - return await github.getReleaseNotesMd( - repository, - apiBaseUrl, - sourceDirectory - ); } catch (err) /* istanbul ignore next */ { if (err.statusCode === 404) { - logger.debug('Error 404 getting changelog md'); + logger.debug( + { repository, type, apiBaseUrl }, + 'Error 404 getting changelog md' + ); } else { - logger.debug({ err, repository }, 'Error getting changelog md'); + logger.debug( + { err, repository, type, apiBaseUrl }, + 'Error getting changelog md' + ); } - return null; } + return null; } export function getReleaseNotesMdFile( - repository: string, - apiBaseUrl: string, - sourceDirectory?: string + project: ChangeLogProject ): Promise<ChangeLogFile | null> { - const cacheKey = `getReleaseNotesMdFile-${repository}-${apiBaseUrl}`; + const cacheKey = `getReleaseNotesMdFile-${project.repository}-${project.apiBaseUrl}`; const cachedResult = memCache.get<Promise<ChangeLogFile | null>>(cacheKey); // istanbul ignore if if (cachedResult !== undefined) { return cachedResult; } - const promisedRes = getReleaseNotesMdFileInner( - repository, - apiBaseUrl, - sourceDirectory - ); + const promisedRes = getReleaseNotesMdFileInner(project); memCache.set(cacheKey, promisedRes); return promisedRes; } export async function getReleaseNotesMd( - repository: string, - version: string, - baseUrl: string, - apiBaseUrl: string, - sourceDirectory?: string + project: ChangeLogProject, + version: string ): Promise<ChangeLogNotes | null> { + const { baseUrl, repository } = project; logger.trace(`getReleaseNotesMd(${repository}, ${version})`); const skippedRepos = ['facebook/react-native']; // istanbul ignore if if (skippedRepos.includes(repository)) { return null; } - const changelog = await getReleaseNotesMdFile( - repository, - apiBaseUrl, - sourceDirectory - ); + const changelog = await getReleaseNotesMdFile(project); if (!changelog) { return null; } @@ -332,20 +313,13 @@ export function releaseNotesCacheMinutes(releaseDate?: string | Date): number { export async function addReleaseNotes( input: ChangeLogResult ): Promise<ChangeLogResult> { - if ( - !input?.versions || - (!input?.project?.github && !input?.project?.gitlab) - ) { + if (!input?.versions || !input.project?.type) { logger.debug('Missing project or versions'); return input; } const output: ChangeLogResult = { ...input, versions: [] }; - const repository = input.project.github - ? input.project.github.replace(/\.git$/, '') - : input.project.gitlab; - const cacheNamespace = input.project.github - ? 'changelog-github-notes' - : 'changelog-gitlab-notes'; + const repository = input.project.repository; + const cacheNamespace = `changelog-${input.project.type}-notes`; function getCacheKey(version: string): string { return `${repository}:${version}`; } @@ -355,23 +329,10 @@ export async function addReleaseNotes( releaseNotes = await packageCache.get(cacheNamespace, cacheKey); // istanbul ignore else: no cache tests if (!releaseNotes) { - const { sourceDirectory } = input.project; - releaseNotes = await getReleaseNotesMd( - repository, - v.version, - input.project.baseUrl, - input.project.apiBaseUrl, - sourceDirectory - ); + releaseNotes = await getReleaseNotesMd(input.project, v.version); // istanbul ignore else: should be tested if (!releaseNotes) { - releaseNotes = await getReleaseNotes( - repository, - v.version, - input.project.depName, - input.project.baseUrl, - input.project.apiBaseUrl - ); + releaseNotes = await getReleaseNotes(input.project, v.version); } // Small hack to force display of release notes when there is a compare url if (!releaseNotes && v.compare.url) { diff --git a/lib/workers/pr/changelog/source-github.ts b/lib/workers/pr/changelog/source-github.ts index b4577088b4..3dee765d2e 100644 --- a/lib/workers/pr/changelog/source-github.ts +++ b/lib/workers/pr/changelog/source-github.ts @@ -68,7 +68,10 @@ export async function getChangeLogJSON({ const apiBaseUrl = sourceUrl.startsWith('https://github.com/') ? 'https://api.github.com/' : baseUrl + 'api/v3/'; - const repository = pathname.slice(1).replace(/\/$/, ''); + const repository = pathname + .slice(1) + .replace(/\/$/, '') + .replace(/\.git$/, ''); if (repository.split('/').length !== 2) { logger.debug({ sourceUrl }, 'Invalid github URL found'); return null; @@ -154,8 +157,9 @@ export async function getChangeLogJSON({ project: { apiBaseUrl, baseUrl, - github: repository, - repository: sourceUrl, + type: 'github', + repository, + sourceUrl, sourceDirectory, depName, }, diff --git a/lib/workers/pr/changelog/source-gitlab.ts b/lib/workers/pr/changelog/source-gitlab.ts index c63c91c485..c241bb9a04 100644 --- a/lib/workers/pr/changelog/source-gitlab.ts +++ b/lib/workers/pr/changelog/source-gitlab.ts @@ -130,8 +130,9 @@ export async function getChangeLogJSON({ project: { apiBaseUrl, baseUrl, - gitlab: repository, - repository: sourceUrl, + type: 'gitlab', + repository, + sourceUrl, depName, }, versions: changelogReleases, diff --git a/lib/workers/pr/changelog/types.ts b/lib/workers/pr/changelog/types.ts index 554eefc2c4..48217fc59e 100644 --- a/lib/workers/pr/changelog/types.ts +++ b/lib/workers/pr/changelog/types.ts @@ -22,11 +22,11 @@ export interface ChangeLogRelease { export interface ChangeLogProject { depName?: string; - github?: string; - gitlab?: string; + type: 'github' | 'gitlab'; apiBaseUrl?: string; baseUrl: string; repository: string; + sourceUrl: string; sourceDirectory?: string; } diff --git a/lib/workers/pr/index.spec.ts b/lib/workers/pr/index.spec.ts index 746c5dff8f..d822834793 100644 --- a/lib/workers/pr/index.spec.ts +++ b/lib/workers/pr/index.spec.ts @@ -1,21 +1,18 @@ -import { git, mocked, partial } from '../../../test/util'; -import { getConfig } from '../../config/defaults'; +import { getConfig, git, mocked, partial, platform } from '../../../test/util'; import { PLATFORM_TYPE_GITLAB } from '../../constants/platforms'; -import { Pr, platform as _platform } from '../../platform'; +import type { Pr } from '../../platform/types'; import { BranchStatus } from '../../types'; import * as _limits from '../global/limits'; import type { BranchConfig } from '../types'; import * as prAutomerge from './automerge'; import * as _changelogHelper from './changelog'; -import { getChangeLogJSON } from './changelog'; +import type { ChangeLogResult } from './changelog'; import * as codeOwners from './code-owners'; import * as prWorker from '.'; const codeOwnersMock = mocked(codeOwners); const changelogHelper = mocked(_changelogHelper); const gitlabChangelogHelper = mocked(_changelogHelper); -const platform = mocked(_platform); -const defaultConfig = getConfig(); const limits = mocked(_limits); jest.mock('../../util/git'); @@ -24,12 +21,12 @@ jest.mock('./code-owners'); jest.mock('../global/limits'); function setupChangelogMock() { - changelogHelper.getChangeLogJSON = jest.fn(); const resultValue = { project: { + type: 'github', baseUrl: 'https://github.com/', - github: 'renovateapp/dummy', - repository: 'https://github.com/renovateapp/dummy', + repository: 'renovateapp/dummy', + sourceUrl: 'https://github.com/renovateapp/dummy', }, hasReleaseNotes: true, versions: [ @@ -51,7 +48,7 @@ function setupChangelogMock() { }, }, ], - }; + } as ChangeLogResult; const errorValue = { error: _changelogHelper.ChangeLogError.MissingGithubToken, }; @@ -61,12 +58,12 @@ function setupChangelogMock() { } function setupGitlabChangelogMock() { - gitlabChangelogHelper.getChangeLogJSON = jest.fn(); const resultValue = { project: { + type: 'gitlab', baseUrl: 'https://gitlab.com/', - gitlab: 'renovateapp/gitlabdummy', - repository: 'https://gitlab.com/renovateapp/gitlabdummy', + repository: 'renovateapp/gitlabdummy', + sourceUrl: 'https://gitlab.com/renovateapp/gitlabdummy', }, hasReleaseNotes: true, versions: [ @@ -88,7 +85,7 @@ function setupGitlabChangelogMock() { }, }, ], - }; + } as ChangeLogResult; const errorValue = { error: _changelogHelper.ChangeLogError.MissingGithubToken, }; @@ -103,7 +100,7 @@ describe('workers/pr/index', () => { let pr: Pr; beforeEach(() => { config = partial<BranchConfig>({ - ...defaultConfig, + ...getConfig(), }); pr = partial<Pr>({ canMerge: true, @@ -180,7 +177,7 @@ describe('workers/pr/index', () => { jest.resetAllMocks(); setupChangelogMock(); config = partial<BranchConfig>({ - ...defaultConfig, + ...getConfig(), }); config.branchName = 'renovate/dummy-1.x'; config.prTitle = 'Update dependency dummy to v1.1.0'; @@ -199,9 +196,7 @@ describe('workers/pr/index', () => { displayNumber: 'New Pull Request', } as never); config.upgrades = [config]; - platform.massageMarkdown = jest.fn((input) => input); - platform.getBranchPr = jest.fn(); - platform.getBranchStatus = jest.fn(); + platform.massageMarkdown.mockImplementation((input) => input); }); afterEach(() => { jest.clearAllMocks(); @@ -252,7 +247,7 @@ describe('workers/pr/index', () => { }); it('should create PR if success', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); config.prCreation = 'status-success'; config.automerge = true; config.schedule = ['before 5am']; @@ -264,7 +259,7 @@ describe('workers/pr/index', () => { }); it('should not create PR if limit is reached', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); config.prCreation = 'status-success'; config.automerge = true; config.schedule = ['before 5am']; @@ -275,7 +270,7 @@ describe('workers/pr/index', () => { }); it('should create PR if limit is reached but dashboard checked', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); config.prCreation = 'status-success'; config.automerge = true; config.schedule = ['before 5am']; @@ -315,7 +310,7 @@ describe('workers/pr/index', () => { config.recreateClosed = true; config.rebaseWhen = 'never'; for (const upgrade of config.upgrades) { - upgrade.logJSON = await getChangeLogJSON(upgrade); + upgrade.logJSON = await changelogHelper.getChangeLogJSON(upgrade); } const { pr } = await prWorker.ensurePr(config); expect(pr).toMatchObject({ displayNumber: 'New Pull Request' }); @@ -330,7 +325,7 @@ describe('workers/pr/index', () => { config.schedule = ['before 5am']; config.timezone = 'some timezone'; config.rebaseWhen = 'behind-base-branch'; - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); const { pr } = await prWorker.ensurePr(config); expect(pr).toMatchObject({ displayNumber: 'New Pull Request' }); // FIXME: explicit assert condition @@ -341,7 +336,6 @@ describe('workers/pr/index', () => { }); it('should return null if creating PR fails', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); - platform.createPr = jest.fn(); platform.createPr.mockImplementationOnce(() => { throw new Error('Validation Failed (422)'); }); @@ -405,6 +399,7 @@ describe('workers/pr/index', () => { config.assignees = ['@foo', 'bar']; config.reviewers = ['foo', '@bar', 'foo@bar.com']; config.filterUnavailableUsers = true; + // optional function is undefined by jest platform.filterUnavailableUsers = jest.fn(); platform.filterUnavailableUsers.mockResolvedValue(['foo']); await prWorker.ensurePr(config); @@ -523,7 +518,7 @@ describe('workers/pr/index', () => { config.semanticCommitScope = null; config.automerge = true; config.schedule = ['before 5am']; - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); const { pr } = await prWorker.ensurePr(config); expect(platform.updatePr.mock.calls).toMatchSnapshot(); expect(platform.updatePr).toHaveBeenCalledTimes(0); @@ -537,7 +532,7 @@ describe('workers/pr/index', () => { config.semanticCommitScope = null; config.automerge = true; config.schedule = ['before 5am']; - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); const { pr } = await prWorker.ensurePr(config); expect(platform.updatePr).toHaveBeenCalledTimes(0); expect(pr).toMatchObject(modifiedPr); @@ -546,7 +541,7 @@ describe('workers/pr/index', () => { config.newValue = '1.2.0'; config.automerge = true; config.schedule = ['before 5am']; - config.logJSON = await getChangeLogJSON(config); + config.logJSON = await changelogHelper.getChangeLogJSON(config); platform.getBranchPr.mockResolvedValueOnce(existingPr); const { pr } = await prWorker.ensurePr(config); // FIXME: explicit assert condition @@ -614,9 +609,8 @@ describe('workers/pr/index', () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.prCreation = 'status-success'; config.privateRepo = false; - config.logJSON = await getChangeLogJSON(config); - config.logJSON.project.gitlab = 'someproject'; - delete config.logJSON.project.github; + config.logJSON = await changelogHelper.getChangeLogJSON(config); + config.logJSON.project.repository = 'someproject'; const { pr } = await prWorker.ensurePr(config); expect(pr).toMatchObject({ displayNumber: 'New Pull Request' }); expect(platform.createPr.mock.calls[0]).toMatchSnapshot(); diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts index b3cf078dae..f6f317cfdd 100644 --- a/lib/workers/pr/index.ts +++ b/lib/workers/pr/index.ts @@ -278,9 +278,7 @@ export async function ensurePr( if (logJSON) { if (typeof logJSON.error === 'undefined') { if (logJSON.project) { - upgrade.repoName = logJSON.project.github - ? logJSON.project.github - : logJSON.project.gitlab; + upgrade.repoName = logJSON.project.repository; } upgrade.hasReleaseNotes = logJSON.hasReleaseNotes; upgrade.releases = []; -- GitLab