From 0145551c3ad1c55593b23d4148908e97cc21bebe Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Thu, 20 Dec 2018 09:15:48 -0200 Subject: [PATCH] feat(cli): improvements and external commands (#4314) --- packages/cli/bin/nuxt-cli.js | 4 ++ packages/cli/package.json | 1 + packages/cli/src/command.js | 70 ++++++++++--------- packages/cli/src/commands/build.js | 35 ++-------- packages/cli/src/commands/dev.js | 7 +- packages/cli/src/commands/generate.js | 19 ++--- packages/cli/src/commands/help.js | 17 +++-- packages/cli/src/commands/index.js | 19 +++-- packages/cli/src/commands/start.js | 13 ++-- packages/cli/src/list.js | 4 +- packages/cli/src/options/common.js | 1 + packages/cli/src/run.js | 55 +++++++++------ packages/cli/src/utils/index.js | 18 ++--- .../unit/__snapshots__/command.test.js.snap | 22 +----- packages/cli/test/unit/build.test.js | 25 ++----- packages/cli/test/unit/cli.test.js | 66 +++-------------- packages/cli/test/unit/command.test.js | 61 +++++----------- packages/cli/test/unit/generate.test.js | 37 ++-------- packages/cli/test/unit/run.test.js | 65 +++++++++++++++++ 19 files changed, 235 insertions(+), 304 deletions(-) create mode 100644 packages/cli/test/unit/run.test.js diff --git a/packages/cli/bin/nuxt-cli.js b/packages/cli/bin/nuxt-cli.js index 9876a2055f..1425f944ca 100755 --- a/packages/cli/bin/nuxt-cli.js +++ b/packages/cli/bin/nuxt-cli.js @@ -1,3 +1,7 @@ #!/usr/bin/env node require('../dist/cli.js').run() + .catch((error) => { + require('consola').fatal(error) + process.exit(1) + }) diff --git a/packages/cli/package.json b/packages/cli/package.json index c4a00c9788..bb2d4467c9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -17,6 +17,7 @@ "chalk": "^2.4.1", "consola": "^2.3.0", "esm": "^3.0.84", + "execa": "^1.0.0", "minimist": "^1.2.0", "pretty-bytes": "^5.1.0", "std-env": "^2.2.1", diff --git a/packages/cli/src/command.js b/packages/cli/src/command.js index 02af3da8ba..e93d7a3957 100644 --- a/packages/cli/src/command.js +++ b/packages/cli/src/command.js @@ -1,66 +1,72 @@ -import parseArgs from 'minimist' + +import minimist from 'minimist' import { name, version } from '../package.json' import { loadNuxtConfig } from './utils' import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting' -import * as commands from './commands' import * as imports from './imports' 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._argv = Array.from(argv) + this._parsedArgv = null // Lazy evaluate } - static async load(name) { - if (name in commands) { - 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 run(cmd, argv) { + return NuxtCommand.from(cmd, argv).run() } - static from(options) { - if (options instanceof NuxtCommand) { - return options + static from(cmd, argv) { + if (cmd instanceof NuxtCommand) { + return cmd } - return new NuxtCommand(options) + return new NuxtCommand(cmd, argv) } 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() { process.stdout.write(`${name} v${version}\n`) - process.exit(0) } showHelp() { process.stdout.write(this._getHelp()) - process.exit(0) } - 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() + get argv() { + if (!this._parsedArgv) { + const minimistOptions = this._getMinimistOptions() + this._parsedArgv = minimist(this._argv, minimistOptions) } - - return argv + return this._parsedArgv } - async getNuxtConfig(argv, extraOptions) { - const config = await loadNuxtConfig(argv) + async getNuxtConfig(extraOptions) { + const config = await loadNuxtConfig(this.argv) const options = Object.assign(config, extraOptions || {}) 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 diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 3a0cd53e25..383c20ca81 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -1,4 +1,3 @@ -import consola from 'consola' import { common } from '../options' export default { @@ -50,37 +49,17 @@ export default { } }, 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) - const nuxt = await cmd.getNuxt( - await cmd.getNuxtConfig(argv, { dev: false }) - ) - - let builderOrGenerator - if (nuxt.options.mode !== 'spa' || argv.generate === false) { + if (nuxt.options.mode !== 'spa' || cmd.argv.generate === false) { // Build only - builderOrGenerator = (await cmd.getBuilder(nuxt)).build() + const builder = await cmd.getBuilder(nuxt) + await builder.build() } else { // Build + Generate for static deployment - builderOrGenerator = (await cmd.getGenerator(nuxt)).generate({ - build: true - }) + const generator = await cmd.getGenerator(nuxt) + 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)) } } diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js index e598338782..69da33c6bf 100644 --- a/packages/cli/src/commands/dev.js +++ b/packages/cli/src/commands/dev.js @@ -13,7 +13,7 @@ export default { }, async run(cmd) { - const argv = cmd.getArgv() + const argv = cmd.argv await this.startDev(cmd, argv) }, @@ -26,10 +26,7 @@ export default { }, async _startDev(cmd, argv) { - // Load config - const config = await cmd.getNuxtConfig(argv, { dev: true }) - - // Initialize nuxt instance + const config = await cmd.getNuxtConfig({ dev: true }) const nuxt = await cmd.getNuxt(config) // Setup hooks diff --git a/packages/cli/src/commands/generate.js b/packages/cli/src/commands/generate.js index ef026d9689..2de34cae2d 100644 --- a/packages/cli/src/commands/generate.js +++ b/packages/cli/src/commands/generate.js @@ -1,4 +1,3 @@ -import consola from 'consola' import { common } from '../options' import { normalizeArg } from '../utils' @@ -36,19 +35,13 @@ export default { } }, 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 cmd.getNuxt( - await cmd.getNuxtConfig(argv, { dev: false }) - ) - ) - - return generator.generate({ + await generator.generate({ init: true, - build: argv.build - }).then(() => { - process.exit(0) - }).catch(err => consola.fatal(err)) + build: cmd.argv.build + }) } } diff --git a/packages/cli/src/commands/help.js b/packages/cli/src/commands/help.js index 4316dbb1c7..0fa3b10850 100644 --- a/packages/cli/src/commands/help.js +++ b/packages/cli/src/commands/help.js @@ -1,20 +1,25 @@ import consola from 'consola' -import NuxtCommand from '../command' +import getCommand from '../commands' import listCommands from '../list' +import { common } from '../options' +import NuxtCommand from '../command' export default { name: 'help', description: 'Shows help for ', usage: 'help ', + options: { + help: common.help, + version: common.version + }, async run(cmd) { - const argv = cmd.getArgv()._ - const name = argv[0] || null + const name = cmd._argv[0] if (!name) { - return listCommands().then(() => process.exit(0)) + return listCommands() } - const command = await NuxtCommand.load(name) + const command = await getCommand(name) if (command) { - command.showHelp() + NuxtCommand.from(command).showHelp() } else { consola.info(`Unknown command: ${name}`) } diff --git a/packages/cli/src/commands/index.js b/packages/cli/src/commands/index.js index 70ad510a39..95c1fbcd71 100644 --- a/packages/cli/src/commands/index.js +++ b/packages/cli/src/commands/index.js @@ -1,5 +1,14 @@ -export const start = () => import('./start') -export const dev = () => import('./dev') -export const build = () => import('./build') -export const generate = () => import('./generate') -export const help = () => import('./help') +const commands = { + start: () => import('./start'), + dev: () => import('./dev'), + build: () => import('./build'), + 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) +} diff --git a/packages/cli/src/commands/start.js b/packages/cli/src/commands/start.js index 602eb53cd5..fc78d89620 100644 --- a/packages/cli/src/commands/start.js +++ b/packages/cli/src/commands/start.js @@ -10,16 +10,11 @@ export default { ...server }, async run(cmd) { - const argv = cmd.getArgv() - - // Create production build when calling `nuxt build` - const nuxt = await cmd.getNuxt( - await cmd.getNuxtConfig(argv, { dev: false, _start: true }) - ) + const config = await cmd.getNuxtConfig({ dev: false, _start: true }) + const nuxt = await cmd.getNuxt(config) // Listen and show ready banner - return nuxt.server.listen().then(() => { - showBanner(nuxt) - }) + await nuxt.server.listen() + showBanner(nuxt) } } diff --git a/packages/cli/src/list.js b/packages/cli/src/list.js index 26200756cd..f61b43f325 100644 --- a/packages/cli/src/list.js +++ b/packages/cli/src/list.js @@ -1,13 +1,13 @@ import chalk from 'chalk' -import NuxtCommand from './command' import { indent, foldLines, startSpaces, optionSpaces, colorize } from './utils/formatting' +import getCommand from './commands' export default async function listCommands() { const commandsOrder = ['dev', 'build', 'generate', 'start', 'help'] // Load all commands const _commands = await Promise.all( - commandsOrder.map(cmd => NuxtCommand.load(cmd)) + commandsOrder.map(cmd => getCommand(cmd)) ) let maxLength = 0 diff --git a/packages/cli/src/options/common.js b/packages/cli/src/options/common.js index bab7c20c24..d42878ae8d 100644 --- a/packages/cli/src/options/common.js +++ b/packages/cli/src/options/common.js @@ -28,6 +28,7 @@ export default { } }, version: { + alias: 'v', type: 'boolean', description: 'Display the Nuxt version' }, diff --git a/packages/cli/src/run.js b/packages/cli/src/run.js index f3d1f3ad71..aed0e52f3a 100644 --- a/packages/cli/src/run.js +++ b/packages/cli/src/run.js @@ -1,31 +1,42 @@ -import consola from 'consola' +import fs from 'fs' +import execa from 'execa' import NuxtCommand from './command' -import * as commands from './commands' import setup from './setup' -import listCommands from './list' +import getCommand from './commands' -export default function run() { - const defaultCommand = 'dev' - let cmd = process.argv[2] +export default async function run(_argv) { + // Read from process.argv + const argv = _argv ? Array.from(_argv) : process.argv.slice(2) - if (commands[cmd]) { // eslint-disable-line import/namespace - process.argv.splice(2, 1) - } else { - if (process.argv.includes('--help') || process.argv.includes('-h')) { - listCommands().then(() => process.exit(0)) - return - } - cmd = defaultCommand + // Check for internal command + 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 runtime - setup({ - dev: cmd === 'dev' - }) + // Setup env + setup({ dev: argv[0] === 'dev' }) - return NuxtCommand.load(cmd) - .then(command => command.run()) - .catch((error) => { - consola.fatal(error) + // 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 { + throw String(`Failed to run command \`nuxt-${argv[0]}\`:\n${error}`) + } + } } diff --git a/packages/cli/src/utils/index.js b/packages/cli/src/utils/index.js index 56714eea82..57535f5864 100644 --- a/packages/cli/src/utils/index.js +++ b/packages/cli/src/utils/index.js @@ -9,7 +9,7 @@ import chalk from 'chalk' import prettyBytes from 'pretty-bytes' import env from 'std-env' -const _require = esm(module, { +export const requireModule = esm(module, { cache: false, cjs: { cache: true, @@ -35,7 +35,7 @@ export async function loadNuxtConfig(argv) { if (existsSync(nuxtConfigFile)) { delete require.cache[nuxtConfigFile] - options = _require(nuxtConfigFile) || {} + options = requireModule(nuxtConfigFile) || {} if (options.default) { options = options.default } @@ -120,6 +120,13 @@ export function showBanner(nuxt) { process.stdout.write(box + '\n') } +export function formatPath(filePath) { + if (!filePath) { + return + } + return filePath.replace(process.cwd() + path.sep, '') +} + /** * Normalize string argument in command * @@ -137,10 +144,3 @@ export function normalizeArg(arg, defaultValue) { } return arg } - -export function formatPath(filePath) { - if (!filePath) { - return - } - return filePath.replace(process.cwd() + path.sep, '') -} diff --git a/packages/cli/test/unit/__snapshots__/command.test.js.snap b/packages/cli/test/unit/__snapshots__/command.test.js.snap index 0e2f57d57a..f6d1dd00cf 100644 --- a/packages/cli/test/unit/__snapshots__/command.test.js.snap +++ b/packages/cli/test/unit/__snapshots__/command.test.js.snap @@ -11,7 +11,7 @@ exports[`cli/command builds help text 1`] = ` --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 + --version, -v Display the Nuxt version --help, -h Display this message --port, -p Port number on which to start the application --hostname, -H Hostname on which to start the application @@ -20,23 +20,3 @@ exports[`cli/command builds help text 1`] = ` " `; - -exports[`cli/command loads command from name 1`] = ` -" Usage: nuxt dev [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 - -" -`; diff --git a/packages/cli/test/unit/build.test.js b/packages/cli/test/unit/build.test.js index 924fb32016..c0ee730752 100644 --- a/packages/cli/test/unit/build.test.js +++ b/packages/cli/test/unit/build.test.js @@ -1,4 +1,4 @@ -import { consola, mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils' +import { mockGetNuxt, mockGetBuilder, mockGetGenerator, NuxtCommand } from '../utils' describe('build', () => { let build @@ -8,7 +8,6 @@ describe('build', () => { jest.spyOn(process, 'exit').mockImplementation(code => code) }) - afterAll(() => process.exit.mockRestore()) afterEach(() => jest.resetAllMocks()) test('has run function', () => { @@ -41,7 +40,6 @@ describe('build', () => { await NuxtCommand.from(build).run() expect(generate).toHaveBeenCalled() - expect(process.exit).toHaveBeenCalled() }) test('build with devtools', async () => { @@ -50,12 +48,9 @@ describe('build', () => { }) const builder = mockGetBuilder(Promise.resolve()) - const cmd = NuxtCommand.from(build) - const args = ['build', '.', '--devtools'] - const argv = cmd.getArgv(args) - argv._ = ['.'] + const cmd = NuxtCommand.from(build, ['build', '.', '--devtools']) - const options = await cmd.getNuxtConfig(argv) + const options = await cmd.getNuxtConfig(cmd.argv) await cmd.run() @@ -69,22 +64,12 @@ describe('build', () => { }) mockGetBuilder(Promise.resolve()) - const cmd = NuxtCommand.from(build) - const args = ['build', '.', '--m'] + const cmd = NuxtCommand.from(build, ['build', '.', '--m']) - const options = await cmd.getNuxtConfig(cmd.getArgv(args)) + const options = await cmd.getNuxtConfig() await cmd.run() 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')) - }) }) diff --git a/packages/cli/test/unit/cli.test.js b/packages/cli/test/unit/cli.test.js index 8f597e7ba4..c1fe4c583f 100644 --- a/packages/cli/test/unit/cli.test.js +++ b/packages/cli/test/unit/cli.test.js @@ -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 * as commands from '../../src/commands' - -const readDir = promisify(readdir) +import getCommand from '../../src/commands' jest.mock('../../src/commands') 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) - - const cmdFn = commands[cmd] // eslint-disable-line import/namespace - expect(cmdFn).toBeDefined() - expect(typeof cmdFn).toBe('function') - } - }) - test('calls expected method', async () => { - const argv = process.argv - process.argv = ['', '', 'dev'] - const defaultExport = { - run: jest.fn().mockImplementation(() => Promise.resolve()) + const mockedCommand = { + run: jest.fn().mockImplementation(() => Promise.resolve({})) } - commands.dev.mockImplementationOnce(() => Promise.resolve({ default: defaultExport })) + getCommand.mockImplementationOnce(() => Promise.resolve(mockedCommand)) await run() - - 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 + expect(mockedCommand.run).toHaveBeenCalled() }) test('sets NODE_ENV=development for dev', async () => { const nodeEnv = 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') process.env.NODE_ENV = nodeEnv }) test('sets NODE_ENV=production for build', async () => { - const argv = process.argv const nodeEnv = process.env.NODE_ENV - process.argv = ['', '', 'build'] 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') - process.argv = argv + 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')) - }) }) diff --git a/packages/cli/test/unit/command.test.js b/packages/cli/test/unit/command.test.js index 237162167a..cba54c97f3 100644 --- a/packages/cli/test/unit/command.test.js +++ b/packages/cli/test/unit/command.test.js @@ -25,50 +25,38 @@ describe('cli/command', () => { }) 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'] - let argv = cmd.getArgv(args) + expect(cmd.argv['config-file']).toBe(argv[1]) + 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]) - expect(argv.spa).toBe(true) - expect(argv.universal).toBe(false) - expect(argv.port).toBe('3001') - - args = ['--no-build'] - argv = cmd.getArgv(args) - - expect(argv.build).toBe(false) + const cmd2 = new Command({ options: { ...common, ...server } }, ['--no-build']) + expect(cmd2.argv.build).toBe(false) }) - test('prints version automatically', () => { - const cmd = new Command() + test('prints version automatically', async () => { + const cmd = new Command({}, ['--version']) cmd.showVersion = jest.fn() - - const args = ['--version'] - cmd.getArgv(args) + await cmd.run() expect(cmd.showVersion).toHaveBeenCalledTimes(1) }) - test('prints help automatically', () => { - const cmd = new Command({ options: allOptions }) + test('prints help automatically', async () => { + const cmd = new Command({ options: allOptions }, ['-h']) cmd.showHelp = jest.fn() - - const args = ['-h'] - cmd.getArgv(args) + await cmd.run() expect(cmd.showHelp).toHaveBeenCalledTimes(1) }) 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 argv = cmd.getArgv(args) - argv._ = ['.'] - - const options = await cmd.getNuxtConfig(argv, { testOption: true }) + const options = await cmd.getNuxtConfig({ testOption: true }) expect(options.testOption).toBe(true) expect(options.server.port).toBe(3001) @@ -117,36 +105,19 @@ describe('cli/command', () => { 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', () => { 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/generate.test.js b/packages/cli/test/unit/generate.test.js index 9c17b35c78..b41373871e 100644 --- a/packages/cli/test/unit/generate.test.js +++ b/packages/cli/test/unit/generate.test.js @@ -1,5 +1,4 @@ -import { consola, mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils' -import Command from '../../src/command' +import { mockGetNuxt, mockGetGenerator, NuxtCommand } from '../utils' describe('generate', () => { let generate @@ -9,7 +8,6 @@ describe('generate', () => { jest.spyOn(process, 'exit').mockImplementation(code => code) }) - afterAll(() => process.exit.mockRestore()) afterEach(() => jest.resetAllMocks()) test('has run function', () => { @@ -28,34 +26,21 @@ describe('generate', () => { 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 NuxtCommand.from(generate).run() + await NuxtCommand.run(generate, ['generate', '.', '--no-build']) expect(generator).toHaveBeenCalled() expect(generator.mock.calls[0][0].build).toBe(false) - Command.prototype.getArgv = getArgv }) test('build with devtools', async () => { mockGetNuxt() const generator = mockGetGenerator(Promise.resolve()) - const cmd = NuxtCommand.from(generate) - const args = ['generate', '.', '--devtools'] - const argv = cmd.getArgv(args) - argv._ = ['.'] + const cmd = NuxtCommand.from(generate, ['generate', '.', '--devtools']) - const options = await cmd.getNuxtConfig(argv) + const options = await cmd.getNuxtConfig() await cmd.run() @@ -68,22 +53,12 @@ describe('generate', () => { mockGetNuxt() mockGetGenerator(Promise.resolve()) - const cmd = NuxtCommand.from(generate) - const args = ['generate', '.', '--m'] + const cmd = NuxtCommand.from(generate, ['generate', '.', '--m']) - const options = await cmd.getNuxtConfig(cmd.getArgv(args)) + const options = await cmd.getNuxtConfig() await cmd.run() 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')) - }) }) diff --git a/packages/cli/test/unit/run.test.js b/packages/cli/test/unit/run.test.js new file mode 100644 index 0000000000..4f60d11243 --- /dev/null +++ b/packages/cli/test/unit/run.test.js @@ -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 aliases to nuxt dev ', 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') + }) +})