fix: render peer dependency issues on strict error (#11450)

Fixes #11439.

When `strictPeerDependencies: true` causes `ERR_PNPM_PEER_DEP_ISSUES`, the peer dependency issues are again rendered inline — using the **same format as `pnpm peers check`** — so users (and CI tools like Renovate) can see what failed without running another command.

The non-strict warning path is unchanged: it still emits the short "Run `pnpm peers check`" hint.

### Behavior

`strictPeerDependencies: true`:
```
 ERR_PNPM_PEER_DEP_ISSUES  Unmet peer dependencies

✕ unmet peer react
  Installed: 17.0.2
  Wanted:
    ^18.2.0:
      react-dom@18.2.0
hint: To disable failing on peer dependency issues, add the following to pnpm-workspace.yaml in your project root:

  strictPeerDependencies: false
```

`strictPeerDependencies: false` (unchanged):
```
 WARN  Issues with peer dependencies found. Run "pnpm peers check" to list them.
```

### Implementation

- Added a new `@pnpm/deps.inspection.peers-issues-renderer` package at `deps/inspection/peers-issues-renderer/`, alongside its data producer `@pnpm/deps.inspection.peers-checker`. It exposes a single `renderPeerIssues()` that emits the flat issue list previously inlined in `pnpm peers check`.
- Removed the duplicated formatter from `deps/inspection/commands/src/peers.ts` and made the `pnpm peers check` command consume the new renderer.
- `cli/default-reporter/src/reportError.ts`: `reportPeerDependencyIssuesError` now calls the shared `renderPeerIssues()` and prefixes the hint block with the rendered output. Tests strip ANSI escapes before substring assertions so they stay correct under `FORCE_COLOR=1`.

Result: a single renderer is shared between the install error and the `pnpm peers check` command — output is identical between the two paths.
This commit is contained in:
Zoltan Kochan
2026-05-04 19:44:30 +02:00
committed by GitHub
parent 81817ec55a
commit 55de4febeb
17 changed files with 438 additions and 64 deletions

View File

@@ -36,6 +36,7 @@
"@pnpm/cli.meta": "workspace:*",
"@pnpm/config.reader": "workspace:*",
"@pnpm/core-loggers": "workspace:*",
"@pnpm/deps.inspection.peers-issues-renderer": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/installing.dedupe.issues-renderer": "workspace:*",
"@pnpm/installing.dedupe.types": "workspace:*",

View File

@@ -1,5 +1,6 @@
import type { Config } from '@pnpm/config.reader'
import type { Log } from '@pnpm/core-loggers'
import { renderPeerIssues } from '@pnpm/deps.inspection.peers-issues-renderer'
import type { PnpmError } from '@pnpm/error'
import { renderDedupeCheckIssues } from '@pnpm/installing.dedupe.issues-renderer'
import type { DedupeCheckIssues } from '@pnpm/installing.dedupe.types'
@@ -461,9 +462,7 @@ function reportPeerDependencyIssuesError (
msg: { issuesByProjects: PeerDependencyIssuesByProjects }
): ErrorInfo {
const hasMissingPeers = getHasMissingPeers(msg.issuesByProjects)
const hints: string[] = [
'Run "pnpm peers check" to list the peer dependency issues.',
]
const hints: string[] = []
if (hasMissingPeers) {
hints.push(`To auto-install peer dependencies, add the following to "pnpm-workspace.yaml" in your project root:
@@ -471,11 +470,12 @@ function reportPeerDependencyIssuesError (
}
hints.push(`To disable failing on peer dependency issues, add the following to pnpm-workspace.yaml in your project root:
strictPeerDependencies: false
`)
strictPeerDependencies: false`)
const formattedHints = hints.map((hint) => `hint: ${hint}`).join('\n')
const rendered = renderPeerIssues(msg.issuesByProjects)
return {
title: err.message,
body: hints.map((hint) => `hint: ${hint}`).join('\n'),
body: rendered ? `${rendered}\n${formattedHints}` : formattedHints,
}
}

View File

@@ -1,3 +1,5 @@
import { stripVTControlCharacters as stripAnsi } from 'node:util'
import { expect, test } from '@jest/globals'
import { toOutput$ } from '@pnpm/cli.default-reporter'
import { peerDependencyIssuesLogger } from '@pnpm/core-loggers'
@@ -44,7 +46,7 @@ test('print peer dependency issues warning', async () => {
expect.assertions(1)
const output = await firstValueFrom(output$)
expect(output).toContain('pnpm peers check')
expect(stripAnsi(output)).toContain('pnpm peers check')
})
test('print peer dependency issues error', async () => {
@@ -81,8 +83,10 @@ test('print peer dependency issues error', async () => {
})
logger.error(err, err)
expect.assertions(1)
expect.assertions(3)
const output = await firstValueFrom(output$)
expect(output).toContain('pnpm peers check')
const output = stripAnsi(await firstValueFrom(output$))
expect(output).toContain('unmet peer a')
expect(output).toContain('Installed: 2')
expect(output).toContain('b@1.0.0')
})

View File

@@ -25,6 +25,9 @@
{
"path": "../../core/types"
},
{
"path": "../../deps/inspection/peers-issues-renderer"
},
{
"path": "../../installing/dedupe/issues-renderer"
},