mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
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:
parent
aa14b777c0
commit
c2d3ca856f
14 changed files with 66 additions and 114 deletions
|
@ -931,7 +931,14 @@ A similar one could strip leading `v` prefixes:
|
||||||
|
|
||||||
## fetchReleaseNotes
|
## 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:
|
Renovate can fetch release notes when they are hosted on one of these platforms:
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import { DepTypesMigration } from './custom/dep-types-migration';
|
||||||
import { DryRunMigration } from './custom/dry-run-migration';
|
import { DryRunMigration } from './custom/dry-run-migration';
|
||||||
import { EnabledManagersMigration } from './custom/enabled-managers-migration';
|
import { EnabledManagersMigration } from './custom/enabled-managers-migration';
|
||||||
import { ExtendsMigration } from './custom/extends-migration';
|
import { ExtendsMigration } from './custom/extends-migration';
|
||||||
|
import { FetchReleaseNotesMigration } from './custom/fetch-release-notes-migration';
|
||||||
import { GoModTidyMigration } from './custom/go-mod-tidy-migration';
|
import { GoModTidyMigration } from './custom/go-mod-tidy-migration';
|
||||||
import { HostRulesMigration } from './custom/host-rules-migration';
|
import { HostRulesMigration } from './custom/host-rules-migration';
|
||||||
import { IgnoreNodeModulesMigration } from './custom/ignore-node-modules-migration';
|
import { IgnoreNodeModulesMigration } from './custom/ignore-node-modules-migration';
|
||||||
|
@ -148,6 +149,7 @@ export class MigrationsService {
|
||||||
DatasourceMigration,
|
DatasourceMigration,
|
||||||
RecreateClosedMigration,
|
RecreateClosedMigration,
|
||||||
StabilityDaysMigration,
|
StabilityDaysMigration,
|
||||||
|
FetchReleaseNotesMigration,
|
||||||
];
|
];
|
||||||
|
|
||||||
static run(originalConfig: RenovateConfig): RenovateConfig {
|
static run(originalConfig: RenovateConfig): RenovateConfig {
|
||||||
|
|
|
@ -2567,9 +2567,10 @@ const options: RenovateOptions[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'fetchReleaseNotes',
|
name: 'fetchReleaseNotes',
|
||||||
description: 'Controls if release notes are fetched.',
|
description: 'Controls if and when release notes are fetched.',
|
||||||
type: 'boolean',
|
type: 'string',
|
||||||
default: true,
|
allowedValues: ['off', 'branch', 'pr'],
|
||||||
|
default: 'pr',
|
||||||
cli: false,
|
cli: false,
|
||||||
env: false,
|
env: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -261,7 +261,7 @@ export interface RenovateConfig
|
||||||
vulnerabilitySeverity?: string;
|
vulnerabilitySeverity?: string;
|
||||||
regexManagers?: RegExManager[];
|
regexManagers?: RegExManager[];
|
||||||
|
|
||||||
fetchReleaseNotes?: boolean;
|
fetchReleaseNotes?: FetchReleaseNotesOptions;
|
||||||
secrets?: Record<string, string>;
|
secrets?: Record<string, string>;
|
||||||
|
|
||||||
constraints?: Record<string, string>;
|
constraints?: Record<string, string>;
|
||||||
|
@ -302,6 +302,8 @@ export type UpdateType =
|
||||||
| 'bump'
|
| 'bump'
|
||||||
| 'replacement';
|
| 'replacement';
|
||||||
|
|
||||||
|
export type FetchReleaseNotesOptions = 'off' | 'branch' | 'pr';
|
||||||
|
|
||||||
export type MatchStringsStrategy = 'any' | 'recursive' | 'combination';
|
export type MatchStringsStrategy = 'any' | 'recursive' | 'combination';
|
||||||
|
|
||||||
export type MergeStrategy =
|
export type MergeStrategy =
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { mockedFunction, partial } from '../../../../test/util';
|
import { mockedFunction, partial } from '../../../../test/util';
|
||||||
import type { BranchUpgradeConfig } from '../../types';
|
import type { BranchUpgradeConfig } from '../../types';
|
||||||
import { getChangeLogJSON } from '../update/pr/changelog';
|
import { getChangeLogJSON } from '../update/pr/changelog';
|
||||||
import { embedChangelogs, needsChangelogs } from '.';
|
import { embedChangelogs } from '.';
|
||||||
|
|
||||||
jest.mock('../update/pr/changelog');
|
jest.mock('../update/pr/changelog');
|
||||||
|
|
||||||
|
@ -27,23 +27,4 @@ describe('workers/repository/changelog/index', () => {
|
||||||
{ logJSON: null },
|
{ 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();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import * as p from '../../../util/promises';
|
import * as p from '../../../util/promises';
|
||||||
import {
|
|
||||||
containsTemplates,
|
|
||||||
exposedConfigOptions,
|
|
||||||
} from '../../../util/template';
|
|
||||||
import type { BranchUpgradeConfig } from '../../types';
|
import type { BranchUpgradeConfig } from '../../types';
|
||||||
import { getChangeLogJSON } from '../update/pr/changelog';
|
import { getChangeLogJSON } from '../update/pr/changelog';
|
||||||
|
|
||||||
|
@ -21,17 +17,3 @@ export async function embedChangelogs(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await p.map(branches, embedChangelog, { concurrency: 10 });
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import {
|
||||||
fs,
|
fs,
|
||||||
git,
|
git,
|
||||||
mocked,
|
mocked,
|
||||||
mockedFunction,
|
|
||||||
partial,
|
partial,
|
||||||
platform,
|
platform,
|
||||||
scm,
|
scm,
|
||||||
|
@ -34,7 +33,6 @@ import * as _mergeConfidence from '../../../../util/merge-confidence';
|
||||||
import * as _sanitize from '../../../../util/sanitize';
|
import * as _sanitize from '../../../../util/sanitize';
|
||||||
import * as _limits from '../../../global/limits';
|
import * as _limits from '../../../global/limits';
|
||||||
import type { BranchConfig, BranchUpgradeConfig } from '../../../types';
|
import type { BranchConfig, BranchUpgradeConfig } from '../../../types';
|
||||||
import { needsChangelogs } from '../../changelog';
|
|
||||||
import type { ResultWithPr } from '../pr';
|
import type { ResultWithPr } from '../pr';
|
||||||
import * as _prWorker from '../pr';
|
import * as _prWorker from '../pr';
|
||||||
import * as _prAutomerge from '../pr/automerge';
|
import * as _prAutomerge from '../pr/automerge';
|
||||||
|
@ -816,9 +814,8 @@ describe('workers/repository/update/branch/index', () => {
|
||||||
ignoreTests: true,
|
ignoreTests: true,
|
||||||
prCreation: 'not-pending',
|
prCreation: 'not-pending',
|
||||||
commitBody: '[skip-ci]',
|
commitBody: '[skip-ci]',
|
||||||
fetchReleaseNotes: true,
|
fetchReleaseNotes: 'branch',
|
||||||
} satisfies BranchConfig;
|
} satisfies BranchConfig;
|
||||||
mockedFunction(needsChangelogs).mockReturnValueOnce(true);
|
|
||||||
scm.getBranchCommit.mockResolvedValue('123test'); //TODO:not needed?
|
scm.getBranchCommit.mockResolvedValue('123test'); //TODO:not needed?
|
||||||
expect(await branchWorker.processBranch(inconfig)).toEqual({
|
expect(await branchWorker.processBranch(inconfig)).toEqual({
|
||||||
branchExists: true,
|
branchExists: true,
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { toMs } from '../../../../util/pretty-time';
|
||||||
import * as template from '../../../../util/template';
|
import * as template from '../../../../util/template';
|
||||||
import { isLimitReached } from '../../../global/limits';
|
import { isLimitReached } from '../../../global/limits';
|
||||||
import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types';
|
import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types';
|
||||||
import { embedChangelog, needsChangelogs } from '../../changelog';
|
import { embedChangelogs } from '../../changelog';
|
||||||
import { ensurePr } from '../pr';
|
import { ensurePr } from '../pr';
|
||||||
import { checkAutoMerge } from '../pr/automerge';
|
import { checkAutoMerge } from '../pr/automerge';
|
||||||
import { setArtifactErrorStatus } from './artifacts';
|
import { setArtifactErrorStatus } from './artifacts';
|
||||||
|
@ -482,6 +482,10 @@ export async function processBranch(
|
||||||
} else {
|
} else {
|
||||||
logger.debug('No updated lock files in branch');
|
logger.debug('No updated lock files in branch');
|
||||||
}
|
}
|
||||||
|
if (config.fetchReleaseNotes === 'branch') {
|
||||||
|
await embedChangelogs(config.upgrades);
|
||||||
|
}
|
||||||
|
|
||||||
const postUpgradeCommandResults = await executePostUpgradeCommands(
|
const postUpgradeCommandResults = await executePostUpgradeCommands(
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
|
@ -540,14 +544,6 @@ export async function processBranch(
|
||||||
|
|
||||||
// compile commit message with body, which maybe needs changelogs
|
// compile commit message with body, which maybe needs changelogs
|
||||||
if (config.commitBody) {
|
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
|
// changelog is on first upgrade
|
||||||
config.commitMessage = `${config.commitMessage!}\n\n${template.compile(
|
config.commitMessage = `${config.commitMessage!}\n\n${template.compile(
|
||||||
config.commitBody,
|
config.commitBody,
|
||||||
|
|
|
@ -102,7 +102,7 @@ describe('workers/repository/update/pr/index', () => {
|
||||||
platform.createPr.mockResolvedValueOnce(pr);
|
platform.createPr.mockResolvedValueOnce(pr);
|
||||||
limits.isLimitReached.mockReturnValueOnce(true);
|
limits.isLimitReached.mockReturnValueOnce(true);
|
||||||
|
|
||||||
config.fetchReleaseNotes = true;
|
config.fetchReleaseNotes = 'pr';
|
||||||
|
|
||||||
const res = await ensurePr(config);
|
const res = await ensurePr(config);
|
||||||
|
|
||||||
|
@ -871,13 +871,13 @@ describe('workers/repository/update/pr/index', () => {
|
||||||
bodyFingerprint: fingerprint(
|
bodyFingerprint: fingerprint(
|
||||||
generatePrBodyFingerprintConfig({
|
generatePrBodyFingerprintConfig({
|
||||||
...config,
|
...config,
|
||||||
fetchReleaseNotes: true,
|
fetchReleaseNotes: 'pr',
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
|
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
|
||||||
};
|
};
|
||||||
prCache.getPrCache.mockReturnValueOnce(cachedPr);
|
prCache.getPrCache.mockReturnValueOnce(cachedPr);
|
||||||
const res = await ensurePr({ ...config, fetchReleaseNotes: true });
|
const res = await ensurePr({ ...config, fetchReleaseNotes: 'pr' });
|
||||||
expect(res).toEqual({
|
expect(res).toEqual({
|
||||||
type: 'with-pr',
|
type: 'with-pr',
|
||||||
pr: existingPr,
|
pr: existingPr,
|
||||||
|
@ -904,13 +904,13 @@ describe('workers/repository/update/pr/index', () => {
|
||||||
bodyFingerprint: fingerprint(
|
bodyFingerprint: fingerprint(
|
||||||
generatePrBodyFingerprintConfig({
|
generatePrBodyFingerprintConfig({
|
||||||
...config,
|
...config,
|
||||||
fetchReleaseNotes: true,
|
fetchReleaseNotes: 'pr',
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
|
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
|
||||||
};
|
};
|
||||||
prCache.getPrCache.mockReturnValueOnce(cachedPr);
|
prCache.getPrCache.mockReturnValueOnce(cachedPr);
|
||||||
const res = await ensurePr({ ...config, fetchReleaseNotes: true });
|
const res = await ensurePr({ ...config, fetchReleaseNotes: 'pr' });
|
||||||
expect(res).toEqual({
|
expect(res).toEqual({
|
||||||
type: 'with-pr',
|
type: 'with-pr',
|
||||||
pr: {
|
pr: {
|
||||||
|
|
|
@ -234,7 +234,7 @@ export async function ensurePr(
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.fetchReleaseNotes) {
|
if (config.fetchReleaseNotes === 'pr') {
|
||||||
// fetch changelogs when not already done;
|
// fetch changelogs when not already done;
|
||||||
await embedChangelogs(upgrades);
|
await embedChangelogs(upgrades);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { RenovateConfig, mocked, mockedFunction } from '../../../../test/util';
|
import { RenovateConfig, mocked } from '../../../../test/util';
|
||||||
import { getConfig } from '../../../config/defaults';
|
import { getConfig } from '../../../config/defaults';
|
||||||
import * as _changelog from '../changelog';
|
import * as _changelog from '../changelog';
|
||||||
import { branchifyUpgrades } from './branchify';
|
import { branchifyUpgrades } from './branchify';
|
||||||
|
@ -124,7 +124,7 @@ describe('workers/repository/updates/branchify', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('no fetch changelogs', async () => {
|
it('no fetch changelogs', async () => {
|
||||||
config.fetchReleaseNotes = false;
|
config.fetchReleaseNotes = 'off';
|
||||||
flattenUpdates.mockResolvedValueOnce([
|
flattenUpdates.mockResolvedValueOnce([
|
||||||
{
|
{
|
||||||
depName: 'foo',
|
depName: 'foo',
|
||||||
|
@ -153,38 +153,5 @@ describe('workers/repository/updates/branchify', () => {
|
||||||
expect(embedChangelogs).not.toHaveBeenCalled();
|
expect(embedChangelogs).not.toHaveBeenCalled();
|
||||||
expect(Object.keys(res.branches)).toHaveLength(2);
|
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { Merge } from 'type-fest';
|
||||||
import type { RenovateConfig, ValidationMessage } from '../../../config/types';
|
import type { RenovateConfig, ValidationMessage } from '../../../config/types';
|
||||||
import { addMeta, logger, removeMeta } from '../../../logger';
|
import { addMeta, logger, removeMeta } from '../../../logger';
|
||||||
import type { BranchConfig, BranchUpgradeConfig } from '../../types';
|
import type { BranchConfig, BranchUpgradeConfig } from '../../types';
|
||||||
import { embedChangelogs, needsChangelogs } from '../changelog';
|
|
||||||
import { flattenUpdates } from './flatten';
|
import { flattenUpdates } from './flatten';
|
||||||
import { generateBranchConfig } from './generate';
|
import { generateBranchConfig } from './generate';
|
||||||
|
|
||||||
|
@ -72,22 +71,6 @@ export async function branchifyUpgrades(
|
||||||
})
|
})
|
||||||
.reverse();
|
.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]);
|
const branch = generateBranchConfig(branchUpgrades[branchName]);
|
||||||
branch.branchName = branchName;
|
branch.branchName = branchName;
|
||||||
branch.packageFiles = packageFiles;
|
branch.packageFiles = packageFiles;
|
||||||
|
|
Loading…
Reference in a new issue