Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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)

ChannelWhatNotes
crates.io librariesCore graph ending at assura-pipelineEmbed API for apps
crates.io frontendsassura-rust-analyzer, assura-llm, assura-lsp, assura-mcpShared by the CLI
crates.io CLIassura binary packagecargo install assura --locked
GitHub Releases / cargo-distSame assura binaryPrebuilt 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).

What ships on crates.io

Publish order is graph-derived by scripts/publish-crates.sh (all path deps including dev). Typical order:

assura-astassura-configassura-diagnosticsassura-runtimeassura-rust-analyzerassura-parserassura-macrosassura-llmassura-fmtassura-stdlibassura-resolveassura-typesassura-codegenassura-smtassura-pipelineassura-lspassura-mcpassura (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

PackageReason
assura-test-supportInternal monorepo test helpers only (publish = false).
assura-server / assura-bench / editor toolingNot 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-approve GitHub App (vars.AUTO_APPROVE_CLIENT_ID + secrets.AUTO_APPROVE_PRIVATE_KEY) so merge pushes are not suppressed. Historical note: Auto-merge with GITHUB_TOKEN does not start push workflows (including release-please.yml). Prefer a human merge for release-related PRs, use the hourly cron catch-up, or gh workflow run "Release Please". See issue #785.
  • Release tag + single pipeline (#1380): release-please creates tags with the App token, which does fire release.yml on.push.tags. Pure GITHUB_TOKEN tags would not. dispatch-release still exists as a fallback, but it skips workflow_dispatch when 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 rewrites workspace.package.version (via extra-files). That does not update Cargo.lock the way release-type: rust does for a single root package (e.g. patchloom). The sync-release-pr-versions job runs sync-path-dep-versions.sh and sync-cargo-lock-workspace-versions.sh on the release PR branch so CI with --locked stays green. Do not omit the lock step when copying this layout to another monorepo.
  • Use chore: for CI/docs/refactor. Reserve fix: / feat: for user-visible changes so version bumps stay intentional.
  • Optional: commit RELEASE_NOTES.md on 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:

PathRole
release-please-config.jsonrelease-type: simple (workspace root has no [package]), updates workspace.package.version, CHANGELOG.md, bump-minor-pre-major
.release-please-manifest.jsonLast released version per package (root ".")
scripts/publish-crates.shFail-closed graph filter + topo publish (pre-check, 429 handling, skip already-uploaded)
scripts/sync-path-dep-versions.shPath-dep version= pins = workspace version
scripts/sync-cargo-lock-workspace-versions.shAlign Cargo.lock workspace member versions after a version bump (required for CI --locked)
scripts/check-publish-plan.shAssert publish order matches the 13-crate library stack
scripts/check-cargo-package.shcargo 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.ymlOpens release PR on main push; syncs path-deps + lock on that PR; dispatches Release only if tag-push did not (#1380)
.github/workflows/release.ymlcargo-dist installers + publish-crates (tag / dispatch); concurrency by tag name
dist-workspace.tomlcargo-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)).

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)

  1. Land normal work on main with conventional commits (feat:, fix:, chore: as appropriate).
  2. 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.
  3. Review version bump, CHANGELOG.md, and path-dep pins (sync-path-dep-versions on the release workflow run).
  4. Optionally land curated RELEASE_NOTES.md on main, wait for release-please to refresh the PR. The host job applies it to the GitHub Release body; cleanup-release-notes then opens an auto-merge PR to remove the file so the next release does not reuse stale notes.
  5. Merge the release PR with an explicit human decision (never auto-merge autorelease: pending).
  6. Watch the Release workflow; verify crates.io + GitHub Release assets.
gh run list --workflow=release.yml --limit 5
gh run watch <RUN_ID>
JobMeaning
release-please (in release-please workflow)Tag + GitHub Release created; dispatches Release with tag
plancargo-dist plan for that tag
build-local-artifacts / build-global-artifactsCLI installers
hostUpload assets (idempotent if the Release already exists); applies RELEASE_NOTES.md if present
publish-cratesLibraries via scripts/publish-crates.sh (skips versions already on crates.io)
announceFinal confirmation
cleanup-release-notesPR to delete RELEASE_NOTES.md from main after apply (non-fatal)

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 + assura CLI) 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 --locked and/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.

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