From d4f781ea279f38571c45a6ee9613c08a60ca10c2 Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Fri, 26 Sep 2025 13:13:08 -0500 Subject: [PATCH] chore: improve and fix the auto-release flow --- .github/workflows/auto-release.yml | 209 ++++++++++++++++------------- .github/workflows/check.yml | 91 ------------- .github/workflows/release.yml | 41 +++--- .github/workflows/validate-tag.yml | 74 ++++++++++ package.json | 2 +- 5 files changed, 213 insertions(+), 204 deletions(-) create mode 100644 .github/workflows/validate-tag.yml diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 58b167611..21922ce2a 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -1,4 +1,4 @@ -name: auto-release +name: Auto Release on: push: @@ -12,19 +12,24 @@ on: - "!.github/workflows/**" concurrency: - group: ${{ github.workflow }} + group: ${{ github.workflow }}-main cancel-in-progress: false +permissions: + contents: write + actions: write + jobs: auto-release: - name: Auto Release + name: Auto Version Bump and Release runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, 'release:')" + if: ${{ !contains(github.event.head_commit.message, '[v') && github.event.head_commit.author.name != 'GitHub Action' && !contains(github.event.head_commit.message, '] [v') }} permissions: contents: write - pull-requests: read + actions: write + steps: - - name: Checkout + - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 @@ -40,117 +45,133 @@ jobs: - name: Configure Git run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" - - name: Get latest release - id: latest_release + - name: Get current version + id: current_version run: | - # Get the latest release tag, fallback to package.json version if none exists - echo "All tags:" - git tag --sort=-version:refname - latest_tag=$(git tag --sort=-version:refname | head -n1) - if [ -z "$latest_tag" ]; then - pkg_version=$(node -p "require('./package.json').version") - latest_tag="v$pkg_version" - fi - echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT - echo "Latest release tag: $latest_tag" + CURRENT_VERSION=$(node -p "require('./package.json').version") + echo "current=${CURRENT_VERSION}" >> $GITHUB_OUTPUT + echo "Current version: ${CURRENT_VERSION}" - - name: Get commits since last release - id: commits + - name: Determine version bump + id: version_bump run: | - latest_tag="${{ steps.latest_release.outputs.latest_tag }}" - if git rev-parse "$latest_tag" >/dev/null 2>&1; then - # Tag exists, get commits since last release - commits=$(git log ${latest_tag}..HEAD --oneline --pretty=format:"%s") + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [[ -z "$LAST_TAG" ]]; then + COMMITS=$(git log --pretty=format:"%s" --no-merges) else - # Tag does not exist, get all commits - commits=$(git log --oneline --pretty=format:"%s" | head -10) + COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"%s" --no-merges) fi - echo "Commits since $latest_tag:" - echo "$commits" + BUMP_TYPE="patch" - # Check if there are any commits to release - if [ -z "$commits" ]; then - echo "No new commits to release" - echo "should_release=false" >> $GITHUB_OUTPUT - exit 0 + if echo "$COMMITS" | grep -qiE "(BREAKING CHANGE|!:|breaking:|major:|bump.*major)"; then + BUMP_TYPE="major" + elif echo "$COMMITS" | grep -qiE "(feat:|feature:|minor:|bump.*minor)"; then + BUMP_TYPE="minor" + else + BUMP_TYPE="patch" fi - echo "should_release=true" >> $GITHUB_OUTPUT + echo "bump_type=${BUMP_TYPE}" >> $GITHUB_OUTPUT - - name: Determine version bump type - id: bump_type - if: steps.commits.outputs.should_release == 'true' + - name: Bump version + id: bump_version run: | - latest_tag="${{ steps.latest_release.outputs.latest_tag }}" - commits=$(git log ${latest_tag}..HEAD --oneline --pretty=format:"%s" || git log --oneline --pretty=format:"%s" | head -10) - - # Default to patch - bump_type="patch" - - # Check for breaking changes (major) - if echo "$commits" | grep -qE "^[^:]+!:|BREAKING CHANGE"; then - bump_type="major" - # Check for features (minor) - elif echo "$commits" | grep -qE "^feat(\(.+\))?:"; then - bump_type="minor" - # Check for fixes and other changes (patch) - elif echo "$commits" | grep -qE "^(fix|perf|refactor|style|docs|test|build|ci|chore)(\(.+\))?:"; then - bump_type="patch" + CURRENT_VERSION="${{ steps.current_version.outputs.current }}" + BUMP_TYPE="${{ steps.version_bump.outputs.bump_type }}" + + IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION" + MAJOR=${VERSION_PARTS[0]} + MINOR=${VERSION_PARTS[1]} + PATCH=${VERSION_PARTS[2]} + + case $BUMP_TYPE in + "major") + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + ;; + "minor") + MINOR=$((MINOR + 1)) + PATCH=0 + ;; + "patch") + PATCH=$((PATCH + 1)) + ;; + esac + + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT + echo "tag=v${NEW_VERSION}" >> $GITHUB_OUTPUT + + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + pkg.version = '${NEW_VERSION}'; + fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + + - name: Collect commit messages for release + id: commit_messages + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + if [[ -z "$LAST_TAG" ]]; then + COMMITS=$(git log --pretty=format:"- %s" --no-merges) + else + COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s" --no-merges) fi - echo "Determined bump type: $bump_type" - echo "bump_type=$bump_type" >> $GITHUB_OUTPUT + echo "Raw commits:" + echo "$COMMITS" + COMMITS_ESCAPED=$(echo "$COMMITS" | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g') - - name: Bump version - id: version_bump - if: steps.commits.outputs.should_release == 'true' - run: | - bump_type="${{ steps.bump_type.outputs.bump_type }}" + echo "commits<> $GITHUB_OUTPUT + echo "$COMMITS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - # Get current version from package.json - current_version=$(node -p "require('./package.json').version") - echo "Current version: $current_version" + echo "commits_json=$COMMITS_ESCAPED" >> $GITHUB_OUTPUT - # Bump version using npm - npm version $bump_type --no-git-tag-version + - name: Amend commit with version + run: | + NEW_VERSION="${{ steps.bump_version.outputs.new_version }}" + CURRENT_MESSAGE=$(git log -1 --pretty=%B) + NEW_MESSAGE="${CURRENT_MESSAGE} [v${NEW_VERSION}]" - # Get new version - new_version=$(node -p "require('./package.json').version") - echo "New version: $new_version" - echo "new_version=$new_version" >> $GITHUB_OUTPUT + git add package.json + git commit --amend -m "${NEW_MESSAGE}" + git push --force-with-lease origin main - - name: Commit version bump and create tag - if: steps.commits.outputs.should_release == 'true' + - name: Create tag and trigger release run: | - new_version="${{ steps.version_bump.outputs.new_version }}" + TAG="${{ steps.bump_version.outputs.tag }}" + NEW_VERSION="${{ steps.bump_version.outputs.new_version }}" - # Commit the version bump - git add package.json - git commit -m "release: $new_version" + # Create tag with commit messages + COMMIT_MESSAGES="${{ steps.commit_messages.outputs.commits }}" - # Website-related operations (commented out) - # npm run --prefix ./website new-release --release=$new_version - # git add -A . + TAG_MESSAGE=$(printf "Release %s\n\nChanges in this release:\n%s" "${TAG}" "${COMMIT_MESSAGES}") - # Create and push tag - git tag "v$new_version" - git push origin main - git push origin "v$new_version" + git tag -a "${TAG}" -m "${TAG_MESSAGE}" + git push origin "${TAG}" - echo "Created and pushed tag v$new_version" + COMMIT_MESSAGES_JSON="${{ steps.commit_messages.outputs.commits_json }}" + curl -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + https://api.github.com/repos/${{ github.repository }}/dispatches \ + -d "{\"event_type\":\"release\",\"client_payload\":{\"tag\":\"${TAG}\",\"version\":\"${NEW_VERSION}\",\"commits\":\"${COMMIT_MESSAGES_JSON}\"}}" - - name: Summary + - name: Output summary run: | - if [ "${{ steps.commits.outputs.should_release }}" = "true" ]; then - echo "✅ Auto-release completed!" - echo "New version: ${{ steps.version_bump.outputs.new_version }}" - echo "Tag created: v${{ steps.version_bump.outputs.new_version }}" - echo "" - echo "The check and release workflows will now run automatically." - else - echo "â„šī¸ No new commits to release - skipping auto-release" - fi + echo "## 🚀 Auto Release Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Previous Version**: ${{ steps.current_version.outputs.current }}" >> $GITHUB_STEP_SUMMARY + echo "- **New Version**: ${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Bump Type**: ${{ steps.version_bump.outputs.bump_type }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag**: ${{ steps.bump_version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: Amended last commit with version info" >> $GITHUB_STEP_SUMMARY + echo "- **Next Step**: Release workflow triggered via repository dispatch" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 93eb88288..320fe1a0e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -1,9 +1,6 @@ name: check on: - push: - tags: - - v* pull_request: branches: - main @@ -13,94 +10,6 @@ concurrency: cancel-in-progress: true jobs: - version: - name: Ensure package version match - if: startsWith(github.ref_name, 'v') - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check Git tag format - env: - TYPE_GRAPHQL_REF_NAME: ${{ github.ref_name }} - run: | - _tag="$TYPE_GRAPHQL_REF_NAME" - if ! printf "%s\n" "$_tag" | grep -q -P '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.(0|[1-9][0-9]*))?$'; then - printf '[ERROR]: Git tag (%s) wrong format\n' "$_tag" - exit 1 - fi - - - name: Read package.json version - uses: sergeysova/jq-action@v2 - id: version_package - with: - cmd: jq --raw-output .version package.json - - - name: Read GitHub version - uses: pozetroninc/github-action-get-latest-release@master - id: version_v_github - with: - owner: scope3data - repo: type-graphql - excludes: prerelease, draft - - - name: Remove leading v* from GitHub version - id: version_github - env: - TYPE_GRAPHQL_VERSION: ${{ steps.version_v_github.outputs.release }} - run: | - _version="$TYPE_GRAPHQL_VERSION" - printf 'value=%s\n' "${_version#?}" >> "$GITHUB_OUTPUT" - - - name: Read Git tag version - id: version_gittag - env: - TYPE_GRAPHQL_REF_NAME: ${{ github.ref_name }} - run: | - _version="$TYPE_GRAPHQL_REF_NAME" - printf 'value=%s\n' "${_version#?}" >> "$GITHUB_OUTPUT" - - - name: Compare package.json with Git tag - uses: madhead/semver-utils@latest - id: comparison_package_gittag - with: - version: ${{ steps.version_package.outputs.value }} - compare-to: ${{ steps.version_gittag.outputs.value }} - lenient: false - - - name: Compare Git tag with GitHub - uses: madhead/semver-utils@latest - id: comparison_gittag_github - with: - version: ${{ steps.version_gittag.outputs.value }} - compare-to: ${{ steps.version_github.outputs.value }} - lenient: false - - - name: Check package.json == Git tag - env: - TYPE_GRAPHQL_COMPARISON: ${{ steps.comparison_package_gittag.outputs.comparison-result }} - TYPE_GRAPHQL_VERSION_PACKAGE: ${{ steps.version_package.outputs.value }} - TYPE_GRAPHQL_VERSION_TAG: ${{ steps.version_gittag.outputs.value }} - run: | - if [ ! "$TYPE_GRAPHQL_COMPARISON" = "=" ]; then - printf '[ERROR]: package.json (%s) != Git tag (%s)\n' "$TYPE_GRAPHQL_VERSION_PACKAGE" "$TYPE_GRAPHQL_VERSION_TAG" - exit 1 - fi - - - name: Check Git tag > GitHub - env: - TYPE_GRAPHQL_COMPARISON: ${{ steps.comparison_gittag_github.outputs.comparison-result }} - TYPE_GRAPHQL_VERSION_TAG: ${{ steps.version_gittag.outputs.value }} - TYPE_GRAPHQL_VERSION_GITHUB: ${{ steps.version_github.outputs.value }} - run: | - if [ ! "$TYPE_GRAPHQL_COMPARISON" = ">" ]; then - printf '[ERROR]: Git tag (%s) !> GitHub (%s)\n' "$TYPE_GRAPHQL_VERSION_TAG" "$TYPE_GRAPHQL_VERSION_GITHUB" - exit 1 - fi - check: name: Build & Lint & Test runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 485f726b8..3dea29d05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,8 @@ name: release on: - workflow_run: - workflows: - - check - types: - - completed + repository_dispatch: + types: [release] concurrency: group: ${{ github.workflow }} @@ -20,7 +17,6 @@ jobs: release: name: Release package on GitHub Packages runs-on: ubuntu-latest - if: github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.head_branch, 'v') permissions: contents: write id-token: write @@ -29,11 +25,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_branch }} + ref: ${{ github.event.client_payload.tag }} - name: Verify version validation passed env: - TYPE_GRAPHQL_VERSION: ${{ github.event.workflow_run.head_branch }} + TYPE_GRAPHQL_VERSION: ${{ github.event.client_payload.tag }} run: | echo "Verifying release for version: $TYPE_GRAPHQL_VERSION" @@ -48,7 +44,7 @@ jobs: - name: Determine if version is prerelease id: prerelease env: - TYPE_GRAPHQL_VERSION: ${{ github.event.workflow_run.head_branch }} + TYPE_GRAPHQL_VERSION: ${{ github.event.client_payload.tag }} run: | _prerelease= if printf "%s\n" "$TYPE_GRAPHQL_VERSION" | grep -q -P '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'; then @@ -77,21 +73,30 @@ jobs: run: | npm run prepublishOnly env: - TYPE_GRAPHQL_REF: ${{ github.event.workflow_run.head_branch }} + TYPE_GRAPHQL_REF: ${{ github.event.client_payload.tag }} - - name: Build Changelog + - name: Format commit messages for release id: changelog - uses: mikepenz/release-changelog-builder-action@v5 - with: - configuration: "./.github/configs/changelog.json" - failOnError: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + COMMITS="${{ github.event.client_payload.commits }}" + + if [ -z "$COMMITS" ]; then + echo "No commits provided in client_payload" + FORMATTED_COMMITS="No changes listed" + else + FORMATTED_COMMITS=$(echo "$COMMITS" | sed 's/\\n/\n/g') + fi + + RELEASE_BODY=$(printf "## Changes in this release:\n\n%s\n\nReleased as %s" "$FORMATTED_COMMITS" "${{ github.event.client_payload.tag }}") + + echo "changelog<> $GITHUB_OUTPUT + echo "$RELEASE_BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - name: Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.event.workflow_run.head_branch }} + tag_name: ${{ github.event.client_payload.tag }} body: ${{ steps.changelog.outputs.changelog }} prerelease: ${{ steps.prerelease.outputs.value == 'true' }} diff --git a/.github/workflows/validate-tag.yml b/.github/workflows/validate-tag.yml new file mode 100644 index 000000000..e70173b4f --- /dev/null +++ b/.github/workflows/validate-tag.yml @@ -0,0 +1,74 @@ +name: validate-tag + +on: + push: + tags: + - v* + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + name: Validate Tag and Version + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check Git tag format + env: + TAG_NAME: ${{ github.ref_name }} + run: | + if ! printf "%s\n" "$TAG_NAME" | grep -q -P '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(alpha|beta|rc)\.(0|[1-9][0-9]*))?$'; then + printf '[ERROR]: Git tag (%s) has invalid format\n' "$TAG_NAME" + exit 1 + fi + echo "✅ Tag format is valid: $TAG_NAME" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + + - name: Install dependencies + run: npm ci + + - name: Read package.json version + id: package_version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Package version: $VERSION" + + - name: Extract tag version + id: tag_version + env: + TAG_NAME: ${{ github.ref_name }} + run: | + VERSION="${TAG_NAME#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Tag version: $VERSION" + + - name: Compare package.json with Git tag + run: | + PACKAGE_VERSION="${{ steps.package_version.outputs.version }}" + TAG_VERSION="${{ steps.tag_version.outputs.version }}" + + if [ "$PACKAGE_VERSION" != "$TAG_VERSION" ]; then + printf '[ERROR]: package.json version (%s) != Git tag version (%s)\n' "$PACKAGE_VERSION" "$TAG_VERSION" + exit 1 + fi + + echo "✅ Version consistency verified: $PACKAGE_VERSION" + + - name: Summary + run: | + echo "## ✅ Tag Validation Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Tag**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ steps.tag_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Format**: Valid semver" >> $GITHUB_STEP_SUMMARY + echo "- **Consistency**: package.json matches tag" >> $GITHUB_STEP_SUMMARY diff --git a/package.json b/package.json index 5ea053199..41ee1a9b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scope3data/type-graphql", - "version": "2.2.0", + "version": "2.0.0", "private": false, "description": "Create GraphQL schema and resolvers with TypeScript, using classes and decorators!", "keywords": [