dep-up-surgeon 2.2.2 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,8 +79,9 @@ dep-up-surgeon [options]
79
79
  | `--security-only` | Run `npm audit` (or `pnpm`/`yarn` equivalent) first, then upgrade **only** the packages with open advisories. Every successful bump carries the advisory severity + ID into its commit subject (`[security:high]`) and into the summary's **Security fixes** table. Pairs well with `--git-commit-mode per-success` to produce one PR per CVE. See **Security-first mode** below. |
80
80
  | `--min-severity <level>` | Minimum advisory severity to consider under `--security-only`: `low` (default), `moderate`, `high`, or `critical`. Lower-severity advisories are filtered out before the upgrade plan is built. |
81
81
  | `--blast-radius` / `--no-blast-radius` | Scan project source files to list which files actually `import`/`require` each upgraded package, and surface the list in `--json` + `--summary`. **Default ON** when `--summary` is active. See **Blast radius** below. |
82
- | `--resolve-peers` / `--no-resolve-peers` | When a linked-group bump (e.g. `react` + `react-dom` + `@types/react`) fails with a peer-dependency conflict, compute the intersection of peer ranges across the registry packument and retry with a satisfiable version tuple (members may land below `latest`). **Default ON**. See **Peer-range intersection resolver** below. |
82
+ | `--resolve-peers` / `--no-resolve-peers` | When a linked-group bump (e.g. `react` + `react-dom` + `@types/react`) **or a single-package bump** fails with a peer-dependency conflict, compute the intersection of peer ranges across the registry packument and retry with a satisfiable version tuple (members may land below `latest`). Linked graphs with 10+ members automatically use a SAT-style AC-3 solver; single-package failures synthesize an **ad-hoc group** from direct-dep blockers named in the install output. **Default ON**. See **Peer-range intersection resolver** below. |
83
83
  | `--apply-overrides` | After the main upgrade loop, fix **transitive** CVEs that no direct bump could reach by writing a package-manager override (`overrides` for npm, `pnpm.overrides` for pnpm, `resolutions` for yarn) pinning each vulnerable transitive to its audit-recommended safe version. Runs install + validator after each pin and rolls back automatically when the validator fails. Requires `--security-only`. See **Transitive overrides** below. |
84
+ | `--override <spec...>` | Apply one or more **manual** override pins independent of the audit. Repeatable and also accepts comma-separated values. Syntax: `<chain>@<range>`, where `<chain>` is a bare name (`lodash`), a pnpm-style chain (`some-dep>foo`, any depth), or a yarn-style chain (`parent/child`). Scoped names (`@scope/pkg`) are preserved as single chain segments. Written to the manager-native nested form (npm object, pnpm `>`-keys, yarn `/`-keys) and run through the same install + validator + rollback loop as `--apply-overrides`. Works standalone — `--security-only` is not required. See **Transitive overrides** below. |
84
85
  | `--override-force` | Used with `--apply-overrides`. Overwrite an **existing** override entry whose value conflicts with the audit-recommended version. By default we refuse to clobber user-managed pins and record `conflict` in the report. |
85
86
  | `--fix-lockfile` | After the main upgrade loop, run the package manager's native dedupe command (`npm dedupe` / `pnpm dedupe` / `yarn dedupe`) to collapse redundant transitive copies **without touching `package.json`**, and flag transitives more than a minor or a full major behind registry `latest`. Lockfile is backed up before dedupe and restored if dedupe OR the post-dedupe validator fails. Yarn classic (v1) has no dedupe subcommand — recorded as `skipped: "unsupported"`. See **Lockfile fix** below. |
86
87
  | `--open-pr` | After `--git-commit --git-branch` pushes the branch, open a GitHub PR with the `--summary` markdown as the body (falls back to a deterministic minimal body). Uses the `gh` CLI (must be installed + authenticated); never fatal — a missing binary, auth failure, or push rejection is recorded as `pullRequest.error` in the JSON report without aborting the run. See **Auto-opening a PR** below. |
@@ -235,6 +236,8 @@ npx dep-up-surgeon --workspaces --security-only --min-severity high \
235
236
  --git-branch "deps/security-$(date +%Y-%m-%d)"
