crates.io packaging and release-please
Operational guide for publishing Assura libraries to
crates.io and shipping CLI installers via cargo-dist.
Versioning and GitHub Releases are driven by release-please, not by
manually pushing v* tags.
Do not merge a release-please PR without explicit maintainer approval. Merging it creates the tag, GitHub Release, cargo-dist artifacts, and crates.io publish for the version in that PR.
Current status (CLI co-publish, #838)
| Channel | What | Notes |
|---|---|---|
| crates.io libraries | Core graph ending at assura-pipeline | Embed API for apps |
| crates.io frontends | assura-rust-analyzer, assura-llm, assura-lsp, assura-mcp | Shared by the CLI |
| crates.io CLI | assura binary package | cargo install assura --locked |
| GitHub Releases / cargo-dist | Same assura binary | Prebuilt multi-platform installers ([package.metadata.dist] dist = true) |
Versions share [workspace.package] version. After a release, co-publish
uploads any package whose version is not yet on crates.io (idempotent skips
for already-published members).
Published crates inherit rust-version = "1.88.0" (edition 2024). That is
the MSRV shown on crates.io and the rustc cargo rejects below that.
Edition 2024 is 1.85. Current dependencies such as rmcp, tonic, time,
zip, and darling declare rust-version 1.88.
What ships on crates.io
Publish order is graph-derived by scripts/publish-crates.sh (all path deps
including dev). Typical order:
assura-ast → assura-config → assura-diagnostics → assura-runtime →
assura-rust-analyzer → assura-parser → assura-macros → assura-llm →
assura-fmt → assura-stdlib → assura-resolve → assura-types →
assura-codegen → assura-smt → assura-pipeline → assura-lsp →
assura-mcp → assura (CLI).
Path dependencies pin version = "…" so packaging resolves from crates.io;
scripts/sync-path-dep-versions.sh keeps pins aligned on release PRs.
What does not ship on crates.io
| Package | Reason |
|---|---|
assura-test-support | Internal monorepo test helpers only (publish = false). |
assura-server / assura-bench / editor tooling | Not co-published; use monorepo or separate install docs. |
How the release pipeline works
push to main
└─ release-please.yml
├─ opens/updates release PR (label: autorelease: pending)
│ └─ sync-path-dep-versions (align path dep pins on that branch)
└─ when release PR is merged (release_created=true):
tag + GitHub Release (App token)
├─ tag-push usually starts release.yml (preferred path)
└─ dispatch-release: only if no healthy tag-push Release exists
plan → build-local/global (cargo-dist) → host (upload assets)
→ publish-crates (scripts/publish-crates.sh) → announce
Important details:
- Auto-merge uses the
assura-auto-approveGitHub App (vars.AUTO_APPROVE_CLIENT_ID+secrets.AUTO_APPROVE_PRIVATE_KEY) so merge pushes are not suppressed. Historical note: Auto-merge withGITHUB_TOKENdoes not start push workflows (includingrelease-please.yml). Prefer a human merge for release-related PRs, use the hourly cron catch-up, orgh workflow run "Release Please". See issue #785. - Release tag + single pipeline (#1380): release-please creates tags with
the App token, which does fire
release.ymlon.push.tags. PureGITHUB_TOKENtags would not.dispatch-releasestill exists as a fallback, but it skipsworkflow_dispatchwhen a healthy tag-push Release already exists (queued / in progress / success). Concurrency is keyed by the release tag (release-${{ inputs.tag || github.ref_name }}) so a race cannot run two full cargo-dist matrices in parallel. Manual re-run:gh workflow run Release -f tag=vX.Y.Z(still works alone). - Auto-approve skips PRs labeled
autorelease: pending. Release PRs must be merged by a human. - Virtual workspace +
release-type: simple: release-please only rewritesworkspace.package.version(viaextra-files). That does not updateCargo.lockthe wayrelease-type: rustdoes for a single root package (e.g. patchloom). Thesync-release-pr-versionsjob runssync-path-dep-versions.shandsync-cargo-lock-workspace-versions.shon the release PR branch so CI with--lockedstays green. Do not omit the lock step when copying this layout to another monorepo. - Use
chore:for CI/docs/refactor. Reservefix:/feat:for user-visible changes so version bumps stay intentional. - Optional: commit
RELEASE_NOTES.mdon main (not on the release-please branch) before merging the release PR to override the GitHub Release body. Remove it after the release lands (see prior issue #813).
Config files:
| Path | Role |
|---|---|
release-please-config.json | release-type: simple (workspace root has no [package]), updates workspace.package.version, CHANGELOG.md, bump-minor-pre-major |
.release-please-manifest.json | Last released version per package (root ".") |
scripts/publish-crates.sh | Fail-closed graph filter + topo publish (pre-check, 429 handling, skip already-uploaded) |
scripts/sync-path-dep-versions.sh | Path-dep version= pins = workspace version |
scripts/sync-cargo-lock-workspace-versions.sh | Align Cargo.lock workspace member versions after a version bump (required for CI --locked) |
scripts/check-publish-plan.sh | Assert publish order matches the 13-crate library stack |
scripts/check-cargo-package.sh | cargo package gate; full verify when version is on crates.io, --list on co-publish version-bump PRs (#814, co-publish skew) |
.github/workflows/release-please.yml | Opens release PR on main push; syncs path-deps + lock on that PR; dispatches Release only if tag-push did not (#1380) |
.github/workflows/release.yml | cargo-dist installers + publish-crates (tag / dispatch); concurrency by tag name; then calls Publish MCP Registry |
.github/workflows/publish-mcp-registry.yml | After crates.io has mcp-name: for that version, publish server.json with GitHub OIDC |
server.json | Official MCP Registry metadata. assura mcp over stdio. Description max 100 characters |
dist-workspace.toml | cargo-dist targets / installers |
Preflight (before merging a release PR)
git status # clean working tree preferred
bash scripts/check-publish-plan.sh
bash scripts/check-cargo-package.sh # full package+verify if version on crates.io;
# auto --list on pre-publish version-bump PRs
# Optional dry-run of the publish script (no token required):
bash scripts/publish-crates.sh --dry-run
On a release-please PR that bumps to a version not yet on crates.io, full
cargo package cannot resolve path+version deps against the registry
(only the old version exists). The script falls back to --list so CI still
checks tarball membership; co-publish (publish-crates.sh) is the ordered
full verify at release time.
check-cargo-package.sh is the gate that would have caught the v0.1.0
assura-smt failure (monorepo include_str! paths outside the package
tarball). CI runs the same script on every rust-touching change (job
Cargo package (publishable)).
Shared native crate versions (rowan lesson, #1448 / #1451): when packaging
a downstream crate (for example assura-fmt), Cargo resolves path+version
deps against crates.io for siblings already published. If the monorepo
bumps a private dependency (for example rowan 0.16 → 0.17) only on
assura-parser, but a sibling crate still depends on rowan directly,
verify can dual-link two rowan majors and fail type-checking
(NodeOrToken from 0.16 vs 0.17). Keep rowan private to assura-parser;
downstream code should use assura_parser types and methods
(as_token / as_node) only.
Fast listing only (no verify build):
bash scripts/check-cargo-package.sh --list-only
Fail-closed real publish normally runs only in CI after a release-please merge:
CARGO_REGISTRY_TOKEN=… bash scripts/publish-crates.sh
Cutting a new release (normal path)
- Land normal work on
mainwith conventional commits (feat:,fix:,chore:as appropriate). - release-please updates the open release PR (or opens a new one). An open PR such as “release 0.1.1” after 0.1.0 is expected.
- Review version bump,
CHANGELOG.md, and path-dep pins (sync-path-dep-versionson the release workflow run). - Optionally push curated notes on a one-file
release-note-<semver>branch (no PR). Host or Apply release notes copies them onto the GitHub Release and deletes the branch. - Merge the release PR with an explicit human decision (never auto-merge
autorelease: pending). - Watch the Release workflow; verify crates.io + GitHub Release assets.
gh run list --workflow=release.yml --limit 5
gh run watch <RUN_ID>
| Job | Meaning |
|---|---|
release-please (in release-please workflow) | Tag + GitHub Release created; dispatches Release with tag |
plan | cargo-dist plan for that tag |
build-local-artifacts / build-global-artifacts | CLI installers |
host | Upload assets (idempotent if the Release already exists); applies notes from release-note-<semver> if present |
publish-crates | Libraries via scripts/publish-crates.sh (skips versions already on crates.io) |
announce | Final confirmation |
| Apply release notes | Cheap re-apply of curated notes (no compile); deletes the notes branch |
If publish-crates fails mid-graph, fix on main and re-dispatch the same
tag (idempotent for already-uploaded crates). You cannot overwrite a version
on crates.io.
# Re-dispatch Release for an existing tag after a script/CI fix
gh workflow run release.yml -f tag=v0.1.0
# or re-run failed jobs on an existing run:
gh run rerun <RUN_ID> --failed
Post-release verification
crates.io
for c in assura-ast assura-config assura-diagnostics assura-macros \
assura-runtime assura-parser assura-fmt assura-stdlib assura-resolve \
assura-types assura-codegen assura-smt assura-pipeline; do
echo -n "$c: "
curl -fsS "https://crates.io/api/v1/crates/$c" | python3 -c \
"import sys,json; d=json.load(sys.stdin); print(d['crate']['max_version'])"
done
GitHub Release
gh release view vX.Y.Z
gh release view vX.Y.Z --json assets --jq '.assets[].name'
- Co-published crates (libraries + frontends +
assuraCLI) show the released version on crates.io (bash scripts/publish-crates.sh --plan-only). - Release page has installers for configured targets.
- Notes state experimental status and CLI install path
(
cargo install assura --lockedand/or GitHub Release installers).
IR templates and packaging pitfalls
IR prompt markdown used by include_str! must live under
crates/assura-smt/templates/ir/ (crate-local). Monorepo
templates/ir/ is a pointer README only (#812). scripts/guards.sh
section 13 fails if pattern bodies reappear at the monorepo root.
scripts/check-cargo-package.sh fails if any publishable crate cannot
package/verify.
Manual / break-glass
Only if CI cannot run and a maintainer approves a laptop publish:
git checkout vX.Y.Z
CARGO_REGISTRY_TOKEN=… bash scripts/publish-crates.sh
Prefer re-running GitHub Actions so logs and token handling stay in one place.
Historical: first release (v0.1.0)
The first cut used a temporary release-as: 0.1.0 pin (otherwise historical
commits computed 1.0.0), multiple Release re-dispatches, and script fixes
for graph order and “already exists” pre-checks. That pin is gone. Do not
re-apply release-as unless intentionally forcing a version for a future
cut. Details live in session notes / assura-contrib skill, not in day-to-day
procedure above.
Related skill notes
Canonical patterns live in ci-build-release (release-please same-workflow,
cargo-dist host vs release-please body ownership, RELEASE_NOTES.md override,
cargo package preflight for publishable members) and ci-branch-protection
(never auto-merge autorelease: pending).