Files
vue-cli/packages/@vue/cli/lib/Creator.js
2018-01-25 13:55:29 -05:00

297 lines
8.1 KiB
JavaScript

const path = require('path')
const chalk = require('chalk')
const debug = require('debug')
const execa = require('execa')
const resolve = require('resolve')
const inquirer = require('inquirer')
const Generator = require('./Generator')
const cloneDeep = require('lodash.clonedeep')
const sortObject = require('./util/sortObject')
const installDeps = require('./util/installDeps')
const clearConsole = require('./util/clearConsole')
const PromptModuleAPI = require('./PromptModuleAPI')
const writeFileTree = require('./util/writeFileTree')
const formatFeatures = require('./util/formatFeatures')
const setupDevProject = require('./util/setupDevProject')
const {
defaults,
validate,
saveOptions,
loadOptions
} = require('./options')
const {
log,
error,
hasGit,
hasYarn,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')
const isMode = _mode => ({ mode }) => _mode === mode
module.exports = class Creator {
constructor (name, context, promptModules) {
this.name = name
this.context = process.env.VUE_CLI_CONTEXT = context
const { modePrompt, featurePrompt } = this.resolveIntroPrompts()
this.modePrompt = modePrompt
this.featurePrompt = featurePrompt
this.outroPrompts = this.resolveOutroPrompts()
this.injectedPrompts = []
this.promptCompleteCbs = []
this.createCompleteCbs = []
const promptAPI = new PromptModuleAPI(this)
promptModules.forEach(m => m(promptAPI))
}
async create (cliOptions = {}) {
const isTestOrDebug = process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG
const { name, context, createCompleteCbs } = this
const run = (command, args) => {
if (!args) { [command, ...args] = command.split(/\s+/) }
return execa(command, args, { cwd: context })
}
let options
if (cliOptions.saved) {
options = loadOptions()
} else if (cliOptions.default) {
options = defaults
} else if (cliOptions.config) {
try {
options = JSON.parse(cliOptions.config)
} catch (e) {
error(`CLI inline config is not valid JSON: ${cliOptions.config}`)
process.exit(1)
}
} else {
options = await this.promptAndResolveOptions()
}
// clone before mutating
options = cloneDeep(options)
// inject core service
options.plugins['@vue/cli-service'] = Object.assign({
projectName: name
}, options)
const packageManager = (
cliOptions.packageManager ||
options.packageManager ||
(hasYarn ? 'yarn' : 'npm')
)
// write base package.json to disk
clearConsole()
logWithSpinner('✨', `Creating project in ${chalk.yellow(context)}.`)
await writeFileTree(context, {
'package.json': JSON.stringify({
name,
version: '0.1.0',
private: true
}, null, 2)
})
// intilaize git repository
if (hasGit) {
logWithSpinner('🗃', `Initializing git repository...`)
await run('git init')
}
// install plugins
logWithSpinner('⚙', `Installing CLI plugins. This might take a while...`)
const deps = Object.keys(options.plugins)
if (isTestOrDebug) {
// in development, avoid installation process
await setupDevProject(context, deps)
} else {
await installDeps(context, packageManager, deps, cliOptions.registry)
}
// run generator
logWithSpinner('🚀', `Invoking generators...`)
const pkg = require(path.join(context, 'package.json'))
const plugins = this.resolvePlugins(options.plugins)
const generator = new Generator(
context,
pkg,
plugins,
createCompleteCbs
)
await generator.generate()
// install additional deps (injected by generators)
logWithSpinner('📦', `Installing additional dependencies...`)
if (!isTestOrDebug) {
await installDeps(context, packageManager, null, cliOptions.registry)
}
// run complete cbs if any (injected by generators)
logWithSpinner('⚓', `Running completion hooks...`)
for (const cb of createCompleteCbs) {
await cb()
}
// commit initial state
if (hasGit) {
await run('git add -A')
if (isTestOrDebug) {
await run('git', ['config', 'user.name', 'test'])
await run('git', ['config', 'user.email', 'test@test.com'])
}
await run(`git commit -m init`)
}
// log instructions
stopSpinner()
log()
log(`🎉 Successfully created project ${chalk.yellow(name)}.`)
log(
`👉 Get started with the following commands:\n\n` +
chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`) +
chalk.cyan(` ${chalk.gray('$')} ${options.packageManager === 'yarn' ? 'yarn serve' : 'npm run serve'}`)
)
log()
}
async promptAndResolveOptions () {
// prompt
clearConsole()
const answers = await inquirer.prompt(this.resolveFinalPrompts())
debug('vue:cli-answers')(answers)
let options
if (answers.mode === 'saved') {
options = loadOptions()
} else if (answers.mode === 'default') {
options = defaults
} else {
// manual
options = {
packageManager: answers.packageManager || loadOptions().packageManager,
plugins: {}
}
// run cb registered by prompt modules to finalize the options
this.promptCompleteCbs.forEach(cb => cb(answers, options))
}
// validate
validate(options)
// save options
if (answers.mode === 'manual' && answers.save) {
saveOptions(options, true /* replace */)
}
debug('vue:cli-options')(options)
return options
}
// { id: options } => [{ id, apply, options }]
resolvePlugins (rawPlugins) {
// ensure cli-service is invoked first
rawPlugins = sortObject(rawPlugins, ['@vue/cli-service'])
return Object.keys(rawPlugins).map(id => {
const module = resolve.sync(`${id}/generator`, { basedir: this.context })
return {
id,
apply: require(module),
options: rawPlugins[id]
}
})
}
resolveIntroPrompts () {
const defualtFeatures = formatFeatures(defaults)
const modePrompt = {
name: 'mode',
type: 'list',
message: `Please pick a project creation mode:`,
choices: [
{
name: `Zero-config with defaults (${defualtFeatures})`,
value: 'default'
},
{
name: 'Manually select features',
value: 'manual'
}
]
}
const savedOptions = loadOptions()
if (savedOptions.plugins) {
const savedFeatures = formatFeatures(savedOptions)
modePrompt.choices.unshift({
name: `Use previously saved config (${savedFeatures})`,
value: 'saved'
})
}
const featurePrompt = {
name: 'features',
when: isMode('manual'),
type: 'checkbox',
message: 'Check the features needed for your project:',
choices: [],
pageSize: 8
}
return {
modePrompt,
featurePrompt
}
}
resolveOutroPrompts () {
const outroPrompts = []
const savedOptions = loadOptions()
if (hasYarn && !savedOptions.packageManager) {
outroPrompts.push({
name: 'packageManager',
when: isMode('manual'),
type: 'list',
message: 'Pick the package manager to use when installing dependencies:',
choices: [
{
name: 'Use Yarn',
value: 'yarn',
short: 'Yarn'
},
{
name: 'Use NPM',
value: 'npm',
short: 'NPM'
}
]
})
}
outroPrompts.push({
name: 'save',
when: isMode('manual'),
type: 'confirm',
message: 'Save the preferences for future projects? (You can always manually edit ~/.vuerc)'
})
return outroPrompts
}
resolveFinalPrompts () {
// patch generator-injected prompts to only show when mode === 'manual'
this.injectedPrompts.forEach(prompt => {
const originalWhen = prompt.when || (() => true)
prompt.when = options => {
return options.mode === 'manual' && originalWhen(options)
}
})
const prompts = [].concat(
this.modePrompt,
this.featurePrompt,
this.injectedPrompts,
this.outroPrompts
)
debug('vue:cli-prompts')(prompts)
return prompts
}
}