236
237
  ```
237
238
 
239
+ The whole path is covered by `test/unit/security-only.test.mjs` — a hermetic regression harness that drives `runAudit` with a canned `npm audit --json` blob, asserts `--min-severity` filters at every tier, and exercises the full `runUpgradeFlow` → install → validator → **rollback** cycle without touching the registry (via the `UpgradeFlowOptions.installer` injection point).
240
+
238
241
  ### Policy engine (policy-as-code)
239
242
 
240
243
  Drop a `.dep-up-surgeon.policy.yaml` (or `.json`) in the repo root to encode upgrade rules that survive across runs and humans. Loaded automatically on startup; violations are reported per-package and the engine skips the offending bumps instead of failing.
@@ -296,16 +299,21 @@ Linked-group bumps (e.g. `react` + `react-dom` + `@types/react`, or the Jest / T
296
299
  2. If the install fails with a **peer** conflict, the resolver fetches each linked package's full registry packument (cached — one call per package per run) and reads every published version's `peerDependencies` block.
297
300
  3. Each member gets a candidate domain: every version between `currentRange`'s `minVersion` and the originally-requested target, sorted newest-first, minus deprecated / pre-release versions.
298
301
  4. A **newest-first backtracking search** enumerates version tuples (variable = one package, domain = its candidate versions). For each partial assignment, every peer constraint that has become knowable is checked; peers on packages inside the linked group are checked against the chosen version, peers on packages OUTSIDE the group are checked against that package's range in the current `package.json` (via `semver.minVersion`).
299
- 5. The **first** complete tuple to satisfy every constraint is also the least-downgrade one. The engine rewrites the batch's target versions and retries the install + validator. On success, every affected row is tagged with `resolvedPeer = { originalTarget, reason, tuplesExplored }`.
300
- 6. If the resolver can't find a satisfiable tuple, or the retried install still fails, the batch falls back to the pre-resolver behavior (rollback + `kind: 'peer'` failure row).
302
+ 5. For **large linked graphs (≥ 10 members)** where the 400-tuple backtracking budget can be burned before the solver escapes the first variable's domain — the resolver automatically switches to a **SAT-style path** (arc-consistency + least-constraining-value DFS). It pre-prunes every member-version that can't be satisfied against external peers, runs up to 128 AC-3 rounds across every ordered member pair until the pruned domains reach a fixed point, then does an **MRV-ordered** (smallest domain first) newest-first DFS on whatever survived. For monorepo link groups up to ~50 members × ~30 recent versions this finishes in milliseconds where plain DFS would return `undefined`. When the SAT path fails the dispatcher falls back to the plain backtracker automatically.
303
+ 6. The **first** complete tuple to satisfy every constraint is also the least-downgrade one. The engine rewrites the batch's target versions and retries the install + validator. On success, every affected row is tagged with `resolvedPeer = { originalTarget, reason, tuplesExplored }` `reason` carries a `[backtracking]` or `[sat]` method tag so reviewers can tell which solver path produced the tuple.
304
+ 7. If the resolver can't find a satisfiable tuple, or the retried install still fails, the batch falls back to the pre-resolver behavior (rollback + `kind: 'peer'` failure row).
305
+
306
+ **Ad-hoc resolver for non-linked bumps.** Single-package upgrades that fail with a peer conflict used to be rolled back unconditionally — the resolver was linked-groups-only. Now we synthesize an **ad-hoc group** from the parsed install output: the primary + every blocker named in the peer-conflict lines that's **already a direct dep** of the workspace (peers on unknown transitives stay out of scope). The same resolver (and the same SAT fallback) runs on that synthesized group. On success the engine writes a small batch: the primary at whatever version the resolver picked, plus any blocker the resolver wants moved within its **current pinned range** (the ad-hoc path is allowed to downgrade the primary, never to silently bump a blocker past its pin).
301
307
 
302
308
  Guard rails that keep it safe:
303
309
 
304
- - **Bounded search** — capped at 400 tuples explored per batch. Past that the resolver gives up silently instead of hanging the run on pathological inputs.
310
+ - **Bounded search** — capped at 400 tuples explored per batch (small graphs) or `400 × members` tuples for the SAT path's DFS phase. Past that the resolver gives up silently instead of hanging the run on pathological inputs.
305
311
  - **Optional peers** (`peerDependenciesMeta[name].optional === true`) are ignored. An unsatisfied optional peer isn't a hard conflict.
306
312
  - **Deprecated versions** never appear in the domain. We'd rather fail to find a solution than auto-suggest a known-bad version.
313
+ - **Ad-hoc group size cap** — default 6 members (primary + up to 5 direct-dep blockers). Prevents registry fetch storms on pathological peer graphs.
314
+ - **Ad-hoc never adds dependencies** — a peer on a transitive that isn't already a direct dep is ignored rather than introduced.
307
315
  - **`--force` bypasses the resolver** — the user has explicitly opted into barreling through peer conflicts.
308
- - **`--no-resolve-peers`** keeps the old behavior when you WANT peer failures to surface so a human resolves them instead of the tool silently nudging versions off latest.
316
+ - **`--no-resolve-peers`** keeps the old behavior when you WANT peer failures to surface so a human resolves them instead of the tool silently nudging versions off latest. Applies to both the linked-group and ad-hoc paths.
309
317
 
310
318
  Where it shows up:
311
319
 
@@ -314,7 +322,7 @@ Where it shows up:
314
322
  - **Commit subjects**: `[peer-resolved]` tag sits between `[breaking]` and `[security:<sev>]` (stable order). The body gets a `Peer-range resolutions (kept linked group satisfiable):` footer listing each pinned member.
315
323
  - **`--json`**: `upgraded[].resolvedPeer = { originalTarget, reason, tuplesExplored }` plus `upgraded[].requestedLatest` still reflects the pre-resolver target so downstream tools can diff them.
316
324
 
317
- ### Transitive overrides (`--apply-overrides`)
325
+ ### Transitive overrides (`--apply-overrides` / `--override`)
318
326
 
319
327
  `--security-only` by itself can only fix vulnerabilities reachable from a direct dependency. For CVEs that live in transitives (very common — `lodash@4.17.20` buried six levels deep under a toolchain package), pair `--security-only` with `--apply-overrides` and the tool will write a package-manager override to pin the vulnerable transitive to its safe version.
320
328
 
@@ -323,14 +331,45 @@ Where it shows up:
323
331
  - **Rollback on failure**: after each override, the tool runs a full install and then the validator. If either fails, the override is removed, install re-runs to restore the starting state, and the next advisory is still attempted. A failed override never strands the workspace — `report.overrides.attempts[].rolledBack === true` appears in the JSON and the summary.
324
332
  - **Conflict protection**: when the user already has a manual override with a value that **conflicts** with the audit recommendation, we refuse to clobber by default (`reason: "conflicts with target ..."`). Pass `--override-force` to overwrite explicitly.
325
333
  - **Where it shows up**:
326
- - **`--summary`**: dedicated `Overrides applied` table with `Package / Pinned to / Severity / Advisory`.
327
- - **`--json`**: `overrides.field` + `overrides.attempts[]` with the full decision trail (`ok`, `skipped`, `reason`, `previous`, `applied`, `installLog`, `rolledBack`).
334
+ - **`--summary`**: dedicated `Overrides applied` table with `Package / Pinned to / Source / Severity / Advisory`. Parent-scoped pins render as `a › b › c` so the chain is visible at a glance.
335
+ - **`--json`**: `overrides.field` + `overrides.attempts[]` with the full decision trail (`ok`, `skipped`, `reason`, `previous`, `applied`, `installLog`, `rolledBack`, `chain`, `source`). Parent-scoped pins carry `chain: ["parent", "child"]`; `source` distinguishes `"advisory"` from `"manual"`.
336
+
337
+ #### Parent-scoped pins (`--override`)
338
+
339
+ `--apply-overrides` only writes the **flat** `name → version` form — every occurrence of the package gets pinned. When you need to pin a transitive **only when it appears under a specific parent** (e.g. you want `foo@1.2.3` under `some-dep` while the rest of the tree uses `foo@2.x`), use `--override` to write a **parent-scoped** selector. Works standalone — no `--security-only` required.
340
+
341
+ Syntax: `<chain>@<range>`. The chain supports three forms, all normalized internally:
342
+
343
+ - **Flat**: `--override lodash@4.17.21` → same shape as a classic flat override.
344
+ - **pnpm-style**: `--override "some-dep>foo@1.2.3"` — pin `foo` only when nested under `some-dep`. Chains of any depth (`a>b>c>d@1.0.0`) are supported.
345
+ - **yarn-style**: `--override "parent/child@1.2.3"` — `/` separator; `@scope/pkg` stays intact as a single chain segment.
346
+
347
+ Each selector is written to the **manager's native nested encoding**:
348
+
349
+ | Manager | Shape written |
350
+ | --- | --- |
351
+ | npm | `{ "overrides": { "some-dep": { "foo": "1.2.3" } } }` — nested object; an existing flat pin for the parent is preserved via npm's `"."` self-selector. |
352
+ | pnpm | `{ "pnpm": { "overrides": { "some-dep>foo": "1.2.3" } } }` — pnpm's `>`-chain keys, deep chains supported. |
353
+ | yarn | `{ "resolutions": { "some-dep/foo": "1.2.3" } }` — `/`-chain keys. |
354
+
355
+ Every pin runs through the same install + validator + rollback loop as advisory-driven pins. A failed manual pin is rolled back (only that specific slot) and the rest of the run continues; a flat pin and a parent-scoped pin with the same leaf name coexist as separate entries.
356
+
357
+ ```bash
358
+ # Pin `lodash@4.17.21` ONLY when it's a transitive of `some-dep`, and pin `axios@1.6.0`
359
+ # globally. Both live in the same run; one failing never touches the other.
360
+ npx dep-up-surgeon \
361
+ --override "some-dep>lodash@4.17.21" \
362
+ --override "axios@1.6.0" \
363
+ --validate "npm test"
364
+ ```
328
365
 
329
366
  ```bash
