diff --git a/.cursor/rules/connect-java-release-process.mdc b/.cursor/rules/connect-java-release-process.mdc index 25d3fe9cf..a7cfa472b 100644 --- a/.cursor/rules/connect-java-release-process.mdc +++ b/.cursor/rules/connect-java-release-process.mdc @@ -4,177 +4,76 @@ description: 'Connect Java plugin release process and versioning workflow' # Connect Java Release Process -This rule documents the complete release workflow for Connect Java plugins across all platforms (Velocity, BungeeCord, Spigot/Paper). - -## Current Release Configuration - -- **Release Branch**: `connect` -- **Current Version**: Check [build.gradle.kts](mdc:build.gradle.kts) line 9 -- **Platforms**: Velocity, BungeeCord, Spigot/Paper -- **Automation**: GitHub Actions via [release.yml](mdc:.github/workflows/release.yml) +Connect Java publishes platform plugins for Velocity, BungeeCord, and +Spigot/Paper. The release branch is `connect`. + +## Stable Releases + +Stable releases are automated with [release-please.yml](mdc:.github/workflows/release-please.yml). + +1. Merge normal changes to `connect`. +2. release-please reads Conventional Commit messages: + - `fix:` -> patch release + - `feat:` -> minor release + - `feat!:` or `BREAKING CHANGE:` -> major release + - `chore:`, `docs:`, `ci:`, `test:` -> no stable release +3. release-please opens a release PR that updates `CHANGELOG.md` and + `.release-please-manifest.json`. +4. The workflow dispatches [pullrequest.yml](mdc:.github/workflows/pullrequest.yml) + on the release PR branch, waits for the Gradle build, then merges the release PR. +5. release-please runs again on `connect`, creates the version tag/release, and + dispatches [release.yml](mdc:.github/workflows/release.yml) on the tag. +6. `release.yml` builds all plugin JARs and uploads them to both the versioned + release and the stable `latest` release. + +Do not manually bump versions or create release tags unless repairing failed +automation. The Gradle build derives the plugin version from git tags via +`gitVersion()`. ## Release Artifacts -Each release produces these platform-specific JARs: - -- **`connect-spigot.jar`** - For Spigot/Paper servers -- **`connect-velocity.jar`** - For Velocity proxy -- **`connect-bungee.jar`** - For BungeeCord proxy - -Built via shadow plugin configuration in [connect.shadow-conventions.gradle.kts](mdc:build-logic/src/main/kotlin/connect.shadow-conventions.gradle.kts). - -## Release Types - -### 1. Versioned Release (Tag-based) +Each release produces: -**Trigger**: Push git tag matching `*.*.*` pattern -**Creates**: +- `connect-spigot.jar` for Spigot/Paper servers +- `connect-velocity.jar` for Velocity proxies +- `connect-bungee.jar` for BungeeCord proxies +- `LICENSE` -- GitHub release with tag name (e.g., `0.7.0`) -- JARs named `connect-{platform}-{version}.jar` -- Updates `latest` release tag -- Standard JARs named `connect-{platform}.jar` +The JARs are built by the shadow plugin configuration in +[connect.shadow-conventions.gradle.kts](mdc:build-logic/src/main/kotlin/connect.shadow-conventions.gradle.kts). -### 2. Pre-release (Branch-based) +## Pre-Releases -**Trigger**: Push to `connect` branch without tag -**Creates**: +Every push to `connect` still updates the `latest-prerelease` GitHub release. +Use those artifacts for staging or urgent production testing before a stable +release is cut. -- GitHub pre-release with `latest-prerelease` tag -- JARs named `connect-{platform}-prerelease.jar` +## Verification -## Step-by-Step Release Process - -### 1. Pre-Release Checks +Before merging code intended for release: ```bash -# Ensure all platforms build successfully ./gradlew build - -# Verify all fixes are working: -# - Velocity: no event loop incompatibilities -# - BungeeCord: no "channel not registered" errors -# - Spigot/Paper: no IllegalAccessException on handshake -``` - -### 2. Version Update - -Edit [build.gradle.kts](mdc:build.gradle.kts): - -```kotlin -allprojects { - group = "com.minekube.connect" - version = "0.7.0" // Update this line - // ... -} -``` - -### 3. Commit and Push Changes - -```bash -git add build.gradle.kts -git commit -m "Release version 0.7.0" -git push origin connect ``` -### 4. Create and Push Tag (Let Workflow Handle Releases) - -The workflow is designed to automatically create releases when tags are pushed. **Do not create releases manually via GitHub UI** - this will conflict with the automated release creation. +After a stable release: ```bash -git tag 0.7.0 -git push origin 0.7.0 +gh -R minekube/connect-java release view --json tagName,targetCommitish,isDraft,isPrerelease,assets +curl -I -L --fail https://github.com/minekube/connect-java/releases/download//connect-velocity.jar +curl -I -L --fail https://github.com/minekube/connect-java/releases/download/latest/connect-velocity.jar ``` -The workflow will automatically create: -1. **Versioned release** (`0.7.0`) with JARs named `connect-{platform}-0.7.0.jar` -2. **Latest release** (`latest`) with standard JAR names - -### 5. Monitor Release - -- Check GitHub Actions workflow completion -- Verify 2 releases created: - 1. **Versioned release** (`0.7.0`) - Created by workflow with versioned JARs - 2. **Latest release** (`latest`) - Updated by workflow with standard JAR names - -## GitHub Actions Workflow - -The [release.yml](mdc:.github/workflows/release.yml) workflow: - -**Environment**: +If the hub uses the new plugin, update the hub dependency pin, rebuild the hub +image, deploy through gitops, and verify a real public Connect join. -- Ubuntu latest -- JDK 17 (Temurin distribution) -- Gradle build action +## Rollback -**Build Process**: - -1. Checkout code -2. Setup Java and Gradle -3. Run `./gradlew build` -4. Copy JARs from `{platform}/build/libs/connect-{platform}.jar` -5. Create releases with appropriate naming - -**Release Logic**: - -- **Tag push**: Creates versioned + latest releases -- **Branch push**: Creates pre-release only - -## Version Numbering - -**Pattern**: `MAJOR.MINOR.PATCH` (semantic versioning) - -- **MAJOR**: Breaking changes to injection or API -- **MINOR**: New platform support, significant features -- **PATCH**: Bug fixes, compatibility updates - -**Recent versions**: 0.6.2 → 0.7.0 (next) - -## Release Verification - -After release, verify: - -1. **GitHub Releases**: All 3 releases created with correct JARs -2. **JAR Integrity**: Download and verify JAR file sizes/contents -3. **Platform Testing**: - - Deploy to test servers - - Verify injection works without errors - - Test player connections through Connect network - -## Rollback Process - -If release has critical issues: +If a release has a critical issue, fix forward with a patch release whenever +possible. If a bad tag/release must be removed: ```bash -# Delete problematic tag -git tag -d 0.7.0 -git push origin :refs/tags/0.7.0 - -# Delete GitHub releases manually via web interface -# Fix issues and re-release with patch version (0.7.1) +git tag -d +git push origin :refs/tags/ +gh -R minekube/connect-java release delete ``` - -## Pre-Release Testing - -For testing unreleased changes: - -1. Push to `connect` branch (without tag) -2. Use JARs from `latest-prerelease` release -3. Test on staging environments -4. When stable, create official release tag - -## Common Release Issues - -- **Build failures**: Check Java/Gradle compatibility in workflow -- **Missing JARs**: Verify shadow plugin configuration -- **Wrong versions**: Ensure [build.gradle.kts](mdc:build.gradle.kts) version updated -- **Tag conflicts**: Delete and recreate tags if needed - -## Workflow Design - -**Why the workflow uses command-line tags:** - -- **Automated release creation**: The workflow creates releases automatically when tags are pushed -- **Multiple release types**: Creates both versioned (`0.7.0`) and latest (`latest`) releases -- **Artifact management**: Handles JAR naming and upload automatically -- **Conflict avoidance**: Manual GitHub UI releases conflict with the automated "Release version" step (lines 82-90) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index f00986825..df264dfd3 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -1,6 +1,8 @@ name: Build Pull Request -on: [pull_request] +on: + pull_request: + workflow_dispatch: jobs: build: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..a0daf68be --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,157 @@ +name: Release Please + +on: + push: + branches: [connect] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + actions: write + checks: write + statuses: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.rp.outputs.release_created }} + tag_name: ${{ steps.rp.outputs.tag_name }} + pr_number: ${{ steps.rp.outputs.pr }} + steps: + - id: rp + uses: googleapis/release-please-action@v5 + with: + config-file: .release-please-config.json + manifest-file: .release-please-manifest.json + + - name: Validate and merge release PR + if: ${{ steps.rp.outputs.pr }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + PR_NUMBER=$(jq -r '.number' <<< '${{ steps.rp.outputs.pr }}') + PR_JSON=$(gh pr view "$PR_NUMBER" \ + --repo "${{ github.repository }}" \ + --json headRefName,headRefOid) + HEAD_REF=$(jq -r '.headRefName' <<< "$PR_JSON") + HEAD_SHA=$(jq -r '.headRefOid' <<< "$PR_JSON") + + echo "Dispatching PR build for release PR #$PR_NUMBER at $HEAD_REF ($HEAD_SHA)" + gh workflow run pullrequest.yml \ + --repo "${{ github.repository }}" \ + --ref "$HEAD_REF" + + RUN_ID="" + for _ in $(seq 1 60); do + RUN_ID=$(gh run list \ + --repo "${{ github.repository }}" \ + --workflow pullrequest.yml \ + --branch "$HEAD_REF" \ + --event workflow_dispatch \ + --json databaseId,headSha \ + --jq "map(select(.headSha == \"$HEAD_SHA\")) | .[0].databaseId // \"\"") + if [ -n "$RUN_ID" ]; then + break + fi + sleep 5 + done + + if [ -z "$RUN_ID" ]; then + echo "::error::Timed out waiting for dispatched PR build to appear" + exit 1 + fi + + echo "Waiting for PR build run $RUN_ID" + STATUS="" + CONCLUSION="" + RUN_URL="" + for _ in $(seq 1 180); do + RUN_JSON=$(gh run view "$RUN_ID" \ + --repo "${{ github.repository }}" \ + --json status,conclusion,url) + STATUS=$(jq -r '.status' <<< "$RUN_JSON") + CONCLUSION=$(jq -r '.conclusion // ""' <<< "$RUN_JSON") + RUN_URL=$(jq -r '.url' <<< "$RUN_JSON") + if [ "$STATUS" = "completed" ]; then + break + fi + sleep 10 + done + + if [ "$STATUS" != "completed" ]; then + echo "::error::Timed out waiting for PR build run $RUN_ID" + exit 1 + fi + + CHECK_CONCLUSION="$CONCLUSION" + if [ "$CHECK_CONCLUSION" = "cancelled" ]; then + CHECK_CONCLUSION="neutral" + fi + + gh api "repos/${{ github.repository }}/check-runs" \ + -f name='build' \ + -f head_sha="$HEAD_SHA" \ + -f status='completed' \ + -f conclusion="$CHECK_CONCLUSION" \ + -f details_url="$RUN_URL" \ + -f output[title]="Release PR build $CONCLUSION" \ + -f output[summary]="Mirrors workflow_dispatch build run $RUN_ID for release PR #$PR_NUMBER: $RUN_URL" + + gh api "repos/${{ github.repository }}/statuses/$HEAD_SHA" \ + -f state="$([ "$CONCLUSION" = "success" ] && echo success || echo failure)" \ + -f context='build' \ + -f target_url="$RUN_URL" \ + -f description="Release PR build $CONCLUSION via workflow_dispatch" + + if [ "$CONCLUSION" != "success" ]; then + echo "::error::Release PR build concluded $CONCLUSION" + exit 1 + fi + + echo "Merging release PR #$PR_NUMBER" + for _ in $(seq 1 30); do + if gh pr merge "$PR_NUMBER" \ + --repo "${{ github.repository }}" \ + --merge; then + break + fi + STATE=$(gh pr view "$PR_NUMBER" \ + --repo "${{ github.repository }}" \ + --json state \ + --jq '.state') + if [ "$STATE" = "MERGED" ]; then + break + fi + sleep 10 + done + + STATE=$(gh pr view "$PR_NUMBER" \ + --repo "${{ github.repository }}" \ + --json state \ + --jq '.state') + if [ "$STATE" != "MERGED" ]; then + echo "::error::Release PR #$PR_NUMBER did not merge" + exit 1 + fi + + gh workflow run release-please.yml \ + --repo "${{ github.repository }}" \ + --ref "${{ github.ref_name }}" + + trigger-release: + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + runs-on: ubuntu-latest + steps: + - name: Dispatch release.yml on the new tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh workflow run release.yml \ + --repo "${{ github.repository }}" \ + --ref "${{ needs.release-please.outputs.tag_name }}" \ + -f release_tag="${{ needs.release-please.outputs.tag_name }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 757eafab4..84fdd1df2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,15 @@ on: # Trigger on GitHub UI release creation release: types: [published] + # release-please creates tags/releases with GITHUB_TOKEN, which does not + # trigger downstream workflows. release-please.yml dispatches this workflow + # on the tag ref so release artifacts are built and uploaded. + workflow_dispatch: + inputs: + release_tag: + description: Existing tag to publish or repair. Empty uses the workflow ref. + required: false + type: string # Trigger on branch push for pre-releases push: branches: [connect] @@ -17,6 +26,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history for git describe + ref: ${{ inputs.release_tag || github.ref }} - name: Set up JDK 17 uses: actions/setup-java@v4 @@ -38,11 +48,26 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Building version: $VERSION" + - name: Resolve release tag + id: release-tag + if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' + run: | + TAG="${{ inputs.release_tag }}" + if [ -z "$TAG" ]; then + TAG="${{ github.event.release.tag_name }}" + fi + if [ -z "$TAG" ]; then + TAG="${GITHUB_REF_NAME}" + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "Release tag: $TAG" + # For release events: upload artifacts to the release - name: Upload Release Artifacts - if: github.event_name == 'release' - uses: softprops/action-gh-release@v1 + if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' + uses: softprops/action-gh-release@v2 with: + tag_name: ${{ steps.release-tag.outputs.tag }} files: | spigot/build/libs/connect-spigot.jar velocity/build/libs/connect-velocity.jar @@ -51,11 +76,11 @@ jobs: # Also update "latest" release with stable file names for download sites - name: Update Latest Release - if: github.event_name == 'release' - uses: softprops/action-gh-release@v1 + if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' + uses: softprops/action-gh-release@v2 with: tag_name: latest - name: "Latest Release (${{ github.ref_name }})" + name: "Latest Release (${{ steps.release-tag.outputs.tag }})" prerelease: false files: | spigot/build/libs/connect-spigot.jar @@ -65,7 +90,7 @@ jobs: body: | Latest stable release. - **Version:** ${{ github.ref_name }} + **Version:** ${{ steps.release-tag.outputs.tag }} Download links (stable URLs): - [connect-spigot.jar](https://github.com/${{ github.repository }}/releases/download/latest/connect-spigot.jar) @@ -83,7 +108,7 @@ jobs: - name: Update Pre-Release if: github.event_name == 'push' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: tag_name: latest-prerelease name: "Latest Pre-Release (${{ steps.version.outputs.version }})" diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 000000000..069272e33 --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "include-component-in-tag": false, + "include-v-in-tag": false, + "packages": { + ".": { + "package-name": "connect-java", + "release-type": "simple", + "changelog-path": "CHANGELOG.md" + } + } +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..98efb1ba2 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.7.9" +} diff --git a/AGENTS.md b/AGENTS.md index aa54180e2..86afc9885 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,10 +13,21 @@ plugin release, hub image rebuild, and production rollout as separate steps. ## Release Flow -- Stable plugin artifacts are published from GitHub releases. +- Stable plugin artifacts are published from GitHub releases managed by + `release-please.yml` on the `connect` branch. +- Use Conventional Commit prefixes to drive releases: + `fix:` for patch, `feat:` for minor, and `feat!:`/`BREAKING CHANGE:` for + major. Non-release prefixes such as `chore:`, `docs:`, `ci:`, and `test:` + should not cut a stable release. +- `release-please.yml` opens and auto-merges a release PR, creates the version + tag/release, then dispatches `release.yml` on that tag so the JAR artifacts + are uploaded. Do not manually bump versions or create release tags unless + repairing automation. - The `release.yml` workflow uploads: `connect-spigot.jar`, `connect-velocity.jar`, `connect-bungee.jar`, and `LICENSE`. +- Pushes to `connect` still update the `latest-prerelease` release for + unreleased testing builds. - After creating a release, verify the release is not draft/prerelease unless intentionally so, and verify the asset digest/availability: