feat: print error codes in error messages (#3748)

This commit is contained in:
Zoltan Kochan
2021-09-06 22:37:23 +03:00
committed by GitHub
parent f1983ea552
commit e0aa55140c
4 changed files with 126 additions and 77 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/default-reporter": minor
---
Print error codes in error messages.

View File

@@ -16,6 +16,18 @@ const highlight = chalk.yellow
const colorPath = chalk.gray
export default function reportError (logObj: Log, config?: Config) {
const errorInfo = getErrorInfo(logObj, config)
let output = formatErrorSummary(errorInfo.title, logObj['err']['code'])
if (errorInfo.body) {
output += `\n\n${errorInfo.body}`
}
return output
}
function getErrorInfo (logObj: Log, config?: Config): {
title: string
body?: string
} {
if (logObj['err']) {
const err = logObj['err'] as (PnpmError & { stack: object })
switch (err.code) {
@@ -32,7 +44,7 @@ export default function reportError (logObj: Log, config?: Config) {
case 'ERR_PNPM_LOCKFILE_BREAKING_CHANGE':
return reportLockfileBreakingChange(err, logObj)
case 'ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT':
return formatErrorSummary(err.message)
return { title: err.message }
case 'ERR_PNPM_NO_MATCHING_VERSION':
return formatNoMatchingVersion(err, logObj)
case 'ERR_PNPM_RECURSIVE_FAIL':
@@ -42,7 +54,7 @@ export default function reportError (logObj: Log, config?: Config) {
case 'ELIFECYCLE':
return reportLifecycleError(logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
case 'ERR_PNPM_UNSUPPORTED_ENGINE':
return reportEngineError(err, logObj as any) // eslint-disable-line @typescript-eslint/no-explicit-any
return reportEngineError(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
@@ -51,18 +63,21 @@ export default function reportError (logObj: Log, config?: Config) {
if (!err.code?.startsWith?.('ERR_PNPM_')) {
return formatGenericError(err.message ?? logObj['message'], err.stack)
}
let errorOutput = formatErrorSummary(err.message)
const errorOutput = []
if (logObj['pkgsStack']?.length) {
errorOutput += `${EOL}${formatPkgsStack(logObj['pkgsStack'])}`
errorOutput.push(formatPkgsStack(logObj['pkgsStack']))
}
if (logObj['hint']) {
errorOutput += `${EOL}${logObj['hint'] as string}`
errorOutput.push(logObj['hint'] as string)
}
return {
title: err.message ?? '',
body: errorOutput.join(EOL),
}
return errorOutput
}
}
}
return formatErrorSummary(logObj['message'])
return { title: logObj['message'] }
}
function formatPkgsStack (pkgsStack: Array<{ id: string, name: string, version: string }>) {
@@ -77,10 +92,7 @@ function formatNoMatchingVersion (err: Error, msg: object) {
'dist-tags': Record<string, string> & { latest: string }
versions: Record<string, object>
} = msg['packageMeta']
let output = `\
${formatErrorSummary(err.message)}
The latest release of ${meta.name} is "${meta['dist-tags'].latest}".${EOL}`
let output = `The latest release of ${meta.name} is "${meta['dist-tags'].latest}".${EOL}`
if (!equals(Object.keys(meta['dist-tags']), ['latest'])) {
output += EOL + 'Other releases are:' + EOL
@@ -93,7 +105,10 @@ The latest release of ${meta.name} is "${meta['dist-tags'].latest}".${EOL}`
output += `${EOL}If you need the full list of all ${Object.keys(meta.versions).length} published versions run "$ pnpm view ${meta.name} versions".`
return output
return {
title: err.message,
body: output,
}
}
function reportUnexpectedStore (
@@ -104,16 +119,17 @@ function reportUnexpectedStore (
modulesDir: string
}
) {
return `${formatErrorSummary(err.message)}
The dependencies at "${msg.modulesDir}" are currently linked from the store at "${msg.expectedStorePath}".
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>".
(This error may happen if the node_modules was installed with a different major version of pnpm)`
(This error may happen if the node_modules was installed with a different major version of pnpm)`,
}
}
function reportUnexpectedVirtualStoreDir (
@@ -124,15 +140,16 @@ function reportUnexpectedVirtualStoreDir (
modulesDir: string
}
) {
return `${formatErrorSummary(err.message)}
The dependencies at "${msg.modulesDir}" are currently symlinked from the virtual store directory at "${msg.expected}".
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.`
You may change the virtual store location by changing the value of the virtual-store-dir config.`,
}
}
function reportStoreBreakingChange (msg: {
@@ -141,9 +158,7 @@ function reportStoreBreakingChange (msg: {
relatedIssue?: number
relatedPR?: number
}) {
let output = `\
${formatErrorSummary('The store used for the current node_modules is incomatible with the current version of pnpm')}
Store path: ${colorPath(msg.storePath)}
let output = `Store path: ${colorPath(msg.storePath)}
Run "pnpm install" to recreate node_modules.`
@@ -152,7 +167,10 @@ Run "pnpm install" to recreate node_modules.`
}
output += formatRelatedSources(msg)
return output
return {
title: 'The store used for the current node_modules is incomatible with the current version of pnpm',
body: output,
}
}
function reportModulesBreakingChange (msg: {
@@ -161,9 +179,7 @@ function reportModulesBreakingChange (msg: {
relatedIssue?: number
relatedPR?: number
}) {
let output = `\
${formatErrorSummary('The current version of pnpm is not compatible with the available node_modules structure')}
node_modules path: ${colorPath(msg.modulesPath)}
let output = `node_modules path: ${colorPath(msg.modulesPath)}
Run ${highlight('pnpm install')} to recreate node_modules.`
@@ -172,7 +188,10 @@ Run ${highlight('pnpm install')} to recreate node_modules.`
}
output += formatRelatedSources(msg)
return output
return {
title: 'The current version of pnpm is not compatible with the available node_modules structure',
body: output,
}
}
function formatRelatedSources (msg: {
@@ -205,31 +224,34 @@ function formatGenericError (errorMessage: string, stack: object) {
prettyStack = undefined
}
if (prettyStack) {
return `${formatErrorSummary(errorMessage)}${EOL}${prettyStack}`
return {
title: errorMessage,
body: prettyStack,
}
}
}
return formatErrorSummary(errorMessage)
return { title: errorMessage }
}
function formatErrorSummary (message: string) {
return `${chalk.bgRed.black('\u2009ERROR\u2009')} ${chalk.red(message)}`
function formatErrorSummary (message: string, code?: string) {
return `${chalk.bgRed.black(`\u2009${code ?? 'ERROR'}\u2009`)} ${chalk.red(message)}`
}
function reportModifiedDependency (msg: { modified: string[] }) {
return `\
${formatErrorSummary('Packages in the store have been mutated')}
These packages are modified:
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')} to refetch the modified packages`
You can run ${highlight('pnpm install')} to refetch the modified packages`,
}
}
function reportLockfileBreakingChange (err: Error, msg: object) {
return `\
${formatErrorSummary(err.message)}
Run with the ${highlight('--force')} parameter to recreate the lockfile.`
return {
title: err.message,
body: `Run with the ${highlight('--force')} parameter to recreate the lockfile.`,
}
}
function formatRecursiveCommandSummary (msg: { fails: Array<Error & {prefix: string}>, passes: number }) {
@@ -237,14 +259,16 @@ function formatRecursiveCommandSummary (msg: { fails: Array<Error & {prefix: str
msg.fails.map((fail) => {
return fail.prefix + ':' + EOL + formatErrorSummary(fail.message)
}).join(EOL + EOL)
return output
return {
title: '',
body: output,
}
}
function reportBadTarballSize (err: Error, msg: object) {
return `\
${formatErrorSummary(err.message)}
Seems like you have internet connection issues.
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:
@@ -257,7 +281,8 @@ If that doesn't help, try one of the following:
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\``
For instance, \`pnpm install --fetch-retries 5 --network-concurrency 1\``,
}
}
function reportLifecycleError (
@@ -267,16 +292,15 @@ function reportLifecycleError (
}
) {
if (msg.stage === 'test') {
return formatErrorSummary('Test failed. See above for more details.')
return { title: 'Test failed. See above for more details.' }
}
if (typeof msg.errno === 'number') {
return formatErrorSummary(`Command failed with exit code ${msg.errno}.`)
return { title: `Command failed with exit code ${msg.errno}.` }
}
return formatErrorSummary('Command failed.')
return { title: 'Command failed.' }
}
function reportEngineError (
err: Error,
msg: {
message: string
current: {
@@ -293,7 +317,7 @@ function reportEngineError (
let output = ''
if (msg.wanted.pnpm) {
output += `\
${formatErrorSummary(`Your pnpm version is incompatible with "${msg.packageId}".`)}
Your pnpm version is incompatible with "${msg.packageId}".
Expected version: ${msg.wanted.pnpm}
Got: ${msg.current.pnpm}
@@ -307,7 +331,7 @@ To check your pnpm version, run "pnpm -v".`
if (msg.wanted.node) {
if (output) output += EOL + EOL
output += `\
${formatErrorSummary(`Your Node version is incompatible with "${msg.packageId}".`)}
Your Node version is incompatible with "${msg.packageId}".
Expected version: ${msg.wanted.node}
Got: ${msg.current.node}
@@ -315,7 +339,10 @@ 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 output || formatErrorSummary(err.message)
return {
title: 'Unsupported environment (bad pnpm and/or Node.js version)',
body: output,
}
}
function reportAuthError (
@@ -340,9 +367,8 @@ function reportAuthError (
foundSettings.push(`${key}=${hideSecureInfo(key, value)}`)
}
}
let output = `${formatErrorSummary(err.message)}${msg.hint ? `${EOL}${msg.hint}` : ''}
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"
@@ -351,7 +377,10 @@ or add the auth tokens manually to the ~/.npmrc file.`
output += `These authorization settings were found:
${foundSettings.join('\n')}`
}
return output
return {
title: err.message,
body: output,
}
}
function hideSecureInfo (key: string, value: string) {

View File

@@ -20,8 +20,10 @@ import chalk from 'chalk'
import normalizeNewline from 'normalize-newline'
import repeat from 'ramda/src/repeat'
const formatErrorCode = (code: string) => chalk.bgRed.black(`\u2009${code}\u2009`)
const ERROR = formatErrorCode('ERROR')
const WARN = chalk.bgYellow.black('\u2009WARN\u2009')
const ERROR = chalk.bgRed.black('\u2009ERROR\u2009')
const DEPRECATED = chalk.red('deprecated')
const versionColor = chalk.grey
const ADD = chalk.green('+')
@@ -989,7 +991,7 @@ test('logLevel=warn', (done) => {
error: done,
next: output => {
expect(output).toBe(`${WARN} Some issue
${ERROR} ${chalk.red('some error')}`)
${formatErrorCode('ERR_PNPM_SOME_CODE')} ${chalk.red('some error')}`)
},
})
})
@@ -1018,7 +1020,7 @@ test('logLevel=error', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('some error')}`)
expect(output).toBe(`${formatErrorCode('ERR_PNPM_SOME_CODE')} ${chalk.red('some error')}`)
},
})
})

View File

@@ -10,7 +10,9 @@ import loadJsonFile from 'load-json-file'
import normalizeNewline from 'normalize-newline'
import StackTracey from 'stacktracey'
const ERROR = chalk.bgRed.black('\u2009ERROR\u2009')
const formatErrorCode = (code: string) => chalk.bgRed.black(`\u2009${code}\u2009`)
const ERROR = formatErrorCode('ERROR')
test('prints generic error', (done) => {
const output$ = toOutput$({
@@ -51,6 +53,7 @@ test('prints generic error when recursive install fails', (done) => {
next: output => {
expect(output).toBe(`/home/src/:
${ERROR} ${chalk.red('some error')}
${new StackTracey(err.stack).pretty as string}`)
},
})
@@ -98,7 +101,7 @@ test('prints no matching version error when only the latest dist-tag exists', (d
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('No matching version found for is-positive@1000.0.0')}
expect(output).toBe(`${formatErrorCode('ERR_PNPM_NO_MATCHING_VERSION')} ${chalk.red('No matching version found for is-positive@1000.0.0')}
The latest release of is-positive is "3.1.0".
@@ -123,7 +126,7 @@ test('prints suggestions when an internet-connection related error happens', (do
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Actual size (99) of tarball (https://foo) did not match the one specified in \'Content-Length\' header (100)')}
expect(output).toBe(`${formatErrorCode('ERR_PNPM_BAD_TARBALL_SIZE')} ${chalk.red('Actual size (99) of tarball (https://foo) did not match the one specified in \'Content-Length\' header (100)')}
Seems like you have internet connection issues.
Try running the same command again.
@@ -160,7 +163,7 @@ test('prints test error', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Test failed. See above for more details.')}`)
expect(output).toBe(`${formatErrorCode('ELIFECYCLE')} ${chalk.red('Test failed. See above for more details.')}`)
},
})
@@ -182,7 +185,7 @@ test('prints command error with exit code', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Command failed with exit code 100.')}`)
expect(output).toBe(`${formatErrorCode('ELIFECYCLE')} ${chalk.red('Command failed with exit code 100.')}`)
},
})
@@ -205,7 +208,7 @@ test('prints command error without exit code', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Command failed.')}`)
expect(output).toBe(`${formatErrorCode('ELIFECYCLE')} ${chalk.red('Command failed.')}`)
},
})
@@ -227,7 +230,9 @@ test('prints unsupported pnpm version error', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Your pnpm version is incompatible with "/home/zoltan/project".')}
expect(output).toBe(`${formatErrorCode('ERR_PNPM_UNSUPPORTED_ENGINE')} Unsupported environment (bad pnpm and/or Node.js version)
Your pnpm version is incompatible with "/home/zoltan/project".
Expected version: 2
Got: 3.0.0
@@ -259,7 +264,9 @@ test('prints unsupported Node version error', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Your Node version is incompatible with "/home/zoltan/project".')}
expect(output).toBe(`${formatErrorCode('ERR_PNPM_UNSUPPORTED_ENGINE')} Unsupported environment (bad pnpm and/or Node.js version)
Your Node version is incompatible with "/home/zoltan/project".
Expected version: >=12
Got: 10.0.0
@@ -288,7 +295,9 @@ test('prints unsupported pnpm and Node versions error', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(`${ERROR} ${chalk.red('Your pnpm version is incompatible with "/home/zoltan/project".')}
expect(output).toBe(`${formatErrorCode('ERR_PNPM_UNSUPPORTED_ENGINE')} Unsupported environment (bad pnpm and/or Node.js version)
Your pnpm version is incompatible with "/home/zoltan/project".
Expected version: 2
Got: 3.0.0
@@ -298,7 +307,7 @@ 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".` + '\n\n' + `\
${ERROR} ${chalk.red('Your Node version is incompatible with "/home/zoltan/project".')}
Your Node version is incompatible with "/home/zoltan/project".
Expected version: >=12
Got: 10.0.0
@@ -330,7 +339,7 @@ test('prints error even if the error object not passed in through the message ob
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}`)
expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}`)
},
})
})
@@ -351,7 +360,7 @@ test('prints error without packages stacktrace when pkgsStack is empty', (done)
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}`)
expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}`)
},
})
})
@@ -378,7 +387,7 @@ test('prints error with packages stacktrace - depth 1 and hint', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}
expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}
This error happened while installing the dependencies of foo@1.0.0
hint`)
},
@@ -412,7 +421,8 @@ test('prints error with packages stacktrace - depth 2', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}
expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}
This error happened while installing the dependencies of foo@1.0.0
at bar@1.0.0`)
},
@@ -434,7 +444,8 @@ test('prints error and hint', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}
expect(output).toBe(formatErrorCode('ERR_PNPM_SOME_ERROR') + ' ' + `${chalk.red('some error')}
some hint`)
},
})
@@ -467,7 +478,8 @@ test('prints authorization error with auth settings', (done) => {
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}
expect(output).toBe(formatErrorCode('ERR_PNPM_FETCH_401') + ' ' + `${chalk.red('some error')}
some hint
These authorization settings were found:
@@ -500,7 +512,8 @@ test('prints authorization error without auth settings, where there are none', (
complete: () => done(),
error: done,
next: output => {
expect(output).toBe(ERROR + ' ' + `${chalk.red('some error')}
expect(output).toBe(formatErrorCode('ERR_PNPM_FETCH_401') + ' ' + `${chalk.red('some error')}
some hint
No authorization settings were found in the configs.