Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/util/pull-request-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,17 @@ function extractMultipleReleases(notes: string, logger: Logger): ReleaseData[] {
const root = parse(notes);
for (const detail of root.getElementsByTagName('details')) {
const summaryNode = detail.getElementsByTagName('summary')[0];
const summary = summaryNode?.textContent;
if (!summaryNode) {
// A commit subject can legitimately contain a token like
// `<details>` inside an inline code span. The HTML parser does not
// understand markdown, so it sees that text as a real element.
// Skip it and let the caller fall back to single-release parsing.
logger.warn(
'found a <details> block without a <summary>, skipping it - the release notes may contain raw HTML from commit subjects'
);
continue;
}
const summary = summaryNode.textContent;
const match = summary.match(SUMMARY_PATTERN);
if (match?.groups) {
detail.removeChild(summaryNode);
Expand Down
44 changes: 44 additions & 0 deletions test/util/pull-request-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,50 @@ describe('PullRequestBody', () => {
expect(releaseData[0].version?.toString()).to.eql('0.1.0');
expect(releaseData[0].notes).matches(/initial generation/);
});

// A commit subject can contain a token like `<details>` inside an
// inline code span. The HTML parser does not understand markdown, so
// the release notes end up containing what looks like a real
// `<details>` element without a `<summary>`.
// https://github.com/googleapis/release-please/issues/2801
it('should tolerate a <details> element missing a <summary>', () => {
const body = [
':robot: I have created a release *beep* *boop*',
'---',
'',
'',
'## [1.2.3](https://github.com/googleapis/release-please/compare/v1.2.2...v1.2.3) (2026-08-22)',
'',
'',
'### Bug Fixes',
'',
'* escape an unbalanced `<details>` tag instead of refusing the draft ([#133](https://github.com/googleapis/release-please/issues/133))',
'',
'---',
'This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).',
].join('\n');
const pullRequestBody = PullRequestBody.parse(body);
expect(pullRequestBody).to.not.be.undefined;
const releaseData = pullRequestBody!.releaseData;
expect(releaseData).lengthOf(1);
expect(releaseData[0].version?.toString()).to.eql('1.2.3');
expect(releaseData[0].notes).matches(/unbalanced/);
});

it('should keep parsing valid components around a <details> element missing a <summary>', () => {
const body = readFileSync(
resolve(fixturesPath, './multiple.txt'),
'utf8'
);
// Inject a stray, summary-less <details> block before the valid ones.
const poisoned = body.replace(
'<details>',
'<details>\nchore: mention `</details>` handling\n</details>\n<details>'
);
const pullRequestBody = PullRequestBody.parse(poisoned);
expect(pullRequestBody).to.not.be.undefined;
expect(pullRequestBody!.releaseData.length).to.be.greaterThan(0);
});
});
describe('toString', () => {
it('can handle multiple entries', () => {
Expand Down
Loading