mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-03-13 12:40:18 -05:00
feat(ui): Progress and Logs systems
This commit is contained in:
@@ -1,6 +1,19 @@
|
||||
const chalk = require('chalk')
|
||||
const readline = require('readline')
|
||||
const padStart = require('string.prototype.padstart')
|
||||
const EventEmitter = require('events')
|
||||
|
||||
exports.events = new EventEmitter()
|
||||
|
||||
function _log (type, tag, message) {
|
||||
if (process.env.VUE_CLI_API_MODE && message) {
|
||||
exports.events.emit('log', {
|
||||
message,
|
||||
type,
|
||||
tag
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const format = (label, msg) => {
|
||||
return msg.split('\n').map((line, i) => {
|
||||
@@ -12,24 +25,32 @@ const format = (label, msg) => {
|
||||
|
||||
const chalkTag = msg => chalk.bgBlackBright.white.dim(` ${msg} `)
|
||||
|
||||
exports.log = (msg = '', tag = null) => tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
|
||||
exports.log = (msg = '', tag = null) => {
|
||||
tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
|
||||
_log('log', tag, msg)
|
||||
}
|
||||
|
||||
exports.info = (msg, tag = null) => {
|
||||
console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg))
|
||||
_log('info', tag, msg)
|
||||
}
|
||||
|
||||
exports.done = (msg, tag = null) => {
|
||||
console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg))
|
||||
_log('done', tag, msg)
|
||||
}
|
||||
|
||||
exports.warn = (msg, tag = null) => {
|
||||
console.warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg)))
|
||||
_log('warn', tag, msg)
|
||||
}
|
||||
|
||||
exports.error = (msg, tag = null) => {
|
||||
console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)))
|
||||
_log('error', tag, msg)
|
||||
if (msg instanceof Error) {
|
||||
console.error(msg.stack)
|
||||
_log('error', tag, msg.stack)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@vue/cli-service": "^3.0.0-beta.3",
|
||||
"@vue/eslint-config-standard": "^3.0.0-beta.3",
|
||||
"@vue/ui": "^0.1.9",
|
||||
"ansi_up": "^2.0.2",
|
||||
"apollo-cache-inmemory": "^1.1.10",
|
||||
"apollo-client": "^2.2.6",
|
||||
"apollo-link": "^1.2.1",
|
||||
@@ -38,7 +39,8 @@
|
||||
"vue-apollo": "^3.0.0-alpha.1",
|
||||
"vue-cli-plugin-apollo": "^0.4.1",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.13"
|
||||
"vue-template-compiler": "^2.5.13",
|
||||
"xterm": "^3.2.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
.app
|
||||
display grid
|
||||
grid-template-columns 1fr
|
||||
grid-template-rows auto 28px
|
||||
grid-template-rows 1fr auto
|
||||
grid-template-areas "content" "status"
|
||||
|
||||
.content
|
||||
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
justify-content center
|
||||
|
||||
.description
|
||||
color lighten($vue-color-dark, 40%)
|
||||
color $color-text-light
|
||||
|
||||
&.selected
|
||||
.name
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="loading-screen"
|
||||
:class="{
|
||||
loading
|
||||
}"
|
||||
>
|
||||
<VueLoadingIndicator
|
||||
class="primary big overlay fixed"
|
||||
>
|
||||
<div class="content">
|
||||
<slot/>
|
||||
</div>
|
||||
</VueLoadingIndicator>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DisableScroll } from '@vue/ui'
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
DisableScroll
|
||||
],
|
||||
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@/style/imports"
|
||||
|
||||
.loading-screen
|
||||
position absolute
|
||||
.content
|
||||
display grid
|
||||
grid-template-columns 1fr
|
||||
grid-gap $padding-item
|
||||
|
||||
>>> .error
|
||||
color $vue-color-danger
|
||||
v-box()
|
||||
box-center()
|
||||
> .vue-icon
|
||||
margin-bottom $padding-item
|
||||
svg
|
||||
fill @color
|
||||
.actions
|
||||
margin-top $padding-item
|
||||
|
||||
&:not(.loading)
|
||||
.vue-loading-indicator
|
||||
>>> .animation
|
||||
display none
|
||||
|
||||
&.loading
|
||||
.content
|
||||
margin-top $padding-item
|
||||
</style>
|
||||
105
packages/@vue/cli-ui/src/components/LoggerMessage.vue
Normal file
105
packages/@vue/cli-ui/src/components/LoggerMessage.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div
|
||||
class="logger-message"
|
||||
:class="[
|
||||
`type-${message.type}`,
|
||||
{
|
||||
'has-type': message.type !== 'log',
|
||||
'has-tag': message.tag,
|
||||
pre
|
||||
}
|
||||
]"
|
||||
>
|
||||
<div v-if="message.type !== 'log'" class="type">{{ message.type }}</div>
|
||||
<div v-if="message.tag" class="tag">{{ message.tag }}</div>
|
||||
<div class="message" v-html="formattedMessage"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AU from 'ansi_up'
|
||||
|
||||
const ansiUp = new AU()
|
||||
ansiUp.use_classes = true
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
|
||||
pre: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
formattedMessage () {
|
||||
return ansiUp.ansi_to_html(this.message.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@/style/imports"
|
||||
|
||||
.logger-message
|
||||
h-box()
|
||||
align-items baseline
|
||||
font-family 'Roboto Mono', monospace
|
||||
box-sizing border-box
|
||||
padding 2px 4px
|
||||
|
||||
.type,
|
||||
.tag
|
||||
padding 2px 6px
|
||||
border-radius $br
|
||||
|
||||
.type
|
||||
text-transform uppercase
|
||||
|
||||
&.type-warn
|
||||
.type
|
||||
background $vue-color-warning
|
||||
color $vue-color-light
|
||||
&.type-error
|
||||
.type
|
||||
background $vue-color-danger
|
||||
color $vue-color-light
|
||||
&.type-info
|
||||
.type
|
||||
background $vue-color-info
|
||||
color $vue-color-light
|
||||
&.type-done
|
||||
.type
|
||||
background $vue-color-success
|
||||
color $vue-color-light
|
||||
|
||||
.tag
|
||||
background lighten($vue-color-dark, 60%)
|
||||
|
||||
&.has-type.has-tag
|
||||
.type
|
||||
border-top-right-radius 0
|
||||
border-bottom-right-radius 0
|
||||
.tag
|
||||
border-top-left-radius 0
|
||||
border-bottom-left-radius 0
|
||||
|
||||
.message
|
||||
flex 100% 1 1
|
||||
width 0
|
||||
ellipsis()
|
||||
|
||||
&.has-type,
|
||||
&.has-tag
|
||||
.message
|
||||
margin-left 12px
|
||||
|
||||
&.pre
|
||||
.message
|
||||
white-space pre-wrap
|
||||
</style>
|
||||
83
packages/@vue/cli-ui/src/components/LoggerView.vue
Normal file
83
packages/@vue/cli-ui/src/components/LoggerView.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="logger-view">
|
||||
<ApolloQuery
|
||||
:query="require('../graphql/consoleLogs.gql')"
|
||||
fetch-policy="cache-and-network"
|
||||
class="logs"
|
||||
>
|
||||
<ApolloSubscribeToMore
|
||||
ref="logs"
|
||||
:document="require('../graphql/consoleLogAdded.gql')"
|
||||
:update-query="onConsoleLogAdded"
|
||||
/>
|
||||
|
||||
<template slot-scope="{ result: { data } }">
|
||||
<template v-if="data && data.consoleLogs">
|
||||
<LoggerMessage
|
||||
v-for="log of data.consoleLogs"
|
||||
:key="log.id"
|
||||
:message="log"
|
||||
pre
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</ApolloQuery>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoggerMessage from './LoggerMessage'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoggerMessage
|
||||
},
|
||||
|
||||
methods: {
|
||||
onConsoleLogAdded (previousResult, { subscriptionData }) {
|
||||
this.scrollToBottom()
|
||||
return {
|
||||
consoleLogs: [
|
||||
...previousResult.consoleLogs,
|
||||
subscriptionData.data.consoleLogAdded
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
async scrollToBottom () {
|
||||
await this.$nextTick()
|
||||
const list = this.$refs.logs.$el
|
||||
list.scrollTop = list.scrollHeight
|
||||
console.log(list.scrollHeight)
|
||||
},
|
||||
|
||||
clearLogs () {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@/style/imports"
|
||||
|
||||
.logger-view
|
||||
background $vue-color-light-neutral
|
||||
padding $padding-item 0
|
||||
height 160px
|
||||
display grid
|
||||
grid-template-columns 1fr
|
||||
grid-template-rows 1fr
|
||||
grid-template-areas "logs"
|
||||
|
||||
.logs
|
||||
grid-area logs
|
||||
padding 0 $padding-item
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
font-size 12px
|
||||
|
||||
.logger-message
|
||||
&:hover
|
||||
background lighten(@background, 40%)
|
||||
</style>
|
||||
117
packages/@vue/cli-ui/src/components/ProgressScreen.vue
Normal file
117
packages/@vue/cli-ui/src/components/ProgressScreen.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<transition name="vue-fade">
|
||||
<div
|
||||
v-if="progress"
|
||||
class="loading-screen"
|
||||
:class="{
|
||||
loading
|
||||
}"
|
||||
>
|
||||
<VueLoadingIndicator
|
||||
class="primary big overlay fixed"
|
||||
>
|
||||
<div class="content">
|
||||
<div v-if="progress.error" class="error">
|
||||
<VueIcon
|
||||
icon="error"
|
||||
class="huge"
|
||||
/>
|
||||
<div>{{ progress.error }}</div>
|
||||
<div class="actions">
|
||||
<VueButton
|
||||
icon-left="close"
|
||||
label="Close"
|
||||
@click="close()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="statusMessage" class="status">
|
||||
{{ statusMessage }}
|
||||
</div>
|
||||
|
||||
<div class="secondary-info">
|
||||
<div v-if="progress.info" class="info">
|
||||
{{ progress.info }}
|
||||
</div>
|
||||
|
||||
<VueLoadingBar
|
||||
v-if="progress.progress !== -1"
|
||||
:value="progress.progress"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</VueLoadingIndicator>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DisableScroll } from '@vue/ui'
|
||||
import Progress from '../mixins/Progress'
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
DisableScroll,
|
||||
Progress
|
||||
],
|
||||
|
||||
methods: {
|
||||
close () {
|
||||
this.progress = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@/style/imports"
|
||||
|
||||
.loading-screen
|
||||
position absolute
|
||||
|
||||
.content
|
||||
display grid
|
||||
grid-template-columns 1fr
|
||||
grid-gap $padding-item
|
||||
text-align center
|
||||
|
||||
.error
|
||||
color $vue-color-danger
|
||||
v-box()
|
||||
box-center()
|
||||
> .vue-icon
|
||||
margin-bottom $padding-item
|
||||
>>> svg
|
||||
fill @color
|
||||
.actions
|
||||
margin-top $padding-item
|
||||
|
||||
.secondary-info
|
||||
position absolute
|
||||
bottom 42px
|
||||
left 0
|
||||
right 0
|
||||
v-box()
|
||||
box-center()
|
||||
|
||||
.info
|
||||
color $color-text-light
|
||||
|
||||
.vue-loading-bar
|
||||
width 50vw
|
||||
max-width 400px
|
||||
margin-top 24px
|
||||
|
||||
|
||||
&:not(.loading)
|
||||
.vue-loading-indicator
|
||||
>>> .animation
|
||||
display none
|
||||
|
||||
&.loading
|
||||
.content
|
||||
margin-top $padding-item
|
||||
</style>
|
||||
@@ -52,7 +52,8 @@ export default {
|
||||
button-colors(rgba($vue-color-light, .7), transparent)
|
||||
border-radius 0
|
||||
&:hover
|
||||
button-colors($vue-color-light, lighten($vue-color-dark, 10%))
|
||||
$bg = darken($vue-color-dark, 70%)
|
||||
button-colors($vue-color-light, $bg)
|
||||
&.selected
|
||||
button-colors($vue-color-primary, lighten($vue-color-dark, 10%))
|
||||
button-colors(lighten($vue-color-primary, 40%), $bg)
|
||||
</style>
|
||||
|
||||
@@ -1,58 +1,112 @@
|
||||
<template>
|
||||
<div class="status-bar">
|
||||
<div
|
||||
class="section current-project"
|
||||
v-tooltip="'Current project'"
|
||||
@click="$emit('project')"
|
||||
>
|
||||
<VueIcon icon="work"/>
|
||||
<span v-if="projectCurrent">{{ projectCurrent.name }}</span>
|
||||
<span v-else class="label">No project</span>
|
||||
</div>
|
||||
<LoggerView
|
||||
v-if="showLogs"
|
||||
/>
|
||||
|
||||
<ApolloQuery
|
||||
:query="require('@/graphql/cwd.gql')"
|
||||
class="section current-path"
|
||||
v-tooltip="'Current Working Directory'"
|
||||
@click.native="$emit('cwd')"
|
||||
>
|
||||
<ApolloSubscribeToMore
|
||||
:document="require('@/graphql/cwdChanged.gql')"
|
||||
:update-query="(previousResult, { subscriptionData }) => ({
|
||||
cwd: subscriptionData.data.cwd
|
||||
})"
|
||||
/>
|
||||
<div class="content">
|
||||
<div
|
||||
class="section current-project"
|
||||
v-tooltip="'Current project'"
|
||||
@click="$emit('project')"
|
||||
>
|
||||
<VueIcon icon="work"/>
|
||||
<span v-if="projectCurrent">{{ projectCurrent.name }}</span>
|
||||
<span v-else class="label">No project</span>
|
||||
</div>
|
||||
|
||||
<template slot-scope="{ result: { data } }">
|
||||
<VueIcon icon="folder"/>
|
||||
<span v-if="data">{{ data.cwd }}</span>
|
||||
</template>
|
||||
</ApolloQuery>
|
||||
<ApolloQuery
|
||||
:query="require('@/graphql/cwd.gql')"
|
||||
class="section current-path"
|
||||
v-tooltip="'Current Working Directory'"
|
||||
@click.native="$emit('cwd')"
|
||||
>
|
||||
<ApolloSubscribeToMore
|
||||
:document="require('@/graphql/cwdChanged.gql')"
|
||||
:update-query="(previousResult, { subscriptionData }) => ({
|
||||
cwd: subscriptionData.data.cwd
|
||||
})"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="section console-log"
|
||||
v-tooltip="'Console'"
|
||||
@click="$emit('console')"
|
||||
>
|
||||
<VueIcon icon="subtitles"/>
|
||||
<span v-if="consoleLog">{{ consoleLog }}</span>
|
||||
<template slot-scope="{ result: { data } }">
|
||||
<VueIcon icon="folder"/>
|
||||
<span v-if="data">{{ data.cwd }}</span>
|
||||
</template>
|
||||
</ApolloQuery>
|
||||
|
||||
<div
|
||||
class="section console-log"
|
||||
v-tooltip="'Logs'"
|
||||
@click="onConsoleClick()"
|
||||
>
|
||||
<VueIcon icon="subtitles"/>
|
||||
<LoggerMessage class="last-message"
|
||||
v-if="consoleLogLast"
|
||||
:message="consoleLogLast"
|
||||
/>
|
||||
<!-- <TerminalView
|
||||
:cols="100"
|
||||
:rows="1"
|
||||
:content="consoleLogLast"
|
||||
auto-size
|
||||
:options="{
|
||||
scorllback: 0,
|
||||
disableStdin: true
|
||||
}"
|
||||
/> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoggerMessage from '../components/LoggerMessage'
|
||||
import LoggerView from '../components/LoggerView'
|
||||
|
||||
import PROJECT_CURRENT from '../graphql/projectCurrent.gql'
|
||||
import CONSOLE_LOG_LAST from '../graphql/consoleLogLast.gql'
|
||||
import CONSOLE_LOG_ADDED from '../graphql/consoleLogAdded.gql'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoggerMessage,
|
||||
LoggerView
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
consoleLog: '',
|
||||
consoleLogLast: null,
|
||||
showLogs: false,
|
||||
projectCurrent: null
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
projectCurrent: PROJECT_CURRENT
|
||||
projectCurrent: {
|
||||
query: PROJECT_CURRENT,
|
||||
fetchPolicy: 'cache-and-network'
|
||||
},
|
||||
|
||||
consoleLogLast: {
|
||||
query: CONSOLE_LOG_LAST,
|
||||
fetchPolicy: 'cache-and-network'
|
||||
},
|
||||
|
||||
$subscribe: {
|
||||
consoleLogAdded: {
|
||||
query: CONSOLE_LOG_ADDED,
|
||||
result ({ data }) {
|
||||
this.consoleLogLast = data.consoleLogAdded
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onConsoleClick () {
|
||||
this.$emit('console')
|
||||
this.showLogs = !this.showLogs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -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
|
||||
</style>
|
||||
|
||||
159
packages/@vue/cli-ui/src/components/TerminalView.vue
Normal file
159
packages/@vue/cli-ui/src/components/TerminalView.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<div class="terminal-view">
|
||||
<div ref="render" class="xterm-render"></div>
|
||||
|
||||
<resize-observer v-if="autoSize" @notify="fit"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Terminal } from 'xterm'
|
||||
import * as fit from 'xterm/dist/addons/fit/fit'
|
||||
import * as webLinks from 'xterm/dist/addons/webLinks/webLinks'
|
||||
|
||||
Terminal.applyAddon(fit)
|
||||
Terminal.applyAddon(webLinks)
|
||||
|
||||
const defaultTheme = {
|
||||
foreground: '#000',
|
||||
background: '#dbebec',
|
||||
cursor: 'rgba(0, 0, 0, .4)',
|
||||
selection: 'rgba(0, 0, 0, 0.3)',
|
||||
black: '#000000',
|
||||
red: '#e06c75',
|
||||
brightRed: '#e06c75',
|
||||
green: '#A4EFA1',
|
||||
brightGreen: '#A4EFA1',
|
||||
brightYellow: '#EDDC96',
|
||||
yellow: '#EDDC96',
|
||||
magenta: '#e39ef7',
|
||||
brightMagenta: '#e39ef7',
|
||||
cyan: '#5fcbd8',
|
||||
brightBlue: '#5fcbd8',
|
||||
brightCyan: '#5fcbd8',
|
||||
blue: '#5fcbd8',
|
||||
white: '#d0d0d0',
|
||||
brightBlack: '#808080',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
cols: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
|
||||
rows: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
|
||||
content: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
|
||||
autoSize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
cols (c) {
|
||||
this.$_terminal.resize(c, this.rows)
|
||||
},
|
||||
|
||||
rows (r) {
|
||||
this.$_terminal.resize(this.cols, r)
|
||||
},
|
||||
|
||||
content: 'setContent'
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.initTerminal()
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
this.$_terminal.destroy()
|
||||
},
|
||||
|
||||
methods: {
|
||||
initTerminal () {
|
||||
let term = this.$_terminal = new Terminal({
|
||||
cols: this.cols,
|
||||
rows: this.rows,
|
||||
theme: defaultTheme,
|
||||
...this.options
|
||||
})
|
||||
webLinks.webLinksInit(term, this.handleLink)
|
||||
term.open(this.$refs.render)
|
||||
|
||||
term.on('blur', () => this.$emit('blur'))
|
||||
term.on('focus', () => this.$emit('focus'))
|
||||
},
|
||||
|
||||
setContent (value) {
|
||||
if (typeof value === 'string') {
|
||||
this.$_terminal.write(value)
|
||||
} else {
|
||||
this.$_terminal.writeln('')
|
||||
}
|
||||
},
|
||||
|
||||
clear () {
|
||||
this.$_terminal.clear()
|
||||
},
|
||||
|
||||
handleLink (event, uri) {
|
||||
this.$emit('link', uri)
|
||||
},
|
||||
|
||||
async fit () {
|
||||
let parent = this.$el
|
||||
let el = this.$refs.render
|
||||
let term = this.$_terminal
|
||||
term.element.style.display = 'none'
|
||||
|
||||
await this.$nextTick()
|
||||
|
||||
term.fit()
|
||||
term.element.style.display = ''
|
||||
term.refresh(0, term.rows - 1)
|
||||
},
|
||||
|
||||
focus () {
|
||||
this.$_terminal.focus()
|
||||
},
|
||||
|
||||
blur () {
|
||||
this.$_terminal.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@import "~xterm/dist/xterm.css"
|
||||
</style>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "~@/style/imports"
|
||||
|
||||
.terminal-view
|
||||
position relative
|
||||
|
||||
.xterm-render
|
||||
width 100%
|
||||
height 100%
|
||||
>>> .xterm
|
||||
.xterm-cursor-layer
|
||||
display none
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
CWD_CHANGED: 'cwd_changed',
|
||||
CREATE_STATUS: 'create_status'
|
||||
PROGRESS_CHANGED: 'progress_changed',
|
||||
CONSOLE_LOG_ADDED: 'console_log_added'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
53
packages/@vue/cli-ui/src/graphql-api/connectors/logs.js
Normal file
53
packages/@vue/cli-ui/src/graphql-api/connectors/logs.js
Normal file
@@ -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
|
||||
}
|
||||
59
packages/@vue/cli-ui/src/graphql-api/connectors/progress.js
Normal file
59
packages/@vue/cli-ui/src/graphql-api/connectors/progress.js
Normal file
@@ -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
|
||||
}
|
||||
@@ -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.addListener('creation', onCreationEvent)
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
return progress.wrap(PROGRESS_ID, context, async setProgress => {
|
||||
setProgress({
|
||||
status: 'creating'
|
||||
})
|
||||
|
||||
const inCurrent = input.folder === '.'
|
||||
const name = inCurrent ? path.relative('../', process.cwd()) : input.folder
|
||||
creator.name = name
|
||||
const targetDir = path.join(cwd.get(), input.folder)
|
||||
creator.context = targetDir
|
||||
|
||||
if (fs.existsSync(targetDir)) {
|
||||
if (input.force) {
|
||||
rimraf.sync(targetDir)
|
||||
} else {
|
||||
throw new Error(`Folder ${targetDir} already exists`)
|
||||
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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!
|
||||
}
|
||||
`
|
||||
|
||||
7
packages/@vue/cli-ui/src/graphql/consoleLogAdded.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/consoleLogAdded.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./consoleLogFragment.gql"
|
||||
|
||||
subscription consoleLogAdded {
|
||||
consoleLogAdded {
|
||||
...consoleLog
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/consoleLogFragment.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/consoleLogFragment.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
fragment consoleLog on ConsoleLog {
|
||||
id
|
||||
type
|
||||
message
|
||||
tag
|
||||
date
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/consoleLogLast.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/consoleLogLast.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./consoleLogFragment.gql"
|
||||
|
||||
query consoleLogLast {
|
||||
consoleLogLast {
|
||||
...consoleLog
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/consoleLogs.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/consoleLogs.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./consoleLogFragment.gql"
|
||||
|
||||
query consoleLogs {
|
||||
consoleLogs {
|
||||
...consoleLog
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/consoleLogsClear.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/consoleLogsClear.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./consoleLogFragment.gql"
|
||||
|
||||
mutation consoleLogsClear {
|
||||
consoleLogsClear {
|
||||
...consoleLog
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
subscription createStatus {
|
||||
createStatus
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/progress.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/progress.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./progressFragment.gql"
|
||||
|
||||
query progress ($id: ID!) {
|
||||
progress (id: $id) {
|
||||
...progress
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/progressChanged.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/progressChanged.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
#import "./progressFragment.gql"
|
||||
|
||||
subscription progressChanged ($id: ID!) {
|
||||
progressChanged (id: $id) {
|
||||
...progress
|
||||
}
|
||||
}
|
||||
7
packages/@vue/cli-ui/src/graphql/progressFragment.gql
Normal file
7
packages/@vue/cli-ui/src/graphql/progressFragment.gql
Normal file
@@ -0,0 +1,7 @@
|
||||
fragment progress on Progress {
|
||||
id
|
||||
status
|
||||
info
|
||||
error
|
||||
progress
|
||||
}
|
||||
68
packages/@vue/cli-ui/src/mixins/Progress.js
Normal file
68
packages/@vue/cli-ui/src/mixins/Progress.js
Normal file
@@ -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 || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
$color-light-background = lighten($vue-color-light-neutral, 80%)
|
||||
$color-text-light = lighten($vue-color-dark, 40%)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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%)
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
@close="showCancel = false"
|
||||
>
|
||||
<div class="default-body">
|
||||
Are you sure you want to cancel the project creation?
|
||||
Are you sure you want to reset the project creation?
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="actions space-between">
|
||||
@@ -312,7 +312,7 @@
|
||||
label="Clear project"
|
||||
icon-left="delete_forever"
|
||||
class="danger"
|
||||
@click="cancel()"
|
||||
@click="reset()"
|
||||
/>
|
||||
</div>
|
||||
</VueModal>
|
||||
@@ -358,36 +358,14 @@
|
||||
</div>
|
||||
</VueModal>
|
||||
|
||||
<transition name="vue-fade">
|
||||
<LoadingScreen
|
||||
v-if="showLoading"
|
||||
:loading="loading"
|
||||
>
|
||||
<div v-if="error" class="error">
|
||||
<VueIcon
|
||||
icon="error"
|
||||
class="huge"
|
||||
/>
|
||||
<div>{{ error }}</div>
|
||||
<div class="actions">
|
||||
<VueButton
|
||||
icon-left="close"
|
||||
label="close"
|
||||
@click="showLoading = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="status">{{ statusMessage }}</div>
|
||||
</template>
|
||||
</LoadingScreen>
|
||||
</transition>
|
||||
<ProgressScreen
|
||||
progress-id="project-create"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingScreen from '../components/LoadingScreen'
|
||||
import ProgressScreen from '../components/ProgressScreen'
|
||||
import ProjectFeatureItem from '../components/ProjectFeatureItem'
|
||||
import ProjectPresetItem from '../components/ProjectPresetItem'
|
||||
import PrompsList from '../components/PromptsList'
|
||||
@@ -399,12 +377,11 @@ import FEATURE_SET_ENABLED from '../graphql/featureSetEnabled.gql'
|
||||
import PRESET_APPLY from '../graphql/presetApply.gql'
|
||||
import PROMPT_ANSWER from '../graphql/promptAnswer.gql'
|
||||
import PROJECT_CREATE from '../graphql/projectCreate.gql'
|
||||
import CREATE_STATUS from '../graphql/createStatus.gql'
|
||||
|
||||
function formDataFactory () {
|
||||
return {
|
||||
folder: '',
|
||||
force: false,
|
||||
folder: 'test-app',
|
||||
force: true,
|
||||
packageManager: undefined,
|
||||
selectedPreset: null,
|
||||
remotePreset: {
|
||||
@@ -419,7 +396,7 @@ let formData = formDataFactory()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LoadingScreen,
|
||||
ProgressScreen,
|
||||
ProjectFeatureItem,
|
||||
ProjectPresetItem,
|
||||
PrompsList,
|
||||
@@ -434,10 +411,6 @@ export default {
|
||||
showCancel: false,
|
||||
showRemotePreset: false,
|
||||
showSavePreset: false,
|
||||
showLoading: false,
|
||||
loading: false,
|
||||
createStatus: '',
|
||||
error: null
|
||||
}
|
||||
},
|
||||
|
||||
@@ -450,15 +423,6 @@ export default {
|
||||
projectCreation: {
|
||||
query: PROJECT_CREATION,
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
|
||||
$subscribe: {
|
||||
createStatus: {
|
||||
query: CREATE_STATUS,
|
||||
result ({ data }) {
|
||||
this.createStatus = data.createStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -495,29 +459,11 @@ export default {
|
||||
name: 'Remote preset',
|
||||
description: 'Fetch a preset from a git repository'
|
||||
}
|
||||
},
|
||||
|
||||
statusMessage () {
|
||||
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 ${this.formData.remotePreset.url}...`,
|
||||
'done': 'Successfully created project'
|
||||
}
|
||||
const message = messages[this.createStatus]
|
||||
if (!message) {
|
||||
console.warn(`Message not found for '${this.createStatus}'`)
|
||||
}
|
||||
return message || ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
cancel () {
|
||||
reset () {
|
||||
formData = formDataFactory()
|
||||
},
|
||||
|
||||
@@ -576,10 +522,6 @@ export default {
|
||||
|
||||
async createProject () {
|
||||
this.showSavePreset = false
|
||||
this.error = null
|
||||
this.createStatus = 'creating'
|
||||
this.showLoading = true
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
@@ -597,15 +539,10 @@ export default {
|
||||
}
|
||||
})
|
||||
this.$router.push({ name: 'home' })
|
||||
this.reset()
|
||||
} catch(e) {
|
||||
if (e.graphQLErrors && e.graphQLErrors.length) {
|
||||
this.error = e.graphQLErrors[0].message
|
||||
} else {
|
||||
this.error = e.message
|
||||
}
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ const cloneDeep = require('lodash.clonedeep')
|
||||
const sortObject = require('./util/sortObject')
|
||||
const getVersions = require('./util/getVersions')
|
||||
const { installDeps } = require('./util/installDeps')
|
||||
const clearConsole = require('./util/clearConsole')
|
||||
const { clearConsole } = require('./util/clearConsole')
|
||||
const PromptModuleAPI = require('./PromptModuleAPI')
|
||||
const writeFileTree = require('./util/writeFileTree')
|
||||
const { formatFeatures } = require('./util/features')
|
||||
|
||||
@@ -4,7 +4,7 @@ const chalk = require('chalk')
|
||||
const rimraf = require('rimraf')
|
||||
const inquirer = require('inquirer')
|
||||
const Creator = require('./Creator')
|
||||
const clearConsole = require('./util/clearConsole')
|
||||
const { clearConsole } = require('./util/clearConsole')
|
||||
const { getPromptModules } = require('./util/createTools')
|
||||
const { error, stopSpinner } = require('@vue/cli-shared-utils')
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const semver = require('semver')
|
||||
const getVersions = require('./getVersions')
|
||||
const { clearConsole } = require('@vue/cli-shared-utils')
|
||||
|
||||
module.exports = async function clearConsoleWithTitle (checkUpdate) {
|
||||
exports.generateTitle = async function (checkUpdate) {
|
||||
const { current, latest } = await getVersions()
|
||||
|
||||
let title = chalk.bold.blue(`Vue CLI v${current}`)
|
||||
@@ -21,5 +21,10 @@ module.exports = async function clearConsoleWithTitle (checkUpdate) {
|
||||
└─────────────────────────${`─`.repeat(latest.length)}─┘`)
|
||||
}
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
exports.clearConsole = async function clearConsoleWithTitle (checkUpdate) {
|
||||
const title = await exports.generateTitle(checkUpdate)
|
||||
clearConsole(title)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const EventEmitter = require('events')
|
||||
const request = require('./request')
|
||||
const chalk = require('chalk')
|
||||
const execa = require('execa')
|
||||
@@ -15,6 +16,37 @@ const registries = {
|
||||
}
|
||||
const taobaoDistURL = 'https://npm.taobao.org/dist'
|
||||
|
||||
class InstallProgress extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this._progress = -1
|
||||
}
|
||||
|
||||
get progress () {
|
||||
return this._progress
|
||||
}
|
||||
|
||||
set progress (value) {
|
||||
this._progress = value
|
||||
this.emit('progress', value)
|
||||
}
|
||||
|
||||
get enabled () {
|
||||
return this._progress !== -1
|
||||
}
|
||||
|
||||
set enabled (value) {
|
||||
this.progress = value ? 0 : -1
|
||||
}
|
||||
|
||||
log (value) {
|
||||
this.emit('log', value)
|
||||
}
|
||||
}
|
||||
|
||||
const progress = exports.progress = new InstallProgress()
|
||||
|
||||
async function ping (registry) {
|
||||
await request.get(`${registry}/vue-cli-version-marker/latest`)
|
||||
return registry
|
||||
@@ -119,28 +151,86 @@ async function addRegistryToArgs (command, args, cliRegistry) {
|
||||
|
||||
function executeCommand (command, args, targetDir) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const apiMode = process.env.VUE_CLI_API_MODE
|
||||
|
||||
progress.enabled = false
|
||||
|
||||
// const lines = []
|
||||
// let count = 0
|
||||
// const unhook = intercept(buffer => {
|
||||
// count++
|
||||
// lines.push(buffer)
|
||||
// const str = buffer === 'string' ? buffer : buffer.toString()
|
||||
// // Steps
|
||||
// const stepMatch = str.match(/\[\d+\/\d+]\s+(.*)/)
|
||||
// if (stepMatch) {
|
||||
// progress.log(stepMatch[1])
|
||||
// }
|
||||
// })
|
||||
|
||||
if (apiMode) {
|
||||
if (command === 'npm') {
|
||||
// TODO when this is supported
|
||||
} else if (command === 'yarn') {
|
||||
args.push('--json')
|
||||
}
|
||||
}
|
||||
|
||||
const child = execa(command, args, {
|
||||
cwd: targetDir,
|
||||
stdio: ['inherit', 'inherit', command === 'yarn' ? 'pipe' : 'inherit']
|
||||
stdio: ['inherit', apiMode ? 'pipe' : 'inherit', !apiMode && command === 'yarn' ? 'pipe' : 'inherit']
|
||||
})
|
||||
|
||||
// filter out unwanted yarn output
|
||||
if (command === 'yarn') {
|
||||
child.stderr.on('data', buf => {
|
||||
const str = buf.toString()
|
||||
if (/warning/.test(str)) {
|
||||
return
|
||||
if (apiMode) {
|
||||
let progressTotal = 0
|
||||
let progressTime = Date.now()
|
||||
child.stdout.on('data', buffer => {
|
||||
let str = buffer.toString().trim()
|
||||
if (str && command === 'yarn' && str.indexOf('"type":') !== -1) {
|
||||
const newLineIndex = str.lastIndexOf('\n')
|
||||
if (newLineIndex !== -1) {
|
||||
str = str.substr(newLineIndex)
|
||||
}
|
||||
const data = JSON.parse(str)
|
||||
if (data.type === 'step') {
|
||||
progress.enabled = false
|
||||
progress.log(data.data.message)
|
||||
} else if (data.type === 'progressStart') {
|
||||
progressTotal = data.data.total
|
||||
} else if (data.type === 'progressTick') {
|
||||
const time = Date.now()
|
||||
if (time - progressTime > 20) {
|
||||
progressTime = time
|
||||
progress.progress = data.data.current / progressTotal
|
||||
}
|
||||
} else {
|
||||
progress.enabled = false
|
||||
}
|
||||
} else {
|
||||
process.stdout.write(buffer)
|
||||
}
|
||||
// progress bar
|
||||
const progressBarMatch = str.match(/\[.*\] (\d+)\/(\d+)/)
|
||||
if (progressBarMatch) {
|
||||
// since yarn is in a child process, it's unable to get the width of
|
||||
// the terminal. reimplement the progress bar ourselves!
|
||||
renderProgressBar(progressBarMatch[1], progressBarMatch[2])
|
||||
return
|
||||
}
|
||||
process.stderr.write(buf)
|
||||
})
|
||||
} else {
|
||||
// filter out unwanted yarn output
|
||||
if (command === 'yarn') {
|
||||
child.stderr.on('data', buf => {
|
||||
const str = buf.toString()
|
||||
if (/warning/.test(str)) {
|
||||
return
|
||||
}
|
||||
|
||||
// progress bar
|
||||
const progressBarMatch = str.match(/\[.*\] (\d+)\/(\d+)/)
|
||||
if (progressBarMatch) {
|
||||
// since yarn is in a child process, it's unable to get the width of
|
||||
// the terminal. reimplement the progress bar ourselves!
|
||||
renderProgressBar(progressBarMatch[1], progressBarMatch[2])
|
||||
return
|
||||
}
|
||||
|
||||
process.stderr.write(buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
child.on('close', code => {
|
||||
|
||||
@@ -905,6 +905,10 @@ ansi-wrap@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
|
||||
|
||||
ansi_up@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ansi_up/-/ansi_up-2.0.2.tgz#9b54de508c5c579f5b6968e65c1b863e0680ab92"
|
||||
|
||||
any-observable@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.2.0.tgz#c67870058003579009083f54ac0abafb5c33d242"
|
||||
@@ -11167,6 +11171,10 @@ xtend@^4.0.0, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
xterm@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.2.0.tgz#da50f54a83d81463c0a2c9f2e0a5d14d3867df02"
|
||||
|
||||
y18n@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
|
||||
|
||||
Reference in New Issue
Block a user