330
- # Weekly security sweep: direct bumps first, then transitive overrides, then a draft PR.
367
+ # Weekly security sweep: direct bumps first, then transitive overrides (audit-driven +
368
+ # one manual pin), then a draft PR.
331
369
  npx dep-up-surgeon --workspaces \
332
370
  --security-only --min-severity high \
333
371
  --apply-overrides \
372
+ --override "@babel/core>@babel/traverse@7.23.2" \
334
373
  --git-commit --git-commit-mode per-success --git-branch "deps/security-$(date +%Y-%m-%d)" \
335
374
  --summary md \
336
375
  --open-pr --open-pr-draft
@@ -605,8 +644,6 @@ The compiled entry is `dist/cli.js` (see `"bin"` in `package.json`).
605
644
  ## Future work (tracked in code)
606
645
 
607
646
  - GitLab / Bitbucket auto-PR providers (today `--open-pr` is GitHub-only via `gh`)
608
- - Nested / parent-scoped override rules beyond the flat `name → version` form (`overrides: { "foo": { ">=2 <3": "3.0.0" } }` in npm, deep pnpm selectors)
609
- - Resolver sharpening: SAT-backed peer-range intersection for very large linked graphs (10+ members) where the current 400-tuple backtracking budget gives up; peer-range intersection across **non-linked** packages too (today the resolver only intervenes on linked-group failures)
610
647
  - Renovate-style scheduling helpers (cron / day-of-week filters, grouping rules)
