@controlla/cli
Version:
Command line interface for rapid Controlla projects development
324 lines (283 loc) • 8.55 kB
JavaScript
// const path = require('path')
const chalk = require('chalk')
const debug = require('debug')
const execa = require('execa')
const inquirer = require('inquirer')
const EventEmitter = require('events')
const path = require('path')
const krnos = require('@krnos/kronos')
const getGitUser = require('./util/git-user')
const shouldBackend = require('./util/shouldBackend')
const { installDeps, installComposerDeps, injectedScripts } = require('./util/installDeps')
const { clearConsole } = require('./util/clearConsole')
const {
saveOptions,
loadOptions
} = require('./options')
const {
log,
warn,
hasGit,
hasProjectGit,
hasYarn,
hasPnpm3OrLater,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')
module.exports = class Creator extends EventEmitter {
constructor (name, context) {
super()
this.name = name
this.context = process.env.VUE_CLI_CONTEXT = context
const { presetPrompt } = this.resolveIntroPrompts()
this.presetPrompt = presetPrompt
this.outroPrompts = this.resolveOutroPrompts()
this.injectedPrompts = []
this.createCompleteCbs = []
this.run = this.run.bind(this)
// const promptAPI = new PromptModuleAPI(this)
// promptModules.forEach(m => m(promptAPI))
}
async create (cliOptions = {}, preset = null) {
const isTestOrDebug = process.env.CONTROLLA_CLI_TEST || process.env.CONTROLLA_CLI_DEBUG
const { run, name, context, createCompleteCbs } = this
if (!preset) {
if (cliOptions.preset) {
preset = cliOptions.preset
} else {
preset = await this.resolvePrompts()
}
}
const packageManager = (
cliOptions.packageManager ||
loadOptions().packageManager ||
(hasYarn() ? 'yarn' : null) ||
(hasPnpm3OrLater() ? 'pnpm' : 'npm')
)
const description = loadOptions().description
const author = getGitUser()
const isShouldBackend = await shouldBackend(preset)
await clearConsole()
logWithSpinner('✨', `Creating project in ${chalk.yellow(context)}.`)
this.emit('creation', { event: 'creating' })
const src = path.join(path.dirname(require.resolve('../templates')), preset)
await krnos.generate(name, description, author, src, context, isShouldBackend, err => {
if (err) console.log(err)
stopSpinner()
})
// intilaize git repository before installing deps
// so that vue-cli-service can setup git hooks.
const shouldInitGit = this.shouldInitGit(cliOptions)
if (shouldInitGit) {
logWithSpinner('🗃', 'Initializing git repository...')
this.emit('creation', { event: 'git-init' })
await run('git init')
}
log()
// log instructions
stopSpinner()
log()
// commit initial state
let gitCommitFailed = false
if (shouldInitGit) {
await run('git add -A')
if (isTestOrDebug) {
await run('git', ['config', 'user.name', 'test'])
await run('git', ['config', 'user.email', 'test@test.com'])
}
const msg = typeof cliOptions.git === 'string' ? cliOptions.git : 'Init project :tada:'
try {
await run('git', ['commit', '-m', msg])
} catch (e) {
gitCommitFailed = true
}
}
const frontendPath = isShouldBackend ? `${context}/frontend` : context
// install plugins
stopSpinner()
log('🚀 Installing CLI plugins. This might take a while...')
log()
this.emit('creation', { event: 'plugins-install' })
if (isTestOrDebug) {
// in development, avoid installation process
await require('./util/setupDevProject')(context)
} else {
await installDeps(frontendPath, packageManager, cliOptions.registry)
}
// install composer
if (isShouldBackend) {
log()
log('🚀 Installing additional dependencies...')
this.emit('creation', { event: 'plugins-install' })
log()
await installDeps(context, packageManager, cliOptions.registry)
}
// install composer
if (isShouldBackend) {
log()
log('🚀 Installing Composer dependencies...')
this.emit('creation', { event: 'composer-install' })
log()
await installComposerDeps(context, 'composer')
}
// Injected additional scripts
if (isShouldBackend) {
log()
log('📦 Injected additional scripts...')
this.emit('creation', { event: 'injected-scripts' })
log()
await injectedScripts(context, 'composer')
}
// run complete cbs if any (injected by generators)
log()
logWithSpinner('⚓', 'Running completion hooks...')
this.emit('creation', { event: 'completion-hooks' })
await execa.shell('find . -type f -name ".controllaignore" -execdir mv {} .gitignore ";"', { cwd: this.context }).then(result => {
stopSpinner()
})
for (const cb of createCompleteCbs) {
await cb()
}
stopSpinner()
// generate README.md
log()
logWithSpinner('📄', 'Generating README.md...')
stopSpinner()
log()
log(`🎉 Successfully created project ${chalk.yellow(name)}.`)
if (!cliOptions.skipGetStarted) {
log(
'👉 Get started with the following commands:\n\n' +
(this.context === process.cwd() ? '' : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) +
chalk.cyan(` ${chalk.gray('$')} ${packageManager === 'yarn' ? 'yarn serve' : packageManager === 'pnpm' ? 'pnpm run serve' : 'npm run serve'}`)
)
}
log()
this.emit('creation', { event: 'done' })
if (gitCommitFailed) {
warn(
'Skipped git commit due to missing username and email in git config.\n' +
'You will need to perform the initial commit yourself.\n'
)
}
// generator.printExitLogs()
}
run (command, args) {
if (!args) { [command, ...args] = command.split(/\s+/) }
return execa(command, args, { cwd: this.context })
}
async resolvePrompts (answers = null) {
// prompt
if (!answers) {
await clearConsole(true)
answers = await inquirer.prompt(this.resolveFinalPrompts())
}
debug('vue-cli:answers')(answers)
if (answers.packageManager) {
saveOptions({
packageManager: answers.packageManager
})
}
saveOptions({
description: answers.description
})
const preset = answers.preset
return preset
}
resolveIntroPrompts () {
const presetPrompt = {
name: 'preset',
type: 'list',
message: 'Please pick a preset:',
choices: [
{
name: 'ERP Project',
value: 'erp'
},
{
name: 'Electron Project (clear)',
value: 'electron(clear)'
},
{
name: 'CRM Project',
value: 'crm'
},
{
name: 'Landing Project',
value: 'landing'
},
{
name: 'Plugin Project',
value: 'plugin'
}
]
}
return {
presetPrompt
}
}
resolveOutroPrompts () {
const outroPrompts = []
// ask for packageManager once
if (hasYarn() || hasPnpm3OrLater()) {
const packageManagerChoices = []
packageManagerChoices.push({
name: 'Use NPM',
value: 'npm',
short: 'NPM'
})
if (hasYarn()) {
packageManagerChoices.push({
name: 'Use Yarn',
value: 'yarn',
short: 'Yarn'
})
}
if (hasPnpm3OrLater()) {
packageManagerChoices.push({
name: 'Use PNPM',
value: 'pnpm',
short: 'PNPM'
})
}
outroPrompts.push({
name: 'packageManager',
type: 'list',
message: 'Pick the package manager to use when installing dependencies:',
choices: packageManagerChoices
})
}
outroPrompts.push({
name: 'description',
type: 'string',
required: false,
message: 'Project description',
default: 'An vue project'
})
return outroPrompts
}
resolveFinalPrompts () {
const prompts = [
this.presetPrompt,
...this.injectedPrompts,
...this.outroPrompts
]
debug('vue-cli:prompts')(prompts)
return prompts
}
shouldInitGit (cliOptions) {
if (!hasGit()) {
return false
}
// --git
if (cliOptions.forceGit) {
return true
}
// --no-git
if (cliOptions.git === false || cliOptions.git === 'false') {
return false
}
// default: true unless already in a git repo
return !hasProjectGit(this.context)
}
}