Skip to content

Commit 3d2deac

Browse files
pimlieclarkdo
authored andcommitted
feat: show warning on forced exit (#4958)
1 parent 71a70fe commit 3d2deac

File tree

16 files changed

+159
-55
lines changed

16 files changed

+159
-55
lines changed

packages/cli/bin/nuxt-cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
require('../dist/cli.js').run()
44
.catch((error) => {
55
require('consola').fatal(error)
6-
process.exit(2)
6+
require('exit')(2)
77
})

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"consola": "^2.3.2",
1919
"esm": "^3.2.3",
2020
"execa": "^1.0.0",
21+
"exit": "^0.1.2",
2122
"minimist": "^1.2.0",
2223
"pretty-bytes": "^5.1.0",
2324
"std-env": "^2.2.1",

packages/cli/src/command.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11

22
import minimist from 'minimist'
33
import { name, version } from '../package.json'
4-
import { loadNuxtConfig } from './utils'
5-
import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting'
4+
import { loadNuxtConfig, forceExit } from './utils'
5+
import { indent, foldLines, colorize } from './utils/formatting'
6+
import { startSpaces, optionSpaces, forceExitTimeout } from './utils/constants'
67
import * as imports from './imports'
78

89
export default class NuxtCommand {
@@ -12,6 +13,9 @@ export default class NuxtCommand {
1213
}
1314
this.cmd = cmd
1415

16+
// If the cmd is a server then dont forcibly exit when the cmd has finished
17+
this.isServer = cmd.isServer !== undefined ? cmd.isServer : Boolean(this.cmd.options.hostname)
18+
1519
this._argv = Array.from(argv)
1620
this._parsedArgv = null // Lazy evaluate
1721
}
@@ -42,7 +46,14 @@ export default class NuxtCommand {
4246
return Promise.resolve()
4347
}
4448

45-
return Promise.resolve(this.cmd.run(this))
49+
const runResolve = Promise.resolve(this.cmd.run(this))
50+
51+
// TODO: For v3 set timeout to 0 when force-exit === true
52+
if (!this.isServer || this.argv['force-exit']) {
53+
runResolve.then(() => forceExit(this.cmd.name, forceExitTimeout))
54+
}
55+
56+
return runResolve
4657
}
4758

