Skip to content

Commit aa00a66

Browse files
Merge pull request #83 from keyqcloud/chore/modernize-release-pipeline
ci(release): modernize release.sh + add tag-triggered GitHub Release workflow
2 parents 2f7d49b + 6942194 commit aa00a66

2 files changed

Lines changed: 152 additions & 48 deletions

File tree

.github/workflows/release.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Release
2+
3+
# Tag-triggered. Extracts the matching `## <version>` section from
4+
# CHANGELOG.md and creates a GitHub Release. Unlike kyte-api-js, no
5+
# S3 / CloudFront step is needed — kyte-php is composer-distributed,
6+
# and Packagist auto-detects the new tag from GitHub within minutes.
7+
#
8+
# Version comes from the tag (refs/tags/v4.4.0 → 4.4.0). CHANGELOG.md
9+
# must have a `## <version>` H2 anywhere in the file — the script
10+
# extracts everything between that header and the next `## ` as
11+
# release notes.
12+
13+
on:
14+
push:
15+
tags:
16+
- 'v*'
17+
18+
permissions:
19+
contents: write # needed for softprops/action-gh-release
20+
21+
# Opt into Node.js 24 ahead of the 2026-06-02 force-migration.
22+
env:
23+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
24+
25+
jobs:
26+
release:
27+
name: Create GitHub Release
28+
runs-on: ubuntu-latest
29+
30+
steps:
31+
- name: Check out source
32+
uses: actions/checkout@v5
33+
34+
- name: Extract version from tag
35+
id: version
36+
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
37+
38+
- name: Verify CHANGELOG.md has matching section
39+
run: |
40+
VERSION="${{ steps.version.outputs.VERSION }}"
41+
if ! grep -q "^## ${VERSION}$" CHANGELOG.md; then
42+
echo "::error::Tag v$VERSION but CHANGELOG.md has no '## $VERSION' section."
43+
echo "::error::Add a CHANGELOG entry before tagging."
44+
exit 1
45+
fi
46+
47+
- name: Extract release notes from CHANGELOG.md
48+
id: notes
49+
run: |
50+
VERSION="${{ steps.version.outputs.VERSION }}"
51+
# Capture everything between `## $VERSION` and the next `## ` line.
52+
awk -v v="^## ${VERSION}$" '
53+
$0 ~ v { capture=1; next }
54+
/^## / && capture { exit }
55+
capture { print }
56+
' CHANGELOG.md > release-notes.md
57+
58+
if [ ! -s release-notes.md ]; then
59+
echo "::error::Could not extract release notes for v$VERSION from CHANGELOG.md"
60+
exit 1
61+
fi
62+
63+
echo "Release notes preview (first 20 lines):"
64+
head -20 release-notes.md
65+
66+
- name: Create GitHub Release
67+
uses: softprops/action-gh-release@v2
68+
with:
69+
name: kyte-php v${{ steps.version.outputs.VERSION }}
70+
body_path: release-notes.md
71+
draft: false
72+
prerelease: false

release.sh

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,82 @@
11
#!/bin/bash
2+
#
3+
# Tag and push a release.
4+
#
5+
# CI (.github/workflows/release.yml) handles the actual GitHub Release
6+
# creation on tag push. This script's only job is to verify CHANGELOG
7+
# matches the version, ensure the working tree is clean, and tag.
8+
#
9+
# kyte-php is distributed via Composer (packagist.org), which auto-
10+
# discovers tags from GitHub. Once the tag exists upstream, customers
11+
# can `composer require keyqcloud/kyte-php:^X.Y` to pull it.
12+
#
13+
# Usage: ./release.sh 4.4.0
214

