feat(cli): improvements and external commands (#4314)

This commit is contained in:
Jonas Galvez 2018-12-20 09:15:48 -02:00 committed by Pooya Parsa
parent 8b366fd216
commit 0145551c3a
19 changed files with 235 additions and 304 deletions

View File

@ -1,3 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
require('../dist/cli.js').run() require('../dist/cli.js').run()
.catch((error) => {
require('consola').fatal(error)
process.exit(1)
})

View File

@ -17,6 +17,7 @@
"chalk": "^2.4.1", "chalk": "^2.4.1",
"consola": "^2.3.0", "consola": "^2.3.0",
"esm": "^3.0.84", "esm": "^3.0.84",
"execa": "^1.0.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"pretty-bytes": "^5.1.0", "pretty-bytes": "^5.1.0",
"std-env": "^2.2.1", "std-env": "^2.2.1",

View File

@ -1,66 +1,72 @@
import parseArgs from 'minimist'
import minimist from 'minimist'
import { name, version } from '../package.json' import { name, version } from '../package.json'
import { loadNuxtConfig } from './utils' import { loadNuxtConfig } from './utils'
import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting' import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting'
import * as commands from './commands'
import * as imports from './imports' import * as imports from './imports'
export default class NuxtCommand { export default class NuxtCommand {
constructor(cmd = { name: '', usage: '', description: '', options: {} }) { constructor(cmd = { name: '', usage: '', description: '' }, argv = process.argv.slice(2)) {
if (!cmd.options) {
cmd.options = {}
}
this.cmd = cmd this.cmd = cmd
this._argv = Array.from(argv)
this._parsedArgv = null // Lazy evaluate
} }
static async load(name) { static run(cmd, argv) {
if (name in commands) { return NuxtCommand.from(cmd, argv).run()
const cmd = await commands[name]() // eslint-disable-line import/namespace
.then(m => m.default)
return NuxtCommand.from(cmd)
} else {
// TODO dynamic module loading
throw new Error('Command ' + name + ' could not be loaded!')
}
} }
static from(options) { static from(cmd, argv) {
if (options instanceof NuxtCommand) { if (cmd instanceof NuxtCommand) {
return options return cmd
} }
return new NuxtCommand(options) return new NuxtCommand(cmd, argv)
} }
run() { run() {
return this.cmd.run(this) if (this.argv.help) {
this.showHelp()
return Promise.resolve()
}
if (this.argv.version) {
this.showVersion()
return Promise.resolve()
}
if (typeof this.cmd.run !== 'function') {
return Promise.resolve()
}
return Promise.resolve(this.cmd.run(this))
} }
showVersion() { showVersion() {
process.stdout.write(`${name} v${version}\n`) process.stdout.write(`${name} v${version}\n`)
process.exit(0)
} }
showHelp() { showHelp() {
process.stdout.write(this._getHelp()) process.stdout.write(this._getHelp())
process.exit(0)
} }
getArgv(args) { get argv() {
if (!this._parsedArgv) {
const minimistOptions = this._getMinimistOptions() const minimistOptions = this._getMinimistOptions()
const argv = parseArgs(args || process.argv.slice(2), minimistOptions) this._parsedArgv = minimist(this._argv, minimistOptions)
}
if (argv.version) { return this._parsedArgv
this.showVersion()
} else if (argv.help) {
this.showHelp()
} }
return argv async getNuxtConfig(extraOptions) {
} const config = await loadNuxtConfig(this.argv)
async getNuxtConfig(argv, extraOptions) {
const config = await loadNuxtConfig(argv)
const options = Object.assign(config, extraOptions || {}) const options = Object.assign(config, extraOptions || {})
for (const name of Object.keys(this.cmd.options)) { for (const name of Object.keys(this.cmd.options)) {
this.cmd.options[name].prepare && this.cmd.options[name].prepare(this, options, argv) this.cmd.options[name].prepare && this.cmd.options[name].prepare(this, options, this.argv)
} }
return options return options

View File

@ -1,4 +1,3 @@
import consola from 'consola'
import { common } from '../options' import { common } from '../options'
export default { export default {
@ -50,37 +49,17 @@ export default {
} }
}, },
async run(cmd) { async run(cmd) {
const argv = cmd.getArgv() const config = await cmd.getNuxtConfig({ dev: false })
const nuxt = await cmd.getNuxt(config)
// Create production build when calling `nuxt build` (dev: false) if (nuxt.options.mode !== 'spa' || cmd.argv.generate === false) {
const nuxt = await cmd.getNuxt(
await cmd.getNuxtConfig(argv, { dev: false })
)
let builderOrGenerator
if (nuxt.options.mode !== 'spa' || argv.generate === false) {
// Build only // Build only
builderOrGenerator = (await cmd.getBuilder(nuxt)).build() const builder = await cmd.getBuilder(nuxt)
await builder.build()
} else { } else {
// Build + Generate for static deployment // Build + Generate for static deployment
builderOrGenerator = (await cmd.getGenerator(nuxt)).generate({ const generator = await cmd.getGenerator(nuxt)
build: true await generator.generate({ build: true })
}) }
}
return builderOrGenerator
.then(() => {
// In analyze mode wait for plugin
// emitting assets and opening browser
if (
nuxt.options.build.analyze === true ||
typeof nuxt.options.build.analyze === 'object'
) {
return
}
process.exit(0)
})
.catch(err => consola.fatal(err))
} }
} }

View File

@ -13,7 +13,7 @@ export default {
}, },
async run(cmd) { async run(cmd) {
const argv = cmd.getArgv() const argv = cmd.argv
await this.startDev(cmd, argv) await this.startDev(cmd, argv)
}, },
@ -26,10 +26,7 @@ export default {
}, },
async _startDev(cmd, argv) { async _startDev(cmd, argv) {
// Load config const config = await cmd.getNuxtConfig({ dev: true })
const config = await cmd.getNuxtConfig(argv, { dev: true })
// Initialize nuxt instance
const nuxt = await cmd.getNuxt(config) const nuxt = await cmd.getNuxt(config)
// Setup hooks // Setup hooks

View File

@ -1,4 +1,3 @@
import consola from 'consola'
import { common } from '../options' import { common } from '../options'
import { normalizeArg } from '../utils' import { normalizeArg } from '../utils'
@ -36,19 +35,13 @@ export default {
} }
}, },
async run(cmd) { async run(cmd) {
const argv = cmd.getArgv() const config = await cmd.getNuxtConfig({ dev: false })
const nuxt = await cmd.getNuxt(config)
const generator = await cmd.getGenerator(nuxt)
const generator = await cmd.getGenerator( await generator.generate({
await cmd.getNuxt(
await cmd.getNuxtConfig(argv, { dev: false })
)
)
return generator.generate({
init: true, init: true,
build: argv.build build: cmd.argv.build
}).then(() => { })
process.exit(0)
}).catch(err => consola.fatal(err))
} }
} }

