Skip to content

fix(packages): continue past failed installs; write MigrationReport on restore#115

Merged
dipto0321 merged 1 commit into
mainfrom
fix/packages/continue-past-failure-and-write-migration-report
Jul 5, 2026
Merged

fix(packages): continue past failed installs; write MigrationReport on restore#115
dipto0321 merged 1 commit into
mainfrom
fix/packages/continue-past-failure-and-write-migration-report

Conversation

@dipto0321

Copy link
Copy Markdown
Owner

Summary

The previous installPackages() returned on the first failed npm install -g, so a single broken / renamed / yanked package silently aborted the migration of every package after it. A user upgrading 30 globals where #3 fails lost 27 packages with no visible signal. Plus the entire MigrationReport.Save() / report.go path was dead code — the file shipped but was never called. Three concerns, same root cause: package install failures were treated as fatal instead of informational. Now treated as data.

Changes

  • internal/packages/snapshot.goinstallPackages now LOOP-ALL. ctx.Err() is checked at the top of each iteration so Ctrl-C still aborts promptly; only per-package npm failures continue. Returns ([]PackageResult, error) where the slice is populated for every package attempted (success or fail) so callers can build a MigrationReport. Aggregate error wraps the first failure with an "N of M packages failed" headline and preserves the original via %w. Restore and RestoreFromSnapshot return a RestoreOutcome struct (parsed snapshot + per-package results) so callers can populate MigrationReport metadata without re-parsing the snapshot.

  • internal/packages/snapshot.go — package-level runShell test seam so the install loop is unit-testable without spawning a real npm. Tests must NOT call t.Parallel() — the seam is package-global, same pattern fnm.go uses.

  • internal/packages/report.goPath() returns a stable filename per-report (with a 4-byte crypto/rand tag to prevent sub-second collisions), and Save() uses the same path. Save is NOT safe to call concurrently on the same instance, but no real caller does.

  • internal/cli/upgrade.go — builds a MigrationReport on every restore (success or partial) using the snapshot's recorded manager / from-version when available. Skips the write on empty results. Partial failure keeps sentinel armed (restoreSucceeded stays false), so nodeup packages restore --from <path> still works for retry.

  • internal/cli/packages.go — same report write on both --from and positional branches; report reflects the snapshot's recorded manager / from-version so the --from branch gets accurate metadata instead of "unknown".

  • internal/packages/snapshot_test.go — 6 new tests covering loop-all behavior (table-driven 5 packages with 2 mid-list failures), all-success, ctx-cancel mid-loop, empty list, MigrationReport path stability + content round-trip, and report path collision resistance between two reports created in the same wall-clock second.

  • internal/packages/sentinel_test.go — updated for the new RestoreFromSnapshot signature.

Sentinel semantics on partial failure

restoreSucceeded only flips to true when zero packages failed. On partial failure, the sentinel stays armed and the user can re-run nodeup packages restore --from <snapshot-path> to retry the failed packages — the report file (always written, success or partial) tells them which ones failed. Documented inline at upgrade.go:88-103.

Verification

  • go test -race ./internal/packages/... — green
  • make ci — green (golangci-lint: 0 issues; coverage: 62.8%)
  • 6 new tests pin the loop-all + report-write behavior
  • 1 new test pins sub-second report path collision resistance (4-byte crypto/rand suffix)

Closes #103.

…n restore

The previous installPackages() returned on the first failed `npm install
-g`, so a single broken / renamed / yanked package silently aborted
the migration of every package after it. A user upgrading 30 globals
where #3 fails lost 27 packages with no visible signal. Plus the
entire MigrationReport.Save() / report.go path was dead code — the
file shipped but was never called.

Three concerns, same root cause: package install failures were
treated as fatal instead of informational. Now treated as data.

- internal/packages/snapshot.go: installPackages LOOP-ALL. ctx.Err()
  is checked at the top of each iteration so Ctrl-C still aborts
  promptly; only per-package npm failures continue. Returns
  ([]PackageResult, error) where the slice is populated for every
  package attempted (success or fail) so callers can build a
  MigrationReport. Aggregate error wraps the first failure with an
  "N of M packages failed" headline and preserves the original via
  %w. Restore and RestoreFromSnapshot now return a RestoreOutcome
  struct (parsed snapshot + per-package results) so callers can
  populate MigrationReport metadata without re-parsing the snapshot.

- internal/packages/snapshot.go: package-level runShell test seam so
  the install loop is unit-testable without spawning a real npm.
  Tests must NOT call t.Parallel() — the seam is package-global,
  same pattern fnm.go uses.

- internal/packages/report.go: Path() returns a stable filename
  per-report (with a 4-byte crypto/rand tag to prevent sub-second
  collisions), and Save() uses the same path. Save is NOT safe to
  call concurrently on the same instance, but no real caller does.

- internal/cli/upgrade.go: builds a MigrationReport on every restore
  (success or partial) using the snapshot's recorded manager /
  from-version when available. Skips the write on empty results.
  Partial failure keeps sentinel armed (restoreSucceeded stays
  false), so `nodeup packages restore --from <path>` still works
  for retry.

- internal/cli/packages.go: same report write on both --from and
  positional branches; report reflects the snapshot's recorded
  manager / from-version so the --from branch gets accurate metadata
  instead of "unknown".

- internal/packages/snapshot_test.go: 6 new tests covering loop-all
  behavior (table-driven 5 packages with 2 mid-list failures),
  all-success, ctx-cancel mid-loop, empty list, MigrationReport
  path stability + content round-trip, and report path collision
  resistance between two reports created in the same wall-clock
  second.

- internal/packages/sentinel_test.go: updated for the new
  RestoreFromSnapshot signature.

Closes #103.

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

cocogitto-bot Bot commented Jul 5, 2026

Copy link
Copy Markdown

✔️ fb4b7d1 - Conventional commits check succeeded.

@dipto0321 dipto0321 merged commit 8f8d433 into main Jul 5, 2026
10 checks passed
@dipto0321 dipto0321 deleted the fix/packages/continue-past-failure-and-write-migration-report branch July 5, 2026 06:32
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.

bug(packages): restore aborts on first failed install; migration report never written (re-files #46)

1 participant