Skip to content

Fix release proposal workflow and add post-release branch sync #1

Fix release proposal workflow and add post-release branch sync

Fix release proposal workflow and add post-release branch sync #1

name: Create Release Tags
on:
pull_request:
types: [closed]
branches:
- main
- develop
permissions:
contents: write
jobs:
create-tags:
# Only run if PR was merged and has release labels
if: |
github.event.pull_request.merged == true &&
(contains(github.event.pull_request.labels.*.name, 'release:setuptools-scm') ||
contains(github.event.pull_request.labels.*.name, 'release:vcs-versioning'))
runs-on: ubuntu-latest
steps:
- name: Create tags and releases
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
const prTitle = pr.title;
const mergeCommitSha = pr.merge_commit_sha;
const labels = pr.labels.map(l => l.name);
console.log(`Processing PR #${pr.number}: ${prTitle}`);
console.log(`Merge commit: ${mergeCommitSha}`);
console.log(`Labels: ${labels.join(', ')}`);
const tagsCreated = [];
// Helper to extract version from PR title
function extractVersion(title, packageName) {
const regex = new RegExp(`${packageName} v(\\d+\\.\\d+\\.\\d+)`);
const match = title.match(regex);
return match ? match[1] : null;
}
// Helper to extract changelog section for a version
async function extractChangelog(packageDir, version) {
try {
const { data: file } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: `${packageDir}/CHANGELOG.md`,
ref: mergeCommitSha
});
const content = Buffer.from(file.content, 'base64').toString('utf-8');
const lines = content.split('\n');
let inSection = false;
let changelog = [];
for (const line of lines) {
if (line.startsWith(`## ${version}`)) {
inSection = true;
continue; // Skip the header line
}
if (inSection && line.match(/^## \d/)) {
break; // Next version section
}
if (inSection) {
changelog.push(line);
}
}
// Trim leading/trailing empty lines
while (changelog.length > 0 && changelog[0].trim() === '') {
changelog.shift();
}
while (changelog.length > 0 && changelog[changelog.length - 1].trim() === '') {
changelog.pop();
}
return changelog.join('\n');
} catch (error) {
console.log(`Could not extract changelog: ${error.message}`);
return `Release ${version}`;
}
}
// Helper to create tag and release
async function createTagAndRelease(packageName, packageDir, tagPrefix) {
const version = extractVersion(prTitle, packageName);
if (!version) {
throw new Error(`Failed to extract ${packageName} version from PR title: ${prTitle}`);
}
const tagName = `${tagPrefix}-v${version}`;
console.log(`Creating tag: ${tagName} at ${mergeCommitSha}`);
// Create annotated tag via API
const { data: tagObject } = await github.rest.git.createTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: tagName,
message: `Release ${packageName} v${version}`,
object: mergeCommitSha,
type: 'commit',
tagger: {
name: 'github-actions[bot]',
email: 'github-actions[bot]@users.noreply.github.com',
date: new Date().toISOString()
}
});
// Create ref for the tag
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/tags/${tagName}`,
sha: tagObject.sha
});
console.log(`Tag ${tagName} created`);
tagsCreated.push(tagName);
// Extract changelog
const changelog = await extractChangelog(packageDir, version);
// Create GitHub release
const { data: release } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `${packageName} v${version}`,
body: changelog,
draft: false,
prerelease: false
});
console.log(`Release created: ${release.html_url}`);
return { tagName, version };
}
// Process setuptools-scm
if (labels.includes('release:setuptools-scm')) {
console.log('\n--- Processing setuptools-scm ---');
await createTagAndRelease('setuptools-scm', 'setuptools-scm', 'setuptools-scm');
}
// Process vcs-versioning
if (labels.includes('release:vcs-versioning')) {
console.log('\n--- Processing vcs-versioning ---');
await createTagAndRelease('vcs-versioning', 'vcs-versioning', 'vcs-versioning');
}
// Write summary
const summary = `## Tags Created\n\n${tagsCreated.map(t => `- \`${t}\``).join('\n')}\n\nPyPI upload will be triggered automatically by tag push.`;
await core.summary.addRaw(summary).write();
console.log(`\nDone! Created tags: ${tagsCreated.join(', ')}`);