Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/utils/__tests__/changelog-generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,36 @@ describe('generateChangelogWithHighlight', () => {
// Bump type should be 'patch' (this PR's contribution), NOT 'minor' (from aggregated)
expect(result.bumpType).toBe('patch');
});

it('uses bold formatting instead of @ mentions to avoid pinging', async () => {
setup({
currentPR: {
number: 2,
title: 'feat: new feature',
body: '',
author: 'bob',
},
existingCommits: [
{
hash: 'abc123',
title: 'fix: bug fix',
body: '',
pr: {
local: '1',
remote: { number: '1', author: { login: 'alice' } },
},
},
],
});

const result = await generateChangelogWithHighlight(dummyGit, '1.0.0', 2);

// Authors should use bold formatting, NOT @ mentions
expect(result.changelog).toContain('by **bob**');
expect(result.changelog).toContain('by **alice**');
expect(result.changelog).not.toContain('@bob');
expect(result.changelog).not.toContain('@alice');
});
});

describe('skip-changelog handling', () => {
Expand Down
71 changes: 45 additions & 26 deletions src/utils/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1201,8 +1201,12 @@ export function stripTitle(
* Formats a single changelog entry with consistent full markdown link format.
* Format: `- Title by @author in [#123](pr-url)` or `- Title in [abcdef12](commit-url)`
* When highlight is true, the entry is prefixed with `> ` (blockquote).
* When disableMentions is true, uses `**author**` instead of `@author` to avoid pinging.
*/
function formatChangelogEntry(entry: ChangelogEntry): string {
function formatChangelogEntry(
entry: ChangelogEntry,
disableMentions = false
): string {
let title = entry.title;

// Strip PR number suffix like "(#123)" since we add the link separately
Expand All @@ -1216,20 +1220,27 @@ function formatChangelogEntry(entry: ChangelogEntry): string {

let text = `- ${title}`;

// Format author reference: use bold instead of @ mention when disableMentions is true
const authorRef = entry.author
? disableMentions
? `**${entry.author}**`
: `@${entry.author}`
: null;

if (entry.prNumber) {
// Full markdown link format for PRs
const prLink = `${entry.repoUrl}/pull/${entry.prNumber}`;
if (entry.author) {
text += ` by @${entry.author} in [#${entry.prNumber}](${prLink})`;
if (authorRef) {
text += ` by ${authorRef} in [#${entry.prNumber}](${prLink})`;
} else {
text += ` in [#${entry.prNumber}](${prLink})`;
}
} else {
// Commits without PRs: link to commit
const shortHash = entry.hash.slice(0, SHORT_SHA_LENGTH);
const commitLink = `${entry.repoUrl}/commit/${entry.hash}`;
if (entry.author) {
text += ` by @${entry.author} in [${shortHash}](${commitLink})`;
if (authorRef) {
text += ` by ${authorRef} in [${shortHash}](${commitLink})`;
} else {
text += ` in [${shortHash}](${commitLink})`;
}
Expand Down Expand Up @@ -1422,8 +1433,8 @@ export async function generateChangelogWithHighlight(
// Step 9: Run categorization on filtered list
const { data: rawData, stats } = categorizeCommits(filteredCommits);

// Step 10: Serialize to markdown
const changelog = await serializeChangelog(rawData, MAX_LEFTOVERS);
// Step 10: Serialize to markdown (disable mentions to avoid pinging in PR preview comments)
const changelog = await serializeChangelog(rawData, MAX_LEFTOVERS, true);

// Determine bump type:
// - If current PR survived revert processing, use its specific bump type (PR 676 fix)
Expand Down Expand Up @@ -1637,11 +1648,13 @@ async function generateRawChangelog(
*
* @param rawData The raw changelog data to serialize
* @param maxLeftovers Maximum number of leftover entries to include
* @param disableMentions When true, uses bold formatting instead of @ mentions for authors
* @returns Formatted markdown changelog string
*/
async function serializeChangelog(
rawData: RawChangelogData,
maxLeftovers: number
maxLeftovers: number,
disableMentions = false
): Promise<string> {
const { categories, leftovers, releaseConfig } = rawData;

Expand Down Expand Up @@ -1717,15 +1730,18 @@ async function serializeChangelog(
const showsScopeHeader = scopeHeader !== null && scope !== null;

const prEntries = prs.map(pr =>
formatChangelogEntry({
title: stripTitle(pr.title, pr.matchedPattern, !showsScopeHeader),
author: pr.author,
prNumber: pr.number,
hash: pr.hash,
body: pr.body,
repoUrl,
highlight: pr.highlight,
})
formatChangelogEntry(
{
title: stripTitle(pr.title, pr.matchedPattern, !showsScopeHeader),
author: pr.author,
prNumber: pr.number,
hash: pr.hash,
body: pr.body,
repoUrl,
highlight: pr.highlight,
},
disableMentions
)
);

if (scopeHeader) {
Expand Down Expand Up @@ -1770,15 +1786,18 @@ async function serializeChangelog(

for (const pr of prEntries) {
leftoverEntries.push(
formatChangelogEntry({
title: pr.title,
author: pr.author,
prNumber: pr.number || undefined,
hash: pr.hash,
repoUrl,
body: pr.body || undefined,
highlight: pr.highlight,
})
formatChangelogEntry(
{
title: pr.title,
author: pr.author,
prNumber: pr.number || undefined,
hash: pr.hash,
repoUrl,
body: pr.body || undefined,
highlight: pr.highlight,
},
disableMentions
)
);
}
}
Expand Down
Loading