tooling active

crate-seq

stop skipping versions. publish every patch.

started
version 0.2.0
category tooling
rust

Stop skipping versions. Publish every patch.

crate-seq is a CLI tool that tracks local Rust crate versions (via git tags or snapshots) and publishes them sequentially to crates.io — so your registry history matches your development history.


The problem

You're on 1.0.10 locally. crates.io is still at 1.0.0 because you forgot to publish. Now you either:

  1. Skip straight to 1.0.10 and lose 9 patches of public history, or
  2. Manually reconstruct and publish each version one at a time

Neither option is acceptable. crate-seq automates option 2.

Installation

cargo install crate-seq

Quick start

Initialize a single-crate project:

cd my-crate
crate-seq init

This scans your git tags (matching v* pattern) and the crates.io registry, then generates a .crate-seq.toml ledger showing which versions are published, pending, or yanked.

Initialize a workspace (multiple crates):

cd my-workspace
crate-seq init

In a workspace, crate-seq creates a separate .crate-seq.toml in each member crate's directory. Tags use per-crate patterns by default ({crate-name}-v*).

my-workspace/
├── Cargo.toml              # workspace root (no ledger here)
├── crates/
   ├── foo/
   ├── Cargo.toml
   └── .crate-seq.toml # foo's ledger (tags: foo-v*)
   └── bar/
       ├── Cargo.toml
       └── .crate-seq.toml # bar's ledger (tags: bar-v*)

Workspace init walkthrough

Initialize all crates in a workspace, then inspect one:

$ crate-seq init
foo: 3 versions
bar: 1 versions

Then check a specific crate to see what is pending:

$ crate-seq check --crate foo
foo @ 1.0.0 (registry latest)
  pending:  1.0.1 (foo-v1.0.1)
            1.0.2 (foo-v1.0.2)
            1.0.3 (foo-v1.0.3)

At this point foo has three unpublished versions. Run a dry-run to validate them before committing to a live publish:

$ crate-seq publish --crate foo
foo:
  ok  1.0.1
  ok  1.0.2
  ok  1.0.3
Dry run complete pass --execute to publish.

Workflow

crate-seq is tag-first: the git tag is the snapshot. What exists at the tagged commit is exactly what gets published — no more, no less.

The lifecycle for every new version:

  1. Make all changes for the version (code, README.md, metadata, version bump in Cargo.toml).
  2. Commit everything.
  3. Run crate-seq tag <version> (or git tag manually). This records a pending entry in the ledger.
  4. Run crate-seq publish --execute to publish all pending versions in SemVer order.

Key invariants:

  • Commits made after a tag are invisible to that version. If you add a README.md after tagging 1.0.0, the published 1.0.0 package on crates.io will have no README.
  • Version bump, readme field, metadata, and Cargo.toml contents must all be in their final state before you tag.
  • Per-crate README.md files must exist at tag time to appear on crates.io.

For patch releases:

Bump the version in Cargo.toml, commit, then crate-seq tag <patch-version>. The ledger entry is added as pending; the next publish --execute picks it up.

Resuming an interrupted publish:

If a publish run is interrupted mid-way, re-run crate-seq publish --execute. Versions already on crates.io are detected and skipped automatically; the run resumes from the first pending entry.


Terminal output examples

crate-seq init

Scans git tags and crates.io, then prints each crate and the number of version entries written to its ledger:

$ crate-seq init
my-crate: 10 versions

For a workspace:

$ crate-seq init
foo: 3 versions
bar: 1 versions

crate-seq check

Diffs the ledger against the registry. Up-to-date crates print a green confirmation; crates with pending work list each version grouped by state:

$ crate-seq check
my-crate @ 1.0.0 (registry latest)
  pending:  1.0.1 (v1.0.1)
            1.0.2 (v1.0.2)
            1.0.3 (v1.0.3)

When a crate is already up to date:

$ crate-seq check
my-crate @ 1.0.3 up to date

Orphaned entries (ledger entry whose git ref no longer exists) appear in red:

orphaned: 1.0.2 <- ref missing

crate-seq publish (dry-run)

Validates every pending version can be packaged. No network writes occur. Each version prints ok or FAIL with a reason:

$ crate-seq publish
my-crate:
  ok  1.0.1
  ok  1.0.2
  ok  1.0.3
Dry run complete pass --execute to publish.

A packaging failure looks like:

FAIL 1.0.2 path deps found: my-internal-lib

crate-seq publish --execute

