import minimist from 'minimist' import { name, version } from '../package.json' import { loadNuxtConfig, forceExit } from './utils' import { indent, foldLines, colorize } from './utils/formatting' import { startSpaces, optionSpaces, forceExitTimeout } from './utils/constants' import * as imports from './imports' export default class NuxtCommand { constructor(cmd = { name: '', usage: '', description: '' }, argv = process.argv.slice(2)) { if (!cmd.options) { cmd.options = {} } this.cmd = cmd // If the cmd is a server then dont forcibly exit when the cmd has finished this.isServer = cmd.isServer !== undefined ? cmd.isServer : Boolean(this.cmd.options.hostname) this._argv = Array.from(argv) this._parsedArgv = null // Lazy evaluate } static run(cmd, argv) { return NuxtCommand.from(cmd, argv).run() } static from(cmd, argv) { if (cmd instanceof NuxtCommand) { return cmd } return new NuxtCommand(cmd, argv) } run() { 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() } const runResolve = Promise.resolve(this.cmd.run(this)) // TODO: For v3 set timeout to 0 when force-exit === true if (!this.isServer || this.argv['force-exit']) { runResolve.then(() => forceExit(this.cmd.name, forceExitTimeout)) } return runResolve } showVersion() { process.stdout.write(`${name} v${version}\n`) } showHelp() { process.stdout.write(this._getHelp()) } get argv() { if (!this._parsedArgv) { const minimistOptions = this._getMinimistOptions() this._parsedArgv = minimist(this._argv, minimistOptions) } return this._parsedArgv } 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, this.argv) } return options } async getNuxt(options) { const { Nuxt } = await imports.core() const nuxt = new Nuxt(options) await nuxt.ready() return nuxt } async getBuilder(nuxt) { const { Builder } = await imports.builder() const { BundleBuilder } = await imports.webpack() return new Builder(nuxt, BundleBuilder) } async getGenerator(nuxt) { const { Generator } = await imports.generator() const builder = await this.getBuilder(nuxt) return new Generator(nuxt, builder) } _getMinimistOptions() { const minimistOptions = { alias: {}, boolean: [], string: [], default: {} } for (const name of Object.keys(this.cmd.options)) { const option = this.cmd.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 } _getHelp() { const options = [] let maxOptionLength = 0 for (const name in this.cmd.options) { const option = this.cmd.options[name] let optionHelp = '--' optionHelp += option.type === 'boolean' && option.default ? 'no-' : '' optionHelp += name if (option.alias) { optionHelp += `, -${option.alias}` } maxOptionLength = Math.max(maxOptionLength, optionHelp.length) options.push([ optionHelp, option.description ]) } const _opts = options.map(([option, description]) => { const i = indent(maxOptionLength + optionSpaces - option.length) return foldLines( option + i + description, startSpaces + maxOptionLength + optionSpaces * 2, startSpaces + optionSpaces ) }).join('\n') const usage = foldLines(`Usage: nuxt ${this.cmd.usage} [options]`, startSpaces) const description = foldLines(this.cmd.description, startSpaces) const opts = foldLines(`Options:`, startSpaces) + '\n\n' + _opts let helpText = colorize(`${usage}\n\n`) if (this.cmd.description) { helpText += colorize(`${description}\n\n`) } if (options.length) { helpText += colorize(`${opts}\n\n`) } return helpText } }