cli: list `bun pm diff` in `bun pm --help`, keep one copy of the pm help text, restore the indent of the `\`-continued messages

Closed
RO
1mo ago
+183-232

Problem

  • bun pm --help does not list bun pm diff (added in #39229). Bare bun pm does.
  • The bun pm command list existed three times. bun pm --help printed the copy in src/install/PackageManager.rs (CommandLineArguments::parse short-circuits to it). Bare bun pm and an unknown subcommand printed a second copy in src/runtime/cli/package_manager_command.rs. #39229 updated only that one. #38333 had to patch both, and the open pm fetch (#29509) and pm sbom (#29512) PRs patch both. A third, six-entry copy sat in tag_print_help in src/runtime/cli/mod.rs and is unreachable (pm is not in USES_GLOBAL_OPTIONS).
  • misctools/generate-cli-completions.ts builds completions/bun-cli.json from bun pm --help, so regenerated shell completions would miss diff too.
  • The second copy also printed wrong. It was a "...\n\ string literal, and a \ line continuation strips the leading whitespace of the next line, so bare bun pm printed every row without the two-space indent (which the completions generator's ^\s+bun pm match also needs). The same continuation strips the indent in three more messages written that way: the bun why / bun pm why usage text (why_command.rs:278), and the bun create react and bun create next redirect messages (mod.rs:1847, mod.rs:1861). The pre-port text had the indent in all of them.
  • tag_print_help has one more unreachable hand-written arm of the same kind, Tag::InfoCommand. bun info --help prints the text in CommandLineArguments::print_help (info is not in USES_GLOBAL_OPTIONS either).

Fix

  • Moves the pm help text into the Subcommand::Pm arm of CommandLineArguments::print_help, where every other install-family command keeps its text, and adds the diff block to it (its description padded to the column the other rows use). The one-method PackageManagerCommand struct in PackageManager.rs that held the text is deleted.
  • Deletes the copy in package_manager_command.rs. The bare and unknown-subcommand paths now call CommandLineArguments::print_help(Subcommand::Pm), which is what bun pm --help already called. The pm and info arms of tag_print_help forward there too, like the patch, outdated, dedupe, ... arms next to them.
  • Drops the trailing newline of the intro string, as the other intros in that function have. The merged text is the layout the pre-port implementation printed: bun pm --help loses one stray blank line, bare bun pm gets its indent back, nothing else in either output changes (diffs below).
  • Marks the first space of the indented lines in the why usage text and the two bun create messages with \x20, the idiom pm_version_command.rs already uses for this, so they print the indent again.
  • Correct because all three pm entry points are the same command list, and the runtime crate already depends on the install crate, so separate copies can only drift. The info arm change is dead code only. The \x20 change restores the text the Zig source had, byte for byte otherwise.
  • Intentionally left alone: the bun why help still has a usage text (bare why) and a --help text (tag_print_help) with different content, which is a separate consolidation. src/runtime/api/cron.rs has the same continuation pattern in generated plist/XML, where the indent does not matter. completions/bun-cli.json is not regenerated here (#38917 does that); whichever of the two lands second regenerates it.
  • Verified:
    • test/cli/install/bun-pm-diff.test.ts "bun pm help lists diff and its flags": bun pm --help and bare bun pm contain the indented bun pm diff [a] [b] row and its --stat, --name-only, -U, --json flags.
    • test/cli/bun.test.ts "bun pm with no subcommand or an unknown one prints the same help as bun pm --help": the three entry points print byte-identical stdout with the diff and default-trusted rows and one blank line before Commands:. Exit codes 0 / 0 / 1 and the unknown-command error on stderr are unchanged.
    • test/cli/install/bun-pm-why.test.ts "should show help when no package is specified" now holds the whole usage text as an inline snapshot, for bun why and bun pm why. It no longer installs packages from the registry; the usage path only needs a package.json.
    • test/cli/install/bun-create.test.ts "retired template names point at the replacement command": inline snapshots of the stderr of bun create react and bun create next. Inline, not a .snap file, so the indent stays visible in the test (and file snapshots do not work inside describe.concurrent).
    • Each of these fails on main without the src/ changes (checked by stashing them and rebuilding) and passes with them. The four files pass in full with the debug build, as do the existing bun pm --help assertions in bun.test.ts and the licenses help test in bun-pm-licenses.test.ts.
    • bun info --help output is unchanged. rustfmt --check on the touched Rust files. The pre-port-identifiers and port-era-markers source lints pass.

Background

  • CommandLineArguments::parse(subcommand) (src/install/PackageManager/CommandLineArguments.rs) parses the flags of the install-family commands (install, add, pm, why, info, ...). On --help it calls CommandLineArguments::print_help(subcommand), a match with one arm of help text per subcommand, and exits. The install crate cannot depend on the CLI crate (the CLI is its consumer), which is why these texts live on the install side and the CLI forwards to them.
  • PackageManagerCommand::exec (src/runtime/cli/package_manager_command.rs) runs after parsing and dispatches on the subcommand name. When nothing matches it prints the help and exits 0 (no subcommand) or prints "<name>" unknown command and exits 1.
  • tag_print_help (src/runtime/cli/mod.rs) is the help printer of the generic Arguments::parse, which create_context_data only runs for commands in USES_GLOBAL_OPTIONS (src/options_types/command_tag.rs). pm and info are excluded, so their arms there never run. Several arms for other install-family commands are already one-line forwards to CommandLineArguments::print_help.
  • In a Rust string literal, \ at the end of a line removes the newline and all leading whitespace of the next line, so "a\n\ followed by b" is "a\nb". \x20 is a space written as an escape, so it survives, and \x20 yields the two-space indent. Raw strings (r"...") keep the indent as written, which is what the print_help arms use.
Before/after output of a debug build (pm)

bun pm --help, before vs after (the only changes):

6d5
<
22a22,28
>   bun pm diff [a] [b]         show what changed between two versions of a package (or vs a folder/tarball)
>   ├ bun pm diff react            installed version → latest
>   ├ bun pm diff react@18.2.0 19.0.0
>   ├ bun pm diff axios@1.6.0:lib 1.6.1  only files under lib/ (also :file.js, or paths after the two sides)
>   ├ --stat, --name-only       summarize instead of printing hunks
>   ├ -U n                      lines of context (default 3)
>   └ --json                    one JSON document (files, patch text, notes, totals)

Bare bun pm, before vs after, with the restored two-space indent stripped from the after output. What remains is the two rows whose description column was out of line:

22c22
< bun pm diff [a] [b]           show what changed between two versions of a package (or vs a folder/tarball)
---
> bun pm diff [a] [b]         show what changed between two versions of a package (or vs a folder/tarball)
34c34
< └ --filter <pattern>      list only the matching workspaces' dependencies
---
> └ --filter <pattern>        list only the matching workspaces' dependencies

After the change bun pm, bun pm --help and bun pm bogus produce byte-identical stdout (cmp), with exit codes 0, 0 and 1.

Earlier shape of this PR

The first push only added the diff block to the PackageManager.rs copy and made the CLI forward to it. A review pass pointed at the sibling sites (the why and bun create messages losing their indent the same way, and the dead info arm), which the second push covers. The third push moved the text into CommandLineArguments::print_help, which removed the struct and the comment that explained it.


no test proof · iteration 3 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/bun.test.ts test/cli/install/bun-pm-diff.test.ts