From b94d37875c0f7ac88c15a05cab25b009b9b60149 Mon Sep 17 00:00:00 2001 From: Marco Fanti Date: Sun, 2 Aug 2026 13:21:06 -0400 Subject: [PATCH] build: add release.sh with preflight checks; document script-based releases --- RELEASING.md | 39 ++++++++-------- release.sh | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 19 deletions(-) create mode 100755 release.sh diff --git a/RELEASING.md b/RELEASING.md index 94fad8c..7dd6c9c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,7 +2,7 @@ The build is already wired for Central publishing (the `release` Maven profile adds sources and javadoc jars, GPG signing, and the Central Portal upload). Two one-time setup steps must -be done by a human; after that, releases are a short procedure. +be done by a human; after that, a release is one script run: `./release.sh `. ## One-time setup @@ -42,27 +42,28 @@ first signing of a session. ## Release procedure -```bash -# 1. Start from a green main -git checkout main && git pull && mvn verify - -# 2. Set the release version (drop -SNAPSHOT) in all three POMs -mvn versions:set -DnewVersion=0.1.0 && mvn versions:commit +Run the release script from a clean, up-to-date `main`: -# 3. Commit and tag (via PR per repo convention, or directly if you prefer for releases) -git commit -am "release: 0.1.0" && git tag v0.1.0 +```bash +./release.sh --dry-run 0.1.1 # preflight only: git state, credentials, GPG, mvn verify +./release.sh 0.1.1 # the real thing +``` -# 4. Build, sign and upload to the Central Portal -mvn clean deploy -Prelease +The script does, in order: -# 5. Publish: the artifacts land in https://central.sonatype.com/publishing as a -# validated deployment — press "Publish" there. (To skip the manual press, add -# true to the central-publishing-maven-plugin config.) +1. **Preflight** — verifies you're on a clean `main` in sync with origin, the `v0.1.1` tag + doesn't exist, `~/.m2/settings.xml` has real `central` credentials, GPG can actually + sign (fails early on the pinentry/`GPG_TTY` problem), and `mvn verify` is green. +2. Sets the version in all three POMs, commits `release: 0.1.1`, tags `v0.1.1`. +3. `mvn clean deploy -Prelease` — builds, signs, and uploads to the Central Portal. +4. Bumps to the next snapshot (defaults to the next minor, e.g. `0.2.0-SNAPSHOT`; pass a + second argument to override), commits, and pushes `main` plus the tag. +5. Prints the last manual step: press **Publish** on the validated deployment at + . (To skip the manual press, add + `true` to the central-publishing-maven-plugin config.) -# 6. Bump back to the next snapshot and push -mvn versions:set -DnewVersion=0.2.0-SNAPSHOT && mvn versions:commit -git commit -am "chore: bump to 0.2.0-SNAPSHOT" && git push && git push --tags -``` +If anything fails before the push, nothing has left your machine; the script prints the +two-line local rollback (`git tag -d`, `git reset --hard origin/main`). Artifacts appear on Maven Central (search.maven.org) within an hour of publishing: @@ -70,7 +71,7 @@ Artifacts appear on Maven Central (search.maven.org) within an hour of publishin io.github.marcofanti aauth - 0.1.0 + 0.1.1 ``` diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..68dc7c9 --- /dev/null +++ b/release.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Release this library to Maven Central. +# +# Usage: +# ./release.sh [--dry-run] [next-snapshot-version] +# +# Examples: +# ./release.sh 0.1.1 # releases 0.1.1, bumps back to 0.2.0-SNAPSHOT +# ./release.sh 0.2.0 0.3.0-SNAPSHOT # explicit next development version +# ./release.sh --dry-run 0.1.1 # preflight checks only, no changes +# +# One-time setup (Central Portal token, GPG key) is described in RELEASING.md. +set -euo pipefail + +usage() { + grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//' | head -10 + exit 1 +} + +DRY_RUN=0 +if [[ ${1:-} == "--dry-run" ]]; then + DRY_RUN=1 + shift +fi + +VERSION=${1:-} +[[ -n $VERSION ]] || usage +[[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { + echo "ERROR: release version must be X.Y.Z (got: $VERSION)" >&2 + exit 1 +} + +if [[ $# -ge 2 ]]; then + NEXT=$2 +else + IFS=. read -r major minor _ <<<"$VERSION" + NEXT="$major.$((minor + 1)).0-SNAPSHOT" +fi +[[ $NEXT == *-SNAPSHOT ]] || { + echo "ERROR: next development version must end in -SNAPSHOT (got: $NEXT)" >&2 + exit 1 +} + +say() { printf '\n==> %s\n' "$*"; } + +# --- Preflight checks -------------------------------------------------------- + +say "Preflight: git state" +[[ $(git branch --show-current) == main ]] || { + echo "ERROR: releases must run from main (currently on $(git branch --show-current))" >&2 + exit 1 +} +git diff-index --quiet HEAD || { + echo "ERROR: working tree is not clean" >&2 + exit 1 +} +git fetch -q origin +[[ $(git rev-parse HEAD) == $(git rev-parse origin/main) ]] || { + echo "ERROR: local main is not in sync with origin/main" >&2 + exit 1 +} +if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then + echo "ERROR: tag v$VERSION already exists" >&2 + exit 1 +fi + +say "Preflight: Central Portal credentials" +grep -q 'central' ~/.m2/settings.xml || { + echo "ERROR: no central entry in ~/.m2/settings.xml (see RELEASING.md)" >&2 + exit 1 +} +if grep -q 'PASTE_TOKEN' ~/.m2/settings.xml; then + echo "ERROR: ~/.m2/settings.xml still has placeholder token values" >&2 + exit 1 +fi + +say "Preflight: GPG signing" +if [[ -z ${GPG_TTY:-} ]] && tty -s; then + GPG_TTY=$(tty) + export GPG_TTY +fi +echo release-test | gpg --clearsign >/dev/null || { + echo "ERROR: gpg cannot sign (check pinentry-mac or GPG_TTY; see RELEASING.md)" >&2 + exit 1 +} + +say "Preflight: build is green (mvn verify)" +mvn -q clean verify + +if [[ $DRY_RUN -eq 1 ]]; then + say "Dry run OK. Would release $VERSION, tag v$VERSION, then bump to $NEXT." + exit 0 +fi + +# --- Release ----------------------------------------------------------------- + +rollback_hint() { + cat >&2 </dev/null || true + git reset --hard origin/main +Nothing has been pushed; re-run after fixing the problem. +EOF +} +trap rollback_hint ERR + +say "Setting version $VERSION" +mvn -q versions:set -DnewVersion="$VERSION" && mvn -q versions:commit +git commit -aqm "release: $VERSION" +git tag "v$VERSION" + +say "Building, signing and uploading to the Central Portal" +mvn clean deploy -Prelease + +say "Bumping to $NEXT" +mvn -q versions:set -DnewVersion="$NEXT" && mvn -q versions:commit +git commit -aqm "chore: bump to $NEXT" + +say "Pushing main and tags" +git push origin main +git push origin "v$VERSION" + +trap - ERR +say "Done. Now press 'Publish' on the validated deployment:" +echo " https://central.sonatype.com/publishing" +echo "Artifacts sync to Maven Central within about an hour of publishing."