View File

@ -1,20 +1,25 @@
import consola from 'consola' import consola from 'consola'
import NuxtCommand from '../command' import getCommand from '../commands'
import listCommands from '../list' import listCommands from '../list'
import { common } from '../options'
import NuxtCommand from '../command'
export default { export default {
name: 'help', name: 'help',
description: 'Shows help for <command>', description: 'Shows help for <command>',
usage: 'help <command>', usage: 'help <command>',
options: {
help: common.help,
version: common.version
},
async run(cmd) { async run(cmd) {
const argv = cmd.getArgv()._ const name = cmd._argv[0]
const name = argv[0] || null
if (!name) { if (!name) {
return listCommands().then(() => process.exit(0)) return listCommands()
} }
const command = await NuxtCommand.load(name) const command = await getCommand(name)
if (command) { if (command) {
command.showHelp() NuxtCommand.from(command).showHelp()
} else { } else {
consola.info(`Unknown command: ${name}`) consola.info(`Unknown command: ${name}`)
} }

View File

@ -1,5 +1,14 @@
export const start = () => import('./start') const commands = {
export const dev = () => import('./dev') start: () => import('./start'),
export const build = () => import('./build') dev: () => import('./dev'),
export const generate = () => import('./generate') build: () => import('./build'),
export const help = () => import('./help') generate: () => import('./generate'),
help: () => import('./help')
}
export default function getCommand(name) {
if (!commands[name]) {
return Promise.resolve(null)
}
return commands[name]().then(m => m.default)
}

View File

@ -10,16 +10,11 @@ export default {
...server ...server
}, },
async run(cmd) { async run(cmd) {
const argv = cmd.getArgv() const config = await cmd.getNuxtConfig({ dev: false, _start: true })
const nuxt = await cmd.getNuxt(config)
// Create production build when calling `nuxt build`
const nuxt = await cmd.getNuxt(
await cmd.getNuxtConfig(argv, { dev: false, _start: true })
)
// Listen and show ready banner // Listen and show ready banner
return nuxt.server.listen().then(() => { await nuxt.server.listen()
showBanner(nuxt) showBanner(nuxt)
})
} }
} }

View File

@ -1,13 +1,13 @@
import chalk from 'chalk' import chalk from 'chalk'
import NuxtCommand from './command'
import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting' import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting'
import getCommand from './commands'
export default async function listCommands() { export default async function listCommands() {
const commandsOrder = ['dev', 'build', 'generate', 'start', 'help'] const commandsOrder = ['dev', 'build', 'generate', 'start', 'help']
// Load all commands // Load all commands
const _commands = await Promise.all( const _commands = await Promise.all(
commandsOrder.map(cmd => NuxtCommand.load(cmd)) commandsOrder.map(cmd => getCommand(cmd))
) )
let maxLength = 0 let maxLength = 0

View File

@ -28,6 +28,7 @@ export default {
} }
}, },
version: { version: {
alias: 'v',
type: 'boolean', type: 'boolean',
description: 'Display the Nuxt version' description: 'Display the Nuxt version'
}, },

