UNPKG

@commerce-kick/create

Version:

CLI tool to create projects from your commerce-kick templates

250 lines (249 loc) • 9.13 kB
#!/usr/bin/env node import * as p from "@clack/prompts"; import chalk from "chalk"; import { execSync } from "child_process"; import { Command } from "commander"; import fs from "fs-extra"; import path from "path"; const program = new Command(); const TEMPLATES = { "start-kit": { name: "[web] Start Kit", repo: "https://github.com/FerVillanuevas/start-kit", description: "Tanstack Start, Vite, and Tailwind CSS", }, "native-kit": { name: "[mobile] Commerce Kit", repo: "https://github.com/FerVillanuevas/mob-kit", description: "Build with love and Expo", }, }; async function main() { p.intro(chalk.blue.bold("šŸš€ Welcome to commerce-kick Generator!")); program .name("create") .description("CLI tool to create projects from commerce-kick templates") .version("1.0.0") .argument("[project-name]", "Name of the project") .option("-t, --template <template>", "Template to use") .option("-p, --package-manager <manager>", "Package manager to use (npm, yarn, pnpm, bun)") .option("--no-install", "Skip dependency installation") .option("--no-git", "Skip git initialization") .action(async (projectName, options) => { await createProject(projectName, options); }); program.parse(); } async function createProject(projectName, cmdOptions = {}) { try { const options = await promptForOptions(projectName, cmdOptions); await scaffoldProject(options); } catch (error) { p.log.error(chalk.red("Error creating project:") + error.message); process.exit(1); } } async function promptForOptions(projectName, cmdOptions = {}) { const answers = {}; // Project name if (!projectName) { const projectNameValue = await p.text({ message: "What is your project name?", placeholder: "my-project", validate: (input) => { if (!input.trim()) return "Project name is required"; if (!/^[a-zA-Z0-9-_]+$/.test(input)) { return "Project name can only contain letters, numbers, hyphens, and underscores"; } return undefined; }, }); if (p.isCancel(projectNameValue)) { p.cancel("Operation cancelled."); process.exit(0); } answers.projectName = projectNameValue; } // Template selection if (!cmdOptions.template) { const templateValue = await p.select({ message: "Which template would you like to use?", options: Object.entries(TEMPLATES).map(([key, template]) => ({ value: key, label: `${template.name} - ${template.description}`, })), }); if (p.isCancel(templateValue)) { p.cancel("Operation cancelled."); process.exit(0); } answers.template = templateValue; } // Package manager selection if (!cmdOptions.packageManager) { const packageManagerValue = await p.select({ message: "Which package manager would you like to use?", options: [ { value: "npm", label: "npm" }, { value: "yarn", label: "yarn" }, { value: "pnpm", label: "pnpm" }, { value: "bun", label: "bun" }, ], }); if (p.isCancel(packageManagerValue)) { p.cancel("Operation cancelled."); process.exit(0); } answers.packageManager = packageManagerValue; } else { answers.packageManager = cmdOptions.packageManager; } // Additional options const installValue = await p.confirm({ message: "Install dependencies?", initialValue: cmdOptions.install !== false, }); if (p.isCancel(installValue)) { p.cancel("Operation cancelled."); process.exit(0); } answers.installDeps = installValue; const gitValue = await p.confirm({ message: "Initialize git repository?", initialValue: cmdOptions.git !== false, }); if (p.isCancel(gitValue)) { p.cancel("Operation cancelled."); process.exit(0); } answers.gitInit = gitValue; return { projectName: projectName || answers.projectName, template: cmdOptions.template || answers.template, packageManager: answers.packageManager, installDeps: answers.installDeps, gitInit: answers.gitInit, }; } async function scaffoldProject(options) { const { projectName, template, installDeps, gitInit } = options; const templateConfig = TEMPLATES[template]; if (!templateConfig) { throw new Error(`Template "${template}" not found`); } const targetDir = path.resolve(process.cwd(), projectName); // Check if directory already exists if (await fs.pathExists(targetDir)) { const overwrite = await p.confirm({ message: `Directory "${projectName}" already exists. Overwrite?`, initialValue: false, }); if (p.isCancel(overwrite)) { p.cancel("Operation cancelled."); process.exit(0); } if (!overwrite) { p.log.warn(chalk.yellow("Operation cancelled.")); return; } await fs.remove(targetDir); } p.log.info(chalk.blue(`\nCreating project "${projectName}" using ${templateConfig.name}...\n`)); // Clone template repository const cloneSpinner = p.spinner(); cloneSpinner.start("Downloading template..."); try { execSync(`git clone ${templateConfig.repo} "${targetDir}"`, { stdio: "pipe", }); // Remove .git directory await fs.remove(path.join(targetDir, ".git")); cloneSpinner.stop("Template downloaded successfully"); } catch (error) { cloneSpinner.stop("Failed to download template"); throw error; } // Update package.json const packageJsonPath = path.join(targetDir, "package.json"); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); packageJson.name = projectName; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); } // Install dependencies if (installDeps) { const { packageManager } = options; const installSpinner = p.spinner(); installSpinner.start(`Installing dependencies with ${packageManager}...`); try { // Define install commands for different package managers const installCommands = { npm: "npm install", yarn: "yarn", pnpm: "pnpm install", bun: "bun install", }; const installCmd = installCommands[packageManager] || "npm install"; execSync(installCmd, { cwd: targetDir, stdio: "pipe", }); installSpinner.stop("Dependencies installed successfully"); } catch (error) { installSpinner.stop(`Failed to install dependencies with ${packageManager}`); p.log.warn(chalk.yellow(`You can install them manually later with: ${packageManager} install`)); } } // Initialize git repository if (gitInit) { const gitSpinner = p.spinner(); gitSpinner.start("Initializing git repository..."); try { execSync("git init", { cwd: targetDir, stdio: "pipe" }); execSync("git add .", { cwd: targetDir, stdio: "pipe" }); execSync('git commit -m "Initial commit"', { cwd: targetDir, stdio: "pipe", }); gitSpinner.stop("Git repository initialized"); } catch (error) { gitSpinner.stop("Failed to initialize git repository"); p.log.warn(chalk.yellow("You can initialize git manually later")); } } // Success message p.log.success(chalk.green.bold("\nšŸŽ‰ Project created successfully!\n")); p.log.info(chalk.cyan("Next steps:")); p.log.message(chalk.white(` cd ${projectName}`)); const { packageManager } = options; if (!installDeps) { // Show appropriate install command based on package manager const installCommands = { npm: "npm install", yarn: "yarn", pnpm: "pnpm install", bun: "bun install", }; const installCmd = installCommands[packageManager] || "npm install"; p.log.message(chalk.white(` ${installCmd}`)); } // Show appropriate run command based on package manager const runCommands = { npm: "npm run dev", yarn: "yarn dev", pnpm: "pnpm dev", bun: "bun dev", }; const runCmd = runCommands[packageManager] || "npm run dev"; p.log.message(chalk.white(` ${runCmd}\n`)); p.outro(chalk.gray("Happy coding! šŸš€")); } main().catch(console.error);