feat: implement numan use for side-by-side Nu version management

Merged
TO
3mo ago
+2377-155

Summary

Implements numan use for side-by-side Nu version management. The legacy-migration story (single-binary → versioned layout, journaled transition, doctor reconcile) lives on its own PR (#PR_MIGRATE_ID) so this PR stays small and reviewable.

Changes

Tracking: src/nu/version_manager.rs

Active version tracking + versioned-layout helpers for side-by-side Nu installs. The active Nu selector is stored at <root>/nu_state/active-version.json and the per-version binaries live at <root>/tools/nushell/<v>/nu. Helpers include listing installed versions, normalizing version strings, resolving the active binary (on-tree or off-tree via the marker), and clearing the active marker (cleared atomically before removing the versioned tree so the marker cannot dangle at a missing binary).

This file no longer carries the legacy single-binary migration fs in this PR — that work lands in #PR_MIGRATE_ID and the call site in cmd/use_cmd.rs is dormant until then.

Updated: src/nu/bootstrap.rs

  • install_from_archive() writes to <root>/tools/nushell/<version>/ (immutable, one dir per version).
  • managed_nu_binary() delegates to version_manager for active version lookup.
  • numan setup nu <x.y.z> persists the freshly installed version as the active version atomically (active-marker write sequenced after archive extraction).
  • Non-interactive numan setup nu is fail-closed without --yes, mirroring setup.rs; refuses to mutate state silently.
  • Hoisted the post-install PATH-mutation consent through a caller_consented_destructive flag on NuSetupOptions so setup.rs and bootstrap.rs only prompt once for the combined wipe + PATH-add.

New: numan use command (src/cmd/use_cmd.rs)

  • numan use <version> — switch to a specific installed version.
  • numan use latest — switch to the newest installed version.
  • numan use list — show installed versions + the active marker + per-version binary paths.
  • Validates the requested version is installed before flipping the active marker; errors with a hint to run setup nu <version> if it isn't. Does not auto-download.
  • Acquires the root mutation lock (so concurrent numan setup nu and numan use cannot interleave) and creates a PreMutation snapshot before any marker write.
  • The off-tree active-marker bridge: when numan setup nu use <path> switches to a user-supplied Nu outside the versioned layout, the marker records the resolved binary path so subsequent numan use list and find_nu_executable_with_root can resolve that version even if no on-tree install exists.

New: destructive-confirm + --yes gate in src/cmd/setup.rs

For numan setup nu use <path> / numan use existing, where the destructive step is wiping <root>/tools/nushell/ (every managed Nu version + active-version marker) before adding the resolved Nu's parent to PATH:

  • One merged prompt explicitly states both effects and includes the literal "there is no undo".
  • The prompt is gated on !args.yes && std::io::stdin().is_terminal() — non-interactive sessions refuse to commit destructive mutations without explicit --yes. An audit-grade eprintln! records whether the gate was an explicit --yes or an interactive confirmation.
  • Hoisted via caller_consented_destructive so setup.rs only prompts once for the combined wipe + PATH-add.

New: require_tty_or_yes (src/util/confirm.rs)

Hard-fail audit helper used by destructive numan setup flows. Refuses to run mutations in a non-TTY session without --yes; audit-grade eprintln! records the gate outcome. Other destructive prompts (remove_managed_nu, the loader overwrite) can adopt the same rubric in a follow-up.

docs/plans/consolidated-multi-repo-roadmap.md

  • Removes the "reserved stub" / "post-1.0" line for numan use.
  • Documents that numan use <version> errors (with a hint to run setup nu <version>) if the version isn't installed — it never auto-downloads.
  • Documents the active-marker as a JSON file at <root>/nu_state/active-version.json, atomic clear-before-tree-remove, and that PATH persistence is owned by numan setup nu while numan use only flips the active marker under the mutation lock with a PreMutation snapshot.

Testing

  • 442 unit tests pass on this branch (down from 468 once migration pieces are stripped; the 26 migration tests live on the migration branch).
  • cargo clippy -- -D warnings clean.
  • cargo fmt --check clean.
  • New end-to-end test coverage for the confirm-gate UX in src/cmd/setup.rs (DI seams via ExecuteUseOpts<'a>).

Architecture Notes

This implements the non-auto-installing shape of numan use: switching the active marker is fast and silent, but downloading a new Nu stays in numan setup nu --version <version>. If the requested version is not installed, the command errors with a hint to run setup nu first. This keeps numan use predictable for users (no surprise downloads) and matches the north-star product decision: Nu-compat onboarding flows through setup nu + try rather than use.

Future Work

  • Per-version activation sets in lockfile (Phase 5+)
  • Show which plugins are active/inactive after switch (Phase 5+ UX)
  • Activation reconciliation (post-1.0)
  • numan alias for Numan-level aliases (post-1.0)