Files
pnpm/cli/default-reporter/src/reportError.ts
Zoltan Kochan 0d88df854f chore: update all dependencies to latest versions (#11032)
* chore: update all dependencies to latest versions

Update all outdated dependencies across the monorepo catalog and fix
breaking changes from major version bumps.

Notable updates:
- ESLint 9 → 10 (fix custom rule API, disable new no-useless-assignment)
- @stylistic/eslint-plugin 4 → 5 (auto-fixed indent changes)
- @cyclonedx/cyclonedx-library 9 → 10 (adapt to removed SPDX API)
- esbuild 0.25 → 0.27
- TypeScript 5.9.2 → 5.9.3
- Various @types packages, test utilities, and build tools

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update unified/remark/mdast imports for v11/v4 API changes

Update imports in get-release-text for the new ESM named exports:
- mdast-util-to-string: default → { toString }
- unified: default → { unified }

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve typecheck errors from dependency updates

- isexe v4: use named import { sync } instead of default export
- remark-parse/remark-stringify v11: add vfile as packageExtension
  dependency so TypeScript can resolve type declarations
- get-release-text: remove unused @ts-expect-error directives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert runtime dependency major version bumps

Revert major version bumps for runtime dependencies that are bundled
into pnpm to fix test failures where pnpm add silently fails:
- bin-links: keep ^5.0.0 (was ^6.0.0)
- cli-truncate: keep ^4.0.0 (was ^5.2.0)
- delay: keep ^6.0.0 (was ^7.0.0)
- filenamify: keep ^6.0.0 (was ^7.0.1)
- find-up: keep ^7.0.0 (was ^8.0.0)
- isexe: keep 2.0.0 (was 4.0.0)
- normalize-newline: keep 4.1.0 (was 5.0.0)
- p-queue: keep ^8.1.0 (was ^9.1.0)
- ps-list: keep ^8.1.1 (was ^9.0.0)
- string-length: keep ^6.0.0 (was ^7.0.1)
- symlink-dir: keep ^7.0.0 (was ^9.0.0)
- terminal-link: keep ^4.0.0 (was ^5.0.0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore runtime dependency major version bumps

Re-apply all runtime dependency major version bumps that were
previously reverted. All packages maintain their default exports
except isexe v4 which needs named imports.

Updated runtime deps:
- bin-links: ^5.0.0 → ^6.0.0
- cli-truncate: ^4.0.0 → ^5.2.0
- delay: ^6.0.0 → ^7.0.0
- filenamify: ^6.0.0 → ^7.0.1
- find-up: ^7.0.0 → ^8.0.0
- isexe: 2.0.0 → 4.0.0 (fix: use named import { sync })
- normalize-newline: 4.1.0 → 5.0.0
- p-queue: ^8.1.0 → ^9.1.0
- ps-list: ^8.1.1 → ^9.0.0
- string-length: ^6.0.0 → ^7.0.1
- symlink-dir: ^7.0.0 → ^9.0.0
- terminal-link: ^4.0.0 → ^5.0.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert tempy to 3.0.0 to fix bundle hang

tempy 3.2.0 pulls in temp-dir 3.0.0 which uses async fs.realpath()
inside its module init. When bundled by esbuild into the __esm lazy
init pattern, this causes a deadlock during module initialization,
making the pnpm binary hang silently on startup.

Keeping tempy at 3.0.0 which uses temp-dir 2.x (sync fs.realpathSync).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add comment explaining why tempy cannot be upgraded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert nock to 13.3.4 for node-fetch compatibility

nock 14 changed its HTTP interception mechanism in a way that doesn't
properly intercept node-fetch requests, causing audit tests to hang
waiting for responses that are never intercepted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add comment explaining why nock cannot be upgraded

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update symlink-dir imports for v10 ESM named exports

symlink-dir v10 removed the default export and switched to named
exports: { symlinkDir, symlinkDirSync }.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: revert @typescript/native-preview to working version

Newer tsgo dev builds (>= 20260318) have a regression where
@types/node cannot be resolved, breaking all node built-in types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: vulnerabilities

* fix: align comment indentation in runLifecycleHook

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: pin msgpackr to 1.11.8 for TypeScript 5.9 compatibility

msgpackr 1.11.9 has broken type definitions that use Iterable/Iterator
without required type arguments, causing compile errors with TS 5.9.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 23:28:53 +01:00

553 lines
18 KiB
TypeScript

import type { Config } from '@pnpm/config.reader'
import type { Log } from '@pnpm/core-loggers'
import type { PnpmError } from '@pnpm/error'
import { renderDedupeCheckIssues } from '@pnpm/installing.dedupe.issues-renderer'
import type { DedupeCheckIssues } from '@pnpm/installing.dedupe.types'
import { renderPeerIssues } from '@pnpm/installing.render-peer-issues'
import type { PeerDependencyIssuesByProjects } from '@pnpm/types'
import chalk from 'chalk'
import { equals } from 'ramda'
import StackTracey from 'stacktracey'
import { EOL } from './constants.js'
StackTracey.maxColumnWidths = {
callee: 25,
file: 350,
sourceLine: 25,
}
const highlight = chalk.yellow
const colorPath = chalk.gray
export function reportError (logObj: Log, config?: Config): string | null {
const errorInfo = getErrorInfo(logObj, config)
if (!errorInfo) return null
let output = formatErrorSummary(errorInfo.title, (logObj as LogObjWithPossibleError).err?.code)
if (logObj.pkgsStack != null) {
if (logObj.pkgsStack.length > 0) {
output += `\n\n${formatPkgsStack(logObj.pkgsStack)}`
} else if ('prefix' in logObj && logObj.prefix) {
output += `\n\nThis error happened while installing a direct dependency of ${logObj.prefix}`
}
}
if (errorInfo.body) {
output += `\n\n${errorInfo.body}`
}
return output
/**
* A type to assist with introspection of the logObj.
* These objects may or may not have an `err` field.
*/
interface LogObjWithPossibleError {
readonly err?: { code?: string }
}
}
interface ErrorInfo {
title: string
body?: string
}
function getErrorInfo (logObj: Log, config?: Config): ErrorInfo | null {
if ('err' in logObj && logObj.err) {
const err = logObj.err as (PnpmError & { stack: object })
switch (err.code) {
case 'ERR_PNPM_UNEXPECTED_STORE':
return reportUnexpectedStore(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_UNEXPECTED_VIRTUAL_STORE':
return reportUnexpectedVirtualStoreDir(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_STORE_BREAKING_CHANGE':
return reportStoreBreakingChange(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_MODULES_BREAKING_CHANGE':
return reportModulesBreakingChange(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_MODIFIED_DEPENDENCY':
return reportModifiedDependency(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_LOCKFILE_BREAKING_CHANGE':
return reportLockfileBreakingChange(err, logObj)
case 'ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT':
return { title: err.message }
case 'ERR_PNPM_MISSING_TIME':
return { title: err.message, body: 'If you cannot fix this registry issue, then set "resolution-mode" to "highest".' }
case 'ERR_PNPM_NO_MATCHING_VERSION':
case 'ERR_PNPM_NO_MATURE_MATCHING_VERSION':
return formatNoMatchingVersion(err, logObj as unknown as { packageMeta: PackageMeta, immatureVersion?: string })
case 'ERR_PNPM_RECURSIVE_FAIL':
return formatRecursiveCommandSummary(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_BAD_TARBALL_SIZE':
return reportBadTarballSize(err, logObj)
case 'ELIFECYCLE':
return reportLifecycleError(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_UNSUPPORTED_ENGINE':
return reportEngineError(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_PEER_DEP_ISSUES':
return reportPeerDependencyIssuesError(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_DEDUPE_CHECK_ISSUES':
return reportDedupeCheckIssuesError(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER':
return reportSpecNotSupportedByAnyResolverError(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_FETCH_401':
case 'ERR_PNPM_FETCH_403':
return reportAuthError(err, logObj as any, config) // eslint-disable-line @typescript-eslint/no-explicit-any
default: {
// Errors with unknown error codes are printed with stack trace
if (!err.code?.startsWith?.('ERR_PNPM_')) {
return formatGenericError(err.message ?? (logObj as { message: string }).message, err.stack)
}
return {
title: err.message ?? '',
body: (logObj as { hint?: string }).hint,
}
}
}
}
return { title: logObj.message! }
}
interface PkgStackItem {
readonly id: string
readonly name: string
// The version may be missing if this was a private workspace package without
// the version field set.
readonly version?: string
}
function formatPkgNameVer ({ name, version }: PkgStackItem) {
return version == null
? name
: `${name}@${version}`
}
function formatPkgsStack (pkgsStack: readonly PkgStackItem[]) {
return `This error happened while installing the dependencies of \
${formatPkgNameVer(pkgsStack[0])}\
${pkgsStack.slice(1).map((pkgInfo) => `${EOL} at ${formatPkgNameVer(pkgInfo)}`).join('')}`
}
interface PackageMeta {
name: string
'dist-tags': Record<string, string> & {
latest: string
}
versions: Record<string, object>
time?: Record<string, string>
}
function formatNoMatchingVersion (err: Error, msg: { packageMeta: PackageMeta, immatureVersion?: string }) {
const meta: PackageMeta = msg.packageMeta
const latestVersion = meta['dist-tags'].latest
let output = `The latest release of ${meta.name} is "${latestVersion}".`
const latestTime = msg.packageMeta.time?.[latestVersion]
if (latestTime) {
output += ` Published at ${stringifyDate(latestTime)}`
}
output += EOL
if (!equals(Object.keys(meta['dist-tags']), ['latest'])) {
output += EOL + 'Other releases are:' + EOL
for (const tag in meta['dist-tags']) {
if (tag !== 'latest') {
const version = meta['dist-tags'][tag]
output += ` * ${tag}: ${version}`
const time = msg.packageMeta.time?.[version]
if (time) {
output += ` published at ${stringifyDate(time)}`
}
output += EOL
}
}
}
output += `${EOL}If you need the full list of all ${Object.keys(meta.versions).length} published versions run "pnpm view ${meta.name} versions".`
if (msg.immatureVersion) {
output += `${EOL}${EOL}If you want to install the matched version ignoring the time it was published, you can add the package name to the minimumReleaseAgeExclude setting. Read more about it: https://pnpm.io/settings#minimumreleaseageexclude`
}
return {
title: err.message,
body: output,
}
}
function stringifyDate (dateStr: string): string {
const now = Date.now()
const oneDayAgo = now - 24 * 60 * 60 * 1000
const date = new Date(dateStr)
if (date.getTime() < oneDayAgo) {
return date.toLocaleDateString()
}
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
}
function reportUnexpectedStore (
err: Error,
msg: {
actualStorePath: string
expectedStorePath: string
modulesDir: string
}
): ErrorInfo {
return {
title: err.message,
body: `The dependencies at "${msg.modulesDir}" are currently linked from the store at "${msg.expectedStorePath}".
pnpm now wants to use the store at "${msg.actualStorePath}" to link dependencies.
If you want to use the new store location, reinstall your dependencies with "pnpm install".
You may change the global store location by running "pnpm config set store-dir <dir> --global".
(This error may happen if the node_modules was installed with a different major version of pnpm)`,
}
}
function reportUnexpectedVirtualStoreDir (
err: Error,
msg: {
actual: string
expected: string
modulesDir: string
}
): ErrorInfo {
return {
title: err.message,
body: `The dependencies at "${msg.modulesDir}" are currently symlinked from the virtual store directory at "${msg.expected}".
pnpm now wants to use the virtual store at "${msg.actual}" to link dependencies from the store.
If you want to use the new virtual store location, reinstall your dependencies with "pnpm install".
You may change the virtual store location by changing the value of the virtual-store-dir config.`,
}
}
function reportStoreBreakingChange (msg: {
additionalInformation?: string
storePath: string
relatedIssue?: number
relatedPR?: number
}): ErrorInfo {
let output = `Store path: ${colorPath(msg.storePath)}
Run "pnpm install" to recreate node_modules.`
if (msg.additionalInformation) {
output = `${output}${EOL}${EOL}${msg.additionalInformation}`
}
output += formatRelatedSources(msg)
return {
title: 'The store used for the current node_modules is incompatible with the current version of pnpm',
body: output,
}
}
function reportModulesBreakingChange (msg: {
additionalInformation?: string
modulesPath: string
relatedIssue?: number
relatedPR?: number
}): ErrorInfo {
let output = `node_modules path: ${colorPath(msg.modulesPath)}
Run ${highlight('pnpm install')} to recreate node_modules.`
if (msg.additionalInformation) {
output = `${output}${EOL}${EOL}${msg.additionalInformation}`
}
output += formatRelatedSources(msg)
return {
title: 'The current version of pnpm is not compatible with the available node_modules structure',
body: output,
}
}
function formatRelatedSources (msg: {
relatedIssue?: number
relatedPR?: number
}): string {
let output = ''
if (!msg.relatedIssue && !msg.relatedPR) return output
output += EOL
if (msg.relatedIssue) {
output += EOL + `Related issue: ${colorPath(`https://github.com/pnpm/pnpm/issues/${msg.relatedIssue}`)}`
}
if (msg.relatedPR) {
output += EOL + `Related PR: ${colorPath(`https://github.com/pnpm/pnpm/pull/${msg.relatedPR}`)}`
}
return output
}
function formatGenericError (errorMessage: string, stack: object): ErrorInfo {
if (stack) {
let prettyStack: string | undefined
try {
prettyStack = new StackTracey(stack).asTable()
} catch {
prettyStack = stack.toString()
}
if (prettyStack) {
return {
title: errorMessage,
body: prettyStack,
}
}
}
return { title: errorMessage }
}
function formatErrorSummary (message: string, code?: string): string {
return `${chalk.bgRed.black(`\u2009${code ?? 'ERROR'}\u2009`)} ${chalk.red(message)}`
}
function reportModifiedDependency (msg: { modified: string[] }): ErrorInfo {
return {
title: 'Packages in the store have been mutated',
body: `These packages are modified:
${msg.modified.map((pkgPath: string) => colorPath(pkgPath)).join(EOL)}
You can run ${highlight('pnpm install --force')} to refetch the modified packages`,
}
}
function reportLockfileBreakingChange (err: Error, _msg: object): ErrorInfo {
return {
title: err.message,
body: `Run with the ${highlight('--force')} parameter to recreate the lockfile.`,
}
}
function formatRecursiveCommandSummary (msg: { failures: Array<Error & { prefix: string }>, passes: number }): ErrorInfo {
const output = EOL + `Summary: ${chalk.red(`${msg.failures.length} fails`)}, ${msg.passes} passes` + EOL + EOL +
msg.failures.map(({ message, prefix }) => {
return prefix + ':' + EOL + formatErrorSummary(message)
}).join(EOL + EOL)
return {
title: '',
body: output,
}
}
function reportBadTarballSize (err: Error, _msg: object): ErrorInfo {
return {
title: err.message,
body: `Seems like you have internet connection issues.
Try running the same command again.
If that doesn't help, try one of the following:
- Set a bigger value for the \`fetch-retries\` config.
To check the current value of \`fetch-retries\`, run \`pnpm get fetch-retries\`.
To set a new value, run \`pnpm set fetch-retries <number>\`.
- Set \`network-concurrency\` to 1.
This change will slow down installation times, so it is recommended to
delete the config once the internet connection is good again: \`pnpm config delete network-concurrency\`
NOTE: You may also override configs via flags.
For instance, \`pnpm install --fetch-retries 5 --network-concurrency 1\``,
}
}
function reportLifecycleError (
msg: {
stage: string
errno?: number | string
}
): ErrorInfo {
if (msg.stage === 'test') {
return { title: 'Test failed. See above for more details.' }
}
if (typeof msg.errno === 'number') {
return { title: `Command failed with exit code ${msg.errno}.` }
}
return { title: 'Command failed.' }
}
function reportEngineError (
msg: {
message: string
current: {
node: string
pnpm: string
}
packageId: string
wanted: {
node?: string
pnpm?: string
}
}
): ErrorInfo {
let output = ''
if (msg.wanted.pnpm) {
output += `\
Your pnpm version is incompatible with "${msg.packageId}".
Expected version: ${msg.wanted.pnpm}
Got: ${msg.current.pnpm}
This is happening because the package's manifest has an engines.pnpm field specified.
To fix this issue, install the required pnpm version globally.
To install the latest version of pnpm, run "pnpm i -g pnpm".
To check your pnpm version, run "pnpm -v".`
}
if (msg.wanted.node) {
if (output) output += EOL + EOL
output += `\
Your Node version is incompatible with "${msg.packageId}".
Expected version: ${msg.wanted.node}
Got: ${msg.current.node}
This is happening because the package's manifest has an engines.node field specified.
To fix this issue, install the required Node version.`
}
return {
title: 'Unsupported environment (bad pnpm and/or Node.js version)',
body: output,
}
}
function reportAuthError (
err: Error,
msg: { hint?: string },
config?: Config
): ErrorInfo {
const foundSettings = [] as string[]
for (const [key, value] of Object.entries(config?.rawConfig ?? {})) {
if (key[0] === '@') {
foundSettings.push(`${key}=${String(value)}`)
continue
}
if (
key.endsWith('_auth') ||
key.endsWith('_authToken') ||
key.endsWith('username') ||
key.endsWith('_password')
) {
foundSettings.push(`${key}=${hideSecureInfo(key, value)}`)
}
}
let output = msg.hint ? `${msg.hint}${EOL}${EOL}` : ''
if (foundSettings.length === 0) {
output += `No authorization settings were found in the configs.
Try to log in to the registry by running "pnpm login"
or add the auth tokens manually to the ~/.npmrc file.`
} else {
output += `These authorization settings were found:
${foundSettings.join('\n')}`
}
return {
title: err.message,
body: output,
}
}
function hideSecureInfo (key: string, value: string): string {
if (key.endsWith('_password')) return '[hidden]'
if (key.endsWith('_auth') || key.endsWith('_authToken')) return `${value.substring(0, 4)}[hidden]`
return value
}
function reportPeerDependencyIssuesError (
err: Error,
msg: { issuesByProjects: PeerDependencyIssuesByProjects }
): ErrorInfo | null {
const hasMissingPeers = getHasMissingPeers(msg.issuesByProjects)
const hints: string[] = []
if (hasMissingPeers) {
hints.push(`To auto-install peer dependencies, add the following to "pnpm-workspace.yaml" in your project root:
autoInstallPeers: true`)
}
hints.push(`To disable failing on peer dependency issues, add the following to pnpm-workspace.yaml in your project root:
strictPeerDependencies: false
`)
const rendered = renderPeerIssues(msg.issuesByProjects)
if (!rendered) {
// This should never happen.
return {
title: err.message,
}
}
return {
title: err.message,
body: `${rendered}
${hints.map((hint) => `hint: ${hint}`).join('\n')}
`,
}
}
function getHasMissingPeers (issuesByProjects: PeerDependencyIssuesByProjects): boolean {
return Object.values(issuesByProjects)
.some((issues) => Object.values(issues.missing).flat().some(({ optional }) => !optional))
}
function reportDedupeCheckIssuesError (err: Error, msg: { dedupeCheckIssues: DedupeCheckIssues }): ErrorInfo {
return {
title: err.message,
body: `\
${renderDedupeCheckIssues(msg.dedupeCheckIssues)}
Run ${chalk.yellow('pnpm dedupe')} to apply the changes above.
`,
}
}
function reportSpecNotSupportedByAnyResolverError (err: Error, logObj: Log): ErrorInfo {
// If the catalog protocol specifier was sent to a "real resolver", it'll
// eventually throw a "specifier not supported" error since the catalog
// protocol is meant to be replaced before it's passed to any of the real
// resolvers.
//
// If this kind of error is thrown, and the dependency bareSpecifier is using the
// catalog protocol it's most likely because we're trying to install an out of
// repo dependency that was published incorrectly. For example, it may be been
// mistakenly published with 'npm publish' instead of 'pnpm publish'. Report a
// more clear error in this case.
if (logObj.package?.bareSpecifier?.startsWith('catalog:')) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return reportExternalCatalogProtocolError(err, logObj as any)
}
return {
title: err.message ?? '',
body: logObj.hint,
}
}
function reportExternalCatalogProtocolError (err: Error, logObj: Log): ErrorInfo {
const { pkgsStack } = logObj
const problemDep = pkgsStack?.[0]
let body = `\
An external package outside of the pnpm workspace declared a dependency using
the catalog protocol. This is likely a bug in that external package. Only
packages within the pnpm workspace may use catalogs. Usages of the catalog
protocol are replaced with real specifiers on 'pnpm publish'.
`
if (problemDep != null) {
body += `\
This is likely a bug in the publishing automation of this package. Consider filing
a bug with the authors of:
${highlight(formatPkgNameVer(problemDep))}
`
}
return {
title: err.message,
body,
}
}