|
| 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