1 |
|
2 | const chalk = require('chalk')
|
3 | const debug = require('debug')
|
4 | const execa = require('execa')
|
5 | const inquirer = require('inquirer')
|
6 | const EventEmitter = require('events')
|
7 | const path = require('path')
|
8 | const krnos = require('@krnos/kronos')
|
9 | const getGitUser = require('./util/git-user')
|
10 | const shouldBackend = require('./util/shouldBackend')
|
11 | const isFlutter = require('./util/isFlutter')
|
12 | const { installDeps, installComposerDeps, installFlutterDeps, injectedScripts } = require('./util/installDeps')
|
13 | const { clearConsole } = require('./util/clearConsole')
|
14 |
|
15 | const {
|
16 | saveOptions,
|
17 | loadOptions
|
18 | } = require('./options')
|
19 |
|
20 | const {
|
21 | log,
|
22 | warn,
|
23 | hasGit,
|
24 | hasProjectGit,
|
25 | hasYarn,
|
26 | hasPnpm3OrLater,
|
27 | logWithSpinner,
|
28 | stopSpinner
|
29 | } = require('@vue/cli-shared-utils')
|
30 |
|
31 | module.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 |
|
46 |
|
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 |
|
88 |
|
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 |
|
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 |
|
105 | await require('./util/setupDevProject')(context)
|
106 | } else {
|
107 | await installDeps(frontendPath, packageManager, cliOptions.registry)
|
108 | }
|
109 |
|
110 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
158 | stopSpinner()
|
159 | log()
|
160 |
|
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 |
|
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 |
|
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 |
|
267 |
|
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 |
|
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 |
|
284 | await require('./util/setupDevProject')(context)
|
285 | } else {
|
286 | await installDeps(frontendPath, packageManager, cliOptions.registry)
|
287 | }
|
288 |
|
289 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
337 | stopSpinner()
|
338 | log()
|
339 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
547 | if (cliOptions.forceGit) {
|
548 | return true
|
549 | }
|
550 |
|
551 | if (cliOptions.git === false || cliOptions.git === 'false') {
|
552 | return false
|
553 | }
|
554 |
|
555 | return !hasProjectGit(this.context)
|
556 | }
|
557 | }
|