Surgical one-by-one
Every upgrade is its own operation: bump → npm install → validate. If a patch fails, the package.json is restored from backup. No mass updates, no mystery regressions.
Upgrade npm dependencies one-by-one — with install + validation after every change, automatic rollback on failure, and a graph-driven conflict report.
▸ framework-agnostic · peerDeps graph · node 20.17+ / 22.9+

A real session: plan → install → validate → rollback on trouble. Hit refresh to replay the surgery.
Peel, slap onto your CI pipeline, press firmly. Everything below is already in the shipped binary — no plugins, no bolt-ons.
Every upgrade is its own operation: bump → npm install → validate. If a patch fails, the package.json is restored from backup. No mass updates, no mystery regressions.
With --link-groups auto, the tool reads each package's published peerDependencies and builds a graph. Connected components become one batch → one write → one install → one validation.
A regex-based parser classifies peer mismatches, missing peers, engine bumps, and unresolved trees — from real npm output. Root-only mentions get filtered so your own app doesn't look like a conflict.
Before the first change, package.json is copied to package.json.dep-up-surgeon.bak. Even when npm exits 0 but the log is full of conflicts, the bump is rolled back — unless you --force it.
@latest first, then walk back: major-lines (default) tries the best stable per major; minor-lines steps by major.minor. ERR_REQUIRE_ESM halts further tries for that package. Set none to lock to @latest only.
--interactive turns failures into choices: skip, retry, pin, or --force the batch. After the run, optionally bulk-add failed names to .dep-up-surgeonrc so the next run stays clean.
--json emits a single JSON object: upgraded, skipped, failed, conflicts, unresolved, groups, ignored. Drop it straight into CI, a PR bot, or a dashboard.
No hardcoded React / Angular / Vue lists. Grouping and conflict handling come from registry metadata and parsed npm output. @types/* pairs stay with their runtime packages automatically.

Install globally or run locally after cloning. From your project root (where package.json lives), call the CLI — optionally with --dry-run first.
{
"ignore": ["some-legacy-package"],
"linkedGroups": [
{
"id": "my-batch",
"packages": ["package-a", "package-b"]
}
]
}The full control panel. Exit code 1 when any upgrade could not be kept (unless --force).
--dry-run--interactive--forcesharp--ignore<pkg,pkg,...>--jsonci-ready--fallback-strategy<major-lines | minor-lines | none>default: major-lines--link-groups<auto | none>default: auto▸ note — non-registry ranges (workspace:, link:, file:, git:) are skipped for upgrades