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.rs → crates/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
Problem
A user who encounters Shipper and tries
cargo install shipperwill get a library crate with no binary—they need to know about the separateshipper-clipackage 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
The library logic lives in
crates/shipper(correct); the CLI binary is built and published viacrates/shipper-cli(separate package, confusing for end users).Proposed change
Phase 1 (this release):
[[bin]] name = "shipper" path = "src/main.rs"tocrates/shipper/Cargo.tomlcrates/shipper-cli/src/main.rs→crates/shipper/src/main.rs(or symlink/include-macro)crates/shipper/Cargo.toml, feature-gated underclifeature (default-on for binary, default-off for embedders)crates/shipper-clias a thin re-export shim:fn main() { shipper::cli::run() }plus re-export of CLI feature.github/workflows/release.ymlto build/installshipperinstead ofshipper-cli(line 88, 198, 384, 462)shipper-clipublished but deprecated in its Cargo.toml docs and READMEshipperandshipper-cli(line 292 references)Phase 2 (+1 minor version):
shipper-clifrom crates.io with advisory in release notesPhase 3 (+2 minor versions):
crates/shipper-clidirectory from repoWhat about library consumers' dependency bloat?
Embedders who depend on
shipperwill pull in clap/indicatif if they don't opt out. Mitigation:shipper = { version = "0.4", default-features = false }avoids the CLI depsAcceptance criteria
cargo install shipperproduces a working binary that matches the currentshipper-clibehaviorcargo install shipper-clistill works with the transitional shimcargo search shipperat the release version shows the binary-capable packageshipperwithout pulling CLI deps:shipper = { version = "0.4", default-features = false }