From 1e07a05d1a1230d66429e7a63a078e916f53e3d2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 2 Mar 2023 09:18:26 +0800 Subject: [PATCH 1/6] feat: init command --- package.json | 2 + pnpm-lock.yaml | 38 ++++++ src/node/cli.ts | 3 + src/node/init/init.ts | 172 +++++++++++++++++++++++++++ template/.vitepress/config.js | 34 ++++++ template/.vitepress/config.ts | 28 +++++ template/.vitepress/theme/Layout.vue | 21 ++++ template/.vitepress/theme/index.js | 24 ++++ template/.vitepress/theme/style.css | 121 +++++++++++++++++++ template/api-examples.md | 55 +++++++++ template/index.md | 27 +++++ template/markdown-examples.md | 85 +++++++++++++ 12 files changed, 610 insertions(+) create mode 100644 src/node/init/init.ts create mode 100644 template/.vitepress/config.js create mode 100644 template/.vitepress/config.ts create mode 100644 template/.vitepress/theme/Layout.vue create mode 100644 template/.vitepress/theme/index.js create mode 100644 template/.vitepress/theme/style.css create mode 100644 template/api-examples.md create mode 100644 template/index.md create mode 100644 template/markdown-examples.md diff --git a/package.json b/package.json index 3af79d8c3ca..61dd10844b3 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "vue": "^3.2.47" }, "devDependencies": { + "@clack/prompts": "^0.6.1", "@mdit-vue/plugin-component": "^0.12.0", "@mdit-vue/plugin-frontmatter": "^0.12.0", "@mdit-vue/plugin-headers": "^0.12.0", @@ -132,6 +133,7 @@ "fs-extra": "^11.1.0", "get-port": "^6.1.2", "lint-staged": "^13.1.2", + "lodash.template": "^4.5.0", "lru-cache": "^7.17.0", "markdown-it": "^13.0.1", "markdown-it-anchor": "^8.6.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20520109c46..429da5202c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,7 @@ importers: .: specifiers: + '@clack/prompts': ^0.6.1 '@docsearch/css': ^3.3.3 '@docsearch/js': ^3.3.3 '@mdit-vue/plugin-component': ^0.12.0 @@ -51,6 +52,7 @@ importers: fs-extra: ^11.1.0 get-port: ^6.1.2 lint-staged: ^13.1.2 + lodash.template: ^4.5.0 lru-cache: ^7.17.0 markdown-it: ^13.0.1 markdown-it-anchor: ^8.6.7 @@ -97,6 +99,7 @@ importers: vite: 4.1.4_@types+node@18.14.1 vue: 3.2.47 devDependencies: + '@clack/prompts': 0.6.1 '@mdit-vue/plugin-component': 0.12.0 '@mdit-vue/plugin-frontmatter': 0.12.0 '@mdit-vue/plugin-headers': 0.12.0 @@ -138,6 +141,7 @@ importers: fs-extra: 11.1.0 get-port: 6.1.2 lint-staged: 13.1.2_dx6s57r75rxv5zregmjdjjgmei + lodash.template: 4.5.0 lru-cache: 7.17.0 markdown-it: 13.0.1 markdown-it-anchor: 8.6.7_ea7kj7wzjkld5jo2noyjqxi764 @@ -337,6 +341,23 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + /@clack/core/0.3.0: + resolution: {integrity: sha512-ujw1888RciTArxUvwLOf24XSygRX7F4qiCPI7WLH3zCTZJuqKPMcTS7Wqjz0x/AuMpwGPlzhKln4+sCuQqYxzA==} + dependencies: + picocolors: 1.0.0 + sisteransi: 1.0.5 + dev: true + + /@clack/prompts/0.6.1: + resolution: {integrity: sha512-7KuMST/5zB7KpvfR00kcnbOaXmfN6tkJmkLpAyV2Iv2SJ7oxFbhNFvR5OQJynSKDhU8oOp/eFMK6Q0k/DXsq8A==} + dependencies: + '@clack/core': 0.3.0 + picocolors: 1.0.0 + sisteransi: 1.0.5 + dev: true + bundledDependencies: + - is-unicode-supported + /@docsearch/css/3.3.3: resolution: {integrity: sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==} dev: false @@ -3007,10 +3028,27 @@ packages: p-locate: 5.0.0 dev: true + /lodash._reinterpolate/3.0.0: + resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} + dev: true + /lodash.ismatch/4.4.0: resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} dev: true + /lodash.template/4.5.0: + resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} + dependencies: + lodash._reinterpolate: 3.0.0 + lodash.templatesettings: 4.2.0 + dev: true + + /lodash.templatesettings/4.2.0: + resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} + dependencies: + lodash._reinterpolate: 3.0.0 + dev: true + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true diff --git a/src/node/cli.ts b/src/node/cli.ts index ed1031a9784..6ba81dcec9e 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -2,6 +2,7 @@ import minimist from 'minimist' import c from 'picocolors' import { createLogger } from 'vite' import { build, createServer, serve } from '.' +import { init } from './init/init' import { version } from '../../package.json' const argv: any = minimist(process.argv.slice(2)) @@ -48,6 +49,8 @@ if (!command || command === 'dev') { ) process.exit(1) }) + } else if (command === 'init') { + init() } else { createLogger().error(c.red(`unknown command "${command}".`)) process.exit(1) diff --git a/src/node/init/init.ts b/src/node/init/init.ts new file mode 100644 index 00000000000..77e2608306a --- /dev/null +++ b/src/node/init/init.ts @@ -0,0 +1,172 @@ +import { + intro, + outro, + group, + text, + select, + cancel, + confirm +} from '@clack/prompts' +import fs from 'fs-extra' +import path from 'path' +import { black, cyan, bgCyan, bold } from 'picocolors' +import { fileURLToPath } from 'url' +// @ts-ignore +import template from 'lodash.template' + +const enum ThemeType { + Default, + DefaultCustom, + Custom +} + +export async function init() { + intro(bgCyan(bold(black(` Welcome to VitePress! `)))) + + const results = await group( + { + root: () => + text({ + message: `Where should VitePress initialize the config?`, + initialValue: './', + validate(value) { + // TODO make sure directory is inside + } + }), + + title: () => + text({ + message: `Site title:`, + placeholder: 'My Awesome Project' + }), + + description: () => + text({ + message: `Site description:`, + placeholder: 'A VitePress Site' + }), + + theme: () => + select({ + message: 'Theme:', + options: [ + { + // @ts-ignore + value: ThemeType.Default, + label: `Default Theme`, + hint: `Out of the box, good-looking docs` + }, + { + // @ts-ignore + value: ThemeType.DefaultCustom, + label: `Default Theme + Customization`, + hint: `Add custom CSS and layout slots` + }, + { + // @ts-ignore + value: ThemeType.Custom, + label: `Custom Theme`, + hint: `Build your own or use external` + } + ] + }), + + useTs: () => + confirm({ message: 'Use TypeScript for config and theme files?' }), + + injectNpmScripts: () => + confirm({ + message: `Add VitePress npm scripts to package.json?` + }) + }, + { + onCancel: () => { + cancel('Cancelled.') + process.exit(0) + } + } + ) + + const { + root = './', + title = 'My Awesome Project', + description = 'A VitePress Site', + theme, + useTs, + injectNpmScripts + } = results + + const resolvedRoot = path.resolve(root) + const templateDir = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../../template' + ) + + const data = { + title: JSON.stringify(title), + description: JSON.stringify(description), + useTs, + defaultTheme: + theme === ThemeType.Default || theme === ThemeType.DefaultCustom + } + + const renderFile = (file: string) => { + const filePath = path.resolve(templateDir, file) + let targetPath = path.resolve(resolvedRoot, file) + if (useTs) { + targetPath = targetPath.replace(/\.js$/, '.ts') + } + const src = fs.readFileSync(filePath, 'utf-8') + const compiled = template(src)(data) + fs.outputFileSync(targetPath, compiled) + } + + const filesToScaffold = [ + 'index.md', + 'api-examples.md', + 'markdown-examples.md', + `.vitepress/config.js` + ] + + if (theme === ThemeType.DefaultCustom) { + filesToScaffold.push( + `.vitepress/theme/index.js`, + `.vitepress/theme/style.css` + ) + } else if (theme === ThemeType.Custom) { + filesToScaffold.push( + `.vitepress/theme/index.js`, + `.vitepress/theme/style.css`, + `.vitepress/theme/Layout.vue` + ) + } + + for (const file of filesToScaffold) { + renderFile(file) + } + + const dir = root === './' ? `` : ` ${root.replace(/^\.\//, '')}` + if (injectNpmScripts) { + const scripts = { + 'docs:dev': `vitepress dev${dir}`, + 'docs:build': `vitepress build${dir}`, + 'docs:preview': `vitepress preview${dir}` + } + const pkgPath = path.resolve('package.json') + let pkg + if (!fs.existsSync(pkgPath)) { + pkg = { scripts } + } else { + pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) + Object.assign(pkg.scripts || (pkg.scripts = {}), scripts) + } + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)) + outro(`Done! Now run ${cyan(`npm run docs:dev`)} and start writing.`) + } else { + outro( + `You're all set! Now run ${cyan( + `npx vitepress dev${dir}` + )} and start writing.` + ) + } +} diff --git a/template/.vitepress/config.js b/template/.vitepress/config.js new file mode 100644 index 00000000000..6708ddbffef --- /dev/null +++ b/template/.vitepress/config.js @@ -0,0 +1,34 @@ +<% if (useTs) { %>import { defineConfig } from 'vitepress' + +// https://vitepress.vuejs.org/config/app-config +export default defineConfig(<% } else { %>/** + * @type {import('vitepress').UserConfig} + * https://vitepress.vuejs.org/config/app-config + */ +const config = <% } %>{ + title: <%= title %>, + description: <%= description %><% if (defaultTheme) { %>, + themeConfig: { + // https://vitepress.vuejs.org/config/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Examples', link: '/markdown-examples' } + ], + + sidebar: [ + { + text: 'Examples', + items: [ + { text: 'Markdown Examples', link: '/markdown-examples' }, + { text: 'Runtime API Examples', link: '/api-examples' } + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + ] + }<% } %> +}<% if (useTs) { %>)<% } else { %> + +export default config<% } %> diff --git a/template/.vitepress/config.ts b/template/.vitepress/config.ts new file mode 100644 index 00000000000..1f00cccb54b --- /dev/null +++ b/template/.vitepress/config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.vuejs.org/config/app-config +export default defineConfig({ + title: <%= title %>, + description: <%= description %><% if (defaultTheme) { %>, + themeConfig: { + // https://vitepress.vuejs.org/config/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Examples', link: '/markdown-examples' } + ], + + sidebar: [ + { + text: 'Examples', + items: [ + { text: 'Markdown Examples', link: '/markdown-examples' }, + { text: 'Runtime API Examples', link: '/api-examples' } + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + ] + }<% } %> +}) diff --git a/template/.vitepress/theme/Layout.vue b/template/.vitepress/theme/Layout.vue new file mode 100644 index 00000000000..f45d7c622ef --- /dev/null +++ b/template/.vitepress/theme/Layout.vue @@ -0,0 +1,21 @@ + + + diff --git a/template/.vitepress/theme/index.js b/template/.vitepress/theme/index.js new file mode 100644 index 00000000000..3abed00cbef --- /dev/null +++ b/template/.vitepress/theme/index.js @@ -0,0 +1,24 @@ +<% if (!defaultTheme) { %>import Layout from './Layout.vue' +import './style.css' + +export default { + Layout, + enhanceApp({ app, router, siteData }) { + // TODO link to app level customizatin + } +} +<% } else { %>import { h } from 'vue' +import Theme from 'vitepress/theme' +import './style.css' + +export default { + ...Theme, + Layout: () => { + return h(Theme.Layout, null, { + // TODO link to layout slots + }) + }, + enhanceApp({ app, router, siteData }) { + // TODO link to app level customizatin + } +}<% } %> diff --git a/template/.vitepress/theme/style.css b/template/.vitepress/theme/style.css new file mode 100644 index 00000000000..e5874acd8c5 --- /dev/null +++ b/template/.vitepress/theme/style.css @@ -0,0 +1,121 @@ +<% if (defaultTheme) { %>/** + * Colors + * -------------------------------------------------------------------------- */ + + :root { + --vp-c-brand: #646cff; + --vp-c-brand-light: #747bff; + --vp-c-brand-lighter: #9499ff; + --vp-c-brand-lightest: #bcc0ff; + --vp-c-brand-dark: #535bf2; + --vp-c-brand-darker: #454ce1; + --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(40px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-darker); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + +.dark { + --vp-custom-block-tip-border: var(--vp-c-brand); + --vp-custom-block-tip-text: var(--vp-c-brand-lightest); + --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand) !important; +} + +/** + * VitePress: Custom fix + * -------------------------------------------------------------------------- */ + +/* + Use lighter colors for links in dark mode for a11y. + Also specify some classes twice to have higher specificity + over scoped class data attribute. +*/ +.dark .vp-doc a, +.dark .vp-doc a > code, +.dark .VPNavBarMenuLink.VPNavBarMenuLink:hover, +.dark .VPNavBarMenuLink.VPNavBarMenuLink.active, +.dark .link.link:hover, +.dark .link.link.active, +.dark .edit-link-button.edit-link-button, +.dark .pager-link .title { + color: var(--vp-c-brand-lighter); +} + +.dark .vp-doc a:hover, +.dark .vp-doc a > code:hover { + color: var(--vp-c-brand-lightest); + opacity: 1; +} + +/* Transition by color instead of opacity */ +.dark .vp-doc .custom-block a { + transition: color 0.25s; +} +<% } else { %> +html { + font-family: Arial, Helvetica; +} +<% } %> diff --git a/template/api-examples.md b/template/api-examples.md new file mode 100644 index 00000000000..07460c713b1 --- /dev/null +++ b/template/api-examples.md @@ -0,0 +1,55 @@ +--- +outline: deep +--- + +# Runtime API Examples + +This page demonstrates usage of some of the runtime APIs provided by VitePress. + +The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: + +```md + + +## Results + +### Site Data +
{{ site }}
+ +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+``` + + + +## Results + +### Site Data +
{{ site }}
+ +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+ +## More + +Check out the documentation for the [full list of runtime APIs](https://vitepress.vuejs.org/api/). diff --git a/template/index.md b/template/index.md new file mode 100644 index 00000000000..be2cd658cff --- /dev/null +++ b/template/index.md @@ -0,0 +1,27 @@ +<% if (defaultTheme) { %>--- +layout: home + +hero: + name: <%= title %> + text: <%= description %> + tagline: My great project tagline + actions: + - theme: brand + text: Markdown Examples + link: /markdown-examples + - theme: alt + text: API Examples + link: /api-examples + +features: + - title: Feature A + details: Lorem ipsum dolor sit amet, consectetur adipiscing elit + - title: Feature B + details: Lorem ipsum dolor sit amet, consectetur adipiscing elit + - title: Feature C + details: Lorem ipsum dolor sit amet, consectetur adipiscing elit +--- +<% } else { %>--- +home: true +--- +<% } %> diff --git a/template/markdown-examples.md b/template/markdown-examples.md new file mode 100644 index 00000000000..cc547eeac8a --- /dev/null +++ b/template/markdown-examples.md @@ -0,0 +1,85 @@ +# Markdown Extension Examples + +This page demonstrates some of the built-in markdown extensions provided by VitePress. + +## Syntax Highlighting + +VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: + +**Input** + +```` +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` +```` + +**Output** + +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` + +## Custom Containers + +**Input** + +```md +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: +``` + +**Output** + +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: + +## More + +Check out the documentation for the [full list of markdown extensions](https://vitepress.vuejs.org/guide/markdown). From c68da10c10b31ee33c8f98e5cb0b3e7412cbbf34 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 2 Mar 2023 09:19:16 +0800 Subject: [PATCH 2/6] include template in dist files --- package.json | 1 + tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 61dd10844b3..b9752a10b53 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "bin", "dist", "types", + "template", "client.d.ts", "theme.d.ts" ], diff --git a/tsconfig.json b/tsconfig.json index 04ddfad2593..226b5082626 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,5 +14,5 @@ "jsx": "preserve", "lib": ["esnext", "dom", "dom.iterable"] }, - "exclude": ["**/node_modules/**", "**/dist/**"] + "exclude": ["**/node_modules/**", "**/dist/**", "template"] } From 15364341737663cb1f1cbe0b69d184a408c424a7 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 2 Mar 2023 09:26:32 +0800 Subject: [PATCH 3/6] refactor scaffold to a separate method --- src/node/init/init.ts | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/node/init/init.ts b/src/node/init/init.ts index 77e2608306a..0d8a6adcd8b 100644 --- a/src/node/init/init.ts +++ b/src/node/init/init.ts @@ -20,10 +20,19 @@ const enum ThemeType { Custom } +interface Options { + root: string + title?: string + description?: string + theme: ThemeType + useTs: boolean + injectNpmScripts: boolean +} + export async function init() { intro(bgCyan(bold(black(` Welcome to VitePress! `)))) - const results = await group( + const options: Options = await group( { root: () => text({ @@ -87,15 +96,17 @@ export async function init() { } ) - const { - root = './', - title = 'My Awesome Project', - description = 'A VitePress Site', - theme, - useTs, - injectNpmScripts - } = results + outro(scaffold(options)) +} +export function scaffold({ + root = './', + title = 'My Awesome Project', + description = 'A VitePress Site', + theme, + useTs, + injectNpmScripts +}: Options) { const resolvedRoot = path.resolve(root) const templateDir = path.resolve( path.dirname(fileURLToPath(import.meta.url)), @@ -161,12 +172,10 @@ export async function init() { Object.assign(pkg.scripts || (pkg.scripts = {}), scripts) } fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)) - outro(`Done! Now run ${cyan(`npm run docs:dev`)} and start writing.`) + return `Done! Now run ${cyan(`npm run docs:dev`)} and start writing.` } else { - outro( - `You're all set! Now run ${cyan( - `npx vitepress dev${dir}` - )} and start writing.` - ) + return `You're all set! Now run ${cyan( + `npx vitepress dev${dir}` + )} and start writing.` } } From 39bf3f0cc89f4a78152c931841841c7b91184edb Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 2 Mar 2023 11:27:02 +0800 Subject: [PATCH 4/6] test for the init command --- .gitignore | 1 + __tests__/e2e/vitestGlobalSetup.ts | 4 +- __tests__/init/init.test.ts | 98 +++++++++++++++++++++++++++++ __tests__/init/package.json | 7 +++ __tests__/init/vitest.config.ts | 23 +++++++ __tests__/init/vitestGlobalSetup.ts | 17 +++++ package.json | 9 ++- pnpm-lock.yaml | 6 ++ src/node/index.ts | 1 + src/node/init/init.ts | 28 ++++----- src/node/plugin.ts | 2 +- 11 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 __tests__/init/init.test.ts create mode 100644 __tests__/init/package.json create mode 100644 __tests__/init/vitest.config.ts create mode 100644 __tests__/init/vitestGlobalSetup.ts diff --git a/.gitignore b/.gitignore index 330a5686b40..81c0093457d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ .vscode dist cache +temp examples-temp node_modules pnpm-global diff --git a/__tests__/e2e/vitestGlobalSetup.ts b/__tests__/e2e/vitestGlobalSetup.ts index 5cca4bc6063..0ee9455f951 100644 --- a/__tests__/e2e/vitestGlobalSetup.ts +++ b/__tests__/e2e/vitestGlobalSetup.ts @@ -1,8 +1,8 @@ import getPort from 'get-port' -import { Server } from 'net' import { chromium, type BrowserServer } from 'playwright-chromium' -import { type ViteDevServer } from 'vite' import { build, createServer, serve } from 'vitepress' +import type { ViteDevServer } from 'vite' +import type { Server } from 'net' let browserServer: BrowserServer let server: ViteDevServer | Server diff --git a/__tests__/init/init.test.ts b/__tests__/init/init.test.ts new file mode 100644 index 00000000000..a05bd8209d7 --- /dev/null +++ b/__tests__/init/init.test.ts @@ -0,0 +1,98 @@ +import { chromium, type Browser, type Page } from 'playwright-chromium' +import { fileURLToPath } from 'url' +import path from 'path' +import fs from 'fs-extra' +import { + scaffold, + build, + createServer, + serve, + ScaffoldThemeType, + type ScaffoldOptions +} from 'vitepress' +import type { ViteDevServer } from 'vite' +import type { Server } from 'net' +import getPort from 'get-port' + +let browser: Browser +let page: Page + +beforeAll(async () => { + browser = await chromium.connect(process.env['WS_ENDPOINT']!) + page = await browser.newPage() +}) + +afterAll(async () => { + await page.close() + await browser.close() +}) + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'temp') + +async function testVariation(options: ScaffoldOptions) { + fs.removeSync(root) + scaffold({ + ...options, + root + }) + + let server: ViteDevServer | Server + const port = await getPort() + + async function goto(path: string) { + await page.goto(`http://localhost:${port}${path}`) + await page.waitForSelector('#app div') + } + + if (process.env['VITE_TEST_BUILD']) { + await build(root) + server = (await serve({ root, port })).server + } else { + server = await createServer(root, { port }) + await server!.listen() + } + + try { + await goto('/') + expect(await page.textContent('h1')).toMatch('My Awesome Project') + + await page.click('a[href="/markdown-examples.html"]') + await page.waitForSelector('pre code') + expect(await page.textContent('h1')).toMatch('Markdown Extension Examples') + + await goto('/') + expect(await page.textContent('h1')).toMatch('My Awesome Project') + + await page.click('a[href="/api-examples.html"]') + await page.waitForSelector('pre code') + expect(await page.textContent('h1')).toMatch('Runtime API Examples') + } finally { + fs.removeSync(root) + if ('ws' in server) { + await server.close() + } else { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + } + } +} + +const themes = [ + ScaffoldThemeType.Default, + ScaffoldThemeType.DefaultCustom, + ScaffoldThemeType.Custom +] +const usingTs = [false, true] + +for (const theme of themes) { + for (const useTs of usingTs) { + test(`${theme}${useTs ? ` + TypeScript` : ``}`, () => + testVariation({ + root: '.', + theme, + useTs, + injectNpmScripts: false + })) + } +} diff --git a/__tests__/init/package.json b/__tests__/init/package.json new file mode 100644 index 00000000000..0aea611704a --- /dev/null +++ b/__tests__/init/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "devDependencies": { + "vitepress": "workspace:*" + } +} diff --git a/__tests__/init/vitest.config.ts b/__tests__/init/vitest.config.ts new file mode 100644 index 00000000000..789ba16d6a6 --- /dev/null +++ b/__tests__/init/vitest.config.ts @@ -0,0 +1,23 @@ +import { dirname, resolve } from 'path' +import { fileURLToPath } from 'url' +import { defineConfig } from 'vitest/config' + +const dir = dirname(fileURLToPath(import.meta.url)) + +const timeout = 60_000 + +export default defineConfig({ + resolve: { + alias: { + node: resolve(dir, '../../src/node') + } + }, + test: { + watchExclude: ['**/node_modules/**', '**/temp/**'], + globalSetup: ['__tests__/init/vitestGlobalSetup.ts'], + testTimeout: timeout, + hookTimeout: timeout, + teardownTimeout: timeout, + globals: true + } +}) diff --git a/__tests__/init/vitestGlobalSetup.ts b/__tests__/init/vitestGlobalSetup.ts new file mode 100644 index 00000000000..3f2c2508bf5 --- /dev/null +++ b/__tests__/init/vitestGlobalSetup.ts @@ -0,0 +1,17 @@ +import { chromium, type BrowserServer } from 'playwright-chromium' + +let browserServer: BrowserServer + +export async function setup() { + browserServer = await chromium.launchServer({ + headless: !process.env.DEBUG, + args: process.env.CI + ? ['--no-sandbox', '--disable-setuid-sandbox'] + : undefined + }) + process.env['WS_ENDPOINT'] = browserServer.wsEndpoint() +} + +export async function teardown() { + await browserServer.close() +} diff --git a/package.json b/package.json index b9752a10b53..00e5d66c587 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,12 @@ "format": "prettier --check --write .", "format-fail": "prettier --check .", "check": "run-s format-fail build test", - "test": "run-p --aggregate-output test-unit test-preview test-build", + "test": "run-p --aggregate-output test-unit test-preview test-build test-init", "test-unit": "vitest run -r __tests__/unit", "test-preview": "vitest run -r __tests__/e2e", "test-build": "VITE_TEST_BUILD=1 pnpm test-preview", + "test-init": "vitest run -r __tests__/init", + "test-init-build": "VITE_TEST_BUILD=1 pnpm test-init", "debug-preview": "DEBUG=1 vitest -r __tests__/e2e", "debug-build": "VITE_TEST_BUILD=1 pnpm debug-preview", "unit-dev": "vitest -r __tests__/unit", @@ -78,7 +80,10 @@ "docs-debug": "node --inspect-brk ./bin/vitepress dev docs", "docs-build": "run-s build docs-build-only", "docs-build-only": "node ./bin/vitepress build docs", - "docs-preview": "node ./bin/vitepress preview docs" + "docs-preview": "node ./bin/vitepress preview docs", + "docs:dev": "vitepress dev /Users/evan/Vue/vitepress/__tests__/init/temp", + "docs:build": "vitepress build /Users/evan/Vue/vitepress/__tests__/init/temp", + "docs:preview": "vitepress preview /Users/evan/Vue/vitepress/__tests__/init/temp" }, "dependencies": { "@docsearch/css": "^3.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 429da5202c0..c3808ea9375 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -181,6 +181,12 @@ importers: devDependencies: vitepress: link:../.. + __tests__/init: + specifiers: + vitepress: workspace:* + devDependencies: + vitepress: link:../.. + docs: specifiers: vitepress: workspace:* diff --git a/src/node/index.ts b/src/node/index.ts index 21b055cf466..c34f37087d5 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -3,6 +3,7 @@ export * from './server' export * from './markdown' export * from './build/build' export * from './serve/serve' +export * from './init/init' // shared types export type { diff --git a/src/node/init/init.ts b/src/node/init/init.ts index 0d8a6adcd8b..1e57f17c189 100644 --- a/src/node/init/init.ts +++ b/src/node/init/init.ts @@ -14,17 +14,17 @@ import { fileURLToPath } from 'url' // @ts-ignore import template from 'lodash.template' -const enum ThemeType { - Default, - DefaultCustom, - Custom +export enum ScaffoldThemeType { + Default = 'default theme', + DefaultCustom = 'default theme + customization', + Custom = 'custom theme' } -interface Options { +export interface ScaffoldOptions { root: string title?: string description?: string - theme: ThemeType + theme: ScaffoldThemeType useTs: boolean injectNpmScripts: boolean } @@ -32,7 +32,7 @@ interface Options { export async function init() { intro(bgCyan(bold(black(` Welcome to VitePress! `)))) - const options: Options = await group( + const options: ScaffoldOptions = await group( { root: () => text({ @@ -61,19 +61,19 @@ export async function init() { options: [ { // @ts-ignore - value: ThemeType.Default, + value: ScaffoldThemeType.Default, label: `Default Theme`, hint: `Out of the box, good-looking docs` }, { // @ts-ignore - value: ThemeType.DefaultCustom, + value: ScaffoldThemeType.DefaultCustom, label: `Default Theme + Customization`, hint: `Add custom CSS and layout slots` }, { // @ts-ignore - value: ThemeType.Custom, + value: ScaffoldThemeType.Custom, label: `Custom Theme`, hint: `Build your own or use external` } @@ -106,7 +106,7 @@ export function scaffold({ theme, useTs, injectNpmScripts -}: Options) { +}: ScaffoldOptions) { const resolvedRoot = path.resolve(root) const templateDir = path.resolve( path.dirname(fileURLToPath(import.meta.url)), @@ -118,7 +118,7 @@ export function scaffold({ description: JSON.stringify(description), useTs, defaultTheme: - theme === ThemeType.Default || theme === ThemeType.DefaultCustom + theme === ScaffoldThemeType.Default || theme === ScaffoldThemeType.DefaultCustom } const renderFile = (file: string) => { @@ -139,12 +139,12 @@ export function scaffold({ `.vitepress/config.js` ] - if (theme === ThemeType.DefaultCustom) { + if (theme === ScaffoldThemeType.DefaultCustom) { filesToScaffold.push( `.vitepress/theme/index.js`, `.vitepress/theme/style.css` ) - } else if (theme === ThemeType.Custom) { + } else if (theme === ScaffoldThemeType.Custom) { filesToScaffold.push( `.vitepress/theme/index.js`, `.vitepress/theme/style.css`, diff --git a/src/node/plugin.ts b/src/node/plugin.ts index 9e1d402432d..6441ba744b5 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -126,7 +126,7 @@ export async function createVitePressPlugin( }, optimizeDeps: { // force include vue to avoid duplicated copies when linked + optimized - include: ['vue'], + include: ['vue', '@vue/devtools-api'], exclude: ['@docsearch/js', 'vitepress'] }, server: { From e2fa7fd4a4566e7f509d94ec78afda38112142cb Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 2 Mar 2023 11:28:57 +0800 Subject: [PATCH 5/6] chore: prettier ignore template dir --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 978b7fca60b..82670a17a40 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ dist pnpm-lock.yaml cache +template From 0eaf5d7fb8da8142a252a33838fafc0de3c7e990 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 2 Mar 2023 11:31:53 +0800 Subject: [PATCH 6/6] format fix --- src/node/init/init.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node/init/init.ts b/src/node/init/init.ts index 1e57f17c189..6abf2b24ac0 100644 --- a/src/node/init/init.ts +++ b/src/node/init/init.ts @@ -118,7 +118,8 @@ export function scaffold({ description: JSON.stringify(description), useTs, defaultTheme: - theme === ScaffoldThemeType.Default || theme === ScaffoldThemeType.DefaultCustom + theme === ScaffoldThemeType.Default || + theme === ScaffoldThemeType.DefaultCustom } const renderFile = (file: string) => {