diff --git a/.github/RELEASE.md b/.github/RELEASE.md new file mode 100644 index 0000000..fc1c55a --- /dev/null +++ b/.github/RELEASE.md @@ -0,0 +1,88 @@ +# Release flow + +Short guide for cutting a release. + +## Branching model (GitHub Flow) + +One long-lived branch — `master` (or `main`, whichever the repo is on). +Feature work lives on `feature/**`, `task/**`, `fix/**` branches that are +merged back via pull requests. `develop` is kept for staging pre-release +work before a tag. + +- `master` — always deployable, tagged on every release +- `develop` — integration branch for the next release +- `feature/*`, `fix/*`, `task/*` — short-lived, merged into `develop` (or + straight to `master` for hotfixes) through a PR + +Both `master` and `develop` are protected by the [Build workflow](.github/workflows/build.yml) +— every push and pull request runs `./gradlew build shadowJar` and publishes +the JAR as an artifact, plus a JUnit test report. + +## Cutting a release + +1. **Bump the version.** Edit `build.gradle`: + ```gradle + version '1.19.0' + ``` + Commit with a message like `chore(release): 1.19.0`. The [Release workflow](.github/workflows/release.yml) + compares the tag against this field and aborts if they disagree. + +2. **Merge `develop` → `master`** via PR (or push directly if you are the + sole maintainer). + +3. **Create a release on GitHub.** Either through the web UI or the CLI: + ```bash + gh release create 1.19.0 \ + --title "AbstractMenus 1.19.0" \ + --notes-file CHANGELOG.md + ``` + Tag format: either `1.19.0` or `v1.19.0` — the workflow strips the + leading `v` before comparing. + +4. **Release workflow fires automatically** (`on: release: types: + [published]`), rebuilds the shaded JAR from the tagged commit, verifies + the version matches, and uploads `AbstractMenus-1.19.0.jar` as a release + asset. + +5. Done. The release page now has the JAR; users pull it straight from + there. + +## Re-attaching a JAR to an existing release + +If the build failed or the asset was deleted, trigger the Release workflow +manually: + +- GitHub UI → Actions → Release → Run workflow → supply the existing tag + (e.g. `1.18.0`). +- Or via CLI: `gh workflow run release.yml -f tag=1.18.0`. + +The workflow will rebuild from that tag's commit and re-upload. If an +asset with the same name already exists, `softprops/action-gh-release` +overwrites it. + +## Hotfix flow + +For a patch release that doesn't touch `develop`: + +1. Branch from `master`: `git checkout -b fix/bad-null-deref master` +2. Fix, PR, merge. +3. Bump `version` on `master`: `1.19.0` → `1.19.1`. Commit. +4. Cut the GitHub release as in the main flow. The workflow picks up the + new version automatically. +5. Cherry-pick the fix commit back to `develop` so it isn't lost. + +## Pre-releases + +GitHub supports a "Set as a pre-release" checkbox on the release form. +That marks the tag as a pre-release (e.g. `1.19.0-rc1`, `1.19.0-beta.2`) +— the Release workflow still builds and attaches the JAR, users just see +the pre-release badge on the release page. + +For a pre-release version bump, use Gradle-compatible syntax: + +```gradle +version '1.19.0-rc1' +``` + +The workflow's version-match check strips only a leading `v`, so +`1.19.0-rc1` and `v1.19.0-rc1` both work. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b7c52f0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,62 @@ +name: Build + +on: + push: + branches: + - master + - main + - develop + - 'feature/**' + - 'task/**' + - 'fix/**' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: Build + test on JDK 21 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + + - name: Build + tests + run: ./gradlew --no-daemon build shadowJar + + - name: Publish test report + if: always() + uses: mikepenz/action-junit-report@v5 + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + fail_on_failure: true + require_tests: false + + - name: Resolve JAR path + id: jar + run: echo "path=$(ls build/libs/AbstractMenus-*.jar | head -n1)" >> "$GITHUB_OUTPUT" + + - name: Upload shaded JAR + uses: actions/upload-artifact@v4 + with: + name: AbstractMenus-jar + path: ${{ steps.jar.outputs.path }} + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0a09a3b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Existing release tag to attach a rebuilt JAR to' + required: true + type: string + +permissions: + contents: write + +jobs: + attach-jar: + name: Build + attach JAR to release + runs-on: ubuntu-latest + steps: + - name: Checkout (release tag) + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name || inputs.tag }} + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Verify tag matches project version + run: | + TAG="${{ github.event.release.tag_name || inputs.tag }}" + # Strip a leading 'v' if present (v1.18.0 → 1.18.0) + TAG_VERSION="${TAG#v}" + GRADLE_VERSION=$(grep -E "^version\s+['\"]" build.gradle | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/") + echo "release tag: $TAG" + echo "tag version: $TAG_VERSION" + echo "gradle version: $GRADLE_VERSION" + if [ "$TAG_VERSION" != "$GRADLE_VERSION" ]; then + echo "::error::Release tag version ($TAG_VERSION) does not match build.gradle version ($GRADLE_VERSION). Bump build.gradle before tagging, or retag." + exit 1 + fi + + - name: Build shaded JAR (skip tests — already run in Build workflow on the tagged commit) + run: ./gradlew --no-daemon shadowJar + + - name: Resolve JAR path + id: jar + run: | + JAR=$(ls build/libs/AbstractMenus-*.jar | head -n1) + echo "path=$JAR" >> "$GITHUB_OUTPUT" + echo "name=$(basename "$JAR")" >> "$GITHUB_OUTPUT" + + - name: Attach JAR to release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.release.tag_name || inputs.tag }} + files: ${{ steps.jar.outputs.path }} + fail_on_unmatched_files: true