Skip to content

Packaging UX: make 'cargo install shipper' work by collapsing binary into shipper package #95

Description

@EffortlessSteven

Problem

A user who encounters Shipper and tries cargo install shipper will get a library crate with no binary—they need to know about the separate shipper-cli package to get the actual CLI. This is friction on the first-impression UX: the package name should match the user's intent.

Current package layout

# crates/shipper/Cargo.toml (lines 1-13)
[package]
name = "shipper"
# ... no [[bin]] target

# crates/shipper-cli/Cargo.toml (lines 15-17)
[[bin]]
name = "shipper"
path = "src/main.rs"

The library logic lives in crates/shipper (correct); the CLI binary is built and published via crates/shipper-cli (separate package, confusing for end users).

Proposed change

Phase 1 (this release):

  • Add [[bin]] name = "shipper" path = "src/main.rs" to crates/shipper/Cargo.toml
  • Move crates/shipper-cli/src/main.rscrates/shipper/src/main.rs (or symlink/include-macro)
  • Move CLI-only deps (clap, indicatif, chrono, clap_complete) into crates/shipper/Cargo.toml, feature-gated under cli feature (default-on for binary, default-off for embedders)
  • Keep crates/shipper-cli as a thin re-export shim: fn main() { shipper::cli::run() } plus re-export of CLI feature
  • Update .github/workflows/release.yml to build/install shipper instead of shipper-cli (line 88, 198, 384, 462)
  • Keep shipper-cli published but deprecated in its Cargo.toml docs and README
  • Update the cargo search verification list in release.yml to check both shipper and shipper-cli (line 292 references)

Phase 2 (+1 minor version):

  • Yank shipper-cli from crates.io with advisory in release notes

Phase 3 (+2 minor versions):

  • Remove crates/shipper-cli directory from repo

What about library consumers' dependency bloat?

Embedders who depend on shipper will pull in clap/indicatif if they don't opt out. Mitigation:

  • CLI feature is opt-in for library users: shipper = { version = "0.4", default-features = false } avoids the CLI deps
  • Cargo.toml default-features list puts non-CLI features first, documenting the library's core purpose
  • This is standard practice (e.g., tokio, serde, reqwest all have similar splits)

Acceptance criteria

  • cargo install shipper produces a working binary that matches the current shipper-cli behavior
  • cargo install shipper-cli still works with the transitional shim
  • Release workflow builds/installs the correct binary
  • cargo search shipper at the release version shows the binary-capable package
  • Embedders can depend on shipper without pulling CLI deps: shipper = { version = "0.4", default-features = false }

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions