ci: add the publish.yaml workflow file (completes d3af1c4)
d3af1c4 described the manual publish workflow and updated ADR-0056, but
`git commit -am` doesn't stage new untracked files, so publish.yaml
itself was left out. Add it here.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# Manual publication workflow (workflow_dispatch) — the outward, irreversible
|
||||
# release steps a human triggers AFTER the automated `release.yaml` build has
|
||||
# produced downloadable assets (and they've been eyeballed as good).
|
||||
#
|
||||
# Why manual + separate from release.yaml:
|
||||
# * Publishing to a public registry is irreversible (crates.io versions can
|
||||
# only be *yanked*, never deleted) — a human pulls this lever, and the
|
||||
# registry token never sits on every tag push.
|
||||
# * Our release is split (Linux/Windows on the tag, macOS dispatched), so a
|
||||
# human is the natural "all assets are up — go" gate. crates.io publish
|
||||
# reads SOURCE so it doesn't strictly need the release, but binstall's
|
||||
# metadata points at the release assets — hence run this once builds exist.
|
||||
#
|
||||
# Structure: each registry is its OWN job with NO inter-job `needs`, so jobs run
|
||||
# independently and one failing (or a newly-added one) never breaks another.
|
||||
# Every job is IDEMPOTENT — re-dispatching when a target is already published is
|
||||
# a clean no-op. Add Scoop / Homebrew / winget as sibling jobs here later.
|
||||
name: publish
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag to publish (e.g. v0.2.0)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
crates-io:
|
||||
runs-on: ci-public
|
||||
container:
|
||||
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: publish to crates.io (idempotent)
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
# A crate-scoped, publish-update crates.io token, stored as a Gitea
|
||||
# Actions secret. `cargo publish` reads CARGO_REGISTRY_TOKEN from env.
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Source of truth = the [package] version at the checked-out tag
|
||||
# (toolchain-free read; same approach as release.yaml's guard, which
|
||||
# avoids the flake devShell's stdout banner corrupting a parse).
|
||||
VER=$(grep -m1 '^version = ' Cargo.toml | sed -E 's/^version = "(.*)"/\1/')
|
||||
[ -n "$VER" ] || { echo "ERROR: could not read version from Cargo.toml" >&2; exit 1; }
|
||||
if [ "$TAG" != "v$VER" ]; then
|
||||
echo "ERROR: dispatch tag '$TAG' != 'v$VER' (Cargo.toml at that tag)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Idempotency: if this version is already on crates.io, no-op.
|
||||
# (crates.io requires a descriptive User-Agent per its data policy;
|
||||
# without one the API returns 403.) Only an explicit 200 means
|
||||
# "already there" — anything else proceeds, and `cargo publish` is the
|
||||
# final backstop (it refuses to overwrite an existing version).
|
||||
UA="rdbms-playground-release-ci (oliver@sturmnet.org)"
|
||||
code=$(curl -sS -o /dev/null -w '%{http_code}' -A "$UA" \
|
||||
"https://crates.io/api/v1/crates/rdbms-playground/$VER" || echo 000)
|
||||
if [ "$code" = "200" ]; then
|
||||
echo "rdbms-playground $VER is already on crates.io — nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
echo "crates.io returned HTTP $code for $VER (not 200) — proceeding to publish."
|
||||
|
||||
echo "publishing rdbms-playground $VER to crates.io ..."
|
||||
nix develop -c cargo publish --locked
|
||||
echo "published rdbms-playground $VER to crates.io."
|
||||
|
||||
# Future manual publication targets go here as independent, idempotent jobs,
|
||||
# e.g.:
|
||||
# scoop-bucket: { ... update the lazyeval Scoop bucket manifest ... }
|
||||
# homebrew-tap: { ... update the lazyeval Homebrew formula ... }
|
||||
# winget: { ... komac submit, or a manual PR helper ... }
|
||||
# No `needs:` between them — each checks the target and skips if already done.
|
||||
Reference in New Issue
Block a user