UNPKG

@twotwoba/vv-cli

Version:

Easily create Vite + React19/Vue3 web/h5/mini-program/chrome-extension projects.

190 lines (189 loc) 8.02 kB
#!/usr/bin/env node import { program } from "commander"; import inquirer from "inquirer"; import fse from "fs-extra"; import path from "path"; import chalk from "chalk"; import ora from "ora"; import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Template paths const templatePathVue = path.resolve(__dirname, "../template-vue"); const templatePathVueMobile = path.resolve(__dirname, "../template-vue-mobile"); const templatePathReact = path.resolve(__dirname, "../template-react"); const templatePathExtension = path.resolve(__dirname, "../template-extension"); const templatePathUiLib = path.resolve(__dirname, "../template-ui-lib"); const templatePathUniapp = path.resolve(__dirname, "../template-uniapp"); // Read package.json to get version const packageJson = JSON.parse(fse.readFileSync(path.resolve(__dirname, "../package.json"), "utf8")); const templateChoices = [ { name: "Vue3 + Unocss (Desktop)", value: "vue-desktop" }, { name: "Vue3 + Unocss (Mobile)", value: "vue-mobile" }, { name: "Vue3 + Unocss (Uniapp)", value: "uniapp" }, { name: "React19 + tailwindcss(Desktop)", value: "react-desktop" }, { name: "React19 + tailwindcss(Chrome Extension)", value: "chrome-extension" }, { name: "Vue3 UI Component Library (pnpm monorepo)", value: "ui-lib" } ]; const templatePaths = { "vue-desktop": templatePathVue, "vue-mobile": templatePathVueMobile, uniapp: templatePathUniapp, "react-desktop": templatePathReact, "chrome-extension": templatePathExtension, "ui-lib": templatePathUiLib }; /** * Validate project name * - Must start with a letter, number, or @ * - Can only contain letters, numbers, hyphens, underscores, dots, and slashes (for scoped packages) * - Cannot be empty */ function validateProjectName(name) { if (!name || name.trim() === "") { return "Project name is required"; } // Check for invalid characters const validNameRegex = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i; if (!validNameRegex.test(name)) { return "Project name can only contain letters, numbers, hyphens, underscores, dots, and @ for scoped packages"; } // Check if directory already exists if (fse.existsSync(name)) { return `Directory "${name}" already exists`; } return true; } /** * Main CLI action */ async function createProject(projectName, options) { try { // If project name is not provided, ask for it if (!projectName) { const answer = await inquirer.prompt([ { type: "input", name: "projectName", message: "Please enter your project name:", validate: validateProjectName } ]); projectName = answer.projectName; } else { // Validate provided project name const validationResult = validateProjectName(projectName); if (validationResult !== true) { console.error(chalk.red(`Error: ${validationResult}`)); process.exit(1); } } let template; // Check if template is provided via CLI option if (options?.template) { const validTemplates = [ "vue-desktop", "vue-mobile", "uniapp", "react-desktop", "chrome-extension", "ui-lib" ]; if (!validTemplates.includes(options.template)) { console.error(chalk.red(`Error: Invalid template "${options.template}". Valid options: ${validTemplates.join(", ")}`)); process.exit(1); } template = options.template; } else { // Ask for template type interactively const answer = await inquirer.prompt([ { type: "list", name: "template", message: "Select a template:", choices: templateChoices } ]); template = answer.template; } const templatePath = templatePaths[template]; const targetPath = path.resolve(process.cwd(), projectName); // Start spinner for copying files const spinner = ora("Creating project directory...").start(); try { // Copy template files await fse.copy(templatePath, targetPath, { filter: (src) => { const basename = path.basename(src); return basename !== "node_modules" && basename !== ".git"; } }); spinner.succeed("Project files copied"); // Rename gitignore to .gitignore spinner.start("Configuring project..."); const gitignorePath = path.join(targetPath, "gitignore"); const dotGitignorePath = path.join(targetPath, ".gitignore"); if (await fse.pathExists(gitignorePath)) { await fse.rename(gitignorePath, dotGitignorePath); } // Update package.json const pkgPath = path.join(targetPath, "package.json"); const pkg = (await fse.readJson(pkgPath)); pkg.name = projectName; // For Chrome Extension template, also update description if (template === "chrome-extension") { pkg.description = `A Chrome Extension for ${projectName}`; } await fse.writeJson(pkgPath, pkg, { spaces: 2 }); spinner.succeed("Project configured"); } catch (error) { spinner.fail("Failed to create project"); throw error; } // Success message console.log(chalk.green("\n✨ Project created successfully!\n")); console.log("Next steps:"); console.log(chalk.cyan(` cd ${projectName}`)); console.log(chalk.cyan(" pnpm install")); console.log(chalk.cyan(" pnpm dev")); if (template === "chrome-extension") { console.log("\n📌 Chrome Extension Development:"); console.log(chalk.yellow(" 1. Run 'pnpm dev' to build the extension")); console.log(chalk.yellow(" 2. Open Chrome and go to chrome://extensions/")); console.log(chalk.yellow(" 3. Enable 'Developer mode'")); console.log(chalk.yellow(" 4. Click 'Load unpacked' and select the 'dist' folder")); console.log(chalk.yellow(" 5. Your extension will be loaded and ready for development!")); } if (template === "ui-lib") { console.log("\n📦 UI Component Library Development:"); console.log(chalk.yellow(" • 'pnpm dev' - Start the playground for component development")); console.log(chalk.yellow(" • 'pnpm build' - Build the component library")); console.log(chalk.yellow(" • 'pnpm build:all' - Build all packages")); console.log(chalk.yellow(" • Components are in packages/components/src/")); console.log(chalk.yellow(" • Test components in the playground/")); } console.log(""); } catch (error) { if (error instanceof Error) { console.error(chalk.red("Error creating project:"), error.message); } else { console.error(chalk.red("Error creating project:"), error); } process.exit(1); } } // Setup CLI program .name("vv") .description("CLI tool for creating Vue3/React + Vite projects, Chrome Extensions, and UI Component Libraries") .version(packageJson.version); program .argument("[project-name]", "Name of the project") .option("-t, --template <template>", "Template to use (vue, vue-mobile, uniapp, react, extension, ui-lib)") .action(createProject); program.parse();