refactor(cli): cleanups and improvements (#4222)

This commit is contained in:
Jonas Galvez 2018-10-29 19:16:16 -03:00 committed by Pooya Parsa
parent 2e2b32b547
commit 4503d42d54
21 changed files with 391 additions and 396 deletions

View File

@ -1,19 +1,26 @@
import parseArgs from 'minimist'
import wrapAnsi from 'wrap-ansi'
import { name, version } from '../package.json'
import { loadNuxtConfig, indent, indentLines, foldLines } from './utils'
import { options as Options, defaultOptions as DefaultOptions } from './options'
import { loadNuxtConfig, indent, foldLines } from './utils'
import * as imports from './imports'
const startSpaces = 6
const startSpaces = 2
const optionSpaces = 2
const maxCharsPerLine = 80
export default class NuxtCommand {
constructor({ description, usage, options } = {}) {
constructor({ name, description, usage, options, run } = {}) {
this.name = name || ''
this.description = description || ''
this.usage = usage || ''
this.options = Array.from(new Set((options || []).concat(DefaultOptions)))
this.options = Object.assign({}, options)
this._run = run
}
static from(options) {
if (options instanceof NuxtCommand) {
return options
}
return new NuxtCommand(options)
}
_getMinimistOptions() {
@ -24,8 +31,8 @@ export default class NuxtCommand {
default: {}
}
for (const name of this.options) {
const option = Options[name]
for (const name of Object.keys(this.options)) {
const option = this.options[name]
if (option.alias) {
minimistOptions.alias[option.alias] = name
@ -54,13 +61,17 @@ export default class NuxtCommand {
return argv
}
run() {
return this._run(this)
}
async getNuxtConfig(argv, extraOptions) {
const config = await loadNuxtConfig(argv)
const options = Object.assign(config, extraOptions || {})
for (const name of this.options) {
if (Options[name].handle) {
Options[name].handle(options, argv)
for (const name of Object.keys(this.options)) {
if (this.options[name].prepare) {
this.options[name].prepare(this, options, argv)
}
}
@ -86,38 +97,37 @@ export default class NuxtCommand {
_getHelp() {
const options = []
let maxOptionLength = 0
// For consistency Options determines order
for (const name in Options) {
const option = Options[name]
if (this.options.includes(name)) {
let optionHelp = '--'
optionHelp += option.type === 'boolean' && option.default ? 'no-' : ''
optionHelp += name
if (option.alias) {
optionHelp += `, -${option.alias}`
}
maxOptionLength = Math.max(maxOptionLength, optionHelp.length)
options.push([ optionHelp, option.description ])
for (const name in this.options) {
const option = this.options[name]
let optionHelp = '--'
optionHelp += option.type === 'boolean' && option.default ? 'no-' : ''
optionHelp += name
if (option.alias) {
optionHelp += `, -${option.alias}`
}
maxOptionLength = Math.max(maxOptionLength, optionHelp.length)
options.push([ optionHelp, option.description ])
}
const optionStr = options.map(([option, description]) => {
const line = option +
indent(maxOptionLength + optionSpaces - option.length) +
wrapAnsi(description, maxCharsPerLine - startSpaces - maxOptionLength - optionSpaces)
return indentLines(line, startSpaces + maxOptionLength + optionSpaces, startSpaces)
const _opts = options.map(([option, description]) => {
const i = indent(maxOptionLength + optionSpaces - option.length)
return foldLines(
option + i + description,
maxCharsPerLine,
startSpaces + maxOptionLength + optionSpaces * 2,
startSpaces + optionSpaces
)
}).join('\n')
const usage = foldLines(`Usage: nuxt ${this.usage} [options]`, maxCharsPerLine, startSpaces)
const description = foldLines(this.description, maxCharsPerLine, startSpaces)
const opts = foldLines(`Options:`, maxCharsPerLine, startSpaces) + '\n\n' + _opts
return `
Description\n${description}
Usage
$ nuxt ${this.usage}
Options\n${optionStr}\n\n`
return `${usage}\n\n${description}\n\n${opts}\n\n`
}
showVersion() {

View File

@ -1,46 +1,77 @@
import consola from 'consola'
import NuxtCommand from '../command'
import { common } from '../options'
export default async function build() {
const nuxtCmd = new NuxtCommand({
description: 'Compiles the application for production deployment',
usage: 'build <dir>',
options: [ 'analyze', 'quiet' ]
})
const argv = nuxtCmd.getArgv()
// Create production build when calling `nuxt build` (dev: false)
const nuxt = await nuxtCmd.getNuxt(
await nuxtCmd.getNuxtConfig(argv, { dev: false })
)
// Setup hooks
nuxt.hook('error', err => consola.fatal(err))
let builderOrGenerator
if (nuxt.options.mode !== 'spa' || argv.generate === false) {
// Build only
builderOrGenerator = (await nuxtCmd.getBuilder(nuxt)).build()
} else {
// Build + Generate for static deployment
builderOrGenerator = (await nuxtCmd.getGenerator(nuxt)).generate({
build: true
})
}
return builderOrGenerator
.then(() => {
// In analyze mode wait for plugin
// emitting assets and opening browser
if (
nuxt.options.build.analyze === true ||
typeof nuxt.options.build.analyze === 'object'
) {
return
export default {
name: 'build',
description: 'Compiles the application for production deployment',
usage: 'build <dir>',
options: {
...common,
analyze: {
alias: 'a',
type: 'boolean',
description: 'Launch webpack-bundle-analyzer to optimize your bundles',
prepare(cmd, options, argv) {
// Analyze option
options.build = options.build || {}
if (argv.analyze && typeof options.build.analyze !== 'object') {
options.build.analyze = true
}
}
},
generate: {
type: 'boolean',
default: true,
description: 'Don\'t generate static version for SPA mode (useful for nuxt start)'
},
quiet: {
alias: 'q',
type: 'boolean',
description: 'Disable output except for errors',
handle(options, argv) {
// Silence output when using --quiet
options.build = options.build || {}
if (argv.quiet) {
options.build.quiet = !!argv.quiet
}
}
}
},
async run(nuxtCmd) {
const argv = nuxtCmd.getArgv()
process.exit(0)
})
.catch(err => consola.fatal(err))
// Create production build when calling `nuxt build` (dev: false)
const nuxt = await nuxtCmd.getNuxt(
await nuxtCmd.getNuxtConfig(argv, { dev: false })
)
// Setup hooks
nuxt.hook('error', err => consola.fatal(err))
let builderOrGenerator
if (nuxt.options.mode !== 'spa' || argv.generate === false) {
// Build only
builderOrGenerator = (await nuxtCmd.getBuilder(nuxt)).build()
} else {
// Build + Generate for static deployment
builderOrGenerator = (await nuxtCmd.getGenerator(nuxt)).generate({
build: true
})
}
return builderOrGenerator
.then(() => {
// In analyze mode wait for plugin
// emitting assets and opening browser
if (
nuxt.options.build.analyze === true ||
typeof nuxt.options.build.analyze === 'object'
) {
return
}
process.exit(0)
})
.catch(err => consola.fatal(err))
}
}

View File

@ -1,59 +1,62 @@
import consola from 'consola'
import NuxtCommand from '../command'
import { common, server } from '../options'
export default async function dev() {
const nuxtCmd = new NuxtCommand({
description: 'Start the application in development mode (e.g. hot-code reloading, error reporting)',
usage: 'dev <dir> -p <port number> -H <hostname>',
options: [ 'hostname', 'port' ]
})
export default {
name: 'dev',
description: 'Start the application in development mode (e.g. hot-code reloading, error reporting)',
usage: 'dev <dir>',
options: {
...common,
...server
},
async run(cmd) {
const argv = cmd.getArgv()
const argv = nuxtCmd.getArgv()
const errorHandler = (err, instance) => {
instance && instance.builder.watchServer()
consola.error(err)
}
// Start dev
async function startDev(oldInstance) {
let nuxt, builder
try {
nuxt = await nuxtCmd.getNuxt(
await nuxtCmd.getNuxtConfig(argv, { dev: true })
)
builder = await nuxtCmd.getBuilder(nuxt)
nuxt.hook('watch:fileChanged', async (builder, fname) => {
consola.debug(`[${fname}] changed, Rebuilding the app...`)
await startDev({ nuxt: builder.nuxt, builder })
})
} catch (err) {
return errorHandler(err, oldInstance)
const errorHandler = (err, instance) => {
instance && instance.builder.watchServer()
consola.error(err)
}
return (
Promise.resolve()
.then(() => oldInstance && oldInstance.nuxt.clearHook('watch:fileChanged'))
.then(() => oldInstance && oldInstance.builder.unwatch())
// Start build
.then(() => builder.build())
// Close old nuxt no matter if build successfully
.catch((err) => {
oldInstance && oldInstance.nuxt.close()
// Jump to errorHandler
throw err
})
.then(() => oldInstance && oldInstance.nuxt.close())
// Start listening
.then(() => nuxt.listen())
// Show ready message first time, others will be shown through WebpackBar
.then(() => !oldInstance && nuxt.showReady(false))
.then(() => builder.watchServer())
// Handle errors
.catch(err => errorHandler(err, { builder, nuxt }))
)
}
// Start dev
async function startDev(oldInstance) {
let nuxt, builder
await startDev()
try {
nuxt = await cmd.getNuxt(
await cmd.getNuxtConfig(argv, { dev: true })
)
builder = await cmd.getBuilder(nuxt)
nuxt.hook('watch:fileChanged', async (builder, fname) => {
consola.debug(`[${fname}] changed, Rebuilding the app...`)
await startDev({ nuxt: builder.nuxt, builder })
})
} catch (err) {
return errorHandler(err, oldInstance)
}
return (
Promise.resolve()
.then(() => oldInstance && oldInstance.nuxt.clearHook('watch:fileChanged'))
.then(() => oldInstance && oldInstance.builder.unwatch())
// Start build
.then(() => builder.build())
// Close old nuxt no matter if build successfully
.catch((err) => {
oldInstance && oldInstance.nuxt.close()
// Jump to errorHandler
throw err
})
.then(() => oldInstance && oldInstance.nuxt.close())
// Start listening
.then(() => nuxt.listen())
// Show ready message first time, others will be shown through WebpackBar
.then(() => !oldInstance && nuxt.showReady(false))
.then(() => builder.watchServer())
// Handle errors
.catch(err => errorHandler(err, { builder, nuxt }))
)
}
await startDev()
}
}

View File

@ -1,25 +1,32 @@
import consola from 'consola'
import NuxtCommand from '../command'
import { common } from '../options'
export default async function generate() {
const nuxtCmd = new NuxtCommand({
description: 'Generate a static web application (server-rendered)',
usage: 'generate <dir>',
options: [ 'build' ]
})
export default {
name: 'generate',
description: 'Generate a static web application (server-rendered)',
usage: 'generate <dir>',
options: {
...common,
build: {
type: 'boolean',
default: true,
description: 'Only generate pages for dynamic routes. Nuxt has to be built once before using this option'
}
},
async run(cmd) {
const argv = cmd.getArgv()
const argv = nuxtCmd.getArgv()
const generator = await nuxtCmd.getGenerator(
await nuxtCmd.getNuxt(
await nuxtCmd.getNuxtConfig(argv, { dev: false })
const generator = await cmd.getGenerator(
await cmd.getNuxt(
await cmd.getNuxtConfig(argv, { dev: false })
)
)
)
return generator.generate({
init: true,
build: argv.build
}).then(() => {
process.exit(0)
}).catch(err => consola.fatal(err))
return generator.generate({
init: true,
build: argv.build
}).then(() => {
process.exit(0)
}).catch(err => consola.fatal(err))
}
}

View File

@ -1,49 +1,52 @@
import fs from 'fs'
import path from 'path'
import consola from 'consola'
import NuxtCommand from '../command'
import { common, server } from '../options'
export default async function start() {
const nuxtCmd = new NuxtCommand({
description: 'Start the application in production mode (the application should be compiled with `nuxt build` first)',
usage: 'start <dir> -p <port number> -H <hostname>',
options: [ 'hostname', 'port', 'unix-socket' ]
})
export default {
name: 'start',
description: 'Start the application in production mode (the application should be compiled with `nuxt build` first)',
usage: 'start <dir>',
options: {
...common,
...server
},
async run(cmd) {
const argv = cmd.getArgv()
const argv = nuxtCmd.getArgv()
// Create production build when calling `nuxt build`
const nuxt = await nuxtCmd.getNuxt(
await nuxtCmd.getNuxtConfig(argv, { dev: false })
)
// Setup hooks
nuxt.hook('error', err => consola.fatal(err))
// Check if project is built for production
const distDir = path.resolve(
nuxt.options.rootDir,
nuxt.options.buildDir || '.nuxt',
'dist',
'server'
)
if (!fs.existsSync(distDir)) {
consola.fatal(
'No build files found, please run `nuxt build` before launching `nuxt start`'
// Create production build when calling `nuxt build`
const nuxt = await cmd.getNuxt(
await cmd.getNuxtConfig(argv, { dev: false })
)
}
// Check if SSR Bundle is required
if (nuxt.options.render.ssr === true) {
const ssrBundlePath = path.resolve(distDir, 'server-bundle.json')
if (!fs.existsSync(ssrBundlePath)) {
// Setup hooks
nuxt.hook('error', err => consola.fatal(err))
// Check if project is built for production
const distDir = path.resolve(
nuxt.options.rootDir,
nuxt.options.buildDir || '.nuxt',
'dist',
'server'
)
if (!fs.existsSync(distDir)) {
consola.fatal(
'No SSR build! Please start with `nuxt start --spa` or build using `nuxt build --universal`'
'No build files found, please run `nuxt build` before launching `nuxt start`'
)
}
}
return nuxt.listen().then(() => {
nuxt.showReady(false)
})
// Check if SSR Bundle is required
if (nuxt.options.render.ssr === true) {
const ssrBundlePath = path.resolve(distDir, 'server-bundle.json')
if (!fs.existsSync(ssrBundlePath)) {
consola.fatal(
'No SSR build! Please start with `nuxt start --spa` or build using `nuxt build --universal`'
)
}
}
return nuxt.listen().then(() => {
nuxt.showReady(false)
})
}
}

View File

@ -4,7 +4,7 @@ import * as _imports from './imports'
export const commands = _commands
export const imports = _imports
export { default as NuxtCommand } from './command'
export { default as setup } from './setup'
export { default as run } from './run'
export { loadNuxtConfig } from './utils'

View File

@ -1,101 +0,0 @@
import consola from 'consola'
export const defaultOptions = [
'spa',
'universal',
'config-file',
'version',
'help'
]
export const options = {
port: {
alias: 'p',
type: 'string',
description: 'Port number on which to start the application',
handle(options, argv) {
if (argv.port) {
options.server.port = +argv.port
}
}
},
hostname: {
alias: 'H',
type: 'string',
description: 'Hostname on which to start the application',
handle(options, argv) {
if (argv.hostname === '') {
consola.fatal('Provided hostname argument has no value')
}
}
},
'unix-socket': {
alias: 'n',
type: 'string',
description: 'Path to a UNIX socket'
},
analyze: {
alias: 'a',
type: 'boolean',
description: 'Launch webpack-bundle-analyzer to optimize your bundles',
handle(options, argv) {
// Analyze option
options.build = options.build || {}
if (argv.analyze && typeof options.build.analyze !== 'object') {
options.build.analyze = true
}
}
},
build: {
type: 'boolean',
default: true,
description: 'Only generate pages for dynamic routes. Nuxt has to be built once before using this option'
},
generate: {
type: 'boolean',
default: true,
description: 'Don\'t generate static version for SPA mode (useful for nuxt start)'
},
spa: {
alias: 's',
type: 'boolean',
description: 'Launch in SPA mode'
},
universal: {
alias: 'u',
type: 'boolean',
description: 'Launch in Universal mode (default)'
},
'config-file': {
alias: 'c',
type: 'string',
default: 'nuxt.config.js',
description: 'Path to Nuxt.js config file (default: nuxt.config.js)'
},
quiet: {
alias: 'q',
type: 'boolean',
description: 'Disable output except for errors',
handle(options, argv) {
// Silence output when using --quiet
options.build = options.build || {}
if (argv.quiet) {
options.build.quiet = !!argv.quiet
}
}
},
verbose: {
alias: 'v',
type: 'boolean',
description: 'Show debug information'
},
version: {
type: 'boolean',
description: 'Display the Nuxt version'
},
help: {
alias: 'h',
type: 'boolean',
description: 'Display this message'
}
}

View File

@ -0,0 +1,27 @@
export default {
spa: {
alias: 's',
type: 'boolean',
description: 'Launch in SPA mode'
},
universal: {
alias: 'u',
type: 'boolean',
description: 'Launch in Universal mode (default)'
},
'config-file': {
alias: 'c',
type: 'string',
default: 'nuxt.config.js',
description: 'Path to Nuxt.js config file (default: nuxt.config.js)'
},
version: {
type: 'boolean',
description: 'Display the Nuxt version'
},
help: {
alias: 'h',
type: 'boolean',
description: 'Display this message'
}
}

View File

@ -0,0 +1,2 @@
export { default as common } from './common'
export { default as server } from './server'

View File

@ -0,0 +1,29 @@
import consola from 'consola'
export default {
port: {
alias: 'p',
type: 'string',
description: 'Port number on which to start the application',
prepare(cmd, options, argv) {
if (argv.port) {
options.server.port = +argv.port
}
}
},
hostname: {
alias: 'H',
type: 'string',
description: 'Hostname on which to start the application',
prepare(cmd, options, argv) {
if (argv.hostname === '') {
consola.fatal('Provided hostname argument has no value')
}
}
},
'unix-socket': {
alias: 'n',
type: 'string',
description: 'Path to a UNIX socket'
}
}

View File

@ -1,4 +1,5 @@
import consola from 'consola'
import NuxtCommand from './command'
import * as commands from './commands'
import setup from './setup'
@ -26,7 +27,9 @@ export default function run() {
})
return commands[cmd]() // eslint-disable-line import/namespace
.then(m => m.default())
.then(m => m.default)
.then(options => NuxtCommand.from(options))
.then(command => command.run())
.catch((error) => {
consola.fatal(error)
})

View File

@ -76,11 +76,11 @@ export function indentLines(string, spaces, firstLineSpaces) {
}
if (lines.length) {
const i = indent(spaces)
s += '\n' + lines.map(l => i + l.trim()).join('\n')
s += '\n' + lines.map(l => i + l).join('\n')
}
return s
}
export function foldLines(string, maxCharsPerLine, spaces, firstLineSpaces) {
return indentLines(wrapAnsi(string, maxCharsPerLine), spaces, firstLineSpaces)
return indentLines(wrapAnsi(string, maxCharsPerLine, { trim: false }), spaces, firstLineSpaces)
}

View File

@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`cli/command builds help text 1`] = `
" Usage: nuxt this is how you do it [options]
a very long description that is longer than 80 chars and should wrap to the next
line while keeping indentation
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)
--version 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
--unix-socket, -n Path to a UNIX socket
--foo very long option that is longer than 80 chars and should wrap
to the next line while keeping indentation
"
`;

View File

@ -1,25 +1,18 @@
import { consola, mockGetNuxt, mockGetBuilder, mockGetGenerator } from '../utils'
import { consola, mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils'
describe('build', () => {
let build
beforeAll(async () => {
build = await import('../../src/commands/build')
build = build.default
build = await import('../../src/commands/build').then(m => m.default)
jest.spyOn(process, 'exit').mockImplementation(code => code)
})
afterAll(() => {
process.exit.mockRestore()
})
afterAll(() => process.exit.mockRestore())
afterEach(() => jest.resetAllMocks())
afterEach(() => {
jest.resetAllMocks()
})
test('is function', () => {
expect(typeof build).toBe('function')
test('has run function', () => {
expect(typeof build.run).toBe('function')
})
test('builds on universal mode', async () => {
@ -31,7 +24,7 @@ describe('build', () => {
})
const builder = mockGetBuilder(Promise.resolve())
await build()
await NuxtCommand.from(build).run()
expect(builder).toHaveBeenCalled()
})
@ -45,7 +38,7 @@ describe('build', () => {
})
const generate = mockGetGenerator(Promise.resolve())
await build()
await NuxtCommand.from(build).run()
expect(generate).toHaveBeenCalled()
expect(process.exit).toHaveBeenCalled()
@ -55,7 +48,7 @@ describe('build', () => {
mockGetNuxt({ mode: 'universal' })
mockGetBuilder(Promise.reject(new Error('Builder Error')))
await build()
await NuxtCommand.from(build).run()
expect(consola.fatal).toHaveBeenCalledWith(new Error('Builder Error'))
})

View File

@ -10,9 +10,7 @@ const readDir = promisify(readdir)
jest.mock('../../src/commands')
describe('cli', () => {
afterEach(() => {
jest.resetAllMocks()
})
afterEach(() => jest.resetAllMocks())
test('exports for all commands defined', async () => {
const cmds = await readDir(resolve(__dirname, '..', '..', 'src', 'commands'))
@ -32,12 +30,14 @@ describe('cli', () => {
test('calls expected method', async () => {
const argv = process.argv
process.argv = ['', '', 'dev']
const defaultExport = jest.fn().mockImplementation(() => Promise.resolve())
const defaultExport = {
run: jest.fn().mockImplementation(() => Promise.resolve())
}
commands.dev.mockImplementationOnce(() => Promise.resolve({ default: defaultExport }))
await run()
expect(defaultExport).toHaveBeenCalled()
expect(defaultExport.run).toHaveBeenCalled()
process.argv = argv
})

View File

@ -1,39 +1,31 @@
import Command from '../../src/command'
import { options as Options } from '../../src/options'
import { common, server } from '../../src/options'
import { consola } from '../utils'
jest.mock('@nuxt/core')
jest.mock('@nuxt/builder')
jest.mock('@nuxt/generator')
const allOptions = {
...common,
...server
}
describe('cli/command', () => {
beforeEach(() => {
jest.restoreAllMocks()
})
test('adds default options', () => {
const cmd = new Command()
expect(cmd.options.length).not.toBe(0)
})
beforeEach(() => jest.restoreAllMocks())
test('builds minimist options', () => {
const cmd = new Command({
options: Object.keys(Options)
})
const cmd = new Command({ options: allOptions })
const minimistOptions = cmd._getMinimistOptions()
expect(minimistOptions.string.length).toBe(4)
expect(minimistOptions.boolean.length).toBe(9)
expect(minimistOptions.boolean.length).toBe(4)
expect(minimistOptions.alias.c).toBe('config-file')
expect(minimistOptions.default.c).toBe(Options['config-file'].default)
expect(minimistOptions.default.c).toBe(common['config-file'].default)
})
test('parses args', () => {
const cmd = new Command({
options: Object.keys(Options)
})
const cmd = new Command({ options: { ...common, ...server } })
let args = ['-c', 'test-file', '-s', '-p', '3001']
let argv = cmd.getArgv(args)
@ -41,7 +33,6 @@ describe('cli/command', () => {
expect(argv['config-file']).toBe(args[1])
expect(argv.spa).toBe(true)
expect(argv.universal).toBe(false)
expect(argv.build).toBe(true)
expect(argv.port).toBe('3001')
args = ['--no-build']
@ -61,7 +52,7 @@ describe('cli/command', () => {
})
test('prints help automatically', () => {
const cmd = new Command()
const cmd = new Command({ options: allOptions })
cmd.showHelp = jest.fn()
const args = ['-h']
@ -71,9 +62,7 @@ describe('cli/command', () => {
})
test('returns nuxt config', async () => {
const cmd = new Command({
options: Object.keys(Options)
})
const cmd = new Command({ options: allOptions })
const args = ['-c', 'test-file', '-a', '-p', '3001', '-q', '-H']
const argv = cmd.getArgv(args)
@ -83,8 +72,6 @@ describe('cli/command', () => {
expect(options.testOption).toBe(true)
expect(options.server.port).toBe(3001)
expect(options.build.quiet).toBe(true)
expect(options.build.analyze).toBe(true)
expect(consola.fatal).toHaveBeenCalledWith('Provided hostname argument has no value') // hostname check
})
@ -117,26 +104,17 @@ describe('cli/command', () => {
description: 'a very long description that is longer than 80 chars and ' +
'should wrap to the next line while keeping indentation',
usage: 'this is how you do it',
options: ['build']
options: {
...allOptions,
foo: {
type: 'boolean',
description: 'very long option that is longer than 80 chars and ' +
'should wrap to the next line while keeping indentation'
}
}
})
const expectedText = `
Description
a very long description that is longer than 80 chars and should wrap to the next
line while keeping indentation
Usage
$ nuxt this is how you do it
Options
--no-build Only generate pages for dynamic routes. Nuxt has to be
built once before using this option
--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)
--version Display the Nuxt version
--help, -h Display this message
`
expect(cmd._getHelp()).toBe(expectedText)
expect(cmd._getHelp()).toMatchSnapshot()
})
test('show version prints to stdout and exits', () => {

View File

@ -1,26 +1,23 @@
import { consola, mockNuxt, mockBuilder, mockGetNuxtConfig } from '../utils'
import { consola, mockNuxt, mockBuilder, mockGetNuxtConfig, NuxtCommand } from '../utils'
describe('dev', () => {
let dev
beforeAll(async () => {
dev = await import('../../src/commands/dev')
dev = dev.default
dev = await import('../../src/commands/dev').then(m => m.default)
})
afterEach(() => {
jest.clearAllMocks()
})
afterEach(() => jest.clearAllMocks())
test('is function', () => {
expect(typeof dev).toBe('function')
test('has run function', () => {
expect(typeof dev.run).toBe('function')
})
test('reloads on fileChanged hook', async () => {
const Nuxt = mockNuxt()
const Builder = mockBuilder()
await dev()
await NuxtCommand.from(dev).run()
expect(consola.error).not.toHaveBeenCalled()
@ -51,7 +48,7 @@ describe('dev', () => {
const Nuxt = mockNuxt()
const Builder = mockBuilder()
await dev()
await NuxtCommand.from(dev).run()
jest.clearAllMocks()
// Test error on second build so we cover oldInstance stuff
@ -68,7 +65,7 @@ describe('dev', () => {
const Nuxt = mockNuxt()
const Builder = mockBuilder()
await dev()
await NuxtCommand.from(dev).run()
jest.clearAllMocks()
const builder = new Builder()
@ -84,7 +81,7 @@ describe('dev', () => {
const Nuxt = mockNuxt()
const Builder = mockBuilder()
await dev()
await NuxtCommand.from(dev).run()
jest.clearAllMocks()
mockGetNuxtConfig().mockImplementationOnce(() => {
@ -106,7 +103,7 @@ describe('dev', () => {
})
mockBuilder()
await dev()
await NuxtCommand.from(dev).run()
expect(consola.error).toHaveBeenCalledWith(new Error('Listen Error'))
})

View File

@ -1,33 +1,26 @@
import { consola, mockGetNuxt, mockGetGenerator } from '../utils'
import { consola, mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils'
import Command from '../../src/command'
describe('generate', () => {
let generate
beforeAll(async () => {
generate = await import('../../src/commands/generate')
generate = generate.default
generate = await import('../../src/commands/generate').then(m => m.default)
jest.spyOn(process, 'exit').mockImplementation(code => code)
})
afterAll(() => {
process.exit.mockRestore()
})
afterAll(() => process.exit.mockRestore())
afterEach(() => jest.resetAllMocks())
afterEach(() => {
jest.resetAllMocks()
})
test('is function', () => {
expect(typeof generate).toBe('function')
test('has run function', () => {
expect(typeof generate.run).toBe('function')
})
test('builds by default', async () => {
mockGetNuxt()
const generator = mockGetGenerator(Promise.resolve())
await generate()
await NuxtCommand.from(generate).run()
expect(generator).toHaveBeenCalled()
expect(generator.mock.calls[0][0].build).toBe(true)
@ -46,7 +39,7 @@ describe('generate', () => {
})
const generator = mockGetGenerator(Promise.resolve())
await generate()
await NuxtCommand.from(generate).run()
expect(generator).toHaveBeenCalled()
expect(generator.mock.calls[0][0].build).toBe(false)
@ -57,7 +50,7 @@ describe('generate', () => {
mockGetNuxt()
mockGetGenerator(Promise.reject(new Error('Generator Error')))
await generate()
await NuxtCommand.from(generate).run()
expect(consola.fatal).toHaveBeenCalledWith(new Error('Generator Error'))
})

View File

@ -1,29 +1,27 @@
import fs from 'fs'
import { consola, mockGetNuxtStart, mockGetNuxtConfig } from '../utils'
import { consola, mockGetNuxtStart, mockGetNuxtConfig, NuxtCommand } from '../utils'
describe('start', () => {
let start
beforeAll(async () => {
start = await import('../../src/commands/start')
start = start.default
start = await import('../../src/commands/start').then(m => m.default)
})
afterEach(() => {
if (fs.existsSync.mockRestore) {
fs.existsSync.mockRestore()
}
jest.resetAllMocks()
})
test('is function', () => {
expect(typeof start).toBe('function')
test('has run function', () => {
expect(typeof start.run).toBe('function')
})
test('starts listening and calls showReady', async () => {
const { listen, showReady } = mockGetNuxtStart()
await start()
await NuxtCommand.from(start).run()
expect(listen).toHaveBeenCalled()
expect(showReady).toHaveBeenCalled()
@ -34,7 +32,7 @@ describe('start', () => {
mockGetNuxtConfig()
jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => true)
await start()
await NuxtCommand.from(start).run()
expect(consola.fatal).not.toHaveBeenCalled()
})
@ -43,7 +41,7 @@ describe('start', () => {
mockGetNuxtStart()
jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => false)
await start()
await NuxtCommand.from(start).run()
expect(consola.fatal).toHaveBeenCalledWith('No build files found, please run `nuxt build` before launching `nuxt start`')
})
@ -53,7 +51,7 @@ describe('start', () => {
mockGetNuxtConfig()
jest.spyOn(fs, 'existsSync').mockImplementation(() => true)
await start()
await NuxtCommand.from(start).run()
expect(consola.fatal).not.toHaveBeenCalled()
})
@ -62,7 +60,7 @@ describe('start', () => {
mockGetNuxtStart(true)
jest.spyOn(fs, 'existsSync').mockImplementation(() => false)
await start()
await NuxtCommand.from(start).run()
expect(consola.fatal).toHaveBeenCalledWith('No SSR build! Please start with `nuxt start --spa` or build using `nuxt build --universal`')
})

View File

@ -3,9 +3,7 @@ import { consola } from '../utils'
import * as utils from '../../src/utils'
describe('cli/utils', () => {
afterEach(() => {
jest.resetAllMocks()
})
afterEach(() => jest.resetAllMocks())
test('loadNuxtConfig: defaults', async () => {
const argv = {

View File

@ -1,5 +1,6 @@
import consola from 'consola'
export * from './mocking'
export { NuxtCommand } from '../../src'
jest.mock('consola')