lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 85fbf0dbebf65b98b33584f7961a78cd2c555ce7
parent 19b29d71693d85d174b8ee279e15b4f5d114f8c1
Author: triesap <tyson@radroots.org>
Date:   Wed, 25 Feb 2026 16:29:36 +0000

ci: add release preflight automation lane


- add manual github workflow for release-preflight execution
- add deterministic preflight script for check validate and coverage gates
- add publish-order script with dry-run dependency deferral handling
- validate shell scripts with bash -n before commit

Diffstat:
A.github/workflows/release-preflight.yml | 37+++++++++++++++++++++++++++++++++++++
Ascripts/ci/release_preflight.sh | 41+++++++++++++++++++++++++++++++++++++++++
Ascripts/ci/release_publish_order.sh | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 160 insertions(+), 0 deletions(-)

diff --git a/.github/workflows/release-preflight.yml b/.github/workflows/release-preflight.yml @@ -0,0 +1,37 @@ +name: release-preflight + +on: + workflow_dispatch: + +jobs: + preflight: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: guard committed ts artifacts + run: ./scripts/ci/guard_committed_ts_artifacts.sh + + - name: install rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.92.0 + + - name: install nightly rust toolchain + run: rustup toolchain install nightly --profile minimal + + - name: install cargo llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: run release preflight + run: ./scripts/ci/release_preflight.sh + + - name: upload release preflight artifacts + uses: actions/upload-artifact@v4 + with: + name: release-preflight + path: | + target/coverage/coverage-refresh.tsv + target/coverage/coverage-refresh-status.tsv + target/coverage/**/gate-report.json diff --git a/scripts/ci/release_preflight.sh b/scripts/ci/release_preflight.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$root_dir" + +cargo check -q +cargo test -q -p xtask +cargo run -q -p xtask -- sdk validate + +required_file="$(mktemp)" +trap 'rm -f "$required_file"' EXIT +cargo run -q -p xtask -- sdk coverage required-crates > "$required_file" + +mkdir -p target/coverage +printf "crate\tstatus\texec\tfunc\tbranch\treport\n" > target/coverage/coverage-refresh.tsv +printf "crate\tstatus\n" > target/coverage/coverage-refresh-status.tsv + +while IFS= read -r crate; do + [ -n "$crate" ] || continue + safe_crate="${crate//-/_}" + out_dir="target/coverage/${safe_crate}" + mkdir -p "$out_dir" + + cargo run -q -p xtask -- sdk coverage run-crate --crate "$crate" --out "$out_dir" --test-threads 1 + cargo run -q -p xtask -- sdk coverage report \ + --scope "${crate}" \ + --summary "${out_dir}/coverage-summary.json" \ + --lcov "${out_dir}/coverage-lcov.info" \ + --out "${out_dir}/gate-report.json" \ + --fail-under-exec-lines 100 \ + --fail-under-functions 100 \ + --fail-under-branches 100 \ + --require-branches + + printf "%s\tpass\t100.0\t100.0\t100.0\t%s\n" "$crate" "${out_dir}/gate-report.json" >> target/coverage/coverage-refresh.tsv + printf "%s\tpass\n" "$crate" >> target/coverage/coverage-refresh-status.tsv +done < "$required_file" + +cargo run -q -p xtask -- sdk release preflight +echo "release preflight complete" diff --git a/scripts/ci/release_publish_order.sh b/scripts/ci/release_publish_order.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$root_dir" + +mode="${1:-publish}" +if [[ "$mode" != "publish" && "$mode" != "dry-run" ]]; then + echo "usage: scripts/ci/release_publish_order.sh [publish|dry-run]" + exit 2 +fi + +release_version="$( + awk ' + /^\[release\]/ { in_release = 1; next } + in_release && /^version = / { + gsub(/"/, "", $3); + print $3; + exit + } + ' contract/release/publish-set.toml +)" + +if [[ -z "$release_version" ]]; then + echo "failed to resolve release.version from contract/release/publish-set.toml" + exit 1 +fi + +order_file="$(mktemp)" +trap 'rm -f "$order_file"' EXIT + +awk ' + /^\[publish_order\]/ { in_order = 1; next } + /^\[/ && in_order { exit } + in_order && /"/ { + line = $0 + gsub(/[" ,]/, "", line) + if (length(line) > 0) print line + } +' contract/release/publish-set.toml > "$order_file" + +if [[ ! -s "$order_file" ]]; then + echo "publish_order.crates list is empty" + exit 1 +fi + +while IFS= read -r crate; do + [ -n "$crate" ] || continue + if [[ "$mode" == "dry-run" ]]; then + log_file="$(mktemp)" + if cargo publish --dry-run --locked --allow-dirty -p "$crate" >"$log_file" 2>&1; then + cat "$log_file" + rm -f "$log_file" + continue + fi + + missing_dep="$(sed -n 's/.*no matching package named `\([^`]*\)`.*/\1/p' "$log_file" | head -n1)" + if [[ -n "$missing_dep" ]] && grep -Fxq "$missing_dep" "$order_file"; then + echo "dry-run defer for ${crate}: dependency ${missing_dep} is not yet published" + rm -f "$log_file" + continue + fi + + cat "$log_file" + rm -f "$log_file" + exit 1 + fi + + cargo publish --locked -p "$crate" + for attempt in $(seq 1 30); do + if curl -fsSL "https://crates.io/api/v1/crates/${crate}/${release_version}" >/dev/null 2>&1; then + break + fi + if [[ "$attempt" == "30" ]]; then + echo "crate ${crate} version ${release_version} not visible on crates.io after publish" + exit 1 + fi + sleep 10 + done +done < "$order_file" + +echo "publish sequence complete for release ${release_version}"