Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show warning on forced exit #4958

Merged
merged 9 commits into from
Feb 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/bin/nuxt-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
require('../dist/cli.js').run()
.catch((error) => {
require('consola').fatal(error)
process.exit(2)
require('exit')(2)
})
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/src/command.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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() {
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/list.js
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/src/options/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
18 changes: 5 additions & 13 deletions packages/cli/src/setup.js
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
}
}
})
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/utils/constants.js
Original file line number Diff line number Diff line change
@@ -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
47 changes: 39 additions & 8 deletions packages/cli/src/utils/formatting.js
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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) {
Expand All @@ -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) {
pimlie marked this conversation as resolved.
Show resolved Hide resolved
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'))
}
46 changes: 30 additions & 16 deletions packages/cli/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
}
34 changes: 25 additions & 9 deletions packages/cli/test/unit/__snapshots__/command.test.js.snap
Original file line number Diff line number Diff line change
@@ -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

"
`;
2 changes: 2 additions & 0 deletions packages/cli/test/unit/build.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as utils from '../../src/utils/'
import { mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils'

describe('build', () => {
Expand All @@ -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())
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/test/unit/cli.test.js
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down
16 changes: 12 additions & 4 deletions packages/cli/test/unit/command.test.js
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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)
})
Expand All @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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'
}
}
})
Expand Down
Loading