fix(release): changelog renderer should prefer breaking change explanation text (#20798)

This commit is contained in:
James Henry 2023-12-17 22:48:31 +04:00 committed by GitHub
parent 71ce32a121
commit 32baa4dab1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 175 additions and 5 deletions

View File

@ -120,6 +120,7 @@ describe('nx release', () => {
dependencyRelationshipLogMatch.length !== 1 dependencyRelationshipLogMatch.length !== 1
) { ) {
// From JamesHenry: explicit error to assist troubleshooting NXC-143 // From JamesHenry: explicit error to assist troubleshooting NXC-143
// Update: after seeing this error in the wild, it somehow seems to be not finding the dependency relationship sometimes
throw new Error( throw new Error(
` `
Error: Expected to find exactly one dependency relationship log line. Error: Expected to find exactly one dependency relationship log line.
@ -128,7 +129,10 @@ If you are seeing this message then you have been impacted by some currently und
Please report the full nx release version command output below to the Nx team: Please report the full nx release version command output below to the Nx team:
${versionOutput}` ${{
versionOutput,
pkg2Contents: readFile(`${pkg2}/package.json`),
}}`
); );
} }
expect(dependencyRelationshipLogMatch.length).toEqual(1); expect(dependencyRelationshipLogMatch.length).toEqual(1);

View File

@ -539,4 +539,125 @@ describe('defaultChangelogRenderer()', () => {
expect(markdown).toMatchInlineSnapshot(`""`); expect(markdown).toMatchInlineSnapshot(`""`);
}); });
}); });
describe('breaking changes', () => {
it('should work for breaking changes with just the ! and no explanation', async () => {
const breakingChangeCommitWithExplanation: GitCommit = {
// ! after the type, no BREAKING CHANGE: in the body
message: 'feat(WebSocketSubject)!: no longer extends `Subject`.',
shortHash: '54f2f6ed1',
author: {
name: 'James Henry',
email: 'jh@example.com',
},
body:
'M\tpackages/rxjs/src/internal/observable/dom/WebSocketSubject.ts\n' +
'"',
authors: [
{
name: 'James Henry',
email: 'jh@example.com',
},
],
description: 'no longer extends `Subject`.',
type: 'feat',
scope: 'WebSocketSubject',
references: [{ value: '54f2f6ed1', type: 'hash' }],
isBreaking: true,
revertedHashes: [],
affectedFiles: [
'packages/rxjs/src/internal/observable/dom/WebSocketSubject.ts',
],
};
const markdown = await defaultChangelogRenderer({
projectGraph,
commits: [breakingChangeCommitWithExplanation],
releaseVersion: 'v1.1.0',
project: null,
entryWhenNoChanges: false,
changelogRenderOptions: {
includeAuthors: true,
},
});
expect(markdown).toMatchInlineSnapshot(`
"## v1.1.0
### 🚀 Features
- **WebSocketSubject:** no longer extends \`Subject\`.
#### Breaking Changes
- **WebSocketSubject:** no longer extends \`Subject\`.
### Thank You
- James Henry"
`);
});
it('should extract the explanation of a breaking change and render it preferentially', async () => {
const breakingChangeCommitWithExplanation: GitCommit = {
// No ! after the type, but BREAKING CHANGE: in the body
message: 'feat(WebSocketSubject): no longer extends `Subject`.',
shortHash: '54f2f6ed1',
author: {
name: 'James Henry',
email: 'jh@example.com',
},
body:
'BREAKING CHANGE: `WebSocketSubject` is no longer `instanceof Subject`. Check for `instanceof WebSocketSubject` instead.\n' +
'"\n' +
'\n' +
'M\tpackages/rxjs/src/internal/observable/dom/WebSocketSubject.ts\n' +
'"',
authors: [
{
name: 'James Henry',
email: 'jh@example.com',
},
],
description: 'no longer extends `Subject`.',
type: 'feat',
scope: 'WebSocketSubject',
references: [{ value: '54f2f6ed1', type: 'hash' }],
isBreaking: true,
revertedHashes: [],
affectedFiles: [
'packages/rxjs/src/internal/observable/dom/WebSocketSubject.ts',
],
};
const markdown = await defaultChangelogRenderer({
projectGraph,
commits: [breakingChangeCommitWithExplanation],
releaseVersion: 'v1.1.0',
project: null,
entryWhenNoChanges: false,
changelogRenderOptions: {
includeAuthors: true,
},
});
expect(markdown).toMatchInlineSnapshot(`
"## v1.1.0
### 🚀 Features
- **WebSocketSubject:** no longer extends \`Subject\`.
#### Breaking Changes
- **WebSocketSubject:** \`WebSocketSubject\` is no longer \`instanceof Subject\`. Check for \`instanceof WebSocketSubject\` instead.
### Thank You
- James Henry"
`);
});
});
}); });

