mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-04-21 11:58:36 -05:00
a09407dd5b
* feat: basic fonctionality, welcome and kill port widgets * fix: contrast improvements * feat: plugin/dep/vulnerability widgets design * fix: widget add/remove animation * feat: run task widget * feat: news + wip resizing * feat: nuxt * chore: removed widget example * fix: visual polish for widget transform * feat(widget): overlap detection * fix: news default/max size * feat(dashboard): sidepane transition * chore: dev api server port * fix(widget): configure tooltip * refactor(widget): generic Movable mixin * refactor(widget): resizable mixin * feat(widget): resize transition * feat(widget): resize improvements * refactor(widget): zoom factor * refactor(widget): OnGrid mixin * refactor(widget): resize handler style moved to global * chore: remove console.log * refactor: files structure * feat: improved design and layout * fix: content background vars * fix: status bar / view nav z-indexes * fix: webpack dashboard grid gap * feat(news feed): handle errors * fix(card): dimmed box shadow * fix: view nav & status bar z-index * fix: remove (wip) * feat(widget): style tweaks * feat(widget): details pane (wip) * feat: news feed widget improvements * feat(widget): custom header button * feat(news): item details pane * feat(widget): custom title * fix(news): better cache and misc fixes * feat(widget): resize left and top handles * feat(widget): transparent widget while moving/resizing * feat(news): better "big size" style * fix(news): media sizes in rich content * feat(plugin): local plugins support * fix: scrolling issue in Fx * fix: colors * fix(nav bar): more item overflowing * feat(vuln): frontend * chore: locale update * fix: image in suggestion dropdown (dev) * fix(suggestion): missing custom image * feat(view): user default plugin logo if no provided icon * feat(view): better loading UX * feat(view): button background if view is selected * feat(view): new nav indicator * feat(widget): use plugin logo as default icon * feat(widget): better widget modal * feat(widget): longDescription * fix(widget): news validate url param * feat(widget): filter widgets in add pane * feat(widget): tease upcoming widgets * chore: fix merge dev * chore: yarn install * chore: sync versions * chore: update apollo * docs: widget * fix(progress): graphql error * fix(deps): localPath * perf(plugin): faster local plugin refresh * fix(nav): center active indicator * feat(task): improved header * feat(client addon): custom component load timeout message * feat(suggestion): ping animation to improve discoverability * chore: update vue-apollo * feat(api): requestRoute * fix(suggestion): hide more info link if no link * fix(style): ul padding * test(e2e): fix plugin path * chore: change test scripts * chore(deps): upgrade * fix: build error * fix(widget): removed moving scale transform * fix(widget): resize handles style * chore(deps): unpin apollo-utilities * chore(deps): lock fix * test(e2e): fix server * fix: issue with writeQuery See: https://github.com/apollographql/apollo-client/issues/4031#issuecomment-433668473 * test(e2e): fix tests * test(e2e): missing widgets build * fix: missing widgets dep
477 lines
12 KiB
JavaScript
477 lines
12 KiB
JavaScript
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, clearModule } = require('@vue/cli-shared-utils')
|
|
const { progress: installProgress } = require('@vue/cli/lib/util/installDeps')
|
|
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')
|
|
// 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) {
|
|
let 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 = 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)
|
|
)
|
|
const info = {
|
|
id: key,
|
|
name: key === 'default' ? 'org.vue.views.project-create.tabs.presets.default-preset' : key,
|
|
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)
|
|
|
|
// Delete existing folder
|
|
if (fs.existsSync(targetDir)) {
|
|
if (input.force) {
|
|
setProgress({
|
|
info: 'Cleaning folder...'
|
|
})
|
|
await folders.delete(targetDir)
|
|
setProgress({
|
|
info: null
|
|
})
|
|
} else {
|
|
throw new Error(`Folder ${targetDir} already exists`)
|
|
}
|
|
}
|
|
|
|
cwd.set(targetDir, context)
|
|
creator.context = targetDir
|
|
|
|
process.env.VUE_CLI_CONTEXT = targetDir
|
|
clearModule('@vue/cli-service/webpack.config.js', targetDir)
|
|
|
|
const inCurrent = input.folder === '.'
|
|
const name = inCurrent ? path.relative('../', process.cwd()) : input.folder
|
|
creator.name = name
|
|
|
|
// Answers
|
|
const answers = prompts.getAnswers()
|
|
await prompts.reset()
|
|
let index
|
|
|
|
// Config files
|
|
if ((index = answers.features.indexOf('use-config-files')) !== -1) {
|
|
answers.features.splice(index, 1)
|
|
answers.useConfigFiles = 'files'
|
|
}
|
|
|
|
const createOptions = {
|
|
packageManager: input.packageManager
|
|
}
|
|
// Git
|
|
if (input.enableGit && input.gitCommitMessage) {
|
|
createOptions.git = input.gitCommitMessage
|
|
} else {
|
|
createOptions.git = input.enableGit
|
|
}
|
|
|
|
// 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
|
|
await creator.create(createOptions, preset)
|
|
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 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,
|
|
initCreator,
|
|
removeCreator,
|
|
getType,
|
|
getHomepage
|
|
}
|