diff --git a/packages/cli/bin/nuxt.js b/packages/cli/bin/nuxt.js
index 45127b7c4f..9876a2055f 100755
--- a/packages/cli/bin/nuxt.js
+++ b/packages/cli/bin/nuxt.js
@@ -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()
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 611a18a1c2..83b0dcf27b 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -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"
diff --git a/packages/cli/src/command.js b/packages/cli/src/command.js
new file mode 100644
index 0000000000..b38e8cfee1
--- /dev/null
+++ b/packages/cli/src/command.js
@@ -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)
+ }
+}
diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js
index 955362fcc3..2868b81ab8 100644
--- a/packages/cli/src/commands/build.js
+++ b/packages/cli/src/commands/build.js
@@ -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
',
+ options: [ 'analyze', 'quiet' ]
})
- if (argv.help) {
- process.stderr.write(`
- Description
- Compiles the application for production deployment
- Usage
- $ nuxt build
- 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))
}
diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js
index 1595b7873e..5779cfdbff 100644
--- a/packages/cli/src/commands/dev.js
+++ b/packages/cli/src/commands/dev.js
@@ -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 -p -H ',
+ 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 -p -H
- 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()
}
diff --git a/packages/cli/src/commands/generate.js b/packages/cli/src/commands/generate.js
index 64ab9e934d..a2d79697ed 100644
--- a/packages/cli/src/commands/generate.js
+++ b/packages/cli/src/commands/generate.js
@@ -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 ',
+ options: [ 'build' ]
})
- if (argv.help) {
- process.stderr.write(`
- Description
- Generate a static web application (server-rendered)
- Usage
- $ nuxt generate
- 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))
}
diff --git a/packages/cli/src/commands/index.js b/packages/cli/src/commands/index.js
new file mode 100644
index 0000000000..d92eb03998
--- /dev/null
+++ b/packages/cli/src/commands/index.js
@@ -0,0 +1,4 @@
+export const start = () => import('./start')
+export const dev = () => import('./dev')
+export const build = () => import('./build')
+export const generate = () => import('./generate')
diff --git a/packages/cli/src/commands/start.js b/packages/cli/src/commands/start.js
index 9d36532976..2498a06a87 100644
--- a/packages/cli/src/commands/start.js
+++ b/packages/cli/src/commands/start.js
@@ -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 -p -H ',
+ 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 -p -H
- 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))
diff --git a/packages/cli/src/imports.js b/packages/cli/src/imports.js
new file mode 100644
index 0000000000..ada97e5c5a
--- /dev/null
+++ b/packages/cli/src/imports.js
@@ -0,0 +1,3 @@
+export const builder = () => import('@nuxt/builder')
+export const generator = () => import('@nuxt/generator')
+export const core = () => import('@nuxt/core')
diff --git a/packages/cli/src/index.js b/packages/cli/src/index.js
index 6382cbe31a..5c39b23a97 100644
--- a/packages/cli/src/index.js
+++ b/packages/cli/src/index.js
@@ -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'
diff --git a/packages/cli/src/options.js b/packages/cli/src/options.js
new file mode 100644
index 0000000000..2afd911650
--- /dev/null
+++ b/packages/cli/src/options.js
@@ -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'
+ }
+}
diff --git a/packages/cli/src/run.js b/packages/cli/src/run.js
new file mode 100644
index 0000000000..97c322b53b
--- /dev/null
+++ b/packages/cli/src/run.js
@@ -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)
+ })
+}
diff --git a/packages/cli/src/setup.js b/packages/cli/src/setup.js
new file mode 100644
index 0000000000..802e7fb168
--- /dev/null
+++ b/packages/cli/src/setup.js
@@ -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)
+ }
+ }
+ })
+}
diff --git a/packages/cli/src/common/utils.js b/packages/cli/src/utils.js
similarity index 71%
rename from packages/cli/src/common/utils.js
rename to packages/cli/src/utils.js
index d989be0999..47dcc27bee 100644
--- a/packages/cli/src/common/utils.js
+++ b/packages/cli/src/utils.js
@@ -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)
+}
diff --git a/packages/cli/test/fixtures/nuxt.async-config.js b/packages/cli/test/fixtures/nuxt.async-config.js
new file mode 100644
index 0000000000..4fa271b380
--- /dev/null
+++ b/packages/cli/test/fixtures/nuxt.async-config.js
@@ -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')
+}
diff --git a/packages/cli/test/fixtures/nuxt.async-error.js b/packages/cli/test/fixtures/nuxt.async-error.js
new file mode 100644
index 0000000000..fb5f8ca711
--- /dev/null
+++ b/packages/cli/test/fixtures/nuxt.async-error.js
@@ -0,0 +1 @@
+export default () => Promise.reject(new Error('Async Config Error'))
diff --git a/packages/cli/test/fixtures/nuxt.config.js b/packages/cli/test/fixtures/nuxt.config.js
new file mode 100644
index 0000000000..7594287a99
--- /dev/null
+++ b/packages/cli/test/fixtures/nuxt.config.js
@@ -0,0 +1,10 @@
+export default {
+ testOption: true,
+ rootDir: '/some/path',
+ mode: 'supercharged',
+ server: {
+ host: 'nuxt-host',
+ port: 3001,
+ socket: '/var/run/nuxt.sock'
+ }
+}
diff --git a/packages/cli/test/unit/build.test.js b/packages/cli/test/unit/build.test.js
new file mode 100644
index 0000000000..5c501804ec
--- /dev/null
+++ b/packages/cli/test/unit/build.test.js
@@ -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'))
+ })
+})
diff --git a/packages/cli/test/unit/cli.test.js b/packages/cli/test/unit/cli.test.js
new file mode 100644
index 0000000000..8150180fa4
--- /dev/null
+++ b/packages/cli/test/unit/cli.test.js
@@ -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'))
+ })
+})
diff --git a/packages/cli/test/unit/command.test.js b/packages/cli/test/unit/command.test.js
new file mode 100644
index 0000000000..d8ff3e579f
--- /dev/null
+++ b/packages/cli/test/unit/command.test.js
@@ -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()
+ })
+})
diff --git a/packages/cli/test/unit/dev.test.js b/packages/cli/test/unit/dev.test.js
new file mode 100644
index 0000000000..a0592d5a34
--- /dev/null
+++ b/packages/cli/test/unit/dev.test.js
@@ -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'))
+ })
+})
diff --git a/packages/cli/test/unit/generate.test.js b/packages/cli/test/unit/generate.test.js
new file mode 100644
index 0000000000..42b8f0d141
--- /dev/null
+++ b/packages/cli/test/unit/generate.test.js
@@ -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'))
+ })
+})
diff --git a/packages/cli/test/unit/start.test.js b/packages/cli/test/unit/start.test.js
new file mode 100644
index 0000000000..876b50eb6b
--- /dev/null
+++ b/packages/cli/test/unit/start.test.js
@@ -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`')
+ })
+})
diff --git a/packages/cli/test/unit/utils.test.js b/packages/cli/test/unit/utils.test.js
new file mode 100644
index 0000000000..f111aeb932
--- /dev/null
+++ b/packages/cli/test/unit/utils.test.js
@@ -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('----')
+ })
+})
diff --git a/packages/cli/test/utils/index.js b/packages/cli/test/utils/index.js
new file mode 100644
index 0000000000..8c36b338fe
--- /dev/null
+++ b/packages/cli/test/utils/index.js
@@ -0,0 +1,8 @@
+import consola from 'consola'
+export * from './mocking'
+
+jest.mock('consola')
+
+export {
+ consola
+}
diff --git a/packages/cli/test/utils/mocking.js b/packages/cli/test/utils/mocking.js
new file mode 100644
index 0000000000..7427a8b8be
--- /dev/null
+++ b/packages/cli/test/utils/mocking.js
@@ -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
+}
diff --git a/yarn.lock b/yarn.lock
index 6a3473c652..c4650f501d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"