From a57082484dbde65bae229c7ff01c2c6f64b77ffa Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:08:07 +0000 Subject: [PATCH 1/3] Migrate releases to release-please Adds release-please (googleapis/release-please-action v5) to own the version bump, tag, and GitHub release. The publish workflow gains a workflow_dispatch trigger and is kicked off automatically when release-please cuts a release (GITHUB_TOKEN-created tags don't fire push/tags triggers). The release-drafter workflow is removed; the oasdiff OpenAPI-changelog job is ported into release-please.yml as a post-release job that appends an 'API changes' section and uploads openapi-diff.md as a release asset. Co-Authored-By: Quentin Rousseau --- .github/release-drafter.yml | 55 --------------- .github/workflows/publish.yml | 46 +------------ ...release-drafter.yml => release-please.yml} | 67 ++++++++++++------- .release-please-manifest.json | 3 + build.gradle | 2 +- release-please-config.json | 18 +++++ 6 files changed, 64 insertions(+), 127 deletions(-) delete mode 100644 .github/release-drafter.yml rename .github/workflows/{release-drafter.yml => release-please.yml} (80%) create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json 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..92637d54 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,3 @@ 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 - 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 - 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 diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-please.yml similarity index 80% rename from .github/workflows/release-drafter.yml rename to .github/workflows/release-please.yml index 4bf88203..0493f4e7 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-please.yml @@ -1,24 +1,41 @@ -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 + 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: ${{ steps.release.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: ${{ steps.release.outputs.tag_name }} fetch-depth: 0 - name: Install oasdiff @@ -30,8 +47,10 @@ jobs: tar -xzf /tmp/oasdiff.tar.gz -C /usr/local/bin oasdiff - name: Generate OpenAPI diff since last release + env: + TAG: ${{ steps.release.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 +86,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: ${{ steps.release.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 +123,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 +131,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/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" + } + ] + } + } +} From a36bb39c7ec6c9efc7781dd8acae0f4e1009dc80 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:21:50 +0000 Subject: [PATCH 2/3] Create GitHub release on manual tag pushes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a release job gated on push events that creates the GitHub release when none exists (manual v* tag pushes). The release-please dispatch path is unaffected — this job only runs on push, and the release already exists there. Replaces the removed draft-publish tail job. Co-Authored-By: Quentin Rousseau --- .github/workflows/publish.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 92637d54..6a5649da 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -49,3 +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: + if: github.event_name == 'push' + needs: + - publish + runs-on: blacksmith-2vcpu-ubuntu-2404 + permissions: + contents: write + steps: + - name: Create GitHub release if missing + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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 From 3a01fecd6395ab48f6b0921f475b488fb6fb18fa Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:28:38 +0000 Subject: [PATCH 3/3] Fix cross-job output references; update release docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit steps.release.outputs.* is only valid inside the job that defined them — the openapi-diff job ran 'if' on an empty context, which fails workflow instantiation. The release-please job now maps the outputs and openapi-diff reads them via needs.release-please.outputs.*. Co-Authored-By: Quentin Rousseau --- .github/workflows/release-please.yml | 11 +++--- CLAUDE.md | 52 ++++++++-------------------- PUBLISHING.md | 13 ++++--- 3 files changed, 30 insertions(+), 46 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 0493f4e7..86602102 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -12,6 +12,9 @@ permissions: jobs: 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 @@ -26,7 +29,7 @@ jobs: run: gh workflow run publish.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" openapi-diff: - if: ${{ steps.release.outputs.release_created }} + if: ${{ needs.release-please.outputs.release_created }} needs: release-please runs-on: blacksmith-2vcpu-ubuntu-2404 permissions: @@ -35,7 +38,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ steps.release.outputs.tag_name }} + ref: ${{ needs.release-please.outputs.tag_name }} fetch-depth: 0 - name: Install oasdiff @@ -48,7 +51,7 @@ jobs: - name: Generate OpenAPI diff since last release env: - TAG: ${{ steps.release.outputs.tag_name }} + TAG: ${{ needs.release-please.outputs.tag_name }} run: | 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 @@ -89,7 +92,7 @@ jobs: - name: Inject OpenAPI diff into release body uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - TAG: ${{ steps.release.outputs.tag_name }} + TAG: ${{ needs.release-please.outputs.tag_name }} with: script: | const fs = require('fs'); 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