feat(release-notes)!: support configurable fetching stage (#22781)

Changes fetchReleaseNotes from boolean to enum, with values off, branch, pr.

Closes #20476

BREAKING CHANGE: Release notes won't be fetched early for commitBody insertion unless explicitly configured with fetchReleaseNotes=branch
This commit is contained in:
RahulGautamSingh 2023-06-18 13:58:25 +05:45 committed by Rhys Arkins
parent aa14b777c0
commit c2d3ca856f
14 changed files with 66 additions and 114 deletions

View file

@ -931,7 +931,14 @@ A similar one could strip leading `v` prefixes:
## fetchReleaseNotes
Set this to `false` if you want to disable release notes fetching.
Use this config option to configure release notes fetching.
The available options are:
- `off` - disable release notes fetching
- `branch` - fetch release notes while creating/updating branch
- `pr`(default) - fetches release notes while creating/updating pull-request
It is not recommended to set fetchReleaseNotes=branch unless you are embedding release notes in commit information, because it results in a performance decrease.
Renovate can fetch release notes when they are hosted on one of these platforms:

View file

@ -0,0 +1,22 @@
import { FetchReleaseNotesMigration } from './fetch-release-notes-migration';
describe('config/migrations/custom/fetch-release-notes-migration', () => {
it('migrates', () => {
expect(FetchReleaseNotesMigration).toMigrate(
{
fetchReleaseNotes: false as never,
},
{
fetchReleaseNotes: 'off',
}
);
expect(FetchReleaseNotesMigration).toMigrate(
{
fetchReleaseNotes: true as never,
},
{
fetchReleaseNotes: 'pr',
}
);
});
});

View file

@ -0,0 +1,12 @@
import is from '@sindresorhus/is';
import { AbstractMigration } from '../base/abstract-migration';
export class FetchReleaseNotesMigration extends AbstractMigration {
override readonly propertyName = 'fetchReleaseNotes';
override run(value: unknown): void {
if (is.boolean(value)) {
this.rewrite(value ? 'pr' : 'off');
}
}
}

View file

@ -20,6 +20,7 @@ import { DepTypesMigration } from './custom/dep-types-migration';
import { DryRunMigration } from './custom/dry-run-migration';
import { EnabledManagersMigration } from './custom/enabled-managers-migration';
import { ExtendsMigration } from './custom/extends-migration';
import { FetchReleaseNotesMigration } from './custom/fetch-release-notes-migration';
import { GoModTidyMigration } from './custom/go-mod-tidy-migration';
import { HostRulesMigration } from './custom/host-rules-migration';
import { IgnoreNodeModulesMigration } from './custom/ignore-node-modules-migration';
@ -148,6 +149,7 @@ export class MigrationsService {
DatasourceMigration,
RecreateClosedMigration,
StabilityDaysMigration,
FetchReleaseNotesMigration,
];
static run(originalConfig: RenovateConfig): RenovateConfig {

View file

@ -2567,9 +2567,10 @@ const options: RenovateOptions[] = [
},
{
name: 'fetchReleaseNotes',
description: 'Controls if release notes are fetched.',
type: 'boolean',
default: true,
description: 'Controls if and when release notes are fetched.',
type: 'string',
allowedValues: ['off', 'branch', 'pr'],
default: 'pr',
cli: false,
env: false,
},

View file

@ -261,7 +261,7 @@ export interface RenovateConfig
vulnerabilitySeverity?: string;
regexManagers?: RegExManager[];
fetchReleaseNotes?: boolean;
fetchReleaseNotes?: FetchReleaseNotesOptions;
secrets?: Record<string, string>;
constraints?: Record<string, string>;
@ -302,6 +302,8 @@ export type UpdateType =
| 'bump'
| 'replacement';
export type FetchReleaseNotesOptions = 'off' | 'branch' | 'pr';
export type MatchStringsStrategy = 'any' | 'recursive' | 'combination';
export type MergeStrategy =

View file

@ -1,7 +1,7 @@
import { mockedFunction, partial } from '../../../../test/util';
import type { BranchUpgradeConfig } from '../../types';
import { getChangeLogJSON } from '../update/pr/changelog';
import { embedChangelogs, needsChangelogs } from '.';
import { embedChangelogs } from '.';
jest.mock('../update/pr/changelog');
@ -27,23 +27,4 @@ describe('workers/repository/changelog/index', () => {
{ logJSON: null },
]);
});
it('needsChangelogs', () => {
expect(needsChangelogs(partial<BranchUpgradeConfig>())).toBeFalse();
expect(
needsChangelogs(
partial<BranchUpgradeConfig>({
commitBody: '{{#if logJSON.hasReleaseNotes}}has changelog{{/if}}',
})
)
).toBeFalse();
expect(
needsChangelogs(
partial<BranchUpgradeConfig>({
commitBody: '{{#if logJSON.hasReleaseNotes}}has changelog{{/if}}',
}),
['commitBody']
)
).toBeTrue();
});
});

