diff --git a/packages/@vue/cli-ui/package.json b/packages/@vue/cli-ui/package.json index 8c1cbe2a79..039340f98e 100644 --- a/packages/@vue/cli-ui/package.json +++ b/packages/@vue/cli-ui/package.json @@ -28,7 +28,6 @@ "lowdb": "^1.0.0", "lru-cache": "^4.1.2", "mkdirp": "^0.5.1", - "parse-diff": "^0.4.2", "rimraf": "^2.6.2", "semver": "^5.5.0", "shortid": "^2.2.8", @@ -51,7 +50,7 @@ "lint-staged": "^6.0.0", "stylus": "^0.54.5", "stylus-loader": "^3.0.1", - "vue-cli-plugin-apollo": "^0.6.1", + "vue-cli-plugin-apollo": "^0.7.1", "vue-template-compiler": "^2.5.13" }, "browserslist": [ diff --git a/packages/@vue/cli-ui/src/components/FileDiff.vue b/packages/@vue/cli-ui/src/components/FileDiff.vue index d9450acca0..7c6a4bc73d 100644 --- a/packages/@vue/cli-ui/src/components/FileDiff.vue +++ b/packages/@vue/cli-ui/src/components/FileDiff.vue @@ -27,11 +27,17 @@
- +
+ + {{ $t('components.file-diff.binary') }} +
+
@@ -102,8 +108,14 @@ status-color($color) flex 100% 1 1 width 0 - .content - overflow-x auto + .is-binary + h-box() + box-center() + padding $padding-item + opacity .5 + + .icon + margin-right 4px &.new status-color($vue-ui-color-success) diff --git a/packages/@vue/cli-ui/src/components/FileDiffChunk.vue b/packages/@vue/cli-ui/src/components/FileDiffChunk.vue index 619048be0f..6fb5ca2363 100644 --- a/packages/@vue/cli-ui/src/components/FileDiffChunk.vue +++ b/packages/@vue/cli-ui/src/components/FileDiffChunk.vue @@ -25,6 +25,11 @@ export default { @import "~@/style/imports" .file-diff-chunk + .changes + overflow-x auto + display grid + grid-template-column 1fr + &:not(:last-child) &::after content '•••' diff --git a/packages/@vue/cli-ui/src/components/FileDiffView.vue b/packages/@vue/cli-ui/src/components/FileDiffView.vue index 21cfc1f469..4dfe319184 100644 --- a/packages/@vue/cli-ui/src/components/FileDiffView.vue +++ b/packages/@vue/cli-ui/src/components/FileDiffView.vue @@ -115,6 +115,11 @@ import PageVisibility from '../mixins/PageVisibility' import FILE_DIFFS from '../graphql/fileDiffs.gql' import GIT_COMMIT from '../graphql/gitCommit.gql' +const defaultCollapsed = [ + 'yarn.lock', + 'package-lock.json' +] + export default { mixins: [ PageVisibility @@ -135,7 +140,18 @@ export default { fileDiffs: { query: FILE_DIFFS, loadingKey: 'loading', - fetchPolicy: 'cahe-and-network' + fetchPolicy: 'cahe-and-network', + result () { + this.fileDiffs.forEach(fileDiff => { + if (typeof this.collapsed[fileDiff.id] === 'undefined' && ( + fileDiff.binary || + defaultCollapsed.includes(fileDiff.from) || + defaultCollapsed.includes(fileDiff.to) + )) { + this.$set(this.collapsed, fileDiff.id, true) + } + }) + } } }, diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/git.js b/packages/@vue/cli-ui/src/graphql-api/connectors/git.js index fca5bd130d..6a707470c3 100644 --- a/packages/@vue/cli-ui/src/graphql-api/connectors/git.js +++ b/packages/@vue/cli-ui/src/graphql-api/connectors/git.js @@ -1,18 +1,41 @@ const execa = require('execa') -const parseDiff = require('parse-diff') +const parseDiff = require('../utils/parse-diff') // Connectors const cwd = require('./cwd') +async function getNewFiles (context) { + const { stdout } = await execa('git', [ + 'ls-files', + '-o', + '--exclude-standard', + '--full-name' + ], { + cwd: cwd.get() + }) + if (stdout.trim()) { + return stdout.split(/\r?\n/g) + } + return [] +} + async function getDiffs (context) { - const { stdout } = await execa('git', ['diff', 'HEAD'], { + const newFiles = await getNewFiles(context) + await execa('git', ['add', '-N', '*'], { + cwd: cwd.get() + }) + const { stdout } = await execa('git', ['diff'], { cwd: cwd.get() }) - return parseDiff(stdout).map( + await reset(context) + const list = parseDiff(stdout).map( fileDiff => ({ id: fileDiff.index.join(' '), - ...fileDiff + ...fileDiff, + new: newFiles.includes(fileDiff.to) }) ) + + return list } async function commit (message, context) { @@ -25,7 +48,15 @@ async function commit (message, context) { return true } +async function reset (context) { + await execa('git', ['reset'], { + cwd: cwd.get() + }) + return true +} + module.exports = { getDiffs, - commit + commit, + reset } diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js b/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js index bc3644ab0e..2405ced7cd 100644 --- a/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js +++ b/packages/@vue/cli-ui/src/graphql-api/connectors/plugins.js @@ -43,6 +43,7 @@ let currentPluginId let eventsInstalled = false let plugins = [] let pluginApi +let installationStep function getPath (id) { return path.dirname(resolveModule(id, cwd.get())) @@ -181,6 +182,7 @@ function getInstallation (context) { return { id: 'plugin-install', pluginId: currentPluginId, + step: installationStep, prompts: prompts.list() } } @@ -192,8 +194,10 @@ function install (id, context) { args: [id] }) currentPluginId = id + installationStep = 'install' await installPackage(cwd.get(), getCommand(), null, id) await initPrompts(id, context) + installationStep = 'config' return getInstallation(context) }) } @@ -204,9 +208,11 @@ function uninstall (id, context) { status: 'plugin-uninstall', args: [id] }) + installationStep = 'uninstall' currentPluginId = id await uninstallPackage(cwd.get(), getCommand(), null, id) currentPluginId = null + installationStep = null return getInstallation(context) }) } @@ -224,11 +230,17 @@ function runInvoke (id, context) { } // Run plugin api runPluginApi(id, context) - currentPluginId = null + installationStep = 'diff' return getInstallation(context) }) } +function finishInstall (context) { + installationStep = null + currentPluginId = null + return getInstallation(context) +} + async function initPrompts (id, context) { prompts.reset() try { @@ -278,5 +290,6 @@ module.exports = { update, runInvoke, resetPluginApi, - getApi + getApi, + finishInstall } diff --git a/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js b/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js index 84c05bbd8c..1eacc90723 100644 --- a/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js +++ b/packages/@vue/cli-ui/src/graphql-api/connectors/prompts.js @@ -72,7 +72,9 @@ function getChoices (prompt) { } else { result = data } - const defaultValue = getDefaultValue(prompt) + const defaultValue = prompt.type === 'list' || prompt.type === 'rawlist' + ? getDefaultValue(prompt) + : undefined return result.map( item => generatePromptChoice(prompt, item, defaultValue) ) diff --git a/packages/@vue/cli-ui/src/graphql-api/resolvers.js b/packages/@vue/cli-ui/src/graphql-api/resolvers.js index 88033786bd..bb67d43ea8 100644 --- a/packages/@vue/cli-ui/src/graphql-api/resolvers.js +++ b/packages/@vue/cli-ui/src/graphql-api/resolvers.js @@ -83,6 +83,7 @@ module.exports = { pluginInstall: (root, { id }, context) => plugins.install(id, context), pluginUninstall: (root, { id }, context) => plugins.uninstall(id, context), pluginInvoke: (root, { id }, context) => plugins.runInvoke(id, context), + pluginFinishInstall: (root, args, context) => plugins.finishInstall(context), pluginUpdate: (root, { id }, context) => plugins.update(id, context), taskRun: (root, { id }, context) => tasks.run(id, context), taskStop: (root, { id }, context) => tasks.stop(id, context), 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 9c3a8cffce..abccad3449 100644 --- a/packages/@vue/cli-ui/src/graphql-api/type-defs.js +++ b/packages/@vue/cli-ui/src/graphql-api/type-defs.js @@ -97,9 +97,17 @@ type Plugin { type PluginInstallation { id: ID! pluginId: ID + step: PluginInstallationStep prompts: [Prompt] } +enum PluginInstallationStep { + install + uninstall + config + diff +} + type Feature implements DescribedEntity { id: ID! name: String @@ -207,6 +215,7 @@ type FileDiff { to: String new: Boolean deleted: Boolean + binary: Boolean chunks: [FileDiffChunk] } @@ -269,6 +278,7 @@ type Mutation { pluginInstall (id: ID!): PluginInstallation pluginUninstall (id: ID!): PluginInstallation pluginInvoke (id: ID!): PluginInstallation + pluginFinishInstall: PluginInstallation pluginUpdate (id: ID!): Plugin taskRun (id: ID!): Task taskStop (id: ID!): Task diff --git a/packages/@vue/cli-ui/src/graphql-api/utils/parse-diff.js b/packages/@vue/cli-ui/src/graphql-api/utils/parse-diff.js new file mode 100644 index 0000000000..49b032928e --- /dev/null +++ b/packages/@vue/cli-ui/src/graphql-api/utils/parse-diff.js @@ -0,0 +1,192 @@ +// From https://github.com/sergeyt/parse-diff +module.exports = function (input) { + if (!input) { return [] } + if (input.match(/^\s+$/)) { return [] } + + const lines = input.split('\n') + if (lines.length === 0) { return [] } + + const files = [] + let file = null + let lnDel = 0 + let lnAdd = 0 + let current = null + + const start = function (line) { + file = { + chunks: [], + deletions: 0, + additions: 0 + } + files.push(file) + + if (!file.to && !file.from) { + const fileNames = parseFile(line) + + if (fileNames) { + file.from = fileNames[0] + file.to = fileNames[1] + } + } + } + + const restart = function () { + if (!file || file.chunks.length) { return start() } + } + + const newFile = function () { + restart() + file.new = true + file.from = '/dev/null' + } + + const deletedFile = function () { + restart() + file.deleted = true + file.to = '/dev/null' + } + + const index = function (line) { + restart() + file.index = line.split(' ').slice(1) + } + + const fromFile = function (line) { + restart() + file.from = parseFileFallback(line) + } + + const toFile = function (line) { + restart() + file.to = parseFileFallback(line) + } + + const binary = function (line) { + file.binary = true + } + + const chunk = function (line, match) { + let newStart, oldStart + lnDel = (oldStart = +match[1]) + const oldLines = +(match[2] || 0) + lnAdd = (newStart = +match[3]) + const newLines = +(match[4] || 0) + current = { + content: line, + changes: [], + oldStart, + oldLines, + newStart, + newLines + } + file.chunks.push(current) + } + + const del = function (line) { + if (!current) return + current.changes.push({type: 'del', del: true, ln: lnDel++, content: line}) + file.deletions++ + } + + const add = function (line) { + if (!current) return + current.changes.push({type: 'add', add: true, ln: lnAdd++, content: line}) + file.additions++ + } + + const normal = function (line) { + if (!current) return + current.changes.push({ + type: 'normal', + normal: true, + ln1: lnDel++, + ln2: lnAdd++, + content: line + }) + } + + const eof = function (line) { + const recentChange = current.changes[current.changes.length - 1] + + return current.changes.push({ + type: recentChange.type, + [recentChange.type]: true, + ln1: recentChange.ln1, + ln2: recentChange.ln2, + ln: recentChange.ln, + content: line + }) + } + + const schema = [ + // todo beter regexp to avoid detect normal line starting with diff + [/^\s+/, normal], + [/^diff\s/, start], + [/^new file mode \d+$/, newFile], + [/^deleted file mode \d+$/, deletedFile], + [/^Binary files/, binary], + [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index], + [/^---\s/, fromFile], + [/^\+\+\+\s/, toFile], + [/^@@\s+-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], + [/^-/, del], + [/^\+/, add], + [/^\\ No newline at end of file$/, eof] + ] + + const parse = function (line) { + for (let p of schema) { + const m = line.match(p[0]) + if (m) { + p[1](line, m) + return true + } + } + return false + } + + for (let line of lines) { + parse(line) + } + + return files +} + +function parseFile (s) { + if (!s) return + + const result = /\sa\/(.*)\sb\/(.*)/.exec(s) + + return [result[1], result[2]] +} + +// fallback function to overwrite file.from and file.to if executed +function parseFileFallback (s) { + s = ltrim(s, '-') + s = ltrim(s, '+') + s = s.trim() + // ignore possible time stamp + const t = (/\t.*|\d{4}-\d\d-\d\d\s\d\d:\d\d:\d\d(.\d+)?\s(\+|-)\d\d\d\d/).exec(s) + if (t) { s = s.substring(0, t.index).trim() } + // ignore git prefixes a/ or b/ + if (s.match((/^(a|b)\//))) { return s.substr(2) } else { return s } +} + +function ltrim (s, chars) { + s = makeString(s) + if (!chars && trimLeft) { return trimLeft.call(s) } + chars = defaultToWhiteSpace(chars) + return s.replace(new RegExp(`^${chars}+`), '') +} + +const makeString = s => s === null ? '' : s + '' + +const { trimLeft } = String.prototype + +function defaultToWhiteSpace (chars) { + if (chars === null) { return '\\s' } + if (chars.source) { return chars.source } + return `[${escapeRegExp(chars)}]` +} + +const escapeRegExp = s => makeString(s).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1') diff --git a/packages/@vue/cli-ui/src/graphql/fileDiffs.gql b/packages/@vue/cli-ui/src/graphql/fileDiffs.gql index 08517a056f..544925b859 100644 --- a/packages/@vue/cli-ui/src/graphql/fileDiffs.gql +++ b/packages/@vue/cli-ui/src/graphql/fileDiffs.gql @@ -5,6 +5,7 @@ query fileDiffs { to new deleted + binary chunks { oldStart oldLines diff --git a/packages/@vue/cli-ui/src/graphql/pluginFinishInstall.gql b/packages/@vue/cli-ui/src/graphql/pluginFinishInstall.gql new file mode 100644 index 0000000000..eb067ff9c6 --- /dev/null +++ b/packages/@vue/cli-ui/src/graphql/pluginFinishInstall.gql @@ -0,0 +1,7 @@ +#import "./pluginInstallationFragment.gql" + +mutation pluginFinishInstall { + pluginFinishInstall { + ...pluginInstallation + } +} diff --git a/packages/@vue/cli-ui/src/graphql/pluginInstallationFragment.gql b/packages/@vue/cli-ui/src/graphql/pluginInstallationFragment.gql index cd867ee574..a757832c5d 100644 --- a/packages/@vue/cli-ui/src/graphql/pluginInstallationFragment.gql +++ b/packages/@vue/cli-ui/src/graphql/pluginInstallationFragment.gql @@ -3,6 +3,7 @@ fragment pluginInstallation on PluginInstallation { id pluginId + step prompts { ...prompt } diff --git a/packages/@vue/cli-ui/src/locales/en.json b/packages/@vue/cli-ui/src/locales/en.json index 2f4e7fc98d..30ff8af188 100644 --- a/packages/@vue/cli-ui/src/locales/en.json +++ b/packages/@vue/cli-ui/src/locales/en.json @@ -1,5 +1,8 @@ { "components": { + "file-diff": { + "binary": "Binary file not shown" + }, "file-diff-view": { "files-changed": "Files changed", "search-file": "Search file", @@ -251,6 +254,9 @@ "cancel": "Cancel", "finish": "Finish installation" } + }, + "diff": { + "label": "Files changed" } }, "modal": { diff --git a/packages/@vue/cli-ui/src/locales/fr.json b/packages/@vue/cli-ui/src/locales/fr.json index 38b0ff2ca8..77c1cc6efa 100644 --- a/packages/@vue/cli-ui/src/locales/fr.json +++ b/packages/@vue/cli-ui/src/locales/fr.json @@ -1,5 +1,8 @@ { "components": { + "file-diff": { + "binary": "Fichier binaire non affiché" + }, "file-diff-view": { "files-changed": "Fichiers modifiés", "search-file": "Rechercher un fichier", @@ -251,6 +254,9 @@ "cancel": "Annuler", "finish": "Terminer l'installation" } + }, + "diff": { + "label": "Fichers modifiés" } }, "modal": { diff --git a/packages/@vue/cli-ui/src/views/ProjectPluginsAdd.vue b/packages/@vue/cli-ui/src/views/ProjectPluginsAdd.vue index 681049dd49..ccbe1ea004 100644 --- a/packages/@vue/cli-ui/src/views/ProjectPluginsAdd.vue +++ b/packages/@vue/cli-ui/src/views/ProjectPluginsAdd.vue @@ -112,6 +112,18 @@ /> + + + + @@ -163,6 +175,7 @@ import PLUGIN_INSTALLATION from '../graphql/pluginInstallation.gql' import PLUGIN_INSTALL from '../graphql/pluginInstall.gql' import PLUGIN_UNINSTALL from '../graphql/pluginUninstall.gql' import PLUGIN_INVOKE from '../graphql/pluginInvoke.gql' +import PLUGIN_FINISH_INSTALL from '../graphql/pluginFinishInstall.gql' export default { name: 'ProjectPluginsAdd', @@ -219,6 +232,17 @@ export default { } else { this.tabId = 'search' } + + switch (this.pluginInstallation.step) { + case 'config': + this.tabId = 'config' + break + case 'diff': + this.tabId = 'diff' + break + default: + this.tabId = 'search' + } }, async installPlugin () { @@ -264,6 +288,17 @@ export default { id: this.pluginId } }) + this.tabId = 'diff' + } catch (e) { + console.error(e) + } + }, + + async finishInstall () { + try { + await this.$apollo.mutate({ + mutation: PLUGIN_FINISH_INSTALL + }) this.close() } catch (e) { console.error(e) @@ -284,6 +319,7 @@ export default { .content grid-area content + overflow hidden .algolia position absolute diff --git a/packages/@vue/cli-ui/vue.config.js b/packages/@vue/cli-ui/vue.config.js index a11153a8f5..a2ee30dab6 100644 --- a/packages/@vue/cli-ui/vue.config.js +++ b/packages/@vue/cli-ui/vue.config.js @@ -3,6 +3,7 @@ module.exports = { pluginOptions: { graphqlMock: false, - apolloEngine: false + apolloEngine: false, + graphqlTimeout: 0 } } diff --git a/yarn.lock b/yarn.lock index b026d20f12..654a76801a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7846,10 +7846,6 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" -parse-diff@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/parse-diff/-/parse-diff-0.4.2.tgz#b173390e916564e8c70ccd37756047941e5b3ef2" - parse-github-repo-url@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" @@ -10580,9 +10576,9 @@ vue-class-component@^6.0.0: version "6.2.0" resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-6.2.0.tgz#7adb1daa9a868c75f30f97f33f4f1b94aee62089" -vue-cli-plugin-apollo@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.6.1.tgz#12e9124aaa362ab656bf83cc7daa582107d0a193" +vue-cli-plugin-apollo@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.7.1.tgz#c4d941fc4d12f245563b4d9ca51289b276ec5c4c" dependencies: apollo-engine "^1.0.1" apollo-server-express "^1.3.2"