From d7b1b0578832f198650b3bccfdb13817c233c70a Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Fri, 25 Sep 2026 10:21:48 +0200 Subject: [PATCH 1/3] Standardize release tooling and reviewed version PRs --- .github/workflows/ci-lint.yml | 12 ++-- .github/workflows/ci.yml | 6 +- .github/workflows/release.yml | 12 +++- CONTRIBUTING.md | 6 +- docs/RELEASE.md | 2 +- scripts/release-gate.py | 48 -------------- scripts/verify-published.py | 65 ------------------- tools/release/README.md | 7 ++ .../release/prepare-version.sh | 2 +- tools/release/project.toml | 12 ++++ tools/release/update-version | 4 ++ 11 files changed, 51 insertions(+), 125 deletions(-) delete mode 100644 scripts/release-gate.py delete mode 100644 scripts/verify-published.py create mode 100644 tools/release/README.md rename update_version.sh => tools/release/prepare-version.sh (98%) create mode 100644 tools/release/project.toml create mode 100755 tools/release/update-version diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index db06112..b7f25dc 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -17,9 +17,9 @@ permissions: jobs: maven-policy: name: Shared Maven policy - uses: HauntedMC/HauntedPlatform/.github/workflows/maven-ci.yml@v1.6.8 + uses: HauntedMC/HauntedPlatform/.github/workflows/maven-ci.yml@v2.0.0 with: - maven-command: ./mvnw -U -B -ntp -DskipTests verify + maven-command: ./mvnw -U -B -ntp validate secrets: PACKAGES_USER: ${{ secrets.HAUNTEDMC_PACKAGES_USERNAME }} PACKAGES_TOKEN: ${{ secrets.HAUNTEDMC_PACKAGES_TOKEN }} @@ -34,8 +34,12 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + - name: Install pinned release CLI + env: + GH_TOKEN: ${{ github.token }} + run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf - shell: bash run: | mapfile -d '' scripts < <(git ls-files -z '*.sh') - if (( ${#scripts[@]} > 0 )); then shellcheck "${scripts[@]}"; fi - - run: ./update_version.sh --dry-run major + if (( ${#scripts[@]} > 0 )); then shellcheck tools/release/update-version tools/release/prepare-version.sh "${scripts[@]}"; fi + - run: ./tools/release/update-version --dry-run major diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c75bbe..7f59dfe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,10 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + - name: Install pinned release CLI + env: + GH_TOKEN: ${{ github.token }} + run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf - name: Verify dependency version ownership run: python3 scripts/verify-version-ownership.py @@ -44,7 +48,7 @@ jobs: - name: Verify architecture boundaries run: bash scripts/verify-architecture.sh - name: Verify release bump helper - run: ./update_version.sh --dry-run major + run: ./tools/release/update-version --dry-run major - name: Verify release-profile reactor run: ./mvnw -U -B -ntp -Prelease install - name: Verify external BOM consumer diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3c55ae..3581813 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,12 +25,16 @@ jobs: with: fetch-depth: 0 persist-credentials: false + - name: Install pinned release CLI + env: + GH_TOKEN: ${{ github.token }} + run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf - id: version name: Detect a reviewed version change env: GITHUB_EVENT_BEFORE: ${{ github.event.before }} GITHUB_EVENT_NAME: ${{ github.event_name }} - run: python3 scripts/release-gate.py pom.xml + run: gh haunted-release gate publish: needs: gate @@ -47,6 +51,10 @@ jobs: with: fetch-depth: 0 persist-credentials: false + - name: Install pinned release CLI + env: + GH_TOKEN: ${{ github.token }} + run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf - name: Set up JDK 25 and Maven package credentials uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: @@ -71,7 +79,7 @@ jobs: - name: Publish the verified reactor run: ./mvnw -U -B -ntp -DdeployAtEnd=true -Prelease deploy - name: Resolve every published artifact from a fresh Maven repository - run: python3 scripts/verify-published.py "${{ needs.gate.outputs.version }}" HauntedObservability + run: gh haunted-release verify-published "${{ needs.gate.outputs.version }}" - name: Create the release tag only after publication is resolvable run: gh release create "${{ needs.gate.outputs.tag }}" --target "$GITHUB_SHA" --title "HauntedObservability ${{ needs.gate.outputs.tag }}" --generate-notes - name: Create cross-repository GitHub App token diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a468946..40b4bd5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ Before opening or updating a pull request, run: bash scripts/verify-architecture.sh ./mvnw -U -B -ntp -Prelease install bash scripts/verify-bom-consumer.sh -./update_version.sh --dry-run patch +./tools/release/update-version --dry-run patch ``` If shell scripts changed, run ShellCheck over tracked `*.sh` files. CI repeats the architecture/privacy boundary checks, strict Java 25 build, release-profile sources/Javadocs, tests, external BOM consumer contract, and shared HauntedPlatform Maven policy. @@ -44,10 +44,10 @@ Any change to telemetry attributes, span/log contents, metric dimensions, export Preview a bump without modifying the worktree: ```bash -./update_version.sh --dry-run major +./tools/release/update-version --dry-run major ``` -Run `./update_version.sh patch` (or an intentional minor/major bump) from a clean branch. The helper updates the reactor revision and timestamp and checks all module versions. Commit the changes in a reviewed PR. After merge, CI runs the release gate, publishes and resolves the package, then creates `vX.Y.Z`. +Run `./tools/release/update-version patch` (or an intentional minor/major bump) from a clean branch. The helper updates the reactor revision and timestamp and checks all module versions. Commit the changes in a reviewed PR. After merge, CI runs the release gate, publishes and resolves the package, then creates `vX.Y.Z`. ## Pull request checklist diff --git a/docs/RELEASE.md b/docs/RELEASE.md index d539840..6d6e4e0 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -2,7 +2,7 @@ Observability publishes `haunted-observability-bom` for its own modules. Its optional DataProvider, DataRegistry, and FeatureFramework adapters compile against versions selected here. -Prepare a reviewed PR from a clean worktree with `./update_version.sh patch` (or `minor`/`major` for an intentional API change). The helper updates version metadata and leaves the changes for review. Merge only after the repository's CI passes. Do not create or push a release tag manually. +From clean, current `main`, run `./tools/release/update-version patch --pr` (or `minor`/`major` for an intentional API change). The command checks the project-specific version metadata, commits the changes, and opens a reviewed PR. Without `--pr`, it only prepares a local diff; `--dry-run` changes nothing. Merge after CI passes. Do not create or push a release tag manually. A version change on `main` starts `.github/workflows/release.yml`. The workflow runs the `release` release profiles, deploys the verified Maven reactor with `deployAtEnd`, resolves the published coordinates from an empty Maven repository, and only then creates tag `vX.Y.Z` and a GitHub Release. The release dispatches HauntedPlatform's dependency reconciler, which proposes reviewed downstream PRs only after the package is available. diff --git a/scripts/release-gate.py b/scripts/release-gate.py deleted file mode 100644 index 938f4cc..0000000 --- a/scripts/release-gate.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -"""Decide whether a main-branch commit introduces an unpublished release.""" -import argparse -import os -import re -import subprocess -import xml.etree.ElementTree as ET -from pathlib import Path - -NS = {"m": "http://maven.apache.org/POM/4.0.0"} - - -def version(xml): - root = ET.fromstring(xml) - value = root.findtext("m:properties/m:revision", namespaces=NS) - if value is None: - value = root.findtext("m:version", namespaces=NS) - if not value or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", value): - raise SystemExit(f"Expected a release version in the POM, got {value!r}") - return value - - -parser = argparse.ArgumentParser() -parser.add_argument("pom", nargs="?", default="pom.xml") -parser.add_argument("--tag-prefix", default="") -args = parser.parse_args() -path = Path(args.pom) -current = version(path.read_text()) -tag = f"{args.tag_prefix}v{current}" -before = os.environ.get("GITHUB_EVENT_BEFORE", "") -force = os.environ.get("GITHUB_EVENT_NAME") == "workflow_dispatch" -previous = None -if before and before != "0" * 40: - old = subprocess.run( - ["git", "show", f"{before}:{path.as_posix()}"], - capture_output=True, text=True, check=False, - ) - if old.returncode == 0: - previous = version(old.stdout) -exists = subprocess.run( - ["git", "show-ref", "--verify", "--quiet", f"refs/tags/{tag}"], - check=False, -).returncode == 0 -publish = not exists and (force or previous != current) -print(f"Current: {current}; previous: {previous}; tag: {tag}; publish: {publish}") -if os.environ.get("GITHUB_OUTPUT"): - with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: - output.write(f"version={current}\ntag={tag}\npublish={str(publish).lower()}\n") diff --git a/scripts/verify-published.py b/scripts/verify-published.py deleted file mode 100644 index d2f613d..0000000 --- a/scripts/verify-published.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -"""Resolve every deployed reactor coordinate from an empty Maven repository.""" -import argparse -import subprocess -import tempfile -import time -import xml.etree.ElementTree as ET -from pathlib import Path - -NS = {"m": "http://maven.apache.org/POM/4.0.0"} - -parser = argparse.ArgumentParser() -parser.add_argument("version") -parser.add_argument("repository") -parser.add_argument("--module", help="Verify only one independently released module") -parser.add_argument("--list", action="store_true", help="Print expected coordinates without network access") -args = parser.parse_args() -root = Path(".") -pom_paths = [root / args.module / "pom.xml"] if args.module else [root / "pom.xml"] -if not args.module: - parent = ET.parse(root / "pom.xml").getroot() - pom_paths += [root / name.text / "pom.xml" for name in parent.findall("m:modules/m:module", NS)] - -coordinates = [] -for path in pom_paths: - pom = ET.parse(path).getroot() - artifact = pom.findtext("m:artifactId", namespaces=NS) - if artifact.endswith("-platform-acceptance") or artifact == "serverfeatures-testkit": - continue - group = pom.findtext("m:groupId", namespaces=NS) or pom.findtext("m:parent/m:groupId", namespaces=NS) - packaging = pom.findtext("m:packaging", default="jar", namespaces=NS) - coordinates.append(f"{group}:{artifact}:{args.version}:{packaging}") - -if not coordinates: - raise SystemExit("No release coordinates found") -if args.list: - print("\n".join(coordinates)) - raise SystemExit(0) -with tempfile.TemporaryDirectory(prefix="haunted-published-m2-") as temporary: - local_repo = Path(temporary) / "repository" - consumer_pom = Path(temporary) / "pom.xml" - consumer_pom.write_text( - '' - '4.0.0' - 'nl.hauntedmc.verification' - 'published-resolution1' - '' - ) - for coordinate in coordinates: - command = [ - "./mvnw" if Path("mvnw").is_file() else "mvn", "-B", "-ntp", "-U", - "-f", str(consumer_pom), - f"-Dmaven.repo.local={local_repo}", - "org.apache.maven.plugins:maven-dependency-plugin:3.11.0:get", - f"-Dartifact={coordinate}", "-Dtransitive=false", - f"-DremoteRepositories=github::default::https://maven.pkg.github.com/HauntedMC/{args.repository}", - ] - for attempt in range(6): - result = subprocess.run(command, check=False) - if result.returncode == 0: - break - if attempt == 5: - raise SystemExit(f"Published artifact could not be resolved: {coordinate}") - time.sleep(10) - print(f"Resolved {coordinate}", flush=True) diff --git a/tools/release/README.md b/tools/release/README.md new file mode 100644 index 0000000..0c4c09a --- /dev/null +++ b/tools/release/README.md @@ -0,0 +1,7 @@ +# Version updates + +Install the pinned shared CLI once with `gh extension install HauntedMC/gh-haunted-release --pin v1.0.0`. This folder keeps the project-specific version adapter and its configuration. + +From clean, current `main`, run `./tools/release/update-version patch --pr` to prepare, commit, push, and open a reviewed PR. Omit `--pr` to prepare only a local diff; add `--dry-run` to inspect the next version without edits. The tool never merges, publishes, or tags. + +Enabled repositories use their pull-request CI as the merge gate. diff --git a/update_version.sh b/tools/release/prepare-version.sh similarity index 98% rename from update_version.sh rename to tools/release/prepare-version.sh index 9612a85..f52ef04 100755 --- a/update_version.sh +++ b/tools/release/prepare-version.sh @@ -18,7 +18,7 @@ readonly MODULES=( die() { echo "Error: $*" >&2; exit 1; } usage() { cat >&2 <<'USAGE' -Usage: ./update_version.sh [--dry-run] +Usage: ./tools/release/update-version [--dry-run] Bumps HauntedObservability's reactor revision and reproducible-build timestamp, then leaves the changed files for review in a pull request. diff --git a/tools/release/project.toml b/tools/release/project.toml new file mode 100644 index 0000000..182b3a7 --- /dev/null +++ b/tools/release/project.toml @@ -0,0 +1,12 @@ +[project] +name = "HauntedObservability" +repository = "HauntedMC/HauntedObservability" +tool_version = "1.0.0" +mode = "bump" +prepare = "tools/release/prepare-version.sh" +verify = ["./mvnw", "-B", "-ntp", "verify"] +verify_before_pr = false + +[components.default] +pom = "pom.xml" +tag_prefix = "v" diff --git a/tools/release/update-version b/tools/release/update-version new file mode 100755 index 0000000..7ba2eb3 --- /dev/null +++ b/tools/release/update-version @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" +exec gh haunted-release version "$@" From ac55f58f17be28d131da62eca3db7e48cb35ba1f Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Fri, 25 Sep 2026 10:32:08 +0200 Subject: [PATCH 2/3] Use consumer-safe shared Maven workflow --- .github/workflows/ci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index b7f25dc..6f00a97 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -17,7 +17,7 @@ permissions: jobs: maven-policy: name: Shared Maven policy - uses: HauntedMC/HauntedPlatform/.github/workflows/maven-ci.yml@v2.0.0 + uses: HauntedMC/HauntedPlatform/.github/workflows/maven-ci.yml@v1.6.8 with: maven-command: ./mvnw -U -B -ntp validate secrets: From b8bfbe1dd4b17690e4bb68d019066151da529976 Mon Sep 17 00:00:00 2001 From: Remy Duijsens Date: Fri, 25 Sep 2026 10:35:59 +0200 Subject: [PATCH 3/3] Pin corrected release CLI v1.0.1 --- .github/workflows/ci-lint.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 ++-- tools/release/README.md | 2 +- tools/release/project.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 6f00a97..8156b26 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -37,7 +37,7 @@ jobs: - name: Install pinned release CLI env: GH_TOKEN: ${{ github.token }} - run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf + run: gh extension install HauntedMC/gh-haunted-release --pin ed440a0a2d5ab23af30c36bb22132f8ffa0ea33f - shell: bash run: | mapfile -d '' scripts < <(git ls-files -z '*.sh') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f59dfe..c39c638 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Install pinned release CLI env: GH_TOKEN: ${{ github.token }} - run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf + run: gh extension install HauntedMC/gh-haunted-release --pin ed440a0a2d5ab23af30c36bb22132f8ffa0ea33f - name: Verify dependency version ownership run: python3 scripts/verify-version-ownership.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3581813..fbb5fef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - name: Install pinned release CLI env: GH_TOKEN: ${{ github.token }} - run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf + run: gh extension install HauntedMC/gh-haunted-release --pin ed440a0a2d5ab23af30c36bb22132f8ffa0ea33f - id: version name: Detect a reviewed version change env: @@ -54,7 +54,7 @@ jobs: - name: Install pinned release CLI env: GH_TOKEN: ${{ github.token }} - run: gh extension install HauntedMC/gh-haunted-release --pin 86bed2fdf7438ea3d6cc3b76f5c7cc36a58a46bf + run: gh extension install HauntedMC/gh-haunted-release --pin ed440a0a2d5ab23af30c36bb22132f8ffa0ea33f - name: Set up JDK 25 and Maven package credentials uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: diff --git a/tools/release/README.md b/tools/release/README.md index 0c4c09a..398a70b 100644 --- a/tools/release/README.md +++ b/tools/release/README.md @@ -1,6 +1,6 @@ # Version updates -Install the pinned shared CLI once with `gh extension install HauntedMC/gh-haunted-release --pin v1.0.0`. This folder keeps the project-specific version adapter and its configuration. +Install the pinned shared CLI once with `gh extension install HauntedMC/gh-haunted-release --pin v1.0.1`. This folder keeps the project-specific version adapter and its configuration. From clean, current `main`, run `./tools/release/update-version patch --pr` to prepare, commit, push, and open a reviewed PR. Omit `--pr` to prepare only a local diff; add `--dry-run` to inspect the next version without edits. The tool never merges, publishes, or tags. diff --git a/tools/release/project.toml b/tools/release/project.toml index 182b3a7..c35e27a 100644 --- a/tools/release/project.toml +++ b/tools/release/project.toml @@ -1,7 +1,7 @@ [project] name = "HauntedObservability" repository = "HauntedMC/HauntedObservability" -tool_version = "1.0.0" +tool_version = "1.0.1" mode = "bump" prepare = "tools/release/prepare-version.sh" verify = ["./mvnw", "-B", "-ntp", "verify"]