feat: add create-vuepress-theme-plume package (#153)

* feat: add `create-vuepress-theme-plume` package
* feat(cli): add support deploy
This commit is contained in:
pengzhanbo 2024-08-29 12:03:32 +08:00 committed by GitHub
parent a4dc03f736
commit 74079390f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 2117 additions and 18 deletions

21
cli/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (C) 2021 - PRESENT by pengzhanbo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

14
cli/README.md Normal file
View File

@ -0,0 +1,14 @@
# create-vuepress-theme-plume
The cli for create vuepress-theme-plume's project
## Usage
```sh
# npm
npm init vuepress-theme-plume@latest
# pnpm
pnpm create vuepress-theme-plume@latest
# yarn
yarn create vuepress-theme-plume@latest
```

2
cli/bin/index.js Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
import '../lib/index.js'

43
cli/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "create-vuepress-theme-plume",
"type": "module",
"version": "1.0.0-rc.90",
"description": "The cli for create vuepress-theme-plume's project",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT",
"homepage": "https://theme-plume.vuejs.press/",
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git",
"directory": "cli"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"keywords": [
"VuePress",
"theme",
"plume",
"cli"
],
"bin": "./bin/index.js",
"files": [
"bin",
"lib",
"templates"
],
"scripts": {
"build": "tsup"
},
"dependencies": {
"@clack/prompts": "^0.7.0",
"@pengzhanbo/utils": "^1.1.2",
"cac": "^6.7.14",
"execa": "^9.3.1",
"handlebars": "^4.7.8",
"picocolors": "^1.0.1"
},
"theme-plume": {
"vuepress": "2.0.0-rc.14"
}
}

30
cli/src/constants.ts Normal file
View File

@ -0,0 +1,30 @@
import type { Bundler, Langs, Options } from './types.js'
export const languageOptions: Options<Langs> = [
{ label: 'English', value: 'en-US' },
{ label: '简体中文', value: 'zh-CN' },
]
export const bundlerOptions: Options<Bundler> = [
{ label: 'Vite', value: 'vite' },
{ label: 'Webpack', value: 'webpack' },
]
export enum Mode {
init,
create,
}
export enum DeployType {
github = 'github',
vercel = 'vercel',
netlify = 'netlify',
custom = 'custom',
}
export const deployOptions: Options<DeployType> = [
{ label: 'Custom', value: DeployType.custom },
{ label: 'GitHub Pages', value: DeployType.github },
{ label: 'Vercel', value: DeployType.vercel },
{ label: 'Netlify', value: DeployType.netlify },
]

123
cli/src/generate.ts Normal file
View File

@ -0,0 +1,123 @@
import path from 'node:path'
import process from 'node:process'
import fs from 'node:fs'
import { execaCommand } from 'execa'
import { createPackageJson } from './packageJson.js'
import { createRender } from './render.js'
import { getTemplate, readFiles, readJsonFile, writeFiles } from './utils/index.js'
import type { File, ResolvedData } from './types.js'
import { DeployType, Mode } from './constants.js'
export async function generate(mode: Mode, data: ResolvedData): Promise<void> {
const cwd = process.cwd()
let userPkg: Record<string, any> = {}
if (mode === Mode.init) {
const pkgPath = path.join(cwd, 'package.json')
if (fs.existsSync(pkgPath)) {
userPkg = (await readJsonFile(pkgPath)) || {}
}
}
const fileList: File[] = [
// add package.json
await createPackageJson(mode, userPkg, data),
// add docs files
...await createDocsFiles(data),
// add vuepress and theme-plume configs
...updateFileListTarget(await readFiles(getTemplate('.vuepress')), `${data.docsDir}/.vuepress`),
]
// add repo root files
if (mode === Mode.create) {
fileList.push(...await readFiles(getTemplate('common')))
if (data.packageManager === 'pnpm') {
fileList.push({
filepath: '.npmrc',
content: 'shamefully-hoist=true\nshell-emulator=true',
})
}
if (data.packageManager === 'yarn') {
const { stdout: yarnVersion } = await execaCommand('yarn --version')
if (yarnVersion.startsWith('2')) {
fileList.push({
filepath: '.yarnrc.yml',
content: 'nodeLinker: \'node-modules\'\n',
})
}
}
}
// rewrite git files begin ==================================
if (data.git)
fileList.push(...await readFiles(getTemplate('git')))
if (mode === Mode.init) {
const gitignorePath = path.join(cwd, '.gitignore')
const docs = data.docsDir
if (fs.existsSync(gitignorePath)) {
const content = await fs.promises.readFile(gitignorePath, 'utf-8')
fileList.push({
filepath: '.gitignore',
content: `${content}\n${docs}/.vuepress/.cache\n${docs}/.vuepress/.temp\n${docs}/.vuepress/dist\n`,
})
}
}
// rewrite git files end ====================================
if (data.deploy !== DeployType.custom) {
fileList.push(...await readFiles(getTemplate(`deploy/${data.deploy}`)))
}
const render = createRender(data)
const renderedFiles = fileList.map((file) => {
if (file.filepath.endsWith('.handlebars'))
file.content = render(file.content)
return file
})
const ext = data.useTs ? '' : userPkg.type !== 'module' ? '.mjs' : '.js'
const REG_EXT = /\.ts$/
const output = mode === Mode.create ? path.join(cwd, data.root) : cwd
await writeFiles(renderedFiles, output, (filepath) => {
if (filepath.endsWith('.d.ts'))
return filepath
if (ext)
return filepath.replace(REG_EXT, ext)
return filepath
})
}
async function createDocsFiles(data: ResolvedData): Promise<File[]> {
const fileList: File[] = []
if (data.multiLanguage) {
const enDocs = await readFiles(getTemplate('docs/en'))
const zhDocs = await readFiles(getTemplate('docs/zh'))
if (data.defaultLanguage === 'en-US') {
fileList.push(...enDocs)
fileList.push(...updateFileListTarget(zhDocs, 'zh'))
}
else {
fileList.push(...zhDocs)
fileList.push(...updateFileListTarget(enDocs, 'en'))
}
}
else {
if (data.defaultLanguage === 'en-US')
fileList.push(...await readFiles(getTemplate('docs/en')))
else
fileList.push(...await readFiles(getTemplate('docs/zh')))
}
return updateFileListTarget(fileList, data.docsDir)
}
function updateFileListTarget(fileList: File[], target: string): File[] {
return fileList.map(({ filepath, content }) => ({
filepath: path.join(target, filepath),
content,
}))
}

21
cli/src/index.ts Normal file
View File

