commit 1bd80a1fa7ae640cc805bc968c13406a0c233600
parent 2c071dc46934c46949b1d000ead6c227cb9c593b
Author: triesap <tyson@radroots.org>
Date: Mon, 2 Mar 2026 23:37:39 +0000
ci: add crates publish workflow
Diffstat:
2 files changed, 108 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml
@@ -0,0 +1,75 @@
+name: publish crates
+
+on:
+ workflow_dispatch:
+ inputs:
+ crates:
+ description: "space or comma separated crate names (empty = full release order)"
+ required: false
+ default: ""
+ dry_run:
+ description: "run cargo publish --dry-run"
+ type: boolean
+ required: false
+ default: false
+ push:
+ tags:
+ - "v*.*.*"
+
+permissions:
+ contents: read
+
+concurrency:
+ group: publish-crates
+ cancel-in-progress: false
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: setup rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ toolchain: 1.92.0
+
+ - name: cache cargo
+ uses: Swatinem/rust-cache@v2
+
+ - name: verify workspace
+ run: cargo check --workspace
+
+ - name: publish crates
+ env:
+ EVENT_NAME: ${{ github.event_name }}
+ CRATES_INPUT: ${{ github.event.inputs.crates }}
+ DRY_RUN_INPUT: ${{ github.event.inputs.dry_run }}
+ CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+ run: |
+ set -euo pipefail
+
+ dry_run="false"
+ crates_input=""
+ if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
+ crates_input="${CRATES_INPUT:-}"
+ dry_run="${DRY_RUN_INPUT:-false}"
+ fi
+
+ mode="publish"
+ if [ "${dry_run}" = "true" ]; then
+ mode="dry-run"
+ fi
+
+ if [ "${mode}" = "publish" ] && [ -z "${CARGO_REGISTRY_TOKEN:-}" ] && [ -n "${CRATES_IO_TOKEN:-}" ]; then
+ export CARGO_REGISTRY_TOKEN="${CRATES_IO_TOKEN}"
+ fi
+
+ if [ "${mode}" = "publish" ] && [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then
+ echo "set CRATES_IO_TOKEN or CARGO_REGISTRY_TOKEN for publish runs"
+ exit 1
+ fi
+
+ ./scripts/ci/release_publish_order.sh "${mode}" "${crates_input}"
diff --git a/scripts/ci/release_publish_order.sh b/scripts/ci/release_publish_order.sh
@@ -6,10 +6,13 @@ cd "$root_dir"
mode="${1:-publish}"
if [[ "$mode" != "publish" && "$mode" != "dry-run" ]]; then
- echo "usage: scripts/ci/release_publish_order.sh [publish|dry-run]"
+ echo "usage: scripts/ci/release_publish_order.sh [publish|dry-run] [crate names]"
exit 2
fi
+requested_raw="${2:-}"
+requested_raw="${requested_raw//,/ }"
+
release_version="$(
awk '
/^\[release\]/ { in_release = 1; next }
@@ -27,7 +30,6 @@ if [[ -z "$release_version" ]]; then
fi
order_file="$(mktemp)"
-trap 'rm -f "$order_file"' EXIT
awk '
/^\[publish_order\]/ { in_order = 1; next }
@@ -44,6 +46,34 @@ if [[ ! -s "$order_file" ]]; then
exit 1
fi
+selected_file="$(mktemp)"
+requested_file="$(mktemp)"
+trap 'rm -f "$order_file" "$selected_file" "$requested_file"' EXIT
+
+if [[ -n "$requested_raw" ]]; then
+ for token in $requested_raw; do
+ [[ -n "$token" ]] || continue
+ echo "$token" >> "$requested_file"
+ done
+ sort -u "$requested_file" -o "$requested_file"
+
+ while IFS= read -r token; do
+ if ! grep -Fxq "$token" "$order_file"; then
+ echo "requested crate is not in publish_order.crates: ${token}"
+ exit 1
+ fi
+ done < "$requested_file"
+
+ while IFS= read -r crate; do
+ [[ -n "$crate" ]] || continue
+ if grep -Fxq "$crate" "$requested_file"; then
+ echo "$crate" >> "$selected_file"
+ fi
+ done < "$order_file"
+else
+ cp "$order_file" "$selected_file"
+fi
+
while IFS= read -r crate; do
[ -n "$crate" ] || continue
if [[ "$mode" == "dry-run" ]]; then
@@ -77,6 +107,6 @@ while IFS= read -r crate; do
fi
sleep 10
done
-done < "$order_file"
+done < "$selected_file"
echo "publish sequence complete for release ${release_version}"