Skip to content

Commit 26c7a4c

Browse files
committed
feat: add publish.sh
1 parent fb2a4b8 commit 26c7a4c

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

publish.sh

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env bash
2+
# Publish all tako sub-crates to crates.io in topological order, then publish
3+
# the umbrella `tako-rs` crate.
4+
#
5+
# Usage:
6+
# ./publish.sh # publish for real (runs gate: fmt + clippy + tests)
7+
# ./publish.sh --dry-run # validate without uploading (still runs gate)
8+
# ./publish.sh --allow-dirty # publish with uncommitted changes
9+
# ./publish.sh --skip-gate # bypass fmt/clippy/test gate (NOT recommended)
10+
#
11+
# Notes:
12+
# - Every sub-crate is currently marked `publish = false` in its Cargo.toml
13+
# ("Internal ... (not published)"). For an actual crates.io release of the
14+
# `tako-rs` family, that flag must be removed from each sub-crate first —
15+
# cargo refuses to upload a crate whose path-deps are unpublishable. This
16+
# script does not edit the manifests; it asssumes the user has prepared
17+
# them. `--dry-run` is still useful to validate the gate even without
18+
# flipping the flag.
19+
# - Crates already published at the current local version are skipped, so the
20+
# script is safe to re-run if a publish fails partway through.
21+
# - Modern cargo (>=1.66) waits for the registry index to sync after each
22+
# publish, so no manual sleep is needed.
23+
24+
set -euo pipefail
25+
26+
DRY_RUN=()
27+
ALLOW_DIRTY=()
28+
SKIP_GATE=0
29+
for arg in "$@"; do
30+
case "$arg" in
31+
--dry-run) DRY_RUN=(--dry-run) ;;
32+
--allow-dirty) ALLOW_DIRTY=(--allow-dirty) ;;
33+
--skip-gate) SKIP_GATE=1 ;;
34+
-h|--help)
35+
sed -n '2,22p' "$0"; exit 0 ;;
36+
*) echo "unknown flag: $arg" >&2; exit 1 ;;
37+
esac
38+
done
39+
40+
# Pre-publish gate: fmt + clippy + workspace test. Catches the kinds of
41+
# regressions the v2 pre-release audit (`AUDIT.md`) was set up to prevent.
42+
# Skip with `--skip-gate` if you really must (e.g., re-running after a
43+
# transient crates.io upload failure on a known-good commit).
44+
if [[ $SKIP_GATE -eq 0 ]]; then
45+
echo "==> pre-publish gate: cargo +nightly fmt --all --check"
46+
# rustfmt.toml uses `imports_granularity` + `group_imports`, both
47+
# nightly-only options — fmt has to run on nightly.
48+
cargo +nightly fmt --all -- --check
49+
50+
echo "==> pre-publish gate: cargo clippy --workspace --all-features -- -D warnings"
51+
# `--all-features` is the strictest config: it activates both runtimes
52+
# (tokio + compio), every transport (TLS / HTTP/2 / HTTP/3 / WebTransport),
53+
# and every extractor / plugin. Workspace.lints sets `pedantic = warn`, so
54+
# `-D warnings` catches pedantic regressions too.
55+
cargo clippy --workspace --all-features --no-deps -- -D warnings
56+
57+
echo "==> pre-publish gate: cargo test --workspace --all-features"
58+
cargo test --workspace --all-features
59+
else
60+
echo "==> WARNING: --skip-gate is set; skipping fmt + clippy + test"
61+
fi
62+
63+
# Topological order — each crate must come AFTER its internal deps.
64+
# Verified against `tako-*/Cargo.toml` `tako-*.workspace = true` entries.
65+
PUBLISH_ORDER=(
66+
"tako-macros" # no internal deps
67+
"tako-core" # no internal deps
68+
"tako-extractors" # tako-core
69+
"tako-server" # tako-core
70+
"tako-server-pt" # tako-core
71+
"tako-streams" # tako-core, tako-server
72+
"tako-plugins" # tako-core, tako-extractors
73+
"tako-rs" # umbrella — last
74+
)
75+
76+
local_version() {
77+
# extract the [package] version from a crate's Cargo.toml. All sub-crates
78+
# set `version.workspace = true`, so they inherit from `[workspace.package]`
79+
# in the root manifest — read that once and reuse.
80+
awk '/^\[workspace\.package\]/{p=1; next} /^\[/{p=0} p && /^version[[:space:]]*=/{gsub(/"/,"",$3); print $3; exit}' Cargo.toml
81+
}
82+
83+
registry_version() {
84+
# latest version on crates.io, "-" if not published
85+
local crate="$1"
86+
curl -fsS "https://crates.io/api/v1/crates/$crate" 2>/dev/null \
87+
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('crate',{}).get('newest_version','-'))" \
88+
2>/dev/null || echo "-"
89+
}
90+
91+
publish_one() {
92+
local spec="$1"
93+
# spec may be "crate-name" or "crate-name --no-verify"
94+
local crate="${spec%% *}"
95+
local extra=()
96+
if [[ "$spec" == *" "* ]]; then
97+
read -r -a extra <<< "${spec#* }"
98+
fi
99+
100+
local lv rv
101+
lv=$(local_version)
102+
rv=$(registry_version "$crate")
103+
104+
echo
105+
echo "==> $crate (local=$lv, registry=$rv)"
106+
107+
if [[ ${#DRY_RUN[@]} -eq 0 && "$lv" == "$rv" ]]; then
108+
echo " already published at $lv — skipping"
109+
return 0
110+
fi
111+
112+
set -x
113+
cargo publish -p "$crate" \
114+
${extra[@]+"${extra[@]}"} \
115+
${DRY_RUN[@]+"${DRY_RUN[@]}"} \
116+
${ALLOW_DIRTY[@]+"${ALLOW_DIRTY[@]}"}
117+
set +x
118+
}
119+
120+
for spec in "${PUBLISH_ORDER[@]}"; do
121+
publish_one "$spec"
122+
done
123+
124+
echo
125+
echo "All crates processed."

0 commit comments

Comments
 (0)