@controlla/cli
Version:
Command line interface for rapid Controlla projects development
407 lines (358 loc) • 10.8 kB
JavaScript
// const path = require('path')
const chalk = require('chalk')
const debug = require('debug')
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 isFlutter = require('./util/isFlutter')
const { installDeps, installComposerDeps, installFlutterDeps, injectedScripts } = require('./util/installDeps')
const { clearConsole } = require('./util/clearConsole')
const {
saveOptions,
loadOptions
} = require('./options')
const {
log,
warn,
hasGit,
hasProjectGit,
hasYarn,
hasPnpm3OrLater,
logWithSpinner,
stopSpinner,
execa
} = 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
let repository = ''
if (!preset) {
if (cliOptions.preset) {
preset = cliOptions.preset
} else {
const props = await this.resolvePrompts()
preset = props.preset
repository = props.repository
}
}
const packageManager = (
cliOptions.packageManager ||
loadOptions().packageManager ||
(hasYarn() ? 'yarn' : null) ||
(hasPnpm3OrLater() ? 'pnpm' : 'npm')
)
const description = loadOptions().description
const author = getGitUser()
const isShouldBackend = await shouldBackend(preset)
const flutterCommand = await isFlutter(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, preset, 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('')
// const frontendPath = isShouldBackend ? preset === 'saas' ? `${context}/central` : `${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)
// }
if (preset === 'saas') {
await installDeps(`${context}/tenant`, 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("find . -name '.controllaignore' -exec rename 's/controlla/git/' -- {} +", { cwd: this.context, shell: true })
// if (isShouldBackend) {
// log()
// logWithSpinner('🚧', 'Creating virtualhost...')
// this.emit('creation', { event: 'creating-virtualhost' })
// await execa.command(`sudo sed -i -e "s/127.0.0.1\tlocalhost/127.0.0.1\tlocalhost\n127.0.0.1\t${name}.test/g" /etc/hosts`, { cwd: context }).then(_ => {
// stopSpinner()
// })
// }
for (const cb of createCompleteCbs) {
await cb()
}
// log instructions
stopSpinner()
log()
// commit initial state
let gitCommitFailed = false
if (shouldInitGit) {
await run('git', ['remote', 'add', 'origin', repository])
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 : 'chore: Init Project :tada:'
try {
await run('git', ['commit', '-m', msg, '--no-verify'])
} catch (e) {
log(`Error: ${e}`)
gitCommitFailed = true
}
}
log()
stopSpinner()
log()
if (shouldInitGit) {
logWithSpinner('⚓', 'Create DEV branch...')
this.emit('creation', { event: 'create-branch' })
await run('git', ['checkout', '-b', 'DEV'])
}
stopSpinner()
// generate README.md
log('')
logWithSpinner('📄', 'Generating README.md...')
stopSpinner()
// install flutter dependencies
if (flutterCommand) {
log()
log('🚀 Installing flutter dependencies...')
this.emit('creation', { event: 'plugins-install' })
log()
await installFlutterDeps(context, 'flutter')
}
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('$')} ${
flutterCommand
? 'flutter run'
: 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
})
return answers
}
resolveIntroPrompts () {
const presetPrompt = {
name: 'preset',
type: 'list',
message: 'Please pick a preset:',
choices: [
{
name: 'ERP Project',
value: 'erp'
},
{
name: 'Flutter Project',
value: 'flutter'
},
{
name: 'Flutter Desktop Project',
value: 'flutter(desktop)'
},
{
name: 'Electron Project (clear)',
value: 'electron(clear)'
},
{
name: 'Saas Project',
value: 'saas'
},
{
name: 'CRM Project',
value: 'crm'
},
{
name: 'CMS Project',
value: 'cms'
},
{
name: 'Landing Project',
value: 'landing'
},
{
name: 'Package Project',
value: 'package'
},
{
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'
})
outroPrompts.push({
name: 'repository',
type: 'input',
required: true,
message: 'Set remote repository',
validate: (value) => {
if (value.length) {
return true
} else {
return 'Please enter the remote repository'
}
}
})
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)
}
}