1 2module.exports = errorHandler 3module.exports.exit = exit 4 5var cbCalled = false 6var log = require('npmlog') 7var npm = require('../npm.js') 8var itWorked = false 9var path = require('path') 10var wroteLogFile = false 11var exitCode = 0 12var rollbacks = npm.rollbacks 13var chain = require('slide').chain 14var errorMessage = require('./error-message.js') 15var replaceInfo = require('./replace-info.js') 16var stopMetrics = require('./metrics.js').stop 17 18const cacheFile = require('./cache-file.js') 19 20var logFileName 21function getLogFile () { 22 if (!logFileName) { 23 logFileName = path.resolve(npm.config.get('cache'), '_logs', (new Date()).toISOString().replace(/[.:]/g, '_') + '-debug.log') 24 } 25 return logFileName 26} 27 28var timings = { 29 version: npm.version, 30 command: process.argv.slice(2), 31 logfile: null 32} 33process.on('timing', function (name, value) { 34 if (timings[name]) { timings[name] += value } else { timings[name] = value } 35}) 36 37process.on('exit', function (code) { 38 process.emit('timeEnd', 'npm') 39 log.disableProgress() 40 if (npm.config && npm.config.loaded && npm.config.get('timing')) { 41 try { 42 timings.logfile = getLogFile() 43 cacheFile.append('_timing.json', JSON.stringify(timings) + '\n') 44 } catch (_) { 45 // ignore 46 } 47 } 48 49 // kill any outstanding stats reporter if it hasn't finished yet 50 stopMetrics() 51 52 if (code) itWorked = false 53 if (itWorked) { 54 log.info('ok') 55 } else { 56 if (!cbCalled) { 57 log.error('', 'cb() never called!') 58 console.error('') 59 log.error('', 'This is an error with npm itself. Please report this error at:') 60 log.error('', ' <https://npm.community>') 61 writeLogFile() 62 } 63 64 if (code) { 65 log.verbose('code', code) 66 } 67 } 68 if (npm.config && npm.config.loaded && npm.config.get('timing') && !wroteLogFile) writeLogFile() 69 if (wroteLogFile) { 70 // just a line break 71 if (log.levels[log.level] <= log.levels.error) console.error('') 72 73 log.error( 74 '', 75 [ 76 'A complete log of this run can be found in:', 77 ' ' + getLogFile() 78 ].join('\n') 79 ) 80 wroteLogFile = false 81 } 82 83 var doExit = npm.config && npm.config.loaded && npm.config.get('_exit') 84 if (doExit) { 85 // actually exit. 86 if (exitCode === 0 && !itWorked) { 87 exitCode = 1 88 } 89 if (exitCode !== 0) process.exit(exitCode) 90 } else { 91 itWorked = false // ready for next exit 92 } 93}) 94 95function exit (code, noLog) { 96 exitCode = exitCode || process.exitCode || code 97 98 var doExit = npm.config && npm.config.loaded ? npm.config.get('_exit') : true 99 log.verbose('exit', [code, doExit]) 100 if (log.level === 'silent') noLog = true 101 102 if (rollbacks.length) { 103 chain(rollbacks.map(function (f) { 104 return function (cb) { 105 npm.commands.unbuild([f], true, cb) 106 } 107 }), function (er) { 108 if (er) { 109 log.error('error rolling back', er) 110 if (!code) { 111 errorHandler(er) 112 } else { 113 if (!noLog) writeLogFile() 114 reallyExit(er) 115 } 116 } else { 117 if (!noLog && code) writeLogFile() 118 reallyExit() 119 } 120 }) 121 rollbacks.length = 0 122 } else if (code && !noLog) { 123 writeLogFile() 124 } else { 125 reallyExit() 126 } 127 128 function reallyExit (er) { 129 if (er && !code) code = typeof er.errno === 'number' ? er.errno : 1 130 131 itWorked = !code 132 133 // Exit directly -- nothing in the CLI should still be running in the 134 // background at this point, and this makes sure anything left dangling 135 // for whatever reason gets thrown away, instead of leaving the CLI open 136 // 137 // Commands that expect long-running actions should just delay `cb()` 138 process.stdout.write('', () => { 139 process.exit(code) 140 }) 141 } 142} 143 144function errorHandler (er) { 145 log.disableProgress() 146 if (!npm.config || !npm.config.loaded) { 147 // logging won't work unless we pretend that it's ready 148 er = er || new Error('Exit prior to config file resolving.') 149 console.error(er.stack || er.message) 150 } 151 152 if (cbCalled) { 153 er = er || new Error('Callback called more than once.') 154 } 155 156 cbCalled = true 157 if (!er) return exit(0) 158 if (typeof er === 'string') { 159 log.error('', er) 160 return exit(1, true) 161 } else if (!(er instanceof Error)) { 162 log.error('weird error', er) 163 return exit(1, true) 164 } 165 166 var m = er.code || er.message.match(/^(?:Error: )?(E[A-Z]+)/) 167 if (m && !er.code) { 168 er.code = m 169 } 170 171 ;[ 172 'type', 173 'stack', 174 'statusCode', 175 'pkgid' 176 ].forEach(function (k) { 177 var v = er[k] 178 if (!v) return 179 v = replaceInfo(v) 180 log.verbose(k, v) 181 }) 182 183 log.verbose('cwd', process.cwd()) 184 185 var os = require('os') 186 var args = replaceInfo(process.argv) 187 log.verbose('', os.type() + ' ' + os.release()) 188 log.verbose('argv', args.map(JSON.stringify).join(' ')) 189 log.verbose('node', process.version) 190 log.verbose('npm ', 'v' + npm.version) 191 192 ;[ 193 'code', 194 'syscall', 195 'file', 196 'path', 197 'dest', 198 'errno' 199 ].forEach(function (k) { 200 var v = er[k] 201 if (v) log.error(k, v) 202 }) 203 204 var msg = errorMessage(er) 205 msg.summary.concat(msg.detail).forEach(function (errline) { 206 log.error.apply(log, errline) 207 }) 208 if (npm.config && npm.config.get('json')) { 209 var error = { 210 error: { 211 code: er.code, 212 summary: messageText(msg.summary), 213 detail: messageText(msg.detail) 214 } 215 } 216 console.log(JSON.stringify(error, null, 2)) 217 } 218 219 exit(typeof er.errno === 'number' ? er.errno : 1) 220} 221 222function messageText (msg) { 223 return msg.map(function (line) { 224 return line.slice(1).join(' ') 225 }).join('\n') 226} 227 228function writeLogFile () { 229 if (wroteLogFile) return 230 231 var os = require('os') 232 233 try { 234 var logOutput = '' 235 log.record.forEach(function (m) { 236 var pref = [m.id, m.level] 237 if (m.prefix) pref.push(m.prefix) 238 pref = pref.join(' ') 239 240 m.message.trim().split(/\r?\n/).map(function (line) { 241 return (pref + ' ' + line).trim() 242 }).forEach(function (line) { 243 logOutput += line + os.EOL 244 }) 245 }) 246 cacheFile.write(getLogFile(), logOutput) 247 248 // truncate once it's been written. 249 log.record.length = 0 250 wroteLogFile = true 251 } catch (ex) { 252 253 } 254} 255