@ -0,0 +1,21 @@
import cac from 'cac'
import { run } from './run.js'
import { Mode } from './constants.js'
declare const __CLI_VERSION__: string
const cli = cac('create-vuepress-theme-plume')
cli
.command('[root]', 'create a new vuepress-theme-plume project / 创建新的 vuepress-theme-plume 项目')
.action((root: string) => run(Mode.create, root))
cli
.command('init [root]', 'Initial vuepress-theme-plume in the existing project / 在现有项目中初始化 vuepress-theme-plume')
.action((root: string) => run(Mode.init, root))
cli.help()
cli.version(__CLI_VERSION__)
cli.parse()

25
cli/src/locales/en.ts Normal file
View File

@ -0,0 +1,25 @@
import type { Locale } from '../types.js'
export const en: Locale = {
'question.root': 'Where would you want to initialize VuePress?',
'question.site.name': 'Site Name:',
'question.site.description': 'Site Description:',
'question.bundler': 'Select a bundler',
'question.multiLanguage': 'Do you want to use multiple languages?',
'question.defaultLanguage': 'Select the default language of the site',
'question.useTs': 'Use TypeScript?',
'question.injectNpmScripts': 'Inject npm scripts?',
'question.deploy': 'Deploy type:',
'question.git': 'Initialize a git repository?',
'question.installDeps': 'Install dependencies?',
'spinner.start': '🚀 Creating...',
'spinner.stop': '🎉 Create success!',
'spinner.git': '📄 Initializing git repository...',
'spinner.install': '📦 Installing dependencies...',
'spinner.command': '🔨 Execute the following command to start:',
'hint.cancel': 'Operation cancelled.',
'hint.root': 'The path cannot be an absolute path, and cannot contain the parent path.',
'hint.root.illegal': 'Project names cannot contain special characters.',
}

8
cli/src/locales/index.ts Normal file
View File

@ -0,0 +1,8 @@
import type { Langs, Locale } from '../types.js'
import { en } from './en.js'
import { zh } from './zh.js'
export const locales: Record<Langs, Locale> = {
'zh-CN': zh,
'en-US': en,
}

25
cli/src/locales/zh.ts Normal file
View File

@ -0,0 +1,25 @@
import type { Locale } from '../types.js'
export const zh: Locale = {
'question.root': '您想在哪里初始化 VuePress',
'question.site.name': '站点名称:',
'question.site.description': '站点描述信息:',
'question.bundler': '请选择打包工具',
'question.multiLanguage': '是否使用多语言?',
'question.defaultLanguage': '请选择站点默认语言',
'question.useTs': '是否使用 TypeScript',
'question.injectNpmScripts': '是否注入 npm 脚本?',
'question.deploy': '部署方式:',
'question.git': '是否初始化 git 仓库?',
'question.installDeps': '是否安装依赖?',
'spinner.start': '🚀 正在创建...',
'spinner.stop': '🎉 创建成功!',
'spinner.git': '📄 初始化 git 仓库...',
'spinner.install': '📦 安装依赖...',
'spinner.command': '🔨 执行以下命令即可启动:',
'hint.cancel': '操作已取消。',
'hint.root': '文件路径不能是绝对路径,不能包含父路径。',
'hint.root.illegal': '文件夹不能包含特殊字符。',
}

79
cli/src/packageJson.ts Normal file
View File

@ -0,0 +1,79 @@
import { execaCommand } from 'execa'
import { kebabCase } from '@pengzhanbo/utils'
import { getDependenciesVersion, readJsonFile, resolve } from './utils/index.js'
import type { File, ResolvedData } from './types.js'
import { Mode } from './constants.js'
export async function createPackageJson(
mode: Mode,
pkg: Record<string, any>,
{
docsDir,
siteName,
siteDescription,
bundler,
injectNpmScripts,
}: ResolvedData,
): Promise<File> {
if (mode === Mode.create) {
pkg.name = kebabCase(siteName)
pkg.type = 'module'
pkg.version = '1.0.0'
pkg.description = siteDescription
const userInfo = await getUserInfo()
if (userInfo) {
pkg.author = userInfo.username + (userInfo.email ? ` <${userInfo.email}>` : '')
}
pkg.license = 'MIT'
}
if (injectNpmScripts) {
pkg.scripts ??= {}
pkg.scripts = {
...pkg.scripts,
'docs:dev': `vuepress dev ${docsDir}`,
'docs:dev-clean': `vuepress dev ${docsDir} --clean-cache --clean-temp`,
'docs:build': `vuepress build ${docsDir} --clean-cache --clean-temp`,
'docs:preview': `http-server ${docsDir}/.vuepress/dist`,
}
if (mode === Mode.create) {
pkg.scripts['vp-update'] = 'vp-update'
}
}
pkg.devDependencies ??= {}
const context = (await readJsonFile(resolve('package.json')))!
const meta = context['theme-plume']
pkg.devDependencies.vuepress = `${meta.vuepress}`
pkg.devDependencies['vuepress-theme-plume'] = `${context.version}`
pkg.devDependencies[`@vuepress/bundler-${bundler}`] = `${meta.vuepress}`
pkg.devDependencies['http-server'] = '^14.1.1'
const deps: string[] = []
if (!pkg.dependencies?.vue && !pkg.devDependencies.vue)
deps.push('vue')
if (bundler === 'webpack' && !pkg.dependencies?.['sass-loader'] && !pkg.devDependencies['sass-loader'])
deps.push('sass-loader')
const dv = await getDependenciesVersion(deps)
for (const [d, v] of Object.entries(dv))
pkg.devDependencies[d] = `^${v}`
return {
filepath: 'package.json',
content: JSON.stringify(pkg, null, 2),
}
}
async function getUserInfo() {
try {
const { stdout: username } = await execaCommand('git config --global user.name')
const { stdout: email } = await execaCommand('git config --global user.email')
return { username, email }
}
catch {
return null
}
}

165
cli/src/prompt.ts Normal file
View File

