create-vuepress-theme-plume
Version:
The cli for create vuepress-theme-plume's project
567 lines (547 loc) • 18.2 kB
JavaScript
// 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();