View File

@ -1,31 +1,42 @@
import consola from 'consola' import fs from 'fs'
import execa from 'execa'
import NuxtCommand from './command' import NuxtCommand from './command'
import * as commands from './commands'
import setup from './setup' import setup from './setup'
import listCommands from './list' import getCommand from './commands'
export default function run() { export default async function run(_argv) {
const defaultCommand = 'dev' // Read from process.argv
let cmd = process.argv[2] const argv = _argv ? Array.from(_argv) : process.argv.slice(2)
if (commands[cmd]) { // eslint-disable-line import/namespace // Check for internal command
process.argv.splice(2, 1) let cmd = await getCommand(argv[0])
// Matching `nuxt` or `nuxt [dir]` or `nuxt -*` for `nuxt dev` shortcut
if (!cmd && (!argv[0] || argv[0][0] === '-' || fs.existsSync(argv[0]))) {
argv.unshift('dev')
cmd = await getCommand('dev')
}
// Setup env
setup({ dev: argv[0] === 'dev' })
// Try internal command
if (cmd) {
return NuxtCommand.run(cmd, argv.slice(1))
}
// Try external command
try {
await execa(`nuxt-${argv[0]}`, argv.slice(1), {
stdout: process.stdout,
stderr: process.stderr,
stdin: process.stdin
})
} catch (error) {
if (error.code === 'ENOENT') {
throw String(`Command not found: nuxt-${argv[0]}`)
} else { } else {
if (process.argv.includes('--help') || process.argv.includes('-h')) { throw String(`Failed to run command \`nuxt-${argv[0]}\`:\n${error}`)
listCommands().then(() => process.exit(0))
return
} }
cmd = defaultCommand
} }
// Setup runtime
setup({
dev: cmd === 'dev'
})
return NuxtCommand.load(cmd)
.then(command => command.run())
.catch((error) => {
consola.fatal(error)
})
} }

View File