@ -0,0 +1,165 @@
import process from 'node:process'
import path from 'node:path'
import { createRequire } from 'node:module'
import { cancel, confirm, group, select, text } from '@clack/prompts'
import { setLang, t } from './translate.js'
import type { Bundler, Langs, Options, PromptResult } from './types.js'
import { DeployType, Mode, bundlerOptions, deployOptions, languageOptions } from './constants.js'
const require = createRequire(process.cwd())
const REG_DIR_CHAR = /[<>:"\\|?*[\]]/
export async function prompt(mode: Mode, root?: string): Promise<PromptResult> {
let hasTs = false
if (mode === Mode.init) {
try {
hasTs = !!require.resolve('typescript')
}
catch {}
}
const result: PromptResult = await group({
displayLang: async () => {
const lang = await select<Options<Langs>, Langs>({
message: 'Select a language to display / 选择显示语言',
options: languageOptions,
})
if (typeof lang === 'string')
setLang(lang)
return lang
},
root: async () => {
if (root)
return root
const DEFAULT_ROOT = mode === Mode.init ? './docs' : './my-project'
return await text({
message: t('question.root'),
placeholder: DEFAULT_ROOT,
validate(value) {
// not absolute path or parent path
if (value?.startsWith('/') || value?.startsWith('..'))
return t('hint.root')
// not contains illegal characters
if (value && REG_DIR_CHAR.test(value))
return t('hint.root.illegal')
return undefined
},
defaultValue: DEFAULT_ROOT,
})
},
siteName: () => text({
message: t('question.site.name'),
placeholder: 'My Vuepress Site',
defaultValue: 'My Vuepress Site',
}),
siteDescription: () => text({
message: t('question.site.description'),
}),
multiLanguage: () => confirm({
message: t('question.multiLanguage'),
initialValue: false,
}),
defaultLanguage: () => select<Options<Langs>, Langs>({
message: t('question.defaultLanguage'),
options: languageOptions,
}),
useTs: async () => {
if (mode === Mode.init)
return hasTs
if (hasTs)
return true
return await confirm({
message: t('question.useTs'),
initialValue: true,
})
},
injectNpmScripts: async () => {
if (mode === Mode.create)
return true
return await confirm({
message: t('question.injectNpmScripts'),
initialValue: true,
})
},
bundler: () => select<Options<Bundler>, Bundler>({
message: t('question.bundler'),
options: bundlerOptions,
}),
deploy: async () => {
if (mode === Mode.init) {
return DeployType.custom
}
return await select<Options<DeployType>, DeployType>({
message: t('question.deploy'),
options: deployOptions,
initialValue: DeployType.custom,
})
},
git: async () => {
if (mode === Mode.init)
return false
return confirm({
message: t('question.git'),
initialValue: true,
})
},
install: () => confirm({
message: t('question.installDeps'),
initialValue: true,
}),
}, {
onCancel: () => {
cancel(t('hint.cancel'))
process.exit(0)
},
})
return result
}
export async function getTargetDir(cwd: string, dir?: string) {
if (dir === '.')
return cwd
if (typeof dir === 'string' && dir) {
return path.resolve(cwd, dir)
}
const DEFAULT_DIR = 'my-project'
const dirPath = await text({
message: t('question.root'),
placeholder: DEFAULT_DIR,
validate(value) {
if (value && REG_DIR_CHAR.test(value))
return t('hint.root.illegal')
return undefined
},
defaultValue: DEFAULT_DIR,
})
if (typeof dirPath === 'string') {
if (dirPath === '.')
return cwd
return path.join(cwd, dirPath || DEFAULT_DIR)
}
return dirPath
}

40
cli/src/render.ts Normal file
View File

@ -0,0 +1,40 @@
import handlebars from 'handlebars'
import { kebabCase } from '@pengzhanbo/utils'
import type { ResolvedData } from './types.js'
export interface RenderData extends ResolvedData {
name: string
siteName: string
locales: { path: string, lang: string, isEn: boolean, prefix: string }[]
isEN: boolean
}
handlebars.registerHelper('removeLeadingSlash', (path: string) => path.replace(/^\//, ''))
handlebars.registerHelper('equal', (a: string, b: string) => a === b)
export function createRender(result: ResolvedData) {
const data: RenderData = {
...result,
name: kebabCase(result.siteName),
isEN: result.defaultLanguage === 'en-US',
locales: result.defaultLanguage === 'en-US'
? [
{ path: '/', lang: 'en-US', isEn: true, prefix: 'en' },
{ path: '/zh/', lang: 'zh-CN', isEn: false, prefix: 'zh' },
]
: [
{ path: '/', lang: 'zh-CN', isEn: false, prefix: 'zh' },
{ path: '/en/', lang: 'en-US', isEn: true, prefix: 'en' },
],
}
return function render(source: string): string {
try {
const template = handlebars.compile(source)
return template(data)
}
catch (e) {
console.error(e)
return source
}
}
}

54
cli/src/run.ts Normal file
View File

@ -0,0 +1,54 @@
import { intro, outro, spinner } from '@clack/prompts'
import { execaCommand } from 'execa'
import colors from 'picocolors'
import { prompt } from './prompt.js'
import { generate } from './generate.js'
import { t } from './translate.js'
import { Mode } from './constants.js'
import type { PromptResult, ResolvedData } from './types.js'
import { getPackageManager } from './utils/index.js'
export async function run(mode: Mode, root?: string) {
intro(colors.cyan('Welcome to VuePress and vuepress-theme-plume !'))
const result = await prompt(mode, root)
const data = resolveData(result, mode)
const progress = spinner()
progress.start(t('spinner.start'))
await generate(mode, data)
if (data.git) {
progress.message(t('spinner.git'))
await execaCommand('git init')
}
const pm = data.packageManager
if (data.install) {
progress.message(t('spinner.install'))
await execaCommand(pm === 'yarn' ? 'yarn' : `${pm} install`)
}
const cdCommand = mode === Mode.create ? colors.green(`cd ${data.root}`) : ''
const runCommand = colors.green(pm === 'yarn' ? 'yarn dev' : `${pm} run dev`)
const installCommand = colors.green(pm === 'yarn' ? 'yarn' : `${pm} install`)
progress.stop(t('spinner.stop'))
if (mode === Mode.create) {
outro(`${t('spinner.command')}
${cdCommand}
${data.install ? '' : `${installCommand} && `}${runCommand}`)
}
}
function resolveData(result: PromptResult, mode: Mode): ResolvedData {
return {
...result,
packageManager: getPackageManager(),
docsDir: mode === Mode.create ? 'docs' : result.root.replace(/^\.\//, '').replace(/\/$/, ''),
siteDescription: result.siteDescription || '',
}
}

15
cli/src/translate.ts Normal file
View File

@ -0,0 +1,15 @@
import type { Langs, Locale } from './types.js'
import { locales } from './locales/index.js'
function createTranslate(lang?: Langs) {
let current: Langs = lang || 'en-US'
return {
setLang: (lang: Langs) => {
current = lang
},
t: (key: keyof Locale) => locales[current][key],
}
}
export const { t, setLang } = createTranslate()

57
cli/src/types.ts Normal file
View File

@ -0,0 +1,57 @@
import type { DeployType } from './constants.js'
export type Langs = 'zh-CN' | 'en-US'
export interface Locale {
'question.root': string
'question.site.name': string
'question.site.description': string
'question.multiLanguage': string
'question.defaultLanguage': string
'question.bundler': string
'question.useTs': string
'question.injectNpmScripts': string
'question.git': string
'question.deploy': string
'question.installDeps': string
'spinner.start': string
'spinner.stop': string
'spinner.git': string
'spinner.install': string
'spinner.command': string
'hint.cancel': string
'hint.root': string
'hint.root.illegal': string
}
export type PackageManager = 'npm' | 'yarn' | 'pnpm'
export type Bundler = 'vite' | 'webpack'
export type Options<Value = string, Label = string> = { label: Label, value: Value }[]
export interface File {
filepath: string
content: string
}
export interface PromptResult {
displayLang: string // cli display language
root: string
siteName: string
siteDescription: string
bundler: Bundler
multiLanguage: boolean
defaultLanguage: Langs
useTs: boolean
injectNpmScripts: boolean
deploy: DeployType
git: boolean
install: boolean
}
export interface ResolvedData extends PromptResult {
packageManager: PackageManager
docsDir: string
}

View File

@ -0,0 +1,16 @@
export type DependencyVersion = 'latest' | 'next' | 'pre' | string
const api = 'https://api.pengzhanbo.cn/npm/dependencies/version'
export async function getDependenciesVersion(
dependencies: string[],
version: DependencyVersion = 'latest',
): Promise<Record<string, string>> {
const result = await fetch(api, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ dependencies, version }),
}).then(res => res.json())
return result
}

43
cli/src/utils/fs.ts Normal file
View File

@ -0,0 +1,43 @@
import path from 'node:path'
import fs from 'node:fs/promises'
import type { File } from '../types.js'
export async function readFiles(root: string): Promise<File[]> {
const filepaths = await fs.readdir(root, { recursive: true })
const files: File[] = []
for (const file of filepaths) {
const filepath = path.join(root, file)
if ((await fs.stat(filepath)).isFile()) {
files.push({
filepath: file,
content: await fs.readFile(filepath, 'utf-8'),
})
}
}
return files
}
export async function writeFiles(
files: File[],
target: string,
rewrite?: (path: string) => string,
) {
for (const { filepath, content } of files) {
let root = path.join(target, filepath).replace(/\.handlebars$/, '')
if (rewrite)
root = rewrite(root)
await fs.mkdir(path.dirname(root), { recursive: true })
await fs.writeFile(root, content)
}
}
export async function readJsonFile<T extends Record<string, any> = Record<string, any>>(filepath: string): Promise<T | null> {
try {
const content = await fs.readFile(filepath, 'utf-8')
return JSON.parse(content)
}
catch {
return null
}
}

View File

@ -0,0 +1,7 @@
import process from 'node:process'
import type { PackageManager } from '../types.js'
export function getPackageManager(): PackageManager {
const name = process.env?.npm_config_user_agent || 'npm'
return name.split('/')[0] as PackageManager
}

12
cli/src/utils/index.ts Normal file
View File

@ -0,0 +1,12 @@
import { fileURLToPath } from 'node:url'
import path from 'node:path'
export const __dirname = path.dirname(fileURLToPath(import.meta.url))
export const resolve = (...args: string[]) => path.resolve(__dirname, '../', ...args)
export const getTemplate = (dir: string) => resolve('templates', dir)
export * from './fs.js'
export * from './depsVersion.js'
export * from './getPackageManager.js'

View File

@ -0,0 +1,12 @@
import { defineClientConfig } from 'vuepress/client'
import RepoCard from 'vuepress-theme-plume/features/RepoCard.vue'
import CustomComponent from './theme/components/Custom.vue'
import './theme/styles/custom.css'
export default defineClientConfig({
enhance({ app }) {
app.component('RepoCard', RepoCard)
app.component('CustomComponent', CustomComponent)
},
})

View File

@ -0,0 +1,90 @@
import { defineUserConfig } from 'vuepress'
import { {{ bundler }}Bundler } from '@vuepress/bundler-{{ bundler }}'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
base: '/',
lang: '{{ defaultLanguage }}',
{{#if multiLanguage}}
locales: {
{{#each locales}}
'{{ path }}': {
title: '{{ ../siteName }}',
lang: '{{ lang }}',
description: '{{ ../siteDescription }}',
},
{{/each}}
},
{{else}}
title: '{{ siteName }}',
description: '{{ siteDescription }}',
{{/if}}
bundler: {{ bundler }}Bundler(),
theme: plumeTheme({
// 添加您的部署域名
// hostname: 'https://your_site_url',
plugins: {
/**
* Shiki 代码高亮
* @see https://theme-plume.vuejs.press/config/plugins/code-highlight/
*/
// shiki: {
// languages: ['shell', 'bash', 'typescript', 'javascript'],
// twoslash: true,
// },
/**
* markdown enhance
* @see https://theme-plume.vuejs.press/config/plugins/markdown-enhance/
*/
markdownEnhance: {
demo: true,
// include: true,
// chart: true,
// echarts: true,
// mermaid: true,
// flowchart: true,
},
/**
* markdown power
* @see https://theme-plume.vuejs.press/config/plugin/markdown-power/
*/
// markdownPower: {
// pdf: true,
// caniuse: true,
// plot: true,
// bilibili: true,
// youtube: true,
// icons: true,
// codepen: true,
// replit: true,
// codeSandbox: true,
// jsfiddle: true,
// repl: {
// go: true,
// rust: true,
// kotlin: true,
// },
// },
/**
* comments
* @see https://theme-plume.vuejs.press/guide/features/comments/
*/
// comment: {
// provider: '', // "Artalk" | "Giscus" | "Twikoo" | "Waline"
// comment: true,
// repo: '',
// repoId: '',
// categoryId: '',
// mapping: 'pathname',
// reactionsEnabled: true,
// inputPosition: 'top',
// },
},
}),
})

View File

@ -0,0 +1,28 @@
import { defineNavbarConfig } from 'vuepress-theme-plume'
{{#if multiLanguage}}
{{#each locales}}
export const {{prefix}}Navbar = defineNavbarConfig([
{ text: '{{#if isEn}}Home{{else}}首页{{/if}}', link: '{{ path }}' },
{ text: '{{#if isEn}}Blog{{else}}博客{{/if}}', link: '{{ path }}blog/' },
{ text: '{{#if isEn}}Tags{{else}}标签{{/if}}', link: '{{ path }}blog/tags/' },
{ text: '{{#if isEn}}Archives{{else}}归档{{/if}}', link: '{{ path }}blog/archives/' },
{
text: '{{#if isEn}}Notes{{else}}笔记{{/if}}',
items: [{ text: '{{#if isEn}}Demo{{else}}示例{{/if}}', link: '{{ path }}notes/demo/README.md' }]
},
])
{{/each}}
{{else}}
export const navbar = defineNavbarConfig([
{ text: '{{#if isEn}}Home{{else}}首页{{/if}}', link: '/' },
{ text: '{{#if isEn}}Blog{{else}}博客{{/if}}', link: '/blog/' },
{ text: '{{#if isEn}}Tags{{else}}标签{{/if}}', link: '/blog/tags/' },
{ text: '{{#if isEn}}Archives{{else}}归档{{/if}}', link: '/blog/archives/' },
{
text: '{{#if isEn}}Notes{{else}}笔记{{/if}}',
items: [{ text: '{{#if isEn}}Demo{{else}}示例{{/if}}', link: '/notes/demo/README.md' }]
},
])
{{/if}}

View File

@ -0,0 +1,32 @@
import { defineNoteConfig, defineNotesConfig } from 'vuepress-theme-plume'
{{#if multiLanguage}}
{{#each locales}}
/* =================== locale: {{ lang }} ======================= */
const {{ prefix }}DemoNote = defineNoteConfig({
dir: 'demo',
link: '/demo',
sidebar: ['', 'foo', 'bar'],
})
export const {{ prefix }}Notes = defineNotesConfig({
dir: '{{ removeLeadingSlash path }}notes',
link: '{{ path }}',
notes: [{{ prefix }}DemoNote],
})
{{/each}}
{{else}}
const demoNote = defineNoteConfig({
dir: 'demo',
link: '/demo',
sidebar: ['', 'foo', 'bar'],
})
export const notes = defineNotesConfig({
dir: 'notes',
link: '/',
notes: [demoNote],
})
{{/if}}

View File

@ -0,0 +1,57 @@
import { defineThemeConfig } from 'vuepress-theme-plume'
{{#if multiLanguage}}
import { enNavbar, zhNavbar } from './navbar'
import { enNotes, zhNotes } from './notes'
{{else}}
import { navbar } from './navbar'
import { notes } from './notes'
{{/if}}
/**
* @see https://theme-plume.vuejs.press/config/basic/
*/
export default defineThemeConfig({
logo: 'https://theme-plume.vuejs.press/plume.png',
// your git repo url
docsRepo: '',
docsDir: '{{ docsDir }}',
appearance: true,
{{#unless multiLanguage}}
profile: {
avatar: 'https://theme-plume.vuejs.press/plume.png',
name: '{{ siteName }}',
description: '{{ siteDescription }}',
// circle: true,
// location: '',
// organization: '',
},
navbar,
notes,
{{/unless}}
social: [
{ icon: 'github', link: '/' },
],
{{#if multiLanguage}}
locales: {
{{#each locales}}
'{{ path }}': {
profile: {
avatar: 'https://theme-plume.vuejs.press/plume.png',
name: '{{ ../siteName }}',
description: '{{ ../siteDescription }}',
// circle: true,
// location: '',
// organization: '',
},
navbar: {{ prefix }}Navbar,
notes: {{ prefix }}Notes,
},
{{/each}}
},
{{/if}}
})

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 72 72">
<path fill="#5086a1" d="M42.334 49.147a29.945 29.945 0 0 1-19.338-8.151c-8.014-7.365-8.378-18.076-8.533-22.649l-.022-.627a2.904 2.904 0 0 1 3.457-2.951c17.005 3.355 21.695 16.324 22.056 17.4a49.543 49.543 0 0 1 3.574 15.922a1 1 0 0 1-.967 1.052c-.029.001-.106.004-.227.004" />
<path fill="#8cccd5" d="M44.436 55.316c-11.646 0-17.376-6.974-17.653-7.354a1 1 0 0 1 .262-1.424a11.103 11.103 0 0 1 12.774-1.574c-1.465-9.078 1.877-13.568 2.031-13.77a.998.998 0 0 1 .75-.39a.97.97 0 0 1 .78.325c8.944 9.771 8.793 16.532 7.908 19.691c-.034.14-1.062 4.092-4.772 4.406c-.711.062-1.405.09-2.08.09" />
<g fill="none" stroke="#333" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
<path d="M55.184 57.69S34.96 45.877 23.097 24.206m22.131 30.096c-11.93.46-17.628-6.88-17.628-6.88" />
<path d="M40.528 42.483c-.56-7.195 2.116-10.679 2.116-10.679c8.834 9.654 8.406 16.162 7.681 18.747m-13.311-3.129a30.15 30.15 0 0 1-13.341-7.162c-8.072-7.419-8.067-18.241-8.232-22.577a1.903 1.903 0 0 1 2.264-1.932C34.694 19.103 39.02 32.528 39.02 32.528" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import { ref } from 'vue'
const message = ref('Hello World!')
</script>
<template>
<div class="my-custom-content">
{{ message }}
</div>
</template>

View File

@ -0,0 +1,6 @@
declare module '*.vue' {
import type { ComponentOptions } from 'vue'
const comp: ComponentOptions
export default comp
}

View File

@ -0,0 +1,50 @@
:root {
/** 主题颜色 */
/*
--vp-c-brand-1: #5086a1;
--vp-c-brand-2: #6aa1b7;
--vp-c-brand-3: #8cccd5;
--vp-c-brand-soft: rgba(131, 208, 218, 0.314);
*/
/** 背景颜色 */
/*
--vp-c-bg: #fff;
--vp-c-bg-alt: #f6f6f7;
--vp-c-bg-elv: #fff;
--vp-c-bg-soft: #f6f6f7;
*/
/** 文本颜色 */
/*
--vp-c-text-1: rgba(60, 60, 67);
--vp-c-text-2: rgba(60, 60, 67, 0.78);
--vp-c-text-3: rgba(60, 60, 67, 0.56);
*/
}
/** 深色模式 */
.dark {
/*
--vp-c-brand-1: #8cccd5;
--vp-c-brand-2: #6aa1b7;
--vp-c-brand-3: #5086a1;
--vp-c-brand-soft: rgba(131, 208, 218, 0.314);
*/
/*
--vp-c-bg: #1b1b1f;
--vp-c-bg-alt: #161618;
--vp-c-bg-elv: #202127;
--vp-c-bg-soft: #202127;
*/
/*
--vp-c-text-1: rgba(255, 255, 245, 0.86);
--vp-c-text-2: rgba(235, 235, 245, 0.6);
--vp-c-text-3: rgba(235, 235, 245, 0.38);
*/
}

View File

@ -0,0 +1,57 @@
# {{ name }}
The Site is generated using [vuepress](https://vuepress.vuejs.org/) and [vuepress-theme-plume](https://github.com/pengzhanbo/vuepress-theme-plume)
## Install
```sh
{{#if (equal packageManager "pnpm")}}
pnpm i
{{else if (equal packageManager "yarn")}}
yarn
{{else}}
npm i
{{/if}}
```
## Usage
{{#if (equal packageManager "pnpm")}}
```sh
# start dev server
pnpm docs:dev
# build for production
pnpm docs:build
# preview production build in local
pnpm docs:preview
# update vuepress and theme
pnpm vp-update
```
{{else if (equal packageManager "yarn")}}
```sh
# start dev server
yarn docs:dev
# build for production
yarn docs:build
# preview production build in local
yarn docs:preview
# update vuepress and theme
yarn vp-update
```
{{else}}
```sh
# start dev server
npm run docs:dev
# build for production
npm run docs:build
# preview production build in local
npm run docs:preview
# update vuepress and theme
npm run vp-update
```
{{/if}}
## Documents
- [vuepress](https://vuepress.vuejs.org/)
- [vuepress-theme-plume](https://theme-plume.vuejs.press/)

View File

@ -0,0 +1,57 @@
# {{ name }}
网站使用 [vuepress](https://vuepress.vuejs.org/) 和 [vuepress-theme-plume](https://github.com/pengzhanbo/vuepress-theme-plume) 构建生成。
## Install
```sh
{{#if (equal packageManager "pnpm")}}
pnpm i
{{else if (equal packageManager "yarn")}}
yarn
{{else}}
npm i
{{/if}}
```
## Usage
{{#if (equal packageManager "pnpm")}}
```sh
# 启动开发服务
pnpm docs:dev
# 构建生产包
pnpm docs:build
# 本地预览生产服务
pnpm docs:preview
# 更新 vuepress 和主题
pnpm vp-update
```
{{else if (equal packageManager "yarn")}}
```sh
# 启动开发服务
yarn docs:dev
# 构建生产包
yarn docs:build
# 本地预览生产服务
yarn docs:preview
# update vuepress and theme
yarn vp-update
```
{{else}}
```sh
# 启动开发服务
npm run docs:dev
# 构建生产包
npm run docs:build
# 本地预览生产服务
npm run docs:preview
# 更新 vuepress 和主题
npm run vp-update
```
{{/if}}
## 文档
- [vuepress](https://vuepress.vuejs.org/)
- [vuepress-theme-plume](https://theme-plume.vuejs.press/)

View File

@ -0,0 +1,70 @@
name: deploy
on:
# 每当 push 到 main 分支时触发部署
# Deployment is triggered whenever a push is made to the main branch.
push:
branches: [main]
# 手动触发部署
# Manually trigger deployment
workflow_dispatch:
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# “最近更新时间” 等 git 日志相关信息,需要拉取全部提交记录
# "Last updated time" and other git log-related information require fetching all commit records.
fetch-depth: 0
{{#if (equal packageManager "pnpm")}}
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
# 选择要使用的 pnpm 版本
version: 9
# 使用 pnpm 安装依赖
run_install: true
{{/if}}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
# 选择要使用的 node 版本
node-version: 20
{{#if (equal packageManager "yarn")}}
- name: Run install
uses: borales/actions-yarn@v4
with:
cmd: install
{{/if}}
# 运行构建脚本
# Run the build script
{{#unless (equal packageManager "yarn")}}
- name: Build VuePress site
run: {{packageManager}} run docs:build
{{/unless}}
{{#if (equal packageManager "yarn")}}
- name: Build VuePress site
uses: borales/actions-yarn@v4
with:
cmd: docs:build
{{/if}}
# 查看 workflow 的文档来获取更多信息
# @see https://github.com/crazy-max/ghaction-github-pages
- name: Deploy to GitHub Pages
uses: crazy-max/ghaction-github-pages@v4
with:
# 部署到 gh-pages 分支
target_branch: gh-pages
# 部署目录为 VuePress 的默认输出目录
build_dir: {{docsDir}}/.vuepress/dist
env:
# @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,9 @@
# prevent Netlify npm install
[build]
publish = "{{ docsDir }}/.vuepress/dist"
command = "{{#if (equal packageManager 'yarn')}}yarn && yarn{{else}}{{packageManager}} run{{/if}} docs:build"
[build.environment]
NODE_VERSION = "20"
NPM_FLAGS = "--version"

View File

@ -0,0 +1,6 @@
{
"framework": null,
"buildCommand": "{{#if (equal packageManager 'yarn')}}yarn{{else}}{{packageManager}} run{{/if}} docs:build",
"installCommand": "{{#if (equal packageManager 'yarn')}}yarn{{else}}{{packageManager}} install{{/if}}",
"outputDirectory": "{{ docsDir }}/.vuepress/dist"
}

View File

@ -0,0 +1,22 @@
---
pageLayout: home
externalLinkIcon: false
config:
-
type: hero
full: true
background: tint-plate
hero:
name: Theme Plume
tagline: VuePress Next Theme
text: A simple, feature-rich, document & blog
actions:
-
theme: brand
text: Blog
link: {{#if (equal defaultLanguage 'en-US')}}/{{else}}/en/{{/if}}blog/
-
theme: alt
text: Github →
link: https://github.com/pengzhanbo/vuepress-theme-plume
---

View File

@ -0,0 +1,6 @@
---
title: Demo
---
- [bar](./bar.md)
- [foo](./foo.md)

View File

@ -0,0 +1,5 @@
---
title: bar
---
[foo](./foo.md)

View File

@ -0,0 +1,5 @@
---
title: foo
---
[bar](./bar.md)

View File

@ -0,0 +1,8 @@
---
title: Custom Component
tags:
- preview
- component
---
<CustomComponent />

View File

@ -0,0 +1,312 @@
---
title: Markdown
tags:
- markdown
---
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
Bold: **Bold text**
Italic: _Italic text_
~~Deleted text~~
Content ==Highlight==
Mathematical expression: $-(2^{n-1})$ ~ $2^{n-1} -1$
$\frac {\partial^r} {\partial \omega^r} \left(\frac {y^{\omega}} {\omega}\right)
= \left(\frac {y^{\omega}} {\omega}\right) \left\{(\log y)^r + \sum_{i=1}^r \frac {(-1)^ Ir \cdots (r-i+1) (\log y)^{ri}} {\omega^i} \right\}$
19^th^
H~2~O
::: center
content center
:::
::: right
content right
:::
- Unordered List 1
- Unordered List 2
- Unordered List 3
1. Ordered List 1
2. Ordered List 2
3. Ordered List 3
- [ ] Task List 1
- [ ] Task List 2
- [x] Task List 3
- [x] Task List 4
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
> quote content
>
> quote content
[links](/)
[outside links](https://github.com/pengzhanbo)
**Badge**
- <Badge type="info" text="info badge" />
- <Badge type="tip" text="tip badge" />
- <Badge type="warning" text="warning badge" />
- <Badge type="danger" text="danger badge" />
**icons**
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" />
**demo wrapper**
::: demo-wrapper title="Demo" no-padding height="200px"
<style scoped>
.open-door {
display: flex;
gap: 20px;
padding: 20px;
}
.open-door .main {
background: #ccc;
}
</style>
<div class="open-door">
<div class="main">main</div>
<div class="aside">aside</div>
</div>
:::
**code block**
```js whitespace
const a = 1
const b = 2
const c = a + b
// [!code word:obj]
const obj = {
toLong: {
deep: {
deep: {
deep: {
value: 'this is to long text. this is to long text. this is to long text. this is to long text.', // [!code highlight]
}
}
}
}
}
```
**code groups**
::: code-tabs
@tab tab1
```js
const a = 1
const b = 2
const c = a + b
```
@tab tab2
```ts
const a: number = 1
const b: number = 2
const c: number = a + b
```
:::
**code highlight**
```ts
function foo() {
const a = 1 // [!code highlight]
console.log(a)
const b = 2 // [!code ++]
const c = 3 // [!code --]
console.log(a + b + c) // [!code error]
console.log(a + b) // [!code warning]
}
```
**code focus**
```ts
function foo() {
const a = 1 // [!code focus]
}
```
::: note
note content [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: info
content [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: tip
content [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: warning
content [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: caution
content [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: important
content [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
**GFM alert**
> [!note]
> note
> [!info]
> info
> [!tip]
> tip
> [!warning]
> warning
> [!caution]
> caution
> [!important]
> important
**code demo:**
::: normal-demo Demo 演示
```html
<h1>Hello Word!</h1>
<p><span id="very">Very</span>Powerful!</p>
```
```js
document.querySelector('#very').addEventListener('click', () => {
alert('Very Powerful')
})
```
```css
span {
color: red;
}
```
:::
**tab card**
::: tabs
@tab title 1
content block
@tab title 2
content block
:::
:::: warning
::: tabs
@tab title 1
content block
@tab title 2
content block
:::
::::
**footnote**
footnote 1 link[^first]。
footnote 2 link[^second]。
inline footnote ^[^first] definition。
Repeated footnote definition[^second]。
[^first]: footnote **you can contain special mark**
also can contain paragraph
[^second]: footnote content.

View File

@ -0,0 +1,22 @@
---
pageLayout: home
externalLinkIcon: false
config:
-
type: hero
full: true
background: tint-plate
hero:
name: Theme Plume
tagline: VuePress Next Theme
text: 一个简约的,功能丰富的 vuepress 文档&博客 主题
actions:
-
theme: brand
text: 博客
link: {{#if (equal defaultLanguage 'zh-CN')}}/{{else}}/zh/{{/if}}blog/
-
theme: alt
text: Github →
link: https://github.com/pengzhanbo/vuepress-theme-plume
---

View File

@ -0,0 +1,6 @@
---
title: Demo
---
- [bar](./bar.md)
- [foo](./foo.md)

View File

@ -0,0 +1,5 @@
---
title: bar
---
[foo](./foo.md)

View File

@ -0,0 +1,5 @@
---
title: foo
---
[bar](./bar.md)

View File

@ -0,0 +1,8 @@
---
title: 自定义组件
tags:
- 预览
- 组件
---
<CustomComponent />

View File

@ -0,0 +1,312 @@
---
title: Markdown
tags:
- markdown
---
## 标题 2
### 标题 3
#### 标题 4
##### 标题 5
###### 标题 6
加粗:**加粗文字**
斜体: _斜体文字_
~~删除文字~~
内容 ==标记==
数学表达式: $-(2^{n-1})$ ~ $2^{n-1} -1$
$\frac {\partial^r} {\partial \omega^r} \left(\frac {y^{\omega}} {\omega}\right)
= \left(\frac {y^{\omega}} {\omega}\right) \left\{(\log y)^r + \sum_{i=1}^r \frac {(-1)^ Ir \cdots (r-i+1) (\log y)^{ri}} {\omega^i} \right\}$
19^th^
H~2~O
::: center
内容居中
:::
::: right
内容右对齐
:::
- 无序列表1
- 无序列表2
- 无序列表3
1. 有序列表1
2. 有序列表2
3. 有序列表3
- [ ] 任务列表1
- [ ] 任务列表2
- [x] 任务列表3
- [x] 任务列表4
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
> 引用内容
>
> 引用内容
[链接](/)
[外部链接](https://github.com/pengzhanbo)
**Badge**
- <Badge type="info" text="info badge" />
- <Badge type="tip" text="tip badge" />
- <Badge type="warning" text="warning badge" />
- <Badge type="danger" text="danger badge" />
**图标:**
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" />
**demo wrapper**
::: demo-wrapper title="示例" no-padding height="200px"
<style scoped>
.open-door {
display: flex;
gap: 20px;
padding: 20px;
}
.open-door .main {
background: #ccc;
}
</style>
<div class="open-door">
<div class="main">main</div>
<div class="aside">aside</div>
</div>
:::
**代码:**
```js whitespace
const a = 1
const b = 2
const c = a + b
// [!code word:obj]
const obj = {
toLong: {
deep: {
deep: {
deep: {
value: 'this is to long text. this is to long text. this is to long text. this is to long text.', // [!code highlight]
}
}
}
}
}
```
**代码分组:**
::: code-tabs
@tab tab1
```js
const a = 1
const b = 2
const c = a + b
```
@tab tab2
```ts
const a: number = 1
const b: number = 2
const c: number = a + b
```
:::
**代码块高亮:**
```ts
function foo() {
const a = 1 // [!code highlight]
console.log(a)
const b = 2 // [!code ++]
const c = 3 // [!code --]
console.log(a + b + c) // [!code error]
console.log(a + b) // [!code warning]
}
```
**代码块聚焦:**
```ts
function foo() {
const a = 1 // [!code focus]
}
```
::: note 注释
注释内容 [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: info 信息
信息内容 [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: tip 提示
提示内容 [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: warning 警告
警告内容 [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: caution 错误
错误内容 [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: important 重要
重要内容 [link](https://github.com/pengzhanbo) `inline code`
```js
const a = 1
const b = 2
const c = a + b
```
:::
**GFM alert**
> [!note]
> note
> [!info]
> info
> [!tip]
> tip
> [!warning]
> warning
> [!caution]
> caution
> [!important]
> important
**代码演示:**
::: normal-demo Demo 演示
```html
<h1>Hello Word!</h1>
<p><span id="very">非常</span>强大!</p>
```
```js
document.querySelector('#very').addEventListener('click', () => {
alert('非常强大')
})
```
```css
span {
color: red;
}
```
:::
**选项卡:**
::: tabs
@tab 标题1
内容区块
@tab 标题2
内容区块
:::
:::: warning
::: tabs
@tab 标题1
内容区块
@tab 标题2
内容区块
:::
::::
**脚注:**
脚注 1 链接[^first]。
脚注 2 链接[^second]。
行内的脚注^[行内脚注文本] 定义。
重复的页脚定义[^second]。
[^first]: 脚注 **可以包含特殊标记**
也可以由多个段落组成
[^second]: 脚注文字。

View File

@ -0,0 +1,10 @@
* text eol=lf
*.txt text eol=crlf
*.png binary
*.jpg binary
*.jpeg binary
*.ico binary
*.tff binary
*.woff binary
*.woff2 binary

View File

@ -0,0 +1,8 @@
node_modules
{{ docsDir }}/.vuepress/.cache
{{ docsDir }}/.vuepress/.temp
{{ docsDir }}/.vuepress/dist
.DS_Store
*.log

15
cli/tsup.config.ts Normal file
View File

@ -0,0 +1,15 @@
import { defineConfig } from 'tsup'
import { version } from './package.json'
export default defineConfig({
entry: ['src/index.ts'],
outDir: 'lib',
dts: true,
format: 'esm',
sourcemap: false,
splitting: false,
clean: true,
define: {
__CLI_VERSION__: JSON.stringify(version),
},
})

View File

@ -9,7 +9,7 @@ const packages = fs.readdirSync(path.resolve(__dirname, 'plugins'))
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'scope-enum': [2, 'always', ['docs', 'theme', ...packages]],
'scope-enum': [2, 'always', ['docs', 'theme', 'cli', ...packages]],
'footer-max-line-length': [0],
},
}

84
pnpm-lock.yaml generated
View File

@ -72,6 +72,27 @@ importers:
specifier: ^8.0.0
version: 8.0.0
cli:
dependencies:
'@clack/prompts':
specifier: ^0.7.0
version: 0.7.0
'@pengzhanbo/utils':
specifier: ^1.1.2
version: 1.1.2
cac:
specifier: ^6.7.14
version: 6.7.14
execa:
specifier: ^9.3.1
version: 9.3.1
handlebars:
specifier: ^4.7.8
version: 4.7.8
picocolors:
specifier: ^1.0.1
version: 1.0.1
docs:
dependencies:
'@iconify/json':
@ -463,6 +484,14 @@ packages:
'@braintree/sanitize-url@6.0.4':
resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
'@clack/core@0.3.4':
resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==}
'@clack/prompts@0.7.0':
resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==}
bundledDependencies:
- is-unicode-supported
'@commitlint/cli@19.4.0':
resolution: {integrity: sha512-sJX4J9UioVwZHq7JWM9tjT5bgWYaIN3rC4FP7YwfEwBYiIO+wMyRttRvQLNkow0vCdM0D67r9NEWU0Ui03I4Eg==}
engines: {node: '>=v18'}
@ -2965,10 +2994,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
escalade@3.1.2:
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
engines: {node: '>=6'}
@ -3204,6 +3229,10 @@ packages:
resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==}
engines: {node: ^18.19.0 || >=20.5.0}
execa@9.3.1:
resolution: {integrity: sha512-gdhefCCNy/8tpH/2+ajP9IQc14vXchNdd0weyzSJEFURhRMGncQ+zKFxwjAufIewPEJm9BPOaJnvg2UtlH2gPQ==}
engines: {node: ^18.19.0 || >=20.5.0}
expand-tilde@2.0.2:
resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==}
engines: {node: '>=0.10.0'}
@ -3581,6 +3610,10 @@ packages:
resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==}
engines: {node: '>=18.18.0'}
human-signals@8.0.0:
resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==}
engines: {node: '>=18.18.0'}
husky@9.1.5:
resolution: {integrity: sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==}
engines: {node: '>=18'}
@ -4629,9 +4662,6 @@ packages:
resolution: {integrity: sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==}
engines: {node: '>= 0.12.0'}
picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@ -5868,6 +5898,17 @@ snapshots:
'@braintree/sanitize-url@6.0.4': {}
'@clack/core@0.3.4':
dependencies:
picocolors: 1.0.1
sisteransi: 1.0.5
'@clack/prompts@0.7.0':
dependencies:
'@clack/core': 0.3.4
picocolors: 1.0.1
sisteransi: 1.0.5
'@commitlint/cli@19.4.0(@types/node@20.12.10)(typescript@5.5.4)':
dependencies:
'@commitlint/format': 19.3.0
@ -5999,7 +6040,7 @@ snapshots:
'@conventional-changelog/git-client@1.0.0(conventional-commits-parser@6.0.0)':
dependencies:
'@types/semver': 7.5.8
semver: 7.6.0
semver: 7.6.3
optionalDependencies:
conventional-commits-parser: 6.0.0
@ -8496,8 +8537,6 @@ snapshots:
'@esbuild/win32-ia32': 0.23.1
'@esbuild/win32-x64': 0.23.1
escalade@3.1.1: {}
escalade@3.1.2: {}
escape-string-regexp@1.0.5: {}
@ -8838,6 +8877,21 @@ snapshots:
strip-final-newline: 4.0.0
yoctocolors: 2.0.0
execa@9.3.1:
dependencies:
'@sindresorhus/merge-streams': 4.0.0
cross-spawn: 7.0.3
figures: 6.1.0
get-stream: 9.0.1
human-signals: 8.0.0
is-plain-obj: 4.1.0
is-stream: 4.0.1
npm-run-path: 5.3.0
pretty-ms: 9.0.0
signal-exit: 4.1.0
strip-final-newline: 4.0.0
yoctocolors: 2.0.0
expand-tilde@2.0.2:
dependencies:
homedir-polyfill: 1.0.3
@ -9266,6 +9320,8 @@ snapshots:
human-signals@7.0.0: {}
human-signals@8.0.0: {}
husky@9.1.5: {}
iconv-lite@0.4.24:
@ -10481,8 +10537,6 @@ snapshots:
photoswipe@5.4.4: {}
picocolors@1.0.0: {}
picocolors@1.0.1: {}
picomatch@2.3.1: {}
@ -10570,7 +10624,7 @@ snapshots:
postcss@8.4.38:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.0
picocolors: 1.0.1
source-map-js: 1.2.0
postcss@8.4.40:
@ -11330,8 +11384,8 @@ snapshots:
update-browserslist-db@1.0.13(browserslist@4.23.0):
dependencies:
browserslist: 4.23.0
escalade: 3.1.1
picocolors: 1.0.0
escalade: 3.1.2
picocolors: 1.0.1
uri-js@4.4.1:
dependencies:

View File

@ -1,4 +1,5 @@
packages:
- docs
- theme
- cli
- plugins/*

View File

@ -1,4 +1,5 @@
import type { Page, Theme } from 'vuepress/core'
import { sleep } from '@pengzhanbo/utils'
import type { PlumeThemeOptions, PlumeThemePageData } from '../shared/index.js'
import { getPlugins } from './plugins/index.js'
import { extendsPageData, setupPage } from './setupPages.js'
@ -47,9 +48,10 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
},
})
waitForConfigLoaded().then(({ autoFrontmatter }) => {
waitForConfigLoaded().then(async ({ autoFrontmatter }) => {
autoFrontmatter ??= pluginOptions.frontmatter
if (autoFrontmatter !== false) {
await sleep(100)
generateAutoFrontmatter(app)
}
})

View File

@ -23,7 +23,7 @@
"plugins/**/*",
"theme/**/*",
"docs/.vuepress/**/*",
"scripts/**/*"
"cli/**/*"
],
"exclude": [
"**/node_modules/**",