Skip to content

Commit 31bf9ed

Browse files
authored
Merge pull request #56 from Cyber-Syntax:ci/release-by-tag
ci: update release workflow to publish from tags
2 parents b86b305 + 20473ce commit 31bf9ed

1 file changed

Lines changed: 126 additions & 100 deletions

File tree

.github/workflows/main.yml

Lines changed: 126 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,132 @@
1-
name: Create Release from CHANGELOG
1+
name: Publish Release from Tag and CHANGELOG
22

33
on:
4-
push:
5-
branches: [main]
6-
paths:
7-
- "CHANGELOG.md" # Only trigger when CHANGELOG.md is updated
4+
push:
5+
tags:
6+
- "v*"
87

98
concurrency:
10-
group: release-${{ github.ref }}
11-
cancel-in-progress: false # Don't cancel running releases
9+
group: release-${{ github.ref_name }}
10+
cancel-in-progress: false
1211

1312
jobs:
14-
release:
15-
runs-on: ubuntu-latest
16-
permissions:
17-
contents: write
18-
19-
steps:
20-
- name: Checkout code
21-
uses: actions/checkout@v5
22-
23-
- name: Validate CHANGELOG format
24-
run: |
25-
if [[ ! -f "CHANGELOG.md" ]]; then
26-
echo "::error::CHANGELOG.md not found"
27-
exit 1
28-
fi
29-
30-
if ! grep -q "^## \[" CHANGELOG.md && ! grep -q "^## v" CHANGELOG.md; then
31-
echo "::error::CHANGELOG.md does not contain valid version headers"
32-
echo "::error::Expected format: '## [X.Y.Z]' or '## vX.Y.Z'"
33-
exit 1
34-
fi
35-
36-
- name: Extract latest version and notes
37-
id: changelog
38-
run: ./scripts/extract_changelog.sh
39-
40-
- name: Validate semantic version
41-
if: steps.changelog.outputs.is_unreleased != 'true'
42-
run: |
43-
VERSION=${{ steps.changelog.outputs.version }}
44-
# Remove 'v' prefix if present
45-
VERSION=${VERSION#v}
46-
47-
# Validate semantic versioning format
48-
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]; then
49-
echo "::error::Version '$VERSION' does not follow semantic versioning"
50-
echo "::error::Expected format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]"
51-
exit 1
52-
fi
53-
54-
echo "::notice::Version $VERSION is valid semantic version"
55-
56-
- name: Check for existing release
57-
if: steps.changelog.outputs.is_unreleased != 'true'
58-
id: check_release
59-
run: |
60-
VERSION=${{ steps.changelog.outputs.version }}
61-
TAG="v$VERSION"
62-
if gh release view "$TAG" &>/dev/null; then
63-
echo "::notice::Release already exists: $TAG"
64-
echo "exists=true" >> $GITHUB_OUTPUT
65-
else
66-
echo "::notice::No existing release found for: $TAG"
67-
echo "exists=false" >> $GITHUB_OUTPUT
68-
fi
69-
env:
70-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71-
72-
- name: Create GitHub Release
73-
if: steps.changelog.outputs.is_unreleased != 'true' && steps.check_release.outputs.exists == 'false'
74-
uses: softprops/action-gh-release@v2.5.0
75-
with:
76-
tag_name: v${{ steps.changelog.outputs.version }}
77-
name: "Release v${{ steps.changelog.outputs.version }}"
78-
body: ${{ steps.changelog.outputs.notes }}
79-
draft: false
80-
prerelease: ${{ contains(steps.changelog.outputs.version, '-') }}
81-
env:
82-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83-
84-
- name: Verify Release Created
85-
if: steps.changelog.outputs.is_unreleased != 'true' && steps.check_release.outputs.exists == 'false'
86-
run: |
87-
TAG="v${{ steps.changelog.outputs.version }}"
88-
# Wait a moment for release to be fully created
89-
sleep 5
90-
91-
# Verify release exists
92-
if gh release view "$TAG" &>/dev/null; then
93-
RELEASE_URL=$(gh release view "$TAG" --json url --jq '.url')
94-
echo "::notice::Release successfully created: $RELEASE_URL"
95-
else
96-
echo "::error::Release verification failed for $TAG"
97-
exit 1
98-
fi
99-
env:
100-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
101-
102-
- name: Notify on Failure
103-
if: failure()
104-
run: |
105-
echo "::error::Release workflow failed"
106-
echo "::error::Please check CHANGELOG.md format and workflow logs"
13+
release:
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 10
16+
permissions:
17+
contents: write
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22+
with:
23+
fetch-depth: 0
24+
fetch-tags: true
25+
26+
- name: Validate CHANGELOG format
27+
run: |
28+
if [[ ! -f "CHANGELOG.md" ]]; then
29+
echo "::error::CHANGELOG.md not found"
30+
exit 1
31+
fi
32+
33+
if ! grep -q "^## \[" CHANGELOG.md && ! grep -q "^## v" CHANGELOG.md; then
34+
echo "::error::CHANGELOG.md does not contain valid version headers"
35+
echo "::error::Expected format: '## [X.Y.Z]' or '## vX.Y.Z'"
36+
exit 1
37+
fi
38+
39+
- name: Extract latest version and notes
40+
id: changelog
41+
run: ./scripts/extract_changelog.sh
42+
43+
- name: Validate semantic version
44+
if: steps.changelog.outputs.is_unreleased != 'true'
45+
run: |
46+
VERSION="${{ steps.changelog.outputs.version }}"
47+
VERSION=${VERSION#v}
48+
49+
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]; then
50+
echo "::error::Version '$VERSION' does not follow semantic versioning"
51+
echo "::error::Expected format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]"
52+
exit 1
53+
fi
54+
55+
echo "::notice::Version $VERSION is valid semantic version"
56+
57+
- name: Validate tag matches changelog version
58+
if: steps.changelog.outputs.is_unreleased != 'true'
59+
run: |
60+
TAG_NAME="${GITHUB_REF_NAME}"
61+
TAG_VERSION="${TAG_NAME#v}"
62+
CHANGELOG_VERSION="${{ steps.changelog.outputs.version }}"
63+
CHANGELOG_VERSION="${CHANGELOG_VERSION#v}"
64+
65+
if [[ "$TAG_VERSION" != "$CHANGELOG_VERSION" ]]; then
66+
echo "::error::Tag '$TAG_NAME' does not match CHANGELOG version '$CHANGELOG_VERSION'"
67+
echo "::error::Update CHANGELOG.md or push the matching release tag"
68+
exit 1
69+
fi
70+
71+
echo "::notice::Tag '$TAG_NAME' matches CHANGELOG version '$CHANGELOG_VERSION'"
72+
73+
- name: Check for existing release
74+
if: steps.changelog.outputs.is_unreleased != 'true'
75+
id: check_release
76+
env:
77+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78+
run: |
79+
TAG_NAME="${GITHUB_REF_NAME}"
80+
if gh release view "$TAG_NAME" &>/dev/null; then
81+
echo "::notice::Release already exists: $TAG_NAME"
82+
echo "exists=true" >> "$GITHUB_OUTPUT"
83+
else
84+
echo "::notice::No existing release found for: $TAG_NAME"
85+
echo "exists=false" >> "$GITHUB_OUTPUT"
86+
fi
87+
88+
- name: Create GitHub Release
89+
if: steps.changelog.outputs.is_unreleased != 'true' && steps.check_release.outputs.exists == 'false'
90+
env:
91+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
92+
RELEASE_NOTES: ${{ steps.changelog.outputs.notes }}
93+
run: |
94+
TAG_NAME="${GITHUB_REF_NAME}"
95+
VERSION="${TAG_NAME#v}"
96+
NOTES_FILE="$(mktemp)"
97+
98+
printf '%s\n' "$RELEASE_NOTES" > "$NOTES_FILE"
99+
100+
PRERELEASE_FLAG=""
101+
if [[ "$VERSION" == *-* ]]; then
102+
PRERELEASE_FLAG="--prerelease"
103+
fi
104+
105+
gh release create "$TAG_NAME" \
106+
--title "Release $TAG_NAME" \
107+
--notes-file "$NOTES_FILE" \
108+
--verify-tag \
109+
$PRERELEASE_FLAG
110+
111+
rm -f "$NOTES_FILE"
112+
113+
- name: Verify Release Created
114+
if: steps.changelog.outputs.is_unreleased != 'true' && steps.check_release.outputs.exists == 'false'
115+
env:
116+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
117+
run: |
118+
TAG_NAME="${GITHUB_REF_NAME}"
119+
120+
if gh release view "$TAG_NAME" &>/dev/null; then
121+
RELEASE_URL=$(gh release view "$TAG_NAME" --json url --jq '.url')
122+
echo "::notice::Release successfully created: $RELEASE_URL"
123+
else
124+
echo "::error::Release verification failed for $TAG_NAME"
125+
exit 1
126+
fi
127+
128+
- name: Notify on Failure
129+
if: failure()
130+
run: |
131+
echo "::error::Release workflow failed"
132+
echo "::error::Please check CHANGELOG.md format and workflow logs"

0 commit comments

Comments
 (0)