611
648
  - True parallel installs in monorepos that don't share a root lockfile (e.g. nohoist setups), going beyond today's parallel scan + serial install model
612
649
  - AI-assisted failure explanation: feed `install.lastLines` + `validation.lastLines` to an LLM and attach a one-sentence "why this broke" note to failed records
@@ -1,5 +1,6 @@
1
1
  import type { SecurityAdvisory } from '../core/audit.js';
2
2
  import type { PackageManager } from '../core/workspaces.js';
3
+ import { runInstall } from '../utils/npm.js';
3
4
  import { type OverrideField } from '../utils/overrides.js';
4
5
  export interface OverrideAttemptRecord {
5
6
  name: string;
@@ -11,6 +12,18 @@ export interface OverrideAttemptRecord {
11
12
  applied?: string;
12
13
  /** The previous pin, if any. */
13
14
  previous?: string;
15
+ /**
16
+ * Full parent chain for parent-scoped pins, outermost → leaf. Omitted for flat pins
17
+ * (equivalent to `chain: [name]`). Included in the report so consumers know which exact
18
+ * slot in the overrides block was touched.
19
+ */
20
+ chain?: string[];
21
+ /**
22
+ * How this attempt was triggered. `advisory` = fed in by `--security-only` audit; `manual`
23
+ * = user-supplied via `--override` CLI flag or policy. Consumers can render the two lists
24
+ * separately (e.g. manual overrides don't map to a CVE).
25
+ */
26
+ source: 'advisory' | 'manual';
14
27
  /** Field we touched (`overrides` / `pnpm.overrides` / `resolutions`). */
15
28
  field: OverrideField;
16
29
  /** True when the write, install, AND validator all succeeded. */
@@ -24,6 +37,19 @@ export interface OverrideAttemptRecord {
24
37
  /** True when a rollback was performed. */
25
38
  rolledBack?: boolean;
26
39
  }
40
+ /**
41
+ * User-supplied override pin (from `--override` CLI flag or similar). Unlike advisory-driven
42
+ * pins, these carry an arbitrary parent chain and no CVE metadata. They run through the same
43
+ * write + install + validator + rollback cycle so a manual pin can't wreck the workspace.
44
+ */
45
+ export interface ManualOverrideSpec {
46
+ /** Parent chain + leaf, outermost → leaf (`[name]` for flat pins). */
47
+ chain: string[];
48
+ /** Version / range to pin. */
49
+ range: string;
50
+ /** Original user-supplied selector string, for error messages and the report. */
51
+ source?: string;
52
+ }
27
53
  export interface OverrideFlowOptions {
28
54
  /** Workspace root. Every override write and `runInstall` happens here. */
29
55
  cwd: string;
@@ -49,6 +75,17 @@ export interface OverrideFlowOptions {
49
75
  dryRun?: boolean;
50
76
  /** True when the CLI is in `--json` mode: suppress human-readable progress log lines. */
51
77
  json?: boolean;
78
+ /**
79
+ * User-supplied parent-scoped or flat pins (via `--override` CLI flag). These run AFTER
80
+ * the advisory-driven pins in the same loop so they share the install + validator +
81
+ * rollback machinery. A failure here never touches advisory results.
82
+ */
83
+ manualOverrides?: ManualOverrideSpec[];
84
+ /**
85
+ * Optional install runner override. Kept as an injection point so tests can exercise the
86
+ * write + rollback cycle without actually shelling out.
87
+ */
88
+ installer?: typeof runInstall;
52
89
  }
53
90
  export interface OverrideFlowResult {
54
91
  /** Every advisory we considered, including skips. */
@@ -1 +1 @@
1
- {"version":3,"file":"overrideFlow.d.ts","sourceRoot":"","sources":["../../src/cli/overrideFlow.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IACnD,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,KAAK,EAAE,aAAa,CAAC;IACrB,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC;IACZ,yFAAyF;IACzF,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,cAAc,CAAC;IACxB,gFAAgF;IAChF,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,mFAAmF;IACnF,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2FAA2F;IAC3F,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,gGAAgG;IAChG,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0FAA0F;IAC1F,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoH5F;AAkCD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAkBrF"}
1
+ {"version":3,"file":"overrideFlow.d.ts","sourceRoot":"","sources":["../../src/cli/overrideFlow.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAsB,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;IACnD,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,yEAAyE;IACzE,KAAK,EAAE,aAAa,CAAC;IACrB,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC;IACZ,yFAAyF;IACzF,OAAO,EAAE,OAAO,CAAC;IACjB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,cAAc,CAAC;IACxB,gFAAgF;IAChF,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,mFAAmF;IACnF,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2FAA2F;IAC3F,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,gGAAgG;IAChG,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,0FAA0F;IAC1F,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,eAAe,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACvC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsF5F;AAsHD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAkBrF"}
@@ -41,10 +41,11 @@ export async function runOverrideFlow(opts) {
41
41
  // the main upgrade loop. These are the pure-transitive CVEs that overrides exist to fix.
42
42
  const targets = opts.advisories.filter((a) => !opts.directDepNames.has(a.name) &&
43
43
  !opts.upgradedNames.has(a.name));
44
- if (targets.length === 0) {
44
+ const hasManual = (opts.manualOverrides?.length ?? 0) > 0;
45
+ if (targets.length === 0 && !hasManual) {
45
46
  return { attempts };
46
47
  }
47
- if (!opts.json) {
48
+ if (targets.length > 0 && !opts.json) {
48
49
  log.info(`--apply-overrides: ${targets.length} transitive advisor${targets.length === 1 ? 'y' : 'ies'} to pin (${targets.map((t) => t.name).slice(0, 5).join(', ')}${targets.length > 5 ? ', ...' : ''})`);
49
50
  }
50
51
  for (const adv of targets) {
@@ -55,6 +56,7 @@ export async function runOverrideFlow(opts) {
55
56
  field,
56
57
  ok: false,
57
58
  skipped: false,
59
+ source: 'advisory',
58
60
  };
59
61
  if (adv.url)
60
62
  rec.url = adv.url;
@@ -67,90 +69,129 @@ export async function runOverrideFlow(opts) {
67
69
  attempts.push(rec);
68
70
  continue;
69
71
  }
70
- if (opts.dryRun) {
71
- rec.skipped = true;
72
- rec.reason = 'dry-run';
73
- rec.applied = target;
74
- attempts.push(rec);
75
- continue;
76
- }
77
- // 1. Write the pin (or skip when already safe).
78
- const applied = await applyOverrideToFile({
79
- packageJsonPath: pkgJson,
80
- manager: opts.manager,
81
- entry: { name: adv.name, range: target },
82
- ...(opts.overwriteConflicts ? { overwriteConflicts: true } : {}),
83
- });
84
- if (!applied.ok) {
85
- rec.skipped = true;
86
- rec.reason = applied.reason;
87
- if (applied.previous)
88
- rec.previous = applied.previous;
89
- attempts.push(rec);
90
- continue;
72
+ await processPin(rec, { name: adv.name, range: target }, opts, pkgJson, `--apply-overrides`);
73
+ attempts.push(rec);
74
+ }
75
+ // Manual --override pins: processed AFTER advisories so any audit-driven pin that clears a
76
+ // path is visible first. Manual pins share the same install + validator + rollback loop.
77
+ if (opts.manualOverrides && opts.manualOverrides.length > 0) {
78
+ if (!opts.json) {
79
+ log.info(`--override: ${opts.manualOverrides.length} manual pin${opts.manualOverrides.length === 1 ? '' : 's'} to apply`);
91
80
  }
92
- if (!applied.written) {
93
- // No-op (already satisfies). Record as skip but mark ok: true so the summary doesn't
94
- // mistake it for a failure.
95
- rec.skipped = true;
96
- rec.ok = true;
97
- rec.reason = applied.reason ?? 'already satisfies target';
98
- if (applied.applied)
99
- rec.applied = applied.applied;
100
- if (applied.previous)
101
- rec.previous = applied.previous;
81
+ for (const spec of opts.manualOverrides) {
82
+ if (spec.chain.length === 0)
83
+ continue;
84
+ const name = spec.chain[spec.chain.length - 1];
85
+ const parentChain = spec.chain.slice(0, -1);
86
+ const rec = {
87
+ name,
88
+ severity: 'low',
89
+ ids: [],
90
+ field,
91
+ ok: false,
92
+ skipped: false,
93
+ source: 'manual',
94
+ };
95
+ if (spec.chain.length > 1)
96
+ rec.chain = [...spec.chain];
97
+ if (spec.source)
98
+ rec.title = spec.source;
99
+ const entry = { name, range: spec.range };
100
+ if (parentChain.length > 0)
101
+ entry.parentChain = parentChain;
102
+ await processPin(rec, entry, opts, pkgJson, `--override`);
102
103
  attempts.push(rec);
103
- continue;
104
104
  }
105
- rec.applied = applied.applied ?? target;
105
+ }
106
+ return { attempts };
107
+ }
108
+ /**
109
+ * Shared write + install + validate + rollback cycle used for BOTH advisory pins and
110
+ * manual pins. Mutates `rec` in place: callers push it into `attempts` regardless of
111
+ * outcome. Returns nothing — every outcome is captured on `rec`.
112
+ */
113
+ async function processPin(rec, entry, opts, pkgJson, logTag) {
114
+ const label = entry.parentChain && entry.parentChain.length > 0
115
+ ? `${[...entry.parentChain, entry.name].join('>')}`
116
+ : entry.name;
117
+ if (opts.dryRun) {
118
+ rec.skipped = true;
119
+ rec.reason = 'dry-run';
120
+ rec.applied = entry.range;
121
+ return;
122
+ }
123
+ const applied = await applyOverrideToFile({
124
+ packageJsonPath: pkgJson,
125
+ manager: opts.manager,
126
+ entry,
127
+ ...(opts.overwriteConflicts ? { overwriteConflicts: true } : {}),
128
+ });
129
+ if (!applied.ok) {
130
+ rec.skipped = true;
131
+ rec.reason = applied.reason;
106
132
  if (applied.previous)
107
133
  rec.previous = applied.previous;
108
- // 2. Install with the new pin.
109
- if (!opts.json) {
110
- log.info(`--apply-overrides: pinning ${adv.name} → ${target} and running ${opts.manager} install`);
111
- }
112
- const install = await runInstall(opts.cwd, opts.manager, {});
113
- if (!install.ok) {
114
- rec.installLog = tailOutput(install);
115
- const rb = await rollback(pkgJson, opts, adv.name);
116
- rec.rolledBack = rb.rolledBack;
117
- rec.reason = `install failed after override (exit ${install.exitCode})`;
118
- attempts.push(rec);
119
- continue;
120
- }
121
- // 3. Validator (when provided).
122
- if (opts.runValidator) {
123
- const v = await opts.runValidator();
124
- if (!v.ok) {
125
- rec.installLog = v.message;
126
- const rb = await rollback(pkgJson, opts, adv.name);
127
- rec.rolledBack = rb.rolledBack;
128
- rec.reason = `validator failed after override: ${v.message ?? 'non-zero exit'}`;
129
- attempts.push(rec);
130
- continue;
131
- }
132
- }
134
+ return;
135
+ }
136
+ if (!applied.written) {
137
+ rec.skipped = true;
133
138
  rec.ok = true;
134
- attempts.push(rec);
135
- if (!opts.json) {
136
- log.success(`--apply-overrides: pinned ${adv.name}@${target} (severity: ${adv.severity})`);
139
+ rec.reason = applied.reason ?? 'already satisfies target';
140
+ if (applied.applied)
141
+ rec.applied = applied.applied;
142
+ if (applied.previous)
143
+ rec.previous = applied.previous;
144
+ return;
145
+ }
146
+ rec.applied = applied.applied ?? entry.range;
147
+ if (applied.previous)
148
+ rec.previous = applied.previous;
149
+ if (!opts.json) {
150
+ log.info(`${logTag}: pinning ${label} → ${entry.range} and running ${opts.manager} install`);
151
+ }
152
+ const installer = opts.installer ?? runInstall;
153
+ const install = await installer(opts.cwd, opts.manager, {});
154
+ if (!install.ok) {
155
+ rec.installLog = tailOutput(install);
156
+ const rb = await rollback(pkgJson, opts, entry);
157
+ rec.rolledBack = rb.rolledBack;
158
+ rec.reason = `install failed after override (exit ${install.exitCode})`;
159
+ return;
160
+ }
161
+ if (opts.runValidator) {
162
+ const v = await opts.runValidator();
163
+ if (!v.ok) {
164
+ if (v.message)
165
+ rec.installLog = v.message;
166
+ const rb = await rollback(pkgJson, opts, entry);
167
+ rec.rolledBack = rb.rolledBack;
168
+ rec.reason = `validator failed after override: ${v.message ?? 'non-zero exit'}`;
169
+ return;
137
170
  }
138
171
  }
139
- return { attempts };
172
+ rec.ok = true;
173
+ if (!opts.json) {
174
+ log.success(`${logTag}: pinned ${label}@${entry.range}`);
175
+ }
140
176
  }
141
177
  /**
142
178
  * Remove the override we just added and re-run install so the workspace ends up in the same
143
- * state the user had at the start of the attempt. Install failures during rollback are logged
179
+ * state the user had at the start of the attempt. The entry's parent chain (if any) is
180
+ * threaded through to `removeOverrideFromFile` so we delete the EXACT slot we wrote and
181
+ * never touch an adjacent flat pin or sibling. Install failures during rollback are logged
144
182
  * but don't crash the flow — the next override attempt will run whatever state we're in.
145
183
  */
146
- async function rollback(pkgJson, opts, name) {
147
- const removed = await removeOverrideFromFile(pkgJson, opts.manager, name);
184
+ async function rollback(pkgJson, opts, entry) {
185
+ const chain = [...(entry.parentChain ?? []), entry.name];
186
+ const label = chain.join('>');
187
+ const removed = await removeOverrideFromFile(pkgJson, opts.manager, { chain });
148
188
  if (!removed.ok || !removed.removed) {
149
189
  return { rolledBack: false, reinstallOk: false };
150
190
  }
151
- const reinstall = await runInstall(opts.cwd, opts.manager, {});
191
+ const installer = opts.installer ?? runInstall;
192
+ const reinstall = await installer(opts.cwd, opts.manager, {});
152
193
  if (!reinstall.ok && !opts.json) {
153
- log.warn(`rollback: re-install after removing override for ${name} exited ${reinstall.exitCode}; workspace may need manual cleanup.`);
194
+ log.warn(`rollback: re-install after removing override for ${label} exited ${reinstall.exitCode}; workspace may need manual cleanup.`);
154
195
  }
155
196
  return { rolledBack: true, reinstallOk: reinstall.ok };
156
197
  }
@@ -1 +1 @@
1
- {"version":3,"file":"overrideFlow.js","sourceRoot":"","sources":["../../src/cli/overrideFlow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"overrideFlow.js","sourceRoot":"","sources":["../../src/cli/overrideFlow.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/cli/summary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAgDpD,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,MAAM,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAID;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CActG;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,gBAAgB,EAC5B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B;AAoBD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CA8S/F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAwN3F"}
1
+ {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/cli/summary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAgDpD,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,MAAM,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,aAAa,CAAC;IACtB,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAID;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CActG;AAED,wBAAsB,YAAY,CAChC,UAAU,EAAE,gBAAgB,EAC5B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAgB7B;AAoBD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAoT/F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAwN3F"}
@@ -303,18 +303,21 @@ export function renderSummaryMarkdown(structured, toolVersion) {
303
303
  lines.push(`_wrote to \`${structured.overrides.field}\` — ${ok.length} pinned, ${noOp.length} already safe, ${failed.length} failed._`);
304
304
  lines.push('');
305
305
  if (ok.length > 0) {
306
- lines.push(`| Package | Pinned to | Severity | Advisory |`);
307
- lines.push(`| --- | --- | --- | --- |`);
306
+ lines.push(`| Package | Pinned to | Source | Severity | Advisory |`);
307
+ lines.push(`| --- | --- | --- | --- | --- |`);
308
308
  for (const a of ok) {
309
309
  const advCell = a.url && a.ids[0] ? `[${a.ids[0]}](${a.url})` : a.ids[0] ?? '';
310
- lines.push(`| \`${a.name}\` | \`${a.applied ?? '?'}\` | ${a.severity} | ${advCell} |`);
310
+ const label = a.chain && a.chain.length > 1 ? a.chain.join(' ') : a.name;
311
+ const sourceLabel = a.source === 'manual' ? '`--override`' : 'advisory';
312
+ lines.push(`| \`${label}\` | \`${a.applied ?? '?'}\` | ${sourceLabel} | ${a.severity} | ${advCell} |`);
311
313
  }
312
314
  lines.push('');
313
315
  }
314
316
  if (failed.length > 0) {
315
317
  lines.push(`**Failed overrides** (rolled back):`);
316
318
  for (const a of failed) {
317
- lines.push(`- \`${a.name}\` ${a.reason ?? 'unknown'}`);
319
+ const label = a.chain && a.chain.length > 1 ? a.chain.join(' ') : a.name;
320
+ lines.push(`- \`${label}\` — ${a.reason ?? 'unknown'}`);
318
321
  }
319
322
  lines.push('');
320
323
  }