From 7aaf6e7077e118751af9a28dd9b6f816d5693ec7 Mon Sep 17 00:00:00 2001 From: dipto0321 Date: Mon, 29 Jun 2026 20:13:22 +0600 Subject: [PATCH] docs: consolidate nodeup.md into README and remove the standalone design doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The internal nodeup.md blueprint served its purpose during initial scaffolding and the Phase 1 detector rollout, but is no longer needed as a tracked file. Its useful user-facing content (compatibility notes, commit conventions, branching rules) now lives in README.md, and its internal-only content (architecture diagrams, CI YAML, scoped decision matrices) is either in code comments, in the .commitlintrc.yml / .goreleaser.yaml configs themselves, or has been superseded by CHANGELOG.md. Changes: - README.md: add 'Compatibility notes' subsection (nvm shell function, multi-manager prompt, system-Node detection, skip list, native addon rebuild, lock file); expand Contributing with full branch-type list, commit type/scope enums, and the breaking-change convention; remove the 3 dangling 'See nodeup.md' references. - CHANGELOG.md: add Unreleased 'Changed' entry for the consolidation; rewrite the v0.0.0 entry to describe what the blueprint covered (without back-linking the deleted file). - internal/cli/*.go and internal/detector/*.go: replace 11 '// See nodeup.md §X' doc comments with package-local pointers (typically 'see internal/detector/detector.go for the Manager interface contract'). - docs/managers.md, docs/configuration.md: strip the two 'See nodeup.md §X' cross-refs at the bottom. - .commitlintrc.yml: rewrite the two comments that pointed at nodeup.md §12 to be self-contained. - .goreleaser.yaml: drop the 'See nodeup.md §15' trailing comment. - .github/ISSUE_TEMPLATE/feature_request.md: replace the 'see nodeup.md §3' link with the explicit out-of-scope list (.nvmrc management, npm self-update, yarn/pnpm globals, self-update). - nodeup.md: removed. .gitignore already lists it, so this is belt-and-suspenders for users who clone from a commit before this change. No code behavior changes. Verified: go build, go vet, go test -race, and GOOS=windows go build/vet all clean. --- .commitlintrc.yml | 8 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .goreleaser.yaml | 2 - CHANGELOG.md | 8 +- README.md | 59 +- docs/configuration.md | 4 +- docs/managers.md | 2 - internal/cli/check.go | 7 +- internal/cli/config.go | 1 - internal/cli/list.go | 5 +- internal/cli/packages.go | 2 +- internal/cli/upgrade.go | 5 +- internal/cli/version.go | 4 +- internal/detector/asdf.go | 3 +- internal/detector/detector.go | 3 +- internal/detector/fnm.go | 4 +- internal/detector/nvm.go | 5 +- internal/detector/volta.go | 4 +- nodeup.md | 1146 --------------------- 19 files changed, 81 insertions(+), 1193 deletions(-) delete mode 100644 nodeup.md diff --git a/.commitlintrc.yml b/.commitlintrc.yml index 3f239fd..2a150e3 100644 --- a/.commitlintrc.yml +++ b/.commitlintrc.yml @@ -10,9 +10,9 @@ extends: - '@commitlint/config-conventional' rules: - # Scopes are project-specific (see nodeup.md §12). We allow a small - # extra set: "deps" for dependency bumps, "release" for release-prep - # commits. + # Scopes are project-specific. The core set comes from the package + # layout (detector, cli, platform, ...); extras: "deps" for + # dependency bumps, "release" for release-prep commits. scope-enum: - 2 - always @@ -29,7 +29,7 @@ rules: - ci - docs - lint - # Type whitelist matches nodeup.md §12 exactly. + # Type whitelist matches Conventional Commits. type-enum: - 2 - always diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 8f53d1d..de191c8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -24,7 +24,7 @@ Who benefits from this? What's the workflow it unlocks? ## Out-of-scope check -Some things are explicitly out of scope for v1 (see [`nodeup.md`](../../nodeup.md) §3). Is your request covered there? +Some things are explicitly out of scope for v1 — at minimum: `.nvmrc` / `.node-version` management, updating `npm` itself, `yarn`/`pnpm` global packages, and `nodeup` self-updates. If your request falls in one of these buckets, comment on the existing tracking issue instead. - [ ] Yes — I'll comment on the existing tracking issue instead - [ ] No — this is a new request diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 51dc0e8..4874d12 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -16,8 +16,6 @@ # - A GitHub Release titled `v` with changelog and all artifacts # - A homebrew formula pushed to github.com/dipto0321/homebrew-tap # - A scoop manifest pushed to github.com/dipto0321/scoop-bucket -# -# See nodeup.md §15 for the rationale behind these choices. version: 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index d75e854..b921fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Consolidated the internal `nodeup.md` design doc into `README.md` + (new "Compatibility notes" subsection, expanded Contributing + conventions) and stripped the now-dangling `nodeup.md` references + from source, config, docs, and issue templates. The file is removed. + ### Added - Initial project scaffolding (`chore: initial project scaffolding`) - Cobra-based CLI with `upgrade`, `check`, `list`, `packages`, `config`, `version` subcommands @@ -22,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.0] - 2024-07-01 ### Added -- Project blueprint (`nodeup.md`) — design doc covering language choice, scope, detection engine, version resolution, package migration, architecture, CLI design, edge cases, git workflow, conventional commits, versioning, CI/CD, and distribution +- Project blueprint — internal design doc covering language choice, scope, detection engine, version resolution, package migration, architecture, CLI design, edge cases, git workflow, conventional commits, versioning, CI/CD, and distribution. (Superseded by `README.md`; the standalone doc was removed in the Unreleased section.) [Unreleased]: https://github.com/dipto0321/nodeup/compare/v0.0.0...HEAD [0.0.0]: https://github.com/dipto0321/nodeup/releases/tag/v0.0.0 \ No newline at end of file diff --git a/README.md b/README.md index b313f58..6e1925a 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,28 @@ them over, and (optionally) cleans up — all interactively, all resumable. - 🧪 **Dry-run mode** — see the plan before anything changes - 🔌 **Zero lock-in**: works on top of your existing manager, doesn't replace it +## Compatibility notes + +A few things worth knowing before you run `nodeup`: + +- **`nvm` is a shell function, not a binary** — `nodeup` transparently + sources `~/.nvm/nvm.sh` (or `$NVM_DIR/nvm.sh`) before calling it. No + setup required. +- **Multiple managers installed?** `nodeup` prompts you to pick one + the first time and remembers it in `~/.nodeup/config.yaml`. You can + override per-invocation with `--manager `. +- **System Node (e.g. installed via Homebrew, apt, or the Windows + installer) is detected but cannot be upgraded** — install a version + manager first if you want `nodeup` to manage it. +- **Bundled packages are always skipped during migration**: `npm`, + `corepack`, and `npx` ship with Node itself and are not reinstalled. +- **Native addons may need a rebuild** after a major Node version + bump. If something like `node-sass` or `sharp` misbehaves, run + `npm rebuild -g` against the new version. +- **Concurrent runs are blocked** via a lock file at + `~/.nodeup/nodeup.lock`. If a run crashes mid-upgrade, the next + invocation offers to restore from the snapshot written at the start. + ## Installation ### Homebrew (macOS, Linux) @@ -122,7 +144,7 @@ Run `nodeup --help` for the full flag reference. ## How it works -See [`nodeup.md`](./nodeup.md) for the full design doc. In short: +In short: 1. **Detect** which version manager(s) are installed 2. **Resolve** to a single manager (prompt if multiple) @@ -161,8 +183,7 @@ full schema. ## Project status -This is the **v1.0.0 development line**. See [`nodeup.md`](./nodeup.md) for the -phased execution plan and `CHANGELOG.md` for what's done. +This is the **v1.0.0 development line**. See `CHANGELOG.md` for what's done. | Version | Status | Notes | |---|---|---| @@ -170,15 +191,29 @@ phased execution plan and `CHANGELOG.md` for what's done. ## Contributing -Contributions welcome! See [`CONTRIBUTING.md`](./CONTRIBUTING.md) (TBD) and the -branching / commit conventions in [`nodeup.md`](./nodeup.md) §11–§12. - -TL;DR: - -- Branch from `main`: `feat//`, `fix//`, etc. -- Conventional Commits: `feat(detector): add Volta support` -- One PR per logical change -- CI must be green; `make ci` runs everything locally +Contributions welcome! A `CONTRIBUTING.md` will land alongside Phase 5; in the +meantime, this is the working contract. + +**Branching.** Branch from `main` using one of: +`feat//…`, `fix//…`, `chore//…`, +`docs/…`, `ci/…`, `test//…`, `refactor//…`. +`main` is protected — every change goes through a PR and is squash-merged +with the source branch deleted. Releases are tag-driven: pushing a +`v*.*.*` tag fires the GoReleaser workflow. + +**Commit messages — Conventional Commits.** Type and scope are +enforced by commitlint in CI. + +- Allowed **types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, + `test`, `build`, `ci`, `chore`, `revert`. +- Allowed **scopes**: `detector`, `manager`, `packages`, `node`, `config`, + `ui`, `platform`, `cli`, `deps`, `release`, `ci`, `docs`, `lint`. +- **Breaking changes** use the `!` marker in the type/scope header and a + `BREAKING CHANGE:` footer in the body, e.g. + `feat(config)!: drop legacy manager=auto key`. + +**Pull requests.** One PR per logical change. CI must be green; `make ci` +runs everything locally. ## License diff --git a/docs/configuration.md b/docs/configuration.md index 9c8953b..0f5a5f3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -24,6 +24,4 @@ Resolution precedence (highest first): | `packages.skip` | []string | `[npm, corepack, npx]` | Packages to never migrate | | `cleanup.auto` | bool | `false` | Auto-remove old versions | | `cleanup.prompt` | bool | `true` | Ask before removing each old version | -| `cache.ttl` | int | `3600` | Cache TTL in seconds | - -See `nodeup.md` §9 for the design rationale. \ No newline at end of file +| `cache.ttl` | int | `3600` | Cache TTL in seconds | \ No newline at end of file diff --git a/docs/managers.md b/docs/managers.md index 5f658a8..97389fd 100644 --- a/docs/managers.md +++ b/docs/managers.md @@ -27,8 +27,6 @@ priority order — earlier managers win when multiple are installed. ~/.nvm/nvm.sh && nvm "` so the function is loaded. 3. **Strategy B (fallback):** Use a binary wrapper if one is installed. -See `nodeup.md` §5 for the rationale. - ## Locking to a specific manager ```bash diff --git a/internal/cli/check.go b/internal/cli/check.go index 799f744..0d96e24 100644 --- a/internal/cli/check.go +++ b/internal/cli/check.go @@ -4,10 +4,9 @@ import "github.com/spf13/cobra" // newCheckCmd is a stub for Phase 0. It will be implemented in Phase 2. // -// See nodeup.md §6 (Node.js Version Resolution) — it calls the -// nodejs.org/dist/index.json endpoint, resolves the latest LTS and -// Current versions, and prints them alongside the user's installed -// versions without making any changes. +// It calls the nodejs.org/dist/index.json endpoint, resolves the latest +// LTS and Current versions, and prints them alongside the user's +// installed versions without making any changes. func newCheckCmd() *cobra.Command { cmd := &cobra.Command{ Use: "check", diff --git a/internal/cli/config.go b/internal/cli/config.go index ef82f40..382ad7a 100644 --- a/internal/cli/config.go +++ b/internal/cli/config.go @@ -4,7 +4,6 @@ import "github.com/spf13/cobra" // newConfigCmd is a stub for Phase 0. It will be implemented in Phase 5. // -// See nodeup.md §9 (CLI Design — nodeup config set/get/show). // The config file lives at ~/.nodeup/config.yaml and stores the manager // preference, tracked channels, migration strategy, cleanup policy, // and cache TTL. diff --git a/internal/cli/list.go b/internal/cli/list.go index 687171b..ea79f66 100644 --- a/internal/cli/list.go +++ b/internal/cli/list.go @@ -4,9 +4,8 @@ import "github.com/spf13/cobra" // newListCmd is a stub for Phase 0. It will be implemented in Phase 1. // -// See nodeup.md §5 (Version Manager Detection Engine). The command -// delegates to the detected manager's native "list" command, or -// enumerates versions directly from the manager's data directory. +// The command delegates to the detected manager's native "list" command, +// or enumerates versions directly from the manager's data directory. func newListCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list", diff --git a/internal/cli/packages.go b/internal/cli/packages.go index 73324ed..8d5810e 100644 --- a/internal/cli/packages.go +++ b/internal/cli/packages.go @@ -4,7 +4,7 @@ import "github.com/spf13/cobra" // newPackagesCmd is a stub for Phase 0. It will be implemented in Phase 3. // -// See nodeup.md §7 (Global Package Migration). Subcommands: +// Subcommands: // // nodeup packages snapshot — capture global npm packages for the active version // nodeup packages list — list packages for a snapshot diff --git a/internal/cli/upgrade.go b/internal/cli/upgrade.go index f06e139..478c936 100644 --- a/internal/cli/upgrade.go +++ b/internal/cli/upgrade.go @@ -6,9 +6,8 @@ import ( // newUpgradeCmd is a stub for Phase 0. It will be implemented in Phase 4. // -// See nodeup.md §4 (Core Algorithm) and §9 (CLI Design) for the planned -// behavior: detect manager → fetch versions → diff → snapshot → install → -// migrate → cleanup. +// Planned behavior: detect manager → fetch versions → diff → snapshot → +// install → migrate → cleanup. func newUpgradeCmd() *cobra.Command { cmd := &cobra.Command{ Use: "upgrade", diff --git a/internal/cli/version.go b/internal/cli/version.go index ea31a75..884a360 100644 --- a/internal/cli/version.go +++ b/internal/cli/version.go @@ -11,7 +11,7 @@ import ( // // It prints the version, git commit, build date, and runtime info (Go // version, OS, architecture). The --check flag is reserved for a future -// self-update mechanism (out of scope for v1.0.0 per nodeup.md §3). +// self-update mechanism (out of scope for v1.0.0). func newVersionCmd(version, commit, date string) *cobra.Command { var check bool @@ -33,7 +33,7 @@ Example: fmt.Fprintf(out, " platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) if check { - // Self-update is a v2 feature (see nodeup.md §3 "Out of Scope"). + // Self-update is a v2 feature — out of scope for v1. // We intentionally do nothing here but make the flag a // no-op rather than failing, so user scripts that pre-set // the flag don't break. diff --git a/internal/detector/asdf.go b/internal/detector/asdf.go index 2c797e1..9bccc10 100644 --- a/internal/detector/asdf.go +++ b/internal/detector/asdf.go @@ -15,7 +15,8 @@ import ( ) // ASDF is the asdf-vm implementation (https://asdf-vm.com) with the -// nodejs plugin installed. See nodeup.md §5 for the detection strategy. +// nodejs plugin installed. See internal/detector/detector.go for the +// Manager interface contract. // // ASDF is a true binary (unlike NVM). Its installed Node versions are // queryable via `asdf list nodejs`, which is more reliable than parsing diff --git a/internal/detector/detector.go b/internal/detector/detector.go index 0d4df29..6622935 100644 --- a/internal/detector/detector.go +++ b/internal/detector/detector.go @@ -30,7 +30,8 @@ import ( var ErrNoManager = errors.New("no Node.js version manager detected") // Manager is the abstraction every concrete manager implementation -// satisfies. See nodeup.md §5 for the full contract. +// satisfies. See the doc comments on individual Manager methods for +// the full contract. type Manager interface { // Name returns the canonical short name: "fnm", "nvm", "volta", ... Name() string diff --git a/internal/detector/fnm.go b/internal/detector/fnm.go index c71d6fd..549dbd9 100644 --- a/internal/detector/fnm.go +++ b/internal/detector/fnm.go @@ -12,8 +12,8 @@ import ( "github.com/dipto0321/nodeup/internal/platform" ) -// FNM is the Fast Node Manager implementation. See nodeup.md §5 for the -// detection strategy and the supported command surface. +// FNM is the Fast Node Manager implementation. See +// internal/detector/detector.go for the Manager interface contract. // // Phase 1 implements the detection surface only: // - Detect : cheap PATH probe via exec.LookPath diff --git a/internal/detector/nvm.go b/internal/detector/nvm.go index 8f33a36..44dc288 100644 --- a/internal/detector/nvm.go +++ b/internal/detector/nvm.go @@ -15,8 +15,9 @@ import ( ) // NVM is the Node Version Manager implementation. nvm is unusual because -// it is a SHELL FUNCTION, not a binary. See nodeup.md §5 "The nvm Special -// Case" for the three strategies we use. +// it is a SHELL FUNCTION, not a binary — NVM is a shell function +// wrapper around the real `nvm` script, so we must source it before +// running any subcommand. // // Strategy C is used for reads (parse ~/.nvm/versions/node/* directly). // For mutating operations (install, uninstall, use) we will fall back to diff --git a/internal/detector/volta.go b/internal/detector/volta.go index 7fc42cf..d1634f2 100644 --- a/internal/detector/volta.go +++ b/internal/detector/volta.go @@ -17,8 +17,8 @@ import ( // Volta is the Volta implementation (https://volta.sh). Volta is a true // binary (unlike NVM, which is a shell function), but it stores Node // installs in a fixed on-disk layout under $VOLTA_HOME rather than -// advertising them through a CLI query. See nodeup.md §5 for the full -// detection strategy. +// advertising them through a CLI query. See +// internal/detector/detector.go for the Manager interface contract. // // Volta's on-disk layout (v2.x, "v4" internal layout): // diff --git a/nodeup.md b/nodeup.md deleted file mode 100644 index e316509..0000000 --- a/nodeup.md +++ /dev/null @@ -1,1146 +0,0 @@ -# `nodeup` — Full Project Blueprint - -> Automated Node.js version upgrade + global package migration CLI -> Cross-platform · Multi-manager · Zero manual steps - ---- - -## Table of Contents - -1. [Why This Tool Exists](#1-why-this-tool-exists) -2. [Language Choice: Go](#2-language-choice-go) -3. [Tool Name & Scope](#3-tool-name--scope) -4. [How It Works — Core Algorithm](#4-how-it-works--core-algorithm) -5. [Version Manager Detection Engine](#5-version-manager-detection-engine) -6. [Node.js Version Resolution](#6-nodejs-version-resolution) -7. [Global Package Migration](#7-global-package-migration) -8. [Architecture & Project Structure](#8-architecture--project-structure) -9. [CLI Design & Commands](#9-cli-design--commands) -10. [Edge Cases & Error Handling](#10-edge-cases--error-handling) -11. [Git & GitHub Workflow](#11-git--github-workflow) -12. [Conventional Commits](#12-conventional-commits) -13. [Versioning Strategy](#13-versioning-strategy) -14. [CI/CD with GitHub Actions](#14-cicd-with-github-actions) -15. [Publishing & Distribution](#15-publishing--distribution) -16. [Phased Execution Plan](#16-phased-execution-plan) - ---- - -## 1. Why This Tool Exists - -Every time Node.js releases a new LTS or Current version, a developer using `fnm` or `nvm` must: - -1. Look up the new LTS version number at nodejs.org -2. Run `fnm install ` or `nvm install --lts` -3. Switch to the old version, run `npm list -g --depth=0`, copy the list -4. Switch to the new version, re-install each package manually -5. Optionally remove the old version -6. Repeat for Current if they track that too - -`nodeup` collapses all of that into one command: `nodeup upgrade`. - ---- - -## 2. Language Choice: Go - -| Criterion | Go | Node.js | Python | Rust | -|---|---|---|---|---| -| Single binary | ✅ Yes | ❌ Needs runtime | ❌ Needs runtime | ✅ Yes | -| Cross-platform build | ✅ `GOOS/GOARCH` | ⚠️ Partial | ⚠️ Partial | ✅ Yes | -| Bootstrap paradox | ✅ None | ❌ Needs Node to manage Node | ✅ None | ✅ None | -| Distribution (Homebrew/Scoop) | ✅ First-class | ⚠️ Via npm only | ⚠️ Via pip/pypi | ✅ First-class | -| Compilation speed | ✅ Fast | N/A | N/A | ⚠️ Slow | -| Shell execution | ✅ `os/exec` | ✅ `child_process` | ✅ `subprocess` | ✅ `std::process` | -| GoReleaser support | ✅ Native | ❌ No | ❌ No | ✅ Via cargo | -| Learning curve | 🟡 Moderate | ✅ You know it | ✅ Easy | 🔴 Steep | - -**Go wins** because: -- The bootstrap paradox is a real UX risk — if the tool is written in Node.js and the user's Node.js install is broken, the tool can't run. -- GoReleaser makes publishing cross-platform binaries to GitHub Releases, Homebrew, and Scoop nearly automatic. -- A single static binary means zero dependency installation for end users. - -### Key Go Libraries - -``` -github.com/spf13/cobra # CLI framework (industry standard) -github.com/charmbracelet/bubbletea # TUI / interactive prompts -github.com/charmbracelet/lipgloss # Terminal styling -github.com/charmbracelet/huh # Form/select UI -github.com/tidwall/gjson # Fast JSON parsing (nodejs.org API) -github.com/Masterminds/semver/v3 # Semantic version comparison -``` - ---- - -## 3. Tool Name & Scope - -**Name: `nodeup`** - -Alternatives considered: `nvmate`, `nodejump`, `nodekick`, `nshift` - -`nodeup` reads naturally as "upgrade Node" and is short for shell use. - -### In Scope (v1) -- Detect installed version managers -- Fetch LTS + Latest versions from nodejs.org API -- List global packages per installed Node version -- Install new Node versions via the detected manager -- Migrate global packages to new versions -- Remove old Node versions (opt-in) -- Dry-run mode - -### Out of Scope (v1, consider for v2) -- Managing `.nvmrc` / `.node-version` files in projects -- Updating npm itself -- Managing yarn/pnpm global packages -- GUI / Electron wrapper -- Self-update mechanism - ---- - -## 4. How It Works — Core Algorithm - -``` -nodeup upgrade - │ - ▼ -┌──────────────────────────────┐ -│ 1. DETECT VERSION MANAGERS │ -│ Scan $PATH + env vars │ -│ Found: [fnm, nvm] │ -└──────────┬───────────────────┘ - │ multiple found? - ▼ -┌──────────────────────────────┐ -│ 2. RESOLVE MANAGER │ -│ Single → use it │ -│ Multiple → prompt user │ -└──────────┬───────────────────┘ - │ - ▼ -┌──────────────────────────────┐ -│ 3. FETCH NODE VERSIONS │ -│ GET nodejs.org/dist/index │ -│ Resolve: LTS latest = 20.x │ -│ Resolve: Current = 22.x │ -└──────────┬───────────────────┘ - │ - ▼ -┌──────────────────────────────┐ -│ 4. DIFF INSTALLED vs REMOTE │ -│ fnm list → [18.x, 20.x] │ -│ Remote LTS = 20.15.0 ✅ │ -│ Remote Current = 22.5.0 ✨ │ -│ "22.x not installed" │ -└──────────┬───────────────────┘ - │ - ▼ -┌──────────────────────────────┐ -│ 5. SNAPSHOT GLOBAL PKGS │ -│ npm list -g --depth=0 --json│ -│ Per version: snapshot saved │ -└──────────┬───────────────────┘ - │ - ▼ -┌──────────────────────────────┐ -│ 6. INSTALL NEW VERSIONS │ -│ fnm install 22.5.0 │ -│ Verify install succeeded │ -└──────────┬───────────────────┘ - │ - ▼ -┌──────────────────────────────┐ -│ 7. MIGRATE GLOBAL PACKAGES │ -│ fnm use 22.5.0 │ -│ npm install -g │ -│ Report: ✅ installed / ❌ fail│ -└──────────┬───────────────────┘ - │ - ▼ -┌──────────────────────────────┐ -│ 8. CLEANUP (opt-in) │ -│ Prompt: remove 20.x? (y/N) │ -│ fnm uninstall 20.x │ -└──────────────────────────────┘ -``` - ---- - -## 5. Version Manager Detection Engine - -### Detection Strategy - -Detection runs in **this priority order**: - -``` -1. Check CLI flags (--manager fnm) ← user override, highest priority -2. Read config file (~/.nodeup.yaml) -3. Auto-detect from environment -4. Prompt if ambiguous -``` - -### Auto-Detection Logic (per manager) - -``` -fnm - ├── env: FNM_DIR exists - ├── binary: `which fnm` resolves - └── files: ~/.local/share/fnm (Linux), ~/Library/Application Support/fnm (macOS) - -nvm - ├── env: NVM_DIR exists - ├── shell: nvm is a shell function (not a binary) - └── files: ~/.nvm/nvm.sh exists - ⚠️ nvm is a SHELL FUNCTION, not an executable. Must be sourced. - Strategy: `bash -i -c "nvm --version"` to detect - Or: check for ~/.nvm/nvm.sh directly - -volta - ├── env: VOLTA_HOME exists - ├── binary: `which volta` resolves - └── files: ~/.volta/bin/volta - -asdf (with nodejs plugin) - ├── env: ASDF_DIR exists OR ~/.asdf exists - ├── binary: `which asdf` resolves - └── plugin: `asdf plugin list | grep nodejs` - -mise (formerly rtx, asdf successor) - ├── binary: `which mise` resolves - └── plugin: `mise plugins list | grep node` - -n (npm-based version manager) - ├── binary: `which n` resolves - └── env: N_PREFIX (optional) - -nodenv - ├── binary: `which nodenv` resolves - └── files: ~/.nodenv/shims - -nvm-windows (Windows only) - ├── binary: `nvm.exe` in PATH - └── registry: HKCU\Software\nvm-windows -``` - -### Multi-Manager Matrix - -```go -// internal/detector/detector.go (concept) - -type Manager interface { - Name() string - Detect() bool - Version() (string, error) - ListInstalled() ([]semver.Version, error) - Install(version semver.Version) error - Uninstall(version semver.Version) error - Use(version semver.Version) error - SetDefault(version semver.Version) error - GlobalNpmPrefix(version semver.Version) (string, error) -} - -func DetectAll() []Manager { - candidates := []Manager{ - &FNM{}, &NVM{}, &Volta{}, &ASDF{}, &Mise{}, &N{}, &Nodenv{}, - } - // On Windows, add NVMWindows{} - - var found []Manager - for _, m := range candidates { - if m.Detect() { - found = append(found, m) - } - } - return found -} -``` - -### The nvm Special Case - -`nvm` is NOT a binary — it's a shell function loaded by `.bashrc`/`.zshrc`. This is the trickiest part of the entire project. - -**Three strategies:** - -``` -Strategy A: Direct script execution (preferred) - source ~/.nvm/nvm.sh && nvm - Spawned as: bash -c "source ~/.nvm/nvm.sh && nvm list" - Portable, but requires bash - -Strategy B: Call nvm shim if it exists - Some installs create an nvm binary wrapper - -Strategy C: Parse ~/.nvm/alias/default and ~/.nvm/versions/node/* - Read the filesystem directly without invoking nvm at all - Most reliable for listing/detecting versions - Use Strategy A only for install/uninstall -``` - ---- - -## 6. Node.js Version Resolution - -### The nodejs.org API - -``` -GET https://nodejs.org/dist/index.json -``` - -Returns an array of ALL Node.js releases. Each entry looks like: - -```json -{ - "version": "v22.5.0", - "date": "2024-07-17", - "files": ["..."], - "npm": "10.8.2", - "v8": "12.7.130.2", - "lts": false, - "security": false -} -``` - -- `lts: false` means it's a Current (non-LTS) release -- `lts: "Iron"` means it IS an LTS release (the string is the codename) - -### Resolution Rules - -``` -LTS Latest = max(version) where lts !== false -Current = max(version) where lts === false AND not a release candidate -LTS Active = all versions where lts !== false AND EOL date > today -Maintenance = versions in security-only maintenance mode -``` - -### Caching - -Cache the index.json response for 1 hour to avoid rate limits: -``` -~/.nodeup/cache/node-dist-index.json -~/.nodeup/cache/node-dist-index.json.ttl -``` - ---- - -## 7. Global Package Migration - -### Snapshot Format - -```json -{ - "nodeVersion": "20.14.0", - "snapshotDate": "2024-07-20T10:30:00Z", - "manager": "fnm", - "packages": [ - { "name": "typescript", "version": "5.4.2", "global": true }, - { "name": "nodemon", "version": "3.1.0", "global": true }, - { "name": "pnpm", "version": "9.1.0", "global": true } - ] -} -``` - -Saved to: `~/.nodeup/snapshots/-.json` - -### Migration Logic - -``` -1. Switch to OLD version via manager -2. Run: npm list -g --depth=0 --json -3. Parse output → package list -4. Save snapshot to disk -5. Switch to NEW version via manager -6. For each package: - a. npm install -g @ # pin exact (conservative) - OR - npm install -g # latest (aggressive, default) - b. Verify: npm list -g - c. Record result: ✅ success | ❌ failed | ⚠️ installed but different version -7. Write migration report -``` - -### Packages to Skip - -Some global "packages" are actually part of the Node.js install itself and should never be migrated: - -```go -var skipPackages = map[string]bool{ - "npm": true, // managed by Node version itself - "corepack": true, - "npx": true, -} -``` - ---- - -## 8. Architecture & Project Structure - -``` -nodeup/ -├── cmd/ -│ └── nodeup/ -│ └── main.go # Entrypoint, cobra root command -│ -├── internal/ # Not exported (internal use only) -│ ├── detector/ -│ │ ├── detector.go # DetectAll(), ResolveManager() -│ │ ├── fnm.go -│ │ ├── nvm.go -│ │ ├── volta.go -│ │ ├── asdf.go -│ │ ├── mise.go -│ │ ├── n.go -│ │ ├── nodenv.go -│ │ └── nvm_windows.go # build tag: //go:build windows -│ │ -│ ├── node/ -│ │ ├── dist.go # nodejs.org API client -│ │ ├── version.go # Version parsing, LTS resolution -│ │ └── cache.go # Response caching -│ │ -│ ├── packages/ -│ │ ├── global.go # npm list -g parsing -│ │ ├── snapshot.go # Save/load snapshots -│ │ └── migrate.go # Migration logic -│ │ -│ ├── config/ -│ │ ├── config.go # ~/.nodeup/config.yaml -│ │ └── defaults.go -│ │ -│ ├── ui/ -│ │ ├── prompt.go # huh-based interactive prompts -│ │ ├── spinner.go # bubbletea spinner for long ops -│ │ └── report.go # Final summary table -│ │ -│ └── platform/ -│ ├── platform.go # OS detection helpers -│ ├── shell.go # Shell command execution helpers -│ └── paths.go # XDG-compliant path resolution -│ -├── .github/ -│ ├── workflows/ -│ │ ├── ci.yml # Lint + test on every PR -│ │ └── release.yml # GoReleaser on tag push -│ ├── ISSUE_TEMPLATE/ -│ │ ├── bug_report.md -│ │ └── feature_request.md -│ └── PULL_REQUEST_TEMPLATE.md -│ -├── docs/ -│ ├── installation.md -│ ├── configuration.md -│ └── managers.md # Per-manager notes -│ -├── .goreleaser.yaml # Build + publish config -├── .golangci.yml # Linter config -├── go.mod -├── go.sum -├── Makefile -├── README.md -├── CHANGELOG.md -└── LICENSE # MIT -``` - -### Key Design Principles - -- `internal/` packages cannot be imported by external projects (Go enforces this) -- Each version manager is its own file implementing the `Manager` interface -- Platform-specific code uses Go build tags: `//go:build windows` -- All user-facing strings go through `ui/` — never `fmt.Println` in business logic -- Config is read once at startup and passed down (no global state) - ---- - -## 9. CLI Design & Commands - -### Command Tree - -``` -nodeup -├── upgrade # Main command — does the full flow -│ ├── --lts # Upgrade LTS only -│ ├── --current # Upgrade Current only -│ ├── --dry-run # Show what would happen, no changes -│ ├── --no-migrate # Skip package migration -│ ├── --no-cleanup # Skip asking about old version removal -│ ├── --manager # Force a specific manager -│ └── --yes # Non-interactive, assume yes to all prompts -│ -├── check # Check what versions are available, no changes -│ └── --json # Output as JSON -│ -├── list # List installed Node versions (via detected manager) -│ └── --json -│ -├── packages # Manage global package snapshots -│ ├── snapshot # Take a snapshot of current global packages -│ ├── list # List packages for a Node version -│ ├── restore # Re-install from a saved snapshot -│ └── diff # Show diff between two snapshots -│ -├── config # Manage nodeup config -│ ├── set -│ ├── get -│ └── show -│ -└── version # Print nodeup version - └── --check # Check if nodeup itself has a newer release -``` - -### Sample Output - -``` -$ nodeup upgrade - - nodeup v1.2.0 - - Detecting version managers... - ✓ Found: fnm (v1.35.1) - - Fetching Node.js release data... - ✓ LTS: v20.15.0 (Iron) - ✓ Current: v22.5.0 - - Installed versions: - v18.20.3 (will be superseded by LTS) - v20.14.0 (outdated LTS — 20.15.0 available) - - Plan: - ───────────────────────────────────────────────────── - [1] Upgrade LTS: 20.14.0 → 20.15.0 - Global packages: typescript@5.4.2, nodemon@3.1.0 - [2] Install Current: 22.5.0 (new) - Migrate from: 20.14.0 snapshot - ───────────────────────────────────────────────────── - - Proceed? (Y/n): Y - - [1/2] Upgrading LTS to 20.15.0... - → Snapshotting packages from 20.14.0 ✓ - → Installing 20.15.0 via fnm ✓ - → Migrating typescript@5.4.2 ✓ - → Migrating nodemon@3.1.0 ✓ - → Setting 20.15.0 as LTS default ✓ - - [2/2] Installing Current 22.5.0... - → Installing 22.5.0 via fnm ✓ - → Migrating typescript@5.4.2 ✓ - → Migrating nodemon@3.1.0 ✓ - - Remove old versions? - 20.14.0 (no longer default) → Remove? (y/N): y - 18.20.3 → Remove? (y/N): n - - ───────────────────────────────────────────────────── - Done in 43s - Active: Node.js v20.15.0 (LTS Iron) | npm v10.7.0 - ───────────────────────────────────────────────────── -``` - -### `nodeup.yaml` Config File - -```yaml -# ~/.nodeup/config.yaml -manager: fnm # lock to a specific manager -track: - lts: true # track LTS upgrades - current: false # don't track Current -packages: - migrate: true - strategy: exact # "exact" | "latest" - skip: - - npm - - corepack -cleanup: - auto: false # never auto-remove old versions - prompt: true -cache: - ttl: 3600 # seconds -``` - ---- - -## 10. Edge Cases & Error Handling - -### Detection Edge Cases - -| Scenario | Handling | -|---|---| -| No manager found | Clear error: "No Node.js version manager detected. Install fnm, nvm, or volta first." | -| Manager found but Node not installed | "fnm detected but no Node versions installed. Run: fnm install --lts" | -| Multiple managers found | Interactive prompt: "We found fnm and nvm. Which do you want to use?" | -| System Node (installed via pkg manager) | Detect `/usr/local/bin/node` not under manager path, warn: "System Node detected but not managed by a version manager. nodeup cannot upgrade it." | -| Manager binary exists but broken | Capture stderr, report: "fnm found but returned an error: " | - -### Network Edge Cases - -| Scenario | Handling | -|---|---| -| No internet | Use cached index.json if fresh enough. Error if stale: "No internet and cache is >24h old." | -| nodejs.org rate limit | Retry with exponential backoff (3 retries) | -| Corporate proxy | Respect HTTP_PROXY / HTTPS_PROXY environment variables | -| Partial JSON response | Error out, don't corrupt state | - -### Package Migration Edge Cases - -| Scenario | Handling | -|---|---| -| Package incompatible with new Node | Log as ⚠️ warning, continue with others. Report at end. | -| Package install fails (network) | Retry once. Log failure. Include in migration report. | -| Package was installed from git URL | npm list shows the git URL — install it as-is | -| Package has no version (linked) | Skip it with a note: "myproject is a linked package, skipping" | -| Migrating to much newer Node | Warn if major Node version gap > 2 (e.g., 16 → 22) | -| npm itself is being upgraded | Skip npm from migration; it comes bundled | -| Packages with native addons | Warn: these may need rebuild. `npm rebuild` hint. | - -### Platform Edge Cases - -| Scenario | Handling | -|---|---| -| Windows + nvm-windows | `nvm.exe` works as a real binary, different command syntax. Needs `nvm arch 64` consideration. | -| Windows path separators | Use `filepath.Join()` everywhere, never hardcode `/` | -| Windows + spaces in path | Quote all paths in shell commands | -| macOS + Apple Silicon vs Intel | GoReleaser builds `darwin/arm64` and `darwin/amd64` + universal binary | -| Linux + flatpak/snap Node | Detect if node binary is inside a flatpak/snap path and warn | -| Permission denied on global npm | Suggest `--prefix` fix or sudo warning | -| Shell not in PATH (nvm) | Source ~/.nvm/nvm.sh explicitly before each nvm command | - -### State Corruption Edge Cases - -| Scenario | Handling | -|---|---| -| Install succeeds, migration fails | Node is installed. Migration report saved. User can `nodeup packages restore` | -| Interrupted mid-migration | On next run: detect partial state from snapshot + migration log | -| Disk full during install | Catch error, suggest cleaning old versions | -| Concurrent nodeup runs | Lock file: `~/.nodeup/nodeup.lock` (check + fail fast) | - ---- - -## 11. Git & GitHub Workflow - -### GitHub Flow (the model) - -``` -main ─────────────────────────────────────────────────────────────► - │ │ │ - │ feat/detect-volta │ fix/nvm-source │ v1.1.0 tag - └──────────┐ └────────┐ │ - │ PR │ PR │ - └──────────────────┘ tag → GoReleaser -``` - -Rules: -- `main` is **protected** — no direct pushes -- Every change goes through a **PR**, even solo work -- `main` is always in a releasable state -- Tags trigger releases (`v1.0.0`, `v1.1.0`, `v2.0.0`) - -### Branch Naming Convention - -``` -feat// -fix// -chore// -docs/ -test// -ci/ -refactor// -``` - -**Real examples:** -``` -feat/detector/add-volta-support -feat/detector/add-mise-support -feat/packages/add-yarn-global-migration -fix/nvm/handle-bash-source-on-zsh -fix/windows/path-separator-in-nvm-windows -chore/deps/update-cobra-v1.9 -docs/add-homebrew-install-guide -ci/add-windows-runner -test/manager/fnm-integration-tests -refactor/detector/extract-manager-interface -``` - -### PR Workflow - -``` -1. Create branch from main: - git checkout main && git pull - git checkout -b feat/detector/add-volta-support - -2. Commit with conventional format (see Section 12) - -3. Push and open PR: - git push -u origin feat/detector/add-volta-support - gh pr create --title "feat(detector): add Volta support" --body "..." - -4. CI runs: - - golangci-lint - - go test ./... - - go build (matrix: linux/mac/windows) - -5. Merge via "Squash and Merge" to keep main history clean - -6. Delete branch after merge -``` - -### Release Flow - -``` -1. All features for the release are merged to main - -2. Update CHANGELOG.md: - git checkout -b chore/release/v1.1.0 - # edit CHANGELOG.md - git commit -m "chore(release): prepare v1.1.0" - # open PR, merge - -3. Tag on main: - git checkout main && git pull - git tag -a v1.1.0 -m "Release v1.1.0" - git push origin v1.1.0 - -4. GitHub Actions: release.yml fires - GoReleaser builds + publishes binaries - GitHub Release created automatically - Homebrew formula auto-updated -``` - ---- - -## 12. Conventional Commits - -### Format - -``` -(): - -[optional body] - -[optional footer(s)] -``` - -### Types - -| Type | When to use | -|---|---| -| `feat` | New feature (triggers MINOR version bump) | -| `fix` | Bug fix (triggers PATCH version bump) | -| `docs` | Documentation only | -| `chore` | Maintenance, dependency updates | -| `test` | Adding or fixing tests | -| `ci` | CI/CD changes | -| `refactor` | Code restructure, no behavior change | -| `perf` | Performance improvement | -| `style` | Formatting, no logic change | -| `build` | Build system changes | - -### Scopes (for this project) - -``` -detector # Manager detection logic -manager # Individual manager implementations (fnm, nvm, etc.) -packages # Global package migration -node # nodejs.org API / version resolution -config # Config file handling -ui # Terminal output / prompts -platform # OS / platform-specific code -deps # Dependency updates -release # Release process -``` - -### Real Commit Examples - -```bash -# Feature commits -git commit -m "feat(detector): add Volta detection via VOLTA_HOME env var" -git commit -m "feat(manager): implement fnm install and uninstall commands" -git commit -m "feat(packages): add snapshot save/restore for global packages" -git commit -m "feat(ui): add interactive manager selection prompt" -git commit -m "feat(node): cache nodejs.org dist index with TTL" - -# Fix commits -git commit -m "fix(nvm): source nvm.sh in bash -i context for shell function" -git commit -m "fix(platform): handle Windows paths with spaces in quotes" -git commit -m "fix(packages): skip npm and corepack from migration list" -git commit -m "fix(detector): prefer fnm over nvm when both are detected" - -# Chore / CI commits -git commit -m "chore(deps): update cobra from v1.8.0 to v1.9.0" -git commit -m "ci: add matrix for windows/linux/macos runners" -git commit -m "chore(release): prepare v1.0.0 changelog" - -# Breaking change (triggers MAJOR version bump) -git commit -m "feat(config)!: rename manager_preference to manager in config - -BREAKING CHANGE: config key renamed from manager_preference to manager. -Update your ~/.nodeup/config.yaml accordingly." -``` - -### Enforcing Conventional Commits - -Use `commitlint` locally and in CI: - -```yaml -# .commitlintrc.yml -extends: - - '@commitlint/config-conventional' -rules: - scope-enum: - - 2 - - always - - [detector, manager, packages, node, config, ui, platform, deps, release] -``` - -```yaml -# .github/workflows/ci.yml (snippet) -- name: Lint commits - uses: wagoid/commitlint-github-action@v5 -``` - ---- - -## 13. Versioning Strategy - -### Semantic Versioning (SemVer) - -``` -v.. - -MAJOR: Breaking change (config format change, removed command, changed output format) -MINOR: New feature (new manager support, new command, new flag) -PATCH: Bug fix (edge case fix, wrong output, crash fix) -``` - -### Release Cadence - -``` -v0.1.0 — Alpha: core flow works for fnm + macOS only -v0.2.0 — Add nvm support -v0.3.0 — Add Windows support (nvm-windows) -v0.4.0 — Add Volta support -v0.5.0 — Add ASDF/Mise support -v0.6.0 — Add packages diff, restore commands -v0.7.0 — Add config file support -v0.8.0 — Add dry-run mode -v0.9.0 — Beta: polish UI, full test coverage -v1.0.0 — Stable: all planned managers supported, docs complete -``` - -### Pre-release Tags - -``` -v1.0.0-alpha.1 # early testers -v1.0.0-beta.1 # wider testing -v1.0.0-rc.1 # release candidate -v1.0.0 # stable -``` - -### CHANGELOG.md Format (Keep a Changelog) - -```markdown -# Changelog - -All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - -## [Unreleased] - -## [1.1.0] - 2024-08-15 -### Added -- Volta version manager support (#42) -- `--dry-run` flag for upgrade command (#38) - -### Fixed -- nvm detection fails on zsh when NVM_DIR not exported (#40) - -## [1.0.0] - 2024-07-20 -### Added -- Initial stable release -- fnm, nvm support -- Global package migration -- macOS, Linux, Windows support -``` - ---- - -## 14. CI/CD with GitHub Actions - -### ci.yml — Runs on every PR and push to main - -```yaml -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - uses: golangci/golangci-lint-action@v4 - - test: - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - run: go test ./... -v -race -coverprofile=coverage.txt - - uses: codecov/codecov-action@v4 - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - run: go build ./cmd/nodeup -``` - -### release.yml — Runs on tag push (v*.*.*) - -```yaml -name: Release - -on: - push: - tags: - - 'v*.*.*' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Full history for changelog - - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} -``` - ---- - -## 15. Publishing & Distribution - -### GoReleaser Configuration - -```yaml -# .goreleaser.yaml -version: 2 - -before: - hooks: - - go mod tidy - -builds: - - id: nodeup - main: ./cmd/nodeup - binary: nodeup - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - goarch: - - amd64 - - arm64 - ignore: - - goos: windows - goarch: arm64 - -archives: - - format: tar.gz - format_overrides: - - goos: windows - format: zip - name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' - -checksum: - name_template: 'checksums.txt' - -brews: - - name: nodeup - repository: - owner: dipto0321 - name: homebrew-tap - token: "{{ .Env.HOMEBREW_TAP_TOKEN }}" - homepage: "https://github.com/dipto0321/nodeup" - description: "Automated Node.js version upgrade with global package migration" - license: "MIT" - -scoops: - - repository: - owner: dipto0321 - name: scoop-bucket - homepage: "https://github.com/dipto0321/nodeup" - description: "Automated Node.js version upgrade with global package migration" - license: MIT - -changelog: - use: git - sort: asc - filters: - exclude: - - "^docs:" - - "^chore:" - - "^test:" -``` - -### Distribution Channels Summary - -| Channel | Command | Audience | -|---|---|---| -| GitHub Releases | Download binary | All platforms | -| Homebrew | `brew install dipto0321/tap/nodeup` | macOS/Linux | -| Scoop | `scoop bucket add dipto0321 ...` then `scoop install nodeup` | Windows | -| npm wrapper | `npm install -g nodeup` | Any Node user | -| Snap | `snap install nodeup` | Ubuntu/Linux | -| AUR (v2) | `yay -S nodeup` | Arch Linux | - -### npm Wrapper Package - -Even though the tool is written in Go, publish a thin npm package that downloads the correct binary at postinstall: - -```json -{ - "name": "nodeup", - "version": "1.0.0", - "description": "Automated Node.js version upgrade with global package migration", - "bin": { "nodeup": "bin/nodeup" }, - "scripts": { - "postinstall": "node scripts/install.js" - } -} -``` - -The `install.js` detects `process.platform` + `process.arch`, downloads the correct binary from the GitHub Release, and saves it to `bin/nodeup`. This lets developers install with: `npm install -g nodeup` — very familiar UX. - -### Required GitHub Repos - -``` -github.com/dipto0321/nodeup # Main repo -github.com/dipto0321/homebrew-tap # Homebrew tap (auto-updated by GoReleaser) -github.com/dipto0321/scoop-bucket # Scoop bucket (auto-updated by GoReleaser) -``` - ---- - -## 16. Phased Execution Plan - -### Phase 0 — Scaffolding (Day 1) -- [ ] `mkdir nodeup && cd nodeup && git init` -- [ ] `go mod init github.com/dipto0321/nodeup` -- [ ] Create directory structure -- [ ] Add `cobra` root command with `version` subcommand -- [ ] Add GitHub repository, branch protection on `main` -- [ ] Add `.github/workflows/ci.yml` -- [ ] Add `.goreleaser.yaml` skeleton -- [ ] Add `.golangci.yml` -- [ ] Add `LICENSE`, `README.md`, `CHANGELOG.md` -- [ ] First commit: `chore: initial project scaffolding` -- [ ] First tag: `v0.1.0-alpha.1` - -### Phase 1 — Detection Engine (Branch: `feat/detector/core`) -- [ ] Implement `Manager` interface -- [ ] Implement fnm detector + commands -- [ ] Implement nvm detector + shell sourcing strategy -- [ ] Write unit tests for detection logic -- [ ] Add `nodeup list` command - -### Phase 2 — Node Version Resolution (Branch: `feat/node/version-api`) -- [ ] Implement nodejs.org API client -- [ ] LTS and Current resolution -- [ ] Response caching with TTL -- [ ] Add `nodeup check` command - -### Phase 3 — Package Migration (Branch: `feat/packages/migration`) -- [ ] `npm list -g --depth=0 --json` parsing -- [ ] Snapshot save/load -- [ ] Migration logic with per-package result tracking -- [ ] Add `nodeup packages snapshot/list/restore` - -### Phase 4 — Upgrade Command (Branch: `feat/upgrade/core`) -- [ ] Wire detection → version resolution → snapshot → install → migrate -- [ ] Interactive prompts via `huh` -- [ ] Progress spinner for long operations -- [ ] Final summary report -- [ ] `--dry-run` flag - -### Phase 5 — Additional Managers -- [ ] Volta (`feat/detector/add-volta`) -- [ ] ASDF (`feat/detector/add-asdf`) -- [ ] Mise (`feat/detector/add-mise`) -- [ ] n (`feat/detector/add-n`) -- [ ] nvm-windows (`feat/detector/add-nvm-windows`) - -### Phase 6 — Config & Polish -- [ ] `~/.nodeup/config.yaml` support -- [ ] `nodeup config` subcommand -- [ ] Multi-manager prompt -- [ ] Windows path handling -- [ ] Error messages + help text polish - -### Phase 7 — Release v1.0.0 -- [ ] Full test coverage (unit + integration) -- [ ] README with all install methods -- [ ] CHANGELOG complete -- [ ] GoReleaser configured -- [ ] Homebrew tap repo created -- [ ] Scoop bucket repo created -- [ ] npm wrapper package -- [ ] `v1.0.0` tag pushed → GitHub Release auto-created - ---- - -## Quick Reference: Day 1 Commands - -```bash -# Init repo -mkdir nodeup && cd nodeup -git init -git checkout -b main - -# Go module -go mod init github.com/dipto0321/nodeup - -# Install core deps -go get github.com/spf13/cobra@latest -go get github.com/charmbracelet/huh@latest -go get github.com/charmbracelet/lipgloss@latest -go get github.com/Masterminds/semver/v3@latest -go get github.com/tidwall/gjson@latest - -# Create remote -gh repo create dipto0321/nodeup --public --source=. --remote=origin - -# First commit -git add . -git commit -m "chore: initial project scaffolding" -git push -u origin main - -# Protect main branch -gh api repos/dipto0321/nodeup/branches/main/protection \ - --method PUT \ - --input protection.json -``` \ No newline at end of file