diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml deleted file mode 100644 index 0f882c4e..00000000 --- a/.github/release-drafter.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Configuration for Release Drafter: https://github.com/release-drafter/release-drafter -name-template: v$RESOLVED_VERSION -tag-template: v$RESOLVED_VERSION - -template: | - $CHANGES - - ## OpenAPI spec - - - -categories: - - type: version-resolver - semver-increment: major - when: - label: breaking - - type: version-resolver - semver-increment: minor - when: - label: enhancement - - type: version-resolver - semver-increment: patch - - title: 🚀 New features and improvements - labels: - - enhancement - - title: 🐛 Bug fixes - labels: - - bug - - title: 📝 Documentation updates - labels: - - documentation - - title: 👻 Maintenance - labels: - - chore - - maintenance - - title: 🚦 Tests - labels: - - test - - title: ✍ Other changes - - title: 📦 Dependency updates - labels: - - dependencies - collapse-after: 5 - - title: 🔐 Security - labels: - - "🔐 security" - -exclude-labels: - - skip-changelog - - invalid - -autolabeler: - - label: documentation - files: - - "*.md" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3a2d1c7c..6a5649da 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: push: tags: - 'v*' + workflow_dispatch: jobs: publish: @@ -48,48 +49,19 @@ jobs: run: mvn --batch-mode deploy -DaltDeploymentRepository=github::default::https://maven.pkg.github.com/${{ github.repository }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + release: - needs: publish + if: github.event_name == 'push' + needs: + - publish runs-on: blacksmith-2vcpu-ubuntu-2404 - timeout-minutes: 5 permissions: contents: write steps: - - name: Checkout Repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Fetch annotated tag message - run: git fetch --force origin "refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}" - - name: Publish GitHub Release + - name: Create GitHub release if missing env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - TAG_REF="refs/tags/${GITHUB_REF_NAME}" - HIGHLIGHTS="" - if [ "$(git cat-file -t "$TAG_REF" 2>/dev/null || true)" = "tag" ]; then - HIGHLIGHTS=$(git for-each-ref "$TAG_REF" --format='%(contents)' | sed '/^-----BEGIN PGP SIGNATURE-----/,$d') - if [ -z "$(printf '%s' "$HIGHLIGHTS" | tr -d '[:space:]')" ]; then - HIGHLIGHTS="" - fi - fi - - RELEASE_ID=$(gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" --jq 'map(select(.draft))[0].id // empty') - if [ -n "$RELEASE_ID" ]; then - BODY=$(gh api "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" --jq '.body // ""') - if [ -n "$HIGHLIGHTS" ]; then - BODY=$(printf '## Highlights\n\n%s\n\n%s' "$HIGHLIGHTS" "$BODY") - fi - jq -n \ - --arg tag_name "$GITHUB_REF_NAME" \ - --arg name "$GITHUB_REF_NAME" \ - --arg body "$BODY" \ - '{tag_name: $tag_name, name: $name, body: $body, draft: false, make_latest: "true"}' \ - > /tmp/release.json - gh api --method PATCH "repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}" --input /tmp/release.json - elif [ -n "$HIGHLIGHTS" ]; then - gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes \ - --title "$GITHUB_REF_NAME" --latest \ - --notes "$(printf '## Highlights\n\n%s' "$HIGHLIGHTS")" - else - gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes \ - --title "$GITHUB_REF_NAME" --latest - fi + TAG: ${{ github.ref_name }} + # Manual `git push origin v*` tags have no release yet; release-please + # dispatch never reaches this job (it only runs on push). + run: gh release view "$TAG" > /dev/null 2>&1 || gh release create "$TAG" --generate-notes --verify-tag diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-please.yml similarity index 78% rename from .github/workflows/release-drafter.yml rename to .github/workflows/release-please.yml index 4bf88203..86602102 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-please.yml @@ -1,24 +1,44 @@ -name: Release Drafter +name: Release Please on: push: - branches: - - master - workflow_dispatch: {} + branches: [master] permissions: - contents: read + actions: write + contents: write + pull-requests: write jobs: - update_release_draft: + release-please: + runs-on: blacksmith-2vcpu-ubuntu-2404 + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Trigger publish workflow + if: ${{ steps.release.outputs.release_created }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.release.outputs.tag_name }} + run: gh workflow run publish.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" + + openapi-diff: + if: ${{ needs.release-please.outputs.release_created }} + needs: release-please + runs-on: blacksmith-2vcpu-ubuntu-2404 permissions: contents: write - pull-requests: write - runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - name: Checkout Repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + ref: ${{ needs.release-please.outputs.tag_name }} fetch-depth: 0 - name: Install oasdiff @@ -30,8 +50,10 @@ jobs: tar -xzf /tmp/oasdiff.tar.gz -C /usr/local/bin oasdiff - name: Generate OpenAPI diff since last release + env: + TAG: ${{ needs.release-please.outputs.tag_name }} run: | - LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | grep -v "^${GITHUB_REF_NAME}$" | head -1) + LATEST_TAG=$(git tag -l 'v*' --sort=-v:refname | grep -v "^${TAG}$" | head -1) if [ -n "$LATEST_TAG" ] && git show "${LATEST_TAG}:api/openapi.yaml" > /tmp/base-openapi.yaml 2>/dev/null; then CHANGELOG=$(oasdiff changelog --format markdown /tmp/base-openapi.yaml api/openapi.yaml || true) if [ -z "$CHANGELOG" ] || echo "$CHANGELOG" | grep -q "No changes detected"; then @@ -67,27 +89,29 @@ jobs: echo "$SUMMARY" > /tmp/openapi-summary.txt echo "${CHANGELOG:-}" > /tmp/openapi-diff.md - - id: release_drafter - uses: release-drafter/release-drafter@978bb0b30c5ee8cdd332ced253758157150d43f0 # v7.10.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Inject OpenAPI diff into release body uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + TAG: ${{ needs.release-please.outputs.tag_name }} with: script: | const fs = require('fs'); - const releaseId = ${{ steps.release_drafter.outputs.id }}; const hasChanges = '${{ env.OPENAPI_HAS_CHANGES }}' === 'true'; const summary = fs.readFileSync('/tmp/openapi-summary.txt', 'utf8').trim(); + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: process.env.TAG, + }); + let inlineContent = summary; if (hasChanges) { const { data: assets } = await github.rest.repos.listReleaseAssets({ owner: context.repo.owner, repo: context.repo.repo, - release_id: releaseId, + release_id: release.id, }); const existing = assets.find(a => a.name === 'openapi-diff.md'); if (existing) { @@ -102,7 +126,7 @@ jobs: const { data: asset } = await github.rest.repos.uploadReleaseAsset({ owner: context.repo.owner, repo: context.repo.repo, - release_id: releaseId, + release_id: release.id, name: 'openapi-diff.md', data: diff, }); @@ -110,22 +134,16 @@ jobs: inlineContent = summary.replace('', asset.browser_download_url); } - const { data: release } = await github.rest.repos.getRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: releaseId, - }); - const bodyLimit = 125000; - let newBody = release.body.replace('', inlineContent); + let newBody = (release.body || '') + '\n\n## API changes\n\n' + inlineContent; if (newBody.length > bodyLimit) { const fallback = inlineContent.replace(/
[\s\S]*<\/details>/, '_Breaking and potentially breaking changes omitted — see attached `openapi-diff.md` for details._'); - newBody = release.body.replace('', fallback); + newBody = (release.body || '') + '\n\n## API changes\n\n' + fallback; } await github.rest.repos.updateRelease({ owner: context.repo.owner, repo: context.repo.repo, - release_id: releaseId, + release_id: release.id, body: newBody, }); diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..d4f6f299 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "3.0.0" +} diff --git a/CLAUDE.md b/CLAUDE.md index 9fe0fa78..cd67ae8e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -160,7 +160,8 @@ The project uses **Spotless** with **Google Java Format (AOSP style)**: GitHub Actions workflows: - **test.yml** - Runs tests on all branches using JDK 17 - **maven.yml** - Maven-based build pipeline -- **publish.yml** - Publishes artifacts to Maven Central and GitHub Packages, then publishes the most recent Release Drafter draft under the pushed tag (triggered on tag push) +- **release-please.yml** - Opens release PRs on merge to master; merging one tags the release and dispatches publish.yml +- **publish.yml** - Publishes artifacts to Maven Central and GitHub Packages and creates the GitHub release (dispatched by release-please, or triggered on a manual tag push) ## Publishing & Releases @@ -168,38 +169,17 @@ See [PUBLISHING.md](PUBLISHING.md) for detailed instructions on publishing relea ### Release Process -The release process uses Makefile targets to automate version bumping and tagging: +Releases are driven by **Release Please** on every merge to `master` +(conventional commits determine the bump). Merging the release PR tags +`vX.Y.Z`, creates the GitHub release, and dispatches `publish.yml` to push +artifacts to Maven Central and GitHub Packages. -```bash -# Check current version -make version - -# Bump version (updates pom.xml and build.gradle, creates tag) -make bump-patch # 0.0.1 -> 0.0.2 -make bump-minor # 0.0.1 -> 0.1.0 -make bump-major # 0.0.1 -> 1.0.0 - -# Push the tag to trigger automated workflows -make push-tag -``` +**Manual fallback (full release):** -Review the Release Drafter draft and its OpenAPI diff before tagging. Label merged pull requests `breaking` for major versions and `enhancement` for minor versions. The tag publishes the most recent draft regardless of its current tag, renames it to the pushed tag, and prepends the annotated tag message under “Highlights”. For custom highlights, use an annotated tag such as `git tag -a vX.Y.Z -m ""`. - -**Automated workflow (triggered by `make push-tag`):** -1. `make bump-patch` (or minor/major) - Updates version in both pom.xml and build.gradle, commits, and creates tag locally -2. `make push-tag` - Pushes the tag to GitHub, which automatically triggers **publish.yml**: - - Runs tests - - Signs artifacts with GPG - - Publishes to Maven Central - - Publishes to GitHub Packages - - Publishes the most recent Release Drafter draft, renamed to the pushed tag, with the annotated tag message under “Highlights”; or creates a release with GitHub-generated notes if no draft exists (only if all publishing succeeds) - -**One-command releases:** ```bash -# These combine bump + push-tag (fully automated!) -make release-patch # Bump patch and push tag → triggers publish + release -make release-minor # Bump minor and push tag → triggers publish + release -make release-major # Bump major and push tag → triggers publish + release +make release-patch # Bump patch, tag, push → publish + release +make release-minor +make release-major ``` **What happens automatically:** @@ -207,18 +187,14 @@ make release-major # Bump major and push tag → triggers publish + release - Signs artifacts with GPG - Deploys to Maven Central Portal (auto-publishes) - Deploys to GitHub Packages -- Publishes the most recent Release Drafter draft, renamed to the pushed tag, with the annotated tag message under “Highlights”; or creates a release with GitHub-generated notes if no draft exists +- Creates a GitHub release with generated notes (the OpenAPI diff job appends + the API changelog to it) - Available in Maven Central within 30 minutes -**No manual steps needed!** Just run `make release-patch` and everything is automated. - ### Version Management -Version is tracked in two files (automatically updated by Makefile): -- `pom.xml` - Line 7: `0.0.1` -- `build.gradle` - Line 7: `version = '0.0.1'` - -Git tags follow semver format: `v0.0.1`, `v0.1.0`, `v1.0.0` +The version is tracked in `pom.xml` and `build.gradle` (release-please bumps +both via `extra-files`). Git tags follow semver: `v0.0.1`, `v0.1.0`. ## Important Notes diff --git a/PUBLISHING.md b/PUBLISHING.md index c22bbb33..a9b3ee09 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -83,7 +83,12 @@ rm gpg-key.txt ## Publishing Process -The publish workflow runs automatically when you push a version tag. Release Drafter maintains a draft with merged pull request notes and an OpenAPI diff; review the draft before tagging. Use the `breaking` and `enhancement` labels for major and minor version resolution. The tag publishes the most recent draft regardless of its current tag, renames it to the pushed tag, and prepends the annotated tag message under “Highlights”. Use an annotated tag such as `git tag -a vX.Y.Z -m ""` when providing release highlights. +Releases are driven by **Release Please**: every merge to `master` updates a +release PR (conventional commits determine the version bump). Merging the +release PR tags `vX.Y.Z`, creates the GitHub release, and dispatches +`publish.yml`. The OpenAPI diff job appends the API changelog to the release. + +The manual tag-push path below still works as a fallback. ### Step 1: Update Version and Create Tag @@ -108,7 +113,7 @@ make push-tag 3. ✅ Deploy to Maven Central Portal 4. ✅ Auto-publish to Maven Central (no manual UI steps needed!) 5. ✅ Deploy to GitHub Packages -6. ✅ Publish the most recent Release Drafter draft, renamed to the pushed tag, with the annotated tag message under “Highlights”; or create a release with GitHub-generated notes if no draft exists +6. ✅ Create a GitHub release with generated notes if none exists ### One-Command Release (Recommended) @@ -125,11 +130,11 @@ The **publish.yml** workflow runs automatically on tag push and performs these s 1. Runs all tests 2. Publishes to Maven Central (with GPG signing) 3. Publishes to GitHub Packages -4. Publishes the most recent Release Drafter draft, renamed to the pushed tag, with the annotated tag message under “Highlights”; or creates a release with generated notes if no draft exists (only if steps 1-3 succeed) +4. Creates a GitHub release with generated notes if none exists (only if steps 1-3 succeed); the OpenAPI diff job appends the API changelog Check the Actions tab: https://github.com/rootlyhq/rootly-java/actions -**Important:** The Release Drafter draft is published only after successful publishing to both registries. +**Important:** The GitHub release is created only after successful publishing to both registries. ### Step 4: Verify Publication diff --git a/build.gradle b/build.gradle index b1cc1871..ccfeaba0 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'java' apply plugin: 'com.diffplug.spotless' group = 'com.rootly.client' -version = '3.0.0' +version = '3.0.0' // x-release-please-version buildscript { repositories { diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..0c84e52c --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "java", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "include-component-in-tag": false, + "skip-changelog": true, + "extra-files": [ + { + "type": "generic", + "path": "build.gradle" + } + ] + } + } +}