UNPKG

15.3 kBJavaScriptView Raw
1// const path = require('path')
2const chalk = require('chalk')
3const debug = require('debug')
4const execa = require('execa')
5const inquirer = require('inquirer')
6const EventEmitter = require('events')
7const path = require('path')
8const krnos = require('@krnos/kronos')
9const getGitUser = require('./util/git-user')
10const shouldBackend = require('./util/shouldBackend')
11const isFlutter = require('./util/isFlutter')
12const { installDeps, installComposerDeps, installFlutterDeps, injectedScripts } = require('./util/installDeps')
13const { clearConsole } = require('./util/clearConsole')
14
15const {
16 saveOptions,
17 loadOptions
18} = require('./options')
19
20const {
21 log,
22 warn,
23 hasGit,
24 hasProjectGit,
25 hasYarn,
26 hasPnpm3OrLater,
27 logWithSpinner,
28 stopSpinner
29} = require('@vue/cli-shared-utils')
30
31module.exports = class Creator extends EventEmitter {
32 constructor (name, context) {
33 super()
34
35 this.name = name
36 this.context = process.env.VUE_CLI_CONTEXT = context
37 const { presetPrompt } = this.resolveIntroPrompts()
38 this.presetPrompt = presetPrompt
39 this.outroPrompts = this.resolveOutroPrompts()
40 this.injectedPrompts = []
41 this.createCompleteCbs = []
42
43 this.run = this.run.bind(this)
44
45 // const promptAPI = new PromptModuleAPI(this)
46 // promptModules.forEach(m => m(promptAPI))
47 }
48
49 async create (cliOptions = {}, preset = null) {
50 const isTestOrDebug = process.env.CONTROLLA_CLI_TEST || process.env.CONTROLLA_CLI_DEBUG
51 const { run, name, context, createCompleteCbs } = this
52 let repository = ''
53
54 if (!preset) {
55 if (cliOptions.preset) {
56 preset = cliOptions.preset
57 } else {
58 const props = await this.resolvePrompts()
59 preset = props.preset
60 repository = props.repository
61 }
62 }
63
64 const packageManager = (
65 cliOptions.packageManager ||
66 loadOptions().packageManager ||
67 (hasYarn() ? 'yarn' : null) ||
68 (hasPnpm3OrLater() ? 'pnpm' : 'npm')
69 )
70
71 const description = loadOptions().description
72
73 const author = getGitUser()
74
75 const isShouldBackend = await shouldBackend(preset)
76 const flutterCommand = await isFlutter(preset)
77
78 await clearConsole()
79 logWithSpinner('✨', `Creating project in ${chalk.yellow(context)}.`)
80 this.emit('creation', { event: 'creating' })
81 const src = path.join(path.dirname(require.resolve('../templates')), preset)
82 await krnos.generate(name, description, author, src, context, isShouldBackend, preset, err => {
83 if (err) console.log(err)
84 stopSpinner()
85 })
86
87 // intilaize git repository before installing deps
88 // so that vue-cli-service can setup git hooks.
89 const shouldInitGit = this.shouldInitGit(cliOptions)
90 if (shouldInitGit) {
91 logWithSpinner('🗃', 'Initializing git repository...')
92 this.emit('creation', { event: 'git-init' })
93 await run('git init')
94 }
95 log()
96
97 const frontendPath = isShouldBackend ? `${context}/frontend` : context
98 // install plugins
99 stopSpinner()
100 log('🚀 Installing CLI plugins. This might take a while...')
101 log()
102 this.emit('creation', { event: 'plugins-install' })
103 if (isTestOrDebug) {
104 // in development, avoid installation process
105 await require('./util/setupDevProject')(context)
106 } else {
107 await installDeps(frontendPath, packageManager, cliOptions.registry)
108 }
109
110 // install flutter dependencies
111 if (flutterCommand) {
112 log()
113 log('🚀 Installing flutter dependencies...')
114 this.emit('creation', { event: 'plugins-install' })
115 log()
116 await installFlutterDeps(context, 'flutter')
117 }
118
119 // install composer
120 if (isShouldBackend) {
121 log()
122 log('🚀 Installing additional dependencies...')
123 this.emit('creation', { event: 'plugins-install' })
124 log()
125 await installDeps(context, packageManager, cliOptions.registry)
126 }
127
128 // install composer
129 if (isShouldBackend) {
130 log()
131 log('🚀 Installing Composer dependencies...')
132 this.emit('creation', { event: 'composer-install' })
133 log()
134 await installComposerDeps(context, 'composer')
135 }
136
137 // Injected additional scripts
138 if (isShouldBackend) {
139 log()
140 log('📦 Injected additional scripts...')
141 this.emit('creation', { event: 'injected-scripts' })
142 log()
143 await injectedScripts(context, 'composer')
144 }
145
146 // run complete cbs if any (injected by generators)
147 log()
148 logWithSpinner('⚓', 'Running completion hooks...')
149 this.emit('creation', { event: 'completion-hooks' })
150 await execa.shell('find . -type f -name ".controllaignore" -execdir mv {} .gitignore ";"', { cwd: context }).then(result => {
151 stopSpinner()
152 })
153 for (const cb of createCompleteCbs) {
154 await cb()
155 }
156
157 // log instructions
158 stopSpinner()
159 log()
160 // commit initial state
161 let gitCommitFailed = false
162 if (shouldInitGit) {
163 await run('git', ['remote', 'add', 'origin', repository])
164 await run('git add -A')
165 if (isTestOrDebug) {
166 await run('git', ['config', 'user.name', 'test'])
167 await run('git', ['config', 'user.email', 'test@test.com'])
168 }
169 const msg = typeof cliOptions.git === 'string' ? cliOptions.git : 'chore: Init Project :tada:'
170 try {
171 await run('git', ['commit', '-m', msg])
172 } catch (e) {
173 gitCommitFailed = true
174 }
175 }
176
177 log()
178
179 stopSpinner()
180
181 log()
182 if (shouldInitGit) {
183 logWithSpinner('⚓', 'Create DevQA branch...')
184 this.emit('creation', { event: 'create-branch' })
185 await run('git', ['checkout', '-b', 'DevQA'])
186 }
187 stopSpinner()
188
189 // generate README.md
190 log()
191 logWithSpinner('📄', 'Generating README.md...')
192 stopSpinner()
193
194 log()
195 log(`🎉 Successfully created project ${chalk.yellow(name)}.`)
196 if (!cliOptions.skipGetStarted) {
197 log(
198 '👉 Get started with the following commands:\n\n' +
199 (this.context === process.cwd()
200 ? ''
201 : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) +
202 chalk.cyan(
203 ` ${chalk.gray('$')} ${
204 flutterCommand
205 ? 'flutter run'
206 : packageManager === 'yarn'
207 ? 'yarn serve'
208 : packageManager === 'pnpm'
209 ? 'pnpm run serve'
210 : 'npm run serve'
211 }`
212 )
213 )
214 }
215 log()
216 this.emit('creation', { event: 'done' })
217
218 if (gitCommitFailed) {
219 warn(
220 'Skipped git commit due to missing username and email in git config.\n' +
221 'You will need to perform the initial commit yourself.\n'
222 )
223 }
224
225 // generator.printExitLogs()
226 }
227
228 async generate (cliOptions = {}, preset = null) {
229 const isTestOrDebug = process.env.CONTROLLA_CLI_TEST || process.env.CONTROLLA_CLI_DEBUG
230 const { run, name, context, createCompleteCbs } = this
231 let repository = ''
232
233 if (!preset) {
234 if (cliOptions.preset) {
235 preset = cliOptions.preset
236 } else {
237 const props = await this.resolvePrompts()
238 preset = props.preset
239 repository = props.repository
240 }
241 }
242
243 const packageManager = (
244 cliOptions.packageManager ||
245 loadOptions().packageManager ||
246 (hasYarn() ? 'yarn' : null) ||
247 (hasPnpm3OrLater() ? 'pnpm' : 'npm')
248 )
249
250 const description = loadOptions().description
251
252 const author = getGitUser()
253
254 const isShouldBackend = await shouldBackend(preset)
255 const flutterCommand = await isFlutter(preset)
256
257 await clearConsole()
258 logWithSpinner('✨', `Creating project in ${chalk.yellow(context)}.`)
259 this.emit('creation', { event: 'creating' })
260 const src = path.join(path.dirname(require.resolve('../templates')), preset)
261 await krnos.generate(name, description, author, src, context, isShouldBackend, preset, err => {
262 if (err) console.log(err)
263 stopSpinner()
264 })
265
266 // intilaize git repository before installing deps
267 // so that vue-cli-service can setup git hooks.
268 const shouldInitGit = this.shouldInitGit(cliOptions)
269 if (shouldInitGit) {
270 logWithSpinner('🗃', 'Initializing git repository...')
271 this.emit('creation', { event: 'git-init' })
272 await run('git init')
273 }
274 log()
275
276 const frontendPath = isShouldBackend ? `${context}/frontend` : context
277 // install plugins
278 stopSpinner()
279 log('🚀 Installing CLI plugins. This might take a while...')
280 log()
281 this.emit('creation', { event: 'plugins-install' })
282 if (isTestOrDebug) {
283 // in development, avoid installation process
284 await require('./util/setupDevProject')(context)
285 } else {
286 await installDeps(frontendPath, packageManager, cliOptions.registry)
287 }
288
289 // install flutter dependencies
290 if (flutterCommand) {
291 log()
292 log('🚀 Installing flutter dependencies...')
293 this.emit('creation', { event: 'plugins-install' })
294 log()
295 await installFlutterDeps(context, 'flutter')
296 }
297
298 // install composer
299 if (isShouldBackend) {
300 log()
301 log('🚀 Installing additional dependencies...')
302 this.emit('creation', { event: 'plugins-install' })
303 log()
304 await installDeps(context, packageManager, cliOptions.registry)
305 }
306
307 // install composer
308 if (isShouldBackend) {
309 log()
310 log('🚀 Installing Composer dependencies...')
311 this.emit('creation', { event: 'composer-install' })
312 log()
313 await installComposerDeps(context, 'composer')
314 }
315
316 // Injected additional scripts
317 if (isShouldBackend) {
318 log()
319 log('📦 Injected additional scripts...')
320 this.emit('creation', { event: 'injected-scripts' })
321 log()
322 await injectedScripts(context, 'composer')
323 }
324
325 // run complete cbs if any (injected by generators)
326 log()
327 logWithSpinner('⚓', 'Running completion hooks...')
328 this.emit('creation', { event: 'completion-hooks' })
329 await execa.shell('find . -type f -name ".controllaignore" -execdir mv {} .gitignore ";"', { cwd: context }).then(result => {
330 stopSpinner()
331 })
332 for (const cb of createCompleteCbs) {
333 await cb()
334 }
335
336 // log instructions
337 stopSpinner()
338 log()
339 // commit initial state
340 let gitCommitFailed = false
341 if (shouldInitGit) {
342 await run('git', ['remote', 'add', 'origin', repository])
343 await run('git add -A')
344 if (isTestOrDebug) {
345 await run('git', ['config', 'user.name', 'test'])
346 await run('git', ['config', 'user.email', 'test@test.com'])
347 }
348 const msg = typeof cliOptions.git === 'string' ? cliOptions.git : 'chore: Init Project :tada:'
349 try {
350 await run('git', ['commit', '-m', msg])
351 } catch (e) {
352 gitCommitFailed = true
353 }
354 }
355
356 log()
357
358 stopSpinner()
359
360 log()
361 if (shouldInitGit) {
362 logWithSpinner('⚓', 'Create DevQA branch...')
363 this.emit('creation', { event: 'create-branch' })
364 await run('git', ['checkout', '-b', 'DevQA'])
365 }
366 stopSpinner()
367
368 // generate README.md
369 log()
370 logWithSpinner('📄', 'Generating README.md...')
371 stopSpinner()
372
373 log()
374 log(`🎉 Successfully created project ${chalk.yellow(name)}.`)
375 if (!cliOptions.skipGetStarted) {
376 log(
377 '👉 Get started with the following commands:\n\n' +
378 (this.context === process.cwd()
379 ? ''
380 : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) +
381 chalk.cyan(
382 ` ${chalk.gray('$')} ${
383 flutterCommand
384 ? 'flutter run'
385 : packageManager === 'yarn'
386 ? 'yarn serve'
387 : packageManager === 'pnpm'
388 ? 'pnpm run serve'
389 : 'npm run serve'
390 }`
391 )
392 )
393 }
394 log()
395 this.emit('creation', { event: 'done' })
396
397 if (gitCommitFailed) {
398 warn(
399 'Skipped git commit due to missing username and email in git config.\n' +
400 'You will need to perform the initial commit yourself.\n'
401 )
402 }
403
404 // generator.printExitLogs()
405 }
406
407 run (command, args) {
408 if (!args) { [command, ...args] = command.split(/\s+/) }
409 return execa(command, args, { cwd: this.context })
410 }
411
412 async resolvePrompts (answers = null) {
413 // prompt
414 if (!answers) {
415 await clearConsole(true)
416 answers = await inquirer.prompt(this.resolveFinalPrompts())
417 }
418 debug('vue-cli:answers')(answers)
419
420 if (answers.packageManager) {
421 saveOptions({
422 packageManager: answers.packageManager
423 })
424 }
425
426 saveOptions({
427 description: answers.description
428 })
429
430 return answers
431 }
432
433 resolveIntroPrompts () {
434 const presetPrompt = {
435 name: 'preset',
436 type: 'list',
437 message: 'Please pick a preset:',
438 choices: [
439 {
440 name: 'ERP Project',
441 value: 'erp'
442 },
443 {
444 name: 'Flutter Project',
445 value: 'flutter'
446 },
447 {
448 name: 'Electron Project (clear)',
449 value: 'electron(clear)'
450 },
451 {
452 name: 'CRM Project',
453 value: 'crm'
454 },
455 {
456 name: 'Landing Project',
457 value: 'landing'
458 },
459 {
460 name: 'Plugin Project',
461 value: 'plugin'
462 }
463 ]
464 }
465 return {
466 presetPrompt
467 }
468 }
469
470 resolveOutroPrompts () {
471 const outroPrompts = []
472
473 // ask for packageManager once
474 if (hasYarn() || hasPnpm3OrLater()) {
475 const packageManagerChoices = []
476
477 packageManagerChoices.push({
478 name: 'Use NPM',
479 value: 'npm',
480 short: 'NPM'
481 })
482
483 if (hasYarn()) {
484 packageManagerChoices.push({
485 name: 'Use Yarn',
486 value: 'yarn',
487 short: 'Yarn'
488 })
489 }
490
491 if (hasPnpm3OrLater()) {
492 packageManagerChoices.push({
493 name: 'Use PNPM',
494 value: 'pnpm',
495 short: 'PNPM'
496 })
497 }
498
499 outroPrompts.push({
500 name: 'packageManager',
501 type: 'list',
502 message: 'Pick the package manager to use when installing dependencies:',
503 choices: packageManagerChoices
504 })
505 }
506
507 outroPrompts.push({
508 name: 'description',
509 type: 'string',
510 required: false,
511 message: 'Project description',
512 default: 'An vue project'
513 })
514
515 outroPrompts.push({
516 name: 'repository',
517 type: 'input',
518 required: true,
519 message: 'Set remote repository',
520 validate: (value) => {
521 if (value.length) {
522 return true
523 } else {
524 return 'Please enter the remote repository'
525 }
526 }
527 })
528
529 return outroPrompts
530 }
531
532 resolveFinalPrompts () {
533 const prompts = [
534 this.presetPrompt,
535 ...this.injectedPrompts,
536 ...this.outroPrompts
537 ]
538 debug('vue-cli:prompts')(prompts)
539 return prompts
540 }
541
542 shouldInitGit (cliOptions) {
543 if (!hasGit()) {
544 return false
545 }
546 // --git
547 if (cliOptions.forceGit) {
548 return true
549 }
550 // --no-git
551 if (cliOptions.git === false || cliOptions.git === 'false') {
552 return false
553 }
554 // default: true unless already in a git repo
555 return !hasProjectGit(this.context)
556 }
557}