3-
print_error() {
4-
echo "\033[1;31m$1\033[0m"
5-
}
6-
7-
if [ "$#" -eq 1 ]; then
8-
# Check the CHANGELOG.md
9-
changelog_version=$(awk '/## /{print $2;exit}' CHANGELOG.md)
10-
11-
if [ "$changelog_version" != "$1" ]; then
12-
print_error "Version in CHANGELOG.md does not match the release version."
13-
exit 1
14-
fi
15-
16-
# Define the path to the Version.php file
17-
php_version_file_path="src/Core/Version.php"
18-
19-
# Extract the version from the PHP file
20-
php_version=$(awk -F '=' '/const MAJOR/{major=$2} /const MINOR/{minor=$2} /const PATCH/{patch=$2} END{print major"."minor"."patch}' "$php_version_file_path")
21-
22-
# Remove semicolons using tr
23-
php_version=$(echo "$php_version" | tr -d ';')
24-
25-
# Compare the desired version with the PHP version
26-
if [ "$1" != "$php_version" ]; then
27-
print_error "Version mismatch: Desired version $1, Version.php is $php_version"
28-
exit 1
29-
fi
30-
31-
echo "Creating tag for release version $1"
32-
33-
git tag "v$1"
34-
35-
if [ $? -eq 0 ]; then
36-
echo "Git tag created successfully for v$1."
37-
# Push the tag to the origin
38-
git push origin --tags
39-
40-
if [ $? -eq 0 ]; then
41-
echo "Git push successful. New release v$1 is available"
42-
else
43-
print_error "Git push failed."
44-
exit 1
45-
fi
46-
else
47-
print_error "Git tag creation failed."
48-
exit 1
49-
fi
50-
fi
15+
set -e
16+
17+
print_error() { printf "\033[1;31m%s\033[0m\n" "$1"; }
18+
print_success() { printf "\033[1;32m%s\033[0m\n" "$1"; }
19+
20+
if [ "$#" -ne 1 ]; then
21+
print_error "Usage: ./release.sh <version>"
22+
print_error "Example: ./release.sh 4.4.0"
23+
exit 1
24+
fi
25+
26+
VERSION="$1"
27+
28+
# Sanity-check CHANGELOG has the new version at the top.
29+
CHANGELOG_VERSION=$(awk '/^## /{print $2; exit}' CHANGELOG.md)
30+
if [ "$CHANGELOG_VERSION" != "$VERSION" ]; then
31+
print_error "Version in CHANGELOG.md ($CHANGELOG_VERSION) does not match $VERSION."
32+
print_error "Update CHANGELOG.md to add a '## $VERSION' section before releasing."
33+
exit 1
34+
fi
35+
36+
# Refuse to tag against a dirty tree — would tag unintended state.
37+
if ! git diff-index --quiet HEAD --; then
38+
print_error "Working tree is not clean. Commit or stash changes first."
39+
git status --short
40+
exit 1
41+
fi
42+
43+
# Refuse to tag if we're not on master.
44+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
45+
if [ "$CURRENT_BRANCH" != "master" ]; then
46+
print_error "Releases must be tagged from master. Current branch: $CURRENT_BRANCH"
47+
exit 1
48+
fi
49+
50+
# Refuse to tag if local master is behind origin.
51+
git fetch origin master --quiet
52+
LOCAL=$(git rev-parse master)
53+
REMOTE=$(git rev-parse origin/master)
54+
if [ "$LOCAL" != "$REMOTE" ]; then
55+
print_error "Local master is not in sync with origin/master. Push or pull first."
56+
exit 1
57+
fi
58+
59+
# Refuse to tag if the tag already exists locally or upstream.
60+
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
61+
print_error "Tag v$VERSION already exists locally."
62+
exit 1
63+
fi
64+
if git ls-remote --tags origin "v$VERSION" 2>/dev/null | grep -q "v$VERSION"; then
65+
print_error "Tag v$VERSION already exists on origin."
66+
exit 1
67+
fi
68+
69+
print_success "Tagging v$VERSION..."
70+
git tag "v$VERSION"
71+
git push origin "v$VERSION"
72+
73+
print_success ""
74+
print_success "Tagged v$VERSION and pushed."
75+
print_success "GitHub Actions will now extract release notes from CHANGELOG.md"
76+
print_success "and create the GitHub Release."
77+
print_success "Watch: https://github.com/keyqcloud/kyte-php/actions"
78+
print_success ""
79+
print_success "Once the GitHub Release is published, Packagist will auto-detect"
80+
print_success "the new tag within minutes. Customers pulling via Composer with"
81+
print_success "a constraint matching v$VERSION (e.g., ^${VERSION%%.*}.${VERSION#*.}"
82+
print_success "or ^${VERSION%%.*}.${VERSION#*.}.0) will receive it on next update."

0 commit comments

Comments
 (0)