Surgical one-by-one
Every upgrade is its own operation: bump → install → validate. If a patch fails, package.json is restored from backup. No mass updates, no mystery regressions.
Upgrade npm, pnpm & yarn deps one-by-one — with pre-flight + install + validation, automatic rollback, a peer-range resolver that backtracks through the packument (SAT / AC-3 for big graphs), --security-only with transitive + parent-scoped overrides, --fix-lockfile dedupe, breaking-change radar, policy-as-code, atomic git commits, auto-opened PRs, and undo + doctor subcommands.
▸ framework-agnostic · peerDeps graph · peer resolver · monorepo-ready · ci-mode · security-first · policy-as-code · auto-PR · undo · doctor · 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. Twenty-nine capabilities in one binary — workspace-aware, git-aware, CI-aware, policy-aware, security-aware, PR-aware, and with undo + doctor subcommands for disaster recovery and pre-flight diagnostics. No plugins, no bolt-ons, no framework lock-in.
Every upgrade is its own operation: bump → install → validate. If a patch fails, 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. @types/* pair with their runtime automatically.
Before mutating any dependency, the resolved validator runs once on the unchanged tree. If the project build was already broken, the run aborts with the validator command, exit code, and last ~40 lines — instead of pretending every rollback is a real conflict.
A regex-based parser classifies peer mismatches, missing peers, engine bumps, and unresolved trees — from real npm / pnpm / yarn output. Root-only mentions get filtered so your own app never looks like a conflict.
--workspaces traverses the root + every member; --workspace <names> picks specific packages. Install + validation run from the workspace root so the lockfile is always coherent. --install-mode filtered uses npm install --workspace, pnpm --filter, or yarn workspaces focus when available.
--concurrency up to 16 overlaps registry scan + plan across workspace targets while a shared mutex keeps install + validation strictly serialized — your shared lockfile is never raced. An in-process manifest cache deduplicates pacote calls across the whole run.
@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.
Detects npm / pnpm / yarn from the packageManager field, then the lockfile, then pnpm-workspace.yaml. The chosen manager drives both install and the default validator (<mgr> test → <mgr> run build). Yarn berry capabilities are auto-probed for filtered installs.
--interactive splits prompts by phase. Linked-group conflict: skip group / retry / force / freeze-all. Single-package failure: continue / pin / retry. After the run, optionally bulk-add every failed name to the .dep-up-surgeonrc ignore list so the next pass converges.
--json emits one structured object: upgraded, skipped, failed, conflicts, groups, project, targets, installMode, concurrency, commits. Persisted to .dep-up-surgeon.last-run.json next to the workspace root for CI dashboards and --retry-failed.
Reads the persisted last-run report and only re-attempts non-terminal residue (install / validation / validation-conflicts). Successful upgrades and terminal failures (peer, validation-script) are auto-frozen so the next pass converges instead of looping.
--summary md|html renders Upgraded / Failed tables, pre-flight status, ignored list, and target counts. On GitHub Actions it's appended to $GITHUB_STEP_SUMMARY automatically — perfect for PR-bot diagnostics without extra workflow plumbing.
--git-commit lands every successful upgrade as its own atomic commit (per-success / per-target / all). Only stages package.json + the lockfile — never git add -A — so prepare/postinstall side effects stay uncommitted. Pair --git-branch + --git-sign for review-ready PR-bot runs.
--ci disables --interactive, auto-enables --summary md, and remaps the exit code: per-package failures stay green so your bot's PR carries the diagnostic. Pre-flight + fatal errors still exit 1 — those mean a human needs to look.
Before the first change, package.json is copied to package.json.dep-up-surgeon.bak. Even when the installer exits 0 but the log is full of conflicts, the bump is rolled back — unless you --force it. A failed git commit never rolls back the upgrade.
No hardcoded React / Angular / Vue / Expo lists. Grouping and conflict handling come from registry metadata and parsed installer output. Custom batches via .dep-up-surgeonrc linkedGroups when the registry graph is invisible.
--security-only flips the tool into CVE-driven mode. It runs npm/pnpm/yarn audit, filters by --min-severity, then upgrades only the vulnerable names. Every bump carries its advisory ID + severity into commit subjects ([security:high]) and into a dedicated Security fixes table in --summary.
--apply-overrides pins vulnerable transitives no direct bump could reach — writes overrides (npm 8.3+), pnpm.overrides (pnpm), or resolutions (yarn). --override accepts manual parent-scoped pins (some-dep>foo@1.2.3 / parent/child@1.2.3) and works standalone. Install + validator run after each pin; failures are rolled back per-slot so one bad pin never strands the rest.
When a linked-group bump hits a peer conflict, the resolver reads every member's published peerDependencies and backtracks to the newest tuple that satisfies them all. Graphs of 10+ members switch to a SAT-style AC-3 solver; single-package failures synthesize an ad-hoc group from direct-dep blockers. --no-resolve-peers restores the old fail-fast behavior.
dep-up-surgeon undo replays the last-run report in reverse: writes every recorded from back to package.json, drops (or restores) every override it added, then runs install + validator so you see green/red before you commit. Drift-protected — deps that moved since the run are skipped as 'drifted' instead of being rewritten.
dep-up-surgeon doctor is a read-only green / yellow / red pre-flight: Node engine match, manager resolution, lockfile parse, workspace coherence, policy parse, validator pre-flight, peer-dep scan, audit severity breakdown, and stale-transitive scan. Exit 2 on any red, 1 on yellow under --strict. The CI gate you run before trusting the upgrade loop.
--fix-lockfile runs `npm dedupe --no-audit --loglevel error` / `pnpm dedupe` / `yarn dedupe` (berry only) to collapse redundant copies without touching package.json, and flags transitives more than a minor or full major behind latest (top 250 by installed-copy count). Lockfile is snapshotted first — restored on dedupe or post-dedupe validator failure. Yarn classic → skipped:"unsupported"; missing lockfile → skipped:"no-lockfile".
In an isolated-lockfile monorepo (pnpm shared-workspace-lockfile=false in .npmrc, or per-member lockfiles) installs + validation run in parallel too — the keyed async mutex unlocks different-dir operations while same-dir ones still serialize. Auto-detected and reported as project.isolatedLockfiles + project.isolatedLockfilesSource ('pnpm-npmrc' | 'per-workspace-lockfiles') + top-level parallelInstalls in --json. --no-parallel-installs forces the serialized path.
Every fetched changelog is scanned for breaking-change markers: Conventional-Commit footers, 💥 / ⚠ BREAKING emoji, Node-version drops, API removals. Matches add a [breaking] tag to the commit subject, a Breaking changes detected section to the body, and a prominent banner above the --summary upgraded table.
Before you hand the PR to a reviewer, --blast-radius scans your source (.ts/.tsx/.js/.jsx/.mjs/.cjs/.mts/.cts/.vue/.svelte/.astro) to list which files actually import each upgraded package. Word-boundary safe. Surfaces as collapsible per-package lists in --summary and upgraded[].blastRadius in --json.
Drop a .dep-up-surgeon.policy.yaml in the repo and encode upgrade rules that survive runs: freeze patterns (wildcards like @types/* supported), maxVersion semver caps, allowMajorAfter date gates, requireReviewers / autoMerge metadata for your PR bot. Every applied rule appears in --summary and under policy.* in --json.
Every successful upgrade is annotated with release notes (GitHub Releases API first, then the package's CHANGELOG.md from its tarball). Embedded in commit bodies and rendered as collapsible <details> blocks in --summary. Set GITHUB_TOKEN / GH_TOKEN to lift the API rate limit from 60/h to 5000/h.
--open-pr closes the loop: after --git-commit --git-branch pushes the branch, it pipes the --summary markdown to gh pr create --body-file -. Reuses existing PRs for the same head. --open-pr-draft for merge-queue safety, --open-pr-reviewers / --open-pr-assignees / --open-pr-base for team wiring. Never fatal — a missing gh never aborts the run.
TTY-aware spinner (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) with (Ns) / (NmNs) elapsed timer on the pre-flight, single-package, and linked-group phases. Status flips through Installing … → Validating <cmd> … → Rolling back … as the engine moves. Non-TTY / CI degrades to plain › … log lines; --json and --ci suppress the spinner entirely so machine-parseable output stays clean. Auto-disabled when --concurrency > 1 in non-JSON mode so per-target lines never interleave.

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"]
}
],
"validate": "tsc -p tsconfig.json --noEmit",
"overrides": [
{
"chain": ["some-dep", "foo"],
"range": "1.2.3",
"reason": "CVE-2025-1234"
},
{
"selector": "lodash@4.17.21",
"reason": "awaiting upstream PR #42"
}
]
}freeze:
- pattern: react
reason: "pinned until Q3 refactor"
- pattern: "@types/*"
maxVersion:
- pattern: next
range: "<=14"
allowMajorAfter:
- pattern: eslint
date: "2026-06-01"
requireReviewers: 2
autoMerge: falseThe full control panel — 53flags & subcommands across eight sections: core run control, validation, workspaces, reports & CI, git integration, security-first mode, PR automation, and the read-only doctor / undo subcommands. Exit code 1 when any upgrade could not be kept (unless --force) or when the pre-flight validator fails. Under --ci, per-package failures stay green.
--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--resolve-peers--no-resolve-peers (opt-out)default: on--validate<cmd>--no-validate--package-manager<auto | npm | pnpm | yarn>default: auto--include-workspace-deps--workspaces--workspaces-only--workspace<names>--install-mode<root | filtered>default: root--concurrency<n>default: 1--no-parallel-installsisolated lockfiles--retry-failed--no-persist-report--summary<md | html>default: md--summary-file<path>--ciPR-bot ready--git-commit--git-commit-mode<per-success | per-target | all>default: per-success--git-commit-prefix<prefix>--git-branch<name>--git-sign--git-allow-dirtyuse with care--security-onlyCVE-driven--min-severity<low | moderate | high | critical>default: low--apply-overridestransitive CVEs--override<spec...>parent-scoped--override-force--fix-lockfilelockfile hygiene--changelog--no-changelog (opt-out)default: on--blast-radius--no-blast-radius (opt-out)default: on--open-prgh CLI required--open-pr-title<title>--open-pr-draft--open-pr-base<branch>--open-pr-reviewers<users>--open-pr-assignees<users>dep-up-surgeon undodisaster recoveryundo --file <path>undo --json / --dry-runundo --skip-installundo --no-validate / --validate <cmd>undo --package-manager <mgr> / --cwd <path>dep-up-surgeon doctorpre-flight gatedoctor --strictdoctor --jsondoctor --skip-audit / --skip-peer-scan / --skip-stale-scan▸ note — non-registry ranges (workspace:, link:, file:, git:, git+, http:, https:) are skipped for upgrades · pre-flight runs once at the workspace root before any mutation