-
-
- {{ projectCurrent.name }}
- No project
-
+
+
+
+
+
+ {{ projectCurrent.name }}
+ No project
+
+
+
+
-
-
-
-
-
- {{ data.cwd }}
-
-
-
-
-
-
{{ consoleLog }}
+
+
+ {{ data.cwd }}
+
+
+
+
+
+
+
+
@@ -62,10 +116,14 @@ export default {
@import "~@/style/imports"
.status-bar
- h-box()
- align-items center
- background $vue-color-light-neutral
- font-size $padding-item
+ $bg = $vue-color-light-neutral
+
+ .content
+ h-box()
+ align-items center
+ background $bg
+ font-size $padding-item
+ height 28px
.section
h-box()
@@ -77,11 +135,20 @@ export default {
&:hover
opacity 1
- background lighten(@background, 40%)
+ background lighten($bg, 40%)
> .vue-icon + *
margin-left 4px
.label
color lighten($vue-color-dark, 20%)
+
+ .console-log
+ &,
+ .last-message
+ flex 100% 1 1
+ width 0
+
+ .last-message
+ font-size .9em
diff --git a/packages/@vue/cli-ui/src/components/TerminalView.vue b/packages/@vue/cli-ui/src/components/TerminalView.vue
new file mode 100644
index 0000000000..2984a8161e
--- /dev/null
+++ b/packages/@vue/cli-ui/src/components/TerminalView.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/@vue/cli-ui/src/graphql-api/channels.js b/packages/@vue/cli-ui/src/graphql-api/channels.js
index f205c04259..6fd0584afc 100644
--- a/packages/@vue/cli-ui/src/graphql-api/channels.js
+++ b/packages/@vue/cli-ui/src/graphql-api/channels.js
@@ -1,4 +1,5 @@
module.exports = {
CWD_CHANGED: 'cwd_changed',
- CREATE_STATUS: 'create_status'
+ PROGRESS_CHANGED: 'progress_changed',
+ CONSOLE_LOG_ADDED: 'console_log_added'
}
diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/folders.js b/packages/@vue/cli-ui/src/graphql-api/connectors/folders.js
index ba56d8911d..e8703918fc 100644
--- a/packages/@vue/cli-ui/src/graphql-api/connectors/folders.js
+++ b/packages/@vue/cli-ui/src/graphql-api/connectors/folders.js
@@ -1,5 +1,6 @@
const path = require('path')
const fs = require('fs')
+const rimraf = require('rimraf')
const cwd = require('./cwd')
@@ -77,6 +78,18 @@ function setFavorite ({ file, favorite }, context) {
return generateFolder(file, context)
}
+function deleteFolder (file) {
+ return new Promise((resolve, reject) => {
+ rimraf(file, err => {
+ if (err) {
+ reject(err)
+ } else {
+ resolve()
+ }
+ })
+ })
+}
+
module.exports = {
getCurrent,
list,
@@ -86,5 +99,6 @@ module.exports = {
readPackage,
isVueProject,
listFavorite,
- setFavorite
+ setFavorite,
+ delete: deleteFolder
}
diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/logs.js b/packages/@vue/cli-ui/src/graphql-api/connectors/logs.js
new file mode 100644
index 0000000000..8041d8af8b
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql-api/connectors/logs.js
@@ -0,0 +1,53 @@
+const shortId = require('shortid')
+const { events } = require('@vue/cli-shared-utils/lib/logger')
+const { generateTitle } = require('@vue/cli/lib/util/clearConsole')
+
+const channels = require('../channels')
+
+let init = false
+let logs = []
+
+exports.add = function (log, context) {
+ const item = {
+ id: shortId.generate(),
+ date: new Date().toISOString(),
+ tag: null,
+ ...log
+ }
+ logs.push(item)
+ context.pubsub.publish(channels.CONSOLE_LOG_ADDED, {
+ consoleLogAdded: item
+ })
+ return item
+}
+
+exports.init = function (context) {
+ if (!init) {
+ init = true
+ events.on('log', log => {
+ exports.add(log, context)
+ })
+
+ exports.add({
+ type: 'info',
+ tag: null,
+ message: generateTitle(true)
+ }, context)
+ }
+}
+
+exports.list = function (context) {
+ return logs
+}
+
+exports.last = function (context) {
+ if (logs.length) {
+ return logs[logs.length - 1]
+ }
+ return null
+}
+
+exports.clear = function (context) {
+ logs = []
+ return logs
+}
diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/progress.js b/packages/@vue/cli-ui/src/graphql-api/connectors/progress.js
new file mode 100644
index 0000000000..9de2630d04
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql-api/connectors/progress.js
@@ -0,0 +1,59 @@
+const channels = require('../channels')
+
+let map = new Map()
+
+function get (id, context) {
+ return map.get(id)
+}
+
+function set (data, context) {
+ const { id } = data
+ let progress = get(id, context)
+ if (!progress) {
+ progress = data
+ map.set(id, Object.assign({}, {
+ status: null,
+ error: null,
+ info: null,
+ progress: -1
+ }, progress))
+ } else {
+ Object.assign(progress, data)
+ }
+ context.pubsub.publish(channels.PROGRESS_CHANGED, { progressChanged: progress })
+ return progress
+}
+
+function remove (id, context) {
+ return map.delete(id)
+}
+
+async function wrap (id, context, operation) {
+ set({ id }, context)
+
+ let result
+ let error = null
+ try {
+ result = await operation(data => {
+ set(Object.assign({ id }, data), context)
+ })
+ } catch (e) {
+ error = e
+ set({ id, error: error.message }, context)
+ }
+
+ remove(id, context)
+
+ if (error) {
+ throw error
+ }
+
+ return result
+}
+
+module.exports = {
+ get,
+ set,
+ remove,
+ wrap
+}
diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js b/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js
index c0294c21d4..5c505e3e87 100644
--- a/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js
+++ b/packages/@vue/cli-ui/src/graphql-api/connectors/projects.js
@@ -1,24 +1,27 @@
const path = require('path')
const fs = require('fs')
const shortId = require('shortid')
-const rimraf = require('rimraf')
const Creator = require('@vue/cli/lib/Creator')
const { getPromptModules } = require('@vue/cli/lib/util/createTools')
const { getFeatures } = require('@vue/cli/lib/util/features')
const { defaults } = require('@vue/cli/lib/options')
const { toShortPluginId } = require('@vue/cli-shared-utils')
+const { progress: installProgress } = require('@vue/cli/lib/util/installDeps')
-const channels = require('../channels')
-
+const progress = require('./progress')
const cwd = require('./cwd')
const prompts = require('./prompts')
const folders = require('./folders')
+const PROGRESS_ID = 'project-create'
+
let currentProject = null
let creator = null
let presets = []
let features = []
let onCreationEvent = null
+let onInstallProgress = null
+let onInstallLog = null
function list (context) {
return context.db.get('projects').value()
@@ -50,9 +53,23 @@ function initCreator (context) {
const creator = new Creator('', cwd.get(), getPromptModules())
onCreationEvent = ({ event }) => {
- context.pubsub.publish(channels.CREATE_STATUS, { createStatus: event })
+ progress.set({ id: PROGRESS_ID, status: event, info: null }, context)
+ }
+ creator.on('creation', onCreationEvent)
+
+ onInstallProgress = value => {
+ if (progress.get(PROGRESS_ID)) {
+ progress.set({ id: PROGRESS_ID, progress: value }, context)
+ }
+ }
+ installProgress.on('progress', onInstallProgress)
+
+ onInstallLog = message => {
+ if (progress.get(PROGRESS_ID)) {
+ progress.set({ id: PROGRESS_ID, info: message }, context)
+ }
}
- creator.addListener('creation', onCreationEvent)
+ installProgress.on('log', onInstallLog)
// Presets
const presetsData = creator.getPresets()
@@ -116,6 +133,8 @@ function initCreator (context) {
function removeCreator (context) {
if (creator) {
creator.removeListener('creation', onCreationEvent)
+ installProgress.removeListener('progress', onInstallProgress)
+ installProgress.removeListener('log', onInstallLog)
creator = null
}
}
@@ -185,59 +204,77 @@ function answerPrompt ({ id, value }, context) {
}
async function create (input, context) {
- const targetDir = path.join(cwd.get(), input.folder)
- creator.context = targetDir
-
- const inCurrent = input.folder === '.'
- const name = inCurrent ? path.relative('../', process.cwd()) : input.folder
- creator.name = name
-
- if (fs.existsSync(targetDir)) {
- if (input.force) {
- rimraf.sync(targetDir)
- } else {
- throw new Error(`Folder ${targetDir} already exists`)
+ return progress.wrap(PROGRESS_ID, context, async setProgress => {
+ setProgress({
+ status: 'creating'
+ })
+
+ const targetDir = path.join(cwd.get(), input.folder)
+ creator.context = targetDir
+
+ const inCurrent = input.folder === '.'
+ const name = inCurrent ? path.relative('../', process.cwd()) : input.folder
+ creator.name = name
+
+ if (fs.existsSync(targetDir)) {
+ if (input.force) {
+ setProgress({
+ info: 'Cleaning folder...'
+ })
+ await folders.delete(targetDir)
+ setProgress({
+ info: null
+ })
+ } else {
+ throw new Error(`Folder ${targetDir} already exists`)
+ }
}
- }
- const answers = prompts.getAnswers()
- prompts.reset()
- let index
+ const answers = prompts.getAnswers()
+ prompts.reset()
+ let index
- // Package Manager
- answers.packageManager = input.packageManager
+ // Package Manager
+ answers.packageManager = input.packageManager
- // Config files
- if ((index = answers.features.includes('use-config-files')) !== -1) {
- answers.features.splice(index, 1)
- answers.useConfigFiles = 'files'
- }
+ // Config files
+ if ((index = answers.features.includes('use-config-files')) !== -1) {
+ answers.features.splice(index, 1)
+ answers.useConfigFiles = 'files'
+ }
- // Preset
- answers.preset = input.preset
- if (input.save) {
- answers.save = true
- answers.saveName = input.save
- }
+ // Preset
+ answers.preset = input.preset
+ if (input.save) {
+ answers.save = true
+ answers.saveName = input.save
+ }
- let preset
- if (input.remote) {
- // vue create foo --preset bar
- preset = await creator.resolvePreset(input.preset, input.clone)
- } else if (input.preset === 'default') {
- // vue create foo --default
- preset = defaults.presets.default
- } else {
- preset = await creator.promptAndResolvePreset(answers)
- }
+ setProgress({
+ info: 'Resolving preset...'
+ })
+ let preset
+ if (input.remote) {
+ // vue create foo --preset bar
+ preset = await creator.resolvePreset(input.preset, input.clone)
+ } else if (input.preset === 'default') {
+ // vue create foo --default
+ preset = defaults.presets.default
+ } else {
+ preset = await creator.promptAndResolvePreset(answers)
+ }
+ setProgress({
+ info: null
+ })
- await creator.create({}, preset)
+ await creator.create({}, preset)
- removeCreator()
+ removeCreator()
- return importProject({
- path: targetDir
- }, context)
+ return importProject({
+ path: targetDir
+ }, context)
+ })
}
async function importProject (input, context) {
diff --git a/packages/@vue/cli-ui/src/graphql-api/resolvers.js b/packages/@vue/cli-ui/src/graphql-api/resolvers.js
index 715479bc25..dce421ac72 100644
--- a/packages/@vue/cli-ui/src/graphql-api/resolvers.js
+++ b/packages/@vue/cli-ui/src/graphql-api/resolvers.js
@@ -1,12 +1,20 @@
+const { withFilter } = require('graphql-subscriptions')
const exit = require('@vue/cli-shared-utils/lib/exit')
+
const channels = require('./channels')
+
+// Connectors
const cwd = require('./connectors/cwd')
const folders = require('./connectors/folders')
const projects = require('./connectors/projects')
+const progress = require('./connectors/progress')
+const logs = require('./connectors/logs')
// Prevent code from exiting server process
exit.exitProcess = false
+process.env.VUE_CLI_API_MODE = true
+
module.exports = {
Folder: {
children: (folder, args, context) => folders.list(folder.path, context),
@@ -20,6 +28,9 @@ module.exports = {
Query: {
cwd: () => cwd.get(),
+ consoleLogs: (root, args, context) => logs.list(context),
+ consoleLogLast: (root, args, context) => logs.last(context),
+ progress: (root, { id }, context) => progress.get(id, context),
folderCurrent: (root, args, context) => folders.getCurrent(args, context),
foldersFavorite: (root, args, context) => folders.listFavorite(context),
projects: (root, args, context) => projects.list(context),
@@ -28,6 +39,7 @@ module.exports = {
},
Mutation: {
+ consoleLogsClear: (root, args, context) => logs.clear(context),
folderOpen: (root, { path }, context) => folders.open(path, context),
folderOpenParent: (root, args, context) => folders.openParent(cwd.get(), context),
folderSetFavorite: (root, args, context) => folders.setFavorite({
@@ -47,8 +59,19 @@ module.exports = {
cwdChanged: {
subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator(channels.CWD_CHANGED)
},
- createStatus: {
- subscribe: (parent, args, { pubsub }) => pubsub.asyncIterator(channels.CREATE_STATUS)
+ progressChanged: {
+ subscribe: withFilter(
+ // Iterator
+ (parent, args, { pubsub }) => pubsub.asyncIterator(channels.PROGRESS_CHANGED),
+ // Filter
+ (payload, variables) => payload.progressChanged.id === variables.id
+ )
+ },
+ consoleLogAdded: {
+ subscribe: (parent, args, context) => {
+ logs.init(context)
+ return context.pubsub.asyncIterator(channels.CONSOLE_LOG_ADDED)
+ }
}
}
}
diff --git a/packages/@vue/cli-ui/src/graphql-api/type-defs.js b/packages/@vue/cli-ui/src/graphql-api/type-defs.js
index 0b0cd67b2b..c5f9430910 100644
--- a/packages/@vue/cli-ui/src/graphql-api/type-defs.js
+++ b/packages/@vue/cli-ui/src/graphql-api/type-defs.js
@@ -3,9 +3,9 @@ module.exports = `
type ConsoleLog {
id: ID!
message: String!
- label: String
tag: String
type: ConsoleLogType!
+ date: String
}
enum ConsoleLogType {
@@ -140,8 +140,20 @@ input PromptInput {
value: String!
}
+type Progress {
+ id: ID!
+ status: String
+ info: String
+ error: String
+ # Progress from 0 to 1 (-1 means disabled)
+ progress: Float
+}
+
type Query {
+ progress (id: ID!): Progress
cwd: String!
+ consoleLogs: [ConsoleLog]
+ consoleLogLast: ConsoleLog
folderCurrent: Folder
foldersFavorite: [Folder]
projects: [Project]
@@ -151,6 +163,7 @@ type Query {
}
type Mutation {
+ consoleLogsClear: [ConsoleLog]
folderOpen (path: String!): Folder
folderOpenParent: Folder
folderSetFavorite (path: String!, favorite: Boolean!): Folder
@@ -166,8 +179,8 @@ type Mutation {
}
type Subscription {
+ progressChanged (id: ID!): Progress
consoleLogAdded: ConsoleLog!
cwdChanged: String!
- createStatus: String!
}
`
diff --git a/packages/@vue/cli-ui/src/graphql/consoleLogAdded.gql b/packages/@vue/cli-ui/src/graphql/consoleLogAdded.gql
new file mode 100644
index 0000000000..3e5e5bb46a
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/consoleLogAdded.gql
@@ -0,0 +1,7 @@
+#import "./consoleLogFragment.gql"
+
+subscription consoleLogAdded {
+ consoleLogAdded {
+ ...consoleLog
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/consoleLogFragment.gql b/packages/@vue/cli-ui/src/graphql/consoleLogFragment.gql
new file mode 100644
index 0000000000..fcf64b1f26
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/consoleLogFragment.gql
@@ -0,0 +1,7 @@
+fragment consoleLog on ConsoleLog {
+ id
+ type
+ message
+ tag
+ date
+}
diff --git a/packages/@vue/cli-ui/src/graphql/consoleLogLast.gql b/packages/@vue/cli-ui/src/graphql/consoleLogLast.gql
new file mode 100644
index 0000000000..e7fca0d77d
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/consoleLogLast.gql
@@ -0,0 +1,7 @@
+#import "./consoleLogFragment.gql"
+
+query consoleLogLast {
+ consoleLogLast {
+ ...consoleLog
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/consoleLogs.gql b/packages/@vue/cli-ui/src/graphql/consoleLogs.gql
new file mode 100644
index 0000000000..685181b651
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/consoleLogs.gql
@@ -0,0 +1,7 @@
+#import "./consoleLogFragment.gql"
+
+query consoleLogs {
+ consoleLogs {
+ ...consoleLog
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/consoleLogsClear.gql b/packages/@vue/cli-ui/src/graphql/consoleLogsClear.gql
new file mode 100644
index 0000000000..4aea6b9140
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/consoleLogsClear.gql
@@ -0,0 +1,7 @@
+#import "./consoleLogFragment.gql"
+
+mutation consoleLogsClear {
+ consoleLogsClear {
+ ...consoleLog
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/createStatus.gql b/packages/@vue/cli-ui/src/graphql/createStatus.gql
deleted file mode 100644
index 1b2d42caff..0000000000
--- a/packages/@vue/cli-ui/src/graphql/createStatus.gql
+++ /dev/null
@@ -1,3 +0,0 @@
-subscription createStatus {
- createStatus
-}
diff --git a/packages/@vue/cli-ui/src/graphql/progress.gql b/packages/@vue/cli-ui/src/graphql/progress.gql
new file mode 100644
index 0000000000..697ced8324
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/progress.gql
@@ -0,0 +1,7 @@
+#import "./progressFragment.gql"
+
+query progress ($id: ID!) {
+ progress (id: $id) {
+ ...progress
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/progressChanged.gql b/packages/@vue/cli-ui/src/graphql/progressChanged.gql
new file mode 100644
index 0000000000..fa2b5a803c
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/progressChanged.gql
@@ -0,0 +1,7 @@
+#import "./progressFragment.gql"
+
+subscription progressChanged ($id: ID!) {
+ progressChanged (id: $id) {
+ ...progress
+ }
+}
diff --git a/packages/@vue/cli-ui/src/graphql/progressFragment.gql b/packages/@vue/cli-ui/src/graphql/progressFragment.gql
new file mode 100644
index 0000000000..633cb11de1
--- /dev/null
+++ b/packages/@vue/cli-ui/src/graphql/progressFragment.gql
@@ -0,0 +1,7 @@
+fragment progress on Progress {
+ id
+ status
+ info
+ error
+ progress
+}
diff --git a/packages/@vue/cli-ui/src/mixins/Progress.js b/packages/@vue/cli-ui/src/mixins/Progress.js
new file mode 100644
index 0000000000..712b0701cf
--- /dev/null
+++ b/packages/@vue/cli-ui/src/mixins/Progress.js
@@ -0,0 +1,68 @@
+import PROGRESS from '../graphql/progress.gql'
+import PROGRESS_CHANGED from '../graphql/progressChanged.gql'
+
+const messages = {
+ 'creating': 'Creating project...',
+ 'git-init': 'Initializing git repository...',
+ 'plugins-install': 'Installing CLI plugins. This might take a while...',
+ 'invoking-generators': 'Invoking generators...',
+ 'deps-install': 'Installing additional dependencies...',
+ 'completion-hooks': 'Running completion hooks...',
+ 'fetch-remote-preset': `Fetching remote preset...`,
+ 'done': 'Successfully created project'
+}
+
+// @vue/component
+export default {
+ props: {
+ progressId: {
+ type: String,
+ required: true
+ }
+ },
+
+ data () {
+ return {
+ progress: null
+ }
+ },
+
+ apollo: {
+ progress: {
+ query: PROGRESS,
+ variables () {
+ return {
+ id: this.progressId
+ }
+ },
+ fetchPolicy: 'network-only',
+ subscribeToMore: {
+ document: PROGRESS_CHANGED,
+ variables () {
+ return {
+ id: this.progressId
+ }
+ },
+ updateQuery: (previousResult, { subscriptionData }) => {
+ return {
+ progress: subscriptionData.data.progressChanged
+ }
+ }
+ }
+ }
+ },
+
+ computed: {
+ loading () {
+ return this.progress && !this.progress.error
+ },
+
+ statusMessage () {
+ if (!this.progress) return null
+
+ const { status } = this.progress
+ const message = messages[status]
+ return message || status || ''
+ }
+ }
+}
diff --git a/packages/@vue/cli-ui/src/style/colors.styl b/packages/@vue/cli-ui/src/style/colors.styl
index f1ee08c99a..033dd3000a 100644
--- a/packages/@vue/cli-ui/src/style/colors.styl
+++ b/packages/@vue/cli-ui/src/style/colors.styl
@@ -1 +1,2 @@
$color-light-background = lighten($vue-color-light-neutral, 80%)
+$color-text-light = lighten($vue-color-dark, 40%)
diff --git a/packages/@vue/cli-ui/src/style/main.styl b/packages/@vue/cli-ui/src/style/main.styl
index 8e36bd280f..dc4d6385d9 100644
--- a/packages/@vue/cli-ui/src/style/main.styl
+++ b/packages/@vue/cli-ui/src/style/main.styl
@@ -32,8 +32,17 @@ body,
.cta-text
margin $padding-item
- color lighten($vue-color-dark, 40%)
+ color $color-text-light
font-size 18px
.list-item
list-item()
+
+ansi-colors('black', $vue-color-dark)
+ansi-colors('red', $vue-color-danger)
+ansi-colors('green', $vue-color-primary)
+ansi-colors('yellow', $vue-color-warning)
+ansi-colors('blue', $md-blue)
+ansi-colors('magenta', $vue-color-accent)
+ansi-colors('cyan', $vue-color-info)
+ansi-colors('white', $vue-color-light)
diff --git a/packages/@vue/cli-ui/src/style/mixins.styl b/packages/@vue/cli-ui/src/style/mixins.styl
index de10c8b9b1..49e16a2043 100644
--- a/packages/@vue/cli-ui/src/style/mixins.styl
+++ b/packages/@vue/cli-ui/src/style/mixins.styl
@@ -7,3 +7,14 @@ list-item()
&:hover
background rgba($vue-color-primary, .1)
+
+
+ansi-colors($name, $color)
+ .ansi-{$name}-fg
+ color $color
+ .ansi-{$name}-bg
+ background $color
+ .ansi-bright-{$name}-fg
+ color lighten($color, 10%)
+ .ansi-bright-{$name}-bg
+ background lighten($color, 10%)
diff --git a/packages/@vue/cli-ui/src/views/ProjectCreate.vue b/packages/@vue/cli-ui/src/views/ProjectCreate.vue
index bb1c47c202..f3adf67a6c 100644
--- a/packages/@vue/cli-ui/src/views/ProjectCreate.vue
+++ b/packages/@vue/cli-ui/src/views/ProjectCreate.vue
@@ -297,7 +297,7 @@
@close="showCancel = false"
>
- Are you sure you want to cancel the project creation?
+ Are you sure you want to reset the project creation?
@@ -312,7 +312,7 @@
label="Clear project"
icon-left="delete_forever"
class="danger"
- @click="cancel()"
+ @click="reset()"
/>
@@ -358,36 +358,14 @@
-