the best lightweight web dev stack built on bun
at main 4.0 kB view raw
1#!/usr/bin/env bun 2/** 3 * Tacy Stack CLI 4 * Interactive project scaffolder 5 */ 6 7import { $ } from "bun"; 8import { existsSync, mkdirSync } from "node:fs"; 9import { join } from "node:path"; 10import * as p from "@clack/prompts"; 11import { setTimeout } from "node:timers/promises"; 12import { version } from "./package.json"; 13 14async function main() { 15 console.clear(); 16 17 p.intro(`🥞 Tacy Stack Generator @ ${version}`); 18 19 // Get project name 20 const projectName = await p.text({ 21 message: "What is your project name?", 22 placeholder: "my-app", 23 validate(value) { 24 if (!value) return "Project name is required"; 25 if (!/^[a-z0-9-_]+$/i.test(value)) { 26 return "Project name can only contain letters, numbers, hyphens, and underscores"; 27 } 28 const targetDir = join(process.cwd(), value); 29 if (existsSync(targetDir)) { 30 return `Directory "${value}" already exists!`; 31 } 32 }, 33 }); 34 35 if (p.isCancel(projectName)) { 36 p.cancel("Operation cancelled"); 37 process.exit(0); 38 } 39 40 const targetDir = join(process.cwd(), projectName as string); 41 const templateDir = import.meta.dir; 42 43 const s = p.spinner(); 44 45 try { 46 // Create directory 47 s.start("Creating project directory"); 48 mkdirSync(targetDir, { recursive: true }); 49 await setTimeout(200); 50 s.stop("Created project directory"); 51 52 // Copy template files 53 s.start("Copying template files"); 54 await $`cp -r ${templateDir}/* ${targetDir}/`.quiet(); 55 56 // Copy dotfiles explicitly 57 const dotfiles = [".env.example", ".gitignore", ".gitattributes"]; 58 for (const dotfile of dotfiles) { 59 const source = join(templateDir, dotfile); 60 const dest = join(targetDir, dotfile); 61 if (existsSync(source)) { 62 await Bun.write(dest, Bun.file(source)); 63 } 64 } 65 66 // Copy .github directory if it exists 67 const githubDir = join(templateDir, ".github"); 68 if (existsSync(githubDir)) { 69 await $`cp -r ${githubDir} ${targetDir}/.github`.quiet(); 70 } 71 72 await setTimeout(200); 73 s.stop("Copied template files"); 74 75 // Remove CLI and template files 76 s.start("Cleaning up template files"); 77 const filesToRemove = [ 78 "cli.ts", 79 "TEMPLATE.md", 80 "TEMPLATE_SETUP_SUMMARY.md", 81 "CLI_SUMMARY.md", 82 "PUBLISHING.md", 83 "template.toml", 84 ".github/TEMPLATE_SETUP.md", 85 ]; 86 87 for (const file of filesToRemove) { 88 const filePath = join(targetDir, file); 89 if (existsSync(filePath)) { 90 await $`rm -rf ${filePath}`.quiet(); 91 } 92 } 93 await setTimeout(200); 94 s.stop("Cleaned up template files"); 95 96 // Update package.json 97 s.start("Configuring package.json"); 98 const packageJsonPath = join(targetDir, "package.json"); 99 const packageJson = await Bun.file(packageJsonPath).json(); 100 packageJson.name = projectName; 101 packageJson.version = "0.1.0"; 102 delete packageJson.bin; 103 // Remove @clack/prompts from dependencies since it's only for the CLI 104 if (packageJson.dependencies?.["@clack/prompts"]) { 105 delete packageJson.dependencies["@clack/prompts"]; 106 } 107 await Bun.write( 108 packageJsonPath, 109 JSON.stringify(packageJson, null, "\t") + "\n", 110 ); 111 await setTimeout(200); 112 s.stop("Configured package.json"); 113 114 // Initialize git 115 s.start("Initializing git repository"); 116 await $`cd ${targetDir} && git init`.quiet(); 117 await setTimeout(200); 118 s.stop("Initialized git repository"); 119 120 // Create .env 121 s.start("Creating .env file"); 122 await $`cd ${targetDir} && cp .env.example .env`.quiet(); 123 await setTimeout(200); 124 s.stop("Created .env file"); 125 126 // Install dependencies 127 s.start("Installing dependencies"); 128 await $`cd ${targetDir} && bun install`.quiet(); 129 s.stop("Installed dependencies"); 130 131 // Setup database 132 s.start("Setting up database"); 133 await $`cd ${targetDir} && bun run db:push`.quiet(); 134 s.stop("Set up database"); 135 } catch (error) { 136 s.stop("Failed"); 137 p.cancel( 138 `Error: ${error instanceof Error ? error.message : "Unknown error"}`, 139 ); 140 process.exit(1); 141 } 142 143 p.outro("🎉 Project created successfully!"); 144 145 p.note(`cd ${projectName}\nbun dev`, "Next steps"); 146} 147 148main().catch((error) => { 149 console.error(error); 150 process.exit(1); 151});