Publishes all pending versions sequentially. A progress bar advances while publishing; a summary is printed after:

$ crate-seq publish --execute
  [██████████████████████████████] 3/3  1.0.3
 1.0.1
 1.0.2
 1.0.3

Versions already present on crates.io are skipped automatically (idempotent resume):

 1.0.1 (already on crates.io)
 1.0.2
 1.0.3

crate-seq status

Prints the full ledger table for each crate with columns for version, source, ref, and status:

$ crate-seq status
my-crate
  version      source     ref                  status
  ────────────────────────────────────────────────────────
  1.0.0        git-tag    v1.0.0               published
  1.0.1        git-tag    v1.0.1               published
  1.0.2        git-tag    v1.0.2               pending
  1.0.3        git-tag    v1.0.3               pending
  ────────────────────────────────────────────────────────
  4 total   2 published   2 pending

crate-seq tag <version>

Creates a git tag and a matching ledger entry atomically. Rolls back the tag if the ledger write fails:

$ crate-seq tag 1.0.4
created tag v1.0.4 and ledger entry for 1.0.4

crate-seq skip <version>

Marks a version as skipped so it is excluded from future publish runs:

$ crate-seq skip 1.0.2
skipped 1.0.2

crate-seq snapshot <version>

Captures the current crate directory as a versioned gzip tarball and records its SHA-256:

$ crate-seq snapshot 1.0.5
snapshot created: a3f8d2...c91b (version 1.0.5)

Commands

Command Description
crate-seq init Initialize ledger from git tags + crates.io state
crate-seq check Show diff between local versions and registry
crate-seq tag <version> Create a git tag and add to ledger
crate-seq publish Dry-run publish all pending versions
crate-seq publish --execute Publish all pending versions to crates.io
crate-seq skip <version> Mark a version as skipped (won't publish)
crate-seq status Show full ledger state
crate-seq snapshot <version> Capture current directory as a versioned snapshot

Configuration

Each crate gets its own .crate-seq.toml ledger file, alongside that crate's Cargo.toml. In a single-crate repo, it lives at the project root.

[crate]
name = "my-crate"
registry = "crates.io"

[settings]
mode = "git-tag"              # or "snapshot"
tag_pattern = "v*"            # "v*" for single-crate, "{name}-v*" for workspaces
dry_run_default = true        # --execute to override
backoff_base_ms = 1000        # rate limit backoff

[auth]                         # optional — all fields are optional
token_env = "CRATE_SEQ_TOKEN" # read token from this env var
# token_cmd = "op read 'op://Vault/crates-io/token'"  # OR shell out to secret manager

[[versions]]
version = "1.0.1"
source = "git-tag"
ref = "v1.0.1"
status = "pending"

[[versions]]
version = "1.0.2"
source = "git-tag"
ref = "v1.0.2"
status = "pending"

Authentication

crate-seq resolves tokens in this order (first match wins):

  1. --token <value> flag
  2. token_env or token_cmd from .crate-seq.toml [auth] section
  3. Cargo's own credentials (~/.cargo/credentials.toml or credential-process)

If no valid token is found, the tool exits with a clear error and setup instructions. It never prompts interactively.

token_cmd supports any secret manager: 1Password (op), AWS Secrets Manager, HashiCorp Vault, or custom scripts.

How it works

  1. Scan — reads git tags matching the configured per-crate pattern and queries the crates.io API for the current published state (including yanked versions).
  2. Diff — compares local tags against registry state, identifies unpublished versions.
  3. Sequence — sorts unpublished versions in strict SemVer order.
  4. Verify — before each publish, queries crates.io to check if the version already exists (idempotent resume after crashes).
  5. Publish — for each version: checks out the tag in a temp directory, unconditionally rewrites Cargo.toml version to match, runs cargo package, then cargo publish. Handles rate limits with exponential backoff (base 1s, cap 60s, 5 retries, jitter up to 1s).
  6. Record — updates the ledger with publish status after each version.

Requirements

  • Rust 1.88+ (MSRV). The gix 0.79 dependency pulls in human_format, which uses let-chains — a feature stabilized in Rust 1.87. crate-seq pins to 1.88.0 via rust-toolchain.toml.
  • Git (for git-tag mode)
  • A valid crates.io API token (via cargo login, --token flag, env var, or secret manager)

License

MIT OR Apache-2.0

documentation

view all docs

architecture

changelog

  • 0.2.0 3 entries
  • 0.1.2 1 entry
  • 0.1.1 2 entries
view full changelog