import fs from 'node:fs' import path from 'node:path' const cwd = process.cwd() const templateDir = path.join(cwd, 'templates', 'react-vite-glitch-component') function fail(message) { console.error(message) process.exit(1) } function toKebabCase(value) { return value .trim() .replace(/([a-z0-9])([A-Z])/g, '$1-$2') .replace(/[^a-zA-Z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .toLowerCase() } function toSnakeCase(value) { return value.replace(/-/g, '_') } function toPascalCase(value) { return value .split('-') .filter(Boolean) .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) .join('') } function toTitleCase(value) { return value .split('-') .filter(Boolean) .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) .join(' ') } function parseArgs(argv) { const args = [...argv] const result = { componentId: '', title: '', dir: '' } while (args.length > 0) { const arg = args.shift() if (!arg) continue if (!result.componentId && !arg.startsWith('--')) { result.componentId = arg continue } if (arg === '--title') { result.title = args.shift() ?? '' continue } if (arg === '--dir') { result.dir = args.shift() ?? '' continue } fail(`Unknown argument: ${arg}`) } return result } function copyTemplate(sourceDir, destinationDir, replacements) { fs.mkdirSync(destinationDir, { recursive: true }) for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) { const sourcePath = path.join(sourceDir, entry.name) const destinationPath = path.join(destinationDir, entry.name) if (entry.isDirectory()) { copyTemplate(sourcePath, destinationPath, replacements) continue } const source = fs.readFileSync(sourcePath, 'utf8') const rendered = Object.entries(replacements).reduce( (content, [key, value]) => content.replaceAll(key, value), source ) fs.writeFileSync(destinationPath, rendered) } } const options = parseArgs(process.argv.slice(2)) if (!options.componentId) { fail('Usage: npm run glitch:new -- --title "Display Name" [--dir /custom/path]') } if (!fs.existsSync(templateDir)) { fail(`Template directory not found: ${templateDir}`) } const componentId = toKebabCase(options.componentId) if (!componentId) { fail('Component id resolved to an empty value.') } const folderName = `glitch_${toSnakeCase(componentId)}` const displayName = options.title.trim() || toTitleCase(componentId) const bundleName = componentId const destinationDir = options.dir ? path.resolve(cwd, options.dir) : path.join(cwd, folderName) if (fs.existsSync(destinationDir)) { fail(`Destination already exists: ${destinationDir}`) } copyTemplate(templateDir, destinationDir, { '__COMPONENT_ID__': componentId, '__COMPONENT_DISPLAY_NAME__': displayName, '__LIB_GLOBAL_NAME__': toPascalCase(componentId), '__FOLDER_NAME__': folderName, '__BUNDLE_NAME__': bundleName }) console.log(`Created ${folderName}`) console.log(`Path: ${destinationDir}`) console.log('Next steps:') console.log(` cd ${folderName}`) console.log(' npm install') console.log(' npm run dev')