UNPKG

create-vuepress-theme-plume

Version:

The cli for create vuepress-theme-plume's project

567 lines (547 loc) 18.2 kB
// src/index.ts import cac from "cac"; // src/constants.ts var languageOptions = [ { label: "English", value: "en-US" }, { label: "\u7B80\u4F53\u4E2D\u6587", value: "zh-CN" } ]; var bundlerOptions = [ { label: "Vite", value: "vite" }, { label: "Webpack", value: "webpack" } ]; var deployOptions = [ { label: "Custom", value: "custom" /* custom */ }, { label: "GitHub Pages", value: "github" /* github */ }, { label: "Vercel", value: "vercel" /* vercel */ }, { label: "Netlify", value: "netlify" /* netlify */ } ]; // src/run.ts import path4 from "node:path"; import process4 from "node:process"; import { intro, outro, spinner } from "@clack/prompts"; import { sleep } from "@pengzhanbo/utils"; import { execaCommand as execaCommand3 } from "execa"; import colors from "picocolors"; // src/generate.ts import fs2 from "node:fs"; import path3 from "node:path"; import process2 from "node:process"; import { execaCommand as execaCommand2 } from "execa"; // src/packageJson.ts import { kebabCase } from "@pengzhanbo/utils"; import { execaCommand } from "execa"; // src/utils/index.ts import path2 from "node:path"; import { fileURLToPath } from "node:url"; // src/utils/fs.ts import fs from "node:fs/promises"; import path from "node:path"; async function readFiles(root) { const filepaths = await fs.readdir(root, { recursive: true }); const files = []; 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; } async function writeFiles(files, target, rewrite) { 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); } } async function readJsonFile(filepath) { try { const content = await fs.readFile(filepath, "utf-8"); return JSON.parse(content); } catch { return null; } } // src/utils/getPackageManager.ts import process from "node:process"; function getPackageManager() { const name = process.env?.npm_config_user_agent || "npm"; return name.split("/")[0]; } // src/utils/index.ts var __dirname = path2.dirname(fileURLToPath(import.meta.url)); var resolve = (...args) => path2.resolve(__dirname, "../", ...args); var getTemplate = (dir) => resolve("templates", dir); // src/packageJson.ts async function createPackageJson(mode, pkg, { packageManager, docsDir, siteName, siteDescription, bundler, injectNpmScripts, useTs }) { if (mode === 1 /* create */) { pkg.name = kebabCase(siteName); pkg.type = "module"; pkg.version = "1.0.0"; pkg.description = siteDescription; if (packageManager !== "npm") { let version = await getPackageManagerVersion(packageManager); if (version) { if (packageManager === "yarn" && version.startsWith("1")) version = "4.6.0"; pkg.packageManager = `${packageManager}@${version}`; if (packageManager === "pnpm" && version.startsWith("10")) { pkg.pnpm = { onlyBuiltDependencies: ["@parcel/watcher", "esbuild"] }; } } } const userInfo = await getUserInfo(); if (userInfo) { pkg.author = userInfo.username + (userInfo.email ? ` <${userInfo.email}>` : ""); } pkg.license = "MIT"; pkg.engines = { node: "^18.19.0 || ^20.6.0 || >=22.0.0" }; } 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 === 1 /* create */) { pkg.scripts["vp-update"] = `${packageManager === "npm" ? "npx" : `${packageManager} dlx`} vp-update`; } } pkg.devDependencies ??= {}; const hasDep = (dep) => pkg.devDependencies?.[dep] || pkg.dependencies?.[dep]; const context = await readJsonFile(resolve("package.json")); const meta = context["plume-deps"]; pkg.devDependencies[`@vuepress/bundler-${bundler}`] = `${meta.vuepress}`; pkg.devDependencies.vuepress = `${meta.vuepress}`; pkg.devDependencies["vuepress-theme-plume"] = `${context.version}`; const deps = ["http-server"]; if (!hasDep("vue")) deps.push("vue"); if (bundler === "webpack" && !hasDep("sass-loader")) deps.push("sass-loader"); if (!hasDep("sass-embedded")) deps.push("sass-embedded"); if (useTs) deps.push("typescript"); for (const dep of deps) pkg.devDependencies[dep] = meta[dep]; 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; } } async function getPackageManagerVersion(pkg) { try { const { stdout } = await execaCommand(`${pkg} -v`); return stdout; } catch { return null; } } // src/render.ts import { kebabCase as kebabCase2 } from "@pengzhanbo/utils"; import handlebars from "handlebars"; handlebars.registerHelper("removeLeadingSlash", (path5) => path5.replace(/^\//, "")); handlebars.registerHelper("equal", (a, b) => a === b); function createRender(result) { const data = { ...result, name: kebabCase2(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) { try { const template = handlebars.compile(source); return template(data); } catch (e) { console.error(e); return source; } }; } // src/generate.ts async function generate(mode, data, cwd = process2.cwd()) { let userPkg = {}; if (mode === 0 /* init */) { const pkgPath = path3.join(cwd, "package.json"); if (fs2.existsSync(pkgPath)) { userPkg = await readJsonFile(pkgPath) || {}; } } const fileList = [ // 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`) ]; if (mode === 1 /* 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 execaCommand2("yarn --version"); if (yarnVersion.startsWith("2")) { fileList.push({ filepath: ".yarnrc.yml", content: "nodeLinker: 'node-modules'\n" }); } } } if (data.git) { const gitFiles = await readFiles(getTemplate("git")); if (mode === 0 /* init */) { const gitignorePath = path3.join(cwd, ".gitignore"); const docs = data.docsDir; if (fs2.existsSync(gitignorePath)) { const content = await fs2.promises.readFile(gitignorePath, "utf-8"); fileList.push({ filepath: ".gitignore", content: `${content} ${docs}/.vuepress/.cache ${docs}/.vuepress/.temp ${docs}/.vuepress/dist ` }); fileList.push(...gitFiles.filter(({ filepath }) => filepath !== ".gitignore")); } else { fileList.push(...gitFiles); } } else { fileList.push(...gitFiles); } } if (data.packageManager === "yarn") { fileList.push({ filepath: ".yarnrc.yml", content: "nodeLinker: 'node-modules'\n" }); } if (data.deploy !== "custom" /* 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 === 1 /* create */ ? path3.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) { const fileList = []; 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, target) { return fileList.map(({ filepath, content }) => ({ filepath: path3.join(target, filepath), content })); } // src/prompt.ts import { createRequire } from "node:module"; import process3 from "node:process"; import { cancel, confirm, group, select, text } from "@clack/prompts"; import { osLocale } from "os-locale"; // src/locales/en.ts var en = { "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": "\u{1F680} Creating...", "spinner.stop": "\u{1F389} Create success!", "spinner.git": "\u{1F4C4} Initializing git repository...", "spinner.install": "\u{1F4E6} Installing dependencies...", "spinner.command": "\u{1F528} 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." }; // src/locales/zh.ts var zh = { "question.root": "\u60A8\u60F3\u5728\u54EA\u91CC\u521D\u59CB\u5316 VuePress\uFF1F", "question.site.name": "\u7AD9\u70B9\u540D\u79F0\uFF1A", "question.site.description": "\u7AD9\u70B9\u63CF\u8FF0\u4FE1\u606F\uFF1A", "question.bundler": "\u8BF7\u9009\u62E9\u6253\u5305\u5DE5\u5177", "question.multiLanguage": "\u662F\u5426\u4F7F\u7528\u591A\u8BED\u8A00\uFF1F", "question.defaultLanguage": "\u8BF7\u9009\u62E9\u7AD9\u70B9\u9ED8\u8BA4\u8BED\u8A00", "question.useTs": "\u662F\u5426\u4F7F\u7528 TypeScript\uFF1F", "question.injectNpmScripts": "\u662F\u5426\u6CE8\u5165 npm \u811A\u672C\uFF1F", "question.deploy": "\u90E8\u7F72\u65B9\u5F0F\uFF1A", "question.git": "\u662F\u5426\u521D\u59CB\u5316 git \u4ED3\u5E93\uFF1F", "question.installDeps": "\u662F\u5426\u5B89\u88C5\u4F9D\u8D56\uFF1F", "spinner.start": "\u{1F680} \u6B63\u5728\u521B\u5EFA...", "spinner.stop": "\u{1F389} \u521B\u5EFA\u6210\u529F!", "spinner.git": "\u{1F4C4} \u521D\u59CB\u5316 git \u4ED3\u5E93...", "spinner.install": "\u{1F4E6} \u5B89\u88C5\u4F9D\u8D56...", "spinner.command": "\u{1F528} \u6267\u884C\u4EE5\u4E0B\u547D\u4EE4\u5373\u53EF\u542F\u52A8\uFF1A", "hint.cancel": "\u64CD\u4F5C\u5DF2\u53D6\u6D88\u3002", "hint.root": "\u6587\u4EF6\u8DEF\u5F84\u4E0D\u80FD\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4E0D\u80FD\u5305\u542B\u7236\u8DEF\u5F84\u3002", "hint.root.illegal": "\u6587\u4EF6\u5939\u4E0D\u80FD\u5305\u542B\u7279\u6B8A\u5B57\u7B26\u3002" }; // src/locales/index.ts var locales = { "zh-CN": zh, "en-US": en }; // src/translate.ts function createTranslate(lang) { let current = lang || "en-US"; return { setLang: (lang2) => { current = lang2; }, t: (key) => locales[current][key] }; } var { t, setLang } = createTranslate(); // src/prompt.ts var require2 = createRequire(process3.cwd()); var REG_DIR_CHAR = /[<>:"\\|?*[\]]/; async function prompt(mode, root) { let hasTs = false; if (mode === 0 /* init */) { try { hasTs = !!require2.resolve("typescript"); } catch { } } const result = await group({ displayLang: async () => { const locale = await osLocale(); if (locale === "zh-CN" || locale === "zh-Hans") { setLang("zh-CN"); return "zh-CN"; } if (locale === "en-US") { setLang("en-US"); return "en-US"; } const lang = await select({ message: "Select a language to display / \u9009\u62E9\u663E\u793A\u8BED\u8A00", options: languageOptions }); if (typeof lang === "string") setLang(lang); return lang; }, root: async () => { if (root) return root; const DEFAULT_ROOT = mode === 0 /* init */ ? "./docs" : "./my-project"; return await text({ message: t("question.root"), placeholder: DEFAULT_ROOT, validate(value) { if (value?.startsWith("/") || value?.startsWith("..")) return t("hint.root"); if (value && REG_DIR_CHAR.test(value)) return t("hint.root.illegal"); return void 0; }, 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({ message: t("question.defaultLanguage"), options: languageOptions }), useTs: async () => { if (mode === 0 /* init */) return hasTs; if (hasTs) return true; return await confirm({ message: t("question.useTs"), initialValue: true }); }, injectNpmScripts: async () => { if (mode === 1 /* create */) return true; return await confirm({ message: t("question.injectNpmScripts"), initialValue: true }); }, bundler: () => select({ message: t("question.bundler"), options: bundlerOptions }), deploy: async () => { if (mode === 0 /* init */) { return "custom" /* custom */; } return await select({ message: t("question.deploy"), options: deployOptions, initialValue: "custom" /* custom */ }); }, git: async () => { if (mode === 0 /* init */) return false; return confirm({ message: t("question.git"), initialValue: true }); }, install: () => confirm({ message: t("question.installDeps"), initialValue: true }) }, { onCancel: () => { cancel(t("hint.cancel")); process3.exit(0); } }); return result; } // src/run.ts async function run(mode, root) { 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")); try { await generate(mode, data); } catch (e) { console.error(`${colors.red("generate files error: ")} `, e); process4.exit(1); } await sleep(200); const cwd = path4.join(process4.cwd(), data.root); if (data.git) { progress.message(t("spinner.git")); try { await execaCommand3("git init", { cwd }); } catch (e) { console.error(`${colors.red("git init error: ")} `, e); process4.exit(1); } } const pm = data.packageManager; if (data.install) { progress.message(t("spinner.install")); try { await execaCommand3(`${pm} install`, { cwd }); } catch (e) { console.error(`${colors.red("install dependencies error: ")} `, e); process4.exit(1); } } const cdCommand = mode === 1 /* create */ ? colors.green(`cd ${data.root}`) : ""; const runCommand = colors.green(`${pm} run docs:dev`); const installCommand = colors.green(`${pm} install`); progress.stop(t("spinner.stop")); if (mode === 1 /* create */) { outro(`${t("spinner.command")} ${cdCommand} ${data.install ? "" : `${installCommand} && `}${runCommand}`); } } function resolveData(result, mode) { return { ...result, packageManager: getPackageManager(), docsDir: mode === 1 /* create */ ? "docs" : result.root.replace(/^\.\//, "").replace(/\/$/, ""), siteDescription: result.siteDescription || "" }; } // src/index.ts var cli = cac("create-vuepress-theme-plume"); cli.command("[root]", "create a new vuepress-theme-plume project / \u521B\u5EFA\u65B0\u7684 vuepress-theme-plume \u9879\u76EE").action((root) => run(1 /* create */, root)); cli.command("init [root]", "Initial vuepress-theme-plume in the existing project / \u5728\u73B0\u6709\u9879\u76EE\u4E2D\u521D\u59CB\u5316 vuepress-theme-plume").action((root) => run(0 /* init */, root)); cli.help(); cli.version("1.0.0-rc.142"); cli.parse();