1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
name: Release to crates.io
on:
push:
tags:
jobs:
publish:
name: publish to crates.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Validate version matches Cargo.toml
run: |
PACKAGE_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
if [ "$PACKAGE_VERSION" != "${{ steps.version.outputs.VERSION }}" ]; then
echo "::error::Tag version (${{ steps.version.outputs.VERSION }}) doesn't match Cargo.toml version ($PACKAGE_VERSION)"
exit 1
fi
# Rebuilds and re-runs the suite here rather than trusting an artifact
# from ci.yml's run: a tag push is a separate event from the branch
# push ci.yml validated, so there is no prior run to reuse. --locked:
# the committed Cargo.lock is what was tested.
# The integration tests dial macula-go's in-process stations
# (tests/teststation), built to target/teststation first.
- uses: actions/setup-go@v5
with:
go-version-file: tests/teststation/go.mod
cache-dependency-path: tests/teststation/go.sum
- run: ./scripts/build-teststation.sh
- run: cargo build --workspace --all-targets --locked
- run: cargo test --workspace --all-features --locked
- run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
- run: cargo fmt --all -- --check
# Dry-run needs no registry auth -- it only verifies metadata,
# compresses the package, and checks the result, never uploads.
- name: Dry-run publish (catches packaging issues before the real one)
run: cargo publish --dry-run
# CRATES_IO_TOKEN (a plain crates.io API token, cargo login-style) --
# NOT OIDC Trusted Publishing, unlike this org's npm (macula-mcp)
# and NuGet (macula-dotnet) release workflows. crates.io does
# support Trusted Publishing (rust-lang/crates-io-auth-action, same
# shape as those two), but it has no PyPI-style "pending publisher"
# for a crate that doesn't exist yet -- a Trusted Publisher can only
# be configured on crates.io AFTER a crate's first release already
# exists there. A static token is the only mechanism that can
# actually perform that first release from CI at all.
#
# macula-rust v0.2.3 published this way 2026-09-05 -- the migration
# path once someone wants to spend the time on it: add a Trusted
# Publisher for macula-io/macula-rust + this workflow file in the
# crate's crates.io settings, replace this step with
# `rust-lang/crates-io-auth-action@v1` (permissions:
# id-token: write) feeding its `token` output to
# CARGO_REGISTRY_TOKEN the same way, then delete the CRATES_IO_TOKEN
# repo secret -- no code reason to keep a long-lived token around
# once the OIDC path is available.
- name: Publish to crates.io
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}