Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/utils/exit-handler.js')
-rw-r--r--lib/utils/exit-handler.js207
1 files changed, 77 insertions, 130 deletions
diff --git a/lib/utils/exit-handler.js b/lib/utils/exit-handler.js
index 5b2811468..324346624 100644
--- a/lib/utils/exit-handler.js
+++ b/lib/utils/exit-handler.js
@@ -1,119 +1,108 @@
const os = require('os')
-const path = require('path')
-const writeFileAtomic = require('write-file-atomic')
-const mkdirp = require('mkdirp-infer-owner')
-const fs = require('graceful-fs')
+const log = require('./log-shim.js')
const errorMessage = require('./error-message.js')
const replaceInfo = require('./replace-info.js')
-let exitHandlerCalled = false
-let logFileName
-let npm // set by the cli
-let wroteLogFile = false
-
-const getLogFile = () => {
- // we call this multiple times, so we need to treat it as a singleton because
- // the date is part of the name
- if (!logFileName) {
- logFileName = path.resolve(
- npm.config.get('cache'),
- '_logs',
- new Date().toISOString().replace(/[.:]/g, '_') + '-debug.log'
- )
- }
+const messageText = msg => msg.map(line => line.slice(1).join(' ')).join('\n')
- return logFileName
-}
+let npm = null // set by the cli
+let exitHandlerCalled = false
+let showLogFileMessage = false
process.on('exit', code => {
+ log.disableProgress()
+
// process.emit is synchronous, so the timeEnd handler will run before the
// unfinished timer check below
process.emit('timeEnd', 'npm')
- npm.log.disableProgress()
- for (const [name, timers] of npm.timers) {
- npm.log.verbose('unfinished npm timer', name, timers)
- }
- if (npm.config.loaded && npm.config.get('timing')) {
- try {
- const file = path.resolve(npm.config.get('cache'), '_timing.json')
- const dir = path.dirname(npm.config.get('cache'))
- mkdirp.sync(dir)
-
- fs.appendFileSync(
- file,
- JSON.stringify({
- command: process.argv.slice(2),
- logfile: getLogFile(),
- version: npm.version,
- ...npm.timings,
- }) + '\n'
- )
-
- const st = fs.lstatSync(path.dirname(npm.config.get('cache')))
- fs.chownSync(dir, st.uid, st.gid)
- fs.chownSync(file, st.uid, st.gid)
- } catch (ex) {
- // ignore
+ const hasNpm = !!npm
+ const hasLoadedNpm = hasNpm && npm.config.loaded
+
+ // Unfinished timers can be read before config load
+ if (hasNpm) {
+ for (const [name, timer] of npm.unfinishedTimers) {
+ log.verbose('unfinished npm timer', name, timer)
}
}
if (!code) {
- npm.log.info('ok')
+ log.info('ok')
} else {
- npm.log.verbose('code', code)
+ log.verbose('code', code)
}
if (!exitHandlerCalled) {
process.exitCode = code || 1
- npm.log.error('', 'Exit handler never called!')
+ log.error('', 'Exit handler never called!')
console.error('')
- npm.log.error('', 'This is an error with npm itself. Please report this error at:')
- npm.log.error('', ' <https://github.com/npm/cli/issues>')
- // TODO this doesn't have an npm.config.loaded guard
- writeLogFile()
+ log.error('', 'This is an error with npm itself. Please report this error at:')
+ log.error('', ' <https://github.com/npm/cli/issues>')
+ showLogFileMessage = true
}
- // In timing mode we always write the log file
- if (npm.config.loaded && npm.config.get('timing') && !wroteLogFile) {
- writeLogFile()
+
+ // In timing mode we always show the log file message
+ if (hasLoadedNpm && npm.config.get('timing')) {
+ showLogFileMessage = true
}
- if (wroteLogFile) {
+
+ // npm must be loaded to know where the log file was written
+ if (showLogFileMessage && hasLoadedNpm) {
// just a line break
- if (npm.log.levels[npm.log.level] <= npm.log.levels.error) {
+ if (log.levels[log.level] <= log.levels.error) {
console.error('')
}
- npm.log.error(
+ log.error(
'',
- ['A complete log of this run can be found in:', ' ' + getLogFile()].join('\n')
+ [
+ 'A complete log of this run can be found in:',
+ ...npm.logFiles.map(f => ' ' + f),
+ ].join('\n')
)
}
+ // This removes any listeners npm setup and writes files if necessary
+ // This is mostly used for tests to avoid max listener warnings
+ if (hasLoadedNpm) {
+ npm.unload()
+ }
+
// these are needed for the tests to have a clean slate in each test case
exitHandlerCalled = false
- wroteLogFile = false
+ showLogFileMessage = false
})
const exitHandler = err => {
- npm.log.disableProgress()
- if (!npm.config.loaded) {
+ exitHandlerCalled = true
+
+ log.disableProgress()
+
+ const hasNpm = !!npm
+ const hasLoadedNpm = hasNpm && npm.config.loaded
+
+ if (!hasNpm) {
+ err = err || new Error('Exit prior to setting npm in exit handler')
+ console.error(err.stack || err.message)
+ return process.exit(1)
+ }
+
+ if (!hasLoadedNpm) {
err = err || new Error('Exit prior to config file resolving.')
console.error(err.stack || err.message)
}
// only show the notification if it finished.
if (typeof npm.updateNotification === 'string') {
- const { level } = npm.log
- npm.log.level = 'notice'
- npm.log.notice('', npm.updateNotification)
- npm.log.level = level
+ const { level } = log
+ log.level = 'notice'
+ log.notice('', npm.updateNotification)
+ log.level = level
}
- exitHandlerCalled = true
-
let exitCode
- let noLog
+ let noLogMessage
if (err) {
exitCode = 1
@@ -125,13 +114,13 @@ const exitHandler = err => {
const quietShellout = isShellout && typeof err.code === 'number' && err.code
if (quietShellout) {
exitCode = err.code
- noLog = true
+ noLogMessage = true
} else if (typeof err === 'string') {
- noLog = true
- npm.log.error('', err)
+ log.error('', err)
+ noLogMessage = true
} else if (!(err instanceof Error)) {
- noLog = true
- npm.log.error('weird error', err)
+ log.error('weird error', err)
+ noLogMessage = true
} else {
if (!err.code) {
const matchErrorCode = err.message.match(/^(?:Error: )?(E[A-Z]+)/)
@@ -141,31 +130,30 @@ const exitHandler = err => {
for (const k of ['type', 'stack', 'statusCode', 'pkgid']) {
const v = err[k]
if (v) {
- npm.log.verbose(k, replaceInfo(v))
+ log.verbose(k, replaceInfo(v))
}
}
- npm.log.verbose('cwd', process.cwd())
-
const args = replaceInfo(process.argv)
- npm.log.verbose('', os.type() + ' ' + os.release())
- npm.log.verbose('argv', args.map(JSON.stringify).join(' '))
- npm.log.verbose('node', process.version)
- npm.log.verbose('npm ', 'v' + npm.version)
+ log.verbose('cwd', process.cwd())
+ log.verbose('', os.type() + ' ' + os.release())
+ log.verbose('argv', args.map(JSON.stringify).join(' '))
+ log.verbose('node', process.version)
+ log.verbose('npm ', 'v' + npm.version)
for (const k of ['code', 'syscall', 'file', 'path', 'dest', 'errno']) {
const v = err[k]
if (v) {
- npm.log.error(k, v)
+ log.error(k, v)
}
}
const msg = errorMessage(err, npm)
for (const errline of [...msg.summary, ...msg.detail]) {
- npm.log.error(...errline)
+ log.error(...errline)
}
- if (npm.config.loaded && npm.config.get('json')) {
+ if (hasLoadedNpm && npm.config.get('json')) {
const error = {
error: {
code: err.code,
@@ -183,17 +171,12 @@ const exitHandler = err => {
}
}
}
- npm.log.verbose('exit', exitCode || 0)
- if (npm.log.level === 'silent') {
- noLog = true
- }
+ log.verbose('exit', exitCode || 0)
- // noLog is true if there was an error, including if config wasn't loaded, so
- // this doesn't need a config.loaded guard
- if (exitCode && !noLog) {
- writeLogFile()
- }
+ showLogFileMessage = log.level === 'silent' || noLogMessage
+ ? false
+ : !!exitCode
// explicitly call process.exit now so we don't hang on things like the
// update notifier, also flush stdout beforehand because process.exit doesn't
@@ -201,42 +184,6 @@ const exitHandler = err => {
process.stdout.write('', () => process.exit(exitCode))
}
-const messageText = msg => msg.map(line => line.slice(1).join(' ')).join('\n')
-
-const writeLogFile = () => {
- try {
- let logOutput = ''
- npm.log.record.forEach(m => {
- const p = [m.id, m.level]
- if (m.prefix) {
- p.push(m.prefix)
- }
- const pref = p.join(' ')
-
- m.message
- .trim()
- .split(/\r?\n/)
- .map(line => (pref + ' ' + line).trim())
- .forEach(line => {
- logOutput += line + os.EOL
- })
- })
-
- const file = getLogFile()
- const dir = path.dirname(file)
- mkdirp.sync(dir)
- writeFileAtomic.sync(file, logOutput)
-
- const st = fs.lstatSync(path.dirname(npm.config.get('cache')))
- fs.chownSync(dir, st.uid, st.gid)
- fs.chownSync(file, st.uid, st.gid)
-
- // truncate once it's been written.
- npm.log.record.length = 0
- wroteLogFile = true
- } catch (ex) {}
-}
-
module.exports = exitHandler
module.exports.setNpm = n => {
npm = n