123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- const path = require('path')
- const fs = require('fs')
- const shortId = require('shortid')
- const Creator = require('@vue/cli/lib/Creator')
- const { getPromptModules } = require('@vue/cli/lib/util/createTools')
- const { getFeatures } = require('@vue/cli/lib/util/features')
- const { defaults } = require('@vue/cli/lib/options')
- const { toShortPluginId, execa } = require('@vue/cli-shared-utils')
- const { progress: installProgress } = require('@vue/cli/lib/util/executeCommand')
- const parseGitConfig = require('parse-git-config')
- // Connectors
- const progress = require('./progress')
- const cwd = require('./cwd')
- const prompts = require('./prompts')
- const folders = require('./folders')
- const plugins = require('./plugins')
- const locales = require('./locales')
- const logs = require('./logs')
- // Context
- const getContext = require('../context')
- // Utils
- const { log } = require('../util/logger')
- const { notify } = require('../util/notification')
- const { getHttpsGitURL } = require('../util/strings')
- const PROGRESS_ID = 'project-create'
- let lastProject = null
- let currentProject = null
- let creator = null
- let presets = []
- let features = []
- let onCreationEvent = null
- let onInstallProgress = null
- let onInstallLog = null
- function list (context) {
- let projects = context.db.get('projects').value()
- projects = autoClean(projects, context)
- return projects
- }
- function findOne (id, context) {
- return context.db.get('projects').find({ id }).value()
- }
- function findByPath (file, context) {
- return context.db.get('projects').find({ path: file }).value()
- }
- function autoClean (projects, context) {
- const result = []
- for (const project of projects) {
- if (fs.existsSync(project.path)) {
- result.push(project)
- }
- }
- if (result.length !== projects.length) {
- console.log(`Auto cleaned ${projects.length - result.length} projects (folder not found).`)
- context.db.set('projects', result).write()
- }
- return result
- }
- function getCurrent (context) {
- if (currentProject && !fs.existsSync(currentProject.path)) {
- log('Project folder not found', currentProject.id, currentProject.path)
- return null
- }
- return currentProject
- }
- function getLast (context) {
- return lastProject
- }
- function generatePresetDescription (preset) {
- let description = `[Vue ${preset.raw.vueVersion || 2}] `
- description += preset.features.join(', ')
- if (preset.raw.useConfigFiles) {
- description += ' (Use config files)'
- }
- return description
- }
- function generateProjectCreation (creator) {
- return {
- presets,
- features,
- prompts: prompts.list()
- }
- }
- async function initCreator (context) {
- const creator = new Creator('', cwd.get(), getPromptModules())
- /* Event listeners */
- // Creator emits creation events (the project creation steps)
- onCreationEvent = ({ event }) => {
- progress.set({ id: PROGRESS_ID, status: event, info: null }, context)
- }
- creator.on('creation', onCreationEvent)
- // Progress bar
- onInstallProgress = value => {
- if (progress.get(PROGRESS_ID)) {
- progress.set({ id: PROGRESS_ID, progress: value }, context)
- }
- }
- installProgress.on('progress', onInstallProgress)
- // Package manager steps
- onInstallLog = message => {
- if (progress.get(PROGRESS_ID)) {
- progress.set({ id: PROGRESS_ID, info: message }, context)
- }
- }
- installProgress.on('log', onInstallLog)
- // Presets
- const manualPreset = {
- id: '__manual__',
- name: 'org.vue.views.project-create.tabs.presets.manual.name',
- description: 'org.vue.views.project-create.tabs.presets.manual.description',
- link: null,
- features: []
- }
- const presetsData = creator.getPresets()
- presets = [
- ...Object.keys(presetsData).map(
- key => {
- const preset = presetsData[key]
- const features = getFeatures(preset).map(
- f => toShortPluginId(f)
- )
- let name = key
- if (key === 'default') {
- name = 'org.vue.views.project-create.tabs.presets.default-preset'
- } else if (key === '__default_vue_3__') {
- name = 'org.vue.views.project-create.tabs.presets.default-preset-vue-3'
- }
- const info = {
- id: key,
- name,
- features,
- link: null,
- raw: preset
- }
- info.description = generatePresetDescription(info)
- return info
- }
- ),
- manualPreset
- ]
- // Features
- const featuresData = creator.featurePrompt.choices
- features = [
- ...featuresData.map(
- data => ({
- id: data.value,
- name: data.name,
- description: data.description || null,
- link: data.link || null,
- plugins: data.plugins || null,
- enabled: !!data.checked
- })
- ),
- {
- id: 'use-config-files',
- name: 'org.vue.views.project-create.tabs.features.userConfigFiles.name',
- description: 'org.vue.views.project-create.tabs.features.userConfigFiles.description',
- link: null,
- plugins: null,
- enabled: false
- }
- ]
- manualPreset.features = features.filter(
- f => f.enabled
- ).map(
- f => f.id
- )
- // Prompts
- await prompts.reset()
- creator.injectedPrompts.forEach(prompts.add)
- await updatePromptsFeatures()
- await prompts.start()
- return creator
- }
- function removeCreator (context) {
- if (creator) {
- creator.removeListener('creation', onCreationEvent)
- installProgress.removeListener('progress', onInstallProgress)
- installProgress.removeListener('log', onInstallLog)
- creator = null
- }
- return true
- }
- async function getCreation (context) {
- if (!creator) {
- creator = await initCreator(context)
- }
- return generateProjectCreation(creator)
- }
- async function updatePromptsFeatures () {
- await prompts.changeAnswers(answers => {
- answers.features = features.filter(
- f => f.enabled
- ).map(
- f => f.id
- )
- })
- }
- async function setFeatureEnabled ({ id, enabled, updatePrompts = true }, context) {
- const feature = features.find(f => f.id === id)
- if (feature) {
- feature.enabled = enabled
- } else {
- console.warn(`Feature '${id}' not found`)
- }
- if (updatePrompts) await updatePromptsFeatures()
- return feature
- }
- async function applyPreset (id, context) {
- const preset = presets.find(p => p.id === id)
- if (preset) {
- for (const feature of features) {
- feature.enabled = !!(
- preset.features.includes(feature.id) ||
- (feature.plugins && preset.features.some(f => feature.plugins.includes(f)))
- )
- }
- if (preset.raw) {
- if (preset.raw.router) {
- await setFeatureEnabled({ id: 'router', enabled: true, updatePrompts: false }, context)
- }
- if (preset.raw.vuex) {
- await setFeatureEnabled({ id: 'vuex', enabled: true, updatePrompts: false }, context)
- }
- if (preset.raw.cssPreprocessor) {
- await setFeatureEnabled({ id: 'css-preprocessor', enabled: true, updatePrompts: false }, context)
- }
- if (preset.raw.useConfigFiles) {
- await setFeatureEnabled({ id: 'use-config-files', enabled: true, updatePrompts: false }, context)
- }
- }
- await updatePromptsFeatures()
- } else {
- console.warn(`Preset '${id}' not found`)
- }
- return generateProjectCreation(creator)
- }
- async function create (input, context) {
- return progress.wrap(PROGRESS_ID, context, async setProgress => {
- setProgress({
- status: 'creating'
- })
- const targetDir = path.join(cwd.get(), input.folder)
- cwd.set(targetDir, context)
- creator.context = targetDir
- const inCurrent = input.folder === '.'
- const name = creator.name = (inCurrent ? path.relative('../', process.cwd()) : input.folder).toLowerCase()
- // Answers
- const answers = prompts.getAnswers()
- await prompts.reset()
- // Config files
- let index
- if ((index = answers.features.indexOf('use-config-files')) !== -1) {
- answers.features.splice(index, 1)
- answers.useConfigFiles = 'files'
- }
- // Preset
- answers.preset = input.preset
- if (input.save) {
- answers.save = true
- answers.saveName = input.save
- }
- setProgress({
- info: 'Resolving preset...'
- })
- let preset
- if (input.preset === '__remote__' && input.remote) {
- // vue create foo --preset bar
- preset = await creator.resolvePreset(input.remote, input.clone)
- } else if (input.preset === 'default') {
- // vue create foo --default
- preset = defaults.presets.default
- } else {
- preset = await creator.promptAndResolvePreset(answers)
- }
- setProgress({
- info: null
- })
- // Create
- const args = [
- '--skipGetStarted'
- ]
- if (input.packageManager) args.push('--packageManager', input.packageManager)
- if (input.bar) args.push('--bare')
- if (input.force) args.push('--force')
- // Git
- if (input.enableGit && input.gitCommitMessage) {
- args.push('--git', input.gitCommitMessage)
- } else if (!input.enableGit) {
- args.push('--no-git')
- }
- // Preset
- args.push('--inlinePreset', JSON.stringify(preset))
- log('create', name, args)
- const child = execa('vue', [
- 'create',
- name,
- ...args
- ], {
- cwd: cwd.get(),
- stdio: ['inherit', 'pipe', 'inherit']
- })
- const onData = buffer => {
- const text = buffer.toString().trim()
- if (text) {
- setProgress({
- info: text
- })
- logs.add({
- type: 'info',
- message: text
- }, context)
- }
- }
- child.stdout.on('data', onData)
- await child
- removeCreator()
- notify({
- title: 'Project created',
- message: `Project ${cwd.get()} created`,
- icon: 'done'
- })
- return importProject({
- path: targetDir
- }, context)
- })
- }
- async function importProject (input, context) {
- if (!input.force && !fs.existsSync(path.join(input.path, 'node_modules'))) {
- throw new Error('NO_MODULES')
- }
- const project = {
- id: shortId.generate(),
- path: input.path,
- favorite: 0,
- type: folders.isVueProject(input.path) ? 'vue' : 'unknown'
- }
- const packageData = folders.readPackage(project.path, context)
- project.name = packageData.name
- context.db.get('projects').push(project).write()
- return open(project.id, context)
- }
- async function open (id, context) {
- const project = findOne(id, context)
- if (!project) {
- log('Project not found', id)
- return null
- }
- if (!fs.existsSync(project.path)) {
- log('Project folder not found', id, project.path)
- return null
- }
- lastProject = currentProject
- currentProject = project
- cwd.set(project.path, context)
- // Reset locales
- locales.reset(context)
- // Load plugins
- await plugins.list(project.path, context)
- // Date
- context.db.get('projects').find({ id }).assign({
- openDate: Date.now()
- }).write()
- // Save for next time
- context.db.set('config.lastOpenProject', id).write()
- log('Project open', id, project.path)
- return project
- }
- async function remove (id, context) {
- if (currentProject && currentProject.id === id) {
- currentProject = null
- }
- context.db.get('projects').remove({ id }).write()
- if (context.db.get('config.lastOpenProject').value() === id) {
- context.db.set('config.lastOpenProject', undefined).write()
- }
- return true
- }
- function resetCwd (context) {
- if (currentProject) {
- cwd.set(currentProject.path, context)
- }
- }
- function setFavorite ({ id, favorite }, context) {
- context.db.get('projects').find({ id }).assign({ favorite }).write()
- return findOne(id, context)
- }
- function rename ({ id, name }, context) {
- context.db.get('projects').find({ id }).assign({ name }).write()
- return findOne(id, context)
- }
- function getType (project, context) {
- if (typeof project === 'string') {
- project = findByPath(project, context)
- }
- if (!project) return 'unknown'
- return !project.type ? 'vue' : project.type
- }
- function getHomepage (project, context) {
- const gitConfigPath = path.join(project.path, '.git', 'config')
- if (fs.existsSync(gitConfigPath)) {
- const gitConfig = parseGitConfig.sync({ path: gitConfigPath })
- const gitRemoteUrl = gitConfig['remote "origin"']
- if (gitRemoteUrl) {
- return getHttpsGitURL(gitRemoteUrl.url)
- }
- }
- const pkg = folders.readPackage(project.path, context)
- return pkg.homepage
- }
- // Open last project
- async function autoOpenLastProject () {
- const context = getContext()
- const id = context.db.get('config.lastOpenProject').value()
- if (id) {
- try {
- await open(id, context)
- } catch (e) {
- log('Project can\'t be auto-opened', id)
- }
- }
- }
- autoOpenLastProject()
- module.exports = {
- list,
- findOne,
- findByPath,
- getCurrent,
- getLast,
- getCreation,
- applyPreset,
- setFeatureEnabled,
- create,
- import: importProject,
- open,
- remove,
- resetCwd,
- setFavorite,
- rename,
- initCreator,
- removeCreator,
- getType,
- getHomepage
- }
|