mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-03-15 13:40:59 -05:00
feat(ui): install/uninstall plugin
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="instant-search-input">
|
||||
<VueInput
|
||||
ref="input"
|
||||
icon-left="search"
|
||||
v-model="query"
|
||||
class="big"
|
||||
@@ -55,6 +56,10 @@ export default {
|
||||
}
|
||||
this.searchStore.start()
|
||||
this.searchStore.refresh()
|
||||
},
|
||||
|
||||
focus () {
|
||||
this.$refs.input.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
:class="{
|
||||
selected,
|
||||
loaded,
|
||||
error,
|
||||
vuejs: image && image.includes('vuejs')
|
||||
}"
|
||||
>
|
||||
@@ -13,11 +14,12 @@
|
||||
icon="done"
|
||||
/>
|
||||
<img
|
||||
v-else-if="image"
|
||||
v-else-if="image && !error"
|
||||
class="image"
|
||||
:src="image"
|
||||
:key="image"
|
||||
@load="loaded = true"
|
||||
@error="error = true"
|
||||
>
|
||||
<VueIcon
|
||||
v-else
|
||||
@@ -48,13 +50,20 @@ export default {
|
||||
|
||||
data () {
|
||||
return {
|
||||
loaded: false
|
||||
loaded: false,
|
||||
error: false
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
image (value) {
|
||||
image: 'reset',
|
||||
selected: 'reset'
|
||||
},
|
||||
|
||||
methods: {
|
||||
reset () {
|
||||
this.loaded = false
|
||||
this.error = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,10 +106,14 @@ export default {
|
||||
animation zoom .1s
|
||||
transform none
|
||||
|
||||
&.selected,
|
||||
&.error
|
||||
.wrapper
|
||||
animation zoom .1s
|
||||
|
||||
&.selected
|
||||
.wrapper
|
||||
background $vue-color-primary
|
||||
animation zoom .1s
|
||||
.vue-icon
|
||||
>>> svg
|
||||
fill $vue-color-light
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
attribute-name="description"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="official" class="info">
|
||||
<VueIcon icon="star" class="top medium"/>
|
||||
<span>Official</span>
|
||||
</span>
|
||||
<span class="info downloads">
|
||||
<VueIcon class="medium" icon="file_download"/>
|
||||
<span>{{ pkg.humanDownloadsLast30Days }}</span>
|
||||
@@ -56,6 +60,12 @@ export default {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
official () {
|
||||
return this.pkg.owner.name === 'vuejs'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
CWD_CHANGED: 'cwd_changed',
|
||||
PROGRESS_CHANGED: 'progress_changed',
|
||||
PROGRESS_REMOVED: 'progress_removed',
|
||||
CONSOLE_LOG_ADDED: 'console_log_added'
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const LRU = require('lru-cache')
|
||||
const { isPlugin, isOfficialPlugin, getPluginLink } = require('@vue/cli-shared-utils')
|
||||
const {
|
||||
isPlugin,
|
||||
isOfficialPlugin,
|
||||
getPluginLink,
|
||||
hasYarn
|
||||
} = require('@vue/cli-shared-utils')
|
||||
const getPackageVersion = require('@vue/cli/lib/util/getPackageVersion')
|
||||
const {
|
||||
progress: installProgress,
|
||||
installPackage,
|
||||
uninstallPackage
|
||||
} = require('@vue/cli/lib/util/installDeps')
|
||||
const { loadOptions } = require('@vue/cli/lib/options')
|
||||
|
||||
const cwd = require('./cwd')
|
||||
const folders = require('./folders')
|
||||
const prompts = require('./prompts')
|
||||
const progress = require('./progress')
|
||||
|
||||
const metadataCache = new LRU({
|
||||
max: 200,
|
||||
@@ -16,6 +29,11 @@ const logoCache = new LRU({
|
||||
max: 50
|
||||
})
|
||||
|
||||
const PROGRESS_ID = 'plugin-installation'
|
||||
|
||||
let currentPluginId
|
||||
let eventsInstalled = false
|
||||
|
||||
function getPath (id) {
|
||||
return path.join(cwd.get(), 'node_modules', id)
|
||||
}
|
||||
@@ -108,9 +126,82 @@ async function getLogo ({ id }, context) {
|
||||
return null
|
||||
}
|
||||
|
||||
function getInstallation (context) {
|
||||
if (!eventsInstalled) {
|
||||
eventsInstalled = true
|
||||
|
||||
// Package installation progress events
|
||||
installProgress.on('progress', value => {
|
||||
if (progress.get(PROGRESS_ID)) {
|
||||
progress.set({ id: PROGRESS_ID, progress: value }, context)
|
||||
}
|
||||
})
|
||||
installProgress.on('log', message => {
|
||||
if (progress.get(PROGRESS_ID)) {
|
||||
progress.set({ id: PROGRESS_ID, info: message }, context)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'plugin-install',
|
||||
pluginId: currentPluginId,
|
||||
prompts: prompts.list()
|
||||
}
|
||||
}
|
||||
|
||||
function install (id, context) {
|
||||
return progress.wrap(PROGRESS_ID, context, async setProgress => {
|
||||
setProgress({
|
||||
status: 'plugin-install',
|
||||
args: [id]
|
||||
})
|
||||
|
||||
currentPluginId = id
|
||||
|
||||
const packageManager = loadOptions().packageManager || (hasYarn() ? 'yarn' : 'npm')
|
||||
await installPackage(cwd.get(), packageManager, null, id)
|
||||
|
||||
return getInstallation(context)
|
||||
})
|
||||
}
|
||||
|
||||
function uninstall (id, context) {
|
||||
return progress.wrap(PROGRESS_ID, context, async setProgress => {
|
||||
setProgress({
|
||||
status: 'plugin-uninstall',
|
||||
args: [id]
|
||||
})
|
||||
|
||||
currentPluginId = id
|
||||
|
||||
const packageManager = loadOptions().packageManager || (hasYarn() ? 'yarn' : 'npm')
|
||||
await uninstallPackage(cwd.get(), packageManager, null, id)
|
||||
|
||||
return getInstallation(context)
|
||||
})
|
||||
}
|
||||
|
||||
function invoke (id, context) {
|
||||
return progress.wrap(PROGRESS_ID, context, async setProgress => {
|
||||
setProgress({
|
||||
status: 'plugin-invoke',
|
||||
args: [id]
|
||||
})
|
||||
|
||||
currentPluginId = id
|
||||
// TODO
|
||||
return getInstallation(context)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list,
|
||||
getVersion,
|
||||
getDescription,
|
||||
getLogo
|
||||
getLogo,
|
||||
getInstallation,
|
||||
install,
|
||||
uninstall,
|
||||
invoke
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ function set (data, context) {
|
||||
status: null,
|
||||
error: null,
|
||||
info: null,
|
||||
args: null,
|
||||
progress: -1
|
||||
}, progress))
|
||||
} else {
|
||||
@@ -25,6 +26,7 @@ function set (data, context) {
|
||||
}
|
||||
|
||||
function remove (id, context) {
|
||||
context.pubsub.publish(channels.PROGRESS_REMOVED, { progressRemoved: { id } })
|
||||
return map.delete(id)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ module.exports = {
|
||||
foldersFavorite: (root, args, context) => folders.listFavorite(context),
|
||||
projects: (root, args, context) => projects.list(context),
|
||||
projectCurrent: (root, args, context) => projects.getCurrent(context),
|
||||
projectCreation: (root, args, context) => projects.getCreation(context)
|
||||
projectCreation: (root, args, context) => projects.getCreation(context),
|
||||
pluginInstallation: (root, args, context) => plugins.getInstallation(context)
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
@@ -60,7 +61,10 @@ module.exports = {
|
||||
projectImport: (root, { input }, context) => projects.import(input, context),
|
||||
projectOpen: (root, { id }, context) => projects.open(id, context),
|
||||
projectRemove: (root, { id }, context) => projects.remove(id, context),
|
||||
projectCwdReset: (root, args, context) => projects.resetCwd(context)
|
||||
projectCwdReset: (root, args, context) => projects.resetCwd(context),
|
||||
pluginInstall: (root, { id }, context) => plugins.install(id, context),
|
||||
pluginUninstall: (root, { id }, context) => plugins.uninstall(id, context),
|
||||
pluginInvoke: (root, { id }, context) => plugins.invoke(id, context)
|
||||
},
|
||||
|
||||
Subscription: {
|
||||
@@ -75,6 +79,14 @@ module.exports = {
|
||||
(payload, variables) => payload.progressChanged.id === variables.id
|
||||
)
|
||||
},
|
||||
progressRemoved: {
|
||||
subscribe: withFilter(
|
||||
// Iterator
|
||||
(parent, args, { pubsub }) => pubsub.asyncIterator(channels.PROGRESS_REMOVED),
|
||||
// Filter
|
||||
(payload, variables) => payload.progressRemoved.id === variables.id
|
||||
)
|
||||
},
|
||||
consoleLogAdded: {
|
||||
subscribe: (parent, args, context) => {
|
||||
logs.init(context)
|
||||
|
||||
@@ -84,10 +84,15 @@ type Plugin {
|
||||
website: String
|
||||
description: String
|
||||
githubStats: GitHubStats
|
||||
prompts: [Prompt]
|
||||
logo: String
|
||||
}
|
||||
|
||||
type PluginInstallation {
|
||||
id: ID!
|
||||
pluginId: ID
|
||||
prompts: [Prompt]
|
||||
}
|
||||
|
||||
type Feature {
|
||||
id: ID!
|
||||
name: String!
|
||||
@@ -145,6 +150,7 @@ type Progress {
|
||||
error: String
|
||||
# Progress from 0 to 1 (-1 means disabled)
|
||||
progress: Float
|
||||
args: [String]
|
||||
}
|
||||
|
||||
type Query {
|
||||
@@ -157,6 +163,7 @@ type Query {
|
||||
projects: [Project]
|
||||
projectCurrent: Project
|
||||
projectCreation: ProjectCreation
|
||||
pluginInstallation: PluginInstallation
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -172,12 +179,15 @@ type Mutation {
|
||||
projectCwdReset: String
|
||||
presetApply (id: ID!): ProjectCreation
|
||||
featureSetEnabled (id: ID!, enabled: Boolean): Feature
|
||||
pluginAdd (id: ID!): Plugin
|
||||
promptAnswer (input: PromptInput!): [Prompt]
|
||||
pluginInstall (id: ID!): PluginInstallation
|
||||
pluginUninstall (id: ID!): PluginInstallation
|
||||
pluginInvoke (id: ID!): PluginInstallation
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
progressChanged (id: ID!): Progress
|
||||
progressRemoved (id: ID!): ID
|
||||
consoleLogAdded: ConsoleLog!
|
||||
cwdChanged: String!
|
||||
}
|
||||
|
||||
7
packages/@vue/cli-ui/src/graphql/pluginInstall.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/pluginInstall.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./pluginInstallationFragment.gql"
|
||||
|
||||
mutation pluginInstall ($id: ID!) {
|
||||
pluginInstall (id: $id) {
|
||||
...pluginInstallation
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/pluginInstallation.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/pluginInstallation.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./pluginInstallationFragment.gql"
|
||||
|
||||
query pluginInstallation {
|
||||
pluginInstallation {
|
||||
...pluginInstallation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#import "./promptFragment.gql"
|
||||
|
||||
fragment pluginInstallation on PluginInstallation {
|
||||
id
|
||||
prompts {
|
||||
...prompt
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/pluginInvoke.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/pluginInvoke.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./pluginInstallationFragment.gql"
|
||||
|
||||
mutation pluginInvoke ($id: ID!) {
|
||||
pluginInvoke (id: $id) {
|
||||
...pluginInstallation
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/pluginUninstall.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/pluginUninstall.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./pluginInstallationFragment.gql"
|
||||
|
||||
mutation pluginUninstall ($id: ID!) {
|
||||
pluginUninstall (id: $id) {
|
||||
...pluginInstallation
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,5 @@ fragment progress on Progress {
|
||||
info
|
||||
error
|
||||
progress
|
||||
args
|
||||
}
|
||||
|
||||
3
packages/@vue/cli-ui/src/graphql/progressRemoved.gql
Normal file
3
packages/@vue/cli-ui/src/graphql/progressRemoved.gql
Normal file
@@ -0,0 +1,3 @@
|
||||
subscription progressRemoved ($id: ID!) {
|
||||
progressRemoved (id: $id)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import PROGRESS from '../graphql/progress.gql'
|
||||
import PROGRESS_CHANGED from '../graphql/progressChanged.gql'
|
||||
import PROGRESS_REMOVED from '../graphql/progressRemoved.gql'
|
||||
|
||||
const messages = {
|
||||
'creating': 'Creating project...',
|
||||
@@ -9,7 +10,10 @@ const messages = {
|
||||
'deps-install': 'Installing additional dependencies...',
|
||||
'completion-hooks': 'Running completion hooks...',
|
||||
'fetch-remote-preset': `Fetching remote preset...`,
|
||||
'done': 'Successfully created project'
|
||||
'done': 'Successfully created project',
|
||||
'plugin-install': 'Installing {{arg0}}',
|
||||
'plugin-uninstall': 'Uninstalling {{arg0}}',
|
||||
'plugin-invoke': 'Invoking {{arg0}}'
|
||||
}
|
||||
|
||||
// @vue/component
|
||||
@@ -36,19 +40,34 @@ export default {
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
subscribeToMore: {
|
||||
document: PROGRESS_CHANGED,
|
||||
variables () {
|
||||
return {
|
||||
id: this.progressId
|
||||
subscribeToMore: [
|
||||
{
|
||||
document: PROGRESS_CHANGED,
|
||||
variables () {
|
||||
return {
|
||||
id: this.progressId
|
||||
}
|
||||
},
|
||||
updateQuery: (previousResult, { subscriptionData }) => {
|
||||
return {
|
||||
progress: subscriptionData.data.progressChanged
|
||||
}
|
||||
}
|
||||
},
|
||||
updateQuery: (previousResult, { subscriptionData }) => {
|
||||
return {
|
||||
progress: subscriptionData.data.progressChanged
|
||||
{
|
||||
document: PROGRESS_REMOVED,
|
||||
variables () {
|
||||
return {
|
||||
id: this.progressId
|
||||
}
|
||||
},
|
||||
updateQuery: () => {
|
||||
return {
|
||||
progress: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -61,7 +80,15 @@ export default {
|
||||
if (!this.progress) return null
|
||||
|
||||
const { status } = this.progress
|
||||
const message = messages[status]
|
||||
let message = messages[status]
|
||||
if (message && this.progress.args) {
|
||||
for (let i = 0, l = this.progress.args.length; i < l; i++) {
|
||||
message = message.replace(
|
||||
new RegExp(`{{arg${i}}}`, 'g'),
|
||||
this.progress.args[i]
|
||||
)
|
||||
}
|
||||
}
|
||||
return message || status || ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ ansi-colors('white', $vue-color-light)
|
||||
overflow-y auto
|
||||
margin $padding-item 0
|
||||
|
||||
.ais-no-results
|
||||
margin-top 42px
|
||||
|
||||
.ais-highlight
|
||||
em
|
||||
font-style normal
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
attributesToHighlight: [
|
||||
'name',
|
||||
'description'
|
||||
]
|
||||
],
|
||||
// filters: `keywords:vue-cli-plugin`
|
||||
}"
|
||||
>
|
||||
<InstantSearchInput/>
|
||||
<InstantSearchInput ref="searchInput"/>
|
||||
<ais-results>
|
||||
<PackageSearchItem
|
||||
slot-scope="{ result }"
|
||||
@@ -44,6 +45,12 @@
|
||||
@click.native="selectedId = result.name"
|
||||
/>
|
||||
</ais-results>
|
||||
<ais-no-results>
|
||||
<div class="vue-empty">
|
||||
<VueIcon icon="search" class="huge"/>
|
||||
<div>No results found</div>
|
||||
</div>
|
||||
</ais-no-results>
|
||||
<InstantSearchPagination/>
|
||||
</ais-index>
|
||||
</div>
|
||||
@@ -61,7 +68,7 @@
|
||||
</div>
|
||||
|
||||
<VueButton
|
||||
icon-left="add"
|
||||
icon-left="file_download"
|
||||
:label="`Install ${selectedId || 'plugin'}`"
|
||||
class="big primary"
|
||||
:disabled="!selectedId"
|
||||
@@ -136,10 +143,19 @@
|
||||
/>
|
||||
</div>
|
||||
</VueModal>
|
||||
|
||||
<ProgressScreen
|
||||
progress-id="plugin-installation"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PLUGIN_INSTALLATION from '../graphql/pluginInstallation.gql'
|
||||
import PLUGIN_INSTALL from '../graphql/pluginInstall.gql'
|
||||
import PLUGIN_UNINSTALL from '../graphql/pluginUninstall.gql'
|
||||
import PROMPT_ANSWER from '../graphql/promptAnswer.gql'
|
||||
|
||||
export default {
|
||||
name: 'ProjectPluginsAdd',
|
||||
|
||||
@@ -148,7 +164,15 @@ export default {
|
||||
tabId: 'search',
|
||||
selectedId: null,
|
||||
enabledPrompts: [],
|
||||
showCancelInstall: false
|
||||
showCancelInstall: false,
|
||||
pluginInstallation: null
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
pluginInstallation: {
|
||||
query: PLUGIN_INSTALLATION,
|
||||
fetchPolicy: 'netork-only'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -158,14 +182,29 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
requestAnimationFrame(() => {
|
||||
this.$refs.searchInput.focus()
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
cancel () {
|
||||
this.$router.push({ name: 'project-home' })
|
||||
},
|
||||
|
||||
installPlugin () {
|
||||
// TODO
|
||||
this.tabId = 'config'
|
||||
async installPlugin () {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: PLUGIN_INSTALL,
|
||||
variables: {
|
||||
id: this.selectedId
|
||||
}
|
||||
})
|
||||
this.tabId = 'config'
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
|
||||
cancelInstall () {
|
||||
@@ -174,18 +213,41 @@ export default {
|
||||
this.showCancelInstall = false
|
||||
},
|
||||
|
||||
uninstallPlugin () {
|
||||
// TODO
|
||||
this.cancelInstall()
|
||||
async uninstallPlugin () {
|
||||
this.showCancelInstall = false
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: PLUGIN_UNINSTALL,
|
||||
variables: {
|
||||
id: this.selectedId
|
||||
}
|
||||
})
|
||||
this.cancelInstall()
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
|
||||
invokePlugin () {
|
||||
async invokePlugin () {
|
||||
// TODO
|
||||
},
|
||||
|
||||
answerPrompt () {
|
||||
// TODO
|
||||
}
|
||||
async answerPrompt ({ prompt, value }) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: PROMPT_ANSWER,
|
||||
variables: {
|
||||
input: {
|
||||
id: prompt.id,
|
||||
value: JSON.stringify(value)
|
||||
}
|
||||
},
|
||||
update: (store, { data: { promptAnswer } }) => {
|
||||
const data = store.readQuery({ query: PLUGIN_INSTALLATION })
|
||||
data.pluginInstallation.prompts = promptAnswer
|
||||
store.writeQuery({ query: PLUGIN_INSTALLATION, data })
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -281,3 +281,23 @@ exports.installPackage = async function (targetDir, command, cliRegistry, packag
|
||||
|
||||
await executeCommand(command, args, targetDir)
|
||||
}
|
||||
|
||||
exports.uninstallPackage = async function (targetDir, command, cliRegistry, packageName) {
|
||||
const args = []
|
||||
if (command === 'npm') {
|
||||
args.push('uninstall', '--loglevel', 'error')
|
||||
} else if (command === 'yarn') {
|
||||
args.push('remove')
|
||||
} else {
|
||||
throw new Error(`Unknown package manager: ${command}`)
|
||||
}
|
||||
|
||||
await addRegistryToArgs(command, args, cliRegistry)
|
||||
|
||||
args.push(packageName)
|
||||
|
||||
debug(`command: `, command)
|
||||
debug(`args: `, args)
|
||||
|
||||
await executeCommand(command, args, targetDir)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user