diff --git a/.github/ISSUE_TEMPLATE/release-template-new.md b/.github/ISSUE_TEMPLATE/release-template-new.md new file mode 100644 index 0000000000..f8aee2e23b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-template-new.md @@ -0,0 +1,68 @@ +--- +name: Release template [NEW] +about: Internally used for new releases +title: Release wp-parsely x.y.z +labels: 'Type: Maintenance' +--- + +This is an issue for tracking the next `wp-parsely` release. This ticket is to be opened the week before the actual release, so we have enough time to complete all the related tasks. + +The actual release of the plugin should be done on Mondays so we can catch the Tuesday WordPress VIP release window. + +## Before releasing + +**1. Merge all outstanding work** +- [ ] Merge any outstanding PRs due for this release to the target branch (usually `develop`). +- [ ] Verify that all important PRs have an appropriate `Changelog` tag. PRs without a `Changelog` tag won't be added to the changelog. + +**2. Conduct additional testing** +We've got automated testing in place and also test under our local development environment during development. For impactful releases we should also: +- [ ] Conduct an additional [smoke test](https://github.com/Parsely/wp-parsely/blob/develop/docs/TESTING.md#manual-smoke-test) under our local development environment. +- [ ] Test under a regular non-local WordPress installation. +- [ ] Test under a real WordPress VIP environment. + +**3. Communicate** +- [ ] Inform Parse.ly support of the upcoming release. + +The following additional tasks might be needed depending on the release and its impact: +- [ ] Write any needed internal documentation. +- [ ] Write an internal P2 post about the release (to be posted immediately so folks are aware of the release ahead of time). +- [ ] Write a WordPress VIP Lobby post about the release (to be posted immediately to preannounce next week's VIP release - don't forget to get someone to proofread!). +- [ ] Prepare any public documentation (to be posted after the WordPress.org release). + +## Release process + +**1. Update version numbers and changelog** +- [ ] [Run the Bump wp-parsely version](https://github.com/Parsely/wp-parsely/actions/workflows/bump-version.yml) GitHub Action to update the version numbers in the plugin files. Use the branch you want to release from (usually `develop`). +- [ ] Verify that the generated PR looks correct. You can amend it with new commits if needed. +- [ ] Merge the PR into the target branch (usually `develop`). + +**2. Merge develop into trunk** +- [ ] [Create a PR](https://github.com/Parsely/wp-parsely/compare/trunk...develop?quick_pull=1&title=Release+wp-parsely+x.y.z&body=This+PR+merges+the+`develop`+branch+into+the+`trunk`+branch+in+order+to+release+wp-parsely+x.y.z.) that merges the target branch (usually `develop`) into `trunk`, named _Release wp-parsely x.y.z_. +- [ ] Merge the PR into `trunk`. + +**3. Create Release and Deploy to WordPress.org** +- [ ] Check if the `develop` and `trunk` branches built successfully. You can check it in the [GitHub Actions](https://github.com/Parsely/wp-parsely/actions/workflows/build-plugin.yml) tab. +- [ ] [Run the Release wp-parsely](https://github.com/Parsely/wp-parsely/actions/workflows/release-plugin.yml) GitHub Action, on the `trunk-built` branch, inputting the new version number, and without selecting Dry run. +- [ ] Check the action logs for any errors. If there are any, fix them and rerun the action. +- [ ] Check the new release on the [GitHub releases page](https://github.com/Parsely/wp-parsely/releases) and verify that it is correct. +- [ ] Verify that the release was successful by checking the [WordPress.org plugin page](https://wordpress.org/plugins/wp-parsely/). + +## After releasing + +**1. Communicate** +- [ ] If needed, update the public documentation. +- [ ] Inform the concerned Slack channels about the new release, also preannouncing the WordPress VIP release. + +**2. Merge trunk back into develop** +- [ ] [Create a PR](https://github.com/Parsely/wp-parsely/compare/develop...trunk?quick_pull=1&title=Merge+trunk+into+develop+after+the+wp-parsely+x.y.z+release&body=This+PR+merges+the+`trunk`+branch+into+the+`develop`+branch+after+the+release+of+wp-parsely+x.y.z.) that merges `trunk` into `develop`, named _Merge trunk into develop after the wp-parsely x.y.z release_. +- [ ] Merge the PR into `develop`. + +**3. Manage milestones** +- [ ] Close the current milestone. +- [ ] If needed, open a new milestone for the next release. + +**4. Release to other platforms** +- [ ] Update the `vip-go-mu-plugins` submodule to the latest version. +- [ ] Release the plugin for WordPress VIP. +- [ ] Release the plugin for WordPress.com. diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml index dcd8f7f963..83475581c2 100644 --- a/.github/workflows/build-plugin.yml +++ b/.github/workflows/build-plugin.yml @@ -81,7 +81,6 @@ jobs: git commit -F final_commit_message.txt --no-verify git push origin "${BUILT_BRANCH}" - - name: Clean up commit message files run: | rm final_commit_message.txt diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index b172da887b..bf394541e7 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -1,4 +1,5 @@ name: Bump wp-parsely version +run-name: Bump wp-parsely version to ${{ github.event.inputs.new_version }} on: workflow_dispatch: @@ -8,12 +9,115 @@ on: required: true type: string +env: + NEW_VERSION: ${{ github.event.inputs.new_version }} + jobs: - display_version: - name: Display New Version + validate_version: + name: Validate the new version runs-on: ubuntu-latest + outputs: + current_version: ${{ steps.get_current_version.outputs.current_version }} steps: - - name: Output the new version + - name: Checks if the new version is valid + run: | + if [[ ! ${{ env.NEW_VERSION }} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid version format. Please use the format x.y.z" + exit 1 + fi + + - name: Setup PHP 8.1 + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: mbstring, json + + - name: Checkout ${{ github.ref_name }} branch + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + + - name: Get the current version + id: get_current_version run: | - echo "New version: ${{ github.event.inputs.new_version }}" + CURRENT_VERSION=$(grep -E "^ \* Version:" wp-parsely.php | awk '{print $3}') + echo "Current version is $CURRENT_VERSION" + echo "New version is ${{ env.NEW_VERSION }}" + + echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV + + - name: Check if the new version is greater than the current version + run: | + php -r ' + $current = "${{ env.CURRENT_VERSION }}"; + $new = "${{ env.NEW_VERSION }}"; + if ( version_compare( $new, $current, "==" ) ) { + echo "The new version (${new}) is the same as the current version (${current}).\n"; + exit( 1 ); + } + if ( ! version_compare( $new, $current, ">" ) ) { + echo "The new version (${new}) must be greater than the current version (${current}).\n"; + exit( 1 ); + } + echo "The new version is greater.\n"; + ' + + run_release_php_script: + name: Bump the version and create the PR + needs: validate_version + runs-on: ubuntu-latest + env: + CURRENT_VERSION: ${{ needs.validate_version.outputs.current_version }} + GH_TOKEN: ${{ github.token }} + steps: + - name: Setup PHP 8.1 + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: mbstring, json + + - name: Configure Git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Checkout ${{ github.ref_name }} branch + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + + - name: Install Composer dependencies + run: composer install --optimize-autoloader --classmap-authoritative + + - name: Run bin/release.php script + run: | + echo "Running 'php bin/release.php ${{ env.CURRENT_VERSION }} ${{ env.NEW_VERSION }}'" + echo "n" | php bin/release.php ${{ env.CURRENT_VERSION }} ${{ env.NEW_VERSION }} + if [ $? -ne 0 ]; then + echo "Failed to run the release script" + exit 1 + fi + git push --set-upstream origin update/wp-parsely-version-to-${{ env.NEW_VERSION }} + + - name: Format the version changelog + run: | + PARSELY_RELEASE_LOG=$(printf '%s' "$PARSELY_RELEASE_LOG" | sed 's/###/##/g') + echo $PARSELY_RELEASE_LOG + # Write the multiline variable to $GITHUB_ENV using the correct syntax + echo "PARSELY_RELEASE_LOG<> $GITHUB_ENV + echo "$PARSELY_RELEASE_LOG" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Create PR + run: | + gh pr create \ + --title "Update version number and changelog for ${{ env.NEW_VERSION }} release" \ + --body "This PR updates the plugin's version number and changelog in preparation for the ${{ env.NEW_VERSION }} release. + + $PARSELY_RELEASE_LOG" \ + --base ${{ github.ref_name }} \ + --head update/wp-parsely-version-to-${{ env.NEW_VERSION }} \ + --assignee ${{ github.actor }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 891dbe2c27..28a17af660 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -6,6 +6,7 @@ on: push: branches: - trunk + workflow_call: # Cancels all previous workflow runs for pull requests that have not completed. concurrency: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index e236f444b3..0c21415df7 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -7,6 +7,7 @@ on: paths-ignore: - '**.md' pull_request: + workflow_call: # Cancels all previous workflow runs for the same branch that have not yet completed. concurrency: diff --git a/.github/workflows/release-plugin.yml b/.github/workflows/release-plugin.yml index fa276c8a1c..54297a580f 100644 --- a/.github/workflows/release-plugin.yml +++ b/.github/workflows/release-plugin.yml @@ -1,33 +1,232 @@ name: Release wp-parsely - +run-name: Release wp-parsely ${{ github.event.inputs.version }}${{ github.event.inputs.dry_run && ' (dry-run)' || '' }} on: workflow_dispatch: inputs: branch: - description: 'Branch to release' + description: 'Built branch to release' required: false - default: 'trunk' + default: 'trunk-built' + version: + description: 'Version to release (eg. 3.16.0)' + required: true skip_tests: description: 'Skip tests' required: false type: boolean default: false dry_run: - description: 'Dry run deployment' + description: 'Dry-run the release' required: false type: boolean default: true - version: - description: 'Version to release' - required: true + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +env: + SOURCE_REF: ${{ github.event.inputs.branch }} + DRY_RUN: ${{ github.event.inputs.dry_run }} + SKIP_TESTS: ${{ github.event.inputs.skip_tests }} + VERSION: ${{ github.event.inputs.version }} jobs: - output_inputs: + validate_version: + name: Validate Version and Branch runs-on: ubuntu-latest steps: - name: Output Inputs run: | - echo "Branch: ${{ github.event.inputs.branch }}" - echo "Skip Tests: ${{ github.event.inputs.skip_tests }}" - echo "Dry Run: ${{ github.event.inputs.dry_run }}" - echo "Version: ${{ github.event.inputs.version }}" + echo "Branch: ${{ env.SOURCE_REF }}" + echo "Dry Run: ${{ env.DRY_RUN }}" + echo "Skip Tests: ${{ env.SKIP_TESTS }}" + echo "Version: ${{ env.VERSION }}" + + - name: Check if version is semver + run: | + if [[ ! "${{ env.VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Version `${{ env.VERSION }}` is not a valid semver version." + exit 1 + fi + + - name: Checkout the specific branch/ref + uses: actions/checkout@v4 + with: + ref: ${{ env.SOURCE_REF }} + fetch-depth: 0 + + - name: Validate if the branch is built by checking if the vendor/autoload.php file exists + run: | + if [[ ! -f vendor/autoload.php ]]; then + echo "The branch '${{ env.SOURCE_REF }}' is not a built branch." + exit 1 + fi + + - name: Check if the version matches the version in wp-parsely.php and package.json + if: ${{ env.DRY_RUN == false }} + run: | + PHP_VERSION=$(grep -E "^ \* Version:" wp-parsely.php | awk '{print $3}') + JSON_VERSION=$(jq -r '.version' package.json) + if [[ "${{ env.VERSION }}" != "${PHP_VERSION}" ]]; then + echo "Version '${{ env.VERSION }}' does not match the version in wp-parsely.php." + echo "Did you mean '${PHP_VERSION}'?" + exit 1 + fi + if [[ "${{ env.VERSION }}" != "${JSON_VERSION}" ]]; then + echo "Version '${{ env.VERSION }}' does not match the version in package.json." + echo "Did you mean '${JSON_VERSION}'?" + exit 1 + fi + + - name: Check if the version is in the CHANGELOG.md file + run: | + if ! grep -q "## \[${{ env.VERSION }}\]" CHANGELOG.md; then + echo "Version '${{ env.VERSION }}' is not in the CHANGELOG.md file." + exit 1 + fi + + - name: Check if the version was already released + if: ${{ env.DRY_RUN == false }} + run: | + if git tag --list | grep -q "${{ env.VERSION }}"; then + echo "Version '${{ env.VERSION }}' has already been released." + exit 1 + fi + + integration_tests: + name: Integration Tests + needs: validate_version + if: ${{ github.event.inputs.skip_tests == 'false' }} + uses: ./.github/workflows/integration-tests.yml + + e2e_tests: + name: End-to-end Tests + needs: validate_version + if: ${{ github.event.inputs.skip_tests == 'false' }} + uses: ./.github/workflows/e2e-tests.yml + + tag_and_release: + name: Tag and Release + needs: [ validate_version, integration_tests, e2e_tests ] + if: ${{ !failure() && !cancelled() }} + runs-on: ubuntu-latest + outputs: + tag_name: ${{ steps.tag.outputs.tag_name }} + steps: + - name: Configure Git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Checkout the specific branch/ref + uses: actions/checkout@v4 + with: + ref: ${{ env.SOURCE_REF }} + fetch-depth: 0 + + - name: Tag the release + id: tag + run: | + TAG_NAME="${{ env.VERSION }}" + if ${{ env.DRY_RUN }}; then + TAG_NAME="dry-run-${{ env.VERSION }}" + # If the tag already exists, delete the tag + if git tag --list | grep -q "${TAG_NAME}"; then + git tag -d "${TAG_NAME}" + fi + fi + + # Set the tag name as an output + echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV + echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_OUTPUT + + git tag "${TAG_NAME}" + git push origin "${TAG_NAME}" + + echo "Tagged release with tag '${TAG_NAME}'" + + - name: Print changelog + run: | + cat CHANGELOG.md + + - name: Extract Changelog + id: extract_changelog + run: | + set -e + VERSION=${{ env.VERSION }} + START_LINE=$(grep -n "## \[${VERSION}\]" CHANGELOG.md | cut -d: -f1) + if [ -z "$START_LINE" ]; then + echo "Version not found in CHANGELOG.md" >&2 + exit 1 + fi + TAIL_LINE=$(tail -n +$((START_LINE + 1)) CHANGELOG.md | grep -n "^## " | head -n 1 | cut -d: -f1 || true) + if [ -z "$TAIL_LINE" ]; then + END_LINE=$(wc -l < CHANGELOG.md) + else + END_LINE=$((START_LINE + TAIL_LINE - 1)) + fi + sed -n "${START_LINE},${END_LINE}p" CHANGELOG.md | sed '$d' > release_notes.md + cat release_notes.md + shell: bash + + - name: Format Changelog + id: format_changelog + run: | + sed -i '1d' release_notes.md # Remove the first line + sed -i 's/###/##/g' release_notes.md # Change headers from ### to ## + shell: bash + + - name: Create a GitHub release + id: github_release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.tag.outputs.tag_name }} + name: ${{ env.VERSION }} + bodyFile: ./release_notes.md + draft: true + prerelease: false + allowUpdates: true + + deploy: + name: Deploy to WordPress.org + needs: tag_and_release + if: ${{ !failure() && !cancelled() }} + runs-on: ubuntu-20.04 + steps: + - name: Deploy Details + run: | + echo "Tag Name: ${{ needs.tag_and_release.outputs.tag_name }}" + echo "Dry Run: ${{ env.DRY_RUN }}" + echo "Skip Tests: ${{ env.SKIP_TESTS }}" + echo "Version: ${{ env.VERSION }}" + + - uses: actions/checkout@v4 + with: + ref: ${{ needs.tag_and_release.outputs.tag_name }} + + - name: WordPress Plugin Deploy + id: wporg_deploy + uses: 10up/action-wordpress-plugin-deploy@stable + with: + dry-run: ${{ env.DRY_RUN }} + generate-zip: true + env: + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} + VERSION: ${{ env.VERSION }} + + - name: Update release with ZIP file + uses: ncipollo/release-action@v1 + with: + tag: ${{ needs.tag_and_release.outputs.tag_name }} + allowUpdates: true + omitBodyDuringUpdate: true + omitNameDuringUpdate: true + omitDraftDuringUpdate: true + omitPrereleaseDuringUpdate: true + removeArtifacts: true + updateOnlyUnreleased: true + draft: ${{ env.DRY_RUN }} + artifacts: ${{ steps.wporg_deploy.outputs.zip-path }} + artifactContentType: application/zip diff --git a/bin/release.php b/bin/release.php index 750820ad58..d42cd66030 100644 --- a/bin/release.php +++ b/bin/release.php @@ -53,6 +53,20 @@ $confirmation = trim( (string) fgets( STDIN ) ); if ( $confirmation === 'y' ) { create_pull_request( $milestone_to, $release_log ); +} else { + // Save the changelog to a environment variable for GitHub Actions. + $github_env = getenv( 'GITHUB_ENV' ); + if ( false !== $github_env ) { + $env_file = fopen($github_env, 'a'); + if ( false === $env_file ) { + echo 'Error: Failed opening the environment file'; + exit( 1 ); + } + fwrite( $env_file, "PARSELY_RELEASE_LOG<