const { log, output, META } = require('proc-log')
const { errorMessage, getExitCodeFromError } = require('../utils/error-message.js')
class ExitHandler {
#npm = null
#process = null
#exited = false
#exitErrorMessage = false
#noNpmError = false
get #hasNpm () {
return !!this.#npm
}
get #loaded () {
return !!this.#npm?.loaded
}
get #showExitErrorMessage () {
if (!this.#loaded) {
return false
}
if (!this.#exited) {
return true
}
return this.#exitErrorMessage
}
get #notLoadedOrExited () {
return !this.#loaded && !this.#exited
}
setNpm (npm) {
this.#npm = npm
}
constructor ({ process }) {
this.#process = process
this.#process.on('exit', this.#handleProcesExitAndReset)
}
registerUncaughtHandlers () {
this.#process.on('uncaughtException', this.#handleExit)
this.#process.on('unhandledRejection', this.#handleExit)
}
exit (err) {
this.#handleExit(err)
}
#handleProcesExitAndReset = (code) => {
this.#handleProcessExit(code)
// Reset all the state. This is only relevant for tests since
// in reality the process fully exits here.
this.#process.off('exit', this.#handleProcesExitAndReset)
this.#process.off('uncaughtException', this.#handleExit)
this.#process.off('unhandledRejection', this.#handleExit)
if (this.#loaded) {
this.#npm.unload()
}
this.#npm = null
this.#exited = false
this.#exitErrorMessage = false
}
#handleProcessExit (code) {
const numCode = Number(code) || 0
// Always exit w/ a non-zero code if exit handler was not called
const exitCode = this.#exited ? numCode : (numCode || 1)
this.#process.exitCode = exitCode
if (this.#notLoadedOrExited) {
// Exit handler was not called and npm was not loaded so we have to log something
this.#logConsoleError(new Error(`Process exited unexpectedly with code: ${exitCode}`))
return
}
if (this.#logNoNpmError()) {
return
}
const os = require('node:os')
log.verbose('cwd', this.#process.cwd())
log.verbose('os', `${os.type()} ${os.release()}`)
log.verbose('node', this.#process.version)
log.verbose('npm ', `v${this.#npm.version}`)
// only show the notification if it finished
if (typeof this.#npm.updateNotification === 'string') {
log.notice('', this.#npm.updateNotification, { [META]: true, force: true })
}
if (!this.#exited) {
log.error('', 'Exit handler never called!')
log.error('', 'This is an error with npm itself. Please report this error at:')
log.error('', '