mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
refactor(cli): better consistency and easier unit testing (#4160)
This commit is contained in:
parent
9df5f49e07
commit
0669b68c91
@ -1,44 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const consola = require('consola')
|
||||
const cli = require('../dist/cli.js')
|
||||
|
||||
// Global error handler
|
||||
process.on('unhandledRejection', (err) => {
|
||||
consola.error(err)
|
||||
})
|
||||
|
||||
// Exit process on fatal errors
|
||||
consola.add({
|
||||
log(logObj) {
|
||||
if (logObj.type === 'fatal') {
|
||||
process.stderr.write('Nuxt Fatal Error :(\n')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const defaultCommand = 'dev'
|
||||
const commands = new Set([
|
||||
defaultCommand,
|
||||
'build',
|
||||
'start',
|
||||
'generate'
|
||||
])
|
||||
|
||||
let cmd = process.argv[2]
|
||||
|
||||
if (commands.has(cmd)) {
|
||||
process.argv.splice(2, 1)
|
||||
} else {
|
||||
cmd = defaultCommand
|
||||
}
|
||||
|
||||
// Apply default NODE_ENV if not provided
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = cmd === 'dev' ? 'development' : 'production'
|
||||
}
|
||||
|
||||
cli[cmd]().then(m => m.default()).catch((error) => {
|
||||
consola.fatal(error)
|
||||
})
|
||||
require('../dist/cli.js').run()
|
||||
|
@ -14,7 +14,8 @@
|
||||
"dependencies": {
|
||||
"consola": "^1.4.4",
|
||||
"esm": "^3.0.84",
|
||||
"minimist": "^1.2.0"
|
||||
"minimist": "^1.2.0",
|
||||
"wrap-ansi": "^4.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
143
packages/cli/src/command.js
Normal file
143
packages/cli/src/command.js
Normal file
@ -0,0 +1,143 @@
|
||||
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 * as imports from './imports'
|
||||
|
||||
const startSpaces = 6
|
||||
const optionSpaces = 2
|
||||
const maxCharsPerLine = 80
|
||||
|
||||
export default class NuxtCommand {
|
||||
constructor({ description, usage, options } = {}) {
|
||||
this.description = description || ''
|
||||
this.usage = usage || ''
|
||||
this.options = Array.from(new Set((options || []).concat(DefaultOptions)))
|
||||
}
|
||||
|
||||
_getMinimistOptions() {
|
||||
const minimistOptions = {
|
||||
alias: {},
|
||||
boolean: [],
|
||||
string: [],
|
||||
default: {}
|
||||
}
|
||||
|
||||
for (const name of this.options) {
|
||||
const option = Options[name]
|
||||
|
||||
if (option.alias) {
|
||||
minimistOptions.alias[option.alias] = name
|
||||
}
|
||||
if (option.type) {
|
||||
minimistOptions[option.type].push(option.alias || name)
|
||||
}
|
||||
if (option.default) {
|
||||
minimistOptions.default[option.alias || name] = option.default
|
||||
}
|
||||
}
|
||||
|
||||
return minimistOptions
|
||||
}
|
||||
|
||||
getArgv(args) {
|
||||
const minimistOptions = this._getMinimistOptions()
|
||||
const argv = parseArgs(args || process.argv.slice(2), minimistOptions)
|
||||
|
||||
if (argv.version) {
|
||||
this.showVersion()
|
||||
} else if (argv.help) {
|
||||
this.showHelp()
|
||||
}
|
||||
|
||||
return argv
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
importCore() {
|
||||
return imports.core()
|
||||
}
|
||||
|
||||
importBuilder() {
|
||||
return imports.builder()
|
||||
}
|
||||
|
||||
importGenerator() {
|
||||
return imports.generator()
|
||||
}
|
||||
|
||||
async getNuxt(options) {
|
||||
const { Nuxt } = await this.importCore()
|
||||
return new Nuxt(options)
|
||||
}
|
||||
|
||||
async getBuilder(nuxt) {
|
||||
const { Builder } = await this.importBuilder()
|
||||
return new Builder(nuxt)
|
||||
}
|
||||
|
||||
async getGenerator(nuxt) {
|
||||
const { Generator } = await this.importGenerator()
|
||||
const { Builder } = await this.importBuilder()
|
||||
return new Generator(nuxt, new Builder(nuxt))
|
||||
}
|
||||
|
||||
_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 ])
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}).join('\n')
|
||||
|
||||
const description = foldLines(this.description, maxCharsPerLine, startSpaces)
|
||||
|
||||
return `
|
||||
Description\n${description}
|
||||
Usage
|
||||
$ nuxt ${this.usage}
|
||||
Options\n${optionStr}\n\n`
|
||||
}
|
||||
|
||||
showVersion() {
|
||||
process.stdout.write(`${name} v${version}\n`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
showHelp() {
|
||||
process.stdout.write(this._getHelp())
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
@ -1,91 +1,46 @@
|
||||
import parseArgs from 'minimist'
|
||||
import consola from 'consola'
|
||||
|
||||
import { loadNuxtConfig } from '../common/utils'
|
||||
import NuxtCommand from '../command'
|
||||
|
||||
export default async function build() {
|
||||
const { Nuxt } = await import('@nuxt/core')
|
||||
const { Builder } = await import('@nuxt/builder')
|
||||
const { Generator } = await import('@nuxt/generator')
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
c: 'config-file',
|
||||
a: 'analyze',
|
||||
s: 'spa',
|
||||
u: 'universal',
|
||||
q: 'quiet'
|
||||
},
|
||||
boolean: ['h', 'a', 's', 'u', 'q'],
|
||||
string: ['c'],
|
||||
default: {
|
||||
c: 'nuxt.config.js'
|
||||
}
|
||||
const nuxtCmd = new NuxtCommand({
|
||||
description: 'Compiles the application for production deployment',
|
||||
usage: 'build <dir>',
|
||||
options: [ 'analyze', 'quiet' ]
|
||||
})
|
||||
|
||||
if (argv.help) {
|
||||
process.stderr.write(`
|
||||
Description
|
||||
Compiles the application for production deployment
|
||||
Usage
|
||||
$ nuxt build <dir>
|
||||
Options
|
||||
--analyze, -a Launch webpack-bundle-analyzer to optimize your bundles.
|
||||
--spa, -s Launch in SPA mode
|
||||
--universal, -u Launch in Universal mode (default)
|
||||
--no-generate Don't generate static version for SPA mode (useful for nuxt start)
|
||||
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
|
||||
--quiet, -q Disable output except for errors
|
||||
--help, -h Displays this message
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
const argv = nuxtCmd.getArgv()
|
||||
|
||||
const options = await loadNuxtConfig(argv)
|
||||
|
||||
// Create production build when calling `nuxt build`
|
||||
options.dev = false
|
||||
|
||||
// Analyze option
|
||||
options.build = options.build || {}
|
||||
if (argv.analyze && typeof options.build.analyze !== 'object') {
|
||||
options.build.analyze = true
|
||||
}
|
||||
|
||||
// Silence output when using --quiet
|
||||
if (argv.quiet) {
|
||||
options.build.quiet = !!argv.quiet
|
||||
}
|
||||
|
||||
const nuxt = new Nuxt(options)
|
||||
const builder = new Builder(nuxt)
|
||||
// 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))
|
||||
|
||||
// Close function
|
||||
const close = () => {
|
||||
// In analyze mode wait for plugin
|
||||
// emitting assets and opening browser
|
||||
if (options.build.analyze === true || typeof options.build.analyze === 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (options.mode !== 'spa' || argv.generate === false) {
|
||||
let builderOrGenerator
|
||||
if (nuxt.options.mode !== 'spa' || argv.generate === false) {
|
||||
// Build only
|
||||
return builder
|
||||
.build()
|
||||
.then(close)
|
||||
.catch(err => consola.fatal(err))
|
||||
builderOrGenerator = (await nuxtCmd.getBuilder(nuxt)).build()
|
||||
} else {
|
||||
// Build + Generate for static deployment
|
||||
return new Generator(nuxt, builder)
|
||||
.generate({ build: true })
|
||||
.then(close)
|
||||
.catch(err => consola.fatal(err))
|
||||
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))
|
||||
}
|
||||
|
@ -1,71 +1,29 @@
|
||||
import parseArgs from 'minimist'
|
||||
import consola from 'consola'
|
||||
import { loadNuxtConfig, runAsyncScript } from '../common/utils'
|
||||
import NuxtCommand from '../command'
|
||||
|
||||
export default async function dev() {
|
||||
const { Nuxt } = await import('@nuxt/core')
|
||||
const { Builder } = await import('@nuxt/builder')
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
H: 'hostname',
|
||||
p: 'port',
|
||||
c: 'config-file',
|
||||
s: 'spa',
|
||||
u: 'universal',
|
||||
v: 'version'
|
||||
},
|
||||
boolean: ['h', 's', 'u', 'v'],
|
||||
string: ['H', 'c'],
|
||||
default: {
|
||||
c: 'nuxt.config.js'
|
||||
}
|
||||
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' ]
|
||||
})
|
||||
|
||||
if (argv.version) {
|
||||
process.stderr.write('TODO' + '\n')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (argv.hostname === '') {
|
||||
consola.fatal('Provided hostname argument has no value')
|
||||
}
|
||||
|
||||
if (argv.help) {
|
||||
process.stderr.write(`
|
||||
Description
|
||||
Starts the application in development mode (hot-code reloading, error
|
||||
reporting, etc)
|
||||
Usage
|
||||
$ nuxt dev <dir> -p <port number> -H <hostname>
|
||||
Options
|
||||
--port, -p A port number on which to start the application
|
||||
--hostname, -H Hostname on which to start the application
|
||||
--spa Launch in SPA mode
|
||||
--universal Launch in Universal mode (default)
|
||||
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
|
||||
--help, -h Displays this message
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const config = async () => {
|
||||
// Force development mode for add hot reloading and watching changes
|
||||
return Object.assign(await loadNuxtConfig(argv), { dev: true })
|
||||
}
|
||||
const argv = nuxtCmd.getArgv()
|
||||
|
||||
const errorHandler = (err, instance) => {
|
||||
instance && instance.builder.watchServer()
|
||||
consola.error(err)
|
||||
}
|
||||
|
||||
const { Nuxt } = await nuxtCmd.importCore()
|
||||
const { Builder } = await nuxtCmd.importBuilder()
|
||||
|
||||
// Start dev
|
||||
async function startDev(oldInstance) {
|
||||
let nuxt, builder
|
||||
|
||||
try {
|
||||
nuxt = new Nuxt(await config())
|
||||
nuxt = new Nuxt(await nuxtCmd.getNuxtConfig(argv, { dev: true }))
|
||||
builder = new Builder(nuxt)
|
||||
nuxt.hook('watch:fileChanged', async (builder, fname) => {
|
||||
consola.debug(`[${fname}] changed, Rebuilding the app...`)
|
||||
@ -81,10 +39,10 @@ export default async function dev() {
|
||||
.then(() => oldInstance && oldInstance.builder.unwatch())
|
||||
// Start build
|
||||
.then(() => builder.build())
|
||||
// Close old nuxt no mater if build successfully
|
||||
// Close old nuxt no matter if build successfully
|
||||
.catch((err) => {
|
||||
oldInstance && oldInstance.nuxt.close()
|
||||
// Jump to eventHandler
|
||||
// Jump to errorHandler
|
||||
throw err
|
||||
})
|
||||
.then(() => oldInstance && oldInstance.nuxt.close())
|
||||
@ -98,5 +56,5 @@ export default async function dev() {
|
||||
)
|
||||
}
|
||||
|
||||
await runAsyncScript(startDev)
|
||||
await startDev()
|
||||
}
|
||||
|
@ -1,61 +1,25 @@
|
||||
import parseArgs from 'minimist'
|
||||
import consola from 'consola'
|
||||
|
||||
import { loadNuxtConfig } from '../common/utils'
|
||||
import NuxtCommand from '../command'
|
||||
|
||||
export default async function generate() {
|
||||
const { Nuxt } = await import('@nuxt/core')
|
||||
const { Builder } = await import('@nuxt/builder')
|
||||
const { Generator } = await import('@nuxt/generator')
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
c: 'config-file',
|
||||
s: 'spa',
|
||||
u: 'universal'
|
||||
},
|
||||
boolean: ['h', 's', 'u', 'build'],
|
||||
string: ['c'],
|
||||
default: {
|
||||
c: 'nuxt.config.js',
|
||||
build: true
|
||||
}
|
||||
const nuxtCmd = new NuxtCommand({
|
||||
description: 'Generate a static web application (server-rendered)',
|
||||
usage: 'generate <dir>',
|
||||
options: [ 'build' ]
|
||||
})
|
||||
|
||||
if (argv.help) {
|
||||
process.stderr.write(`
|
||||
Description
|
||||
Generate a static web application (server-rendered)
|
||||
Usage
|
||||
$ nuxt generate <dir>
|
||||
Options
|
||||
--spa Launch in SPA mode
|
||||
--universal Launch in Universal mode (default)
|
||||
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
|
||||
--help, -h Displays this message
|
||||
--no-build Just run generate for faster builds when just dynamic routes changed. Nuxt build is needed before this command.
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
const argv = nuxtCmd.getArgv()
|
||||
|
||||
const options = await loadNuxtConfig(argv)
|
||||
const generator = await nuxtCmd.getGenerator(
|
||||
await nuxtCmd.getNuxt(
|
||||
await nuxtCmd.getNuxtConfig(argv, { dev: false })
|
||||
)
|
||||
)
|
||||
|
||||
options.dev = false // Force production mode (no webpack middleware called)
|
||||
|
||||
const nuxt = new Nuxt(options)
|
||||
const builder = new Builder(nuxt)
|
||||
const generator = new Generator(nuxt, builder)
|
||||
|
||||
const generateOptions = {
|
||||
return generator.generate({
|
||||
init: true,
|
||||
build: argv.build
|
||||
}
|
||||
|
||||
return generator
|
||||
.generate(generateOptions)
|
||||
.then(() => {
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => consola.fatal(err))
|
||||
}).then(() => {
|
||||
process.exit(0)
|
||||
}).catch(err => consola.fatal(err))
|
||||
}
|
||||
|
4
packages/cli/src/commands/index.js
Normal file
4
packages/cli/src/commands/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
export const start = () => import('./start')
|
||||
export const dev = () => import('./dev')
|
||||
export const build = () => import('./build')
|
||||
export const generate = () => import('./generate')
|
@ -1,59 +1,21 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import parseArgs from 'minimist'
|
||||
import consola from 'consola'
|
||||
|
||||
import { loadNuxtConfig } from '../common/utils'
|
||||
import NuxtCommand from '../command'
|
||||
|
||||
export default async function start() {
|
||||
const { Nuxt } = await import('@nuxt/core')
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
H: 'hostname',
|
||||
p: 'port',
|
||||
n: 'unix-socket',
|
||||
c: 'config-file',
|
||||
s: 'spa',
|
||||
u: 'universal'
|
||||
},
|
||||
boolean: ['h', 's', 'u'],
|
||||
string: ['H', 'c', 'n'],
|
||||
default: {
|
||||
c: 'nuxt.config.js'
|
||||
}
|
||||
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' ]
|
||||
})
|
||||
|
||||
if (argv.hostname === '') {
|
||||
consola.fatal('Provided hostname argument has no value')
|
||||
}
|
||||
const argv = nuxtCmd.getArgv()
|
||||
|
||||
if (argv.help) {
|
||||
process.stderr.write(`
|
||||
Description
|
||||
Starts the application in production mode.
|
||||
The application should be compiled with \`nuxt build\` first.
|
||||
Usage
|
||||
$ nuxt start <dir> -p <port number> -H <hostname>
|
||||
Options
|
||||
--port, -p A 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
|
||||
--spa Launch in SPA mode
|
||||
--universal Launch in Universal mode (default)
|
||||
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
|
||||
--help, -h Displays this message
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const options = await loadNuxtConfig(argv)
|
||||
|
||||
// Force production mode (no webpack middleware called)
|
||||
options.dev = false
|
||||
|
||||
const nuxt = new Nuxt(options)
|
||||
// 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))
|
||||
|
3
packages/cli/src/imports.js
Normal file
3
packages/cli/src/imports.js
Normal file
@ -0,0 +1,3 @@
|
||||
export const builder = () => import('@nuxt/builder')
|
||||
export const generator = () => import('@nuxt/generator')
|
||||
export const core = () => import('@nuxt/core')
|
@ -1,5 +1,10 @@
|
||||
export const start = () => import('./commands/start')
|
||||
export const dev = () => import('./commands/dev')
|
||||
import * as _commands from './commands'
|
||||
import * as _imports from './imports'
|
||||
|
||||
export const build = () => import('./commands/build')
|
||||
export const generate = () => import('./commands/generate')
|
||||
export const commands = _commands
|
||||
export const imports = _imports
|
||||
|
||||
export { default as setup } from './setup'
|
||||
export { default as run } from './run'
|
||||
|
||||
export { loadNuxtConfig } from './utils'
|
||||
|
101
packages/cli/src/options.js
Normal file
101
packages/cli/src/options.js
Normal file
@ -0,0 +1,101 @@
|
||||
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'
|
||||
}
|
||||
}
|
33
packages/cli/src/run.js
Normal file
33
packages/cli/src/run.js
Normal file
@ -0,0 +1,33 @@
|
||||
import consola from 'consola'
|
||||
import * as commands from './commands'
|
||||
import setup from './setup'
|
||||
|
||||
export default function run() {
|
||||
const defaultCommand = 'dev'
|
||||
|
||||
const cmds = new Set([
|
||||
defaultCommand,
|
||||
'build',
|
||||
'start',
|
||||
'generate'
|
||||
])
|
||||
|
||||
let cmd = process.argv[2]
|
||||
|
||||
if (cmds.has(cmd)) {
|
||||
process.argv.splice(2, 1)
|
||||
} else {
|
||||
cmd = defaultCommand
|
||||
}
|
||||
|
||||
// Setup runtime
|
||||
setup({
|
||||
dev: cmd === 'dev'
|
||||
})
|
||||
|
||||
return commands[cmd]() // eslint-disable-line import/namespace
|
||||
.then(m => m.default())
|
||||
.catch((error) => {
|
||||
consola.fatal(error)
|
||||
})
|
||||
}
|
32
packages/cli/src/setup.js
Normal file
32
packages/cli/src/setup.js
Normal file
@ -0,0 +1,32 @@
|
||||
import consola from 'consola'
|
||||
|
||||
let _setup = false
|
||||
|
||||
export default function setup({ dev }) {
|
||||
// Apply default NODE_ENV if not provided
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = dev ? 'development' : 'production'
|
||||
}
|
||||
|
||||
if (_setup) {
|
||||
return
|
||||
}
|
||||
_setup = true
|
||||
|
||||
// Global error handler
|
||||
/* istanbul ignore next */
|
||||
process.on('unhandledRejection', (err) => {
|
||||
consola.error(err)
|
||||
})
|
||||
|
||||
// Exit process on fatal errors
|
||||
/* istanbul ignore next */
|
||||
consola.add({
|
||||
log(logObj) {
|
||||
if (logObj.type === 'fatal') {
|
||||
process.stderr.write('Nuxt Fatal Error :(\n')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -2,6 +2,7 @@ import path from 'path'
|
||||
import { existsSync } from 'fs'
|
||||
import consola from 'consola'
|
||||
import esm from 'esm'
|
||||
import wrapAnsi from 'wrap-ansi'
|
||||
|
||||
const _require = esm(module, {
|
||||
cache: false,
|
||||
@ -33,15 +34,6 @@ const getLatestHost = (argv) => {
|
||||
return { port, host, socket }
|
||||
}
|
||||
|
||||
export async function runAsyncScript(fn) {
|
||||
try {
|
||||
await fn()
|
||||
} catch (err) {
|
||||
consola.error(err)
|
||||
consola.fatal(`Failed to run async Nuxt script!`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadNuxtConfig(argv) {
|
||||
const rootDir = getRootDir(argv)
|
||||
const nuxtConfigFile = getNuxtConfigFile(argv)
|
||||
@ -50,16 +42,17 @@ export async function loadNuxtConfig(argv) {
|
||||
|
||||
if (existsSync(nuxtConfigFile)) {
|
||||
delete require.cache[nuxtConfigFile]
|
||||
options = _require(nuxtConfigFile)
|
||||
if (!options) {
|
||||
options = {}
|
||||
}
|
||||
options = _require(nuxtConfigFile) || {}
|
||||
if (options.default) {
|
||||
options = options.default
|
||||
}
|
||||
|
||||
if (typeof options === 'function') {
|
||||
try {
|
||||
options = await options()
|
||||
if (options.default) {
|
||||
options = options.default
|
||||
}
|
||||
} catch (error) {
|
||||
consola.error(error)
|
||||
consola.fatal('Error while fetching async configuration')
|
||||
@ -68,7 +61,6 @@ export async function loadNuxtConfig(argv) {
|
||||
} else if (argv['config-file'] !== 'nuxt.config.js') {
|
||||
consola.fatal('Could not load config file: ' + argv['config-file'])
|
||||
}
|
||||
|
||||
if (typeof options.rootDir !== 'string') {
|
||||
options.rootDir = rootDir
|
||||
}
|
||||
@ -81,9 +73,33 @@ export async function loadNuxtConfig(argv) {
|
||||
if (!options.server) {
|
||||
options.server = {}
|
||||
}
|
||||
|
||||
const { port, host, socket } = getLatestHost(argv)
|
||||
options.server.port = port || options.server.port || 3000
|
||||
options.server.host = host || options.server.host || 'localhost'
|
||||
options.server.socket = socket || options.server.socket
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
export function indent(count, chr = ' ') {
|
||||
return chr.repeat(count)
|
||||
}
|
||||
|
||||
export function indentLines(string, spaces, firstLineSpaces) {
|
||||
const lines = Array.isArray(string) ? string : string.split('\n')
|
||||
let s = ''
|
||||
if (lines.length) {
|
||||
const i0 = indent(firstLineSpaces === undefined ? spaces : firstLineSpaces)
|
||||
s = i0 + lines.shift()
|
||||
}
|
||||
if (lines.length) {
|
||||
const i = indent(spaces)
|
||||
s += '\n' + lines.map(l => i + l.trim()).join('\n')
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
export function foldLines(string, maxCharsPerLine, spaces, firstLineSpaces) {
|
||||
return indentLines(wrapAnsi(string, maxCharsPerLine), spaces, firstLineSpaces)
|
||||
}
|
9
packages/cli/test/fixtures/nuxt.async-config.js
vendored
Normal file
9
packages/cli/test/fixtures/nuxt.async-config.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default () => {
|
||||
// delete cache is needed because otherwise Jest will return the same
|
||||
// object reference as the previous test and then mode will not be
|
||||
// set correctly. jest.resetModules doesnt work for some reason
|
||||
delete require.cache[resolve(__dirname, 'nuxt.config.js')]
|
||||
return import('./nuxt.config.js')
|
||||
}
|
1
packages/cli/test/fixtures/nuxt.async-error.js
vendored
Normal file
1
packages/cli/test/fixtures/nuxt.async-error.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default () => Promise.reject(new Error('Async Config Error'))
|
10
packages/cli/test/fixtures/nuxt.config.js
vendored
Normal file
10
packages/cli/test/fixtures/nuxt.config.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
export default {
|
||||
testOption: true,
|
||||
rootDir: '/some/path',
|
||||
mode: 'supercharged',
|
||||
server: {
|
||||
host: 'nuxt-host',
|
||||
port: 3001,
|
||||
socket: '/var/run/nuxt.sock'
|
||||
}
|
||||
}
|
62
packages/cli/test/unit/build.test.js
Normal file
62
packages/cli/test/unit/build.test.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { consola, mockGetNuxt, mockGetBuilder, mockGetGenerator } from '../utils'
|
||||
|
||||
describe('build', () => {
|
||||
let build
|
||||
|
||||
beforeAll(async () => {
|
||||
build = await import('../../src/commands/build')
|
||||
build = build.default
|
||||
|
||||
jest.spyOn(process, 'exit').mockImplementation(code => code)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.exit.mockRestore()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
test('is function', () => {
|
||||
expect(typeof build).toBe('function')
|
||||
})
|
||||
|
||||
test('builds on universal mode', async () => {
|
||||
mockGetNuxt({
|
||||
mode: 'universal',
|
||||
build: {
|
||||
analyze: true
|
||||
}
|
||||
})
|
||||
const builder = mockGetBuilder(Promise.resolve())
|
||||
|
||||
await build()
|
||||
|
||||
expect(builder).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('generates on spa mode', async () => {
|
||||
mockGetNuxt({
|
||||
mode: 'spa',
|
||||
build: {
|
||||
analyze: false
|
||||
}
|
||||
})
|
||||
const generate = mockGetGenerator(Promise.resolve())
|
||||
|
||||
await build()
|
||||
|
||||
expect(generate).toHaveBeenCalled()
|
||||
expect(process.exit).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('catches error', async () => {
|
||||
mockGetNuxt({ mode: 'universal' })
|
||||
mockGetBuilder(Promise.reject(new Error('Builder Error')))
|
||||
|
||||
await build()
|
||||
|
||||
expect(consola.fatal).toHaveBeenCalledWith(new Error('Builder Error'))
|
||||
})
|
||||
})
|
95
packages/cli/test/unit/cli.test.js
Normal file
95
packages/cli/test/unit/cli.test.js
Normal file
@ -0,0 +1,95 @@
|
||||
import { readdir } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import { promisify } from 'util'
|
||||
import { consola } from '../utils'
|
||||
import { run } from '../../src'
|
||||
import * as commands from '../../src/commands'
|
||||
|
||||
const readDir = promisify(readdir)
|
||||
|
||||
consola.add = jest.fn()
|
||||
|
||||
const mockCommand = (cmd, p) => {
|
||||
commands[cmd] = jest.fn().mockImplementationOnce(() => { // eslint-disable-line import/namespace
|
||||
return Promise.resolve({
|
||||
default: () => {
|
||||
return p
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('cli', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
test('exports for all commands defined', async () => {
|
||||
const cmds = await readDir(resolve(__dirname, '..', '..', 'src', 'commands'))
|
||||
|
||||
for (let cmd of cmds) {
|
||||
if (cmd === 'index.js') {
|
||||
continue
|
||||
}
|
||||
cmd = cmd.substring(0, cmd.length - 3)
|
||||
|
||||
expect(commands[cmd]).toBeDefined() // eslint-disable-line import/namespace
|
||||
expect(typeof commands[cmd]).toBe('function') // eslint-disable-line import/namespace
|
||||
}
|
||||
})
|
||||
|
||||
test('calls expected method', async () => {
|
||||
const argv = process.argv
|
||||
process.argv = ['', '', 'dev']
|
||||
mockCommand('dev', Promise.resolve())
|
||||
|
||||
await run()
|
||||
|
||||
expect(commands.dev).toHaveBeenCalled()
|
||||
process.argv = argv
|
||||
})
|
||||
|
||||
test('unknown calls default method', async () => {
|
||||
const argv = process.argv
|
||||
process.argv = ['', '', 'test']
|
||||
mockCommand('dev', Promise.resolve())
|
||||
|
||||
await run()
|
||||
|
||||
expect(commands.dev).toHaveBeenCalled()
|
||||
process.argv = argv
|
||||
})
|
||||
|
||||
test('sets NODE_ENV=development for dev', async () => {
|
||||
const nodeEnv = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = ''
|
||||
mockCommand('dev', Promise.resolve())
|
||||
|
||||
await run()
|
||||
|
||||
expect(process.env.NODE_ENV).toBe('development')
|
||||
process.env.NODE_ENV = nodeEnv
|
||||
})
|
||||
|
||||
test('sets ODE_ENV=production for build', async () => {
|
||||
const argv = process.argv
|
||||
const nodeEnv = process.env.NODE_ENV
|
||||
process.argv = ['', '', 'build']
|
||||
process.env.NODE_ENV = ''
|
||||
mockCommand('build', Promise.resolve())
|
||||
|
||||
await run()
|
||||
|
||||
expect(process.env.NODE_ENV).toBe('production')
|
||||
process.argv = argv
|
||||
process.env.NODE_ENV = nodeEnv
|
||||
})
|
||||
|
||||
test('catches fatal error', async () => {
|
||||
mockCommand('dev', Promise.reject(new Error('Command Error')))
|
||||
|
||||
await run()
|
||||
|
||||
expect(consola.fatal).toHaveBeenCalledWith(new Error('Command Error'))
|
||||
})
|
||||
})
|
169
packages/cli/test/unit/command.test.js
Normal file
169
packages/cli/test/unit/command.test.js
Normal file
@ -0,0 +1,169 @@
|
||||
import { consola } from '../utils'
|
||||
import Command from '../../src/command'
|
||||
import { options as Options } from '../../src/options'
|
||||
|
||||
jest.mock('@nuxt/core')
|
||||
jest.mock('@nuxt/builder')
|
||||
jest.mock('@nuxt/generator')
|
||||
|
||||
describe('cli/command', () => {
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
test('adds default options', () => {
|
||||
const cmd = new Command()
|
||||
|
||||
expect(cmd.options.length).not.toBe(0)
|
||||
})
|
||||
|
||||
test('builds minimist options', () => {
|
||||
const cmd = new Command({
|
||||
options: Object.keys(Options)
|
||||
})
|
||||
|
||||
const minimistOptions = cmd._getMinimistOptions()
|
||||
|
||||
expect(minimistOptions.string.length).toBe(4)
|
||||
expect(minimistOptions.boolean.length).toBe(9)
|
||||
expect(minimistOptions.alias.c).toBe('config-file')
|
||||
expect(minimistOptions.default.c).toBe(Options['config-file'].default)
|
||||
})
|
||||
|
||||
test('parses args', () => {
|
||||
const cmd = new Command({
|
||||
options: Object.keys(Options)
|
||||
})
|
||||
|
||||
let args = ['-c', 'test-file', '-s', '-p', '3001']
|
||||
let argv = cmd.getArgv(args)
|
||||
|
||||
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']
|
||||
argv = cmd.getArgv(args)
|
||||
|
||||
expect(argv.build).toBe(false)
|
||||
})
|
||||
|
||||
test('prints version automatically', () => {
|
||||
const cmd = new Command()
|
||||
cmd.showVersion = jest.fn()
|
||||
|
||||
const args = ['--version']
|
||||
cmd.getArgv(args)
|
||||
|
||||
expect(cmd.showVersion).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('prints help automatically', () => {
|
||||
const cmd = new Command()
|
||||
cmd.showHelp = jest.fn()
|
||||
|
||||
const args = ['-h']
|
||||
cmd.getArgv(args)
|
||||
|
||||
expect(cmd.showHelp).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('returns nuxt config', async () => {
|
||||
const cmd = new Command({
|
||||
options: Object.keys(Options)
|
||||
})
|
||||
|
||||
const args = ['-c', 'test-file', '-a', '-p', '3001', '-q', '-H']
|
||||
const argv = cmd.getArgv(args)
|
||||
argv._ = ['.']
|
||||
|
||||
const options = await cmd.getNuxtConfig(argv, { testOption: true })
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
test('returns Nuxt instance', async () => {
|
||||
const cmd = new Command()
|
||||
const nuxt = await cmd.getNuxt()
|
||||
|
||||
expect(nuxt.constructor.name).toBe('Nuxt')
|
||||
expect(typeof nuxt.ready).toBe('function')
|
||||
})
|
||||
|
||||
test('returns Builder instance', async () => {
|
||||
const cmd = new Command()
|
||||
const builder = await cmd.getBuilder()
|
||||
|
||||
expect(builder.constructor.name).toBe('Builder')
|
||||
expect(typeof builder.build).toBe('function')
|
||||
})
|
||||
|
||||
test('returns Generator instance', async () => {
|
||||
const cmd = new Command()
|
||||
const generator = await cmd.getGenerator()
|
||||
|
||||
expect(generator.constructor.name).toBe('Generator')
|
||||
expect(typeof generator.generate).toBe('function')
|
||||
})
|
||||
|
||||
test('builds help text', () => {
|
||||
const cmd = new 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']
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
test('show version prints to stdout and exits', () => {
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation(() => {})
|
||||
jest.spyOn(process, 'exit').mockImplementationOnce(code => code)
|
||||
|
||||
const cmd = new Command()
|
||||
cmd.showVersion()
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalled()
|
||||
expect(process.exit).toHaveBeenCalled()
|
||||
|
||||
process.stdout.write.mockRestore()
|
||||
process.exit.mockRestore()
|
||||
})
|
||||
|
||||
test('show help prints to stdout and exits', () => {
|
||||
jest.spyOn(process.stdout, 'write').mockImplementation(() => {})
|
||||
jest.spyOn(process, 'exit').mockImplementationOnce(code => code)
|
||||
|
||||
const cmd = new Command()
|
||||
cmd.showHelp()
|
||||
|
||||
expect(process.stdout.write).toHaveBeenCalled()
|
||||
expect(process.exit).toHaveBeenCalled()
|
||||
|
||||
process.stdout.write.mockRestore()
|
||||
process.exit.mockRestore()
|
||||
})
|
||||
})
|
114
packages/cli/test/unit/dev.test.js
Normal file
114
packages/cli/test/unit/dev.test.js
Normal file
@ -0,0 +1,114 @@
|
||||
import { consola } from '../utils'
|
||||
import { mockNuxt, mockBuilder, mockGetNuxtConfig } from '../utils/mocking'
|
||||
|
||||
describe('dev', () => {
|
||||
let dev
|
||||
|
||||
beforeAll(async () => {
|
||||
dev = await import('../../src/commands/dev')
|
||||
dev = dev.default
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
test('is function', () => {
|
||||
expect(typeof dev).toBe('function')
|
||||
})
|
||||
|
||||
test('reloads on fileChanged hook', async () => {
|
||||
const Nuxt = mockNuxt()
|
||||
const Builder = mockBuilder()
|
||||
|
||||
await dev()
|
||||
|
||||
expect(consola.error).not.toHaveBeenCalled()
|
||||
|
||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.listen).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.showReady).toHaveBeenCalled()
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||
|
||||
jest.resetAllMocks()
|
||||
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
expect(consola.debug).toHaveBeenCalled()
|
||||
|
||||
expect(Nuxt.prototype.clearHook).toHaveBeenCalled()
|
||||
expect(Builder.prototype.unwatch).toHaveBeenCalled()
|
||||
expect(Builder.prototype.build).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.listen).toHaveBeenCalled()
|
||||
expect(Nuxt.prototype.showReady).not.toHaveBeenCalled()
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalled()
|
||||
|
||||
expect(consola.error).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('catches build error', async () => {
|
||||
const Nuxt = mockNuxt()
|
||||
const Builder = mockBuilder()
|
||||
|
||||
await dev()
|
||||
jest.resetAllMocks()
|
||||
|
||||
// Test error on second build so we cover oldInstance stuff
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
Builder.prototype.build = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('Build Error')))
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
|
||||
expect(Nuxt.prototype.close).toHaveBeenCalled()
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('Build Error'))
|
||||
})
|
||||
|
||||
test('catches watchServer error', async () => {
|
||||
const Nuxt = mockNuxt()
|
||||
const Builder = mockBuilder()
|
||||
|
||||
await dev()
|
||||
jest.resetAllMocks()
|
||||
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
Builder.prototype.watchServer = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('watchServer Error')))
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('watchServer Error'))
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test('catches error on hook error', async () => {
|
||||
const Nuxt = mockNuxt()
|
||||
const Builder = mockBuilder()
|
||||
|
||||
await dev()
|
||||
jest.resetAllMocks()
|
||||
|
||||
mockGetNuxtConfig().mockImplementationOnce(() => {
|
||||
throw new Error('Config Error')
|
||||
})
|
||||
const builder = new Builder()
|
||||
builder.nuxt = new Nuxt()
|
||||
await Nuxt.fileChangedHook(builder)
|
||||
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('Config Error'))
|
||||
expect(Builder.prototype.watchServer).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('catches error on startDev', async () => {
|
||||
mockNuxt({
|
||||
listen: jest.fn().mockImplementation(() => {
|
||||
throw new Error('Listen Error')
|
||||
})
|
||||
})
|
||||
mockBuilder()
|
||||
|
||||
await dev()
|
||||
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('Listen Error'))
|
||||
})
|
||||
})
|
64
packages/cli/test/unit/generate.test.js
Normal file
64
packages/cli/test/unit/generate.test.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { consola, mockGetNuxt, mockGetGenerator } from '../utils'
|
||||
import Command from '../../src/command'
|
||||
|
||||
describe('generate', () => {
|
||||
let generate
|
||||
|
||||
beforeAll(async () => {
|
||||
generate = await import('../../src/commands/generate')
|
||||
generate = generate.default
|
||||
|
||||
jest.spyOn(process, 'exit').mockImplementation(code => code)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.exit.mockRestore()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
test('is function', () => {
|
||||
expect(typeof generate).toBe('function')
|
||||
})
|
||||
|
||||
test('builds by default', async () => {
|
||||
mockGetNuxt()
|
||||
const generator = mockGetGenerator(Promise.resolve())
|
||||
|
||||
await generate()
|
||||
|
||||
expect(generator).toHaveBeenCalled()
|
||||
expect(generator.mock.calls[0][0].build).toBe(true)
|
||||
})
|
||||
|
||||
test('doesnt build with no-build', async () => {
|
||||
mockGetNuxt()
|
||||
const getArgv = Command.prototype.getArgv
|
||||
Command.prototype.getArgv = jest.fn().mockImplementationOnce(() => {
|
||||
return {
|
||||
'_': ['.'],
|
||||
rootDir: '.',
|
||||
'config-file': 'nuxt.config.js',
|
||||
build: false
|
||||
}
|
||||
})
|
||||
const generator = mockGetGenerator(Promise.resolve())
|
||||
|
||||
await generate()
|
||||
|
||||
expect(generator).toHaveBeenCalled()
|
||||
expect(generator.mock.calls[0][0].build).toBe(false)
|
||||
Command.prototype.getArgv = getArgv
|
||||
})
|
||||
|
||||
test('catches error', async () => {
|
||||
mockGetNuxt()
|
||||
mockGetGenerator(Promise.reject(new Error('Generator Error')))
|
||||
|
||||
await generate()
|
||||
|
||||
expect(consola.fatal).toHaveBeenCalledWith(new Error('Generator Error'))
|
||||
})
|
||||
})
|
69
packages/cli/test/unit/start.test.js
Normal file
69
packages/cli/test/unit/start.test.js
Normal file
@ -0,0 +1,69 @@
|
||||
import fs from 'fs'
|
||||
import { consola, mockGetNuxtStart, mockGetNuxtConfig } from '../utils'
|
||||
|
||||
describe('start', () => {
|
||||
let start
|
||||
|
||||
beforeAll(async () => {
|
||||
start = await import('../../src/commands/start')
|
||||
start = start.default
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync.mockRestore) {
|
||||
fs.existsSync.mockRestore()
|
||||
}
|
||||
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
test('is function', () => {
|
||||
expect(typeof start).toBe('function')
|
||||
})
|
||||
|
||||
test('starts listening and calls showReady', async () => {
|
||||
const { listen, showReady } = mockGetNuxtStart()
|
||||
await start()
|
||||
|
||||
expect(listen).toHaveBeenCalled()
|
||||
expect(showReady).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('no error if dist dir exists', async () => {
|
||||
mockGetNuxtStart()
|
||||
mockGetNuxtConfig()
|
||||
jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => true)
|
||||
|
||||
await start()
|
||||
|
||||
expect(consola.fatal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('fatal error if dist dir doesnt exist', async () => {
|
||||
mockGetNuxtStart()
|
||||
jest.spyOn(fs, 'existsSync').mockImplementationOnce(() => false)
|
||||
|
||||
await start()
|
||||
|
||||
expect(consola.fatal).toHaveBeenCalledWith('No build files found, please run `nuxt build` before launching `nuxt start`')
|
||||
})
|
||||
|
||||
test('no error on ssr and server bundle exists', async () => {
|
||||
mockGetNuxtStart(true)
|
||||
mockGetNuxtConfig()
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => true)
|
||||
|
||||
await start()
|
||||
|
||||
expect(consola.fatal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('fatal error on ssr and server bundle doesnt exist', async () => {
|
||||
mockGetNuxtStart(true)
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => false)
|
||||
|
||||
await start()
|
||||
|
||||
expect(consola.fatal).toHaveBeenCalledWith('No SSR build! Please start with `nuxt start --spa` or build using `nuxt build --universal`')
|
||||
})
|
||||
})
|
111
packages/cli/test/unit/utils.test.js
Normal file
111
packages/cli/test/unit/utils.test.js
Normal file
@ -0,0 +1,111 @@
|
||||
import { consola } from '../utils'
|
||||
import * as utils from '../../src/utils'
|
||||
|
||||
describe('cli/utils', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
test('loadNuxtConfig: defaults', async () => {
|
||||
const argv = {
|
||||
_: ['.'],
|
||||
'config-file': 'nuxt.config.js',
|
||||
universal: true
|
||||
}
|
||||
|
||||
const options = await utils.loadNuxtConfig(argv)
|
||||
expect(options.rootDir).toBe(process.cwd())
|
||||
expect(options.mode).toBe('universal')
|
||||
expect(options.server.host).toBe('localhost')
|
||||
expect(options.server.port).toBe(3000)
|
||||
expect(options.server.socket).not.toBeDefined()
|
||||
})
|
||||
|
||||
test('loadNuxtConfig: config-file', async () => {
|
||||
const argv = {
|
||||
_: [__dirname],
|
||||
'config-file': '../fixtures/nuxt.config.js',
|
||||
spa: true
|
||||
}
|
||||
|
||||
const options = await utils.loadNuxtConfig(argv)
|
||||
expect(options.testOption).toBe(true)
|
||||
expect(options.rootDir).toBe('/some/path')
|
||||
expect(options.mode).toBe('spa')
|
||||
expect(options.server.host).toBe('nuxt-host')
|
||||
expect(options.server.port).toBe(3001)
|
||||
expect(options.server.socket).toBe('/var/run/nuxt.sock')
|
||||
})
|
||||
|
||||
test('loadNuxtConfig: not-existing config-file', async () => {
|
||||
const argv = {
|
||||
_: [__dirname],
|
||||
'config-file': '../fixtures/nuxt.doesnt-exist.js'
|
||||
}
|
||||
|
||||
const options = await utils.loadNuxtConfig(argv)
|
||||
expect(options.testOption).not.toBeDefined()
|
||||
|
||||
expect(consola.fatal).toHaveBeenCalledTimes(1)
|
||||
expect(consola.fatal).toHaveBeenCalledWith(expect.stringMatching(/Could not load config file/))
|
||||
})
|
||||
|
||||
test('loadNuxtConfig: async config-file', async () => {
|
||||
const argv = {
|
||||
_: [__dirname],
|
||||
'config-file': '../fixtures/nuxt.async-config.js',
|
||||
hostname: 'async-host',
|
||||
port: 3002,
|
||||
'unix-socket': '/var/run/async.sock'
|
||||
}
|
||||
|
||||
const options = await utils.loadNuxtConfig(argv)
|
||||
expect(options.testOption).toBe(true)
|
||||
expect(options.mode).toBe('supercharged')
|
||||
expect(options.server.host).toBe('async-host')
|
||||
expect(options.server.port).toBe(3002)
|
||||
expect(options.server.socket).toBe('/var/run/async.sock')
|
||||
})
|
||||
|
||||
test('loadNuxtConfig: async config-file with error', async () => {
|
||||
const argv = {
|
||||
_: [__dirname],
|
||||
'config-file': '../fixtures/nuxt.async-error.js'
|
||||
}
|
||||
|
||||
const options = await utils.loadNuxtConfig(argv)
|
||||
expect(options.testOption).not.toBeDefined()
|
||||
|
||||
expect(consola.error).toHaveBeenCalledTimes(1)
|
||||
expect(consola.error).toHaveBeenCalledWith(new Error('Async Config Error'))
|
||||
expect(consola.fatal).toHaveBeenCalledWith('Error while fetching async configuration')
|
||||
})
|
||||
|
||||
test('loadNuxtConfig: server env', async () => {
|
||||
const env = process.env
|
||||
|
||||
process.env.HOST = 'env-host'
|
||||
process.env.PORT = 3003
|
||||
process.env.UNIX_SOCKET = '/var/run/env.sock'
|
||||
|
||||
const argv = {
|
||||
_: [__dirname],
|
||||
'config-file': '../fixtures/nuxt.config.js'
|
||||
}
|
||||
|
||||
const options = await utils.loadNuxtConfig(argv)
|
||||
expect(options.server.host).toBe('env-host')
|
||||
expect(options.server.port).toBe('3003')
|
||||
expect(options.server.socket).toBe('/var/run/env.sock')
|
||||
|
||||
process.env = env
|
||||
})
|
||||
|
||||
test('indent', () => {
|
||||
expect(utils.indent(4)).toBe(' ')
|
||||
})
|
||||
|
||||
test('indent custom char', () => {
|
||||
expect(utils.indent(4, '-')).toBe('----')
|
||||
})
|
||||
})
|
8
packages/cli/test/utils/index.js
Normal file
8
packages/cli/test/utils/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import consola from 'consola'
|
||||
export * from './mocking'
|
||||
|
||||
jest.mock('consola')
|
||||
|
||||
export {
|
||||
consola
|
||||
}
|
96
packages/cli/test/utils/mocking.js
Normal file
96
packages/cli/test/utils/mocking.js
Normal file
@ -0,0 +1,96 @@
|
||||
import Command from '../../src/command'
|
||||
|
||||
export const mockGetNuxt = (options, implementation) => {
|
||||
Command.prototype.getNuxt = jest.fn().mockImplementationOnce(() => {
|
||||
return Object.assign({
|
||||
hook: jest.fn(),
|
||||
options
|
||||
}, implementation || {})
|
||||
})
|
||||
}
|
||||
|
||||
export const mockGetBuilder = (ret) => {
|
||||
const build = jest.fn().mockImplementationOnce(() => {
|
||||
return ret
|
||||
})
|
||||
|
||||
Command.prototype.getBuilder = jest.fn().mockImplementationOnce(() => {
|
||||
return { build }
|
||||
})
|
||||
|
||||
return build
|
||||
}
|
||||
|
||||
export const mockGetGenerator = (ret) => {
|
||||
const generate = jest.fn()
|
||||
if (ret) {
|
||||
generate.mockImplementationOnce(() => {
|
||||
return ret
|
||||
})
|
||||
}
|
||||
|
||||
Command.prototype.getGenerator = jest.fn().mockImplementationOnce(() => {
|
||||
return { generate }
|
||||
})
|
||||
|
||||
return generate
|
||||
}
|
||||
|
||||
export const mockGetNuxtStart = (ssr) => {
|
||||
const listen = jest.fn().mockImplementationOnce(() => {
|
||||
return Promise.resolve()
|
||||
})
|
||||
const showReady = jest.fn()
|
||||
|
||||
mockGetNuxt({
|
||||
rootDir: '.',
|
||||
render: {
|
||||
ssr
|
||||
}
|
||||
}, {
|
||||
listen,
|
||||
showReady
|
||||
})
|
||||
|
||||
return { listen, showReady }
|
||||
}
|
||||
|
||||
export const mockGetNuxtConfig = () => {
|
||||
const spy = jest.fn()
|
||||
Command.prototype.getNuxtConfig = spy
|
||||
return spy
|
||||
}
|
||||
|
||||
export const mockNuxt = (implementation) => {
|
||||
const Nuxt = function () {}
|
||||
Object.assign(Nuxt.prototype, {
|
||||
hook(type, fn) {
|
||||
if (type === 'watch:fileChanged') {
|
||||
Nuxt.fileChangedHook = fn
|
||||
}
|
||||
},
|
||||
clearHook: jest.fn(),
|
||||
close: jest.fn(),
|
||||
listen: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
showReady: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||
}, implementation || {})
|
||||
|
||||
Command.prototype.importCore = jest.fn().mockImplementationOnce(() => {
|
||||
return { Nuxt }
|
||||
})
|
||||
return Nuxt
|
||||
}
|
||||
|
||||
export const mockBuilder = (implementation) => {
|
||||
const Builder = function () {}
|
||||
Object.assign(Builder.prototype, {
|
||||
build: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
unwatch: jest.fn().mockImplementationOnce(() => Promise.resolve()),
|
||||
watchServer: jest.fn().mockImplementationOnce(() => Promise.resolve())
|
||||
}, implementation || {})
|
||||
|
||||
Command.prototype.importBuilder = jest.fn().mockImplementationOnce(() => {
|
||||
return { Builder }
|
||||
})
|
||||
return Builder
|
||||
}
|
@ -11088,6 +11088,15 @@ wrap-ansi@^3.0.1:
|
||||
string-width "^2.1.1"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
wrap-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131"
|
||||
integrity sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
string-width "^2.1.1"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
Loading…
Reference in New Issue
Block a user