Crates.io release

The release workflow publishes the component crates to crates.io and then lets cargo-dist create the GitHub Release with binary artifacts. The workflow runs when a v<semver> tag is pushed. A manual dry_run dispatch runs the same gates but skips crates.io upload and GitHub Release creation.

One-time setup

Create a scoped crates.io token with publish-new, publish-update, and yank permissions. Add it to the GitHub repository as the Actions secret CARGO_REGISTRY_TOKEN.

Local preflight

Run the gates before tagging:

cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo nextest run --workspace --no-fail-fast
cargo doc --workspace --no-deps
mdbook build docs/
cargo xtask doc-rules --check
cargo xtask doc-cli --check
cargo xtask doc-config --check
python3 scripts/check_package_docsrs.py --allow-dirty
actionlint .github/workflows/*.yml

Check public API drift:

for crate in mdwright-latex mdwright-math mdwright-document mdwright-format mdwright-lint mdwright-config mdwright-lsp mdwright; do
  cargo public-api --simplified -p "$crate" > /tmp/"$crate"-public.txt
  diff -u "docs/api-review/$crate-public.txt" /tmp/"$crate"-public.txt
done

If a public API change is intentional, regenerate the baselines in the same commit:

scripts/update-api-review.sh

Version and changelog

The tag must match the workspace package version exactly. For version 0.1.0, tag v0.1.0.

CHANGELOG.md must contain a release section named ## [0.1.0]. The release workflow extracts that section before any crate is published.

Dry run

Before tagging, run the Release workflow manually with dry_run: true. It verifies the workspace, builds the cargo-dist artifacts, checks package contents, simulates docs.rs from packaged tarballs, and skips live publishing.

Publish

After the release commit is on main, create and push the tag:

git tag -s v0.1.0 -m "mdwright v0.1.0"
git push origin v0.1.0

The workflow publishes crates in dependency order:

mdwright-latex -> mdwright-math -> mdwright-document -> mdwright-format -> mdwright-lint -> mdwright-config -> mdwright-lsp -> mdwright

It waits 90 seconds between crates so crates.io can index each newly published dependency before downstream crates are published.

If publishing fails after a crate has uploaded, stop. Crates.io versions are immutable. Fix the problem, bump the workspace version, update the changelog, and tag a new commit.