View File

@ -143,7 +143,16 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
const line = formatCommit(commit, repoSlug); const line = formatCommit(commit, repoSlug);
markdownLines.push(line); markdownLines.push(line);
if (commit.isBreaking) { if (commit.isBreaking) {
breakingChanges.push(line); const breakingChangeExplanation = extractBreakingChangeExplanation(
commit.body
);
breakingChanges.push(
breakingChangeExplanation
? `- ${
commit.scope ? `**${commit.scope.trim()}:** ` : ''
}${breakingChangeExplanation}`
: line
);
} }
} }
} }
@ -188,7 +197,16 @@ const defaultChangelogRenderer: ChangelogRenderer = async ({
const line = formatCommit(commit, repoSlug); const line = formatCommit(commit, repoSlug);
markdownLines.push(line + '\n'); markdownLines.push(line + '\n');
if (commit.isBreaking) { if (commit.isBreaking) {
breakingChanges.push(line); const breakingChangeExplanation = extractBreakingChangeExplanation(
commit.body
);
breakingChanges.push(
breakingChangeExplanation
? `- ${
commit.scope ? `**${commit.scope.trim()}:** ` : ''
}${breakingChangeExplanation}`
: line
);
} }
} }
} }
@ -294,11 +312,37 @@ function groupBy(items: any[], key: string) {
function formatCommit(commit: GitCommit, repoSlug?: RepoSlug): string { function formatCommit(commit: GitCommit, repoSlug?: RepoSlug): string {
let commitLine = let commitLine =
'- ' + '- ' +
(commit.scope ? `**${commit.scope.trim()}:** ` : '') +
(commit.isBreaking ? '⚠️ ' : '') + (commit.isBreaking ? '⚠️ ' : '') +
(commit.scope ? `**${commit.scope.trim()}:** ` : '') +
commit.description; commit.description;
if (repoSlug) { if (repoSlug) {
commitLine += formatReferences(commit.references, repoSlug); commitLine += formatReferences(commit.references, repoSlug);
} }
return commitLine; return commitLine;
} }
/**
* It is common to add further information about a breaking change in the commit body,
* and it is naturally that information that should be included in the BREAKING CHANGES
* section of changelog, rather than repeating the commit title/description.
*/
function extractBreakingChangeExplanation(message: string): string | null {
const breakingChangeIdentifier = 'BREAKING CHANGE:';
const startIndex = message.indexOf(breakingChangeIdentifier);
if (startIndex === -1) {
// "BREAKING CHANGE:" not found in the message
return null;
}
const startOfBreakingChange = startIndex + breakingChangeIdentifier.length;
const endOfBreakingChange = message.indexOf('\n', startOfBreakingChange);
if (endOfBreakingChange === -1) {
// No newline character found, extract till the end of the message
return message.substring(startOfBreakingChange).trim();
}
// Extract and return the breaking change message
return message.substring(startOfBreakingChange, endOfBreakingChange).trim();
}

View File

@ -285,7 +285,8 @@ export function parseGitCommit(commit: RawGitCommit): GitCommit | null {
const scope = match.groups.scope || ''; const scope = match.groups.scope || '';
const isBreaking = Boolean(match.groups.breaking); const isBreaking =
Boolean(match.groups.breaking) || commit.body.includes('BREAKING CHANGE:');
let description = match.groups.description; let description = match.groups.description;
// Extract references from message // Extract references from message