Skip to content

feat(ui): complete internal/ui call-site migration#119

Merged
dipto0321 merged 1 commit into
mainfrom
feat/ui/full-call-site-migration
Jul 5, 2026
Merged

feat(ui): complete internal/ui call-site migration#119
dipto0321 merged 1 commit into
mainfrom
feat/ui/full-call-site-migration

Conversation

@dipto0321

Copy link
Copy Markdown
Owner

Summary

Final PR of #105's three-PR phasing. Migrates every remaining user-facing cmd.Printf / cmd.Println / fmt.Fprintf in internal/cli/* to ui.Writer.{Info,Success,Warn,Error,Println}. SPEC.md V2 now flips from aspirational to enforced: nothing in internal/ or cmd/ bypasses internal/ui.

What's in this PR

internal/cli/check.gooutputCheckJSON / outputCheckTable rewritten to take (ui.Writer, context.Context) instead of *cobra.Command. Same field layout (LTS, Current, System node) preserved so existing nodeup check | grep LTS pipelines keep working.

internal/cli/list.gooutputListJSON / outputListTable rewritten the same way.

internal/cli/packages.gosnapshot / list / restore / diff all migrated. The writeReport closure and clearSentinel closure use writerFromCmd for warn/info/success emission.

internal/cli/config.goshow / get / set / init all migrated. Lock-release warnings now go through writer.Warn; success lines ("set foo=bar (saved to ...)", "wrote scaffolded config to ...") through writer.Success.

