From 11b55fc63858d6c4926066e0cefac57905776930 Mon Sep 17 00:00:00 2001 From: Daniel Volz Date: Sun, 18 Jan 2026 15:20:18 +0100 Subject: [PATCH] chore: improve release script for branch protection (#52) * chore: improve release script for branch protection - Create PR for version bump instead of direct push to main - Wait for CI checks before merging - Auto-merge PR and create signed tag - Better error handling and gh CLI validation - Works with GitHub branch protection rules * chore(ci): create draft releases for manual release notes Release notes should be descriptive, not auto-generated commit lists. The workflow now creates a DRAFT release with a template. User edits the release notes following the style guide, then publishes. --- .github/workflows/release.yml | 64 +++++++++++++------- scripts/release.sh | 110 ++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 32 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0910dce..61a56c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,41 +16,63 @@ jobs: with: fetch-depth: 0 - - name: Get previous tag - id: prev_tag + - name: Get version info + id: version run: | - # Get all tags sorted by version, find the one before current CURRENT_TAG=${GITHUB_REF#refs/tags/} - PREV_TAG=$(git tag --sort=-v:refname | grep -A1 "^${CURRENT_TAG}$" | tail -1) + VERSION=${CURRENT_TAG#v} + echo "tag=$CURRENT_TAG" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT - # If no previous tag found (first release), use empty + # Get previous tag + PREV_TAG=$(git tag --sort=-v:refname | grep -A1 "^${CURRENT_TAG}$" | tail -1) if [ "$PREV_TAG" = "$CURRENT_TAG" ]; then PREV_TAG="" fi - echo "previous_tag=$PREV_TAG" >> $GITHUB_OUTPUT - echo "Current tag: $CURRENT_TAG, Previous tag: $PREV_TAG" - - name: Generate changelog - id: changelog + - name: Generate release template run: | - PREV_TAG="${{ steps.prev_tag.outputs.previous_tag }}" + cat > release_notes.md << 'EOF' + ## What's New - if [ -z "$PREV_TAG" ]; then - # First release - get all commits - CHANGES=$(git log --pretty=format:"- %s" HEAD) - else - # Get commits since last tag - CHANGES=$(git log --pretty=format:"- %s" ${PREV_TAG}..HEAD) - fi + - # Write to file for multiline support - echo "$CHANGES" > changelog.txt + ### New Features + + + - **Feature Name**: Description of the feature + + ### Improvements + + + - **Improvement**: Description + + ### Where to Find It + + + + --- + + ## Docker Images + + ```bash + docker pull ghcr.io/danielvolz/medassist-ng-backend:${{ steps.version.outputs.version }} + docker pull ghcr.io/danielvolz/medassist-ng-frontend:${{ steps.version.outputs.version }} + ``` + + **Full Changelog**: https://github.com/DanielVolz/medassist-ng/compare/${{ steps.version.outputs.previous_tag }}...${{ steps.version.outputs.tag }} + EOF - - name: Create Release + - name: Create Draft Release uses: softprops/action-gh-release@v1 with: - body_path: changelog.txt + body_path: release_notes.md + draft: true generate_release_notes: false + name: "Release ${{ steps.version.outputs.tag }}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/release.sh b/scripts/release.sh index d3e5e1e..1113e5c 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -7,6 +7,9 @@ # ./scripts/release.sh minor # 1.0.0 -> 1.1.0 (new features) # ./scripts/release.sh major # 1.0.0 -> 2.0.0 (breaking changes) # ./scripts/release.sh 1.2.3 # explicit version +# +# This script creates a PR for the version bump (required due to branch protection), +# waits for CI, merges it, and then creates a signed tag for the release. # ============================================================================= set -e @@ -18,11 +21,28 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +# GitHub repo +GITHUB_REPO="DanielVolz/medassist-ng" + # Get script directory and project root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" cd "$PROJECT_ROOT" +# Check for gh CLI +if ! command -v gh &> /dev/null; then + echo -e "${RED}Error: GitHub CLI (gh) is required but not installed.${NC}" + echo "Install it with: brew install gh" + exit 1 +fi + +# Check gh authentication +if ! gh auth status &> /dev/null; then + echo -e "${RED}Error: Not authenticated with GitHub CLI.${NC}" + echo "Run: gh auth login" + exit 1 +fi + # Check for uncommitted changes if [[ -n $(git status --porcelain) ]]; then echo -e "${RED}Error: You have uncommitted changes. Commit or stash them first.${NC}" @@ -30,6 +50,11 @@ if [[ -n $(git status --porcelain) ]]; then exit 1 fi +# Make sure we're on main and up to date +echo -e "${BLUE}Updating main branch...${NC}" +git checkout main +git pull origin main 2>/dev/null || git pull github main 2>/dev/null || true + # Get current version from backend/package.json CURRENT_VERSION=$(grep '"version"' backend/package.json | sed 's/.*"version": "\(.*\)".*/\1/') echo -e "${BLUE}Current version: ${YELLOW}v${CURRENT_VERSION}${NC}" @@ -74,6 +99,19 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi +# Branch name for the release +RELEASE_BRANCH="chore/release-${NEW_VERSION}" + +# Check if branch already exists +if git show-ref --verify --quiet "refs/heads/${RELEASE_BRANCH}"; then + echo -e "${YELLOW}Branch ${RELEASE_BRANCH} already exists locally. Deleting...${NC}" + git branch -D "${RELEASE_BRANCH}" +fi + +# Create release branch +echo -e "${BLUE}Creating release branch...${NC}" +git checkout -b "${RELEASE_BRANCH}" + # Update version in package.json files echo -e "${BLUE}Updating package.json files...${NC}" sed -i '' "s/\"version\": \"${CURRENT_VERSION}\"/\"version\": \"${NEW_VERSION}\"/" backend/package.json @@ -84,23 +122,73 @@ echo -e "${BLUE}Committing version bump...${NC}" git add backend/package.json frontend/package.json 2>/dev/null || git add backend/package.json git commit -m "chore: release v${NEW_VERSION}" -# Check if tag exists -if git rev-parse "v${NEW_VERSION}" >/dev/null 2>&1; then - echo -e "${YELLOW}Tag v${NEW_VERSION} already exists. Overwriting...${NC}" - git tag -d "v${NEW_VERSION}" - git push origin ":refs/tags/v${NEW_VERSION}" 2>/dev/null || true +# Push branch to GitHub +echo -e "${BLUE}Pushing release branch to GitHub...${NC}" +git push -u origin "${RELEASE_BRANCH}" 2>/dev/null || git push -u github "${RELEASE_BRANCH}" + +# Create PR +echo -e "${BLUE}Creating Pull Request...${NC}" +PR_URL=$(gh pr create \ + --repo "${GITHUB_REPO}" \ + --head "${RELEASE_BRANCH}" \ + --title "chore: release v${NEW_VERSION}" \ + --body "## Release v${NEW_VERSION} + +Automated version bump for release v${NEW_VERSION}. + +This PR was created by the release script." \ + 2>&1) + +echo -e "${GREEN}PR created: ${YELLOW}${PR_URL}${NC}" + +# Extract PR number +PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') + +# Wait for CI checks +echo -e "${BLUE}Waiting for CI checks to complete...${NC}" +if ! gh pr checks "${PR_NUMBER}" --repo "${GITHUB_REPO}" --watch; then + echo -e "${RED}CI checks failed! Please fix the issues and try again.${NC}" + exit 1 fi -# Create and push tag +echo -e "${GREEN}CI checks passed!${NC}" + +# Merge PR +echo -e "${BLUE}Merging PR...${NC}" +gh pr merge "${PR_NUMBER}" --repo "${GITHUB_REPO}" --squash --delete-branch + +# Switch back to main and pull +echo -e "${BLUE}Updating main branch with merged changes...${NC}" +git checkout main +git pull origin main 2>/dev/null || git pull github main 2>/dev/null || true + +# Check if tag exists and delete it +if git rev-parse "v${NEW_VERSION}" >/dev/null 2>&1; then + echo -e "${YELLOW}Tag v${NEW_VERSION} already exists locally. Deleting...${NC}" + git tag -d "v${NEW_VERSION}" +fi + +# Check if remote tag exists +if git ls-remote --tags origin "v${NEW_VERSION}" 2>/dev/null | grep -q "v${NEW_VERSION}" || \ + git ls-remote --tags github "v${NEW_VERSION}" 2>/dev/null | grep -q "v${NEW_VERSION}"; then + echo -e "${YELLOW}Tag v${NEW_VERSION} exists on remote. Deleting...${NC}" + git push origin ":refs/tags/v${NEW_VERSION}" 2>/dev/null || true + git push github ":refs/tags/v${NEW_VERSION}" 2>/dev/null || true +fi + +# Create signed tag echo -e "${BLUE}Creating signed tag v${NEW_VERSION}...${NC}" git tag -s "v${NEW_VERSION}" -m "Release v${NEW_VERSION}" -# Push -echo -e "${BLUE}Pushing to origin (GitHub)...${NC}" -git push origin main -git push origin "v${NEW_VERSION}" +# Push tag +echo -e "${BLUE}Pushing tag to GitHub...${NC}" +git push origin "v${NEW_VERSION}" 2>/dev/null || git push github "v${NEW_VERSION}" echo "" +echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}" echo -e "${GREEN}✓ Released v${NEW_VERSION}${NC}" +echo -e "${GREEN}════════════════════════════════════════════════════════════${NC}" +echo "" echo -e "${BLUE}GitHub Actions will now build and publish Docker images.${NC}" -echo -e "Track progress: ${YELLOW}https://github.com/DanielVolz/medassist-ng/actions${NC}" +echo -e "Track progress: ${YELLOW}https://github.com/${GITHUB_REPO}/actions${NC}" +echo -e "Release page: ${YELLOW}https://github.com/${GITHUB_REPO}/releases/tag/v${NEW_VERSION}${NC}"