mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-05-03 10:32:10 -05:00
feat(ui): client addons, ipc, shared data, plugin actions
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ module.exports = api => {
|
||||
description: 'Do not fix errors'
|
||||
}
|
||||
],
|
||||
onRun: ({ answers, args }) => {
|
||||
onBeforeRun: ({ answers, args }) => {
|
||||
if (answers.noFix) {
|
||||
args.push('--no-fix')
|
||||
}
|
||||
|
||||
@@ -97,6 +97,14 @@ module.exports = (api, options) => {
|
||||
targetDir
|
||||
)
|
||||
|
||||
// Expose advanced stats
|
||||
if (args.dashboard) {
|
||||
const DashboardPlugin = require('../../webpack/DashboardPlugin')
|
||||
;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({
|
||||
type: 'build'
|
||||
}))
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
rimraf(targetDir, err => {
|
||||
if (err) {
|
||||
|
||||
@@ -49,6 +49,16 @@ module.exports = (api, options) => {
|
||||
return portPromise.then(port => new Promise((resolve, reject) => {
|
||||
const webpackConfig = api.resolveWebpackConfig()
|
||||
|
||||
// Expose advanced stats
|
||||
if (args.dashboard) {
|
||||
const DashboardPlugin = require('../webpack/DashboardPlugin')
|
||||
;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({
|
||||
type: 'serve',
|
||||
gzip: false,
|
||||
minified: false
|
||||
}))
|
||||
}
|
||||
|
||||
const urls = prepareURLs(
|
||||
useHttps ? 'https' : 'http',
|
||||
host,
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
// From https://github.com/FormidableLabs/webpack-dashboard/blob/master/plugin/index.js
|
||||
// Modified by Guillaume Chau (Akryum)
|
||||
/* eslint-disable max-params, max-statements */
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const most = require('most')
|
||||
const webpack = require('webpack')
|
||||
const InspectpackDaemon = require('inspectpack').daemon
|
||||
const flatten = require('lodash.flatten')
|
||||
const zlib = require('zlib')
|
||||
const ipc = require('node-ipc')
|
||||
|
||||
const ONE_SECOND = 1000
|
||||
const INSPECTPACK_PROBLEM_ACTIONS = ['versions', 'duplicates']
|
||||
const INSPECTPACK_PROBLEM_TYPE = 'problems'
|
||||
|
||||
ipc.config.id = 'vue-cli'
|
||||
ipc.config.retry = 1500
|
||||
ipc.config.silent = true
|
||||
|
||||
let sendMessage
|
||||
|
||||
ipc.connectTo('vue-cli', () => {
|
||||
sendMessage = data => ipc.of['vue-cli'].emit('message', data)
|
||||
})
|
||||
|
||||
const cacheFilename = path.resolve(os.homedir(), '.webpack-dashboard-cache.db')
|
||||
|
||||
const serializeError = err => ({
|
||||
code: err.code,
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
})
|
||||
|
||||
function getTimeMessage (timer) {
|
||||
let time = Date.now() - timer
|
||||
|
||||
if (time >= ONE_SECOND) {
|
||||
time /= ONE_SECOND
|
||||
time = Math.round(time)
|
||||
time += 's'
|
||||
} else {
|
||||
time += 'ms'
|
||||
}
|
||||
|
||||
return ` (${time})`
|
||||
}
|
||||
|
||||
function getGzipSize (buffer) {
|
||||
return zlib.gzipSync(buffer).length
|
||||
}
|
||||
|
||||
class DashboardPlugin {
|
||||
constructor (options) {
|
||||
if (typeof options === 'function') {
|
||||
this.handler = options
|
||||
} else {
|
||||
options = options || {}
|
||||
this.root = options.root
|
||||
this.gzip = !(options.gzip === false)
|
||||
// `gzip = true` implies `minified = true`.
|
||||
this.minified = this.gzip || !(options.minified === false)
|
||||
this.handler = options.handler || null
|
||||
this.type = options.type
|
||||
}
|
||||
|
||||
this.cleanup = this.cleanup.bind(this)
|
||||
|
||||
this.watching = false
|
||||
}
|
||||
|
||||
cleanup () {
|
||||
if (!this.watching) {
|
||||
this.handler = null
|
||||
if (this.inspectpack) {
|
||||
this.inspectpack.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
ipc.disconnect('vue-cli')
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
apply (compiler) {
|
||||
// Lazily created so plugin can be configured without starting the daemon
|
||||
this.inspectpack = InspectpackDaemon.create({ cacheFilename })
|
||||
|
||||
let handler = this.handler
|
||||
let timer
|
||||
|
||||
let assetSources
|
||||
|
||||
// Enable pathinfo for inspectpack support
|
||||
compiler.options.output.pathinfo = true
|
||||
|
||||
const nodeEnv = process.env.NODE_ENV
|
||||
|
||||
if (!handler) {
|
||||
handler = data => sendMessage && sendMessage({
|
||||
webpackDashboardData: {
|
||||
type: this.type,
|
||||
value: data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
compiler.apply(
|
||||
new webpack.ProgressPlugin((percent, msg) => {
|
||||
handler([
|
||||
{
|
||||
type: 'status',
|
||||
value: 'Compiling'
|
||||
},
|
||||
{
|
||||
type: 'progress',
|
||||
value: percent
|
||||
},
|
||||
{
|
||||
type: 'operations',
|
||||
value: msg + getTimeMessage(timer)
|
||||
}
|
||||
])
|
||||
})
|
||||
)
|
||||
|
||||
compiler.plugin('watch-run', (c, done) => {
|
||||
this.watching = true
|
||||
done()
|
||||
})
|
||||
|
||||
compiler.plugin('run', (c, done) => {
|
||||
this.watching = false
|
||||
done()
|
||||
})
|
||||
|
||||
compiler.plugin('compile', () => {
|
||||
timer = Date.now()
|
||||
handler([
|
||||
{
|
||||
type: 'status',
|
||||
value: 'Compiling'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
compiler.plugin('invalid', () => {
|
||||
handler([
|
||||
{
|
||||
type: 'status',
|
||||
value: 'Invalidated'
|
||||
},
|
||||
{
|
||||
type: 'progress',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
type: 'operations',
|
||||
value: 'idle'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
compiler.plugin('failed', () => {
|
||||
handler([
|
||||
{
|
||||
type: 'status',
|
||||
value: 'Failed'
|
||||
},
|
||||
{
|
||||
type: 'operations',
|
||||
value: `idle${getTimeMessage(timer)}`
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
compiler.plugin('after-emit', (compilation, done) => {
|
||||
assetSources = new Map()
|
||||
for (const name in compilation.assets) {
|
||||
const asset = compilation.assets[name]
|
||||
assetSources.set(name, asset.source())
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
||||
compiler.plugin('done', stats => {
|
||||
const statsData = stats.toJson()
|
||||
const outputPath = compiler.options.output.path
|
||||
statsData.assets.forEach(asset => {
|
||||
asset.fullPath = path.join(outputPath, asset.name)
|
||||
asset.gzipSize = getGzipSize(assetSources.get(asset.name))
|
||||
})
|
||||
|
||||
handler([
|
||||
{
|
||||
type: 'status',
|
||||
value: 'Success'
|
||||
},
|
||||
{
|
||||
type: 'progress',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
type: 'operations',
|
||||
value: `idle${getTimeMessage(timer)}`
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
value: {
|
||||
errors: stats.hasErrors(),
|
||||
warnings: stats.hasWarnings(),
|
||||
data: statsData
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
if (!this.minimal && nodeEnv !== 'production') {
|
||||
this.observeBundleMetrics(stats, this.inspectpack).subscribe({
|
||||
next: message => handler([message]),
|
||||
error: err => {
|
||||
console.log('Error from inspectpack:', err) // eslint-disable-line no-console
|
||||
this.cleanup()
|
||||
},
|
||||
complete: this.cleanup
|
||||
})
|
||||
} else {
|
||||
this.cleanup()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer the root of the project, w/ package.json + node_modules.
|
||||
*
|
||||
* Inspectpack's `version` option needs to know where to start resolving
|
||||
* packages from to translate `~/lodash/index.js` to
|
||||
* `/ACTUAL/PATH/node_modules/index.js`.
|
||||
*
|
||||
* In common practice, this is _usually_ `bundle.context`, but sometimes folks
|
||||
* will set that to a _different_ directory of assets directly copied in or
|
||||
* something.
|
||||
*
|
||||
* To handle varying scenarios, we resolve the project's root as:
|
||||
* 1. Plugin `root` option, if set
|
||||
* 2. `bundle.context`, if `package.json` exists
|
||||
* 3. `process.cwd()`, if `package.json` exists
|
||||
* 4. `null` if nothing else matches
|
||||
*
|
||||
* @param {Object} bundle Bundle
|
||||
* @returns {String|null} Project root path or null
|
||||
*/
|
||||
getProjectRoot (bundle) {
|
||||
/*eslint-disable global-require*/
|
||||
// Start with plugin option (and don't check it).
|
||||
// We **will** allow a bad project root to blow up webpack-dashboard.
|
||||
if (this.root) {
|
||||
return this.root
|
||||
}
|
||||
|
||||
// Try bundle context.
|
||||
try {
|
||||
if (bundle.context && require(path.join(bundle.context, 'package.json'))) {
|
||||
return bundle.context
|
||||
}
|
||||
} catch (err) { /* passthrough */ }
|
||||
|
||||
// Try CWD.
|
||||
try {
|
||||
if (require(path.resolve('package.json'))) {
|
||||
return process.cwd()
|
||||
}
|
||||
} catch (err) { /* passthrough */ }
|
||||
|
||||
// A null will be filtered out, disabling `versions` action.
|
||||
return null
|
||||
}
|
||||
|
||||
observeBundleMetrics (stats, inspectpack) {
|
||||
const bundlesToObserve = Object.keys(stats.compilation.assets)
|
||||
.filter(
|
||||
bundlePath =>
|
||||
// Don't include hot reload assets, they break everything
|
||||
// and the updates are already included in the new assets
|
||||
bundlePath.indexOf('.hot-update.') === -1 &&
|
||||
// Don't parse sourcemaps!
|
||||
path.extname(bundlePath) === '.js'
|
||||
)
|
||||
.map(bundlePath => ({
|
||||
path: bundlePath,
|
||||
context: stats.compilation.options.context,
|
||||
source: stats.compilation.assets[bundlePath].source()
|
||||
}))
|
||||
|
||||
const getSizes = bundles =>
|
||||
Promise.all(
|
||||
bundles.map(bundle =>
|
||||
inspectpack
|
||||
.sizes({
|
||||
code: bundle.source,
|
||||
format: 'object',
|
||||
minified: this.minified,
|
||||
gzip: this.gzip
|
||||
})
|
||||
.then(metrics => ({
|
||||
path: bundle.path,
|
||||
metrics
|
||||
}))
|
||||
)
|
||||
)
|
||||
.then(bundle => ({
|
||||
type: 'sizes',
|
||||
value: bundle
|
||||
}))
|
||||
.catch(err => ({
|
||||
type: 'sizes',
|
||||
error: true,
|
||||
value: serializeError(err)
|
||||
}))
|
||||
|
||||
const getProblems = bundles =>
|
||||
Promise.all(
|
||||
INSPECTPACK_PROBLEM_ACTIONS.map(action =>
|
||||
Promise.all(
|
||||
bundles
|
||||
.map(bundle => {
|
||||
// Root is only needed for versions and we hit disk to check it.
|
||||
// So, only check on actual actions and bail out if not found.
|
||||
let root
|
||||
if (action === 'versions') {
|
||||
root = this.getProjectRoot(bundle)
|
||||
if (!root) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return inspectpack[action]({
|
||||
code: bundle.source,
|
||||
root,
|
||||
duplicates: true,
|
||||
format: 'object',
|
||||
minified: this.minified,
|
||||
gzip: this.gzip
|
||||
})
|
||||
.then(metrics => ({
|
||||
path: bundle.path,
|
||||
[action]: metrics
|
||||
}))
|
||||
})
|
||||
.filter(Boolean) // Filter out incorrect actions.
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(bundle => ({
|
||||
type: INSPECTPACK_PROBLEM_TYPE,
|
||||
value: flatten(bundle)
|
||||
}))
|
||||
.catch(err => ({
|
||||
type: INSPECTPACK_PROBLEM_TYPE,
|
||||
error: true,
|
||||
value: serializeError(err)
|
||||
}))
|
||||
|
||||
const sizesStream = most.of(bundlesToObserve).map(getSizes)
|
||||
const problemsStream = most.of(bundlesToObserve).map(getProblems)
|
||||
|
||||
return most.mergeArray([sizesStream, problemsStream]).chain(most.fromPromise)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DashboardPlugin
|
||||
@@ -40,10 +40,14 @@
|
||||
"get-value": "^3.0.0",
|
||||
"globby": "^8.0.1",
|
||||
"html-webpack-plugin": "^3.0.6",
|
||||
"inspectpack": "^2.2.4",
|
||||
"javascript-stringify": "^1.6.0",
|
||||
"launch-editor-middleware": "^2.2.1",
|
||||
"lodash.defaultsdeep": "^4.6.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"minimist": "^1.2.0",
|
||||
"most": "^1.7.3",
|
||||
"node-ipc": "^9.1.1",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"ora": "^2.0.0",
|
||||
"portfinder": "^1.0.13",
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
module.exports = api => {
|
||||
const { setSharedData } = api.namespace('webpack-dashboard-')
|
||||
|
||||
function resetSharedData (key) {
|
||||
setSharedData(`${key}-status`, null)
|
||||
setSharedData(`${key}-progress`, 0)
|
||||
setSharedData(`${key}-operations`, null)
|
||||
setSharedData(`${key}-stats`, null)
|
||||
setSharedData(`${key}-sizes`, null)
|
||||
setSharedData(`${key}-problems`, null)
|
||||
}
|
||||
|
||||
function onWebpackMessage ({ data: message }) {
|
||||
if (message.webpackDashboardData) {
|
||||
const type = message.webpackDashboardData.type
|
||||
for (const data of message.webpackDashboardData.value) {
|
||||
setSharedData(`${type}-${data.type}`, data.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init data
|
||||
for (const key of ['serve', 'build']) {
|
||||
resetSharedData(key)
|
||||
}
|
||||
|
||||
// Tasks
|
||||
api.describeTask({
|
||||
match: /vue-cli-service serve/,
|
||||
description: 'Compiles and hot-reloads for development',
|
||||
link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#serve',
|
||||
prompts: [
|
||||
{
|
||||
name: 'open',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
description: 'Open browser on server start'
|
||||
},
|
||||
{
|
||||
name: 'mode',
|
||||
type: 'list',
|
||||
default: 'development',
|
||||
choices: [
|
||||
{
|
||||
name: 'development',
|
||||
value: 'development'
|
||||
},
|
||||
{
|
||||
name: 'production',
|
||||
value: 'production'
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
value: 'test'
|
||||
}
|
||||
],
|
||||
description: 'Specify env mode'
|
||||
},
|
||||
{
|
||||
name: 'host',
|
||||
type: 'input',
|
||||
default: '0.0.0.0',
|
||||
description: 'Specify host'
|
||||
},
|
||||
{
|
||||
name: 'port',
|
||||
type: 'input',
|
||||
default: 8080,
|
||||
description: 'Specify port'
|
||||
},
|
||||
{
|
||||
name: 'https',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
description: 'Use HTTPS'
|
||||
}
|
||||
],
|
||||
onBeforeRun: ({ answers, args }) => {
|
||||
// Args
|
||||
if (answers.open) args.push('--open')
|
||||
if (answers.mode) args.push('--mode', answers.mode)
|
||||
if (answers.host) args.push('--host', answers.host)
|
||||
if (answers.port) args.push('--port', answers.port)
|
||||
if (answers.https) args.push('--https')
|
||||
args.push('--dashboard')
|
||||
|
||||
// Data
|
||||
resetSharedData('serve')
|
||||
},
|
||||
onRun: () => {
|
||||
api.ipcOn(onWebpackMessage)
|
||||
},
|
||||
onExit: () => {
|
||||
api.ipcOff(onWebpackMessage)
|
||||
},
|
||||
views: [
|
||||
{
|
||||
id: 'vue-webpack-dashboard',
|
||||
label: 'Dashboard',
|
||||
icon: 'dashboard',
|
||||
component: 'vue-webpack-dashboard'
|
||||
}
|
||||
],
|
||||
defaultView: 'vue-webpack-dashboard'
|
||||
})
|
||||
api.describeTask({
|
||||
match: /vue-cli-service build/,
|
||||
description: 'Compiles and minifies for production',
|
||||
link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#build',
|
||||
prompts: [
|
||||
{
|
||||
name: 'mode',
|
||||
type: 'list',
|
||||
default: 'production',
|
||||
choices: [
|
||||
{
|
||||
name: 'development',
|
||||
value: 'development'
|
||||
},
|
||||
{
|
||||
name: 'production',
|
||||
value: 'production'
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
value: 'test'
|
||||
}
|
||||
],
|
||||
description: 'Specify env mode'
|
||||
},
|
||||
{
|
||||
name: 'dest',
|
||||
type: 'input',
|
||||
default: 'dist',
|
||||
description: 'Output directory'
|
||||
},
|
||||
{
|
||||
name: 'target',
|
||||
type: 'list',
|
||||
default: 'app',
|
||||
choices: [
|
||||
{
|
||||
name: 'Web app',
|
||||
value: 'app'
|
||||
},
|
||||
{
|
||||
name: 'Library',
|
||||
value: 'lib'
|
||||
},
|
||||
{
|
||||
name: 'Web component',
|
||||
value: 'wc'
|
||||
},
|
||||
{
|
||||
name: 'Async web component',
|
||||
value: 'wc-async'
|
||||
}
|
||||
],
|
||||
description: 'Build target'
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'input',
|
||||
default: '',
|
||||
description: 'Name for library or web-component mode (default: "name" in package.json or entry filename)'
|
||||
}
|
||||
],
|
||||
onBeforeRun: ({ answers, args }) => {
|
||||
// Args
|
||||
if (answers.mode) args.push('--mode', answers.mode)
|
||||
if (answers.dest) args.push('--dest', answers.dest)
|
||||
if (answers.target) args.push('--target', answers.target)
|
||||
if (answers.name) args.push('--port', answers.name)
|
||||
args.push('--dashboard')
|
||||
|
||||
// Data
|
||||
resetSharedData('build')
|
||||
},
|
||||
onRun: () => {
|
||||
api.ipcOn(onWebpackMessage)
|
||||
},
|
||||
onExit: () => {
|
||||
api.ipcOff(onWebpackMessage)
|
||||
},
|
||||
views: [
|
||||
{
|
||||
id: 'vue-webpack-dashboard',
|
||||
label: 'Dashboard',
|
||||
icon: 'dashboard',
|
||||
component: 'vue-webpack-dashboard'
|
||||
}
|
||||
],
|
||||
defaultView: 'vue-webpack-dashboard'
|
||||
})
|
||||
|
||||
// Testing client addon
|
||||
api.addClientAddon({
|
||||
id: 'vue-webpack',
|
||||
url: 'http://localhost:8081/app.js'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
"@vue/app"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
],
|
||||
"globals": {
|
||||
"ClientAddonApi": false,
|
||||
"Vue": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "cli-ui-addon-build",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-progress-path": "^0.0.2",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.0.0-beta.6",
|
||||
"@vue/cli-plugin-eslint": "^3.0.0-beta.6",
|
||||
"@vue/cli-service": "^3.0.0-beta.6",
|
||||
"@vue/eslint-config-standard": "^3.0.0-beta.3",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.5.13"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%= webpackConfig.output.publicPath %>favicon.ico">
|
||||
<title>cli-ui-addon-build</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but cli-ui-addon-build doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"global": { "title": "Global Average", "mbps": 7, "rtt": 30 },
|
||||
"edge": { "title": "Mobile Edge", "mbps": 0.24, "rtt": 840 },
|
||||
"2g": { "title": "2G", "mbps": 0.28, "rtt": 800 },
|
||||
"3gs": { "title": "3G Slow", "mbps": 0.4, "rtt": 400 },
|
||||
"3gb": { "title": "3G Basic", "mbps": 1.6, "rtt": 300 },
|
||||
"3gf": { "title": "3G Fast", "mbps": 1.6, "rtt": 150 },
|
||||
"4g": { "title": "4G", "mbps": 9, "rtt": 170 },
|
||||
"lte": { "title": "LTE", "mbps": 12, "rtt": 70 },
|
||||
"dup": { "title": "Dial Up", "mbps": 0.05, "rtt": 120 },
|
||||
"dsl": { "title": "DSL", "mbps": 1.5, "rtt": 50 },
|
||||
"cable": { "title": "Cable", "mbps": 5, "rtt": 28 },
|
||||
"fios": { "title": "FIOS", "mbps": 20, "rtt": 4 }
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="asset-list list-block">
|
||||
<div class="content">
|
||||
<div class="title">Assets</div>
|
||||
|
||||
<VueIcon
|
||||
v-if="!assetsSorted.length"
|
||||
icon="more_horiz"
|
||||
class="blank-icon"
|
||||
/>
|
||||
|
||||
<template v-else>
|
||||
<div class="headers">
|
||||
<div class="header"></div>
|
||||
<div class="header">{{ useGzip ? 'Gzip' : 'Disk' }}</div>
|
||||
<div class="header">Global</div>
|
||||
<div class="header">3G Slow</div>
|
||||
<div class="header">3G Fast</div>
|
||||
<div class="header"></div>
|
||||
</div>
|
||||
|
||||
<div class="list">
|
||||
<AssetListItem
|
||||
v-for="asset of assetsSorted"
|
||||
:key="asset.name"
|
||||
:asset="asset"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import AssetListItem from './AssetListItem'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AssetListItem
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'assetsSorted',
|
||||
'useGzip'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.headers
|
||||
display grid
|
||||
grid-template-columns 5fr repeat(4, 1fr) 16px
|
||||
grid-gap $padding-item
|
||||
font-family $font-mono
|
||||
font-size 12px
|
||||
text-align right
|
||||
margin-bottom $padding-item
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
big: asset.big,
|
||||
secondary: asset.secondary
|
||||
}"
|
||||
class="asset-list-item"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="info name">{{ asset.name }}</div>
|
||||
<div class="info size">{{ asset.size | size('B') }}</div>
|
||||
<div class="info speed global">{{ asset.speeds.global.totalDownloadTime | round(100) }}s</div>
|
||||
<div class="info speed 3gs">{{ asset.speeds['3gs'].totalDownloadTime | round(100) }}s</div>
|
||||
<div class="info speed 3gf">{{ asset.speeds['3gf'].totalDownloadTime | round(100) }}s</div>
|
||||
<div class="info warning">
|
||||
<VueIcon
|
||||
v-if="!asset.secondary && asset.big"
|
||||
icon="warning"
|
||||
class="icon"
|
||||
v-tooltip="'This asset is big, consider using Code splitting to create smaller assets.'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { size, round } from '../filters'
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
size,
|
||||
round
|
||||
},
|
||||
|
||||
props: {
|
||||
asset: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.asset-list-item
|
||||
.content
|
||||
display grid
|
||||
grid-template-columns 5fr repeat(4, 1fr) 16px
|
||||
grid-gap $padding-item
|
||||
font-family $font-mono
|
||||
font-size 12px
|
||||
|
||||
.info
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.name
|
||||
word-break break-all
|
||||
|
||||
.size,
|
||||
.speed
|
||||
color $vue-ui-color-primary
|
||||
justify-content flex-end
|
||||
|
||||
.icon
|
||||
>>> svg
|
||||
fill $vue-ui-color-primary
|
||||
|
||||
&.big
|
||||
font-weight bold
|
||||
|
||||
&.secondary
|
||||
opacity .5
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="build-progress">
|
||||
<div class="content">
|
||||
<loading-progress
|
||||
:progress="progress"
|
||||
size="128"
|
||||
counter-clockwise
|
||||
/>
|
||||
|
||||
<div class="progress">
|
||||
{{ Math.round(progress * 100) }}
|
||||
</div>
|
||||
|
||||
<div class="operations">
|
||||
<span v-if="operations">{{ operations }}</span>
|
||||
<VueIcon
|
||||
v-else
|
||||
icon="more_horiz"
|
||||
class="blank-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'mode',
|
||||
])
|
||||
},
|
||||
|
||||
sharedData () {
|
||||
return mapSharedData('webpack-dashboard-', {
|
||||
progress: `${this.mode}-progress`,
|
||||
operations: `${this.mode}-operations`
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.build-progress
|
||||
.content
|
||||
v-box()
|
||||
box-center()
|
||||
position relative
|
||||
height 100%
|
||||
|
||||
>>> > *
|
||||
space-between-y($padding-item)
|
||||
|
||||
.vue-progress-path
|
||||
>>> .background
|
||||
stroke rgba($vue-ui-color-dark, .1)
|
||||
|
||||
.operations
|
||||
color $vue-ui-color-dark
|
||||
padding-bottom 12px
|
||||
&:first-letter
|
||||
text-transform uppercase
|
||||
|
||||
.progress
|
||||
h-box()
|
||||
box-center()
|
||||
flex none
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 178px
|
||||
color lighten($vue-ui-color-dark, 60%)
|
||||
font-weight lighter
|
||||
font-size 42px
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="build-status">
|
||||
<div class="content">
|
||||
<div class="info-block status">
|
||||
<div class="label">Status</div>
|
||||
<div class="value">{{ status || 'Idle' }}</div>
|
||||
</div>
|
||||
<div class="info-block errors">
|
||||
<div class="label">Errors</div>
|
||||
<div class="value">{{ errors.length }}</div>
|
||||
</div>
|
||||
<div class="info-block warnings">
|
||||
<div class="label">Warnings</div>
|
||||
<div class="value">{{ warnings.length }}</div>
|
||||
</div>
|
||||
<div class="info-block assets">
|
||||
<div class="label">Assets</div>
|
||||
<div class="value">
|
||||
{{ assetsTotalSize | size('B') }}
|
||||
<span class="secondary">
|
||||
{{ useGzip ? '(gzip)' : '(disk)' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-block modules">
|
||||
<div class="label">Modules</div>
|
||||
<div class="value">{{ modulesTotalSize | size('B') }}</div>
|
||||
</div>
|
||||
<div class="info-block dep-modules">
|
||||
<div class="label">Dependencies</div>
|
||||
<div class="value">
|
||||
{{ depModulesTotalSize | size('B') }}
|
||||
<span class="secondary">
|
||||
{{ depSizeRatio | round(100) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { size, round } from '../filters'
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
size,
|
||||
round
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'useGzip',
|
||||
'mode',
|
||||
'errors',
|
||||
'warnings',
|
||||
'assetsTotalSize',
|
||||
'modulesTotalSize',
|
||||
'depModulesTotalSize'
|
||||
]),
|
||||
|
||||
depSizeRatio () {
|
||||
if (this.modulesTotalSize) {
|
||||
return this.depModulesTotalSize / this.modulesTotalSize * 100
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
sharedData () {
|
||||
return {
|
||||
status: `webpack-dashboard-${this.mode}-status`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.build-status
|
||||
.content
|
||||
height 100%
|
||||
display grid
|
||||
grid-template-columns repeat(3, 1fr)
|
||||
grid-gap $padding-item
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="module-list list-block">
|
||||
<div class="content">
|
||||
<div class="title">Dependencies</div>
|
||||
|
||||
<VueIcon
|
||||
v-if="!depModules.length"
|
||||
icon="more_horiz"
|
||||
class="blank-icon"
|
||||
/>
|
||||
|
||||
<div class="list">
|
||||
<ModuleListItem
|
||||
v-for="m of depModules"
|
||||
:key="m.id"
|
||||
:module="m"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import ModuleListItem from './ModuleListItem'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ModuleListItem
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'depModules'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="module-list-item">
|
||||
<div class="content">
|
||||
<div class="info name">{{ module.name }}</div>
|
||||
<div class="info size">{{ module.size | size('B') }}</div>
|
||||
<div class="info">
|
||||
<VueLoadingBar :value="module.ratio" class="primary"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { size } from '../filters'
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
size
|
||||
},
|
||||
|
||||
props: {
|
||||
module: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.module-list-item
|
||||
.content
|
||||
display grid
|
||||
grid-template-columns 3fr 1fr 1fr
|
||||
grid-gap $padding-item
|
||||
font-family $font-mono
|
||||
font-size 12px
|
||||
|
||||
.info
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.size
|
||||
color $vue-ui-color-primary
|
||||
justify-content flex-end
|
||||
|
||||
.vue-ui-loading-bar
|
||||
width 100%
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="speed-stats">
|
||||
<div class="content">
|
||||
<div class="title">Speed stats</div>
|
||||
|
||||
<VueIcon
|
||||
v-if="!assetsTotalSize"
|
||||
icon="more_horiz"
|
||||
class="blank-icon"
|
||||
/>
|
||||
|
||||
<div v-else class="items">
|
||||
<SpeedStatsItem
|
||||
v-for="(stats, key) of speeds"
|
||||
:key="key"
|
||||
:stats="stats"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { getSpeeds } from '../util/assets'
|
||||
|
||||
import SpeedStatsItem from './SpeedStatsItem.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SpeedStatsItem
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'assetsTotalSize'
|
||||
]),
|
||||
|
||||
speeds () {
|
||||
return getSpeeds(this.assetsTotalSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.items
|
||||
display grid
|
||||
grid-template-columns repeat(auto-fill, 200px)
|
||||
grid-gap 16px
|
||||
justify-content space-between
|
||||
padding 16px
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div
|
||||
class="speed-stats-item"
|
||||
v-tooltip="{
|
||||
content: tooltip,
|
||||
offset: 16
|
||||
}"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="label">{{ stats.title }}</div>
|
||||
<div class="value">{{ stats.totalDownloadTime | round(100) }}s</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { round } from '../filters'
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
round
|
||||
},
|
||||
|
||||
props: {
|
||||
stats: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tooltip () {
|
||||
return `${this.stats.mbps}Mbps + ${this.stats.rtt}ms RTT`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.content
|
||||
font-family $font-mono
|
||||
font-size 12px
|
||||
display grid
|
||||
grid-template-columns 3fr 2fr
|
||||
grid-gap $padding-item
|
||||
|
||||
.label
|
||||
text-align right
|
||||
|
||||
.value
|
||||
color $vue-ui-color-primary
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="vue-webpack-dashboard">
|
||||
<div class="pane-toolbar">
|
||||
<VueIcon icon="dashboard"/>
|
||||
<div class="title">Dashboard</div>
|
||||
<VueSwitch
|
||||
v-model="useGzip"
|
||||
>
|
||||
Gzip assets
|
||||
</VueSwitch>
|
||||
</div>
|
||||
|
||||
<div class="content vue-ui-grid default-gap">
|
||||
<BuildStatus />
|
||||
<BuildProgress />
|
||||
<SpeedStats class="span-2"/>
|
||||
<AssetList />
|
||||
<ModuleList />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '../store'
|
||||
|
||||
import BuildStatus from './BuildStatus'
|
||||
import BuildProgress from './BuildProgress'
|
||||
import SpeedStats from './SpeedStats'
|
||||
import AssetList from './AssetList'
|
||||
import ModuleList from './ModuleList'
|
||||
|
||||
export default {
|
||||
store,
|
||||
|
||||
components: {
|
||||
BuildStatus,
|
||||
BuildProgress,
|
||||
SpeedStats,
|
||||
AssetList,
|
||||
ModuleList
|
||||
},
|
||||
|
||||
inject: [
|
||||
'TaskDetails'
|
||||
],
|
||||
|
||||
computed: {
|
||||
useGzip: {
|
||||
get () { return this.$store.getters.useGzip },
|
||||
set (value) { this.$store.commit('useGzip', value) }
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
const mode = this.TaskDetails.task.command.indexOf('vue-cli-service serve') !== -1 ? 'serve' : 'build'
|
||||
this.$store.commit('mode', mode)
|
||||
this.$watchSharedData(`webpack-dashboard-${mode}-stats`, value => {
|
||||
this.$store.commit('stats', {
|
||||
mode,
|
||||
value
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '~vue-progress-path/dist/vue-progress-path.css';
|
||||
</style>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@vue/cli-ui/src/style/imports"
|
||||
|
||||
.vue-webpack-dashboard
|
||||
.content
|
||||
grid-template-columns 9fr 4fr
|
||||
|
||||
>>>
|
||||
.title
|
||||
color lighten($vue-ui-color-dark, 60%)
|
||||
font-size 20px
|
||||
font-weight lighter
|
||||
text-align center
|
||||
margin-bottom $padding-item
|
||||
|
||||
.list
|
||||
display grid
|
||||
grid-template-columns 1fr
|
||||
grid-gap 16px
|
||||
|
||||
.info-block
|
||||
v-box()
|
||||
box-center()
|
||||
font-weight lighter
|
||||
text-align center
|
||||
|
||||
.label
|
||||
color lighten($vue-ui-color-dark, 60%)
|
||||
font-size 20px
|
||||
|
||||
.value
|
||||
color $vue-ui-color-dark
|
||||
font-size 24px
|
||||
|
||||
.secondary
|
||||
opacity .75
|
||||
font-size 16px
|
||||
|
||||
.pane-toolbar,
|
||||
.content >>> > div
|
||||
padding $padding-item
|
||||
background $vue-ui-color-light-neutral
|
||||
border-radius $br
|
||||
|
||||
.pane-toolbar
|
||||
margin-bottom $padding-item
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
export function size (size, unit = '', precision = 1) {
|
||||
const kb = {
|
||||
label: 'k',
|
||||
value: 1024
|
||||
}
|
||||
const mb = {
|
||||
label: 'M',
|
||||
value: 1024 * 1024
|
||||
}
|
||||
let denominator
|
||||
|
||||
if (size >= mb.value) {
|
||||
denominator = mb
|
||||
} else {
|
||||
denominator = kb
|
||||
if (size < kb.value * 0.92 && precision === 0) {
|
||||
precision = 1
|
||||
}
|
||||
}
|
||||
return (size / denominator.value).toFixed(precision) + denominator.label + unit
|
||||
}
|
||||
|
||||
export function round (value, precision) {
|
||||
return Math.round(value * precision) / precision
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import VueProgress from 'vue-progress-path'
|
||||
import WebpackDashboard from './components/WebpackDashboard.vue'
|
||||
|
||||
Vue.use(VueProgress, {
|
||||
defaultShape: 'circle'
|
||||
})
|
||||
|
||||
ClientAddonApi.component('vue-webpack-dashboard', WebpackDashboard)
|
||||
@@ -0,0 +1,53 @@
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import { buildSortedAssets } from '../util/assets'
|
||||
import { buildDepModules } from '../util/modules'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state () {
|
||||
return {
|
||||
useGzip: true,
|
||||
mode: 'serve',
|
||||
serve: {
|
||||
stats: null
|
||||
},
|
||||
build: {
|
||||
stats: null
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
useGzip: state => state.useGzip,
|
||||
mode: state => state.mode,
|
||||
stats: state => state[state.mode].stats,
|
||||
errors: (state, getters) => (getters.stats && getters.stats.data.errors) || [],
|
||||
warnings: (state, getters) => (getters.stats && getters.stats.data.warnings) || [],
|
||||
assets: (state, getters) => (getters.stats && getters.stats.data.assets) || [],
|
||||
assetsSorted: (state, getters) => buildSortedAssets(getters.assets, getters.useGzip),
|
||||
assetsTotalSize: (state, getters) => getters.assetsSorted.filter(a => !a.secondary).reduce((total, asset) => total + asset.size, 0),
|
||||
modules: (state, getters) => (getters.stats && getters.stats.data.modules) || [],
|
||||
modulesTotalSize: (state, getters) => getters.modules.reduce((total, module) => total + module.size, 0),
|
||||
depModules: (state, getters) => buildDepModules(getters.modules),
|
||||
depModulesTotalSize: (state, getters) => getters.depModules.reduce((total, module) => total + module.size, 0),
|
||||
chunks: (state, getters) => (getters.stats && getters.stats.data.chunks) || []
|
||||
},
|
||||
|
||||
mutations: {
|
||||
useGzip (state, value) {
|
||||
state.useGzip = value
|
||||
},
|
||||
|
||||
mode (state, value) {
|
||||
state.mode = value
|
||||
},
|
||||
|
||||
stats (state, { mode, value }) {
|
||||
state[mode].stats = value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default store
|
||||
@@ -0,0 +1,63 @@
|
||||
import speedsData from '../assets/speeds.json'
|
||||
|
||||
const DOWNLOAD_TIME_THRESHOLD_SECONDS = 5
|
||||
|
||||
export function getSpeedData (datapoint, size) {
|
||||
const assetsSizeInMB = size / 1024 / 1024
|
||||
const bandwidthInMbps = datapoint.mbps
|
||||
const bandwidthInMBps = bandwidthInMbps / 8
|
||||
const rttInSeconds = datapoint.rtt / 1000
|
||||
|
||||
const totalDownloadTime = assetsSizeInMB / bandwidthInMBps + rttInSeconds
|
||||
|
||||
const isDownloadTimeOverThreshold =
|
||||
totalDownloadTime > DOWNLOAD_TIME_THRESHOLD_SECONDS
|
||||
const timeDifferenceToThreshold =
|
||||
(isDownloadTimeOverThreshold ? '+' : '-') +
|
||||
Math.abs(totalDownloadTime - DOWNLOAD_TIME_THRESHOLD_SECONDS).toFixed(2) +
|
||||
's'
|
||||
|
||||
return {
|
||||
totalDownloadTime,
|
||||
isDownloadTimeOverThreshold,
|
||||
timeDifferenceToThreshold
|
||||
}
|
||||
}
|
||||
|
||||
export function getSpeeds (size) {
|
||||
return Object.keys(speedsData).reduce((obj, key) => {
|
||||
obj[key] = {
|
||||
...getSpeedData(speedsData[key], size),
|
||||
...speedsData[key]
|
||||
}
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function buildSortedAssets (assets, userGzip) {
|
||||
let list = assets.slice()
|
||||
if (list.length) {
|
||||
const max = list[0].size
|
||||
list = list.map(asset => {
|
||||
const size = userGzip ? asset.gzipSize : asset.size
|
||||
return {
|
||||
name: asset.name,
|
||||
size,
|
||||
big: size > 250000,
|
||||
ratio: size / max,
|
||||
secondary: /\.map$/.test(asset.name),
|
||||
speeds: getSpeeds(size)
|
||||
}
|
||||
})
|
||||
list = list.sort((a, b) => {
|
||||
if (a.secondary === b.secondary) {
|
||||
return b.size - a.size
|
||||
} else if (a.secondary && !b.secondary) {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
const getModulePath = function (identifier) {
|
||||
return identifier.replace(/.*!/, '').replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
export function buildDepModules (modules) {
|
||||
const deps = new Map()
|
||||
for (const module of modules) {
|
||||
const path = getModulePath(module.identifier)
|
||||
const pathParts = path.split('/node_modules/')
|
||||
if (pathParts.length === 2) {
|
||||
let name = pathParts[1]
|
||||
if (name.charAt(0) === '@') {
|
||||
// Scoped package
|
||||
name = name.substr(0, name.indexOf('/', name.indexOf('/') + 1))
|
||||
} else {
|
||||
name = name.substr(0, name.indexOf('/'))
|
||||
}
|
||||
let dep = deps.get(name)
|
||||
if (!dep) {
|
||||
dep = {
|
||||
name,
|
||||
size: 0
|
||||
}
|
||||
deps.set(name, dep)
|
||||
}
|
||||
dep.size += module.size
|
||||
}
|
||||
}
|
||||
let list = Array.from(deps.values())
|
||||
list = list.sort((a, b) => b.size - a.size)
|
||||
if (list.length) {
|
||||
const max = list[0].size
|
||||
for (const dep of list) {
|
||||
dep.ratio = dep.size / max
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
configureWebpack: {
|
||||
output: {
|
||||
publicPath: 'http://localhost:8081/'
|
||||
}
|
||||
},
|
||||
devServer: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"@vue/standard"
|
||||
]
|
||||
}
|
||||
],
|
||||
"globals": {
|
||||
"ClientAddonApi": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"clone": "^1.0.4",
|
||||
"file-icons-js": "^1.0.3",
|
||||
"graphql": "^0.13.0",
|
||||
"graphql-type-json": "^0.2.0",
|
||||
"js-yaml": "^3.11.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"lru-cache": "^4.1.2",
|
||||
|
||||
@@ -5,19 +5,10 @@
|
||||
</div>
|
||||
|
||||
<StatusBar/>
|
||||
<ClientAddonLoader/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from './components/StatusBar'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StatusBar
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@import "~@vue/ui/dist/vue-ui.css"
|
||||
@import "~file-icons-js/css/style.css"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<component
|
||||
v-if="component"
|
||||
:is="component"
|
||||
/>
|
||||
<div v-else class="loading">
|
||||
<VueLoadingIndicator />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
component: null
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
name: {
|
||||
handler: 'updateComponent',
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async updateComponent () {
|
||||
this.component = await ClientAddonApi.awaitComponent(this.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@/style/imports"
|
||||
|
||||
.loading
|
||||
v-box()
|
||||
box-center()
|
||||
padding 100px
|
||||
</style>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script>
|
||||
import CLIENT_ADDONS from '../graphql/clientAddons.gql'
|
||||
import CLIENT_ADDON_ADDED from '../graphql/clientAddonAdded.gql'
|
||||
|
||||
export default {
|
||||
apollo: {
|
||||
clientAddons: {
|
||||
query: CLIENT_ADDONS,
|
||||
manual: true,
|
||||
result ({ data: { clientAddons } }) {
|
||||
clientAddons.forEach(this.loadAddon)
|
||||
}
|
||||
},
|
||||
|
||||
$subscribe: {
|
||||
clientAddonAdded: {
|
||||
query: CLIENT_ADDON_ADDED,
|
||||
result ({ data }) {
|
||||
this.loadAddon(data.clientAddonAdded)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$_scripts = new Map()
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadAddon (addon) {
|
||||
console.log(`Loading addon ${addon.id}...`)
|
||||
const script = document.createElement('script')
|
||||
this.$_scripts.set(addon.id, script)
|
||||
script.setAttribute('src', addon.url)
|
||||
document.body.appendChild(script)
|
||||
}
|
||||
},
|
||||
|
||||
render () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -71,6 +71,7 @@ export default {
|
||||
.list-item-info
|
||||
flex 100% 1 1
|
||||
width 0
|
||||
overflow hidden
|
||||
|
||||
>>> .description
|
||||
white-space nowrap
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="terminal-view">
|
||||
<div v-if="toolbar" class="toolbar">
|
||||
<div v-if="toolbar" class="pane-toolbar">
|
||||
<VueIcon
|
||||
icon="dvr"
|
||||
/>
|
||||
@@ -97,6 +97,11 @@ export default {
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
|
||||
openLinks: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -166,6 +171,9 @@ export default {
|
||||
},
|
||||
|
||||
handleLink (event, uri) {
|
||||
if (this.openLinks) {
|
||||
window.open(uri, '_blank')
|
||||
}
|
||||
this.$emit('link', uri)
|
||||
},
|
||||
|
||||
@@ -205,19 +213,6 @@ export default {
|
||||
align-items stretch
|
||||
background $vue-ui-color-light-neutral
|
||||
|
||||
.toolbar
|
||||
h-box()
|
||||
align-items center
|
||||
padding 6px 6px 6px $padding-item
|
||||
> :not(.separator)
|
||||
space-between-x(6px)
|
||||
> * + .separator
|
||||
margin-left 6px
|
||||
.title
|
||||
flex 100% 1 1
|
||||
width 0
|
||||
ellipsis()
|
||||
|
||||
.view
|
||||
flex 100% 1 1
|
||||
height 0
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
const plugins = require('../connectors/plugins')
|
||||
const sharedData = require('../connectors/shared-data')
|
||||
const ipc = require('../utils/ipc')
|
||||
|
||||
class PluginApi {
|
||||
constructor () {
|
||||
constructor (context) {
|
||||
this.context = context
|
||||
this.configurations = []
|
||||
this.tasks = []
|
||||
this.clientAddons = []
|
||||
this.actions = new Map()
|
||||
}
|
||||
|
||||
describeConfig (options) {
|
||||
@@ -17,6 +24,54 @@ class PluginApi {
|
||||
options => options.match.test(command)
|
||||
)
|
||||
}
|
||||
|
||||
addClientAddon (options) {
|
||||
this.clientAddons.push(options)
|
||||
}
|
||||
|
||||
ipcOn (cb) {
|
||||
return ipc.on(cb)
|
||||
}
|
||||
|
||||
ipcOff (cb) {
|
||||
ipc.off(cb)
|
||||
}
|
||||
|
||||
ipcSend (data) {
|
||||
ipc.send(data)
|
||||
}
|
||||
|
||||
/* Namespaced */
|
||||
|
||||
getSharedData (id) {
|
||||
return sharedData.get(id, this.context)
|
||||
}
|
||||
|
||||
setSharedData (id, value) {
|
||||
sharedData.set({ id, value }, this.context)
|
||||
}
|
||||
|
||||
onAction (id, cb) {
|
||||
let list = this.actions.get(id)
|
||||
if (!list) {
|
||||
list = []
|
||||
this.actions.set(id, list)
|
||||
}
|
||||
list.push(cb)
|
||||
}
|
||||
|
||||
callAction (id, params) {
|
||||
return plugins.callAction({ id, params }, this.context)
|
||||
}
|
||||
|
||||
namespace (namespace) {
|
||||
return {
|
||||
getSharedData: (id) => this.getSharedData(namespace + id),
|
||||
setSharedData: (id, value) => this.setSharedData(namespace + id, value),
|
||||
onAction: (id, cb) => this.onAction(namespace + id, cb),
|
||||
callAction: (id, params) => this.callAction(namespace + id, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PluginApi
|
||||
|
||||
@@ -7,5 +7,9 @@ module.exports = {
|
||||
TASK_LOG_ADDED: 'task_log_added',
|
||||
ROUTE_ADDED: 'route_added',
|
||||
ROUTE_REMOVED: 'route_removed',
|
||||
ROUTE_CHANGED: 'route_changed'
|
||||
ROUTE_CHANGED: 'route_changed',
|
||||
CLIENT_ADDON_ADDED: 'client_addon_added',
|
||||
SHARED_DATA_UPDATED: 'shared_data_updated',
|
||||
PLUGIN_ACTION_CALLED: 'plugin_action_called',
|
||||
PLUGIN_ACTION_RESOLVED: 'plugin_action_resolved'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
const channels = require('../channels')
|
||||
|
||||
let addons = []
|
||||
|
||||
function list (context) {
|
||||
return addons
|
||||
}
|
||||
|
||||
function add (options, context) {
|
||||
if (findOne(options.id)) return
|
||||
|
||||
addons.push(options)
|
||||
context.pubsub.publish(channels.CLIENT_ADDON_ADDED, {
|
||||
clientAddonAdded: options
|
||||
})
|
||||
}
|
||||
|
||||
function findOne (id, context) {
|
||||
return addons.find(
|
||||
addon => addon.id === id
|
||||
)
|
||||
}
|
||||
|
||||
function remove (id, context) {
|
||||
const index = addons.findIndex(
|
||||
addon => addon.id === id
|
||||
)
|
||||
if (index !== -1) addons.splice(index, 1)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list,
|
||||
add,
|
||||
remove,
|
||||
findOne
|
||||
}
|
||||
@@ -16,12 +16,15 @@ const {
|
||||
updatePackage
|
||||
} = require('@vue/cli/lib/util/installDeps')
|
||||
const invoke = require('@vue/cli/lib/invoke')
|
||||
// Subs
|
||||
const channels = require('../channels')
|
||||
// Connectors
|
||||
const cwd = require('./cwd')
|
||||
const folders = require('./folders')
|
||||
const prompts = require('./prompts')
|
||||
const progress = require('./progress')
|
||||
const logs = require('./logs')
|
||||
const clientAddons = require('./client-addons')
|
||||
// Api
|
||||
const PluginApi = require('../api/PluginApi')
|
||||
// Utils
|
||||
@@ -73,12 +76,17 @@ function list (file, context) {
|
||||
}
|
||||
|
||||
function resetPluginApi (context) {
|
||||
pluginApi = new PluginApi()
|
||||
pluginApi = new PluginApi(context)
|
||||
// Run Plugin API
|
||||
runPluginApi('@vue/cli-service', context)
|
||||
plugins.forEach(plugin => runPluginApi(plugin.id, context))
|
||||
runPluginApi('.', context, 'vue-cli-ui')
|
||||
// Add client addons
|
||||
pluginApi.clientAddons.forEach(options => clientAddons.add(options, context))
|
||||
}
|
||||
|
||||
function runPluginApi (id, context) {
|
||||
const module = loadModule(`${id}/ui`, cwd.get(), true)
|
||||
function runPluginApi (id, context, fileName = 'ui') {
|
||||
const module = loadModule(`${id}/${fileName}`, cwd.get(), true)
|
||||
if (module) {
|
||||
module(pluginApi)
|
||||
}
|
||||
@@ -278,6 +286,32 @@ function getApi () {
|
||||
return pluginApi
|
||||
}
|
||||
|
||||
async function callAction ({ id, params }, context) {
|
||||
context.pubsub.publish(channels.PLUGIN_ACTION_CALLED, {
|
||||
pluginActionCalled: { id, params }
|
||||
})
|
||||
const results = []
|
||||
const errors = []
|
||||
const list = pluginApi.actions.get(id)
|
||||
if (list) {
|
||||
for (const cb of list) {
|
||||
let result = null
|
||||
let error = null
|
||||
try {
|
||||
result = await cb(params)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
results.push(result)
|
||||
errors.push(error)
|
||||
}
|
||||
}
|
||||
context.pubsub.publish(channels.PLUGIN_ACTION_RESOLVED, {
|
||||
pluginActionResolved: { id, params, results, errors }
|
||||
})
|
||||
return { id, params, results, errors }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list,
|
||||
findOne,
|
||||
@@ -291,5 +325,6 @@ module.exports = {
|
||||
runInvoke,
|
||||
resetPluginApi,
|
||||
getApi,
|
||||
finishInstall
|
||||
finishInstall,
|
||||
callAction
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ function setAnswer (id, value) {
|
||||
}
|
||||
|
||||
function getAnswer (id) {
|
||||
ObjectUtil.get(answers, id)
|
||||
return ObjectUtil.get(answers, id)
|
||||
}
|
||||
|
||||
function removeAnswer (id) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Subscriptions channels
|
||||
const channels = require('../channels')
|
||||
|
||||
let sharedData = new Map()
|
||||
|
||||
function get (id, context) {
|
||||
const value = sharedData.get(id)
|
||||
|
||||
if (typeof value === 'undefined') return null
|
||||
|
||||
return {
|
||||
id,
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
function set ({ id, value }, context) {
|
||||
sharedData.set(id, value)
|
||||
context.pubsub.publish(channels.SHARED_DATA_UPDATED, {
|
||||
sharedDataUpdated: { id, value }
|
||||
})
|
||||
return { id, value }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get,
|
||||
set
|
||||
}
|
||||
@@ -44,6 +44,7 @@ function list (context) {
|
||||
command,
|
||||
index: list.findIndex(t => t.id === id),
|
||||
prompts: [],
|
||||
views: [],
|
||||
...moreData
|
||||
}
|
||||
}
|
||||
@@ -141,9 +142,9 @@ function run (id, context) {
|
||||
answers
|
||||
}, context)
|
||||
|
||||
// Plugin api
|
||||
if (task.onRun) {
|
||||
task.onRun({
|
||||
// Plugin API
|
||||
if (task.onBeforeRun) {
|
||||
task.onBeforeRun({
|
||||
answers,
|
||||
args
|
||||
})
|
||||
@@ -151,9 +152,18 @@ function run (id, context) {
|
||||
|
||||
const child = execa(getCommand(), args, {
|
||||
cwd: cwd.get(),
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
stdio: ['inherit', 'pipe', 'pipe', 'ipc']
|
||||
})
|
||||
|
||||
// Plugin API
|
||||
if (task.onRun) {
|
||||
task.onRun({
|
||||
args,
|
||||
child,
|
||||
cwd: cwd.get()
|
||||
})
|
||||
}
|
||||
|
||||
updateOne({
|
||||
id: task.id,
|
||||
status: 'running',
|
||||
@@ -180,7 +190,18 @@ function run (id, context) {
|
||||
}, context)
|
||||
})
|
||||
|
||||
child.on('close', (code, signal) => {
|
||||
const onExit = (code, signal) => {
|
||||
// Plugin API
|
||||
if (task.onExit) {
|
||||
task.onExit({
|
||||
args,
|
||||
child,
|
||||
cwd: cwd.get(),
|
||||
code,
|
||||
signal
|
||||
})
|
||||
}
|
||||
|
||||
if (code === null) {
|
||||
updateOne({
|
||||
id: task.id,
|
||||
@@ -212,7 +233,9 @@ function run (id, context) {
|
||||
type: 'done'
|
||||
}, context)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
child.on('exit', onExit)
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const { withFilter } = require('graphql-subscriptions')
|
||||
const exit = require('@vue/cli-shared-utils/lib/exit')
|
||||
|
||||
const GraphQLJSON = require('graphql-type-json')
|
||||
// Channels for subscriptions
|
||||
const channels = require('./channels')
|
||||
|
||||
// Connectors
|
||||
const cwd = require('./connectors/cwd')
|
||||
const folders = require('./connectors/folders')
|
||||
@@ -15,6 +15,10 @@ const configurations = require('./connectors/configurations')
|
||||
const git = require('./connectors/git')
|
||||
const files = require('./connectors/files')
|
||||
const routes = require('./connectors/routes')
|
||||
const clientAddons = require('./connectors/client-addons')
|
||||
const sharedData = require('./connectors/shared-data')
|
||||
// Start ipc server
|
||||
require('./utils/ipc')
|
||||
|
||||
// Prevent code from exiting server process
|
||||
exit.exitProcess = false
|
||||
@@ -22,6 +26,8 @@ exit.exitProcess = false
|
||||
process.env.VUE_CLI_API_MODE = true
|
||||
|
||||
module.exports = {
|
||||
JSON: GraphQLJSON,
|
||||
|
||||
Folder: {
|
||||
children: (folder, args, context) => folders.list(folder.path, context),
|
||||
isPackage: (folder, args, context) => folders.isPackage(folder.path, context),
|
||||
@@ -63,7 +69,9 @@ module.exports = {
|
||||
configurations: (root, args, context) => configurations.list(context),
|
||||
configuration: (root, { id }, context) => configurations.findOne(id, context),
|
||||
fileDiffs: (root, args, context) => git.getDiffs(context),
|
||||
routes: (root, args, context) => routes.list(context)
|
||||
routes: (root, args, context) => routes.list(context),
|
||||
clientAddons: (root, args, context) => clientAddons.list(context),
|
||||
sharedData: (root, { id }, context) => sharedData.get(id, context)
|
||||
},
|
||||
|
||||
Mutation: {
|
||||
@@ -88,13 +96,15 @@ module.exports = {
|
||||
pluginInvoke: (root, { id }, context) => plugins.runInvoke(id, context),
|
||||
pluginFinishInstall: (root, args, context) => plugins.finishInstall(context),
|
||||
pluginUpdate: (root, { id }, context) => plugins.update(id, context),
|
||||
pluginActionCall: (root, args, context) => plugins.callAction(args, context),
|
||||
taskRun: (root, { id }, context) => tasks.run(id, context),
|
||||
taskStop: (root, { id }, context) => tasks.stop(id, context),
|
||||
taskLogsClear: (root, { id }, context) => tasks.clearLogs(id, context),
|
||||
configurationSave: (root, { id }, context) => configurations.save(id, context),
|
||||
configurationCancel: (root, { id }, context) => configurations.cancel(id, context),
|
||||
gitCommit: (root, { message }, context) => git.commit(message, context),
|
||||
fileOpenInEditor: (root, { input }, context) => files.openInEditor(input, context)
|
||||
fileOpenInEditor: (root, { input }, context) => files.openInEditor(input, context),
|
||||
sharedDataUpdate: (root, args, context) => sharedData.set(args, context)
|
||||
},
|
||||
|
||||
Subscription: {
|
||||
@@ -131,6 +141,21 @@ module.exports = {
|
||||
(parent, args, { pubsub }) => pubsub.asyncIterator(channels.TASK_LOG_ADDED),
|
||||
(payload, vars) => payload.taskLogAdded.taskId === vars.id
|
||||
)
|
||||
},
|
||||
clientAddonAdded: {
|
||||
subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator(channels.CLIENT_ADDON_ADDED)
|
||||
},
|
||||
sharedDataUpdated: {
|
||||
subscribe: withFilter(
|
||||
(parent, args, { pubsub }) => pubsub.asyncIterator(channels.SHARED_DATA_UPDATED),
|
||||
(payload, vars) => payload.sharedDataUpdated.id === vars.id
|
||||
)
|
||||
},
|
||||
pluginActionCalled: {
|
||||
subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator(channels.PLUGIN_ACTION_CALLED)
|
||||
},
|
||||
pluginActionResolved: {
|
||||
subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator(channels.PLUGIN_ACTION_RESOLVED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = `
|
||||
scalar JSON
|
||||
|
||||
type ConsoleLog {
|
||||
id: ID!
|
||||
@@ -108,6 +109,18 @@ enum PluginInstallationStep {
|
||||
diff
|
||||
}
|
||||
|
||||
type PluginActionCall {
|
||||
id: ID!
|
||||
params: JSON
|
||||
}
|
||||
|
||||
type PluginActionResult {
|
||||
id: ID!
|
||||
params: JSON
|
||||
results: [JSON]
|
||||
errors: [JSON]
|
||||
}
|
||||
|
||||
type Feature implements DescribedEntity {
|
||||
id: ID!
|
||||
name: String
|
||||
@@ -179,6 +192,8 @@ type Task implements DescribedEntity {
|
||||
link: String
|
||||
logs: [TaskLog]
|
||||
prompts: [Prompt]
|
||||
views: [TaskView]
|
||||
defaultView: String
|
||||
}
|
||||
|
||||
enum TaskStatus {
|
||||
@@ -200,6 +215,13 @@ enum TaskLogType {
|
||||
stderr
|
||||
}
|
||||
|
||||
type TaskView {
|
||||
id: ID!
|
||||
label: String!
|
||||
component: String!
|
||||
icon: String
|
||||
}
|
||||
|
||||
type Configuration implements DescribedEntity {
|
||||
id: ID!
|
||||
name: String
|
||||
@@ -256,6 +278,16 @@ type Route {
|
||||
tooltip: String
|
||||
}
|
||||
|
||||
type ClientAddon {
|
||||
id: ID!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type SharedData {
|
||||
id: ID!
|
||||
value: JSON
|
||||
}
|
||||
|
||||
type Query {
|
||||
progress (id: ID!): Progress
|
||||
cwd: String!
|
||||
@@ -274,6 +306,8 @@ type Query {
|
||||
configuration (id: ID!): Configuration
|
||||
fileDiffs: [FileDiff]
|
||||
routes: [Route]
|
||||
clientAddons: [ClientAddon]
|
||||
sharedData (id: ID!): SharedData
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -295,6 +329,7 @@ type Mutation {
|
||||
pluginInvoke (id: ID!): PluginInstallation
|
||||
pluginFinishInstall: PluginInstallation
|
||||
pluginUpdate (id: ID!): Plugin
|
||||
pluginActionCall (id: ID!, params: JSON): PluginActionResult
|
||||
taskRun (id: ID!): Task
|
||||
taskStop (id: ID!): Task
|
||||
taskLogsClear (id: ID!): Task
|
||||
@@ -302,6 +337,7 @@ type Mutation {
|
||||
configurationCancel (id: ID!): Configuration
|
||||
gitCommit (message: String!): Boolean
|
||||
fileOpenInEditor (input: OpenInEditorInput!): Boolean
|
||||
sharedDataUpdate (id: ID!, value: JSON!): SharedData
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
@@ -314,5 +350,9 @@ type Subscription {
|
||||
routeAdded: Route
|
||||
routeRemoved: Route
|
||||
routeChanged: Route
|
||||
clientAddonAdded: ClientAddon
|
||||
sharedDataUpdated (id: ID!): SharedData
|
||||
pluginActionCalled: PluginActionCall
|
||||
pluginActionResolved: PluginActionResult
|
||||
}
|
||||
`
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const ipc = require('node-ipc')
|
||||
|
||||
ipc.config.id = 'vue-cli'
|
||||
ipc.config.retry = 1500
|
||||
ipc.config.silent = true
|
||||
|
||||
const listeners = []
|
||||
|
||||
ipc.serve(() => {
|
||||
ipc.server.on('message', (data, socket) => {
|
||||
for (const listener of listeners) {
|
||||
listener({
|
||||
data,
|
||||
emit: data => {
|
||||
ipc.server.emit(socket, 'message', data)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
ipc.server.start()
|
||||
|
||||
function on (cb) {
|
||||
listeners.push(cb)
|
||||
return () => off(cb)
|
||||
}
|
||||
|
||||
function off (cb) {
|
||||
const index = listeners.indexOf(cb)
|
||||
if (index !== -1) listeners.splice(index, 1)
|
||||
}
|
||||
|
||||
function send (data) {
|
||||
ipc.server.emit('message', data)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
on,
|
||||
off,
|
||||
send
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./clientAddonFragment.gql"
|
||||
|
||||
subscription clientAddonAdded {
|
||||
clientAddonAdded {
|
||||
...clientAddon
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fragment clientAddon on ClientAddon {
|
||||
id
|
||||
url
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./clientAddonFragment.gql"
|
||||
|
||||
query clientAddons {
|
||||
clientAddons {
|
||||
...clientAddon
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./pluginActionResultFragment.gql"
|
||||
|
||||
mutation pluginActionCall ($id: ID!, $params: JSON) {
|
||||
pluginActionCall (id: $id, params: $params) {
|
||||
...pluginActionResult
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fragment pluginActionCall on PluginActionCall {
|
||||
id
|
||||
params
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./pluginActionCallFragment.gql"
|
||||
|
||||
subscription pluginActionCalled {
|
||||
pluginActionCalled {
|
||||
...pluginActionCall
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./pluginActionResultFragment.gql"
|
||||
|
||||
subscription pluginActionResolved {
|
||||
pluginActionResolved {
|
||||
...pluginActionResult
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fragment pluginActionResult on PluginActionResult {
|
||||
id
|
||||
params
|
||||
results
|
||||
errors
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./sharedDataFragment.gql"
|
||||
|
||||
query sharedData ($id: ID!) {
|
||||
sharedData (id: $id) {
|
||||
...sharedData
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fragment sharedData on SharedData {
|
||||
id
|
||||
value
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./sharedDataFragment.gql"
|
||||
|
||||
mutation sharedDataUpdate ($id: ID!, $value: JSON!) {
|
||||
sharedDataUpdate (id: $id, value: $value) {
|
||||
...sharedData
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "./sharedDataFragment.gql"
|
||||
|
||||
subscription sharedDataUpdated ($id: ID!) {
|
||||
sharedDataUpdated (id: $id) {
|
||||
...sharedData
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,16 @@
|
||||
query task ($id: ID!) {
|
||||
task (id: $id) {
|
||||
...task
|
||||
link
|
||||
prompts {
|
||||
...prompt
|
||||
}
|
||||
link
|
||||
views {
|
||||
id
|
||||
label
|
||||
icon
|
||||
component
|
||||
}
|
||||
defaultView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,31 @@ import VueUi from '@vue/ui'
|
||||
import InstantSearch from 'vue-instantsearch'
|
||||
import * as Filters from './filters'
|
||||
import './register-components'
|
||||
import ClientAddonApi from './util/ClientAddonApi'
|
||||
import Responsive from './util/responsive'
|
||||
import SharedData from './util/SharedData'
|
||||
import PluginAction from './util/PluginAction'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
window.gql = gql
|
||||
|
||||
Vue.use(VueUi)
|
||||
Vue.use(InstantSearch)
|
||||
Vue.use(Responsive, {
|
||||
computed: {
|
||||
mobile () {
|
||||
return this.width <= 768
|
||||
},
|
||||
tablet () {
|
||||
return this.width <= 900
|
||||
},
|
||||
desktop () {
|
||||
return !this.tablet
|
||||
}
|
||||
}
|
||||
})
|
||||
Vue.use(SharedData)
|
||||
Vue.use(PluginAction)
|
||||
|
||||
for (const key in Filters) {
|
||||
Vue.filter(key, Filters[key])
|
||||
@@ -17,6 +39,10 @@ for (const key in Filters) {
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// For client addons
|
||||
window.Vue = Vue
|
||||
window.ClientAddonApi = new ClientAddonApi()
|
||||
|
||||
const app = new Vue({
|
||||
provide: apolloProvider.provide(),
|
||||
router,
|
||||
|
||||
@@ -101,3 +101,29 @@ ansi-colors('white', $vue-ui-color-light)
|
||||
|
||||
.fill-height
|
||||
height 100%
|
||||
|
||||
.blank-icon
|
||||
width 24px
|
||||
height @width
|
||||
display block
|
||||
margin auto
|
||||
svg
|
||||
fill rgba($vue-ui-color-dark, .3)
|
||||
|
||||
.pane-toolbar
|
||||
h-box()
|
||||
align-items center
|
||||
padding 6px 6px 6px $padding-item
|
||||
> :not(.separator)
|
||||
space-between-x(6px)
|
||||
> * + .separator
|
||||
margin-left 6px
|
||||
.title
|
||||
flex 100% 1 1
|
||||
width 0
|
||||
ellipsis()
|
||||
|
||||
> .vue-ui-switch
|
||||
&:not(.selected)
|
||||
.wrapper
|
||||
background rgba($vue-ui-color-dark, .2)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
export default class ClientAddonApi {
|
||||
constructor (addonId) {
|
||||
this.components = new Map()
|
||||
this.componentListeners = new Map()
|
||||
}
|
||||
|
||||
component (id, definition) {
|
||||
this.components.set(id, definition)
|
||||
const componentId = toComponentId(id)
|
||||
Vue.component(componentId, definition)
|
||||
console.log(`Registered ${componentId} component`)
|
||||
// Call listeners
|
||||
const listeners = this.componentListeners.get(id)
|
||||
if (listeners) {
|
||||
listeners.forEach(l => l(definition))
|
||||
this.componentListeners.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
getComponent (id) {
|
||||
return this.components.get(id)
|
||||
}
|
||||
|
||||
listenForComponent (id, cb) {
|
||||
let listeners = this.componentListeners.get(id)
|
||||
if (!listeners) {
|
||||
listeners = []
|
||||
this.componentListeners.set(id, listeners)
|
||||
}
|
||||
listeners.push(cb)
|
||||
}
|
||||
|
||||
awaitComponent (id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const result = this.getComponent(id)
|
||||
if (result) {
|
||||
resolve(result)
|
||||
} else {
|
||||
this.listenForComponent(id, resolve)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function toComponentId (id) {
|
||||
return `client-addon--${id}`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import PLUGIN_ACTION_CALL from '../graphql/pluginActionCall.gql'
|
||||
import PLUGIN_ACTION_CALLED from '../graphql/pluginActionCalled.gql'
|
||||
import PLUGIN_ACTION_RESOLVED from '../graphql/pluginActionResolved.gql'
|
||||
|
||||
let uid = 0
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
$callAction (id, params) {
|
||||
return this.$apollo.mutate({
|
||||
mutation: PLUGIN_ACTION_CALL,
|
||||
variables: {
|
||||
id,
|
||||
params
|
||||
}
|
||||
})
|
||||
},
|
||||
$onActionCalled (cb) {
|
||||
return this.$apollo.addSmartSubscription(`plugin-action-called-${uid++}`, {
|
||||
query: PLUGIN_ACTION_CALLED,
|
||||
result: cb
|
||||
})
|
||||
},
|
||||
$onActionResolved (cb) {
|
||||
return this.$apollo.addSmartSubscription(`plugin-action-resolved-${uid++}`, {
|
||||
query: PLUGIN_ACTION_RESOLVED,
|
||||
result: cb
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import SHARED_DATA from '../graphql/sharedData.gql'
|
||||
import SHARED_DATA_UPDATE from '../graphql/sharedDataUpdate.gql'
|
||||
import SHARED_DATA_UPDATED from '../graphql/sharedDataUpdated.gql'
|
||||
|
||||
function genQuery (id) {
|
||||
return {
|
||||
query: SHARED_DATA,
|
||||
variables: {
|
||||
id
|
||||
},
|
||||
fetchPolicy: 'cache-and-network',
|
||||
update: ({ sharedData }) => sharedData.value,
|
||||
subscribeToMore: {
|
||||
document: SHARED_DATA_UPDATED,
|
||||
variables: {
|
||||
id
|
||||
},
|
||||
updateQuery: (previousResult, { subscriptionData }) => {
|
||||
return {
|
||||
sharedData: subscriptionData.data.sharedDataUpdated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.mixin({
|
||||
data () {
|
||||
return {
|
||||
$sharedData: {}
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
const options = this.$options.sharedData
|
||||
if (options) {
|
||||
if (typeof options === 'function') {
|
||||
let smartQueries
|
||||
this.$watch(options.bind(this), result => {
|
||||
if (smartQueries) {
|
||||
smartQueries.forEach(s => s.destroy())
|
||||
}
|
||||
smartQueries = this.$syncSharedData(result)
|
||||
}, {
|
||||
immediate: true
|
||||
})
|
||||
} else {
|
||||
this.$syncSharedData(options)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async $getSharedData (id) {
|
||||
const result = await this.$apollo.query({
|
||||
query: SHARED_DATA,
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
})
|
||||
return result.sharedData.value
|
||||
},
|
||||
$watchSharedData (id, cb) {
|
||||
return this.$apollo.addSmartQuery(id, {
|
||||
...genQuery(id),
|
||||
manual: true,
|
||||
result: ({ data }) => {
|
||||
data && cb(data.sharedData.value)
|
||||
}
|
||||
})
|
||||
},
|
||||
$setSharedData (id, value) {
|
||||
return this.$apollo.mutate({
|
||||
mutation: SHARED_DATA_UPDATE,
|
||||
variables: {
|
||||
id,
|
||||
value
|
||||
}
|
||||
})
|
||||
},
|
||||
$syncSharedData (options) {
|
||||
const smartQueries = []
|
||||
for (const key in options) {
|
||||
const id = options[key]
|
||||
this.$set(this.$data.$sharedData, key, null)
|
||||
// Proxy
|
||||
Object.defineProperty(this, key, {
|
||||
get: () => this.$data.$sharedData[key],
|
||||
set: value => this.$set(this.$data.$sharedData, key, value),
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
const smartQuery = this.$apollo.addSmartQuery(key, genQuery(id))
|
||||
smartQueries.push(smartQuery)
|
||||
}
|
||||
return smartQueries
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
window.mapSharedData = (namespace, options) => {
|
||||
const result = {}
|
||||
for (const key in options) {
|
||||
result[key] = namespace + options[key]
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export let responsive
|
||||
|
||||
export default {
|
||||
install (Vue, options) {
|
||||
const finalOptions = Object.assign({}, {
|
||||
computed: {}
|
||||
}, options)
|
||||
|
||||
responsive = new Vue({
|
||||
data () {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
}
|
||||
},
|
||||
computed: finalOptions.computed
|
||||
})
|
||||
|
||||
Object.defineProperty(Vue.prototype, '$responsive', {
|
||||
get: () => responsive
|
||||
})
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
responsive.width = window.innerWidth
|
||||
responsive.height = window.innerHeight
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="project-task-details">
|
||||
<template v-if="task">
|
||||
<div class="header">
|
||||
<VueIcon icon="assignment" class="task-icon big"/>
|
||||
<div class="name">{{ task.name }}</div>
|
||||
<div class="description">{{ task.description }}</div>
|
||||
</div>
|
||||
@@ -48,11 +49,33 @@
|
||||
/>
|
||||
|
||||
<div class="vue-ui-spacer"/>
|
||||
|
||||
<VueGroup
|
||||
v-if="task.views.length"
|
||||
v-model="currentView"
|
||||
>
|
||||
<VueGroupButton
|
||||
:label="$t('views.project-task-details.output')"
|
||||
icon-left="dvr"
|
||||
value="_output"
|
||||
/>
|
||||
|
||||
<VueGroupButton
|
||||
v-for="view of task.views"
|
||||
:key="view.id"
|
||||
:value="view.id"
|
||||
:icon-left="view.icon"
|
||||
:label="view.label"
|
||||
/>
|
||||
</VueGroup>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<TerminalView
|
||||
ref="terminal"
|
||||
:class="{
|
||||
ghost: currentView !== '_output'
|
||||
}"
|
||||
:key="id"
|
||||
:cols="100"
|
||||
:rows="24"
|
||||
@@ -64,8 +87,16 @@
|
||||
}"
|
||||
:title="$t('views.project-task-details.output')"
|
||||
toolbar
|
||||
open-links
|
||||
@clear="clearLogs()"
|
||||
/>
|
||||
|
||||
<ClientAddonComponent
|
||||
v-if="currentView !== '_output'"
|
||||
:name="currentViewComponent"
|
||||
:key="currentView"
|
||||
class="view"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -106,6 +137,12 @@ import TASK_LOG_ADDED from '../graphql/taskLogAdded.gql'
|
||||
export default {
|
||||
name: 'ProjectTaskDetails',
|
||||
|
||||
provide () {
|
||||
return {
|
||||
TaskDetails: this
|
||||
}
|
||||
},
|
||||
|
||||
mixins: [
|
||||
Prompts({
|
||||
field: 'task',
|
||||
@@ -123,7 +160,8 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
task: null,
|
||||
showParameters: false
|
||||
showParameters: false,
|
||||
currentView: '_output'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -135,7 +173,14 @@ export default {
|
||||
id: this.id
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'cache-and-network'
|
||||
fetchPolicy: 'cache-and-network',
|
||||
async result ({ data, loading }) {
|
||||
if (!this.$_init && !loading && data && data.task.defaultView) {
|
||||
this.$_init = true
|
||||
await this.$nextTick()
|
||||
this.currentView = data.task.defaultView
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
taskLogs: {
|
||||
@@ -147,10 +192,13 @@ export default {
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
manual: true,
|
||||
result ({ data, loading }) {
|
||||
async result ({ data, loading }) {
|
||||
if (!loading) {
|
||||
await this.$nextTick()
|
||||
const terminal = this.$refs.terminal
|
||||
data.taskLogs.logs.forEach(terminal.addLog)
|
||||
if (terminal) {
|
||||
data.taskLogs.logs.forEach(terminal.addLog)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -163,8 +211,9 @@ export default {
|
||||
id: this.id
|
||||
}
|
||||
},
|
||||
result ({ data }) {
|
||||
async result ({ data }) {
|
||||
if (data.taskLogAdded.taskId === this.id) {
|
||||
await this.$nextTick()
|
||||
const terminal = this.$refs.terminal
|
||||
terminal.addLog(data.taskLogAdded)
|
||||
}
|
||||
@@ -173,9 +222,22 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentViewComponent () {
|
||||
if (this.currentView !== '_output') {
|
||||
const id = this.task.views.find(
|
||||
view => view.id === this.currentView
|
||||
).component
|
||||
return id
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
id () {
|
||||
this.showParameters = false
|
||||
this.currentView = '_output'
|
||||
this.$_init = false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -232,19 +294,40 @@ export default {
|
||||
.content
|
||||
flex 100% 1 1
|
||||
height 0
|
||||
padding 0 $padding-item $padding-item
|
||||
margin 0 $padding-item $padding-item
|
||||
position relative
|
||||
|
||||
.terminal-view
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 100%
|
||||
border-radius $br
|
||||
&.ghost
|
||||
opacity 0
|
||||
pointer-events none
|
||||
|
||||
.view
|
||||
max-height 100%
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
|
||||
.header
|
||||
padding $padding-item $padding-item 0
|
||||
h-box()
|
||||
align-items center
|
||||
|
||||
.task-icon
|
||||
margin-right 4px
|
||||
>>> svg
|
||||
fill $vue-ui-color-dark
|
||||
|
||||
.name
|
||||
font-size 18px
|
||||
font-size 22px
|
||||
color $vue-ui-color-dark
|
||||
position relative
|
||||
top -1px
|
||||
|
||||
.description
|
||||
color $color-text-light
|
||||
|
||||
@@ -575,6 +575,16 @@
|
||||
dependencies:
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@most/multicast@^1.2.5":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@most/multicast/-/multicast-1.3.0.tgz#e01574840df634478ac3fabd164c6e830fb3b966"
|
||||
dependencies:
|
||||
"@most/prelude" "^1.4.0"
|
||||
|
||||
"@most/prelude@^1.4.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@most/prelude/-/prelude-1.7.0.tgz#0956ed464ad03e7fc95143eac0c6dd028498d975"
|
||||
|
||||
"@mrmlnc/readdir-enhanced@^2.2.1":
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
|
||||
@@ -1486,7 +1496,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.26.0:
|
||||
invariant "^2.2.2"
|
||||
lodash "^4.17.4"
|
||||
|
||||
babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.0:
|
||||
babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.0, babel-types@^6.7.2:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
|
||||
dependencies:
|
||||
@@ -1499,7 +1509,7 @@ babylon@7.0.0-beta.40, babylon@^7.0.0-beta.40:
|
||||
version "7.0.0-beta.40"
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.40.tgz#91fc8cd56d5eb98b28e6fde41045f2957779940a"
|
||||
|
||||
babylon@^6.18.0:
|
||||
babylon@^6.18.0, babylon@^6.7.0:
|
||||
version "6.18.0"
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
|
||||
|
||||
@@ -1545,6 +1555,14 @@ bcrypt-pbkdf@^1.0.0:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
better-sqlite3@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-4.1.0.tgz#1dbb0b13b280ce1eebebad431502deb08b78941e"
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
integer "^1.0.3"
|
||||
lzz-gyp "^0.4.2"
|
||||
|
||||
big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
@@ -1553,6 +1571,10 @@ binary-extensions@^1.0.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
|
||||
|
||||
bindings@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e"
|
||||
@@ -3400,6 +3422,10 @@ duplexify@^3.4.2, duplexify@^3.5.3:
|
||||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
easy-stack@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/easy-stack/-/easy-stack-1.0.0.tgz#12c91b3085a37f0baa336e9486eac4bf94e3e788"
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
|
||||
@@ -3857,6 +3883,10 @@ event-emitter@~0.3.5:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
|
||||
event-pubsub@4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e"
|
||||
|
||||
event-stream@~3.3.0:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
|
||||
@@ -4090,6 +4120,12 @@ extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
|
||||
farmhash@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/farmhash/-/farmhash-1.2.1.tgz#2dbf12604ef5ca1f1420fb6600fccb30d5474dfd"
|
||||
dependencies:
|
||||
nan "^2.4.0"
|
||||
|
||||
fast-deep-equal@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
|
||||
@@ -4842,6 +4878,10 @@ graphql-tools@^2.21.0:
|
||||
iterall "^1.1.3"
|
||||
uuid "^3.1.0"
|
||||
|
||||
graphql-type-json@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-type-json/-/graphql-type-json-0.2.0.tgz#34c1a5fc2b7707a367c436482b253dbeebf1130e"
|
||||
|
||||
graphql@^0.13.0, graphql@^0.13.1:
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.1.tgz#9b3db3d8e40d1827e4172404bfdd2e4e17a58b55"
|
||||
@@ -5331,6 +5371,27 @@ inquirer@^4.0.1:
|
||||
strip-ansi "^4.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inspectpack@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/inspectpack/-/inspectpack-2.2.4.tgz#db783646d89f59133b3087c83f201d44546d0651"
|
||||
dependencies:
|
||||
babel-traverse "^6.26.0"
|
||||
babel-types "^6.7.2"
|
||||
babylon "^6.7.0"
|
||||
lodash "^4.6.1"
|
||||
uglify-es "^3.0.28"
|
||||
workerpool "^2.2.1"
|
||||
yargs "^4.3.1"
|
||||
optionalDependencies:
|
||||
better-sqlite3 "^4.0.2"
|
||||
farmhash "^1.2.1"
|
||||
|
||||
integer@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/integer/-/integer-1.0.3.tgz#6e6403896de6781c50974c5adf9eea9bb0ce6acb"
|
||||
dependencies:
|
||||
bindings "^1.3.0"
|
||||
|
||||
internal-ip@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
|
||||
@@ -6128,6 +6189,16 @@ js-beautify@^1.6.12, js-beautify@^1.6.14:
|
||||
mkdirp "~0.5.0"
|
||||
nopt "~3.0.1"
|
||||
|
||||
js-message@1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.5.tgz#2300d24b1af08e89dd095bc1a4c9c9cfcb892d15"
|
||||
|
||||
js-queue@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-queue/-/js-queue-2.0.0.tgz#362213cf860f468f0125fc6c96abc1742531f948"
|
||||
dependencies:
|
||||
easy-stack "^1.0.0"
|
||||
|
||||
js-tokens@^3.0.0, js-tokens@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
@@ -6712,6 +6783,10 @@ lodash.endswith@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09"
|
||||
|
||||
lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
|
||||
lodash.get@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f"
|
||||
@@ -6812,7 +6887,7 @@ lodash.uniq@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
|
||||
lodash@4, lodash@4.x, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
|
||||
lodash@4, lodash@4.x, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
|
||||
version "4.17.5"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||
|
||||
@@ -6902,6 +6977,10 @@ lru-cache@~2.6.5:
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
|
||||
|
||||
lzz-gyp@^0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lzz-gyp/-/lzz-gyp-0.4.2.tgz#495c44034a1f50c48a60f0ef9c28d42763a5b297"
|
||||
|
||||
macaddress@^0.2.8:
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
||||
@@ -7271,6 +7350,14 @@ moment@^2.6.0:
|
||||
version "2.21.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a"
|
||||
|
||||
most@^1.7.3:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/most/-/most-1.7.3.tgz#406c31a66d73aa16957816fdf96965e27df84f1a"
|
||||
dependencies:
|
||||
"@most/multicast" "^1.2.5"
|
||||
"@most/prelude" "^1.4.0"
|
||||
symbol-observable "^1.0.2"
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
@@ -7318,6 +7405,10 @@ nan@^2.3.0:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866"
|
||||
|
||||
nan@^2.4.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.9"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2"
|
||||
@@ -7397,6 +7488,14 @@ node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
|
||||
node-ipc@^9.1.1:
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/node-ipc/-/node-ipc-9.1.1.tgz#4e245ed6938e65100e595ebc5dc34b16e8dd5d69"
|
||||
dependencies:
|
||||
event-pubsub "4.3.0"
|
||||
js-message "1.0.5"
|
||||
js-queue "2.0.0"
|
||||
|
||||
"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
|
||||
@@ -7578,7 +7677,7 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
|
||||
|
||||
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
object-assign@4.1.1, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
@@ -10233,7 +10332,7 @@ typescript@^2.7.2:
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836"
|
||||
|
||||
uglify-es@^3.3.4:
|
||||
uglify-es@^3.0.28, uglify-es@^3.3.4:
|
||||
version "3.3.9"
|
||||
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
|
||||
dependencies:
|
||||
@@ -10688,6 +10787,10 @@ vue-parser@^1.1.5:
|
||||
dependencies:
|
||||
parse5 "^3.0.3"
|
||||
|
||||
vue-progress-path@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-progress-path/-/vue-progress-path-0.0.2.tgz#d780fc7a96dbc7f784eb52895aeef7a5f0af6325"
|
||||
|
||||
vue-property-decorator@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-6.0.0.tgz#bb651b293542e31db0d24f36f4b0250ef08d8515"
|
||||
@@ -11052,6 +11155,12 @@ worker-farm@^1.5.2:
|
||||
errno "~0.1.7"
|
||||
xtend "~4.0.1"
|
||||
|
||||
workerpool@^2.2.1:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-2.3.0.tgz#86c5cbe946b55e7dc9d12b1936c8801a6e2d744d"
|
||||
dependencies:
|
||||
object-assign "4.1.1"
|
||||
|
||||
wrap-ansi@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
|
||||
@@ -11246,7 +11355,7 @@ yargs@^11.0.0:
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^9.0.2"
|
||||
|
||||
yargs@^4.8.0:
|
||||
yargs@^4.3.1, yargs@^4.8.0:
|
||||
version "4.8.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user