import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { doctorCommand } from './doctor.js'; const here = dirname(fileURLToPath(import.meta.url)); const TEMPLATES_DIR = join(here, '..', '..', 'templates'); export interface InitOptions { name?: string; template?: string; prekeyServer?: string; cwd?: string; runDoctor?: boolean; } export async function initCommand(opts: InitOptions = {}): Promise { const name = opts.name ?? 'my-shade-app'; const template = opts.template ?? 'bun-server'; const cwd = opts.cwd ?? process.cwd(); const target = join(cwd, name); if (existsSync(target)) { throw new Error(`Target directory "${target}" already exists`); } const templateDir = join(TEMPLATES_DIR, template); if (!existsSync(templateDir)) { const available = listTemplates(); throw new Error( `Template "${template}" not found. Available: ${available.join(', ')}`, ); } // Recursive copy with placeholder substitution mkdirSync(target, { recursive: true }); copyRecursive(templateDir, target, { __PROJECT_NAME__: name, __PREKEY_SERVER__: opts.prekeyServer ?? 'http://localhost:3900', }); console.log(`✓ Created ${name} from template "${template}"`); console.log(''); console.log(` cd ${name}`); console.log(' bun install'); console.log(' bun run start'); if (opts.runDoctor) { console.log(''); await doctorCommand({ cwd: target }); } } export function listTemplates(): string[] { if (!existsSync(TEMPLATES_DIR)) return []; return readdirSync(TEMPLATES_DIR).filter((name) => { return statSync(join(TEMPLATES_DIR, name)).isDirectory(); }); } function copyRecursive( source: string, dest: string, replacements: Record, ): void { mkdirSync(dest, { recursive: true }); for (const entry of readdirSync(source)) { const srcPath = join(source, entry); const destPath = join(dest, entry); const st = statSync(srcPath); if (st.isDirectory()) { copyRecursive(srcPath, destPath, replacements); } else { const content = readFileSync(srcPath, 'utf-8'); const substituted = Object.entries(replacements).reduce( (acc, [key, value]) => acc.replaceAll(key, value), content, ); writeFileSync(destPath, substituted); } } }