diff --git a/.gitignore b/.gitignore index e31dfe570c..8065e608d4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules TODOs.md packages/@vuepress/shared-utils/lib/ types +.vscode \ No newline at end of file diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/README.md b/packages/@vuepress/vue-cli-plugin-vuepress/README.md new file mode 100644 index 0000000000..a5f4b6750d --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/README.md @@ -0,0 +1,53 @@ +# vue-cli-plugin-vuepress + +> The official vue-cli plugin for VuePress + +## Usage + +### With vue ui + +- Run `vue ui` in a terminal. +- Choose an existing Vue project or create a new one +- Go to the `Plugins` section +- Click the `Add plugin` button +- Search `@vuepress/vue-cli-plugin-vuepress` +- Install the plugin and answer questions + +If you go to the `Tasks` section, you can see that you now have access to two new VuePress commands: + +- docs:dev (Run your VuePress site in dev mode) +- docs:build (Build your VuePress site) + +Those commands are available in your `package.json` in the `scripts` section. + +To edit/bootstrap your VuePress configuration, go to the `Configuration` section and select VuePress. +Note that if you edit your VuePress config while it's running in dev mode, it will be live reloaded. + +**⚠️ Some configurations are not covered by this ui plugin. Refer to the [official VuePress documentaion](https://vuepress.vuejs.org/) for advanced config ⚠️** + +### With vue-cli + +- Run `vue add @vuepress/vue-cli-plugin-vuepress` in a terminal: +- Answer questions + +Run `yarn docs:dev` for dev mode or `yarn docs:build` to build your VuePress site. +See more details in the [official VuePress documentaion](https://vuepress.vuejs.org/). + +## Local Development + +### 1. Clone this repository + +``` +git clone https://github.com/vuepressjs/vue-cli-plugin-vuepress.git +cd vue-cli-plugin-vuepress +``` + +### 2. Install dependencies + +``` +yarn +``` + +### 3. Install plugin locally + +Please refer to the official [vue-cli documentation](https://cli.vuejs.org/dev-guide/plugin-dev.html#installing-plugin-locally) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/generator/index.js b/packages/@vuepress/vue-cli-plugin-vuepress/generator/index.js new file mode 100644 index 0000000000..116ecdc054 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/generator/index.js @@ -0,0 +1,13 @@ +module.exports = async (api, projectOptions) => { + api.extendPackage({ + scripts: { + 'docs:dev': 'vuepress dev docs', + 'docs:build': 'vuepress build docs' + }, + devDependencies: { + vuepress: '^1.1.0' + } + }) + + api.render('./template', projectOptions) +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/generator/template/docs/README.md b/packages/@vuepress/vue-cli-plugin-vuepress/generator/template/docs/README.md new file mode 100644 index 0000000000..bf357b1ef5 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/generator/template/docs/README.md @@ -0,0 +1 @@ +# <%= title %> \ No newline at end of file diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/generator/template/docs/_vuepress/config.js b/packages/@vuepress/vue-cli-plugin-vuepress/generator/template/docs/_vuepress/config.js new file mode 100644 index 0000000000..4191381e02 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/generator/template/docs/_vuepress/config.js @@ -0,0 +1,4 @@ +module.exports = { + title: '<%= title %>', + description: '<%= description %>' +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/index.js b/packages/@vuepress/vue-cli-plugin-vuepress/index.js new file mode 100644 index 0000000000..0c0c42d5b5 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/index.js @@ -0,0 +1 @@ +module.exports = () => {} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/logo.png b/packages/@vuepress/vue-cli-plugin-vuepress/logo.png new file mode 100644 index 0000000000..6363798a8f Binary files /dev/null and b/packages/@vuepress/vue-cli-plugin-vuepress/logo.png differ diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/package.json b/packages/@vuepress/vue-cli-plugin-vuepress/package.json new file mode 100644 index 0000000000..ef43a79171 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/package.json @@ -0,0 +1,29 @@ +{ + "name": "@vuepress/vue-cli-plugin-vuepress", + "version": "1.1.0", + "description": "vue-cli plugin for VuePress", + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/vuepress.git", + "directory": "packages/@vuepress/vue-cli-plugin-vuepress" + }, + "keywords": [ + "vuepress", + "vue", + "vue-cli", + "bootstrap" + ], + "author": "Franck Abgrall", + "license": "MIT", + "bugs": { + "url": "https://github.com/vuejs/vuepress/issues" + }, + "homepage": "https://github.com/vuejs/vuepress#readme", + "dependencies": { + "load-json-file": "^6.2.0" + } +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/prompts.js b/packages/@vuepress/vue-cli-plugin-vuepress/prompts.js new file mode 100644 index 0000000000..f26dd9473b --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/prompts.js @@ -0,0 +1,20 @@ +module.exports = [ + { + name: 'title', + type: 'input', + message: 'Title', + description: 'Title for the site.', + link: 'https://vuepress.vuejs.org/config/#title', + group: 'General settings', + validate: input => !!input + }, + { + name: 'description', + type: 'input', + message: 'Description', + description: 'Description for the site.', + link: 'https://vuepress.vuejs.org/config/#description', + group: 'General settings', + validate: input => !!input + } +] diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/index.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/index.js new file mode 100644 index 0000000000..85226cb595 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/index.js @@ -0,0 +1,31 @@ +const flattenDeep = require('lodash/flattenDeep') + +const navbarItems = require('./navbarItems') +const sidebar = require('./sidebar') +const navigation = require('./navigation') +const searchBox = require('./searchBox') +const pages = require('./pages') +const { isDefaultTheme } = require('../utils') + +module.exports = data => { + const promptItems = flattenDeep([ + navigation(data), + navbarItems(data), + sidebar(data), + searchBox(data), + pages(data) + ]) + + return promptItems.map(item => ({ + ...item, + when: answer => { + if (!isDefaultTheme(answer)) { + return false + } else if (item.when) { + return item.when(answer) + } + + return true + } + })) +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/navbarItems.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/navbarItems.js new file mode 100644 index 0000000000..642b6dfad6 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/navbarItems.js @@ -0,0 +1,36 @@ +const times = require('lodash/times') +const get = require('lodash/get') + +const isNavBarVisible = answer => answer.themeConfig.navbar === true +const isPreviousLinkDefined = (answer, previousLinkIndex) => previousLinkIndex === -1 +|| ( + get(answer, `themeConfig.nav[${previousLinkIndex}].text`) + && get(answer, `themeConfig.nav[${previousLinkIndex}].link`) +) + +const NAVLINKS_GROUP_NAME = 'Navbar items' +const NAVBAR_LINKS_COUNT = 10 + +module.exports = data => times(NAVBAR_LINKS_COUNT, count => { + const shouldDisplayItem = answer => isNavBarVisible(answer) + && isPreviousLinkDefined(answer, count - 1) + + return [{ + when: shouldDisplayItem, + name: `themeConfig.nav.${count}.text`, + type: 'input', + message: `Item ${count + 1}: text`, + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#navbar', + group: NAVLINKS_GROUP_NAME, + value: get(data, `config.themeConfig.nav[${count}].text`) + }, { + when: shouldDisplayItem, + name: `themeConfig.nav.${count}.link`, + type: 'input', + message: `Item ${count + 1}: link`, + description: 'This is the targeted page. Can be both external (example: https://vuepress.vuejs.org) or internal (example: /my/page/path) URL.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#navbar', + group: NAVLINKS_GROUP_NAME, + value: get(data, `config.themeConfig.nav[${count}].link`) + }] +}) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/navigation.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/navigation.js new file mode 100644 index 0000000000..1795716355 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/navigation.js @@ -0,0 +1,25 @@ +const get = require('lodash/get') + +const GROUP_NAME = 'Navbar settings' + +module.exports = data => ([ + { + name: 'themeConfig.navbar', + type: 'confirm', + message: 'Show navigation bar', + description: 'Disable the navbar globally.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#disable-the-navbar', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.navbar'), + default: true + }, + { + name: 'themeConfig.logo', + type: 'input', + message: 'Logo path', + description: 'You can add a logo to the navbar. Logo can be placed in public folder.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#navbar-logo', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.logo') + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/pages.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/pages.js new file mode 100644 index 0000000000..a1a0b653b5 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/pages.js @@ -0,0 +1,108 @@ +const get = require('lodash/get') + +const { emptyStringToUndefined } = require('../utils') + +const GROUP_NAME = 'Pages settings' + +module.exports = data => ([ + { + name: 'themeConfig.lastUpdated', + type: 'input', + message: 'Last updated label', + description: 'This option allows you to get the UNIX timestamp(ms) of each file’s last git commit, and it will also be displayed at the bottom of each page in an appropriate format. Enter the string that will be displayed as a prefix (example: \'Last updated\')', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#last-updated', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.lastUpdated') + }, + { + name: 'themeConfig.repo', + type: 'input', + message: 'Github/Gitlab/Bitbucket repository url', + description: 'Auto generates a Github/Gitlab/Bitbucket link in the navbar and "Edit this page" links at the bottom of each page. (value example: "https://github.com/vuejs/vuepress")', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.repo') + }, + { + when: answer => get(answer, 'themeConfig.repo'), + name: 'themeConfig.repoLabel', + type: 'input', + message: 'Repository label in navbar', + description: 'Defaults to "GitHub"/"GitLab"/"Bitbucket" depending on platform you\'re using', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.repoLabel') + }, + { + name: 'themeConfig.docsRepo', + type: 'input', + message: 'Github/Gitlab/Bitbucket documentation repository url', + description: 'Use this field if your doc is in a different repo from your main project.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.docsRepo'), + transform: emptyStringToUndefined + }, + { + when: answer => get(answer, 'themeConfig.docsRepo'), + name: 'themeConfig.docsDir', + type: 'input', + message: 'Documentation directory', + description: 'Use this if your documentation is not at the root of the repo', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.docsDir') + }, + { + when: answer => get(answer, 'themeConfig.docsRepo'), + name: 'themeConfig.docsBranch', + type: 'input', + message: 'Documentation branch', + description: "Use this field if your docs are in a specific branch (defaults to 'master')", + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.docsBranch'), + default: 'master' + }, + { + name: 'themeConfig.editLinks', + type: 'confirm', + message: 'Edit links', + description: 'Allows to display edit link on all pages', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.editLinks'), + default: false + }, + { + when: answer => get(answer, 'themeConfig.editLinks'), + name: 'themeConfig.editLinkText', + type: 'input', + message: 'Edit link text', + description: 'Allows to custom text for edit links', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.editLinkText'), + default: 'Edit this page' + }, + { + name: 'themeConfig.nextLinks', + type: 'confirm', + message: 'Next links', + description: 'Allows to hide next page links on all pages', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.nextLinks'), + default: true + }, + { + name: 'themeConfig.prevLinks', + type: 'confirm', + message: 'Prev links', + description: 'Allows to hide prev page links on all pages', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#git-repository-and-edit-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.prevLinks'), + default: true + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/searchBox.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/searchBox.js new file mode 100644 index 0000000000..321424c212 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/searchBox.js @@ -0,0 +1,44 @@ + +const get = require('lodash/get') + +const { isNumber } = require('../validators') + +const GROUP_NAME = 'Search box' + +module.exports = data => ([ + { + name: 'themeConfig.search', + type: 'confirm', + message: 'Show search box', + description: 'Show the search box globally.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#built-in-search', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.search'), + default: true + }, + { + when: answer => get(answer, 'themeConfig.search'), + name: 'themeConfig.searchMaxSuggestions', + type: 'input', + message: 'Max suggestions', + description: 'Allows to customize how many suggestions will be shown in the search box.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#built-in-search', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.searchMaxSuggestions'), + default: '5', + validate: isNumber, + transform: Number + }, + { + when: answer => get(answer, 'themeConfig.search'), + name: 'themeConfig.searchPlaceholder', + type: 'input', + message: 'Search placeholder', + description: 'Define a placeholder for the search box.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#search-placeholder', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.searchPlaceholder'), + default: '' + } +]) + diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/sidebar.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/sidebar.js new file mode 100644 index 0000000000..95ecf93380 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/defaultThemeConfig/sidebar.js @@ -0,0 +1,50 @@ +const get = require('lodash/get') + +const { isJSONOrAuto } = require('../validators') +const { getJSONObj } = require('../utils') + +const GROUP_NAME = 'Sidebar settings' + +module.exports = data => ([ + { + name: 'themeConfig.sidebar', + type: 'editor', + message: 'Sidebar links', + description: 'The basic configuration expects an Array of links. You can also use \"auto\" value to automatically generate a sidebar that contains only the header links for the current page. Find examples and more infos here:', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#sidebar', + group: GROUP_NAME, + value: data === 'auto' ? get(data, 'config.themeConfig.sidebar') : getJSONObj(data, 'config.themeConfig.sidebar'), + transform: data => data === 'auto' ? data : JSON.parse(data), + validate: data => isJSONOrAuto(data) + }, + { + name: 'themeConfig.displayAllHeaders', + type: 'confirm', + message: 'Display header links of all pages', + description: 'The sidebar only displays links for headers in the current active page. This option allow to display all header links for every page.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#displaying-header-links-of-all-pages', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.displayAllHeaders'), + default: false + }, + { + name: 'themeConfig.activeHeaderLinks', + type: 'confirm', + message: 'Active Header Links', + description: 'By default, the nested header links and the hash in the URL are updated as the user scrolls to view the different sections of the page. This behavior can be disabled/enabled with this option.', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#active-header-links', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.activeHeaderLinks'), + default: true + }, + { + name: 'themeConfig.smoothScroll', + type: 'confirm', + message: 'Smooth scrolling', + description: 'Allows to enable smooth scrolling', + link: 'https://vuepress.vuejs.org/theme/default-theme-config.html#smooth-scrolling', + group: GROUP_NAME, + value: get(data, 'config.themeConfig.smoothScroll'), + default: false + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/downloadTheme.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/downloadTheme.js new file mode 100644 index 0000000000..163106400f --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/downloadTheme.js @@ -0,0 +1,48 @@ +const fs = require('fs') +const util = require('util') +const exec = util.promisify(require('child_process').exec) +const has = require('lodash/has') +const { getPackageJson } = require('./utils') + +/** + * Check if theme is already in package.json + * + * @param {string} theme + */ +const shouldDownloadTheme = async themeName => { + const packageJson = await getPackageJson() + const isThemeAlreadyDownloaded = has(packageJson, `devDependencies.${themeName}`) + || has(packageJson, `dependencies.${themeName}`) + + return !isThemeAlreadyDownloaded +} + +/** + * Download theme with npm or yarn + * + * @param {string} themeName + * @param {Object} api + */ +const downloadTheme = async (themeName, api) => { + const packageManager = fs.existsSync('yarn.lock') ? 'yarn' : 'npm' + const option = packageManager === 'yarn' ? 'add' : 'i' + const command = `${packageManager} ${option} ${themeName} -D` + + api.setProgress({ + status: `Installing theme: ${themeName}`, + info: `Running '${command}'` + }) + + try { + await exec(command) + } catch (err) { + console.error(err) + } + + api.removeProgress() +} + +module.exports = { + shouldDownloadTheme, + downloadTheme +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/advanced.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/advanced.js new file mode 100644 index 0000000000..464434d490 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/advanced.js @@ -0,0 +1,118 @@ +const { isNumber, isJSON } = require('../validators') +const { getJSONObj, emptyStringToUndefined } = require('../utils') + +const GROUP_NAME = 'Advanced settings' + +module.exports = data => ([ + { + name: 'base', + type: 'input', + message: 'Base', + description: 'The base URL the site will be deployed at.', + link: 'https://vuepress.vuejs.org/config/#base', + group: GROUP_NAME, + value: data.config.base, + default: '/', + transform: emptyStringToUndefined + }, + { + name: 'host', + type: 'input', + message: 'Host', + description: 'Specify the host to use for the dev server.', + link: 'https://vuepress.vuejs.org/config/#host', + group: GROUP_NAME, + value: data.config.host, + default: '0.0.0.0' + }, + { + name: 'port', + type: 'input', + message: 'Port', + description: 'Specify the port to use for the dev server. ⚠️ Restart required.', + link: 'https://vuepress.vuejs.org/config/#port', + group: GROUP_NAME, + value: data.config.port, + default: '8080', + validate: isNumber, + transform: Number + }, + { + name: 'temp', + type: 'input', + message: 'Temp', + description: 'Specify the temporary directory for client. ⚠️ Restart required.', + link: 'https://vuepress.vuejs.org/config/#temp', + group: GROUP_NAME, + value: data.config.temp, + transform: emptyStringToUndefined + }, + { + name: 'dest', + type: 'input', + message: 'Dest', + description: 'Specify the output directory for vuepress build. If a relative path is specified, it will be resolved based on process.cwd().', + link: 'https://vuepress.vuejs.org/config/#dest', + group: GROUP_NAME, + value: data.config.dest, + transform: emptyStringToUndefined + }, + { + name: 'cache', + type: 'confirm', + message: 'Cache', + description: 'VuePress uses cache-loader by default to greatly speed up the compilation of webpack. Remove the cache before each build by setting it to false.', + link: 'https://vuepress.vuejs.org/config/#cache', + group: GROUP_NAME, + value: data.config.cache, + default: true + }, + { + name: 'locales', + type: 'editor', + message: 'Locales', + description: 'Specify locales for i18n support.', + link: 'https://vuepress.vuejs.org/config/#locales', + group: GROUP_NAME, + value: getJSONObj(data, 'config.locales'), + transform: JSON.parse, + validate: isJSON, + default: undefined + }, + { + name: 'head', + type: 'editor', + message: 'Head', + description: 'Extra tags to inject into the page HTML <head>.', + link: 'https://vuepress.vuejs.org/config/#head', + group: GROUP_NAME, + value: getJSONObj(data, 'config.head'), + transform: JSON.parse, + validate: isJSON, + default: '[]' + }, + { + name: 'extraWatchFiles', + type: 'editor', + message: 'Watch extra files', + description: 'Specify extra files to watch. You can watch any file if you want. File changes will trigger vuepress rebuilding and real-time updates. ⚠️ Restart required.', + link: 'https://vuepress.vuejs.org/config/#extrawatchfiles', + group: GROUP_NAME, + value: getJSONObj(data, 'config.extraWatchFiles'), + transform: JSON.parse, + validate: isJSON, + default: '[]' + }, + { + name: 'patterns', + type: 'input', + message: 'Patterns', + description: 'Specify which pattern of files you want to be resolved. ⚠️ Restart required.', + link: 'https://vuepress.vuejs.org/config/#extrawatchfiles', + group: GROUP_NAME, + value: getJSONObj(data, 'config.patterns'), + transform: JSON.parse, + validate: isJSON, + default: `["**/*.md", "**/*.vue"]` + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/basic.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/basic.js new file mode 100644 index 0000000000..0d1682a9f0 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/basic.js @@ -0,0 +1,44 @@ +const { required } = require('../validators') + +const GROUP_NAME = 'Basic settings' + +module.exports = data => ([ + { + name: 'theme', + type: 'list', + message: 'VuePress theme', + description: 'Only default-theme is available for now in vue UI.', + // `Select a VuePress theme you want to use. + // ⚠️ Changing theme requires to start VuePress again ⚠️`, + default: null, + choices: [ + { + name: 'Default theme', + value: null + } + ], + link: 'https://vuepress.vuejs.org/theme/using-a-theme.html', + group: GROUP_NAME, + value: data.config.theme + }, + { + name: 'title', + type: 'input', + message: 'Title', + description: 'Title for the site.', + link: 'https://vuepress.vuejs.org/config/#title', + group: GROUP_NAME, + value: data.config.title, + validate: required + }, + { + name: 'description', + type: 'input', + message: 'Description', + description: 'Description for the site.', + link: 'https://vuepress.vuejs.org/config/#description', + group: GROUP_NAME, + value: data.config.description, + validate: required + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/browserCompatibility.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/browserCompatibility.js new file mode 100644 index 0000000000..5692b2d153 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/browserCompatibility.js @@ -0,0 +1,16 @@ +const get = require('lodash/get') + +const GROUP_NAME = 'Browser Compatibility' + +module.exports = data => ([ + { + name: 'evergreen', + type: 'confirm', + message: 'Evergreen', + description: 'Set to true if you are only targeting evergreen browsers. This will disable ES5 transpilation and polyfills for IE, and result in faster builds and smaller files.', + link: 'https://vuepress.vuejs.org/config/#evergreen', + group: GROUP_NAME, + value: get(data, 'config.evergreen'), + default: false + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/index.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/index.js new file mode 100644 index 0000000000..a7ab9b7d28 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/generalConfig/index.js @@ -0,0 +1,11 @@ +const flattenDeep = require('lodash/flattenDeep') + +const basic = require('./basic') +const advanced = require('./advanced') +const browserCompatibility = require('./browserCompatibility') + +module.exports = data => flattenDeep([ + basic(data), + advanced(data), + browserCompatibility(data) +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/markdownConfig/index.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/markdownConfig/index.js new file mode 100644 index 0000000000..5c4d74e5a8 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/markdownConfig/index.js @@ -0,0 +1,76 @@ +const get = require('lodash/get') + +const { isJSON } = require('../validators') +const { getJSONObj } = require('../utils') + +const GROUP_NAME = 'Markdown settings' + +module.exports = data => ([ + { + name: 'markdown.lineNumbers', + type: 'confirm', + message: 'Display line numbers for code blocks', + description: 'Whether to show line numbers to the left of each code blocks.', + link: 'https://vuepress.vuejs.org/config/#markdown-linenumbers', + group: GROUP_NAME, + value: get(data, 'config.markdown.lineNumbers'), + default: undefined + }, + { + name: 'markdown.toc', + type: 'editor', + message: 'Table of contents', + description: 'Options for markdown-it-table-of-contents (Note: prefer markdown.slugify to customize header ids).', + link: 'https://vuepress.vuejs.org/config/#markdown-toc', + group: GROUP_NAME, + value: getJSONObj(data, 'config.markdown.toc'), + validate: isJSON, + transform: JSON.parse, + default: `{ + "includeLevel": [2, 3] +}` + }, + { + name: 'markdown.anchor', + type: 'editor', + message: 'Anchor', + description: 'Options for markdown-it-anchor. (Note: prefer markdown.slugify to customize header ids.)', + link: 'https://vuepress.vuejs.org/config/#markdown-anchor', + group: GROUP_NAME, + value: getJSONObj(data, 'config.markdown.anchor'), + validate: isJSON, + transform: JSON.parse, + default: `{ + "permalink": true, + "permalinkBefore": true, + "permalinkSymbol": "#" +}` + }, + { + name: 'markdown.externalLinks', + type: 'editor', + message: 'External links', + description: 'The key and value pair will be added to <a> tags that point to an external link. The default option will open external links in a new window.', + link: 'https://vuepress.vuejs.org/config/#markdown-externallinks', + group: GROUP_NAME, + value: getJSONObj(data, 'config.markdown.externalLinks'), + validate: isJSON, + transform: JSON.parse, + default: `{ + "target": "_blank", + "rel": "noopener noreferrer" +}` + }, + { + name: 'markdown.extractHeaders', + type: 'input', + message: 'Extract headers', + description: 'While preparing the page, headers are extracted from the Markdown file and stored in this.$page.headers. By default, VuePress will extract h2 and h3 elements for you. You can override the headers it pulls out in your markdown options.', + link: 'https://vuepress.vuejs.org/config/#markdown-extractheaders', + group: GROUP_NAME, + value: getJSONObj(data, 'config.markdown.extractHeaders'), + validate: isJSON, + transform: JSON.parse, + default: `["h2", "h3"]` + } +]) diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/read.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/read.js new file mode 100644 index 0000000000..e61a17553e --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/read.js @@ -0,0 +1,34 @@ +const generalConfig = require('./generalConfig') +const markdownConfig = require('./markdownConfig') +const defaultThemeConfig = require('./defaultThemeConfig') + +module.exports = () => ({ data }) => { + const tabs = [ + { + id: 'generalConfig', + label: 'General', + icon: 'settings_applications', + prompts: [generalConfig] + }, + { + id: 'markdownConfig', + label: 'Markdown', + icon: 'edit', + prompts: [markdownConfig] + }, + { + id: 'themeConfig', + label: 'Theme', + icon: 'palette', + prompts: [ + defaultThemeConfig + ] + } + ] + + tabs.forEach(tab => { + tab.prompts = tab.prompts.flatMap(getConfig => getConfig(data)) + }) + + return { tabs } +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/utils.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/utils.js new file mode 100644 index 0000000000..07e91e0668 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/utils.js @@ -0,0 +1,34 @@ +const loadJsonFile = require('load-json-file') +const get = require('lodash/get') + +/* Parse field inputs */ + +const getJSONObj = (data, prop) => { + const obj = get(data, prop) + return JSON.stringify(obj, null, 4) +} + +/* Check themes */ + +const isDefaultTheme = answer => answer.theme === null + +/* Prompt value transformers */ + +const emptyStringToUndefined = data => data === '' ? undefined : data + +/* Miscellaneous */ + +const getPackageJson = async () => { + try { + return loadJsonFile('package.json') + } catch (err) { + return undefined + } +} + +module.exports = { + getJSONObj, + isDefaultTheme, + getPackageJson, + emptyStringToUndefined +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/validators.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/validators.js new file mode 100644 index 0000000000..a8cc6fa7a4 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/validators.js @@ -0,0 +1,33 @@ +const required = input => !!input + +const isJSON = input => { + try { + const res = JSON.parse(input) + + // JSON.parse('"something"') will not throw an error but return a string + if (typeof res === 'string') throw new Error() + + return true + } catch (e) { + return 'This input must be a valid JSON object' + } +} + +const isJSONOrAuto = input => { + if (input === 'auto' || input === '"auto"') return true + + const isJSONValue = isJSON(input) + + return isJSONValue !== true + ? "This input must be a valid JSON object or 'auto' value" + : isJSONValue +} + +const isNumber = input => !isNaN(input) || 'This input must be a number' + +module.exports = { + required, + isJSON, + isJSONOrAuto, + isNumber +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/write.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/write.js new file mode 100644 index 0000000000..6f8ac7eabe --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/config/write.js @@ -0,0 +1,20 @@ +const { shouldDownloadTheme, downloadTheme } = require('./downloadTheme') + +module.exports = mainAPI => async ({ api, prompts }) => { + const result = {} + + result['themeConfig.nav'] = [] + + for (const prompt of prompts) { + if (!prompt.error) { + result[prompt.id] = await api.getAnswer(prompt.id, prompt.raw.transform) + + // Check if VuePress theme field has changed + if (prompt.id === 'theme' && prompt.valueChanged && await shouldDownloadTheme(result.theme)) { + await downloadTheme(result[prompt.id], mainAPI) + } + } + } + + api.setData('config', result) +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/index.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/index.js new file mode 100644 index 0000000000..0d093a6ca7 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/index.js @@ -0,0 +1,23 @@ +const onRead = require('./config/read') +const onWrite = require('./config/write') +const { describeTasks } = require('./tasks') + +const ID = 'org.vuepress.config' + +module.exports = api => { + api.describeConfig({ + id: ID, + name: 'VuePress', + description: 'VuePress configuration', + link: 'https://vuepress.vuejs.org', + files: { + config: { + js: ['docs/.vuepress/config.js'] + } + }, + onRead: onRead(api), + onWrite: onWrite(api) + }) + + describeTasks(api) +} diff --git a/packages/@vuepress/vue-cli-plugin-vuepress/ui/tasks.js b/packages/@vuepress/vue-cli-plugin-vuepress/ui/tasks.js new file mode 100644 index 0000000000..b47ed784b5 --- /dev/null +++ b/packages/@vuepress/vue-cli-plugin-vuepress/ui/tasks.js @@ -0,0 +1,22 @@ +function describeTasks (api) { + const tasks = [ + { + match: /dev/, + description: 'Run VuePress in dev mode', + link: 'https://vuepress.vuejs.org/guide/getting-started.html#inside-an-existing-project' + }, + { + match: /build/, + description: 'Build static VuePress website', + link: 'https://vuepress.vuejs.org/guide/getting-started.html#inside-an-existing-project' + } + ] + + tasks.forEach(task => { + api.describeTask(task) + }) +} + +module.exports = { + describeTasks +} diff --git a/yarn.lock b/yarn.lock index 4f783a1f27..f43759c5e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7907,6 +7907,16 @@ load-json-file@^5.3.0: strip-bom "^3.0.0" type-fest "^0.3.0" +load-json-file@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== + dependencies: + graceful-fs "^4.1.15" + parse-json "^5.0.0" + strip-bom "^4.0.0" + type-fest "^0.6.0" + load-plugin@^2.0.0: version "2.3.1" resolved "https://registry.yarnpkg.com/load-plugin/-/load-plugin-2.3.1.tgz#8024739afb4aa04de1e602e15e5b1a678c443d00" @@ -11899,6 +11909,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-comments@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d" @@ -13040,10 +13055,10 @@ vue-jest@^4.0.0-beta.1: source-map "^0.5.6" ts-jest "^23.10.5" -vue-loader@^15.2.4: - version "15.7.1" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd" - integrity sha512-fwIKtA23Pl/rqfYP5TSGK7gkEuLhoTvRYW+TU7ER3q9GpNLt/PjG5NLv3XHRDiTg7OPM1JcckBgds+VnAc+HbA== +vue-loader@^15.7.1: + version "15.7.2" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.2.tgz#cc89e2716df87f70fe656c9da9d7f8bec06c73d6" + integrity sha512-H/P9xt/nkocyu4hZKg5TzPqyCT1oKOaCSk9zs0JCbJuy0Q8KtR0bjJpnT/5R5x/Ckd1GFkkLQnQ1C4x6xXeLZg== dependencies: "@vue/component-compiler-utils" "^3.0.0" hash-sum "^1.0.2" @@ -13051,12 +13066,12 @@ vue-loader@^15.2.4: vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" -vue-router@^3.0.2: +vue-router@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.3.tgz#e6b14fabc0c0ee9fda0e2cbbda74b350e28e412b" integrity sha512-8iSa4mGNXBjyuSZFCCO4fiKfvzqk+mhL0lnKuGcQtO1eoj8nq3CmbEG8FwK5QqoqwDgsjsf1GDuisDX4cdb/aQ== -vue-server-renderer@^2.5.16: +vue-server-renderer@^2.6.10: version "2.6.10" resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz#cb2558842ead360ae2ec1f3719b75564a805b375" integrity sha512-UYoCEutBpKzL2fKCwx8zlRtRtwxbPZXKTqbl2iIF4yRZUNO/ovrHyDAJDljft0kd+K0tZhN53XRHkgvCZoIhug== @@ -13078,7 +13093,7 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.5.16: +vue-template-compiler@^2.6.10: version "2.6.10" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc" integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg== @@ -13096,7 +13111,7 @@ vue-toasted@^1.1.25: resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.27.tgz#ce0a74b875f90c2e4a9e163cce6d5fc37d78a07c" integrity sha512-GVbwInwnqkVxQ4GU/XYeQt1e0dAXL8sF5Hr1H/coCBbYUan5xP0G2mEz/HRDf1lt73rFQAN/bJcLTOKkqiM6tg== -vue@^2.5.16: +vue@^2.6.10: version "2.6.10" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637" integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ== @@ -13113,10 +13128,10 @@ vuepress-html-webpack-plugin@^3.2.0: toposort "^1.0.0" util.promisify "1.0.0" -vuepress-plugin-container@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vuepress-plugin-container/-/vuepress-plugin-container-2.0.2.tgz#3489cc732c7a210b31f202556e1346125dffeb73" - integrity sha512-SrGYYT7lkie7xlIlAVhn+9sDW42MytNCoxWL/2uDr+q9wZA4h1uYlQvfc2DVjy+FsM9PPPSslkeo/zCpYVY82g== +vuepress-plugin-container@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/vuepress-plugin-container/-/vuepress-plugin-container-2.1.0.tgz#eb2ba3e01cdac419bd678d40e05c934caffe6db0" + integrity sha512-i4p7S1cqYUrg/3pt+xSghZtKSHVI3VXMQNept8ILxA+lMK1XJkdRkjNovZzwpXlrErQssvrUOTWBV0hdBv7eXQ== dependencies: markdown-it-container "^2.0.0"