mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-01-19 05:40:13 -06:00
596 lines
15 KiB
JavaScript
596 lines
15 KiB
JavaScript
const path = require('path')
|
|
// Connectors
|
|
const logs = require('../connectors/logs')
|
|
const sharedData = require('../connectors/shared-data')
|
|
const views = require('../connectors/views')
|
|
const suggestions = require('../connectors/suggestions')
|
|
const folders = require('../connectors/folders')
|
|
const progress = require('../connectors/progress')
|
|
// Utils
|
|
const ipc = require('../util/ipc')
|
|
const { notify } = require('../util/notification')
|
|
const { matchesPluginId } = require('@vue/cli-shared-utils')
|
|
const { log } = require('../util/logger')
|
|
// Validators
|
|
const { validateConfiguration } = require('./configuration')
|
|
const { validateDescribeTask, validateAddTask } = require('./task')
|
|
const { validateClientAddon } = require('./client-addon')
|
|
const { validateView, validateBadge } = require('./view')
|
|
const { validateNotify } = require('./notify')
|
|
const { validateSuggestion } = require('./suggestion')
|
|
const { validateProgress } = require('./progress')
|
|
|
|
class PluginApi {
|
|
constructor ({ plugins, file, project, lightMode = false }, context) {
|
|
// Context
|
|
this.context = context
|
|
this.pluginId = null
|
|
this.project = project
|
|
this.plugins = plugins
|
|
this.cwd = file
|
|
this.lightMode = lightMode
|
|
// Hooks
|
|
this.hooks = {
|
|
projectOpen: [],
|
|
pluginReload: [],
|
|
configRead: [],
|
|
configWrite: [],
|
|
taskRun: [],
|
|
taskExit: [],
|
|
taskOpen: [],
|
|
viewOpen: []
|
|
}
|
|
// Data
|
|
this.configurations = []
|
|
this.describedTasks = []
|
|
this.addedTasks = []
|
|
this.clientAddons = []
|
|
this.views = []
|
|
this.actions = new Map()
|
|
this.ipcHandlers = []
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when the project is open (only if this plugin is loaded).
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onProjectOpen (cb) {
|
|
if (this.lightMode) return
|
|
if (this.project) {
|
|
cb(this.project)
|
|
return
|
|
}
|
|
this.hooks.projectOpen.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when the plugin is reloaded.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onPluginReload (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.pluginReload.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when a config is red.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onConfigRead (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.configRead.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when a config is written.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onConfigWrite (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.configWrite.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when a task is run.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onTaskRun (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.taskRun.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when a task has exited.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onTaskExit (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.taskExit.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when the user opens one task details.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onTaskOpen (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.taskOpen.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Register an handler called when a view is open.
|
|
*
|
|
* @param {function} cb Handler
|
|
*/
|
|
onViewOpen (cb) {
|
|
if (this.lightMode) return
|
|
this.hooks.viewOpen.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Describe a project configuration (usually for config file like `.eslintrc.json`).
|
|
*
|
|
* @param {object} options Configuration description
|
|
*/
|
|
describeConfig (options) {
|
|
if (this.lightMode) return
|
|
try {
|
|
validateConfiguration(options)
|
|
this.configurations.push({
|
|
...options,
|
|
pluginId: this.pluginId
|
|
})
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'describeConfig' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Describe a project task with additional information.
|
|
* The tasks are generated from the scripts in the project `package.json`.
|
|
*
|
|
* @param {object} options Task description
|
|
*/
|
|
describeTask (options) {
|
|
try {
|
|
validateDescribeTask(options)
|
|
this.describedTasks.push({
|
|
...options,
|
|
pluginId: this.pluginId
|
|
})
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'describeTask' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the task description matching a script command.
|
|
*
|
|
* @param {string} command Npm script command from `package.json`
|
|
* @returns {object} Task description
|
|
*/
|
|
getDescribedTask (command) {
|
|
return this.describedTasks.find(
|
|
options => options.match.test(command)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Add a new task indepently from the scripts.
|
|
* The task will only appear in the UI.
|
|
*
|
|
* @param {object} options Task description
|
|
*/
|
|
addTask (options) {
|
|
try {
|
|
validateAddTask(options)
|
|
this.addedTasks.push({
|
|
...options,
|
|
pluginId: this.pluginId
|
|
})
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'addTask' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register a client addon (a JS bundle which will be loaded in the browser).
|
|
* Used to load components and add vue-router routes.
|
|
*
|
|
* @param {object} options Client addon options
|
|
* {
|
|
* id: string,
|
|
* url: string
|
|
* }
|
|
* or
|
|
* {
|
|
* id: string,
|
|
* path: string
|
|
* }
|
|
*/
|
|
addClientAddon (options) {
|
|
if (this.lightMode) return
|
|
try {
|
|
validateClientAddon(options)
|
|
if (options.url && options.path) {
|
|
throw new Error(`'url' and 'path' can't be defined at the same time.`)
|
|
}
|
|
this.clientAddons.push({
|
|
...options,
|
|
pluginId: this.pluginId
|
|
})
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'addClientAddon' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/* Project view */
|
|
|
|
/**
|
|
* Add a new project view below the builtin 'plugins', 'config' and 'tasks' ones.
|
|
*
|
|
* @param {object} options ProjectView options
|
|
*/
|
|
addView (options) {
|
|
if (this.lightMode) return
|
|
try {
|
|
validateView(options)
|
|
this.views.push({
|
|
...options,
|
|
pluginId: this.pluginId
|
|
})
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'addView' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a badge to the project view button.
|
|
* If the badge already exists, add 1 to the counter.
|
|
*
|
|
* @param {string} viewId Project view id
|
|
* @param {object} options Badge options
|
|
*/
|
|
addViewBadge (viewId, options) {
|
|
if (this.lightMode) return
|
|
try {
|
|
validateBadge(options)
|
|
views.addBadge({ viewId, badge: options }, this.context)
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'addViewBadge' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove 1 from the counter of a badge if it exists.
|
|
* If the badge counter reaches 0, it is removed from the button.
|
|
*
|
|
* @param {any} viewId
|
|
* @param {any} badgeId
|
|
* @memberof PluginApi
|
|
*/
|
|
removeViewBadge (viewId, badgeId) {
|
|
views.removeBadge({ viewId, badgeId }, this.context)
|
|
}
|
|
|
|
/* IPC */
|
|
|
|
/**
|
|
* Add a listener to the IPC messages.
|
|
*
|
|
* @param {function} cb Callback with 'data' param
|
|
*/
|
|
ipcOn (cb) {
|
|
this.ipcHandlers.push(cb)
|
|
return ipc.on(cb)
|
|
}
|
|
|
|
/**
|
|
* Remove a listener for IPC messages.
|
|
*
|
|
* @param {any} cb Callback to be removed
|
|
*/
|
|
ipcOff (cb) {
|
|
const index = this.ipcHandlers.indexOf(cb)
|
|
if (index !== -1) this.ipcHandlers.splice(index, 1)
|
|
ipc.off(cb)
|
|
}
|
|
|
|
/**
|
|
* Send an IPC message to all connected IPC clients.
|
|
*
|
|
* @param {any} data Message data
|
|
*/
|
|
ipcSend (data) {
|
|
ipc.send(data)
|
|
}
|
|
|
|
/**
|
|
* Get the local DB instance (lowdb)
|
|
*
|
|
* @readonly
|
|
*/
|
|
get db () {
|
|
return this.context.db
|
|
}
|
|
|
|
/**
|
|
* Display a notification in the user OS
|
|
* @param {object} options Notification options
|
|
*/
|
|
notify (options) {
|
|
try {
|
|
validateNotify(options)
|
|
notify(options)
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'notify' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates if a specific plugin is used by the project
|
|
* @param {string} id Plugin id or short id
|
|
*/
|
|
hasPlugin (id) {
|
|
if (id === 'router') id = 'vue-router'
|
|
if (['vue-router', 'vuex'].includes(id)) {
|
|
const pkg = folders.readPackage(this.cwd, this.context, true)
|
|
return ((pkg.dependencies && pkg.dependencies[id]) || (pkg.devDependencies && pkg.devDependencies[id]))
|
|
}
|
|
return this.plugins.some(p => matchesPluginId(id, p.id))
|
|
}
|
|
|
|
/**
|
|
* Display the progress screen.
|
|
*
|
|
* @param {object} options Progress options
|
|
*/
|
|
setProgress (options) {
|
|
if (this.lightMode) return
|
|
try {
|
|
validateProgress(options)
|
|
progress.set({
|
|
...options,
|
|
id: '__plugins__'
|
|
}, this.context)
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'setProgress' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the progress screen.
|
|
*/
|
|
removeProgress () {
|
|
progress.remove('__plugins__', this.context)
|
|
}
|
|
|
|
/**
|
|
* Get current working directory.
|
|
*/
|
|
getCwd () {
|
|
return this.cwd
|
|
}
|
|
|
|
/**
|
|
* Resolves a file relative to current working directory
|
|
* @param {string} file Path to file relative to project
|
|
*/
|
|
resolve (file) {
|
|
return path.resolve(this.cwd, file)
|
|
}
|
|
|
|
/**
|
|
* Get currently open project
|
|
*/
|
|
getProject () {
|
|
return this.project
|
|
}
|
|
|
|
/* Namespaced */
|
|
|
|
/**
|
|
* Retrieve a Shared data value.
|
|
*
|
|
* @param {string} id Id of the Shared data
|
|
* @returns {any} Shared data value
|
|
*/
|
|
getSharedData (id) {
|
|
return sharedData.get({ id, projectId: this.project.id }, this.context)
|
|
}
|
|
|
|
/**
|
|
* Set or update the value of a Shared data
|
|
*
|
|
* @param {string} id Id of the Shared data
|
|
* @param {any} value Value of the Shared data
|
|
*/
|
|
setSharedData (id, value) {
|
|
sharedData.set({ id, projectId: this.project.id, value }, this.context)
|
|
}
|
|
|
|
/**
|
|
* Delete a shared data.
|
|
*
|
|
* @param {string} id Id of the Shared data
|
|
*/
|
|
removeSharedData (id) {
|
|
sharedData.remove({ id, projectId: this.project.id }, this.context)
|
|
}
|
|
|
|
/**
|
|
* Watch for a value change of a shared data
|
|
*
|
|
* @param {string} id Id of the Shared data
|
|
* @param {function} handler Callback
|
|
*/
|
|
watchSharedData (id, handler) {
|
|
sharedData.watch({ id, projectId: this.project.id }, handler)
|
|
}
|
|
|
|
/**
|
|
* Delete the watcher of a shared data.
|
|
*
|
|
* @param {string} id Id of the Shared data
|
|
* @param {function} handler Callback
|
|
*/
|
|
unwatchSharedData (id, handler) {
|
|
sharedData.unwatch({ id, projectId: this.project.id }, handler)
|
|
}
|
|
|
|
/**
|
|
* Listener triggered when a Plugin action is called from a client addon component.
|
|
*
|
|
* @param {string} id Id of the action to listen
|
|
* @param {any} cb Callback (ex: (params) => {} )
|
|
*/
|
|
onAction (id, cb) {
|
|
let list = this.actions.get(id)
|
|
if (!list) {
|
|
list = []
|
|
this.actions.set(id, list)
|
|
}
|
|
list.push(cb)
|
|
}
|
|
|
|
/**
|
|
* Call a Plugin action. This can also listened by client addon components.
|
|
*
|
|
* @param {string} id Id of the action
|
|
* @param {object} params Params object passed as 1st argument to callbacks
|
|
* @returns {Promise}
|
|
*/
|
|
callAction (id, params) {
|
|
const plugins = require('../connectors/plugins')
|
|
return plugins.callAction({ id, params }, this.context)
|
|
}
|
|
|
|
/**
|
|
* Retrieve a value from the local DB
|
|
*
|
|
* @param {string} id Path to the item
|
|
* @returns Item value
|
|
*/
|
|
storageGet (id) {
|
|
return this.db.get(id).value()
|
|
}
|
|
|
|
/**
|
|
* Store a value into the local DB
|
|
*
|
|
* @param {string} id Path to the item
|
|
* @param {any} value Value to be stored (must be serializable in JSON)
|
|
*/
|
|
storageSet (id, value) {
|
|
log('Storage set', id, value)
|
|
this.db.set(id, value).write()
|
|
}
|
|
|
|
/**
|
|
* Add a suggestion for the user.
|
|
*
|
|
* @param {object} options Suggestion
|
|
*/
|
|
addSuggestion (options) {
|
|
if (this.lightMode) return
|
|
try {
|
|
validateSuggestion(options)
|
|
suggestions.add(options, this.context)
|
|
} catch (e) {
|
|
logs.add({
|
|
type: 'error',
|
|
tag: 'PluginApi',
|
|
message: `(${this.pluginId || 'unknown plugin'}) 'addSuggestion' options are invalid\n${e.message}`
|
|
}, this.context)
|
|
console.error(new Error(`Invalid options: ${e.message}`))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a suggestion
|
|
*
|
|
* @param {string} id Id of the suggestion
|
|
*/
|
|
removeSuggestion (id) {
|
|
suggestions.remove(id, this.context)
|
|
}
|
|
|
|
/**
|
|
* Create a namespaced version of:
|
|
* - getSharedData
|
|
* - setSharedData
|
|
* - onAction
|
|
* - callAction
|
|
*
|
|
* @param {string} namespace Prefix to add to the id params
|
|
* @returns {object} Namespaced methods
|
|
*/
|
|
namespace (namespace) {
|
|
return {
|
|
getSharedData: (id) => this.getSharedData(namespace + id),
|
|
setSharedData: (id, value) => this.setSharedData(namespace + id, value),
|
|
removeSharedData: (id) => this.removeSharedData(namespace + id),
|
|
watchSharedData: (id, handler) => this.watchSharedData(namespace + id, handler),
|
|
unwatchSharedData: (id, handler) => this.unwatchSharedData(namespace + id, handler),
|
|
onAction: (id, cb) => this.onAction(namespace + id, cb),
|
|
callAction: (id, params) => this.callAction(namespace + id, params),
|
|
storageGet: (id) => this.storageGet(namespace + id),
|
|
storageSet: (id, value) => this.storageSet(namespace + id, value),
|
|
addSuggestion: (options) => {
|
|
options.id = namespace + options.id
|
|
return this.addSuggestion(options)
|
|
},
|
|
removeSuggestion: (id) => this.removeSuggestion(namespace + id)
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = PluginApi
|