import fs from 'fs' import path from 'path' import readline from 'readline' import { spawn } from 'child_process' const NO_COPY = [ 'node_modules', 'dist', 'package-lock.json', '.git', 'config.js', ] const templateDir = path.join(process.cwd(), 'template') const dropsDir = path.join(process.cwd(), 'drops') const kebabCase = (str) => str .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/[\s_]+/g, '-') .toLowerCase() const pascalCase = (str) => { const camel = str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '', ) return camel.charAt(0).toUpperCase() + camel.slice(1) } const titleCase = (str) => { const words = str.split('-') return words .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') } const copyDir = (src, dest) => { fs.mkdirSync(dest, { recursive: true }) const entries = fs.readdirSync(src, { withFileTypes: true }) for (const entry of entries) { const srcPath = path.join(src, entry.name) const destPath = path.join(dest, entry.name) if (NO_COPY.includes(entry.name)) continue if (entry.isDirectory()) { copyDir(srcPath, destPath) } else { fs.copyFileSync(srcPath, destPath) } } } const updatePackageJson = (dir, name) => { const pkgPath = path.join(dir, 'package.json') const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) pkg.name = name fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n') } const replaceFileString = ( dir, filePath, oldString, newString, newFilePath, ) => { const fullPath = path.join(dir, filePath) const fileContent = fs.readFileSync(fullPath, 'utf-8') const updatedContent = fileContent.replace(oldString, newString) if (newFilePath) { fs.writeFileSync(path.join(dir, newFilePath), updatedContent) } else { fs.writeFileSync(fullPath, updatedContent) } } const updateViteConfig = (dir, name) => { replaceFileString(dir, 'vite.config.js', /\$NAME/g, name) } const updateVue = (dir, kebabName, pascalName) => { replaceFileString(dir, 'src/App.vue', /Name/g, pascalName) replaceFileString( dir, 'src/components/Name.vue', /\$name/g, kebabName, `src/components/${pascalName}.vue`, ) fs.unlinkSync(path.join(dir, 'src/components/Name.vue')) } const updateIndexHtml = (dir, name) => { replaceFileString(dir, 'index.html', /\$NAME/g, name) } const updateMetaJson = (dir, kebabName, titleName) => { const metaPath = path.join(dir, 'public/meta.json') if (fs.existsSync(metaPath)) { let content = fs.readFileSync(metaPath, 'utf-8') content = content.replace(/\$name/g, kebabName) content = content.replace(/\$NAME/g, titleName) // Update the date to today's date const today = new Date().toISOString().split('T')[0] content = content.replace(/"date":\s*"[^"]*"/, `"date": "${today}"`) fs.writeFileSync(metaPath, content) } } const runCommand = (command, args, cwd = process.cwd()) => { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd, stdio: 'inherit', shell: true, }) child.on('close', (code) => { if (code === 0) resolve() else reject(new Error(`Command failed with code ${code}`)) }) child.on('error', reject) }) } const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }) rl.question('Enter drop name: ', async (name) => { const trimmedName = name.trim() const kebabName = kebabCase(trimmedName) const pascalName = pascalCase(trimmedName) const titleName = titleCase(kebabName) if (!kebabName) { console.error('Error: drop name cannot be empty') rl.close() process.exit(1) } const destDir = path.join(dropsDir, kebabName) if (fs.existsSync(destDir)) { console.error(`Error: drop "${kebabName}" already exists`) rl.close() process.exit(1) } try { console.log(`Creating drop "${kebabName}"...`) copyDir(templateDir, destDir) // Update template files updatePackageJson(destDir, kebabName) updateViteConfig(destDir, kebabName) updateVue(destDir, kebabName, pascalName) updateIndexHtml(destDir, titleName) updateMetaJson(destDir, kebabName, titleName) console.log(`Drop "${kebabName}" created successfully`) console.log('Running npm install...') await runCommand('npm', ['install']) console.log('npm install complete!') } catch (err) { console.error('Error:', err.message) process.exit(1) } rl.close() })