UNPKG

create-rari-app

Version:

Create Runtime Accelerated Rendering Infrastructure (Rari) applications with no build configuration

156 lines (154 loc) 4.77 kB
#!/usr/bin/env node import { spawn } from "node:child_process"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import process from "node:process"; import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts"; import pc from "picocolors"; //#region src/index.ts const templates = { default: { name: "Default", description: "A clean starter with React Server Components" } }; const packageManagers = { pnpm: "pnpm", npm: "npm", yarn: "yarn", bun: "bun" }; async function main() { intro(pc.bgCyan(pc.black(" create-rari-app "))); const projectName = await text({ message: "What is your project named?", placeholder: "my-rari-app", validate: (value) => { if (!value) return "Please enter a project name."; if (value.includes(" ")) return "Project name cannot contain spaces."; if (!/^[\w-]+$/.test(value)) return "Project name can only contain letters, numbers, hyphens, and underscores."; } }); if (isCancel(projectName)) { cancel("Operation cancelled."); process.exit(0); } const template = await select({ message: "Which template would you like to use?", options: Object.entries(templates).map(([key, { name, description }]) => ({ value: key, label: name, hint: description })) }); if (isCancel(template)) { cancel("Operation cancelled."); process.exit(0); } const packageManager = await select({ message: "Which package manager would you like to use?", options: Object.entries(packageManagers).map(([key, value]) => ({ value: key, label: value })) }); if (isCancel(packageManager)) { cancel("Operation cancelled."); process.exit(0); } const installDeps = await confirm({ message: "Install dependencies?", initialValue: true }); if (isCancel(installDeps)) { cancel("Operation cancelled."); process.exit(0); } const options = { name: projectName, template, packageManager, installDeps }; await createProject(options); outro(pc.green("🎉 Project created successfully!")); console.warn(); console.warn(pc.cyan("Next steps:")); console.warn(pc.gray(` cd ${options.name}`)); if (!options.installDeps) console.warn(pc.gray(` ${options.packageManager} install`)); console.warn(pc.gray(` ${options.packageManager} run dev`)); console.warn(); } async function createProject(options) { const projectPath = join(process.cwd(), options.name); const templatePath = join(import.meta.dirname, "..", "templates", options.template); const s = spinner(); try { s.start("Creating project structure..."); await mkdir(projectPath, { recursive: true }); await copyTemplate(templatePath, projectPath, options); s.stop("Project structure created."); if (options.installDeps) { s.start("Installing dependencies..."); await installDependencies(projectPath, options.packageManager); s.stop("Dependencies installed."); } } catch (error) { s.stop("Error occurred."); throw error; } } async function copyTemplate(templatePath, projectPath, options) { const templateFiles = [ "package.json", "vite.config.ts", "tsconfig.json", "tsconfig.app.json", "tsconfig.node.json", "index.html", "README.md", "railway.toml", "render.yaml", "src/main.tsx", "src/App.tsx", "src/vite-env.d.ts", "src/styles/index.css", "src/components/Welcome.tsx", "src/components/ServerTime.tsx", "src/pages/index.tsx", "src/pages/about.tsx", "gitignore" ]; await mkdir(join(projectPath, "src", "components"), { recursive: true }); await mkdir(join(projectPath, "src", "styles"), { recursive: true }); await mkdir(join(projectPath, "src", "pages"), { recursive: true }); for (const file of templateFiles) { const sourcePath = join(templatePath, file); const destFile = file === "gitignore" ? ".gitignore" : file; const destPath = join(projectPath, destFile); try { let content = await readFile(sourcePath, "utf-8"); content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.name).replace(/\{\{PACKAGE_MANAGER\}\}/g, options.packageManager); await mkdir(dirname(destPath), { recursive: true }); await writeFile(destPath, content); } catch (error) { console.warn(`Warning: Could not copy ${file}:`, error); } } } async function installDependencies(projectPath, packageManager) { return new Promise((resolve, reject) => { const child = spawn(packageManager, ["install"], { cwd: projectPath, stdio: "pipe" }); child.on("close", (code) => { if (code === 0) resolve(); else reject(/* @__PURE__ */ new Error(`${packageManager} install failed with code ${code}`)); }); child.on("error", reject); }); } main().catch((error) => { console.error(pc.red("Error:"), error.message); process.exit(1); }); //#endregion