name: Build Gutenberg Plugin Zip

on:
    pull_request:
    push:
        branches:
            - trunk
            - 'release/**'
            - 'wp/**'
    workflow_dispatch:
        inputs:
            version:
                description: 'rc or stable?'
                required: true

# Cancels all previous workflow runs for pull requests that have not completed.
concurrency:
    # The concurrency group contains the workflow name and the branch name for pull requests
    # or the commit hash for any other events.
    group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
    cancel-in-progress: true

jobs:
    compute-stable-branches:
        name: Compute current and next stable release branches
        runs-on: ubuntu-latest
        if: ${{ github.event_name == 'workflow_dispatch' }}
        outputs:
            current_stable_branch: ${{ steps.get_branches.outputs.current_stable_branch }}
            next_stable_branch: ${{ steps.get_branches.outputs.next_stable_branch }}

        steps:
            - name: Get current and next stable release branches
              id: get_branches
              run: |
                  curl \
                    -H "Accept: application/vnd.github.v3+json" \
                    -o latest.json \
                    "https://api.github.com/repos/${{ github.repository }}/releases/latest"
                  LATEST_STABLE_TAG=$(jq --raw-output '.tag_name' latest.json)
                  IFS='.' read LATEST_STABLE_MAJOR LATEST_STABLE_MINOR LATEST_STABLE_PATCH <<< "${LATEST_STABLE_TAG#v}"
                  echo "current_stable_branch=release/${LATEST_STABLE_MAJOR}.${LATEST_STABLE_MINOR}" >> $GITHUB_OUTPUT
                  if [[ ${LATEST_STABLE_MINOR} == "9" ]]; then
                    echo "next_stable_branch=release/$((LATEST_STABLE_MAJOR + 1)).0" >> $GITHUB_OUTPUT
                  else
                    echo "next_stable_branch=release/${LATEST_STABLE_MAJOR}.$((LATEST_STABLE_MINOR + 1))" >> $GITHUB_OUTPUT
                  fi

    bump-version:
        name: Bump version
        runs-on: ubuntu-latest
        needs: compute-stable-branches
        if: |
            github.event_name == 'workflow_dispatch' && (
              (
                github.ref == 'refs/heads/trunk' ||
                endsWith( github.ref, needs.compute-stable-branches.outputs.next_stable_branch )
              ) && (
                github.event.inputs.version == 'rc' ||
                github.event.inputs.version == 'stable'
              ) || (
                startsWith( github.ref, 'refs/heads/release/' ) &&
                github.event.inputs.version == 'stable'
              )
            )
        outputs:
            old_version: ${{ steps.get_version.outputs.old_version }}
            new_version: ${{ steps.get_version.outputs.new_version }}
            release_branch: ${{ steps.get_version.outputs.release_branch }}
            release_branch_commit: ${{ steps.commit_version_bump_to_release_branch.outputs.version_bump_commit }}
            trunk_commit: ${{ steps.commit_version_bump_to_trunk.outputs.version_bump_commit }}

        steps:
            - name: Checkout code
              uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
              with:
                  token: ${{ secrets.GUTENBERG_TOKEN }}
                  show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}

            - name: Compute old and new version
              id: get_version
              run: |
                  OLD_VERSION=$(jq --raw-output '.version' package.json)
                  echo "old_version=${OLD_VERSION}" >> $GITHUB_OUTPUT
                  if [[ ${{ github.event.inputs.version }} == 'stable' ]]; then
                    NEW_VERSION=$(npx semver $OLD_VERSION -i patch)
                  else
                    if [[ $OLD_VERSION == *"rc"* ]]; then
                      NEW_VERSION=$(npx semver $OLD_VERSION -i prerelease)
                    else
                      # WordPress version guidelines: If minor is 9, bump major instead.
                      IFS='.' read -r -a OLD_VERSION_ARRAY <<< "$OLD_VERSION"
                      if [[ ${OLD_VERSION_ARRAY[1]} == "9" ]]; then
                        NEW_VERSION="$(npx semver $OLD_VERSION -i major)-rc.1"
                      else
                        NEW_VERSION="$(npx semver $OLD_VERSION -i minor)-rc.1"
                      fi
                    fi
                  fi
                  echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT
                  IFS='.' read -r -a NEW_VERSION_ARRAY <<< "$NEW_VERSION"
                  RELEASE_BRANCH="release/${NEW_VERSION_ARRAY[0]}.${NEW_VERSION_ARRAY[1]}"
                  echo "release_branch=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT

            - name: Configure git user name and email
              run: |
                  git config user.name "Gutenberg Repository Automation"
                  git config user.email gutenberg@wordpress.org

            - name: Create and switch to release branch
              if: |
                  github.event.inputs.version == 'rc' &&
                  ! contains( steps.get_version.outputs.old_version, 'rc' )
              run: git checkout -b "${{ steps.get_version.outputs.release_branch }}"

            - name: Switch to release branch
              if: |
                  github.event.inputs.version == 'stable' ||
                  contains( steps.get_version.outputs.old_version, 'rc' )
              run: |
                  git fetch --depth=1 origin "${{ steps.get_version.outputs.release_branch }}"
                  git checkout "${{ steps.get_version.outputs.release_branch }}"

            - name: Update plugin version
              env:
                  VERSION: ${{ steps.get_version.outputs.new_version }}
              run: |
                  cat <<< $(jq --tab --arg version "${VERSION}" '.version = $version' package.json) > package.json
                  cat <<< $(jq --tab --arg version "${VERSION}" '.version = $version | .packages[""].version = $version' package-lock.json) > package-lock.json
                  sed -i "s/${{ steps.get_version.outputs.old_version }}/${VERSION}/g" gutenberg.php

            - name: Commit the version bump to the release branch
              id: commit_version_bump_to_release_branch
              run: |
                  git add gutenberg.php package.json package-lock.json
                  git commit -m "Bump plugin version to ${{ steps.get_version.outputs.new_version }}"
                  git push --set-upstream origin "${{ steps.get_version.outputs.release_branch }}"
                  echo "version_bump_commit=$(git rev-parse --verify --short HEAD)" >> $GITHUB_OUTPUT

            - name: Fetch trunk
              if: ${{ github.ref != 'refs/heads/trunk' }}
              run: git fetch --depth=1 origin trunk

            - name: Cherry-pick the version bump commit to trunk
              id: commit_version_bump_to_trunk
              run: |
                  git checkout trunk
                  git pull
                  TRUNK_VERSION=$(jq --raw-output '.version' package.json)
                  if [[ ${{ steps.get_version.outputs.old_version }} == "$TRUNK_VERSION" ]]; then
                    git cherry-pick "${{ steps.get_version.outputs.release_branch }}"
                    git push
                    echo "version_bump_commit=$(git rev-parse --verify --short HEAD)" >> $GITHUB_OUTPUT
                  fi

    build:
        name: Build Release Artifact
        runs-on: ubuntu-latest
        needs: bump-version
        if: |
            always() && (
              github.event_name == 'pull_request' ||
              github.event_name == 'workflow_dispatch' ||
              github.repository == 'WordPress/gutenberg'
            )
        outputs:
            job_status: ${{ job.status }}

        steps:
            - name: Checkout code
              uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
              with:
                  ref: ${{ needs.bump-version.outputs.release_branch || github.ref }}
                  show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}

            - name: Use desired version of Node.js
              uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
              with:
                  node-version-file: '.nvmrc'
                  check-latest: true
                  cache: npm

            - name: Build Gutenberg plugin ZIP file
              run: ./bin/build-plugin-zip.sh
              env:
                  NO_CHECKS: 'true'

            - name: Upload artifact
              uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
              with:
                  name: gutenberg-plugin
                  path: ./gutenberg.zip

            - name: Build release notes draft
              if: ${{ needs.bump-version.outputs.new_version }}
              env:
                  VERSION: ${{ needs.bump-version.outputs.new_version }}
              run: |
                  IFS='.' read -r -a VERSION_ARRAY <<< "${VERSION}"
                  MILESTONE="Gutenberg ${VERSION_ARRAY[0]}.${VERSION_ARRAY[1]}"
                  npm run other:changelog -- --milestone="$MILESTONE" --unreleased > release-notes.txt
                  sed -ie '1,6d' release-notes.txt
                  if [[ ${{ needs.bump-version.outputs.new_version }} != *"rc"* ]]; then
                    # Include previous RCs' release notes, if any
                    CHANGELOG_REGEX="=\s[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?\s="
                    RC_REGEX="=\s${VERSION}(-rc\.[0-9]+)?\s="
                    awk "/${RC_REGEX}/ {found=1;print;next} /${CHANGELOG_REGEX}/ {found=0} found" changelog.txt >> release-notes.txt
                  fi

            - name: Upload release notes artifact
              if: ${{ needs.bump-version.outputs.new_version }}
              uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
              with:
                  name: release-notes
                  path: ./release-notes.txt

    revert-version-bump:
        name: Revert version bump if build failed
        needs: [bump-version, build]
        runs-on: ubuntu-latest
        if: |
            always() &&
            ( needs.build.outputs.job_status == 'failure' ) &&
            needs.bump-version.outputs.release_branch_commit

        steps:
            - name: Checkout code
              uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
              with:
                  fetch-depth: 2
                  ref: ${{ needs.bump-version.outputs.release_branch }}
                  token: ${{ secrets.GUTENBERG_TOKEN }}
                  show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}

            - name: Configure git user name and email
              run: |
                  git config user.name "Gutenberg Repository Automation"
                  git config user.email gutenberg@wordpress.org

            - name: Revert version bump commit on release branch
              if: |
                  github.event.inputs.version == 'stable' ||
                  contains( needs.bump-version.outputs.old_version, 'rc' )
              run: |
                  git revert --no-edit ${{ needs.bump-version.outputs.release_branch_commit }}
                  git push --set-upstream origin "${{ needs.bump-version.outputs.release_branch }}"

            - name: Delete release branch if it was only just created for the RC
              if: |
                  github.event.inputs.version == 'rc' &&
                  ! contains( needs.bump-version.outputs.old_version, 'rc' )
              run: |
                  git push origin :"${{ needs.bump-version.outputs.release_branch }}"

            - name: Revert version bump on trunk
              if: ${{ needs.bump-version.outputs.trunk_commit }}
              run: |
                  git fetch --depth=2 origin trunk
                  git checkout trunk
                  git revert --no-edit ${{ needs.bump-version.outputs.trunk_commit }}
                  git push --set-upstream origin trunk

    create-release:
        name: Create Release Draft and Attach Asset
        needs: [bump-version, build]
        runs-on: ubuntu-latest

        steps:
            - name: Set Release Version
              id: get_release_version
              env:
                  VERSION: ${{ needs.bump-version.outputs.new_version }}
              run: echo "version=$(echo $VERSION | cut -d / -f 3 | sed 's/-rc./ RC/' )" >> $GITHUB_OUTPUT

            - name: Download Plugin Zip Artifact
              uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
              with:
                  name: gutenberg-plugin

            - name: Download Release Notes Artifact
              uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
              with:
                  name: release-notes

            - name: Create Release Draft
              id: create_release
              uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4
              env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              with:
                  tag_name: 'v${{ needs.bump-version.outputs.new_version }}'
                  release_name: ${{ steps.get_release_version.outputs.version }}
                  commitish: ${{ needs.bump-version.outputs.release_branch || github.ref }}
                  draft: true
                  prerelease: ${{ contains(needs.bump-version.outputs.new_version, 'rc') }}
                  body_path: release-notes.txt

            - name: Upload Release Asset
              id: upload-release-asset
              uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2
              env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              with:
                  upload_url: ${{ steps.create_release.outputs.upload_url }}
                  asset_path: ./gutenberg.zip
                  asset_name: gutenberg.zip
                  asset_content_type: application/zip

    npm-publish:
        name: Publish WordPress packages to npm
        runs-on: ubuntu-latest
        environment: WordPress packages
        needs: [bump-version, build]
        if: ${{ endsWith( needs.bump-version.outputs.new_version, '-rc.1' ) }}
        steps:
            - name: Checkout (for CLI)
              uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
              with:
                  path: main
                  ref: trunk
                  show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}

            - name: Checkout (for publishing)
              uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
              with:
                  path: publish
                  # Later, we switch this branch in the script that publishes packages.
                  ref: trunk
                  token: ${{ secrets.GUTENBERG_TOKEN }}
                  show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}

            - name: Configure git user name and email (for publishing)
              run: |
                  cd publish
                  git config user.name "Gutenberg Repository Automation"
                  git config user.email gutenberg@wordpress.org

            - name: Setup Node.js
              uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
              with:
                  node-version-file: 'main/.nvmrc'
                  registry-url: 'https://registry.npmjs.org'
                  check-latest: true

            - name: Publish packages to npm ("latest" dist-tag)
              run: |
                  cd main
                  npm ci
                  ./bin/plugin/cli.js npm-latest --semver minor --ci --repository-path ../publish
              env:
                  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}