@ -9,7 +9,7 @@ import chalk from 'chalk'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import env from 'std-env' import env from 'std-env'
const _require = esm(module, { export const requireModule = esm(module, {
cache: false, cache: false,
cjs: { cjs: {
cache: true, cache: true,
@ -35,7 +35,7 @@ export async function loadNuxtConfig(argv) {
if (existsSync(nuxtConfigFile)) { if (existsSync(nuxtConfigFile)) {
delete require.cache[nuxtConfigFile] delete require.cache[nuxtConfigFile]
options = _require(nuxtConfigFile) || {} options = requireModule(nuxtConfigFile) || {}
if (options.default) { if (options.default) {
options = options.default options = options.default
} }
@ -120,6 +120,13 @@ export function showBanner(nuxt) {
process.stdout.write(box + '\n') process.stdout.write(box + '\n')
} }
export function formatPath(filePath) {
if (!filePath) {
return
}
return filePath.replace(process.cwd() + path.sep, '')
}
/** /**
* Normalize string argument in command * Normalize string argument in command
* *
@ -137,10 +144,3 @@ export function normalizeArg(arg, defaultValue) {
} }
return arg return arg
} }
export function formatPath(filePath) {
if (!filePath) {
return
}
return filePath.replace(process.cwd() + path.sep, '')
}

View File

@ -11,7 +11,7 @@ exports[`cli/command builds help text 1`] = `
--universal, -u Launch in Universal mode (default) --universal, -u Launch in Universal mode (default)
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js) --config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--modern, -m Build/Start app for modern browsers, e.g. server, client and false --modern, -m Build/Start app for modern browsers, e.g. server, client and false
--version Display the Nuxt version --version, -v Display the Nuxt version
--help, -h Display this message --help, -h Display this message
--port, -p Port number on which to start the application --port, -p Port number on which to start the application
--hostname, -H Hostname on which to start the application --hostname, -H Hostname on which to start the application
@ -20,23 +20,3 @@ exports[`cli/command builds help text 1`] = `
" "
`; `;
exports[`cli/command loads command from name 1`] = `
" Usage: nuxt dev <dir> [options]
Start the application in development mode (e.g. hot-code reloading, error reporting)
Options:
--spa, -s Launch in SPA mode
--universal, -u Launch in Universal mode (default)
--config-file, -c Path to Nuxt.js config file (default: nuxt.config.js)
--modern, -m Build/Start app for modern browsers, e.g. server, client and false
--version Display the Nuxt version
--help, -h Display this message
--port, -p Port number on which to start the application
--hostname, -H Hostname on which to start the application
--unix-socket, -n Path to a UNIX socket
"
`;

View File

@ -1,4 +1,4 @@
import { consola, mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils' import { mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils'
describe('build', () => { describe('build', () => {
let build let build
@ -8,7 +8,6 @@ describe('build', () => {
jest.spyOn(process, 'exit').mockImplementation(code => code) jest.spyOn(process, 'exit').mockImplementation(code => code)
}) })
afterAll(() => process.exit.mockRestore())
afterEach(() => jest.resetAllMocks()) afterEach(() => jest.resetAllMocks())
test('has run function', () => { test('has run function', () => {
@ -41,7 +40,6 @@ describe('build', () => {
await NuxtCommand.from(build).run() await NuxtCommand.from(build).run()
expect(generate).toHaveBeenCalled() expect(generate).toHaveBeenCalled()
expect(process.exit).toHaveBeenCalled()
}) })
test('build with devtools', async () => { test('build with devtools', async () => {
@ -50,12 +48,9 @@ describe('build', () => {
}) })
const builder = mockGetBuilder(Promise.resolve()) const builder = mockGetBuilder(Promise.resolve())
const cmd = NuxtCommand.from(build) const cmd = NuxtCommand.from(build, ['build', '.', '--devtools'])
const args = ['build', '.', '--devtools']
const argv = cmd.getArgv(args)
argv._ = ['.']
const options = await cmd.getNuxtConfig(argv) const options = await cmd.getNuxtConfig(cmd.argv)
await cmd.run() await cmd.run()
@ -69,22 +64,12 @@ describe('build', () => {
}) })
mockGetBuilder(Promise.resolve()) mockGetBuilder(Promise.resolve())
const cmd = NuxtCommand.from(build) const cmd = NuxtCommand.from(build, ['build', '.', '--m'])
const args = ['build', '.', '--m']
const options = await cmd.getNuxtConfig(cmd.getArgv(args)) const options = await cmd.getNuxtConfig()
await cmd.run() await cmd.run()
expect(options.modern).toBe(true) expect(options.modern).toBe(true)
}) })
test('catches error', async () => {
mockGetNuxt({ mode: 'universal' })
mockGetBuilder(Promise.reject(new Error('Builder Error')))
await NuxtCommand.from(build).run()
expect(consola.fatal).toHaveBeenCalledWith(new Error('Builder Error'))
})
}) })

View File

@ -1,87 +1,41 @@
import { readdir } from 'fs'
import { resolve } from 'path'
import { promisify } from 'util'
import { consola } from '../utils'
import { run } from '../../src' import { run } from '../../src'
import * as commands from '../../src/commands' import getCommand from '../../src/commands'
const readDir = promisify(readdir)
jest.mock('../../src/commands') jest.mock('../../src/commands')
describe('cli', () => { describe('cli', () => {
afterEach(() => jest.resetAllMocks()) 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)
const cmdFn = commands[cmd] // eslint-disable-line import/namespace
expect(cmdFn).toBeDefined()
expect(typeof cmdFn).toBe('function')
}
})
test('calls expected method', async () => { test('calls expected method', async () => {
const argv = process.argv const mockedCommand = {
process.argv = ['', '', 'dev'] run: jest.fn().mockImplementation(() => Promise.resolve({}))
const defaultExport = {
run: jest.fn().mockImplementation(() => Promise.resolve())
} }
commands.dev.mockImplementationOnce(() => Promise.resolve({ default: defaultExport })) getCommand.mockImplementationOnce(() => Promise.resolve(mockedCommand))
await run() await run()
expect(mockedCommand.run).toHaveBeenCalled()
expect(defaultExport.run).toHaveBeenCalled()
process.argv = argv
})
test('unknown calls default method', async () => {
const argv = process.argv
process.argv = ['', '', 'test']
commands.dev.mockImplementationOnce(() => Promise.resolve())
await run()
expect(commands.dev).toHaveBeenCalled()
process.argv = argv
}) })
test('sets NODE_ENV=development for dev', async () => { test('sets NODE_ENV=development for dev', async () => {
const nodeEnv = process.env.NODE_ENV const nodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = '' process.env.NODE_ENV = ''
commands.dev.mockImplementationOnce(() => Promise.resolve())
await run() getCommand.mockImplementationOnce(() => Promise.resolve({}))
await run(['dev'])
expect(process.env.NODE_ENV).toBe('development') expect(process.env.NODE_ENV).toBe('development')
process.env.NODE_ENV = nodeEnv process.env.NODE_ENV = nodeEnv
}) })
test('sets NODE_ENV=production for build', async () => { test('sets NODE_ENV=production for build', async () => {
const argv = process.argv
const nodeEnv = process.env.NODE_ENV const nodeEnv = process.env.NODE_ENV
process.argv = ['', '', 'build']
process.env.NODE_ENV = '' process.env.NODE_ENV = ''
commands.build.mockImplementationOnce(() => Promise.resolve())
await run() getCommand.mockImplementationOnce(() => Promise.resolve({}))
await run(['', '', 'build'])
expect(process.env.NODE_ENV).toBe('production') expect(process.env.NODE_ENV).toBe('production')
process.argv = argv
process.env.NODE_ENV = nodeEnv process.env.NODE_ENV = nodeEnv
}) })
test('catches fatal error', async () => {
commands.dev.mockImplementationOnce(() => Promise.reject(new Error('Command Error')))
await run()
expect(consola.fatal).toHaveBeenCalledWith(new Error('Command Error'))
})
}) })

View File

@ -25,50 +25,38 @@ describe('cli/command', () => {
}) })
test('parses args', () => { test('parses args', () => {
const cmd = new Command({ options: { ...common, ...server } }) const argv = ['-c', 'test-file', '-s', '-p', '3001']
const cmd = new Command({ options: { ...common, ...server } }, argv)
let args = ['-c', 'test-file', '-s', '-p', '3001'] expect(cmd.argv['config-file']).toBe(argv[1])
let argv = cmd.getArgv(args) expect(cmd.argv.spa).toBe(true)
expect(cmd.argv.universal).toBe(false)
expect(cmd.argv.port).toBe('3001')
expect(argv['config-file']).toBe(args[1]) const cmd2 = new Command({ options: { ...common, ...server } }, ['--no-build'])
expect(argv.spa).toBe(true) expect(cmd2.argv.build).toBe(false)
expect(argv.universal).toBe(false)
expect(argv.port).toBe('3001')
args = ['--no-build']
argv = cmd.getArgv(args)
expect(argv.build).toBe(false)
}) })
test('prints version automatically', () => { test('prints version automatically', async () => {
const cmd = new Command() const cmd = new Command({}, ['--version'])
cmd.showVersion = jest.fn() cmd.showVersion = jest.fn()
await cmd.run()
const args = ['--version']
cmd.getArgv(args)
expect(cmd.showVersion).toHaveBeenCalledTimes(1) expect(cmd.showVersion).toHaveBeenCalledTimes(1)
}) })
test('prints help automatically', () => { test('prints help automatically', async () => {
const cmd = new Command({ options: allOptions }) const cmd = new Command({ options: allOptions }, ['-h'])
cmd.showHelp = jest.fn() cmd.showHelp = jest.fn()
await cmd.run()
const args = ['-h']
cmd.getArgv(args)
expect(cmd.showHelp).toHaveBeenCalledTimes(1) expect(cmd.showHelp).toHaveBeenCalledTimes(1)
}) })
test('returns nuxt config', async () => { test('returns nuxt config', async () => {
const cmd = new Command({ options: allOptions }) const cmd = new Command({ options: allOptions }, ['-c', 'test-file', '-a', '-p', '3001', '-q', '-H'])
const args = ['-c', 'test-file', '-a', '-p', '3001', '-q', '-H'] const options = await cmd.getNuxtConfig({ testOption: true })
const argv = cmd.getArgv(args)
argv._ = ['.']
const options = await cmd.getNuxtConfig(argv, { testOption: true })
expect(options.testOption).toBe(true) expect(options.testOption).toBe(true)
expect(options.server.port).toBe(3001) expect(options.server.port).toBe(3001)
@ -117,36 +105,19 @@ describe('cli/command', () => {
expect(cmd._getHelp()).toMatchSnapshot() expect(cmd._getHelp()).toMatchSnapshot()
}) })
test('loads command from name', async () => {
const cmd = await Command.load('dev')
expect(cmd._getHelp()).toMatchSnapshot()
})
test('show version prints to stdout and exits', () => { test('show version prints to stdout and exits', () => {
jest.spyOn(process.stdout, 'write').mockImplementation(() => {}) jest.spyOn(process.stdout, 'write').mockImplementation(() => {})
jest.spyOn(process, 'exit').mockImplementationOnce(code => code)
const cmd = new Command() const cmd = new Command()
cmd.showVersion() cmd.showVersion()
expect(process.stdout.write).toHaveBeenCalled() expect(process.stdout.write).toHaveBeenCalled()
expect(process.exit).toHaveBeenCalled()
process.stdout.write.mockRestore() process.stdout.write.mockRestore()
process.exit.mockRestore()
}) })
test('show help prints to stdout and exits', () => { test('show help prints to stdout and exits', () => {
jest.spyOn(process.stdout, 'write').mockImplementation(() => {}) jest.spyOn(process.stdout, 'write').mockImplementation(() => {})
jest.spyOn(process, 'exit').mockImplementationOnce(code => code)
const cmd = new Command() const cmd = new Command()
cmd.showHelp() cmd.showHelp()
expect(process.stdout.write).toHaveBeenCalled() expect(process.stdout.write).toHaveBeenCalled()
expect(process.exit).toHaveBeenCalled()
process.stdout.write.mockRestore() process.stdout.write.mockRestore()
process.exit.mockRestore()
}) })
}) })

View File

@ -1,5 +1,4 @@
import { consola, mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils' import { mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils'
import Command from '../../src/command'
describe('generate', () => { describe('generate', () => {
let generate let generate
@ -9,7 +8,6 @@ describe('generate', () => {
jest.spyOn(process, 'exit').mockImplementation(code => code) jest.spyOn(process, 'exit').mockImplementation(code => code)
}) })
afterAll(() => process.exit.mockRestore())
afterEach(() => jest.resetAllMocks()) afterEach(() => jest.resetAllMocks())
test('has run function', () => { test('has run function', () => {
@ -28,34 +26,21 @@ describe('generate', () => {
test('doesnt build with no-build', async () => { test('doesnt build with no-build', async () => {
mockGetNuxt() 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()) const generator = mockGetGenerator(Promise.resolve())
await NuxtCommand.from(generate).run() await NuxtCommand.run(generate, ['generate', '.', '--no-build'])
expect(generator).toHaveBeenCalled() expect(generator).toHaveBeenCalled()
expect(generator.mock.calls[0][0].build).toBe(false) expect(generator.mock.calls[0][0].build).toBe(false)
Command.prototype.getArgv = getArgv
}) })
test('build with devtools', async () => { test('build with devtools', async () => {
mockGetNuxt() mockGetNuxt()
const generator = mockGetGenerator(Promise.resolve()) const generator = mockGetGenerator(Promise.resolve())
const cmd = NuxtCommand.from(generate) const cmd = NuxtCommand.from(generate, ['generate', '.', '--devtools'])
const args = ['generate', '.', '--devtools']
const argv = cmd.getArgv(args)
argv._ = ['.']
const options = await cmd.getNuxtConfig(argv) const options = await cmd.getNuxtConfig()
await cmd.run() await cmd.run()
@ -68,22 +53,12 @@ describe('generate', () => {
mockGetNuxt() mockGetNuxt()
mockGetGenerator(Promise.resolve()) mockGetGenerator(Promise.resolve())
const cmd = NuxtCommand.from(generate) const cmd = NuxtCommand.from(generate, ['generate', '.', '--m'])
const args = ['generate', '.', '--m']
const options = await cmd.getNuxtConfig(cmd.getArgv(args)) const options = await cmd.getNuxtConfig()
await cmd.run() await cmd.run()
expect(options.modern).toBe('client') expect(options.modern).toBe('client')
}) })
test('catches error', async () => {
mockGetNuxt()
mockGetGenerator(Promise.reject(new Error('Generator Error')))
await NuxtCommand.from(generate).run()
expect(consola.fatal).toHaveBeenCalledWith(new Error('Generator Error'))
})
}) })

View File

@ -0,0 +1,65 @@
import execa from 'execa'
import run from '../../src/run'
import getCommand from '../../src/commands'
import NuxtCommand from '../../src/command'
jest.mock('execa')
jest.mock('../../src/commands')
jest.mock('../../src/command')
describe('run', () => {
beforeEach(() => {
jest.resetAllMocks()
getCommand.mockImplementation(cmd => cmd === 'dev' ? ({ name: 'dev', run: jest.fn() }) : undefined)
})
afterAll(() => {
jest.clearAllMocks()
})
test('nuxt aliases to nuxt dev', async () => {
await run([])
expect(getCommand).toHaveBeenCalledWith('dev')
expect(NuxtCommand.run).toHaveBeenCalledWith(expect.anything(), [])
})
test('nuxt --foo aliases to nuxt dev --foo', async () => {
await run(['--foo'])
expect(getCommand).toHaveBeenCalledWith('dev')
expect(NuxtCommand.run).toHaveBeenCalledWith(expect.anything(), ['--foo'])
})
test('nuxt <dir> aliases to nuxt dev <dir>', async () => {
await run([__dirname])
expect(getCommand).toHaveBeenCalledWith('dev')
expect(NuxtCommand.run).toHaveBeenCalledWith(expect.anything(), [__dirname])
})
test('external commands', async () => {
await run(['custom', 'command', '--args'])
expect(execa).toHaveBeenCalledWith('nuxt-custom', ['command', '--args'], {
stdout: process.stdout,
stderr: process.stderr,
stdin: process.stdin
})
})
test('throws error if external command not found', async () => {
execa.mockImplementationOnce(() => {
const e = new Error()
e.code = 'ENOENT'
throw e
})
await expect(run(['custom', 'command', '--args']))
.rejects.toBe('Command not found: nuxt-custom')
})
test('throws error if external command failed', async () => {
execa.mockImplementationOnce(() => { throw new Error('boo') })
await expect(run(['custom', 'command', '--args']))
.rejects.toBe('Failed to run command `nuxt-custom`:\nError: boo')
})
})