View file

@ -1,8 +1,4 @@
import * as p from '../../../util/promises';
import {
containsTemplates,
exposedConfigOptions,
} from '../../../util/template';
import type { BranchUpgradeConfig } from '../../types';
import { getChangeLogJSON } from '../update/pr/changelog';
@ -21,17 +17,3 @@ export async function embedChangelogs(
): Promise<void> {
await p.map(branches, embedChangelog, { concurrency: 10 });
}
export function needsChangelogs(
upgrade: BranchUpgradeConfig,
fields = exposedConfigOptions.filter((o) => o !== 'commitBody')
): boolean {
// commitBody is now compiled when commit is done
for (const field of fields) {
// fields set by `getChangeLogJSON`
if (containsTemplates(upgrade[field], ['logJSON', 'releases'])) {
return true;
}
}
return false;
}

View file

@ -2,7 +2,6 @@ import {
fs,
git,
mocked,
mockedFunction,
partial,
platform,
scm,
@ -34,7 +33,6 @@ import * as _mergeConfidence from '../../../../util/merge-confidence';
import * as _sanitize from '../../../../util/sanitize';
import * as _limits from '../../../global/limits';
import type { BranchConfig, BranchUpgradeConfig } from '../../../types';
import { needsChangelogs } from '../../changelog';
import type { ResultWithPr } from '../pr';
import * as _prWorker from '../pr';
import * as _prAutomerge from '../pr/automerge';
@ -816,9 +814,8 @@ describe('workers/repository/update/branch/index', () => {
ignoreTests: true,
prCreation: 'not-pending',
commitBody: '[skip-ci]',
fetchReleaseNotes: true,
fetchReleaseNotes: 'branch',
} satisfies BranchConfig;
mockedFunction(needsChangelogs).mockReturnValueOnce(true);
scm.getBranchCommit.mockResolvedValue('123test'); //TODO:not needed?
expect(await branchWorker.processBranch(inconfig)).toEqual({
branchExists: true,

View file

@ -34,7 +34,7 @@ import { toMs } from '../../../../util/pretty-time';
import * as template from '../../../../util/template';
import { isLimitReached } from '../../../global/limits';
import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types';
import { embedChangelog, needsChangelogs } from '../../changelog';
import { embedChangelogs } from '../../changelog';
import { ensurePr } from '../pr';
import { checkAutoMerge } from '../pr/automerge';
import { setArtifactErrorStatus } from './artifacts';
@ -482,6 +482,10 @@ export async function processBranch(
} else {
logger.debug('No updated lock files in branch');
}
if (config.fetchReleaseNotes === 'branch') {
await embedChangelogs(config.upgrades);
}
const postUpgradeCommandResults = await executePostUpgradeCommands(
config
);
@ -540,14 +544,6 @@ export async function processBranch(
// compile commit message with body, which maybe needs changelogs
if (config.commitBody) {
if (
config.fetchReleaseNotes &&
needsChangelogs(config, ['commitBody'])
) {
// we only need first upgrade, the others are only needed on PR update
// we add it to first, so PR fetch can skip fetching for that update
await embedChangelog(config.upgrades[0]);
}
// changelog is on first upgrade
config.commitMessage = `${config.commitMessage!}\n\n${template.compile(
config.commitBody,

View file

@ -102,7 +102,7 @@ describe('workers/repository/update/pr/index', () => {
platform.createPr.mockResolvedValueOnce(pr);
limits.isLimitReached.mockReturnValueOnce(true);
config.fetchReleaseNotes = true;
config.fetchReleaseNotes = 'pr';
const res = await ensurePr(config);
@ -871,13 +871,13 @@ describe('workers/repository/update/pr/index', () => {
bodyFingerprint: fingerprint(
generatePrBodyFingerprintConfig({
...config,
fetchReleaseNotes: true,
fetchReleaseNotes: 'pr',
})
),
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
};
prCache.getPrCache.mockReturnValueOnce(cachedPr);
const res = await ensurePr({ ...config, fetchReleaseNotes: true });
const res = await ensurePr({ ...config, fetchReleaseNotes: 'pr' });
expect(res).toEqual({
type: 'with-pr',
pr: existingPr,
@ -904,13 +904,13 @@ describe('workers/repository/update/pr/index', () => {
bodyFingerprint: fingerprint(
generatePrBodyFingerprintConfig({
...config,
fetchReleaseNotes: true,
fetchReleaseNotes: 'pr',
})
),
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
};
prCache.getPrCache.mockReturnValueOnce(cachedPr);
const res = await ensurePr({ ...config, fetchReleaseNotes: true });
const res = await ensurePr({ ...config, fetchReleaseNotes: 'pr' });
expect(res).toEqual({
type: 'with-pr',
pr: {

View file

@ -234,7 +234,7 @@ export async function ensurePr(
}`;
}
if (config.fetchReleaseNotes) {
if (config.fetchReleaseNotes === 'pr') {
// fetch changelogs when not already done;
await embedChangelogs(upgrades);
}

View file

@ -1,4 +1,4 @@
import { RenovateConfig, mocked, mockedFunction } from '../../../../test/util';
import { RenovateConfig, mocked } from '../../../../test/util';
import { getConfig } from '../../../config/defaults';
import * as _changelog from '../changelog';
import { branchifyUpgrades } from './branchify';
@ -124,7 +124,7 @@ describe('workers/repository/updates/branchify', () => {
});
it('no fetch changelogs', async () => {
config.fetchReleaseNotes = false;
config.fetchReleaseNotes = 'off';
flattenUpdates.mockResolvedValueOnce([
{
depName: 'foo',
@ -153,38 +153,5 @@ describe('workers/repository/updates/branchify', () => {
expect(embedChangelogs).not.toHaveBeenCalled();
expect(Object.keys(res.branches)).toHaveLength(2);
});
it('fetch changelogs if required', async () => {
config.fetchReleaseNotes = true;
config.repoIsOnboarded = true;
mockedFunction(_changelog.needsChangelogs).mockReturnValueOnce(true);
flattenUpdates.mockResolvedValueOnce([
{
depName: 'foo',
branchName: 'foo',
prTitle: 'some-title',
version: '1.1.0',
groupName: 'My Group',
group: { branchName: 'renovate/{{groupSlug}}' },
},
{
depName: 'foo',
branchName: 'foo',
prTitle: 'some-title',
version: '2.0.0',
},
{
depName: 'bar',
branchName: 'bar-{{version}}',
prTitle: 'some-title',
version: '1.1.0',
groupName: 'My Group',
group: { branchName: 'renovate/my-group' },
},
]);
const res = await branchifyUpgrades(config, {});
expect(embedChangelogs).toHaveBeenCalledOnce();
expect(Object.keys(res.branches)).toHaveLength(2);
});
});
});

View file

@ -3,7 +3,6 @@ import type { Merge } from 'type-fest';
import type { RenovateConfig, ValidationMessage } from '../../../config/types';
import { addMeta, logger, removeMeta } from '../../../logger';
import type { BranchConfig, BranchUpgradeConfig } from '../../types';
import { embedChangelogs, needsChangelogs } from '../changelog';
import { flattenUpdates } from './flatten';
import { generateBranchConfig } from './generate';
@ -72,22 +71,6 @@ export async function branchifyUpgrades(
})
.reverse();
if (config.fetchReleaseNotes && config.repoIsOnboarded) {
const branches = branchUpgrades[branchName].filter((upg) =>
needsChangelogs(upg)
);
if (branches.length) {
logger.warn(
{
branches: branches.map((b) => b.branchName),
docs: 'https://docs.renovatebot.com/templates/',
},
'Fetching changelogs early is deprecated. Remove `logJSON` and `releases` from config templates. They are only allowed in `commitBody` template. See template docs for allowed templates'
);
await embedChangelogs(branches);
}
}
const branch = generateBranchConfig(branchUpgrades[branchName]);
branch.branchName = branchName;
branch.packageFiles = packageFiles;