mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-27 11:31:45 -04:00
* 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>
553 lines
18 KiB
TypeScript
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,
|
|
}
|
|
}
|