@commerce-kick/create
Version:
CLI tool to create projects from your commerce-kick templates
250 lines (249 loc) ⢠9.13 kB
JavaScript
#!/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);