Skip to content
Permalink
Browse files

feat: show warning on forced exit (#4958)

  • Loading branch information...
pimlie authored and clarkdo committed Feb 6, 2019
1 parent 71a70fe commit 3d2deacd3ae9acf035edf5467de9796fa8974528
@@ -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.3.2",
"esm": "^3.2.3",
"execa": "^1.0.0",
"exit": "^0.1.2",
"minimist": "^1.2.0",
"pretty-bytes": "^5.1.0",
"std-env": "^2.2.1",
@@ -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)
}
}
})
@@ -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'
}
}
})
Oops, something went wrong.

0 comments on commit 3d2deac

Please sign in to comment.
You can’t perform that action at this time.