Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ go test ./internal/detector/... -run TestNvm # single test
cmd/nodeup/main.go entrypoint; injects version/commit/date via ldflags
internal/cli/ cobra command wiring — thin layer, delegates to internal/
root.go NewRootCmd; registers all subcommands
upgrade.go nodeup upgrade (Phase 4, PR #20 open)
upgrade.go nodeup upgrade (Phase 4, merged)
check.go / list.go / packages.go / config.go / version.go
internal/detector/ Manager interface + one file per manager
detector.go DetectAll(), ResolveManager()
Expand All @@ -30,12 +30,14 @@ internal/node/
dist.go nodejs.org/dist/index.json client + 24h TTL cache
internal/packages/ npm global snapshot / restore / migrate (merged in PR #19)
snapshot.go Snapshot(ctx, managerName, version) → ~/.../snapshots/<mgr>-<ver>.json
Restore(ctx, managerName, version)
RestoreFromSnapshot(ctx, path)
Restore(ctx, managerName, version) ([]PackageResult, error)
RestoreFromSnapshot(ctx, path) ([]PackageResult, error)
report.go MigrationReport: per-package results, Save() → ~/.../reports/migration-<ts>.json
sentinel.go UpgradeSentinel: in-progress marker for interrupted-upgrade replay
internal/platform/
platform.go DataDir(), SnapshotsDir(), CacheDir(), LockPath(), IsWindows(), …
shell.go RunShell() — all shell exec goes here
internal/ui/ (planned, not yet implemented) all user-facing output
internal/ui/ scaffold (mode.go + theme.go + writer.go) merged via #74 PR1; spinners/huh/full migration tracked by #105
```

## Key invariants — read before writing code
Expand All @@ -50,9 +52,9 @@ internal/ui/ (planned, not yet implemented) all user-facing output

**Platform-specific code:** Use `//go:build windows` build tags on `*_windows.go` files. Files without build tags must compile on all three OSes.

**Dependencies:** No new dependencies without a rationale line in the PR body. Core runtime deps: `cobra`, `Masterminds/semver/v3`, `yaml.v3`. Planned but not yet in `go.mod`: `huh`, `bubbletea`, `lipgloss` (the Charm stack for `internal/ui` — see #74).
**Dependencies:** No new dependencies without a rationale line in the PR body. Core runtime deps: `cobra`, `Masterminds/semver/v3`, `yaml.v3`, `lipgloss` (for `internal/ui` — see #74). Planned but not yet in `go.mod`: `huh`, `bubbletea` (the remaining Charm stack for `internal/ui` spinners + prompts — see #105).

**Manager detection order:** `--manager` flag → `~/.nodeup/config.yaml` → auto-detect (env vars → PATH → well-known dirs). `DetectAll()` returns a `Registry`; `ResolveManager(reg, preferred)` picks one or errors. When multiple managers found and no preference, the caller should use `ResolveInteractive` (not yet implemented).
**Manager detection order:** `--manager` flag → `~/.nodeup/config.yaml` → auto-detect (env vars → PATH → well-known dirs). `DetectAll()` returns a `Registry`; `ResolveManager(reg, preferred)` picks one or errors. When multiple managers found and no preference, the caller should use `ResolveInteractive` (planned under #105 as part of the `huh` migration).

**Packages to skip during migration:** `npm`, `corepack`, `npx` — these are bundled with Node and must not be migrated.

Expand Down Expand Up @@ -125,8 +127,8 @@ orchestrator.
| 4 — Upgrade command + UI | Done | merged |
| 5 — Config subsystem | Done | merged |
| 6 — Cross-platform polish | Done | merged (interrupted-upgrade sentinel, `QuotePath`, system-node classifier) |
| 7 — Distribution packaging | In progress | partial — post-upgrade cleanup prompt merged; GoReleaser/brew/scoop/npm tracked by #17 / #18 |
| 8 — v1.0.0 release | In progress | blocked on Phase 7 distribution (#17 / #18) |
| 7 — Distribution packaging | Done | GoReleaser, brew/scoop/npm wrappers all shipped (#17, #18); manual v1.0.0 npm publish tracked by #35 |
| 8 — v1.0.0 release | Done | `nodeupx@1.0.1` live on npm (closes #35); trusted-publisher OIDC flow in place for future tags |

## On-disk data layout

Expand Down
58 changes: 32 additions & 26 deletions SPEC.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SPEC — nodeup

Distilled from code (README, CLAUDE.md, CONTRIBUTING.md, docs/*, and
`internal/*` source) on 2026-07-02, post-merge of PR #56 (`5ddbe43`).
`internal/*` source). Last refresh: 2026-07-05 (post #103 fix-wave;
§C/V2 + §T updated to match current state).
Caveman encoding per FORMAT.md convention (not present at repo root —
using shapes from the caveman skill).

Expand All @@ -25,9 +26,8 @@ manual steps, single static binary, no Node runtime dependency.
`/` or `\`.
- `*_windows.go` → `//go:build windows`; other files compile ∀ 3 OS
(macOS, Linux, Windows).
- ∀ user-facing output → `internal/ui` (? not yet implemented — currently
direct `cmd.Printf`/`fmt.Fprintf` throughout `internal/cli`, tolerated
as pre-`ui`-package debt).
- ∀ user-facing output → `internal/ui` (scaffold merged via #74 PR1;
spinners + huh prompts + full call-site migration tracked by #105).
- packages `{npm, corepack, npx}` ⊥ migrate (bundled w/ Node itself).
- Manager resolution order: `--manager` flag → `~/.nodeup/config.yaml` →
env vars → PATH → well-known dirs.
Expand All @@ -38,15 +38,16 @@ manual steps, single static binary, no Node runtime dependency.

- cmd: `nodeup upgrade [--lts|--current|--dry-run|--yes|--no-migrate|--no-cleanup|--cleanup|--cleanup-version <v>|--manager <name>]`
- cmd: `nodeup check [--json]`
- cmd: `nodeup list [--json]` (? stub only, ⊥ implemented — see T4)
- cmd: `nodeup list [--json]`
- cmd: `nodeup packages {snapshot|restore [--from <path>]|diff}`
- cmd: `nodeup config {show|get <key>|set <key> <val>|init [--force]}`
- cmd: `nodeup version`
- api: `GET https://nodejs.org/dist/index.json` → `Manifest ([]ManifestVersion)`
- file: `~/.nodeup/config.yaml` (override: `$NODEUP_CONFIG`; home override: `$NODEUP_HOME`)
- file: `<DataDir>/snapshots/<manager>-<node-version>.json`
- file: `<DataDir>/reports/migration-<timestamp>.json` (one per upgrade, success or partial)
- file: `<DataDir>/upgrade-in-progress.json` (sentinel)
- file: `<DataDir>/nodeup.lock` (? built, ⊥ wired — see T3)
- file: `<DataDir>/nodeup.lock`
- env: `NODEUP_MANAGER`, `NODEUP_TRACK_LTS`, `NODEUP_TRACK_CURRENT`,
`NODEUP_PACKAGES_MIGRATE`, `NODEUP_PACKAGES_STRATEGY`,
`NODEUP_CACHE_TTL`, `NODEUP_CONFIG`, `NODEUP_HOME`
Expand All @@ -61,7 +62,9 @@ manual steps, single static binary, no Node runtime dependency.
## §V Invariants

- V1: ∀ shell exec → `platform.RunShell()` | `RunShellScript()` (never raw `os/exec`).
- V2: ∀ user-facing string → `internal/ui` (? aspirational, see §C).
- V2: ∀ user-facing string → `internal/ui` (partial — scaffold via #74 PR1;
full migration tracked by #105; cmd.Printf still present in upgrade/check/
list/packages/config/cleanup).
- V3: ∀ error return → handled, wrapped w/ `%w` for context.
- V4: ∀ cobra `RunE` → `cmd.Context()`, ⊥ `context.Background()`.
- V5: ∀ path → `filepath.Join()`, ⊥ hardcoded separators.
Expand Down Expand Up @@ -91,29 +94,32 @@ manual steps, single static binary, no Node runtime dependency.
so version strings are safe as shell args by construction.
- V21: lock file (`<DataDir>/nodeup.lock`) ! held around any critical
section that mutates installed Node versions or the config file
concurrently (? built but not wired — see T3).
concurrently.

## §T Tasks

id|status|task|cites
T1|.|fix snapshot/restore version-key mismatch in upgrade flow|V13,#42
T2|.|fix NVM_DIR shell-injection surface (QuotePath doesn't escape `$`/backtick)|V15,#43
T3|.|wire AcquireLock/Release into upgrade + config set/init|V21,#44
T4|.|implement `nodeup list` (currently a stub)|I.list,#45
T5|.|fix installPackages abort-on-first-failure; wire or delete MigrationReport|V19,#46
T6|.|fix sentinel lifecycle (deleted on restore failure; never cleared after manual restore)|#47
T7|.|add HTTP timeout/retry/context + atomic cache write to node manifest fetch|#48
T8|.|fix `context.Background()` → `cmd.Context()` in internal/cli/packages.go|V4,#49
T9|.|fix asdf ASDF_DIR vs ASDF_DATA_DIR inconsistency (docs + system_node.go)|V7,#50
T10|.|validate manager-name arg against allowlist in packages restore/diff|V5,#51
T11|.|add context.Context param to Manager interface methods|V4,#53
T12|.|refresh CLAUDE.md known-bugs section + phase-status table|#54
T13|.|fix `--yes` silently overriding `--no-cleanup`|V16,V18,#57
T14|.|fail closed (not open) when Current() errors during cleanup|V14,#58
T15|.|verify/fix `n` manager's reliance on `n current` subcommand|#59
T16|.|fix cleanup docs inaccuracies (CHANGELOG issue ref, nvm-windows note, PATH failure mode, Phase 7 conflict)|#60
T17|.|dead-code cleanup: report.go, FileOverlay(), confirm var, fnm.go error wrap, nvm Current() test gap|#61
T18|.|design + implement internal/ui output layer|V2,#74
T1|x|fix snapshot/restore version-key mismatch in upgrade flow|V13,#42,#77
T2|x|fix NVM_DIR shell-injection surface (QuotePath doesn't escape `$`/backtick)|V15,#43,#78
T3|x|wire AcquireLock/Release into upgrade + config set/init|V21,#44,#79
T4|x|implement `nodeup list` (currently a stub)|I.list,#45,#80
T5|x|fix installPackages abort-on-first-failure; wire or delete MigrationReport|V19,#46,#103
T6|x|fix sentinel lifecycle (deleted on restore failure; never cleared after manual restore)|#47,#81
T7|x|add HTTP timeout/retry/context + atomic cache write to node manifest fetch|#48,#82
T8|x|fix `context.Background()` → `cmd.Context()` in internal/cli/packages.go|V4,#49,#83
T9|x|fix asdf ASDF_DIR vs ASDF_DATA_DIR inconsistency (docs + system_node.go)|V7,#50,#84
T10|x|validate manager-name arg against allowlist in packages restore/diff|V5,#51,#85
T11|x|add context.Context param to Manager interface methods|V4,#53,#101
T12|x|refresh CLAUDE.md known-bugs section + phase-status table|#54,#96
T13|x|fix `--yes` silently overriding `--no-cleanup`|V16,V18,#57,#86
T14|x|fail closed (not open) when Current() errors during cleanup|V14,#58,#87
T15|x|verify/fix `n` manager's reliance on `n current` subcommand|#59,#88
T16|x|fix cleanup docs inaccuracies (CHANGELOG issue ref, nvm-windows note, PATH failure mode, Phase 7 conflict)|#60,#97
T17|x|dead-code cleanup: report.go, FileOverlay(), confirm var, fnm.go error wrap, nvm Current() test gap|#61,#98
T18|.|design + implement internal/ui output layer|V2,#74,#105
T19|.|fix double-prompt + sticky-no-override at cleanup decision point|#76,#95
T20|x|rest of cleanup/chore/ci/release batch|#62-#69,#89-#94,#99,#100,#107-#109
T21|.|doc drift: CLAUDE.md ui/lipgloss/phase-table stale; SPEC §T stale|#106

## §B Bugs

Expand Down
Loading