Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 50 additions & 151 deletions .cursor/rules/connect-java-release-process.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -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 <version> --json tagName,targetCommitish,isDraft,isPrerelease,assets
curl -I -L --fail https://github.com/minekube/connect-java/releases/download/<version>/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 <version>
git push origin :refs/tags/<version>
gh -R minekube/connect-java release delete <version>
```

## 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)
4 changes: 3 additions & 1 deletion .github/workflows/pullrequest.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Build Pull Request

on: [pull_request]
on:
pull_request:
workflow_dispatch:

jobs:
build:
Expand Down
157 changes: 157 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -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 }}"
Loading
Loading