diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 1fdb5949fd265334502c59f5ce128577f9218a09..78a95f31b08a24f102aeeeb776222e467d71c11a 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1876,6 +1876,30 @@ For example to apply a special label for Major updates: } ``` +### customChangelogUrl + +Use this field to set the source URL for a package, including overriding an existing one. +Source URLs are necessary in order to look up release notes. + +Using this field we can specify the exact url to fetch release notes from. + +Example setting source URL for package "dummy": + +```json +{ + "packageRules": [ + { + "matchPackageNames": ["dummy"], + "customChangelogUrl": "https://github.com/org/dummy" + } + ] +} +``` + +<!-- prettier-ignore --> +!!! note +Renovate can fetch changelogs from GitHub and GitLab platforms only, and setting the URL to an unsupported host/platform type won't change that. + ### replacementName This config option only works with the `npm` manager. diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index a8736facccf5c49880169567c0b869cf301cd0ef..a744bc9dde4f192dbea6b09a66114b558d5c147f 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1185,6 +1185,16 @@ const options: RenovateOptions[] = [ cli: false, env: false, }, + { + name: 'customChangelogUrl', + description: + 'If set, Renovate will use this url to fetch changelogs for a matched dependency. Valid only within a `packageRules` object.', + type: 'string', + stage: 'pr', + parent: 'packageRules', + cli: false, + env: false, + }, { name: 'pinDigests', description: 'Whether to add digests to Dockerfile source images.', diff --git a/lib/config/types.ts b/lib/config/types.ts index 65ccc58323e5c86e1cf4a789c90af6dcab27b121..a955ec2ee749b971f3dcce2ef7d37762e8d626c8 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -34,6 +34,7 @@ export interface RenovateSharedConfig { commitMessage?: string; commitMessagePrefix?: string; confidential?: boolean; + customChangelogUrl?: string; draftPR?: boolean; enabled?: boolean; enabledManagers?: string[]; diff --git a/lib/workers/repository/update/pr/changelog/common.spec.ts b/lib/workers/repository/update/pr/changelog/common.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca4613d24c944262a14e5e106a17b8905ccceaae --- /dev/null +++ b/lib/workers/repository/update/pr/changelog/common.spec.ts @@ -0,0 +1,14 @@ +import { slugifyUrl } from './common'; + +describe('workers/repository/update/pr/changelog/common', () => { + it.each` + url | expected + ${'https://github-enterprise.example.com/çhãlk/chálk'} | ${'https-github-enterprise-example-com-chalk-chalk'} + ${'https://github.com/chalk/chalk'} | ${'https-github-com-chalk-chalk'} + ${'https://github-enterprise.example.com/'} | ${'https-github-enterprise-example-com'} + ${'https://github.com/sindresorhus/delay'} | ${'https-github-com-sindresorhus-delay'} + ${'https://github.com/🔥/∂u/∂t/equals/α∇^2u'} | ${'https-github-com-du-dt-equals-a2u'} + `('isSingleVersion("$url") === $expected', ({ url, expected }) => { + expect(slugifyUrl(url)).toBe(expected); + }); +}); diff --git a/lib/workers/repository/update/pr/changelog/common.ts b/lib/workers/repository/update/pr/changelog/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd2f8f4d3f90dcb46af97b7e5e69ec48325f60ed --- /dev/null +++ b/lib/workers/repository/update/pr/changelog/common.ts @@ -0,0 +1,7 @@ +import slugify from 'slugify'; +import { regEx } from '../../../../../util/regex'; + +export function slugifyUrl(url: string): string { + const r = regEx(/(:?[:/.])+/g); + return slugify(url.replace(r, ' ')); +} diff --git a/lib/workers/repository/update/pr/changelog/github.spec.ts b/lib/workers/repository/update/pr/changelog/github.spec.ts index cb5ab72dc5f11912cce3308496864d4ef871504b..7d0c6d6f2cbaf2fd8b96659d9067630ae7a40a4f 100644 --- a/lib/workers/repository/update/pr/changelog/github.spec.ts +++ b/lib/workers/repository/update/pr/changelog/github.spec.ts @@ -265,6 +265,34 @@ describe('workers/repository/update/pr/changelog/github', () => { }); }); + it('supports overwriting sourceUrl for supports github enterprise and github.com changelog', async () => { + const sourceUrl = upgrade.sourceUrl; + const replacementSourceUrl = 'https://github.com/sindresorhus/got'; + const config = { + ...upgrade, + endpoint: 'https://github-enterprise.example.com/', + customChangelogUrl: replacementSourceUrl, + }; + hostRules.add({ + hostType: PlatformId.Github, + token: 'super_secret', + matchHost: 'https://github-enterprise.example.com/', + }); + expect(await getChangeLogJSON(config)).toMatchObject({ + hasReleaseNotes: true, + project: { + apiBaseUrl: 'https://api.github.com/', + baseUrl: 'https://github.com/', + depName: 'renovate', + repository: 'sindresorhus/got', + sourceDirectory: undefined, + sourceUrl: 'https://github.com/sindresorhus/got', + type: 'github', + }, + }); + expect(upgrade.sourceUrl).toBe(sourceUrl); // ensure unmodified function argument + }); + it('supports github enterprise and github enterprise changelog', async () => { hostRules.add({ hostType: PlatformId.Github, @@ -298,6 +326,37 @@ describe('workers/repository/update/pr/changelog/github', () => { }); }); + it('supports overwriting sourceUrl for github enterprise and github enterprise changelog', async () => { + const sourceUrl = 'https://github-enterprise.example.com/chalk/chalk'; + const replacementSourceUrl = + 'https://github-enterprise.example.com/sindresorhus/got'; + const config = { + ...upgrade, + sourceUrl, + endpoint: 'https://github-enterprise.example.com/', + customChangelogUrl: replacementSourceUrl, + }; + hostRules.add({ + hostType: PlatformId.Github, + matchHost: 'https://github-enterprise.example.com/', + token: 'abc', + }); + process.env.GITHUB_ENDPOINT = ''; + expect(await getChangeLogJSON(config)).toMatchObject({ + hasReleaseNotes: true, + project: { + apiBaseUrl: 'https://github-enterprise.example.com/api/v3/', + baseUrl: 'https://github-enterprise.example.com/', + depName: 'renovate', + repository: 'sindresorhus/got', + sourceDirectory: undefined, + sourceUrl: 'https://github-enterprise.example.com/sindresorhus/got', + type: 'github', + }, + }); + expect(config.sourceUrl).toBe(sourceUrl); // ensure unmodified function argument + }); + it('works with same version releases but different prefix', async () => { const githubTagsMock = jest.spyOn( CacheableGithubTags.prototype, diff --git a/lib/workers/repository/update/pr/changelog/gitlab.spec.ts b/lib/workers/repository/update/pr/changelog/gitlab.spec.ts index 0e27e9f65115040e91c267035f334b42878af4c0..c9196a53e65697dbad799ea0b74d3e8c84b41b0f 100644 --- a/lib/workers/repository/update/pr/changelog/gitlab.spec.ts +++ b/lib/workers/repository/update/pr/changelog/gitlab.spec.ts @@ -315,5 +315,38 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { ], }); }); + + it('supports overwriting sourceUrl for self-hosted gitlab changelog', async () => { + httpMock.scope('https://git.test.com').persist().get(/.*/).reply(200, []); + const sourceUrl = 'https://git.test.com/meno/dropzone/'; + const replacementSourceUrl = + 'https://git.test.com/replacement/sourceurl/'; + const config = { + ...upgrade, + platform: PlatformId.Gitlab, + endpoint: 'https://git.test.com/api/v4/', + sourceUrl, + customChangelogUrl: replacementSourceUrl, + }; + hostRules.add({ + hostType: PlatformId.Gitlab, + matchHost: 'https://git.test.com/', + token: 'abc', + }); + process.env.GITHUB_ENDPOINT = ''; + expect(await getChangeLogJSON(config)).toMatchObject({ + hasReleaseNotes: false, + project: { + apiBaseUrl: 'https://git.test.com/api/v4/', + baseUrl: 'https://git.test.com/', + depName: 'renovate', + repository: 'replacement/sourceurl', + sourceDirectory: undefined, + sourceUrl: 'https://git.test.com/replacement/sourceurl/', + type: 'gitlab', + }, + }); + expect(config.sourceUrl).toBe(sourceUrl); // ensure unmodified function argument + }); }); }); diff --git a/lib/workers/repository/update/pr/changelog/index.ts b/lib/workers/repository/update/pr/changelog/index.ts index fcca0fc7f56028d078d1da1ae92874d34398587e..a0e07f0c22736d6df35a979536528f1fc0d53e51 100644 --- a/lib/workers/repository/update/pr/changelog/index.ts +++ b/lib/workers/repository/update/pr/changelog/index.ts @@ -9,9 +9,11 @@ import type { ChangeLogResult } from './types'; export * from './types'; export async function getChangeLogJSON( - config: BranchUpgradeConfig + _config: BranchUpgradeConfig ): Promise<ChangeLogResult | null> { - const { sourceUrl, versioning, currentVersion, newVersion } = config; + const sourceUrl = _config.customChangelogUrl ?? _config.sourceUrl!; + const config: BranchUpgradeConfig = { ..._config, sourceUrl }; + const { versioning, currentVersion, newVersion } = config; try { if (!(sourceUrl && currentVersion && newVersion)) { return null; diff --git a/lib/workers/repository/update/pr/changelog/source-github.ts b/lib/workers/repository/update/pr/changelog/source-github.ts index 7631f6cd2e90537f874e746bbdf8c63745427550..d8dabe224e50130aa7cf62278431294d5295cb32 100644 --- a/lib/workers/repository/update/pr/changelog/source-github.ts +++ b/lib/workers/repository/update/pr/changelog/source-github.ts @@ -10,6 +10,7 @@ import * as packageCache from '../../../../../util/cache/package'; import * as hostRules from '../../../../../util/host-rules'; import { regEx } from '../../../../../util/regex'; import type { BranchUpgradeConfig } from '../../../../types'; +import { slugifyUrl } from './common'; import { getTags } from './github'; import { addReleaseNotes } from './release-notes'; import { getInRangeReleases } from './releases'; @@ -120,8 +121,9 @@ export async function getChangeLogJSON( } const cacheNamespace = 'changelog-github-release'; + function getCacheKey(prev: string, next: string): string { - return `${manager}:${depName}:${prev}:${next}`; + return `${slugifyUrl(sourceUrl)}:${depName}:${prev}:${next}`; } const changelogReleases: ChangeLogRelease[] = []; diff --git a/lib/workers/repository/update/pr/changelog/source-gitlab.ts b/lib/workers/repository/update/pr/changelog/source-gitlab.ts index 2cd1b38c2761090fed30a4c6dbd5f95bb1e02d26..e55b681849df16147d7271f314e9d04f5ff5560a 100644 --- a/lib/workers/repository/update/pr/changelog/source-gitlab.ts +++ b/lib/workers/repository/update/pr/changelog/source-gitlab.ts @@ -7,6 +7,7 @@ import * as memCache from '../../../../../util/cache/memory'; import * as packageCache from '../../../../../util/cache/package'; import { regEx } from '../../../../../util/regex'; import type { BranchUpgradeConfig } from '../../../../types'; +import { slugifyUrl } from './common'; import { getTags } from './gitlab'; import { addReleaseNotes } from './release-notes'; import { getInRangeReleases } from './releases'; @@ -38,7 +39,6 @@ export async function getChangeLogJSON( const newVersion = config.newVersion!; const sourceUrl = config.sourceUrl!; const depName = config.depName!; - const manager = config.manager; const sourceDirectory = config.sourceDirectory!; logger.trace('getChangeLogJSON for gitlab'); @@ -95,7 +95,7 @@ export async function getChangeLogJSON( } function getCacheKey(prev: string, next: string): string { - return `${manager}:${depName}:${prev}:${next}`; + return `${slugifyUrl(sourceUrl)}:${depName}:${prev}:${next}`; } const changelogReleases: ChangeLogRelease[] = [];