Skip to content

Create Draft Release #19

Create Draft Release

Create Draft Release #19

Workflow file for this run

name: Create Draft Release
on:
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Fail if not on main branch
if: ${{ github.ref != 'refs/heads/main' }}
run: |
echo "❌ Error: This workflow must be run on the main branch."
echo "Current ref: $GITHUB_REF"
exit 1
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
- name: Install dependencies
run: npm ci
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine version bump from merged PRs
id: version
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
# Get the latest tag, default to v0.0.0 if no tags exist
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LATEST_TAG" ]; then
echo "No tags found, starting from version 0.0.0"
LATEST_TAG="v0.0.0"
# Get all commits in the repository
COMMITS=$(git log --oneline)
else
echo "Latest tag: $LATEST_TAG"
# Get all commits since the last tag
COMMITS=$(git log ${LATEST_TAG}..HEAD --oneline)
fi
# Check if there are any commits to process
if [ -z "$COMMITS" ]; then
echo "❌ Error: No commits found since $LATEST_TAG"
echo "No changes have been made since the last release."
exit 1
fi
# Initialize semver bump level (0=none, 1=patch, 2=minor, 3=major)
MAX_BUMP=0 # Must be explicitly set by PR checkbox
# Parse each commit to find semver selections
while IFS= read -r line; do
if [ -z "$line" ]; then
continue
fi
COMMIT_HASH=$(echo "$line" | awk '{print $1}')
COMMIT_MESSAGE=${line#* }
# Extract PR number from squash merge format: "Title (#123)"
PR_NUMBER=$(echo "$COMMIT_MESSAGE" | grep -oE '\(#[0-9]+\)$' | grep -oE '[0-9]+')
if [ -z "$PR_NUMBER" ]; then
echo "❌ Error: No PR number found in commit $COMMIT_HASH"
echo "Commit message: $COMMIT_MESSAGE"
echo "All commits must be from merged PRs with format 'Title (#123)'"
exit 1
fi
echo "Checking PR #$PR_NUMBER for commit $COMMIT_HASH"
# Fetch PR body using GitHub CLI
GH_OUTPUT=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>&1)
GH_EXIT_CODE=$?
if [ $GH_EXIT_CODE -ne 0 ] || [ -z "$GH_OUTPUT" ]; then
echo "❌ Error: Could not fetch PR body for #$PR_NUMBER"
echo "gh CLI error output:"
echo "$GH_OUTPUT"
exit 1
fi
# Check for semver selections in PR body (look for checked boxes)
PR_BODY="$GH_OUTPUT"
if echo "$PR_BODY" | grep -q "\[[xX]\] \*\*MAJOR\*\*"; then
echo "Found MAJOR bump in PR #$PR_NUMBER"
MAX_BUMP=3
elif echo "$PR_BODY" | grep -q "\[[xX]\] \*\*MINOR\*\*"; then
echo "Found MINOR bump in PR #$PR_NUMBER"
if [ $MAX_BUMP -lt 2 ]; then
MAX_BUMP=2
fi
elif echo "$PR_BODY" | grep -q "\[[xX]\] \*\*PATCH\*\*"; then
echo "Found PATCH bump in PR #$PR_NUMBER"
if [ $MAX_BUMP -lt 1 ]; then
MAX_BUMP=1
fi
else
echo "❌ Error: No version bump checkbox selected in PR #$PR_NUMBER"
echo "Please select MAJOR, MINOR, or PATCH in the PR description"
exit 1
fi
done <<< "$COMMITS"
# Determine bump type
case $MAX_BUMP in
3)
BUMP_TYPE="major"
;;
2)
BUMP_TYPE="minor"
;;
1)
BUMP_TYPE="patch"
;;
esac
echo "Bump type: $BUMP_TYPE"
# Export for later steps
echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT
- name: Update version in package.json
id: npm_version
run: |
# Use npm version to automatically bump the version
npm version ${{ steps.version.outputs.bump_type }} --no-git-tag-version
# Read the new version
NEW_VERSION=$(node -p "require('./package.json').version")
echo "New version: $NEW_VERSION"
# Export for later steps
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Update CHANGELOG.md
run: |
VERSION="${{ steps.npm_version.outputs.version }}"
DATE=$(date +%Y-%m-%d)
# Create a temporary file
TEMP_FILE=$(mktemp)
# Read the changelog and update it
awk -v version="$VERSION" -v date="$DATE" '
BEGIN { found_unreleased = 0; seen_content = 0 }
/## \[Unreleased\]/ {
found_unreleased = 1
print "## [Unreleased]"
print ""
print "## [" version "] - " date
print ""
next
}
# Skip empty lines immediately after [Unreleased] until we hit content or another ##
found_unreleased && /^$/ && !seen_content {
next
}
found_unreleased && /./ {
seen_content = 1
}
{ print }
' docs/CHANGELOG.md > "$TEMP_FILE"
# Replace the original file
mv "$TEMP_FILE" docs/CHANGELOG.md
echo "✓ CHANGELOG.md updated: Added version $VERSION section"
- name: Commit changelog and version bump
run: |
git add docs/CHANGELOG.md package.json package-lock.json
git commit -m "Release version ${{ steps.npm_version.outputs.version }}"
git push origin main
- name: Create and push tag
run: |
# Create the tag pointing to the commit with updated files
git tag -a "v${{ steps.npm_version.outputs.version }}" -m "Release version ${{ steps.npm_version.outputs.version }}"
git push origin "v${{ steps.npm_version.outputs.version }}"
- name: Create Draft GitHub Release
uses: actions/github-script@v7
with:
script: |
const tagName = 'v${{ steps.npm_version.outputs.version }}';
// Create a draft release with auto-generated notes for manual curation
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `Release ${tagName}`,
body: `Release version ${{ steps.npm_version.outputs.version }} (${{ steps.version.outputs.bump_type }} bump)\n\nSee [CHANGELOG.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/docs/CHANGELOG.md) for details.`,
draft: true,
prerelease: false,
generate_release_notes: true
});
console.log(`Draft release created: ${release.data.html_url}`);
console.log('Please review and publish the release manually from the GitHub UI.');