diff --git a/CHANGELOG.md b/CHANGELOG.md index adf4f0b..4fa0b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,71 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.2] - 2026-11-01 + +A patch release that **stops the snapshot leak** and gives the user +explicit control over whatever is left on disk. + +Nothing in nodeup ever deleted a snapshot file. `nodeup upgrade` +writes one snapshot of the global npm packages per installed Node +version so the migration can be rolled back or replayed — and then +never removed it. The set only ever grew: one file per `(manager, +Node version)` pair the user had ever installed, plus the orphan +left behind by an upgrade that failed partway. The migration reports +in `/reports/` leaked the same way, with unbounded +random-suffixed filenames. + +### Fixed +- The upgrade flow now reclaims the snapshots it wrote, once they + have served their purpose. The snapshots are deleted only when the + migration actually replayed them onto the new Node version — or + when there was nothing to migrate at all, in which case they are + redundant copies of the current state. A snapshot whose contents + were **not** replayed survives, because on the failure path it is + the only record of the user's globals and the "interrupted upgrade" + warning points straight at it. Closes #128. +- The upgrade sentinel (`/upgrade-in-progress.json`) was + armed even when there was nothing to install. `restoreSucceeded` + then never became true, so the sentinel was never cleared — and + *every* subsequent `nodeup` invocation warned about an interrupted + upgrade that never happened. The sentinel is now armed only when + there is actually something to install. +- `nodeup packages snapshot` captured the wrong version. It returned + `versions[0]` — the **lowest** installed version, because + `ListInstalled` sorts ascending. On a machine with Node 18 and + Node 20 it captured the (usually empty) 18 install and filed it + under the 20.x filename, producing a snapshot useless as a restore + source. It now prefers the manager's *active* version + (`Manager.Current`) and falls back to the **highest** installed + version for managers that cannot report one (`nvm-windows` returns + "not implemented"). +- Snapshot writes are now atomic. A crash mid-write left a truncated + file that the snapshot lister silently skipped — an invisible + orphan that could never be cleaned up. Writes go through a `.tmp` + file plus a rename. + +### Added +- `nodeup packages cleanup` — delete the snapshot and migration-report + files nodeup keeps on disk. It lists what's there (name, size, mtime) + and lets you pick files one by one (multi-select) or delete them all + at once. `--list` shows what would go without deleting anything, + `--reports` targets the report files instead of the snapshots, and + `--yes` skips the confirmation prompt (it implies `--all`). +- `--keep-snapshots` on `nodeup upgrade`, plus the + `packages.keep_snapshots` config key and the + `NODEUP_PACKAGES_KEEP_SNAPSHOTS` environment variable, to opt out of + the automatic snapshot deletion. + +### Changed +- The GoReleaser configuration no longer relies on deprecated keys + (`archives[].format` → `formats`, `brews:` → `homebrew_casks:`), and + the build metadata ldflags moved out of `builds[].flags` into the + dedicated `builds[].ldflags` key. Passing ldflags through `flags` + produced two `-ldflags` arguments on the `go build` command line, + and Go takes the last one — so our `-X main.commit=…` was silently + dropped and the shipped binary reported GoReleaser's defaults + instead. Closes #129. + ## [1.2.1] - 2026-11-01 A patch release that repairs the **npm install path**, which has been @@ -909,7 +974,8 @@ without weakening the body-length guard for hand-authored commits. ### Added - 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/v1.2.1...HEAD +[Unreleased]: https://github.com/dipto0321/nodeup/compare/v1.2.2...HEAD +[1.2.2]: https://github.com/dipto0321/nodeup/compare/v1.2.1...v1.2.2 [1.2.1]: https://github.com/dipto0321/nodeup/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/dipto0321/nodeup/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/dipto0321/nodeup/compare/v1.0.1...v1.1.0 diff --git a/CLAUDE.md b/CLAUDE.md index 3b9a90b..3dca531 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,7 +136,7 @@ orchestrator. | 5 — Config subsystem | Done | merged | | 6 — Cross-platform polish | Done | merged (interrupted-upgrade sentinel, `QuotePath`, system-node classifier) | | 7 — Distribution packaging | Done | GoReleaser, brew/scoop/npm wrappers all shipped (#17, #18); manual v1.0.0 npm publish (#35) bootstrapped the OIDC trust | -| 8 — v1.0.0 → v1.2.1 releases | Done | `nodeupx@1.2.1` live on npm (pinned to the v1.2.0 Go binary); trusted-publisher OIDC flow active for every tag (`#109`) | +| 8 — v1.0.0 → v1.2.2 releases | Done | `nodeupx@1.2.2` live on npm (matching the Go binary); trusted-publisher OIDC flow active for every tag (`#109`) | ## On-disk data layout diff --git a/README.md b/README.md index a0e2a84..276b599 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ brew install dipto0321/tap/nodeup ``` Best for: macOS / Linux users who already use Homebrew for tools. The -formula is auto-managed by GoReleaser from `brew: {}` in +cask is auto-managed by GoReleaser from `homebrew_casks: {}` in `.goreleaser.yaml`, so a `v*.*.*` tag updates the tap. #### Scoop (Windows) @@ -268,9 +268,9 @@ See [`docs/configuration.md`](./docs/configuration.md) for the full schema. ## Project status -Current release: **v1.2.1** (`nodeupx@1.2.1` on npm — the wrapper +Current release: **v1.2.2** (`nodeupx@1.2.2` on npm — the wrapper publishes automatically alongside the tag). See `CHANGELOG.md` for the -v1.1.0 → v1.2.1 history. +v1.1.0 → v1.2.2 history. | Phase | Status | What shipped | |---|---|---| @@ -281,9 +281,9 @@ v1.1.0 → v1.2.1 history. | 5 — Config subsystem | ✅ | YAML config at `~/.nodeup/config.yaml` + `config {show,get,set,init}` + env vars | | 6 — Cross-platform polish | ✅ | `QuotePath`, interrupted-upgrade replay, system-node classifier, lock file | | 7 — Distribution packaging | ✅ | GoReleaser + brew tap + Scoop bucket + npm wrapper (`nodeupx` via OIDC Trusted Publishing) | -| 8 — Tagged releases | ✅ | `v1.0.0` shipped (PR #35); `v1.1.0` shipped with the `internal/ui` rollout (`#117`/`#118`/`#119`); `v1.2.0` adds Vite+ support (`#124`); `v1.2.1` repairs the npm install path (`#126`) | +| 8 — Tagged releases | ✅ | `v1.0.0` shipped (PR #35); `v1.1.0` shipped with the `internal/ui` rollout (`#117`/`#118`/`#119`); `v1.2.0` adds Vite+ support (`#124`); `v1.2.1` repairs the npm install path (`#126`); `v1.2.2` stops the snapshot leak (`#130`) | -**Release train:** every `v*.*.*` tag triggers GoReleaser → brew formula +**Release train:** every `v*.*.*` tag triggers GoReleaser → brew cask push → Scoop manifest push → `nodeupx` publish via OIDC. To ship a new version: see `docs/release-checklist.md`. diff --git a/SPEC.md b/SPEC.md index 012ad72..10938a7 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,10 +1,10 @@ # SPEC — nodeup Distilled from code (README, CLAUDE.md, CONTRIBUTING.md, docs/*, and -`internal/*` source). Last refresh: 2026-11-01 (post v1.2.0 release; -§C/§I/§V/§T updated to match current state — Vite+ support complete -via #124, `internal/ui` rollout complete via #117/#118/#119, #120/#121 -are commitlint relaxations). +`internal/*` source). Last refresh: 2026-11-01 (post v1.2.2 release; +§C/§I/§V/§T updated to match current state — snapshot cleanup complete +via #130, Vite+ support complete via #124, `internal/ui` rollout +complete via #117/#118/#119, #120/#121 are commitlint relaxations). Caveman encoding per FORMAT.md convention (not present at repo root — using shapes from the caveman skill). diff --git a/docs/installation.md b/docs/installation.md index d4a32b5..faf2d15 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,7 +2,7 @@ All five channels below are wired up and tested. The GoReleaser config that produces the binary archives lives in `.goreleaser.yaml`; the -Homebrew tap formula and Scoop bucket manifest are pushed +Homebrew tap cask and Scoop bucket manifest are pushed automatically on every `v*.*.*` tag (see `.github/workflows/release.yml`). ## Supported platforms @@ -41,9 +41,16 @@ brew install dipto0321/tap/nodeup Homebrew for dev tooling (`brew` is the package manager for command-line apps, distinct from the App Store). The tap [`dipto0321/homebrew-tap`](https://github.com/dipto0321/homebrew-tap) -holds the formula and is auto-pushed by GoReleaser on every +holds the cask and is auto-pushed by GoReleaser on every `v*.*.*` tag. +> **Upgrading from the old formula:** `nodeup` shipped as a Homebrew +> formula up to v1.2.1 and switched to a cask in v1.2.2. The command +> is unchanged (`brew install dipto0321/tap/nodeup` still works), but +> Homebrew treats a formula and a cask as different packages — so if +> you installed the formula, run `brew uninstall --formula nodeup` +> after upgrading the tap. + **Tradeoffs:** upgrades happen via `brew upgrade`, which all Homebrew users already run on a schedule. The install lives in Homebrew's prefix (typically `/opt/homebrew/bin` on Apple Silicon), completely diff --git a/docs/managers.md b/docs/managers.md index b3c5683..8a90ce5 100644 --- a/docs/managers.md +++ b/docs/managers.md @@ -1,6 +1,6 @@ # Supported Version Managers -All 9 managers listed below are fully detected by `nodeup` as of v1.2.0 +All 9 managers listed below are fully detected by `nodeup` as of v1.2.2 (macOS, Linux, and Windows where applicable). The matrix is the source of truth — keep it in sync with `internal/detector/registry_*.go` and the per-manager files (`fnm.go`, `nvm.go`, `volta.go`, `asdf.go`, `mise.go`, @@ -78,7 +78,7 @@ bin dir (e.g., `fnm env --use-on-cd | source`, or add points at a binary inside the manager's data dir, nodeup's next run will classify it as `manager` and proceed without warning. -> _Status: Phase 7 complete. Post-upgrade cleanup prompt, per-manager native mutation commands, and full distribution packaging (GoReleaser, brew/scoop taps, npm wrapper at `nodeupx@1.2.0` via OIDC Trusted Publishing) have all shipped — see `CHANGELOG.md` for the v1.1.0 → v1.2.0 history._ +> _Status: Phase 7 complete. Post-upgrade cleanup prompt, per-manager native mutation commands, and full distribution packaging (GoReleaser, brew/scoop taps, npm wrapper at `nodeupx@1.2.2` via OIDC Trusted Publishing) have all shipped — see `CHANGELOG.md` for the v1.1.0 → v1.2.2 history._ ## Post-upgrade cleanup diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 420c333..8cde201 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -37,12 +37,42 @@ git push origin vX.Y.Z - [ ] GitHub Release visible at /releases/tag/vX.Y.Z - [ ] All 5 binaries attached + checksums.txt -- [ ] Homebrew formula pushed to `dipto0321/homebrew-tap` +- [ ] Homebrew cask pushed to `dipto0321/homebrew-tap` - [ ] Scoop manifest pushed to `dipto0321/scoop-bucket` - [ ] `publish-npm` job green; `npm view nodeupx version` matches the tag (automatic via OIDC Trusted Publishing — see below; **manual for the very first `nodeupx` publish only**) +### The formula → cask transition (one-time, v1.2.2) + +`nodeup` shipped as a Homebrew **formula** up to v1.2.1. GoReleaser +deprecated `brews:` in v2.10 in favour of `homebrew_casks:`, so v1.2.2 +ships a **cask** instead — a cask's `binary:` stanza is declarative, +while a formula's `install:` block is raw Ruby, and Homebrew now +prefers casks for "download a binary and put it on PATH" distributions. + +Homebrew treats a formula and a cask of the same name as two +different packages, so the old formula has to be removed from the tap +by hand, and the tap needs a `tap_migrations.json` so Homebrew can +perform the hand-off automatically on `brew update`: + +```json +{ + "Formula": { + "nodeup": "Cask", + "nodeup@1.2.1": "Cask" + } +} +``` + +- [ ] `tap_migrations.json` present in `dipto0321/homebrew-tap` with + the `Formula → Cask` mapping for `nodeup` +- [ ] The old `nodeup.rb` formula removed from the tap (GoReleaser + pushes the cask but does not delete the formula) +- [ ] `brew install dipto0321/tap/nodeup` verified on a scratch tap +- [ ] Existing formula installs get the hand-off on `brew update` + (Homebrew tells them to `brew uninstall --formula --force nodeup`) + ### Publishing `nodeupx` (`nodeup-npm/`) to the npm registry The npm wrapper lives in `nodeup-npm/` at the repo root. Publishing diff --git a/nodeup-npm/package.json b/nodeup-npm/package.json index caa616e..5276ea7 100644 --- a/nodeup-npm/package.json +++ b/nodeup-npm/package.json @@ -1,6 +1,6 @@ { "name": "nodeupx", - "version": "1.2.1", + "version": "1.2.2", "description": "Automated Node.js version upgrade + global package migration CLI. Static Go binary wrapped for npm distribution.", "license": "MIT", "author": "Dipto Karmakar ", @@ -42,5 +42,5 @@ "README.md", "LICENSE" ], - "binaryVersion": "1.2.0" + "binaryVersion": "1.2.2" } \ No newline at end of file