Skip to content

feat(detector): implement NVM detection with mocked tests#2

Merged
dipto0321 merged 1 commit into
mainfrom
feat/detector/implement-nvm
Jun 29, 2026
Merged

feat(detector): implement NVM detection with mocked tests#2
dipto0321 merged 1 commit into
mainfrom
feat/detector/implement-nvm

Conversation

@dipto0321

Copy link
Copy Markdown
Owner

feat(detector): implement NVM detection with mocked tests

Implements manager #2 of 8 for Phase 1 (detection engine). Closes the
NVM stub that has been living in internal/detector/nvm.go since
scaffolding.

What this PR does

Replaces the zero-value stub methods on NVM with real
Detect / Version / ListInstalled implementations, and adds 23
unit tests covering each one.

Why NVM is special

NVM is a shell function, not a binary. It only exists after
source ~/.nvm/nvm.sh. There is no nvm executable on PATH. This
breaks the simple which fnm && fnm --version pattern that works
for every other manager.

Per nodeup.md §5 ("The nvm Special Case"), three strategies exist.
This PR uses a hybrid:

Method Strategy Subprocess?
Detect() File existence check on ~/.nvm/nvm.sh (or $NVM_DIR/nvm.sh) No
ListInstalled() Read <nvmDir>/versions/node/* directory entries (Strategy C) No
Version() source nvm.sh && nvm --version via platform.RunShellScript (Strategy A) Yes

The plan itself recommends Strategy C for reads ("most reliable for
listing/detecting versions") and Strategy A only for mutating
operations. We follow that.

Test seams

Two package-level vars, swappable per-test, mirroring the FNM
pattern from PR #1:

var listDir   = os.ReadDir                  // Strategy C: filesystem read
var runScript = platform.RunShellScript     // Strategy A: shell invocation

Plus a withStubNVMScript helper that writes a real (empty) nvm.sh
file to a temp dir for Detect() tests, sidestepping the complexity
of stubbing os.Stat.

23 tests cover:

  • parseNVMVersion × 5 (bare, prefixed, whitespace, empty, whitespace-only)
  • parseNVMInstalledEntries × 7 (standard layout, bare versions,
    skips system, skips non-dirs, skips unparseable, empty, only-system)
  • NVM.Name, NVM.Detect (2 paths), NVM.Version (3 paths),
    NVM.ListInstalled (4 paths), mutation stubs, GlobalNpmPrefix

Live verification

Probe (one-off dev binary, since deleted) against a real
~/.nvm install:

Detect:        true
Version:       0.40.5
Installed:     3 versions: [16.19.0 18.14.0 19.6.0]

The actual ~/.nvm/versions/node/ contains v16.19.0, v18.14.0,
v19.6.0. Output matches.

Out of scope (intentional)

Mutation methods (Install, Uninstall, Use, SetDefault,
GlobalNpmPrefix) return ErrNVMNotImplemented, same as FNM. They
will be filled in when the upgrade engine (Phase 2) needs them.

Test plan

  • go vet ./internal/detector/... — clean
  • go test -race ./internal/detector/... — 23 NVM tests pass,
    full package (FNM + registry + NVM) green
  • go build ./... — repo-wide build clean
  • go test -race ./... — all packages green
  • Live probe — confirmed against real ~/.nvm
  • CI on this PR — please watch the green ✓

NVM is a shell function, not a binary, so the implementation needs a
hybrid strategy per nodeup.md §5:

  - Detect       : check $NVM_DIR or ~/.nvm/nvm.sh existence (cheap,
                   no subprocess)
  - ListInstalled: read ~/.nvm/versions/node/* directory entries
                   directly (Strategy C, no subprocess)
  - Version      : source nvm.sh via platform.RunShellScript and run
                   nvm --version (Strategy A)

Mutation methods (Install, Uninstall, Use, SetDefault, GlobalNpmPrefix)
return a new ErrNVMNotImplemented sentinel, matching the FNM pattern.

Tests cover 23 cases on two package-level seams:
  - var listDir = os.ReadDir         (filesystem)
  - var runScript = platform.RunShellScript  (shell)

plus a withStubNVMScript helper that writes a real nvm.sh file to a
temp dir for Detect() tests, which avoids the complexity of stubbing
os.Stat.

Live probe against a real ~/.nvm install (nvm 0.40.5) confirms:
  - Detect:        true
  - Version:       0.40.5
  - Installed:     [16.19.0 18.14.0 19.6.0]  (sorted asc)
@dipto0321 dipto0321 force-pushed the feat/detector/implement-nvm branch from 3c24280 to 043e100 Compare June 29, 2026 07:22
@cocogitto-bot

cocogitto-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

✔️ 043e100 - Conventional commits check succeeded.

@dipto0321 dipto0321 merged commit c1491d9 into main Jun 29, 2026
10 checks passed
@dipto0321 dipto0321 deleted the feat/detector/implement-nvm branch June 29, 2026 07:24
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.

1 participant