From acbf11b09034e0168a03949712a43d19d31ef096 Mon Sep 17 00:00:00 2001 From: Shane Utt Date: Wed, 26 Aug 2026 20:55:58 -0400 Subject: [PATCH 1/2] ci(release): add draft pre-release, publish on maintainer publish Signed-off-by: Shane Utt --- .github/workflows/release.yaml | 93 +++++++++++++++++++++++++++------- Makefile | 13 ++++- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 29becba..858ba97 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -4,15 +4,30 @@ name: Release # Workflow Settings # ------------------------------------------------------------------------------ # -# Tag-driven release pipeline: validate the tag against the workspace -# version, run the full test suite, verify crates package cleanly -# (publish dry run - crates.io publishing stays manual), publish the -# container image to GHCR, and create the GitHub release. +# Tag-driven release pipeline in two phases: +# +# Phase 1 (version tag push): validate the tag against the workspace +# version, run the full test suite, verify crates package cleanly +# (publish dry run), then cut a DRAFT pre-release. Nothing is pushed to +# any registry yet. +# +# Phase 2 (a maintainer publishes the draft): publishing the draft -- +# whether left as a pre-release or promoted to a full release -- fires +# the `release: published` event, which builds and pushes the container +# image to GHCR and publishes the crates to crates.io. crates.io +# publishing is a real publish when the RUST_CRATES_PUBLISH_TOKEN secret +# is configured, and a dry run otherwise. +# +# In other words, cutting the final release stays in human hands: the tag +# push only prepares a reviewable draft, and no artifacts are emitted until +# a maintainer publishes it. on: push: tags: - "v[0-9]+.[0-9]+.[0-9]+" + release: + types: [published] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -25,10 +40,11 @@ env: jobs: # ---------------------------------------------------------------------------- - # Validate the tag matches Cargo.toml + # Phase 1 (tag push): validate the tag matches Cargo.toml # ---------------------------------------------------------------------------- validate: + if: github.event_name == 'push' runs-on: ubuntu-24.04 timeout-minutes: 45 permissions: @@ -54,10 +70,11 @@ jobs: echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT" # ---------------------------------------------------------------------------- - # Run the full test suite + # Phase 1 (tag push): run the full test suite # ---------------------------------------------------------------------------- test: + if: github.event_name == 'push' needs: [validate] runs-on: ubuntu-24.04 timeout-minutes: 45 @@ -75,10 +92,11 @@ jobs: run: make test # ---------------------------------------------------------------------------- - # Verify crates package cleanly (publish dry run) + # Phase 1 (tag push): verify crates package cleanly (publish dry run) # ---------------------------------------------------------------------------- publish-dry-run: + if: github.event_name == 'push' needs: [validate] runs-on: ubuntu-24.04 timeout-minutes: 45 @@ -98,13 +116,37 @@ jobs: run: make publish-dry-run # ---------------------------------------------------------------------------- - # Build and publish container image + # Phase 1 (tag push): cut the draft pre-release + # + # A maintainer must review and publish this draft to trigger Phase 2. # ---------------------------------------------------------------------------- - container: + draft-release: + if: github.event_name == 'push' needs: [validate, test, publish-dry-run] runs-on: ubuntu-24.04 timeout-minutes: 45 + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Create draft pre-release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: gh release create "$TAG" --generate-notes --prerelease --draft + + # ---------------------------------------------------------------------------- + # Phase 2 (draft published): build and publish the container image + # ---------------------------------------------------------------------------- + + container: + if: github.event_name == 'release' + runs-on: ubuntu-24.04 + timeout-minutes: 45 permissions: contents: read packages: write @@ -112,6 +154,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + ref: ${{ github.event.release.tag_name }} - name: Build and publish container uses: ./.github/actions/ghcr-publish @@ -119,22 +162,38 @@ jobs: image-name: ${{ github.repository }} # ---------------------------------------------------------------------------- - # Create GitHub release + # Phase 2 (draft published): publish crates to crates.io + # + # Real publish when RUST_CRATES_PUBLISH_TOKEN is configured, dry run + # otherwise. # ---------------------------------------------------------------------------- - github-release: - needs: [validate, container] + crates-publish: + if: github.event_name == 'release' + needs: [container] runs-on: ubuntu-24.04 timeout-minutes: 45 permissions: - contents: write + contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false + ref: ${{ github.event.release.tag_name }} + + - name: Set up Rust + uses: ./.github/actions/setup-rust + with: + cache-suffix: publish - - name: Generate release notes + - name: Publish crates env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ github.ref_name }} - run: gh release create "$TAG" --generate-notes + CARGO_REGISTRY_TOKEN: ${{ secrets.RUST_CRATES_PUBLISH_TOKEN }} + run: | + if [ -n "$CARGO_REGISTRY_TOKEN" ]; then + echo "RUST_CRATES_PUBLISH_TOKEN detected; publishing to crates.io" + make publish + else + echo "RUST_CRATES_PUBLISH_TOKEN not set; performing publish dry run" + make publish-dry-run + fi diff --git a/Makefile b/Makefile index 3e5de27..6038f3f 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ ifneq ($(V),) endif .PHONY: all build release check clean \ - test mutants lint lint-extra fmt doc audit semver publish-dry-run \ + test mutants lint lint-extra fmt doc audit semver publish-dry-run publish \ coverage coverage-check \ check-prereqs check-prereqs-extra check-prereqs-audit check-prereqs-nightly \ require-container-engine \ @@ -150,6 +150,16 @@ publish-dry-run: done cargo package -p $(firstword $(PUBLISH_CRATES)) +# Real crates.io publish, in dependency order. Requires a crates.io token +# in CARGO_REGISTRY_TOKEN (set from the RUST_CRATES_PUBLISH_TOKEN secret in +# CI). `cargo publish` blocks until each crate is available in the index +# before the next one publishes. +publish: + @for crate in $(PUBLISH_CRATES); do \ + echo "publishing $$crate"; \ + cargo publish -p "$$crate"; \ + done + # ------------------------------------------------------------------- # Container # ------------------------------------------------------------------- @@ -235,6 +245,7 @@ help: @echo " audit cargo audit + cargo deny" @echo " semver cargo semver-checks" @echo " publish-dry-run package + verify release crates" + @echo " publish publish release crates to crates.io" @echo " coverage HTML coverage report" @echo " coverage-check fail if lines < 90%% or regions < 80%%" @echo "" From 85bfc26ffeb0ebed483e8d3a3f5b322c262692a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:56:44 +0000 Subject: [PATCH 2/2] chore(deps): Bump alpine from 3.23 to 3.24 Bumps alpine from 3.23 to 3.24. --- updated-dependencies: - dependency-name: alpine dependency-version: '3.24' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Containerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Containerfile b/Containerfile index 1dc0d20..c6be1bc 100644 --- a/Containerfile +++ b/Containerfile @@ -56,7 +56,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ # Stage 2: Runtime # ------------------------------------------------------------------------------ -FROM alpine:3.23 +FROM alpine:3.24 LABEL org.opencontainers.image.source="https://github.com/praxis-proxy/conventions" \ org.opencontainers.image.description="Conventions probe binary" \