/
reportError.ts
416 lines (363 loc) · 13.1 KB
/
reportError.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
import { type Config } from '@pnpm/config'
import { type Log } from '@pnpm/core-loggers'
import { type PnpmError } from '@pnpm/error'
import { renderPeerIssues } from '@pnpm/render-peer-issues'
import { type PeerDependencyIssuesByProjects } from '@pnpm/types'
import chalk from 'chalk'
import equals from 'ramda/src/equals'
import StackTracey from 'stacktracey'
import { EOL } from './constants'
StackTracey.maxColumnWidths = {
callee: 25,
file: 350,
sourceLine: 25,
}
const highlight = chalk.yellow
const colorPath = chalk.gray
export function reportError (logObj: Log, config?: Config) {
const errorInfo = getErrorInfo(logObj, config)
let output = formatErrorSummary(errorInfo.title, logObj['err']['code'])
if (logObj['pkgsStack'] != null) {
if (logObj['pkgsStack'].length > 0) {
output += `\n\n${formatPkgsStack(logObj['pkgsStack'])}`
} else if (logObj['prefix']) {
output += `\n\nThis error happened while installing a direct dependency of ${logObj['prefix'] as string}`
}
}
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) {
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_NO_MATCHING_VERSION':
return formatNoMatchingVersion(err, logObj)
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_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['message'], err.stack)
}
return {
title: err.message ?? '',
body: logObj['hint'],
}
}
}
}
return { title: logObj['message'] }
}
function formatPkgsStack (pkgsStack: Array<{ id: string, name: string, version: string }>) {
return `This error happened while installing the dependencies of \
${pkgsStack[0].name}@${pkgsStack[0].version}\
${pkgsStack.slice(1).map(({ name, version }) => `${EOL} at ${name}@${version}`).join('')}`
}
function formatNoMatchingVersion (err: Error, msg: object) {
const meta: {
name: string
'dist-tags': Record<string, string> & { latest: string }
versions: Record<string, object>
} = msg['packageMeta']
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
for (const tag in meta['dist-tags']) {
if (tag !== 'latest') {
output += ` * ${tag}: ${meta['dist-tags'][tag]}${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 {
title: err.message,
body: output,
}
}
function reportUnexpectedStore (
err: Error,
msg: {
actualStorePath: string
expectedStorePath: string
modulesDir: string
}
) {
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
}
) {
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
}) {
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
}) {
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
}) {
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) {
if (stack) {
let prettyStack: string | undefined
try {
prettyStack = new StackTracey(stack).asTable()
} catch (err: any) { // eslint-disable-line
prettyStack = stack.toString()
}
if (prettyStack) {
return {
title: errorMessage,
body: prettyStack,
}
}
}
return { title: errorMessage }
}
function formatErrorSummary (message: string, code?: string) {
return `${chalk.bgRed.black(`\u2009${code ?? 'ERROR'}\u2009`)} ${chalk.red(message)}`
}
function reportModifiedDependency (msg: { modified: string[] }) {
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) {
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 }) {
const output = EOL + `Summary: ${chalk.red(`${msg.fails.length} fails`)}, ${msg.passes} passes` + EOL + EOL +
msg.fails.map((fail) => {
return fail.prefix + ':' + EOL + formatErrorSummary(fail.message)
}).join(EOL + EOL)
return {
title: '',
body: output,
}
}
function reportBadTarballSize (err: Error, msg: object) {
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
}
) {
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
}
}
) {
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
) {
const foundSettings = [] as string[]
for (const [key, value] of Object.entries(config?.rawConfig ?? {})) {
if (key.startsWith('@')) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
foundSettings.push(`${key}=${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) {
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 }
) {
const hasMissingPeers = getHasMissingPeers(msg.issuesByProjects)
const hints: string[] = []
if (hasMissingPeers) {
hints.push('If you want peer dependencies to be automatically installed, add "auto-install-peers=true" to an .npmrc file at the root of your project.')
}
hints.push('If you don\'t want pnpm to fail on peer dependency issues, add "strict-peer-dependencies=false" to an .npmrc file at the root of your project.')
return {
title: err.message,
body: `${renderPeerIssues(msg.issuesByProjects)}
${hints.map((hint) => `hint: ${hint}`).join('\n')}
`,
}
}
function getHasMissingPeers (issuesByProjects: PeerDependencyIssuesByProjects) {
return Object.values(issuesByProjects)
.some((issues) => Object.values(issues.missing).flat().some(({ optional }) => !optional))
}