mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
feat: show warning on forced exit (#4958)
This commit is contained in:
parent
f5220cfbbb
commit
5094d9c75d
@ -3,5 +3,5 @@
|
||||
require('../dist/cli.js').run()
|
||||
.catch((error) => {
|
||||
require('consola').fatal(error)
|
||||
process.exit(2)
|
||||
require('exit')(2)
|
||||
})
|
||||
|
@ -18,6 +18,7 @@
|
||||
"consola": "^2.4.0",
|
||||
"esm": "^3.2.1",
|
||||
"execa": "^1.0.0",
|
||||
"exit": "^0.1.2",
|
||||
"minimist": "^1.2.0",
|
||||
"opener": "1.5.1",
|
||||
"pretty-bytes": "^5.1.0",
|
||||
|
@ -1,8 +1,9 @@
|
||||
|
||||
import minimist from 'minimist'
|
||||
import { name, version } from '../package.json'
|
||||
import { loadNuxtConfig } from './utils'
|
||||
import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting'
|
||||
import { loadNuxtConfig, forceExit } from './utils'
|
||||
import { indent, foldLines, colorize } from './utils/formatting'
|
||||
import { startSpaces, optionSpaces, forceExitTimeout } from './utils/constants'
|
||||
import * as imports from './imports'
|
||||
|
||||
export default class NuxtCommand {
|
||||
@ -12,6 +13,9 @@ export default class NuxtCommand {
|
||||
}
|
||||
this.cmd = cmd
|
||||
|
||||
// If the cmd is a server then dont forcibly exit when the cmd has finished
|
||||
this.isServer = cmd.isServer !== undefined ? cmd.isServer : Boolean(this.cmd.options.hostname)
|
||||
|
||||
this._argv = Array.from(argv)
|
||||
this._parsedArgv = null // Lazy evaluate
|
||||
}
|
||||
@ -42,7 +46,14 @@ export default class NuxtCommand {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return Promise.resolve(this.cmd.run(this))
|
||||
const runResolve = Promise.resolve(this.cmd.run(this))
|
||||
|
||||
// TODO: For v3 set timeout to 0 when force-exit === true
|
||||
if (!this.isServer || this.argv['force-exit']) {
|
||||
runResolve.then(() => forceExit(this.cmd.name, forceExitTimeout))
|
||||
}
|
||||
|
||||
return runResolve
|
||||
}
|
||||
|
||||
showVersion() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import chalk from 'chalk'
|
||||
import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting'
|
||||
import { indent, foldLines, colorize } from './utils/formatting'
|
||||
import { startSpaces, optionSpaces } from './utils/constants'
|
||||
import getCommand from './commands'
|
||||
|
||||
export default async function listCommands() {
|
||||
|
@ -28,6 +28,12 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO: Change this to default: false in Nuxt v3 (see related todo's)
|
||||
'force-exit': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Do not force Nuxt.js to exit after the command has finished (this option has no effect on commands which start a server)'
|
||||
},
|
||||
version: {
|
||||
alias: 'v',
|
||||
type: 'boolean',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import consola from 'consola'
|
||||
import chalk from 'chalk'
|
||||
import boxen from 'boxen'
|
||||
import exit from 'exit'
|
||||
import { fatalBox } from './utils/formatting'
|
||||
|
||||
let _setup = false
|
||||
|
||||
@ -26,17 +26,9 @@ export default function setup({ dev }) {
|
||||
consola.addReporter({
|
||||
log(logObj) {
|
||||
if (logObj.type === 'fatal') {
|
||||
process.stderr.write(boxen([
|
||||
chalk.red('✖ Nuxt Fatal Error'),
|
||||
'',
|
||||
chalk.white(String(logObj.args[0]))
|
||||
].join('\n'), {
|
||||
borderColor: 'red',
|
||||
borderStyle: 'round',
|
||||
padding: 1,
|
||||
margin: 1
|
||||
}) + '\n')
|
||||
process.exit(1)
|
||||
const errorMessage = String(logObj.args[0])
|
||||
process.stderr.write(fatalBox(errorMessage))
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
8
packages/cli/src/utils/constants.js
Normal file
8
packages/cli/src/utils/constants.js
Normal file
@ -0,0 +1,8 @@
|
||||
export const forceExitTimeout = 5
|
||||
|
||||
export const startSpaces = 2
|
||||
export const optionSpaces = 2
|
||||
|
||||
// 80% of terminal column width
|
||||
// this is a fn because console width can have changed since startup
|
||||
export const maxCharsPerLine = () => (process.stdout.columns || 100) * 80 / 100
|
@ -1,11 +1,7 @@
|
||||
import wrapAnsi from 'wrap-ansi'
|
||||
import chalk from 'chalk'
|
||||
|
||||
export const startSpaces = 2
|
||||
export const optionSpaces = 2
|
||||
|
||||
// 80% of terminal column width
|
||||
export const maxCharsPerLine = (process.stdout.columns || 100) * 80 / 100
|
||||
import boxen from 'boxen'
|
||||
import { maxCharsPerLine } from './constants'
|
||||
|
||||
export function indent(count, chr = ' ') {
|
||||
return chr.repeat(count)
|
||||
@ -25,8 +21,8 @@ export function indentLines(string, spaces, firstLineSpaces) {
|
||||
return s
|
||||
}
|
||||
|
||||
export function foldLines(string, spaces, firstLineSpaces, maxCharsPerLine) {
|
||||
return indentLines(wrapAnsi(string, maxCharsPerLine, { trim: false }), spaces, firstLineSpaces)
|
||||
export function foldLines(string, spaces, firstLineSpaces, charsPerLine = maxCharsPerLine()) {
|
||||
return indentLines(wrapAnsi(string, charsPerLine, { trim: false }), spaces, firstLineSpaces)
|
||||
}
|
||||
|
||||
export function colorize(text) {
|
||||
@ -36,3 +32,38 @@ export function colorize(text) {
|
||||
.replace(/ (-[-\w,]+)/g, m => chalk.bold(m))
|
||||
.replace(/`(.+)`/g, (_, m) => chalk.bold.cyan(m))
|
||||
}
|
||||
|
||||
export function box(message, title, options) {
|
||||
return boxen([
|
||||
title || chalk.white('Nuxt Message'),
|
||||
'',
|
||||
chalk.white(foldLines(message, 0, 0, maxCharsPerLine()))
|
||||
].join('\n'), Object.assign({
|
||||
borderColor: 'white',
|
||||
borderStyle: 'round',
|
||||
padding: 1,
|
||||
margin: 1
|
||||
}, options)) + '\n'
|
||||
}
|
||||
|
||||
export function successBox(message, title) {
|
||||
return box(message, title || chalk.green('✔ Nuxt Success'), {
|
||||
borderColor: 'green'
|
||||
})
|
||||
}
|
||||
|
||||
export function warningBox(message, title) {
|
||||
return box(message, title || chalk.yellow('⚠ Nuxt Warning'), {
|
||||
borderColor: 'yellow'
|
||||
})
|
||||
}
|
||||
|
||||
export function errorBox(message, title) {
|
||||
return box(message, title || chalk.red('✖ Nuxt Error'), {
|
||||
borderColor: 'red'
|
||||
})
|
||||
}
|
||||
|
||||
export function fatalBox(message, title) {
|
||||
return errorBox(message, title || chalk.red('✖ Nuxt Fatal Error'))
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ import path from 'path'
|
||||
import { existsSync } from 'fs'
|
||||
import consola from 'consola'
|
||||
import esm from 'esm'
|
||||
import exit from 'exit'
|
||||
import defaultsDeep from 'lodash/defaultsDeep'
|
||||
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from '@nuxt/config'
|
||||
import boxen from 'boxen'
|
||||
import chalk from 'chalk'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import env from 'std-env'
|
||||
import { successBox, warningBox } from './formatting'
|
||||
|
||||
export const requireModule = process.env.NUXT_TS ? require : esm(module, {
|
||||
cache: false,
|
||||
@ -87,37 +88,30 @@ export function showBanner(nuxt) {
|
||||
return
|
||||
}
|
||||
|
||||
const lines = []
|
||||
const titleLines = []
|
||||
const messageLines = []
|
||||
|
||||
// Name and version
|
||||
lines.push(`${chalk.green.bold('Nuxt.js')} ${nuxt.constructor.version}`)
|
||||
titleLines.push(`${chalk.green.bold('Nuxt.js')} ${nuxt.constructor.version}`)
|
||||
|
||||
// Running mode
|
||||
lines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
|
||||
titleLines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
|
||||
|
||||
// https://nodejs.org/api/process.html#process_process_memoryusage
|
||||
const { heapUsed, rss } = process.memoryUsage()
|
||||
lines.push(`Memory usage: ${chalk.bold(prettyBytes(heapUsed))} (RSS: ${prettyBytes(rss)})`)
|
||||
titleLines.push(`Memory usage: ${chalk.bold(prettyBytes(heapUsed))} (RSS: ${prettyBytes(rss)})`)
|
||||
|
||||
// Listeners
|
||||
lines.push('')
|
||||
for (const listener of nuxt.server.listeners) {
|
||||
lines.push(chalk.bold('Listening on: ') + chalk.underline.blue(listener.url))
|
||||
messageLines.push(chalk.bold('Listening on: ') + chalk.underline.blue(listener.url))
|
||||
}
|
||||
|
||||
// Add custom badge messages
|
||||
if (nuxt.options.cli.badgeMessages.length) {
|
||||
lines.push('', ...nuxt.options.cli.badgeMessages)
|
||||
messageLines.push('', ...nuxt.options.cli.badgeMessages)
|
||||
}
|
||||
|
||||
const box = boxen(lines.join('\n'), {
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
padding: 1,
|
||||
margin: 1
|
||||
})
|
||||
|
||||
process.stdout.write(box + '\n')
|
||||
process.stdout.write(successBox(messageLines.join('\n'), titleLines.join('\n')))
|
||||
}
|
||||
|
||||
export function formatPath(filePath) {
|
||||
@ -144,3 +138,23 @@ export function normalizeArg(arg, defaultValue) {
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
export function forceExit(cmdName, timeout) {
|
||||
if (timeout) {
|
||||
const exitTimeout = setTimeout(() => {
|
||||
const msg = `The command 'nuxt ${cmdName}' finished but did not exit after ${timeout}s
|
||||
This is most likely not caused by a bug in Nuxt.js\
|
||||
Make sure to cleanup all timers and listeners you or your plugins/modules start.
|
||||
Nuxt.js will now force exit
|
||||
|
||||
${chalk.bold('DeprecationWarning: Starting with Nuxt version 3 this will be a fatal error')}`
|
||||
|
||||
// TODO: Change this to a fatal error in v3
|
||||
process.stderr.write(warningBox(msg))
|
||||
exit(0)
|
||||
}, timeout * 1000)
|
||||
exitTimeout.unref()
|
||||
} else {
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`cli/command builds help text 1`] = `
|
||||
" Usage: nuxt this is how you do it [options]
|
||||
" Usage: nuxt this is how you do it
|
||||
[options]
|
||||
|
||||
a very long description that should not wrap to the next line because is not longer than the terminal width
|
||||
a very long description that should wrap
|
||||
to the next line because is not longer
|
||||
than the terminal width
|
||||
|
||||
Options:
|
||||
|
||||
--spa, -s Launch in SPA mode
|
||||
--universal, -u Launch in Universal mode (default)
|
||||
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
|
||||
--modern, -m Build/Start app for modern browsers, e.g. server, client and false
|
||||
--version, -v Display the Nuxt version
|
||||
--universal, -u Launch in Universal
|
||||
mode (default)
|
||||
--config-file, -c Path to Nuxt.js
|
||||
config file (default: nuxt.config.js)
|
||||
--modern, -m Build/Start app for
|
||||
modern browsers, e.g. server, client and
|
||||
false
|
||||
--no-force-exit Do not force Nuxt.js
|
||||
to exit after the command has finished
|
||||
(this option has no effect on commands
|
||||
which start a server)
|
||||
--version, -v Display the Nuxt
|
||||
version
|
||||
--help, -h Display this message
|
||||
--port, -p Port number on which to start the application
|
||||
--hostname, -H Hostname on which to start the application
|
||||
--port, -p Port number on which
|
||||
to start the application
|
||||
--hostname, -H Hostname on which to
|
||||
start the application
|
||||
--unix-socket, -n Path to a UNIX socket
|
||||
--foo very long option that is not longer than the terminal width and should not wrap to the next line
|
||||
--foo very long option that
|
||||
is longer than the terminal width and
|
||||
should wrap to the next line
|
||||
|
||||
"
|
||||
`;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as utils from '../../src/utils/'
|
||||
import { mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils'
|
||||
|
||||
describe('build', () => {
|
||||
@ -6,6 +7,7 @@ describe('build', () => {
|
||||
beforeAll(async () => {
|
||||
build = await import('../../src/commands/build').then(m => m.default)
|
||||
jest.spyOn(process, 'exit').mockImplementation(code => code)
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => jest.resetAllMocks())
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { run } from '../../src'
|
||||
import getCommand from '../../src/commands'
|
||||
import * as utils from '../../src/utils/'
|
||||
|
||||
jest.mock('../../src/commands')
|
||||
|
||||
describe('cli', () => {
|
||||
beforeAll(() => {
|
||||
// TODO: Below spyOn can be removed in v3 when force-exit is default false
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => jest.resetAllMocks())
|
||||
|
||||
test('calls expected method', async () => {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import Command from '../../src/command'
|
||||
import { common, server } from '../../src/options'
|
||||
import * as utils from '../../src/utils/'
|
||||
import * as constants from '../../src/utils/constants'
|
||||
import { consola } from '../utils'
|
||||
|
||||
jest.mock('@nuxt/core')
|
||||
@ -19,7 +21,7 @@ describe('cli/command', () => {
|
||||
const minimistOptions = cmd._getMinimistOptions()
|
||||
|
||||
expect(minimistOptions.string.length).toBe(5)
|
||||
expect(minimistOptions.boolean.length).toBe(4)
|
||||
expect(minimistOptions.boolean.length).toBe(5)
|
||||
expect(minimistOptions.alias.c).toBe('config-file')
|
||||
expect(minimistOptions.default.c).toBe(common['config-file'].default)
|
||||
})
|
||||
@ -38,6 +40,8 @@ describe('cli/command', () => {
|
||||
})
|
||||
|
||||
test('prints version automatically', async () => {
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
|
||||
const cmd = new Command({}, ['--version'])
|
||||
cmd.showVersion = jest.fn()
|
||||
await cmd.run()
|
||||
@ -46,6 +50,8 @@ describe('cli/command', () => {
|
||||
})
|
||||
|
||||
test('prints help automatically', async () => {
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
|
||||
const cmd = new Command({ options: allOptions }, ['-h'])
|
||||
cmd.showHelp = jest.fn()
|
||||
await cmd.run()
|
||||
@ -88,16 +94,18 @@ describe('cli/command', () => {
|
||||
})
|
||||
|
||||
test('builds help text', () => {
|
||||
jest.spyOn(constants, 'maxCharsPerLine').mockReturnValue(40)
|
||||
|
||||
const cmd = new Command({
|
||||
description: 'a very long description that should not wrap to the next line because is not longer ' +
|
||||
description: 'a very long description that should wrap to the next line because is not longer ' +
|
||||
'than the terminal width',
|
||||
usage: 'this is how you do it',
|
||||
options: {
|
||||
...allOptions,
|
||||
foo: {
|
||||
type: 'boolean',
|
||||
description: 'very long option that is not longer than the terminal width and ' +
|
||||
'should not wrap to the next line'
|
||||
description: 'very long option that is longer than the terminal width and ' +
|
||||
'should wrap to the next line'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as utils from '../../src/utils/'
|
||||
import { consola, mockNuxt, mockBuilder, mockGetNuxtConfig, NuxtCommand } from '../utils'
|
||||
|
||||
describe('dev', () => {
|
||||
@ -5,6 +6,8 @@ describe('dev', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
dev = await import('../../src/commands/dev').then(m => m.default)
|
||||
// TODO: Below spyOn can be removed in v3 when force-exit is default false
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => jest.clearAllMocks())
|
||||
|
@ -1,3 +1,4 @@
|
||||
import * as utils from '../../src/utils/'
|
||||
import { mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils'
|
||||
|
||||
describe('generate', () => {
|
||||
@ -6,6 +7,7 @@ describe('generate', () => {
|
||||
beforeAll(async () => {
|
||||
generate = await import('../../src/commands/generate').then(m => m.default)
|
||||
jest.spyOn(process, 'exit').mockImplementation(code => code)
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => jest.resetAllMocks())
|
||||
|
@ -1,4 +1,5 @@
|
||||
import fs from 'fs-extra'
|
||||
import * as utils from '../../src/utils/'
|
||||
import { consola, mockGetNuxtStart, mockGetNuxtConfig, NuxtCommand } from '../utils'
|
||||
|
||||
describe('start', () => {
|
||||
@ -6,6 +7,8 @@ describe('start', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
start = await import('../../src/commands/start').then(m => m.default)
|
||||
// TODO: Below spyOn can be removed in v3 when force-exit is default false
|
||||
jest.spyOn(utils, 'forceExit').mockImplementation(() => {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user