the best lightweight web dev stack built on bun

feat: add basic bits

dunkirk.sh 24adf7d0 6b1a8881

verified
+221 -28
+4
.gitattributes
··· 1 + # Templates that should be processed 2 + *.ts linguist-detectable=true 3 + *.html linguist-detectable=true 4 + *.css linguist-detectable=true
-21
LICENSE
··· 1 - MIT License 2 - 3 - Copyright (c) 2025 Kieran Klukas 4 - 5 - Permission is hereby granted, free of charge, to any person obtaining a copy 6 - of this software and associated documentation files (the "Software"), to deal 7 - in the Software without restriction, including without limitation the rights 8 - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 - copies of the Software, and to permit persons to whom the Software is 10 - furnished to do so, subject to the following conditions: 11 - 12 - The above copyright notice and this permission notice shall be included in all 13 - copies or substantial portions of the Software. 14 - 15 - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 - SOFTWARE.
+10 -6
README.md
··· 4 4 5 5 It uses Lit, Bun, and Drizzle as the main stack and they all work together to make a wonderful combo. 6 6 7 - ## How do I hack on it? 7 + ## Quick Start 8 8 9 - ### Development 9 + ```bash 10 + bunx tacy-stack 11 + ``` 10 12 11 - Getting started is super straightforward: 13 + The CLI will guide you through creating a new project with an interactive prompt. 14 + 15 + **Manual setup:** 12 16 13 17 ```bash 14 - bun install 15 - cp .env.example .env 16 - bun run db:push 18 + git clone <this-repo> my-app 19 + cd my-app 20 + bun run setup 17 21 bun dev 18 22 ``` 19 23
+3
bunfig.toml
··· 1 + [install] 2 + # Bun's fast native lockfile 3 + frozen = false
+187
cli.ts
··· 1 + #!/usr/bin/env bun 2 + /** 3 + * Tacy Stack CLI 4 + * Interactive project scaffolder 5 + */ 6 + 7 + import { $ } from "bun"; 8 + import { existsSync, mkdirSync } from "node:fs"; 9 + import { join } from "node:path"; 10 + 11 + // ANSI color codes 12 + const colors = { 13 + reset: "\x1b[0m", 14 + bold: "\x1b[1m", 15 + cyan: "\x1b[36m", 16 + green: "\x1b[32m", 17 + yellow: "\x1b[33m", 18 + red: "\x1b[31m", 19 + dim: "\x1b[2m", 20 + }; 21 + 22 + function log(message: string, color = colors.reset) { 23 + console.log(`${color}${message}${colors.reset}`); 24 + } 25 + 26 + function header() { 27 + log("\n╔═══════════════════════════════════════╗", colors.cyan); 28 + log("║ ║", colors.cyan); 29 + log("║ 🥞 Tacy Stack Generator 🥞 ║", colors.cyan); 30 + log("║ ║", colors.cyan); 31 + log("║ Bun + Lit + Drizzle + Passkeys ║", colors.cyan); 32 + log("║ ║", colors.cyan); 33 + log("╚═══════════════════════════════════════╝", colors.cyan); 34 + log(""); 35 + } 36 + 37 + async function prompt( 38 + question: string, 39 + defaultValue?: string, 40 + ): Promise<string> { 41 + const hint = defaultValue 42 + ? colors.dim + ` (${defaultValue})` + colors.reset 43 + : ""; 44 + process.stdout.write(`${colors.bold}${question}${hint}: ${colors.reset}`); 45 + 46 + for await (const line of console) { 47 + const input = line.trim(); 48 + return input || defaultValue || ""; 49 + } 50 + 51 + return defaultValue || ""; 52 + } 53 + 54 + function validateProjectName(name: string): string | null { 55 + if (!name) return "Project name is required"; 56 + if (!/^[a-z0-9-_]+$/i.test(name)) { 57 + return "Project name can only contain letters, numbers, hyphens, and underscores"; 58 + } 59 + return null; 60 + } 61 + 62 + async function main() { 63 + header(); 64 + 65 + // Get project name 66 + let projectName = ""; 67 + while (true) { 68 + projectName = await prompt("Project name", "my-app"); 69 + const error = validateProjectName(projectName); 70 + if (!error) break; 71 + log(`❌ ${error}\n`, colors.red); 72 + } 73 + 74 + // Get target directory 75 + const targetDir = join(process.cwd(), projectName); 76 + 77 + if (existsSync(targetDir)) { 78 + log(`\n❌ Directory "${projectName}" already exists!`, colors.red); 79 + log( 80 + ` Please choose a different name or remove the existing directory.\n`, 81 + colors.dim, 82 + ); 83 + process.exit(1); 84 + } 85 + 86 + // Confirm 87 + log(""); 88 + log("📋 Project Summary:", colors.bold); 89 + log(` Name: ${colors.cyan}${projectName}${colors.reset}`); 90 + log(` Path: ${colors.dim}${targetDir}${colors.reset}`); 91 + log(""); 92 + 93 + const confirm = await prompt("Create project?", "Y/n"); 94 + if (confirm.toLowerCase() === "n") { 95 + log("\n👋 Cancelled\n", colors.yellow); 96 + process.exit(0); 97 + } 98 + 99 + // Create project 100 + log(`\n🚀 Creating project...`, colors.green); 101 + 102 + // Create directory 103 + mkdirSync(targetDir, { recursive: true }); 104 + 105 + // Get the template directory (where this CLI is located) 106 + const templateDir = import.meta.dir; 107 + 108 + // Copy template files 109 + log(`📁 Copying template files...`, colors.dim); 110 + 111 + // Copy all files except hidden files first 112 + await $`cp -r ${templateDir}/* ${targetDir}/`.quiet(); 113 + 114 + // Copy hidden files (dotfiles) separately, ignore errors if they don't exist 115 + try { 116 + await $`cp ${templateDir}/.env.example ${targetDir}/.env.example`.quiet(); 117 + await $`cp ${templateDir}/.gitignore ${targetDir}/.gitignore`.quiet(); 118 + await $`cp ${templateDir}/.gitattributes ${targetDir}/.gitattributes`.quiet(); 119 + } catch { 120 + // Some dotfiles might not exist, that's ok 121 + } 122 + 123 + // Remove CLI and template setup files 124 + const filesToRemove = [ 125 + "cli.ts", 126 + "TEMPLATE.md", 127 + "TEMPLATE_SETUP_SUMMARY.md", 128 + "CLI_SUMMARY.md", 129 + "PUBLISHING.md", 130 + "template.toml", 131 + ".github/TEMPLATE_SETUP.md", 132 + ]; 133 + 134 + for (const file of filesToRemove) { 135 + const filePath = join(targetDir, file); 136 + if (existsSync(filePath)) { 137 + await $`rm -rf ${filePath}`.quiet(); 138 + } 139 + } 140 + 141 + // Update package.json with new name 142 + log(`📝 Configuring project...`, colors.dim); 143 + const packageJsonPath = join(targetDir, "package.json"); 144 + const packageJson = await Bun.file(packageJsonPath).json(); 145 + packageJson.name = projectName; 146 + packageJson.version = "0.1.0"; 147 + delete packageJson.bin; // Remove CLI bin entry from generated projects 148 + await Bun.write( 149 + packageJsonPath, 150 + JSON.stringify(packageJson, null, "\t") + "\n", 151 + ); 152 + 153 + // Initialize git 154 + log(`🔧 Initializing git repository...`, colors.dim); 155 + await $`cd ${targetDir} && git init`.quiet(); 156 + 157 + // Create .env from example 158 + log(`🔐 Creating .env file...`, colors.dim); 159 + await $`cd ${targetDir} && cp .env.example .env`.quiet(); 160 + 161 + // Install dependencies 162 + log(`📦 Installing dependencies...`, colors.dim); 163 + await $`cd ${targetDir} && bun install`.quiet(); 164 + 165 + // Setup database 166 + log(`🗄️ Setting up database...`, colors.dim); 167 + await $`cd ${targetDir} && bun run db:push`.quiet(); 168 + 169 + // Success! 170 + log(""); 171 + log("✅ Project created successfully!", colors.green + colors.bold); 172 + log(""); 173 + log("Next steps:", colors.bold); 174 + log(` ${colors.cyan}cd ${projectName}${colors.reset}`); 175 + log(` ${colors.cyan}bun dev${colors.reset}`); 176 + log(""); 177 + log( 178 + `Open ${colors.cyan}http://localhost:3000${colors.reset} to see your app!`, 179 + colors.dim, 180 + ); 181 + log(""); 182 + } 183 + 184 + main().catch((error) => { 185 + log(`\n❌ Error: ${error.message}`, colors.red); 186 + process.exit(1); 187 + });
+4 -1
package.json
··· 1 1 { 2 2 "name": "tacy-stack", 3 + "version": "1.0.0", 3 4 "module": "src/index.ts", 4 5 "type": "module", 5 - "private": true, 6 + "bin": { 7 + "tacy-stack": "./cli.ts" 8 + }, 6 9 "scripts": { 7 10 "dev": "bun run src/index.ts --hot", 8 11 "test": "NODE_ENV=test bun test",
+13
template.toml
··· 1 + # Bun template metadata 2 + [template] 3 + name = "tacy-stack" 4 + description = "Minimal full-stack starter with Bun, Lit, Drizzle ORM, and Passkeys" 5 + author = "Kieran K" 6 + 7 + [hooks] 8 + # Commands to run after project initialization 9 + postinstall = [ 10 + "bun install", 11 + "cp .env.example .env", 12 + "bun run db:push" 13 + ]