internal/cli/upgrade.go — ~15 remaining cmd.Printf call sites migrated: sentinel cleanup, manager-resolution log, dry-run plan, upgrade-lock release, snapshot warnings, sentinel-write warnings, restore warnings, migration-report paths, active-version-detection failure (#58), cleanup error summary, and the final "Upgrade complete!" line. The spinner wiring (#117) and cleanup prompt migration (#118) are unchanged.

internal/cli/version.gowriterFromCmd made nil-context-safe. Bare cobra.Command constructions (common in unit tests) used to panic on cmd.Context(); now it returns cmd.OutOrStdout() / ErrOrStderr via the existing fallback path.

internal/cli/list_test.gonewBufCmd now also redirects ErrOrStderr to the same buffer (mirroring how the migrated ui.Writer routes warn/error). outputListJSON / outputListTable test signatures updated to pass writerFromCmd(cmd).

Implementation notes

  • Every cmd.Printf("Warning: ...")writerFromCmd(cmd).Warn(...) so warnings land in stderr rather than stdout (matters for nodeup upgrade | jq pipelines where Warn output would corrupt the JSON envelope).
  • Every "snapshot saved" / "set X=Y" / "Restored packages" → Success so FancyMode gets the ✓ glyph while PlainMode stays plain.
  • Every Info-level message (LTS line, candidate list headers, dry-run plans) routes through Info so FancyMode gets the • bullet — same rationale as Success.
  • Dry-run plan keeps the literal "Dry run - would install:" header through Println rather than Info so it doesn't get a bullet prefix (the line IS the header; bullets below it would be confusing).
  • Active-version-detection warning (bug(cli): Current() failure leaves the active Node version unprotected from cleanup deletion #58) stays as Warn because the user must see why their --cleanup / --yes shortcut was downgraded to per-version confirmation.

Edge cases I considered before opening

  • writerFromCmd with bare cobra.Command (no SetContext): previously crashed with a nil-deref. Now falls through to cmd.OutOrStdout() / ErrOrStderr — same fallback path that production code already took when writerCtxKey was missing. Pinned by every existing test that builds a bare cobra.Command via newBufCmd.
  • Mixed stdout/stderr in tests: newBufCmd now redirects both to the same bytes.Buffer. Without this fix, the migration's Warn calls would silently write to the real os.Stderr during tests and the assertions would look incomplete (we hit exactly this bug — see the failing TestOutputListTable_MixedStates run during development).
  • Migration order — upgrade.go first or last?: Last. The upgrade flow has the most complex output surface (spinners + warnings + success + summary), so testing the simpler commands' migrations first caught the nil-context / writer-wiring issues before touching upgrade. The upgrade-related tests are unchanged because they don't construct a bare cobra.Command — they go through NewRootCmd which sets the context properly.
  • writerFromCmd(cmd) called from a defer (lock release warnings): cmd is captured by the closure, so the defer works correctly. No lifetime issues.
  • The "Restored packages for %s %s" line changed from cmd.Printf to writer.Success — that's a deliberate category change. Old behavior was an unstructured info line; new behavior (FancyMode ✓) emphasizes the success. PlainMode is byte-identical except for the absence of \r cleanup (we never had \r in the old cmd.Printf either; this is a no-op for piped output).

Verification

$ make ci
go mod tidy
gofmt -s -w .
go vet ./...
golangci-lint run ./...
0 issues.
go test -race -coverprofile=coverage.out -covermode=atomic ./...
ok  	github.com/dipto0321/nodeup/internal/cli	coverage: 33.2%
ok  	github.com/dipto0321/nodeup/internal/ui	coverage: 67.4%
total: 62.4%

Plus manual smoke tests:

$ make build && ./bin/nodeup upgrade --help  # works
$ ./bin/nodeup check --help                  # works
$ ./bin/nodeup list --help                   # works

What this enables

  • SPEC V2 flips from aspirational to enforced. PR1 of feat(ui): complete internal/ui rollout — spinners, huh prompts, migrate remaining call sites (#74 phases 2–4) #105 (the scaffold) made it possible to centralize output; PR2 / PR3 expanded the surface; this PR proves the invariant holds across every command in the binary. A future linter could grep for cmd.Printf / cmd.Println / fmt.Fprintln in internal/cli/ and gate new contributions.
  • The single decision point for plain-vs-fancy (ui.DecideMode) now really IS a single decision point — before this PR, callers that bypassed ui.Writer (e.g., the old fmt.Fprintf(cmd.OutOrStdout(), ...) in config.go) made their own mode decision implicitly (always plain). Now DecideMode runs once in root.go's PersistentPreRunE and every output observes its result.
  • FancyMode features (colored prefixes ✓/•/⚠/✗) are now uniformly available for every output line, not just the ones routed through ui.Writer (which used to be only upgrade.go's "Using manager", "Upgrade complete", and a few others).

Closes #105 (last of 3 PRs). No follow-up PRs planned; the Spinner + Prompt + Writer abstractions are stable.

Final PR of #105's three-PR phasing: every user-facing string
in internal/cli now flows through ui.Writer.{Info, Success,
Warn, Error, Println} — no direct cmd.Printf / fmt.Fprintf in
business logic. Wraps up the rollout started by the scaffold
(#74 PR1) and continued by the spinners (#117) and huh prompts
(#118).

What's migrated:

- check.go: outputCheckJSON / outputCheckTable rewritten to
  take (ui.Writer, context.Context) instead of (cobra.Command).
  The table renderer still preserves the same field layout
  (LTS, Current, System node) so existing scripts piping `nodeup
  check | grep LTS` keep working.

- list.go: outputListJSON / outputListTable rewritten the same
  way. Empty-state message now routes through writer.Info so
  FancyMode gets the bullet prefix while PlainMode stays clean.

- packages.go: snapshot, list, restore, diff all migrated. The
  writeReport closure uses writerFromCmd for warn/info, the
  clearSentinel closure does too. The `Restored packages from
  %s` and `Restored packages for %s %s` lines route through
  writer.Success now (was cmd.Printf).

- config.go: config show / get / set / init all migrated. The
  `set foo=bar (saved to ...)` and `wrote scaffolded config`
  lines are now Success messages; lock-release failures now go
  through writer.Warn.

- upgrade.go: ~15 remaining cmd.Printf call sites migrated
  (sentinel cleanup, manager-resolution log, dry-run plan,
  upgrade-lock release, snapshot warnings, sentinel write
  warnings, restore warnings, migration report, active-version
  detection failure, cleanup error summary, completion line).
  All spinners / cleanup prompts / ResolveInteractive integration
  from #117/#118 are unchanged.

- version.go: writerFromCmd made nil-context-safe (was: panicked
  on bare cobra.Command, which many tests construct).

- list_test.go: newBufCmd now also redirects ErrOrStderr to the
  same buffer (mirrors how the migrated ui.Writer splits stdout
  vs stderr). outputListJSON / outputListTable tests rewritten
  to use writerFromCmd(cmd) for the writer argument.

No new files, no go.mod changes. SPEC.md's V2 invariant now
flips from aspirational to enforced: every user-facing string
in internal/ and cmd/ routes through internal/ui.

After this lands, issue #105 fully closes.

Closes #105.

Co-Authored-By: puku-ai-2.8 <noreply@puku.sh>
@cocogitto-bot

cocogitto-bot Bot commented Jul 5, 2026

Copy link
Copy Markdown

✔️ ac6a4ca - Conventional commits check succeeded.

@dipto0321 dipto0321 merged commit da41dbc into main Jul 5, 2026
10 checks passed
@dipto0321 dipto0321 deleted the feat/ui/full-call-site-migration branch July 5, 2026 08:05
dipto0321 added a commit that referenced this pull request Jul 5, 2026
Closes #121. body-max-line-length raised to 200 to match footer; retroactive fix for the four historical lint failures (#117/#118/#119/#120) caused by admin squash-merge prose over 100 cols.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ui): complete internal/ui rollout — spinners, huh prompts, migrate remaining call sites (#74 phases 2–4)

1 participant