pnpm-audit-promote
v1.8.0
Published
Refresh pnpm dependencies, run audit --fix, and promote catalog-eligible overrides back into the pnpm catalog.
Downloads
267
Maintainers
Readme
pnpm-audit-promote
Refresh a pnpm workspace, run pnpm audit --fix, and promote any
catalog-eligible overrides back into the pnpm catalog.
Direct-dependency vulnerabilities (those resolved by a catalog package) get the
catalog bumped before pnpm audit --fix runs, so they never end up as
overrides. Transitive-only vulnerabilities are still handled by
pnpm audit --fix adding overrides as usual.
Why
In a pnpm 10+ workspace using catalogs, pnpm audit --fix
naively pins fixes via overrides:. For packages that are already declared in
the catalog, this is the wrong place — the fix should land in the catalog so
every workspace package picks it up consistently. This tool runs the audit
flow and then reconciles overrides back into the catalog.
Install
# global
npm i -g pnpm-audit-promote
# or run on demand
pnpm dlx pnpm-audit-promote
npx pnpm-audit-promoteRequires Node.js >= 22 and pnpm (10 or 11) available on PATH.
pnpm 11 notes
pnpm-audit-promote runs against both pnpm 10 and pnpm 11 workspaces. When it detects pnpm 11 it makes two adjustments for the duration of the run:
- Pre-seeds the specific vulnerable packages into
minimumReleaseAgeExcludeinpnpm-workspace.yamlso freshly-published advisory-fix versions are not blocked by pnpm 11's default 24-hour release-age gate — while leaving the globalminimumReleaseAgesetting intact for all other packages. - Merges any
minimumReleaseAgeExcludeentries pnpm 11 writes duringpnpm audit --fixback into the file, so the security excludes are preserved across the catalog-restoration step.
pnpm.overrides defined in package.json are still migrated into the catalog, even though pnpm 11 itself no longer reads them — the migration is the whole point of running this tool. devEngines.packageManager: [email protected] is recognized as a pnpm workspace signal alongside the legacy packageManager field.
The target directory qualifies as a workspace root when any of the following are present:
pnpm-workspace.yaml, orpnpm-lock.yaml, orpackage.jsonwhosepackageManagerfield starts withpnpm@(e.g."[email protected]"or"[email protected]"), orpackage.jsonwhosedevEngines.packageManagernames pnpm (string form"[email protected]"or object form{ "name": "pnpm" }; the format pnpm 11'spnpm initwrites), orpackage.jsonwith apnpmconfig object (e.g. a barepnpm.overrides).
When pnpm-workspace.yaml is absent, the tool still runs the lockfile / node_modules cleanup, strips pnpm.overrides from package.json, and runs pnpm install and pnpm audit --fix. Catalog promotion steps are skipped because pnpm catalogs only live in pnpm-workspace.yaml.
If an enclosing pnpm-workspace.yaml is found in any parent directory, the tool refuses to run by default and throws EnclosingWorkspaceError. This prevents pnpm install from silently walking up and mutating the parent monorepo's lockfile and pnpm-workspace.yaml. To proceed, either re-run with --path <enclosing workspace root> to operate on the parent, or pass --ignore-workspace to keep all operations local to --path.
Usage
pnpm-audit-promote [options]| Flag | Description | Default |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| -p, --path <dir> | Workspace root (pnpm-workspace.yaml, pnpm-lock.yaml, or package.json declaring pnpm) | cwd |
| -f, --force / -y, --yes | Skip the destructive-action confirmation prompt | false |
| -n, --dry-run | Plan and log changes without writing files or pnpm | false |
| --no-audit | Skip the audit + catalog promotion phase | |
| --no-dedupe | Skip pnpm dedupe calls | |
| --allow-major | Allow catalog bumps that cross a major version boundary (still logged as warnings). Use --no-allow-major to refuse them and keep the bump as an override. | true |
| --no-summary | Suppress the terminal-pretty run summary printed at the end | |
| --summary-file <path> | Also write a plain-text (no ANSI) copy of the run summary to the given path. Path must be within the workspace root; outside paths are silently skipped. | |
| --ignore-workspace | Treat --path as the workspace root even when an enclosing pnpm-workspace.yaml is found in a parent directory. Forwards --ignore-workspace to every pnpm invocation so installs/overrides stay local. | false |
| -v, --verbose | Verbose output (raw pnpm output + tracing) | false |
| -q, --quiet | Quiet output (warnings + errors only) | false |
| -V, --version | Print version | |
| -h, --help | Print help | |
Example
pnpm-audit-promote --force
pnpm-audit-promote --dry-run --verbose
# operate on a single package nested inside another pnpm monorepo
pnpm-audit-promote --path ./examples/angular-v20 --ignore-workspace --forceA minimal end-to-end fixture lives under examples/basic — see its README for a --dry-run walkthrough.
Output modes
- Default (
normal): clean, decision-focused CLI output (phases, actions, and outcomes). --verbose: includes everything from default mode plus rawpnpmcommand output.--quiet: warnings + errors only.
What it does (in order)
- Remove
pnpm-lock.yaml - Remove every
node_modulesfolder under the workspace - Strip the
overrides:block frompnpm-workspace.yaml - Strip
pnpm.overridesfrom everypackage.json pnpm installpnpm dedupe(skip with--no-dedupe)- Pre-audit catalog bump for direct-dep vulnerabilities
pnpm audit --fix(transitive vulnerabilities)- Promote any catalog-eligible audit overrides back into the catalog
pnpm installpnpm dedupe(skip with--no-dedupe)
After every pnpm command the tool re-applies the desired pnpm-workspace.yaml
because pnpm 10 normalizes the file on install/up and may silently drop
settings (e.g. savePrefix: '') or bump catalog versions.
Concrete example
Before:
# pnpm-workspace.yaml
catalog:
react: '18.2.0'
lodash: '4.17.20'pnpm audit --fix would normally produce:
catalog:
react: '18.2.0'
lodash: '4.17.20'
overrides:
react: '18.3.1' # direct dep — should live in the catalog
'foo@<1.0.0': '1.0.1' # transitive — keep as overrideAfter pnpm-audit-promote:
catalog:
react: '18.3.1'
lodash: '4.17.20'
overrides:
'foo@<1.0.0': '1.0.1'Programmatic API
import { refreshDeps, createLogger } from 'pnpm-audit-promote';
const result = await refreshDeps({
path: '/path/to/workspace',
force: true,
dryRun: false,
logger: createLogger({ level: 'verbose' }),
// Optional — all default-friendly:
// skipAudit: false,
// skipDedupe: false,
// allowMajor: true,
// ignoreWorkspace: false, // forward --ignore-workspace to pnpm
// summary: true, // render terminal summary at the end
// summaryFile: './summary.txt', // must resolve within the workspace root
// confirm: async ({ force, dryRun }) => true, // override the destructive-action prompt
// pnpm: customPnpmRunner, // inject a fake/recording PnpmRunner in tests
});
console.log(`Fixed ${result.fixedAdvisories.length} vulnerabilities in ${result.durationMs}ms.`);refreshDeps resolves to a RefreshResult with:
canceled—truewhen the destructive-action prompt was declined.durationMs— wall-clock duration of the run.catalogChanges,overrideChanges— direct-dep catalog bumps and the overrides that were added/modified.initialAdvisories,finalAdvisories,fixedAdvisories— the audit-derived vulnerability sets before, after, and the diff.summary— the full structuredRunSummaryDataregardless of thesummaryrendering flag (so CI can serialize it).
Exported surface
refreshDeps, RefreshOptions, RefreshResult,
createLogger, consoleLogger, silentLogger, Logger, LogLevel, ConsoleLoggerOptions,
WorkspaceState,
createPnpmRunner, ensurePnpmAvailable, PnpmRunner, PnpmOptions,
renderTerminalSummary, RenderOptions,
AdvisorySummary, CatalogChange, OverrideChange, RunSummaryData, Severity,
ConfirmFn, ConfirmContext,
PKG_VERSION,
and the typed errors
WorkspaceNotFoundError, EnclosingWorkspaceError, PnpmNotInstalledError, PnpmCommandFailedError, NonInteractiveConfirmationError.
The pnpm option on RefreshOptions allows injecting a custom PnpmRunner implementation (for tests or advanced integrations). The confirm option overrides the default stdin-based destructive-action prompt — useful in CI or when wrapping the tool in another UI.
Limitations
- YAML edits are line-oriented (regex-based) to preserve formatting bit-for-bit;
JSON edits use
jsonc-parserfor minimal, structure-aware changes. Unusual constructs (YAML anchors, custom comments inside the catalog block) are not deeply parsed. - Only the root
package.json'spnpm.overridesis promoted into the catalog. pnpmmust be available onPATH. The tool does not bundle pnpm.- Direct-dependency promotion uses
pnpm audit --json; if pnpm changes that schema, the pre-audit bump becomes a no-op (the safety net via post-audit promotion still works).
Troubleshooting
pnpm is not installed or not on PATH— install pnpm globally (npm i -g pnpm) or use Corepack.- Refusing to run destructive operations non-interactively — re-run with
--force(or--yes) when running from CI. - A
node_modulesfolder cannot be fully removed (Windows) — close any process holding files open (editors, dev servers) and re-run. Refusing to operate on '<path>': an enclosing pnpm workspace was found at '<parent>'— the directory you targeted sits inside another pnpm workspace.pnpm installwould walk up and mutate that parent'spnpm-workspace.yamlandpnpm-lock.yaml. Either re-run with--path <parent>to refresh the whole monorepo, or pass--ignore-workspaceto keep every pnpm invocation local to your--path.
Contributing
See CONTRIBUTING.md.
