error-message.js 12 KB

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