1const { format } = require('util') 2const { resolve } = require('path') 3const nameValidator = require('validate-npm-package-name') 4const replaceInfo = require('./replace-info.js') 5const { report } = require('./explain-eresolve.js') 6const log = require('./log-shim') 7 8const messageText = msg => msg.map(line => line.slice(1).join(' ')).join('\n') 9 10const jsonError = (er, npm, { summary, detail }) => { 11 if (npm?.config.loaded && npm.config.get('json')) { 12 return { 13 error: { 14 code: er.code, 15 summary: messageText(summary), 16 detail: messageText(detail), 17 }, 18 } 19 } 20} 21 22const errorMessage = (er, npm) => { 23 const short = [] 24 const detail = [] 25 const files = [] 26 27 if (er.message) { 28 er.message = replaceInfo(er.message) 29 } 30 if (er.stack) { 31 er.stack = replaceInfo(er.stack) 32 } 33 34 switch (er.code) { 35 case 'ERESOLVE': { 36 short.push(['ERESOLVE', er.message]) 37 detail.push(['', '']) 38 // XXX(display): error messages are logged so we use the logColor since that is based 39 // on stderr. This should be handled solely by the display layer so it could also be 40 // printed to stdout if necessary. 41 const { explanation, file } = report(er, npm.logChalk, npm.noColorChalk) 42 detail.push(['', explanation]) 43 files.push(['eresolve-report.txt', file]) 44 break 45 } 46 47 case 'ENOLOCK': { 48 const cmd = npm.command || '' 49 short.push([cmd, 'This command requires an existing lockfile.']) 50 detail.push([cmd, 'Try creating one first with: npm i --package-lock-only']) 51 detail.push([cmd, `Original error: ${er.message}`]) 52 break 53 } 54 55 case 'ENOAUDIT': 56 short.push(['audit', er.message]) 57 break 58 59 case 'ECONNREFUSED': 60 short.push(['', er]) 61 detail.push([ 62 '', 63 [ 64 '\nIf you are behind a proxy, please make sure that the', 65 "'proxy' config is set properly. See: 'npm help config'", 66 ].join('\n'), 67 ]) 68 break 69 70 case 'EACCES': 71 case 'EPERM': { 72 const isCachePath = 73 typeof er.path === 'string' && 74 npm.config.loaded && 75 er.path.startsWith(npm.config.get('cache')) 76 const isCacheDest = 77 typeof er.dest === 'string' && 78 npm.config.loaded && 79 er.dest.startsWith(npm.config.get('cache')) 80 81 const { isWindows } = require('./is-windows.js') 82 83 if (!isWindows && (isCachePath || isCacheDest)) { 84 // user probably doesn't need this, but still add it to the debug log 85 log.verbose(er.stack) 86 short.push([ 87 '', 88 [ 89 '', 90 'Your cache folder contains root-owned files, due to a bug in', 91 'previous versions of npm which has since been addressed.', 92 '', 93 'To permanently fix this problem, please run:', 94 ` sudo chown -R ${process.getuid()}:${process.getgid()} ${JSON.stringify( 95 npm.config.get('cache') 96 )}`, 97 ].join('\n'), 98 ]) 99 } else { 100 short.push(['', er]) 101 detail.push([ 102 '', 103 [ 104 '\nThe operation was rejected by your operating system.', 105 isWindows 106 /* eslint-disable-next-line max-len */ 107 ? "It's possible that the file was already in use (by a text editor or antivirus),\n" + 108 'or that you lack permissions to access it.' 109 /* eslint-disable-next-line max-len */ 110 : 'It is likely you do not have the permissions to access this file as the current user', 111 '\nIf you believe this might be a permissions issue, please double-check the', 112 'permissions of the file and its containing directories, or try running', 113 'the command again as root/Administrator.', 114 ].join('\n'), 115 ]) 116 } 117 break 118 } 119 120 case 'ENOGIT': 121 short.push(['', er.message]) 122 detail.push([ 123 '', 124 ['', 'Failed using git.', 'Please check if you have git installed and in your PATH.'].join( 125 '\n' 126 ), 127 ]) 128 break 129 130 case 'EJSONPARSE': 131 // Check whether we ran into a conflict in our own package.json 132 if (er.path === resolve(npm.prefix, 'package.json')) { 133 const { isDiff } = require('parse-conflict-json') 134 const txt = require('fs').readFileSync(er.path, 'utf8').replace(/\r\n/g, '\n') 135 if (isDiff(txt)) { 136 detail.push([ 137 '', 138 [ 139 'Merge conflict detected in your package.json.', 140 '', 141 'Please resolve the package.json conflict and retry.', 142 ].join('\n'), 143 ]) 144 break 145 } 146 } 147 short.push(['JSON.parse', er.message]) 148 detail.push([ 149 'JSON.parse', 150 [ 151 'Failed to parse JSON data.', 152 'Note: package.json must be actual JSON, not just JavaScript.', 153 ].join('\n'), 154 ]) 155 break 156 157 case 'EOTP': 158 case 'E401': 159 // E401 is for places where we accidentally neglect OTP stuff 160 if (er.code === 'EOTP' || /one-time pass/.test(er.message)) { 161 short.push(['', 'This operation requires a one-time password from your authenticator.']) 162 detail.push([ 163 '', 164 [ 165 'You can provide a one-time password by passing --otp=<code> to the command you ran.', 166 'If you already provided a one-time password then it is likely that you either typoed', 167 'it, or it timed out. Please try again.', 168 ].join('\n'), 169 ]) 170 } else { 171 // npm ERR! code E401 172 // npm ERR! Unable to authenticate, need: Basic 173 const auth = 174 !er.headers || !er.headers['www-authenticate'] 175 ? [] 176 : er.headers['www-authenticate'].map(au => au.split(/[,\s]+/))[0] 177 178 if (auth.includes('Bearer')) { 179 short.push([ 180 '', 181 'Unable to authenticate, your authentication token seems to be invalid.', 182 ]) 183 detail.push([ 184 '', 185 ['To correct this please try logging in again with:', ' npm login'].join('\n'), 186 ]) 187 } else if (auth.includes('Basic')) { 188 short.push(['', 'Incorrect or missing password.']) 189 detail.push([ 190 '', 191 [ 192 'If you were trying to login, change your password, create an', 193 'authentication token or enable two-factor authentication then', 194 'that means you likely typed your password in incorrectly.', 195 'Please try again, or recover your password at:', 196 ' https://www.npmjs.com/forgot', 197 '', 198 'If you were doing some other operation then your saved credentials are', 199 'probably out of date. To correct this please try logging in again with:', 200 ' npm login', 201 ].join('\n'), 202 ]) 203 } else { 204 short.push(['', er.message || er]) 205 } 206 } 207 break 208 209 case 'E404': 210 // There's no need to have 404 in the message as well. 211 short.push(['404', er.message.replace(/^404\s+/, '')]) 212 if (er.pkgid && er.pkgid !== '-') { 213 const pkg = er.pkgid.replace(/(?!^)@.*$/, '') 214 215 detail.push(['404', '']) 216 detail.push(['404', '', `'${replaceInfo(er.pkgid)}' is not in this registry.`]) 217 218 const valResult = nameValidator(pkg) 219 220 if (!valResult.validForNewPackages) { 221 detail.push(['404', 'This package name is not valid, because', '']) 222 223 const errorsArray = [...(valResult.errors || []), ...(valResult.warnings || [])] 224 errorsArray.forEach((item, idx) => detail.push(['404', ' ' + (idx + 1) + '. ' + item])) 225 } 226 227 detail.push(['404', '\nNote that you can also install from a']) 228 detail.push(['404', 'tarball, folder, http url, or git url.']) 229 } 230 break 231 232 case 'EPUBLISHCONFLICT': 233 short.push(['publish fail', 'Cannot publish over existing version.']) 234 detail.push(['publish fail', "Update the 'version' field in package.json and try again."]) 235 detail.push(['publish fail', '']) 236 detail.push(['publish fail', 'To automatically increment version numbers, see:']) 237 detail.push(['publish fail', ' npm help version']) 238 break 239 240 case 'EISGIT': 241 short.push(['git', er.message]) 242 short.push(['git', ' ' + er.path]) 243 detail.push([ 244 'git', 245 ['Refusing to remove it. Update manually,', 'or move it out of the way first.'].join('\n'), 246 ]) 247 break 248 249 case 'EBADPLATFORM': { 250 const actual = er.current 251 const expected = { ...er.required } 252 const checkedKeys = [] 253 for (const key in expected) { 254 if (Array.isArray(expected[key]) && expected[key].length > 0) { 255 expected[key] = expected[key].join(',') 256 checkedKeys.push(key) 257 } else if (expected[key] === undefined || 258 Array.isArray(expected[key]) && expected[key].length === 0) { 259 delete expected[key] 260 delete actual[key] 261 } else { 262 checkedKeys.push(key) 263 } 264 } 265 266 const longestKey = Math.max(...checkedKeys.map((key) => key.length)) 267 const detailEntry = [] 268 for (const key of checkedKeys) { 269 const padding = key.length === longestKey 270 ? 1 271 : 1 + (longestKey - key.length) 272 273 // padding + 1 because 'actual' is longer than 'valid' 274 detailEntry.push(`Valid ${key}:${' '.repeat(padding + 1)}${expected[key]}`) 275 detailEntry.push(`Actual ${key}:${' '.repeat(padding)}${actual[key]}`) 276 } 277 278 short.push([ 279 'notsup', 280 [ 281 format( 282 'Unsupported platform for %s: wanted %j (current: %j)', 283 er.pkgid, 284 expected, 285 actual 286 ), 287 ].join('\n'), 288 ]) 289 detail.push([ 290 'notsup', 291 detailEntry.join('\n'), 292 ]) 293 break 294 } 295 296 case 'EEXIST': 297 short.push(['', er.message]) 298 short.push(['', 'File exists: ' + (er.dest || er.path)]) 299 detail.push(['', 'Remove the existing file and try again, or run npm']) 300 detail.push(['', 'with --force to overwrite files recklessly.']) 301 break 302 303 case 'ENEEDAUTH': 304 short.push(['need auth', er.message]) 305 detail.push(['need auth', 'You need to authorize this machine using `npm adduser`']) 306 break 307 308 case 'ECONNRESET': 309 case 'ENOTFOUND': 310 case 'ETIMEDOUT': 311 case 'ERR_SOCKET_TIMEOUT': 312 case 'EAI_FAIL': 313 short.push(['network', er.message]) 314 detail.push([ 315 'network', 316 [ 317 'This is a problem related to network connectivity.', 318 'In most cases you are behind a proxy or have bad network settings.', 319 '\nIf you are behind a proxy, please make sure that the', 320 "'proxy' config is set properly. See: 'npm help config'", 321 ].join('\n'), 322 ]) 323 break 324 325 case 'ETARGET': 326 short.push(['notarget', er.message]) 327 detail.push([ 328 'notarget', 329 [ 330 'In most cases you or one of your dependencies are requesting', 331 "a package version that doesn't exist.", 332 ].join('\n'), 333 ]) 334 break 335 336 case 'E403': 337 short.push(['403', er.message]) 338 detail.push([ 339 '403', 340 [ 341 'In most cases, you or one of your dependencies are requesting', 342 'a package version that is forbidden by your security policy, or', 343 'on a server you do not have access to.', 344 ].join('\n'), 345 ]) 346 break 347 348 case 'EBADENGINE': 349 short.push(['engine', er.message]) 350 short.push(['engine', 'Not compatible with your version of node/npm: ' + er.pkgid]) 351 detail.push([ 352 'notsup', 353 [ 354 'Not compatible with your version of node/npm: ' + er.pkgid, 355 'Required: ' + JSON.stringify(er.required), 356 'Actual: ' + 357 JSON.stringify({ 358 npm: npm.version, 359 node: process.version, 360 }), 361 ].join('\n'), 362 ]) 363 break 364 365 case 'ENOSPC': 366 short.push(['nospc', er.message]) 367 detail.push([ 368 'nospc', 369 [ 370 'There appears to be insufficient space on your system to finish.', 371 'Clear up some disk space and try again.', 372 ].join('\n'), 373 ]) 374 break 375 376 case 'EROFS': 377 short.push(['rofs', er.message]) 378 detail.push([ 379 'rofs', 380 [ 381 'Often virtualized file systems, or other file systems', 382 "that don't support symlinks, give this error.", 383 ].join('\n'), 384 ]) 385 break 386 387 case 'ENOENT': 388 short.push(['enoent', er.message]) 389 detail.push([ 390 'enoent', 391 [ 392 'This is related to npm not being able to find a file.', 393 er.file ? "\nCheck if the file '" + er.file + "' is present." : '', 394 ].join('\n'), 395 ]) 396 break 397 398 case 'EMISSINGARG': 399 case 'EUNKNOWNTYPE': 400 case 'EINVALIDTYPE': 401 case 'ETOOMANYARGS': 402 short.push(['typeerror', er.stack]) 403 detail.push([ 404 'typeerror', 405 [ 406 'This is an error with npm itself. Please report this error at:', 407 ' https://github.com/npm/cli/issues', 408 ].join('\n'), 409 ]) 410 break 411 412 default: 413 short.push(['', er.message || er]) 414 if (er.signal) { 415 detail.push(['signal', er.signal]) 416 } 417 418 if (er.cmd && Array.isArray(er.args)) { 419 detail.push(['command', ...[er.cmd, ...er.args.map(replaceInfo)]]) 420 } 421 422 if (er.stdout) { 423 detail.push(['', er.stdout.trim()]) 424 } 425 426 if (er.stderr) { 427 detail.push(['', er.stderr.trim()]) 428 } 429 430 break 431 } 432 return { summary: short, detail, files, json: jsonError(er, npm, { summary: short, detail }) } 433} 434 435module.exports = errorMessage 436