Tutorial

Version 0.2.0 Updated Jun 21, 2026

This tutorial assumes a Rust workspace with a root Cargo.toml and one or more member crates.

1. Create `lading.toml`

Create a minimal configuration file at the workspace root:

[bump.documentation]
globs = ["README.md", "docs/**/*.md"]

[publish]
strip_patches = "per-crate"

lading.toml can be omitted entirely. When absent, lading uses the defaults documented in the configuration reference below.

2. Bump versions

To update the workspace and member crate manifests to 1.2.3:

lading bump 1.2.3

To preview changes without writing any files:

lading bump 1.2.3 --dry-run

After updating manifest versions, lading bump automatically refreshes any git-tracked Cargo.lock files (excluding those under target/) that have an adjacent Cargo.toml. Refreshed lockfiles are listed in the command output with a (lockfile) suffix. In dry-run mode the lockfiles are listed but not modified.

If bump.documentation.globs is configured, lading also searches those Markdown files for TOML code fences and updates version values that refer to workspace crates.

3. Publish in dry-run mode

By default, publish runs cargo publish --dry-run so the full pipeline can be validated without uploading crates.

lading publish

Before running cargo check and cargo test, lading publish validates that all git-tracked Cargo.lock files are fresh under --locked mode. If any lockfile is stale — for example after a lading bump that regenerated a nested workspace lockfile — the command exits with code 1 and prints a repair command:

Tracked Cargo.lock files are stale after manifest version changes.
Run the following to repair:
  cargo generate-lockfile --manifest-path <path>/Cargo.toml

Run the repair command, commit the updated lockfile, then re-run lading publish.

To require a clean working tree before running the pre-flight checks, pass --forbid-dirty:

lading publish --forbid-dirty

To perform a real publish (no --dry-run), pass --live:

lading publish --live

Dry-run publishing packages every publishable crate first, then runs cargo publish --dry-run for every crate. Live publishing follows publish.order crate by crate: cargo package, then cargo publish, then the next crate. This lets a later crate depend on a newly published earlier crate in the same --live run. Live publishing is not transactional; if a later crate fails, crates already uploaded to crates.io are not rolled back. Reruns skip versions that are already present on crates.io and continue with the remaining crates.

bump adopts the workspace README.md for any member crate that sets readme.workspace = true. The adopted README is written into the crate directory and relative Markdown links are rewritten so they still resolve from that directory. publish then stages the already-prepared workspace into a temporary directory before packaging.

Dry-run limitations with unpublished workspace dependencies

cargo package validates dependency versions against the live crates.io index, even in dry-run mode. When two or more workspace crates are released together for the first time and one depends on another at a version that is not yet on crates.io, cargo package will fail with an error similar to:

error: failed to prepare local package for uploading

Caused by:
  failed to select a version for the requirement `inner_crate = "^0.8.0"`
  candidate versions found which didn't match: 0.7.0, 0.6.0, ...
  location searched: crates.io index
  required by package `outer_crate v0.8.0`

This affects dry-run release trains that introduce a new shared version across multiple workspace crates. lading publish --live avoids the limitation by publishing each crate immediately after it is packaged, so a later crate can resolve a dependency that an earlier crate in publish.order just uploaded. Plain dry-runs still use Cargo's live index. By default, lading downgrades these index-lookup failures to warnings when the missing dependency is also in the publish plan and appears earlier in publish order.

Manual staged publishing

When a release must be split manually, run lading publish --live for the foundational crate first, then run lading publish (dry-run) or lading publish --live for the remaining workspace once the new version is indexed:

# 1. Publish the foundational crate live so crates.io has the new version.
lading publish --live --workspace-root path/to/workspace

# 2. Once the new version is indexed, publish (or dry-run) dependent crates.
lading publish --workspace-root path/to/workspace

lading skips crates whose versions are already on crates.io, so the second invocation only acts on the remaining crates.

--allow-unpublished-workspace-deps (dry-run only)

For CI gating where a real publish is not desirable, dry-run mode defaults to the same behaviour as passing --allow-unpublished-workspace-deps: it downgrades the index-lookup failure to a warning when the missing dependency is itself part of the planned publish set and appears earlier in publish order:

lading publish --allow-unpublished-workspace-deps

The override applies to both the cargo package step and the subsequent cargo publish --dry-run step (which packages internally and hits the same crates.io index lookup), so the dry run completes end-to-end.

Use --no-allow-unpublished-workspace-deps to opt out during dry-runs and keep Cargo's index lookup strict:

lading publish --no-allow-unpublished-workspace-deps

--allow-unpublished-workspace-deps is rejected when combined with --live because the failure cannot be bypassed during a real publish. --no-allow-unpublished-workspace-deps remains valid with --live; it preserves the strict behaviour that live publishes already use. When the missing dependency is not in the publish plan, or when it appears after the current crate in publish.order, the failure is still treated as an error. Fix the explicit publish.order so foundational crates come before dependants, or remove publish.order and rely on dependency-derived topological sorting. Each such downgrade is counted and surfaced in the metrics summary emitted at exit; see Observability.