Skip to content

Commit 057d98f

Browse files
committed
feat(release): automate GitHub releases from version tags
Add a tag-driven workflow that creates or updates GitHub Releases for version tags that match the repository's versioning policy. The workflow prefers the matching releases/x.y.z branch as the release target when it exists and falls back to the tagged commit otherwise.
1 parent 3bb11ce commit 057d98f

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: Publish GitHub Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: Existing release tag to publish or refresh
11+
required: true
12+
type: string
13+
14+
permissions:
15+
contents: write
16+
17+
jobs:
18+
publish-release:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Resolve release metadata
22+
id: metadata
23+
shell: bash
24+
run: |
25+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
26+
tag="${{ inputs.tag }}"
27+
else
28+
tag="${GITHUB_REF_NAME}"
29+
fi
30+
31+
if [[ -z "$tag" ]]; then
32+
echo "::error::Unable to determine release tag"
33+
exit 1
34+
fi
35+
36+
if [[ ! "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)(r[0-9]+)?$ ]]; then
37+
echo "::error::Tag '$tag' does not match the expected release format"
38+
exit 1
39+
fi
40+
41+
base_version="${BASH_REMATCH[1]}"
42+
release_name="S1API ${tag#v}"
43+
release_branch="releases/${base_version}"
44+
45+
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
46+
echo "base_version=${base_version}" >> "$GITHUB_OUTPUT"
47+
echo "release_name=${release_name}" >> "$GITHUB_OUTPUT"
48+
echo "release_branch=${release_branch}" >> "$GITHUB_OUTPUT"
49+
50+
- name: Publish or update GitHub release
51+
uses: actions/github-script@v7
52+
env:
53+
RELEASE_TAG: ${{ steps.metadata.outputs.tag }}
54+
RELEASE_NAME: ${{ steps.metadata.outputs.release_name }}
55+
RELEASE_BRANCH: ${{ steps.metadata.outputs.release_branch }}
56+
with:
57+
script: |
58+
const owner = context.repo.owner;
59+
const repo = context.repo.repo;
60+
const tag = process.env.RELEASE_TAG;
61+
const releaseName = process.env.RELEASE_NAME;
62+
const releaseBranch = process.env.RELEASE_BRANCH;
63+
64+
const resolveTargetCommitish = async () => {
65+
try {
66+
await github.rest.repos.getBranch({
67+
owner,
68+
repo,
69+
branch: releaseBranch
70+
});
71+
return releaseBranch;
72+
} catch (error) {
73+
if (error.status !== 404) {
74+
throw error;
75+
}
76+
}
77+
78+
const ref = await github.rest.git.getRef({
79+
owner,
80+
repo,
81+
ref: `tags/${tag}`
82+
});
83+
84+
let target = ref.data.object;
85+
if (target.type === "tag") {
86+
const annotated = await github.rest.git.getTag({
87+
owner,
88+
repo,
89+
tag_sha: target.sha
90+
});
91+
target = annotated.data.object;
92+
}
93+
94+
return target.sha;
95+
};
96+
97+
const targetCommitish = await resolveTargetCommitish();
98+
const notes = await github.rest.repos.generateReleaseNotes({
99+
owner,
100+
repo,
101+
tag_name: tag,
102+
target_commitish: targetCommitish
103+
});
104+
105+
const payload = {
106+
owner,
107+
repo,
108+
tag_name: tag,
109+
target_commitish: targetCommitish,
110+
name: releaseName,
111+
body: notes.data.body,
112+
draft: false,
113+
prerelease: false,
114+
make_latest: "legacy"
115+
};
116+
117+
let existingRelease = null;
118+
try {
119+
existingRelease = await github.rest.repos.getReleaseByTag({
120+
owner,
121+
repo,
122+
tag
123+
});
124+
} catch (error) {
125+
if (error.status !== 404) {
126+
throw error;
127+
}
128+
}
129+
130+
if (existingRelease) {
131+
const updated = await github.rest.repos.updateRelease({
132+
...payload,
133+
release_id: existingRelease.data.id
134+
});
135+
136+
core.notice(`Updated GitHub release ${updated.data.html_url}`);
137+
return;
138+
}
139+
140+
const created = await github.rest.repos.createRelease(payload);
141+
core.notice(`Published GitHub release ${created.data.html_url}`);

0 commit comments

Comments
 (0)