4859
showVersion() {

packages/cli/src/list.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import chalk from 'chalk'
2-
import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting'
2+
import { indent, foldLines, colorize } from './utils/formatting'
3+
import { startSpaces, optionSpaces } from './utils/constants'
34
import getCommand from './commands'
45

56
export default async function listCommands() {

packages/cli/src/options/common.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export default {
2828
}
2929
}
3030
},
31+
// TODO: Change this to default: false in Nuxt v3 (see related todo's)
32+
'force-exit': {
33+
type: 'boolean',
34+
default: true,
35+
description: 'Do not force Nuxt.js to exit after the command has finished (this option has no effect on commands which start a server)'
36+
},
3137
version: {
3238
alias: 'v',
3339
type: 'boolean',

packages/cli/src/setup.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import consola from 'consola'
2-
import chalk from 'chalk'
3-
import boxen from 'boxen'
2+
import exit from 'exit'
3+
import { fatalBox } from './utils/formatting'
44

55
let _setup = false
66

@@ -26,17 +26,9 @@ export default function setup({ dev }) {
2626
consola.addReporter({
2727
log(logObj) {
2828
if (logObj.type === 'fatal') {
29-
process.stderr.write(boxen([
30-
chalk.red('✖ Nuxt Fatal Error'),
31-
'',
32-
chalk.white(String(logObj.args[0]))
33-
].join('\n'), {
34-
borderColor: 'red',
35-
borderStyle: 'round',
36-
padding: 1,
37-
margin: 1
38-
}) + '\n')
39-
process.exit(1)
29+
const errorMessage = String(logObj.args[0])
30+
process.stderr.write(fatalBox(errorMessage))
31+
exit(1)
4032
}
4133
}
4234
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const forceExitTimeout = 5
2+
3+
export const startSpaces = 2
4+
export const optionSpaces = 2
5+
6+
// 80% of terminal column width
7+
// this is a fn because console width can have changed since startup
8+
export const maxCharsPerLine = () => (process.stdout.columns || 100) * 80 / 100

packages/cli/src/utils/formatting.js

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import wrapAnsi from 'wrap-ansi'
22
import chalk from 'chalk'
3-
4-
export const startSpaces = 2
5-
export const optionSpaces = 2
6-
7-
// 80% of terminal column width
8-
export const maxCharsPerLine = (process.stdout.columns || 100) * 80 / 100
3+
import boxen from 'boxen'
4+
import { maxCharsPerLine } from './constants'
95

106
export function indent(count, chr = ' ') {
117
return chr.repeat(count)
@@ -25,8 +21,8 @@ export function indentLines(string, spaces, firstLineSpaces) {
2521
return s
2622
}
2723

28-
export function foldLines(string, spaces, firstLineSpaces, maxCharsPerLine) {
29-
return indentLines(wrapAnsi(string, maxCharsPerLine, { trim: false }), spaces, firstLineSpaces)
24+
export function foldLines(string, spaces, firstLineSpaces, charsPerLine = maxCharsPerLine()) {
25+
return indentLines(wrapAnsi(string, charsPerLine, { trim: false }), spaces, firstLineSpaces)
3026
}
3127

3228
export function colorize(text) {
@@ -36,3 +32,38 @@ export function colorize(text) {
3632
.replace(/ (-[-\w,]+)/g, m => chalk.bold(m))
3733
.replace(/`(.+)`/g, (_, m) => chalk.bold.cyan(m))
3834
}
35+
36+
export function box(message, title, options) {
37+
return boxen([
38+
title || chalk.white('Nuxt Message'),
39+
'',
40+
chalk.white(foldLines(message, 0, 0, maxCharsPerLine()))
41+
].join('\n'), Object.assign({
42+
borderColor: 'white',
43+
borderStyle: 'round',
44+
padding: 1,
45+
margin: 1
46+
}, options)) + '\n'
47+
}
48+
49+
export function successBox(message, title) {
50+
return box(message, title || chalk.green('✔ Nuxt Success'), {
51+
borderColor: 'green'
52+
})
53+
}
54+
55+
export function warningBox(message, title) {
56+
return box(message, title || chalk.yellow('⚠ Nuxt Warning'), {
57+
borderColor: 'yellow'
58+
})
59+
}
60+
61+
export function errorBox(message, title) {
62+
return box(message, title || chalk.red('✖ Nuxt Error'), {
63+
borderColor: 'red'
64+
})
65+
}
66+
67+
export function fatalBox(message, title) {
68+
return errorBox(message, title || chalk.red('✖ Nuxt Fatal Error'))
69+
}

packages/cli/src/utils/index.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import path from 'path'
22
import { existsSync } from 'fs'
33
import consola from 'consola'
44
import esm from 'esm'
5+
import exit from 'exit'
56
import defaultsDeep from 'lodash/defaultsDeep'
67
import { defaultNuxtConfigFile, getDefaultNuxtConfig } from '@nuxt/config'
7-
import boxen from 'boxen'
88
import chalk from 'chalk'
99
import prettyBytes from 'pretty-bytes'
1010
import env from 'std-env'
11+
import { successBox, warningBox } from './formatting'
1112

1213
export const requireModule = process.env.NUXT_TS ? require : esm(module, {
1314
cache: false,
@@ -87,37 +88,30 @@ export function showBanner(nuxt) {
8788
return
8889
}
8990

90-
const lines = []
91+
const titleLines = []
92+
const messageLines = []
9193

9294
// Name and version
93-
lines.push(`${chalk.green.bold('Nuxt.js')} ${nuxt.constructor.version}`)
95+
titleLines.push(`${chalk.green.bold('Nuxt.js')} ${nuxt.constructor.version}`)
9496

9597
// Running mode
96-
lines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
98+
titleLines.push(`Running in ${nuxt.options.dev ? chalk.bold.blue('development') : chalk.bold.green('production')} mode (${chalk.bold(nuxt.options.mode)})`)
9799

98100
// https://nodejs.org/api/process.html#process_process_memoryusage
99101
const { heapUsed, rss } = process.memoryUsage()
100-
lines.push(`Memory usage: ${chalk.bold(prettyBytes(heapUsed))} (RSS: ${prettyBytes(rss)})`)
102+
titleLines.push(`Memory usage: ${chalk.bold(prettyBytes(heapUsed))} (RSS: ${prettyBytes(rss)})`)
101103

102104
// Listeners
103-
lines.push('')
104105
for (const listener of nuxt.server.listeners) {
105-
lines.push(chalk.bold('Listening on: ') + chalk.underline.blue(listener.url))
106+
messageLines.push(chalk.bold('Listening on: ') + chalk.underline.blue(listener.url))
106107
}
107108

108109
// Add custom badge messages
109110
if (nuxt.options.cli.badgeMessages.length) {
110-
lines.push('', ...nuxt.options.cli.badgeMessages)
111+
messageLines.push('', ...nuxt.options.cli.badgeMessages)
111112
}
112113

113-
const box = boxen(lines.join('\n'), {
114-
borderColor: 'green',
115-
borderStyle: 'round',
116-
padding: 1,
117-
margin: 1
118-
})
119-
120-
process.stdout.write(box + '\n')
114+
process.stdout.write(successBox(messageLines.join('\n'), titleLines.join('\n')))
121115
}
122116

123117
export function formatPath(filePath) {
@@ -144,3 +138,23 @@ export function normalizeArg(arg, defaultValue) {
144138
}
145139
return arg
146140
}
141+
142+
export function forceExit(cmdName, timeout) {
143+
if (timeout) {
144+
const exitTimeout = setTimeout(() => {
145+
const msg = `The command 'nuxt ${cmdName}' finished but did not exit after ${timeout}s
146+
This is most likely not caused by a bug in Nuxt.js\
147+
Make sure to cleanup all timers and listeners you or your plugins/modules start.
148+
Nuxt.js will now force exit
149+
150+
${chalk.bold('DeprecationWarning: Starting with Nuxt version 3 this will be a fatal error')}`
151+
152+
// TODO: Change this to a fatal error in v3
153+
process.stderr.write(warningBox(msg))
154+
exit(0)
155+
}, timeout * 1000)
156+
exitTimeout.unref()
157+
} else {
158+
exit(0)
159+
}
160+
}
Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`cli/command builds help text 1`] = `
4-
" Usage: nuxt this is how you do it [options]
4+
" Usage: nuxt this is how you do it
5+
[options]
56
6-
a very long description that should not wrap to the next line because is not longer than the terminal width
7+
a very long description that should wrap
8+
to the next line because is not longer
9+
than the terminal width
710
811
Options:
912
1013
--spa, -s Launch in SPA mode
11-
--universal, -u Launch in Universal mode (default)
12-
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
13-
--modern, -m Build/Start app for modern browsers, e.g. server, client and false
14-
--version, -v Display the Nuxt version
14+
--universal, -u Launch in Universal
15+
mode (default)
16+
--config-file, -c Path to Nuxt.js
17+
config file (default: nuxt.config.js)
18+
--modern, -m Build/Start app for
19+
modern browsers, e.g. server, client and
20+
false
21+
--no-force-exit Do not force Nuxt.js
22+
to exit after the command has finished
23+
(this option has no effect on commands
24+
which start a server)
25+
--version, -v Display the Nuxt
26+
version
1527
--help, -h Display this message
16-
--port, -p Port number on which to start the application
17-
--hostname, -H Hostname on which to start the application
28+
--port, -p Port number on which
29+
to start the application
30+
--hostname, -H Hostname on which to
31+
start the application
1832
--unix-socket, -n Path to a UNIX socket
19-
--foo very long option that is not longer than the terminal width and should not wrap to the next line
33+
--foo very long option that
34+
is longer than the terminal width and
35+
should wrap to the next line
2036
2137
"
2238
`;

0 commit comments

Comments
 (0)