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
51 changes: 50 additions & 1 deletion .github/workflows/release-automation-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,7 @@ jobs:
id: changelog
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const snapshotBranch = '${{ needs.derive-state.outputs.snapshot_branch }}';
const releaseTag = '${{ needs.derive-state.outputs.release_tag }}';
Expand All @@ -1602,6 +1603,7 @@ jobs:

let changelogContent = '';
let changelogPath = '';
let changelogSha = '';
for (const path of candidates) {
try {
const response = await github.rest.repos.getContent({
Expand All @@ -1612,6 +1614,7 @@ jobs:
});
changelogContent = Buffer.from(response.data.content, 'base64').toString('utf-8');
changelogPath = path;
changelogSha = response.data.sha;
console.log(`Fetched ${path} (${changelogContent.length} chars)`);
break;
} catch (error) {
Expand All @@ -1622,9 +1625,54 @@ jobs:
if (!changelogContent) {
console.log('::warning::No CHANGELOG file found on snapshot branch');
core.setOutput('release_notes', '');
core.setOutput('candidate_block_stripped', '');
return;
}

// --- Strip AUTOGENERATED:CANDIDATE_CHANGES block if present ---
const START_MARKER = '<!-- BEGIN:AUTOGENERATED:CANDIDATE_CHANGES -->';
const END_MARKER = '<!-- END:AUTOGENERATED:CANDIDATE_CHANGES -->';

const startPos = changelogContent.indexOf(START_MARKER);
const endPos = changelogContent.indexOf(END_MARKER);
let candidateBlockStripped = false;

if (startPos !== -1 && endPos !== -1 && endPos > startPos) {
// Both markers present — strip the entire block (inclusive)
changelogContent = (
changelogContent.substring(0, startPos) +
changelogContent.substring(endPos + END_MARKER.length)
).replace(/\n{3,}/g, '\n\n');
candidateBlockStripped = true;
console.log('Stripped full candidate changes block');
} else if (startPos !== -1 || endPos !== -1) {
// Orphan marker — codeowner partially deleted the block.
// Strip just the orphan marker line (HTML comment, always safe to remove).
const orphanMarker = startPos !== -1 ? START_MARKER : END_MARKER;
changelogContent = changelogContent
.split('\n')
.filter(line => line.trim() !== orphanMarker)
.join('\n');
candidateBlockStripped = true;
console.log(`Stripped orphan marker: ${orphanMarker}`);
}
// If neither marker found: codeowner already removed the block — no action needed

if (candidateBlockStripped) {
await github.rest.repos.createOrUpdateFileContents({
owner: context.repo.owner,
repo: context.repo.repo,
path: changelogPath,
message: 'chore: auto-remove candidate changes block from CHANGELOG',
content: Buffer.from(changelogContent).toString('base64'),
sha: changelogSha,
branch: snapshotBranch
});
console.log(`Committed cleaned CHANGELOG to ${snapshotBranch}`);
}

core.setOutput('candidate_block_stripped', candidateBlockStripped ? 'true' : '');

// Extract section from "## Release Notes" to next "# rX.Y" heading or EOF
const lines = changelogContent.split('\n');
let startIdx = -1;
Expand Down Expand Up @@ -1749,7 +1797,8 @@ jobs:
{
"draft_release_url": "${{ steps.create-draft.outputs.draft_release_url }}",
"release_pr_number": "${{ github.event.pull_request.number }}",
"release_pr_url": "${{ github.event.pull_request.html_url }}"
"release_pr_url": "${{ github.event.pull_request.html_url }}",
"candidate_block_stripped": "${{ steps.changelog.outputs.candidate_block_stripped }}"
}

# ─────────────────────────────────────────────────────────────────────────────
Expand Down
2 changes: 2 additions & 0 deletions release_automation/scripts/bot_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class BotContext:
# Display fields
workflow_run_url: str = ""
draft_release_url: str = ""
candidate_block_stripped: str = ""
reason: str = ""

# Publication fields
Expand Down Expand Up @@ -175,6 +176,7 @@ def to_dict(self) -> Dict[str, Any]:
# Display fields
"workflow_run_url": self.workflow_run_url,
"draft_release_url": self.draft_release_url,
"candidate_block_stripped": self.candidate_block_stripped,
"reason": self.reason,
# Publication fields
"release_url": self.release_url,
Expand Down
3 changes: 3 additions & 0 deletions release_automation/templates/bot_messages/draft_created.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
**📦 Draft release created — State: `draft-ready`**
Triggered by merge of [Release PR #{{release_pr_number}}]({{release_pr_url}}).
**Draft release:** [`{{release_tag}}`]({{draft_release_url}})
{{#candidate_block_stripped}}
Release notes cleaned (candidate block removed).
{{/candidate_block_stripped}}

<details><summary><b>Configuration:</b> Release {{release_tag}}{{#short_type}} ({{short_type}}{{#has_meta_release}}, {{meta_release}}{{/has_meta_release}}){{/short_type}}</summary>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ The API definition(s) are based on

{{#candidate_changes}}
<!-- BEGIN:AUTOGENERATED:CANDIDATE_CHANGES -->
> **Working area — candidate changes (must be removed before merge)**
> Use these entries to fill the Added/Changed/Fixed/Removed sections below. **Delete this block before merge.**
> **Working area — candidate changes (auto-removed on merge)**
> Copy relevant entries into the Added/Changed/Fixed/Removed sections below.
> You may edit this list while triaging; it will be removed on merge.
> This working-area section is removed automatically when the PR is merged.

<details>
<summary>Candidate changes (auto-generated from merged PRs)</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ Codeowners update and review this PR. After codeowner and release management app
### Codeowner Review

**Update this PR:**
- [ ] Move CHANGELOG candidate changes into the correct categories (Added/Changed/Fixed/Removed) and complete them
- [ ] Remove the autogenerated candidate changes block from the CHANGELOG
- [ ] All relevant changes copied into Added / Changed / Fixed / Removed (per API as needed)

**Confirm readiness:**
- [ ] API version numbers match the intent declared in `release-plan.yaml`
Expand Down
4 changes: 2 additions & 2 deletions release_automation/tests/test_bot_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def test_to_dict_returns_all_keys(self):
"trigger_workflow_dispatch", "trigger_issue_close",
"trigger_release_plan_change",
"has_meta_release", "has_reason",
"workflow_run_url", "draft_release_url", "reason",
"workflow_run_url", "draft_release_url", "candidate_block_stripped", "reason",
# Publication fields
"release_url", "reference_tag", "reference_tag_url",
"sync_pr_number", "sync_pr_url",
Expand Down Expand Up @@ -251,7 +251,7 @@ def test_returns_complete_dict(self):
"trigger_workflow_dispatch", "trigger_issue_close",
"trigger_release_plan_change",
"has_meta_release", "has_reason",
"workflow_run_url", "draft_release_url", "reason",
"workflow_run_url", "draft_release_url", "candidate_block_stripped", "reason",
# Publication fields
"release_url", "reference_tag", "reference_tag_url",
"sync_pr_number", "sync_pr_url",
Expand Down
2 changes: 1 addition & 1 deletion release_automation/tests/test_changelog_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def test_generate_draft_with_candidate_changes(
assert CANDIDATE_CHANGES_START_MARKER in result
assert CANDIDATE_CHANGES_END_MARKER in result
# Instruction text visible (not collapsed)
assert "must be removed before merge" in result
assert "auto-removed on merge" in result
# Full Changelog link at end, outside markers
assert "compare/r3.2...r4.1" in result
end_marker_pos = result.index(CANDIDATE_CHANGES_END_MARKER)
Expand Down
4 changes: 2 additions & 2 deletions release_automation/tests/test_template_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_render_release_review_pr_rc_template(self):
assert "**Verify snapshot content (during automation introduction phase only):**" in result
assert "**Update this PR:**" in result
assert "**Confirm readiness:**" in result
assert "Move CHANGELOG candidate changes" in result
assert "All relevant changes copied into Added" in result
assert "declared Commonalities version" in result
assert "Commonalities r3.4" in result
assert "mandatory release assets for the APIs are present per the API status and confirmed" in result
Expand Down Expand Up @@ -64,7 +64,7 @@ def test_render_release_review_pr_alpha_template(self):
assert "| NumberVerification | `v0.3.0-alpha.1` | alpha |" in result
assert "API definitions are consistent with the declared API version" in result
assert "API documentation (`info.description`) is up to date" in result
assert "Move CHANGELOG candidate changes" in result
assert "All relevant changes copied into Added" in result
# Alpha should NOT have rc/public-specific items
assert "Enhanced test cases" not in result

Expand Down