UNPKG

@pitifulhawk/flash-up

Version:

Interactive project scaffolder for modern web applications

451 lines โ€ข 18.1 kB
#!/usr/bin/env node import { Command } from "commander"; import { Framework, PackageManager, ProjectLanguage, } from "./types/index.js"; import { promptForProjectConfig, confirmProjectConfig } from "./ui/prompts.js"; import { logger } from "./ui/logger.js"; import { ProjectScaffolder } from "./core/scaffolder.js"; import { detectPackageManager, validatePackageManager, } from "./core/package-manager.js"; import { validateProjectName, validateFramework, validatePackageManager as validatePMInput, } from "./utils/validation.js"; const VERSION = "1.0.0"; const PRESETS = { react: { framework: Framework.REACT, language: ProjectLanguage.JAVASCRIPT, description: "React with Vite (JavaScript)", }, "react-ts": { framework: Framework.REACT, language: ProjectLanguage.TYPESCRIPT, description: "React with Vite (TypeScript)", }, next: { framework: Framework.NEXTJS, language: ProjectLanguage.JAVASCRIPT, description: "Next.js (JavaScript)", }, "next-ts": { framework: Framework.NEXTJS, language: ProjectLanguage.TYPESCRIPT, description: "Next.js (TypeScript)", }, express: { framework: Framework.EXPRESS, language: ProjectLanguage.JAVASCRIPT, description: "Express.js (JavaScript)", }, "express-ts": { framework: Framework.EXPRESS, language: ProjectLanguage.TYPESCRIPT, description: "Express.js (TypeScript)", }, }; class FlashUpCLI { program; options = {}; constructor() { this.program = new Command(); this.setupCommands(); } setupCommands() { this.program .name("flash-up") .description("Interactive project scaffolder for modern web applications") .version(VERSION) .argument("[preset]", "preset template (react, react-ts, next, next-ts, express, express-ts)") .option("-n, --name <name>", "project name") .option("-f, --framework <framework>", "framework (react, nextjs, express)") .option("-p, --package-manager <pm>", "package manager (npm, pnpm, yarn, bun)") .option("--skip-prompts", "skip interactive prompts") .option("-v, --verbose", "verbose output") .action(async (preset, options) => { this.options = options; if (preset) { await this.runWithPreset(preset); } else { await this.run(); } }); this.program .command("help") .description("display help information") .action(() => { this.displayHelp(); }); this.program .command("version") .description("display version information") .action(() => { console.log(VERSION); }); } async runWithPreset(presetName) { try { if (this.options.verbose) { logger.setVerbose(true); } const preset = PRESETS[presetName]; if (!preset) { console.error(`โŒ Unknown preset: ${presetName}`); console.log(`Available presets: ${Object.keys(PRESETS).join(", ")}`); process.exit(1); } this.displayBanner(); let projectName = this.options.name; if (!projectName) { projectName = `my-${presetName}-app`; } const nameValidation = validateProjectName(projectName); if (!nameValidation.valid) { console.error(`โŒ Invalid project name: ${nameValidation.error}`); process.exit(1); } let packageManager = this.options.packageManager; if (!packageManager) { const detectedPM = await detectPackageManager(); packageManager = detectedPM || PackageManager.NPM; } else { const pmValidation = validatePMInput(packageManager); if (!pmValidation.valid) { console.error(`โŒ Invalid package manager: ${pmValidation.error}`); process.exit(1); } } const config = { name: projectName, language: preset.language, framework: preset.framework, packageManager, addOns: { categories: [], includeLinting: false, includeAuthentication: false, includeClientRouting: false, includeDocker: false, }, targetPath: projectName, }; console.log(); logger.info(`๐Ÿš€ Creating ${preset.description} project: ${projectName}`); console.log(); if (this.options.skipPrompts) { await this.createProject(config); } else { const confirmed = await confirmProjectConfig(config); if (!confirmed) { logger.info("Project creation cancelled."); process.exit(0); } const enhancedConfig = await this.enhanceConfigWithPrompts(config); await this.createProject(enhancedConfig); } } catch (error) { logger.error(`Failed to create project: ${error.message}`); if (this.options.verbose) { console.error(error.stack); } process.exit(1); } } async run() { try { if (this.options.verbose) { logger.setVerbose(true); } this.displayBanner(); const config = await this.getProjectConfiguration(); if (!this.options.skipPrompts) { const confirmed = await confirmProjectConfig(config); if (!confirmed) { logger.info("Project creation cancelled."); process.exit(0); } } const isValidPM = await validatePackageManager(config.packageManager); if (!isValidPM) { logger.error(`Package manager ${config.packageManager} is not available on this system.`); logger.info("Please install the package manager or choose a different one."); process.exit(1); } await this.createProject(config); } catch (error) { logger.error(`Error: ${error.message}`); if (this.options.verbose) { console.error(error.stack); } process.exit(1); } } async enhanceConfigWithPrompts(config) { const enhancedConfig = await promptForProjectConfig({ name: config.name, language: config.language, framework: config.framework, packageManager: config.packageManager, addOns: config.addOns, }); return enhancedConfig; } displayHelp() { console.log(); console.log("๐Ÿš€ flash-up - Interactive project scaffolder for modern web applications"); console.log(); console.log("QUICK START (Preset Commands):"); console.log(" npx flash-up react Create React + Vite project (JavaScript)"); console.log(" npx flash-up react-ts Create React + Vite project (TypeScript)"); console.log(" npx flash-up next Create Next.js project (JavaScript)"); console.log(" npx flash-up next-ts Create Next.js project (TypeScript)"); console.log(" npx flash-up express Create Express.js project (JavaScript)"); console.log(" npx flash-up express-ts Create Express.js project (TypeScript)"); console.log(); console.log("INTERACTIVE MODE:"); console.log(" npx flash-up Start interactive configuration"); console.log(); console.log("OPTIONS:"); console.log(" -n, --name <name> Project name"); console.log(" -p, --package-manager <pm> Package manager (npm, pnpm, yarn, bun)"); console.log(" --skip-prompts Skip interactive prompts"); console.log(" -v, --verbose Verbose output"); console.log(" -h, --help Display help information"); console.log(" -V, --version Display version number"); console.log(); console.log("EXAMPLES:"); console.log(" npx flash-up react-ts --name my-app"); console.log(" npx flash-up next --name my-blog --package-manager pnpm"); console.log(" npx flash-up express-ts --skip-prompts"); console.log(); } displayBanner() { if (!this.options.skipPrompts) { logger.banner("โšก flash-up - Project Scaffolder"); logger.info("Create modern web applications with ease!"); logger.newLine(); } } async getProjectConfiguration() { const initialConfig = {}; if (this.options.name) { const nameValidation = validateProjectName(this.options.name); if (!nameValidation.valid) { throw new Error(`Invalid project name: ${nameValidation.error}`); } initialConfig.name = this.options.name; } if (this.options.framework) { const frameworkValidation = validateFramework(this.options.framework); if (!frameworkValidation.valid) { throw new Error(`Invalid framework: ${frameworkValidation.error}`); } initialConfig.framework = this.options.framework; } if (this.options.packageManager) { const pmValidation = validatePMInput(this.options.packageManager); if (!pmValidation.valid) { throw new Error(`Invalid package manager: ${pmValidation.error}`); } initialConfig.packageManager = this.options .packageManager; } else { const detected = await detectPackageManager(); if (detected) { initialConfig.packageManager = detected; logger.debug(`Detected package manager: ${detected}`); } } if (this.options.skipPrompts) { return this.getDefaultConfiguration(initialConfig); } return await promptForProjectConfig(initialConfig); } getDefaultConfiguration(partial) { const name = partial.name || "my-app"; const language = partial.language || ProjectLanguage.TYPESCRIPT; const framework = partial.framework || Framework.REACT; const packageManager = partial.packageManager || PackageManager.NPM; const addOns = { categories: [], includeLinting: false, includeAuthentication: false, includeClientRouting: false, includeDocker: false, }; return { name, language, framework, packageManager, addOns, targetPath: name, }; } async createProject(config) { logger.section("Creating Project"); logger.keyValue("Name", config.name); logger.keyValue("Language", config.language); logger.keyValue("Framework", config.framework); logger.keyValue("Package Manager", config.packageManager); logger.keyValue("Features", this.getAddOnsDisplayText(config.addOns)); logger.newLine(); const scaffolder = new ProjectScaffolder(config); const success = await scaffolder.createProject(); if (success) { logger.newLine(); logger.success("Project created successfully! ๐ŸŽ‰"); logger.newLine(); const nextSteps = scaffolder.getNextSteps(); logger.nextSteps(nextSteps); this.displayTips(config); } else { logger.error("Project creation failed. Please check the errors above."); process.exit(1); } } displayTips(config) { logger.section("Tips"); switch (config.framework) { case Framework.REACT: logger.list([ "Edit src/App.tsx to start building your React application", "Add components in the src/components directory", "Use Vite's hot reload for fast development", ]); break; case Framework.NEXTJS: logger.list([ "Create pages in the src/app directory", "Use Next.js App Router for routing", "Add API routes in src/app/api", ]); break; case Framework.EXPRESS: logger.list([ "Add routes in src/routes directory", "Use middleware for authentication and validation", "Build with `npm run build` before deployment", ]); break; } logger.newLine(); logger.info(`๐ŸŽฏ Your ${config.language} project is ready!`); if (config.addOns.cssFramework) { logger.newLine(); logger.info(`๐Ÿ’ก ${config.addOns.cssFramework} CSS is configured and ready to use!`); if (config.addOns.uiLibrary) { logger.info(`๐ŸŽจ ${config.addOns.uiLibrary} UI components are available!`); } } if (config.addOns.stateManagement) { logger.newLine(); logger.info(`๐Ÿ—ƒ๏ธ ${config.addOns.stateManagement} state management is set up!`); const storeFile = config.language === ProjectLanguage.TYPESCRIPT ? "store.ts" : "store.js"; logger.list([ `Check src/store/${storeFile} for your global state configuration`, ]); } if (config.addOns.includeClientRouting) { logger.newLine(); logger.info("๐Ÿ›ฃ๏ธ React Router is configured for client-side routing!"); logger.list([ "Check src/pages/ for your route components", "Update src/App.tsx to add new routes", ]); } if (config.addOns.httpClient) { logger.newLine(); logger.info(`๐ŸŒ ${config.addOns.httpClient} HTTP client is configured!`); const apiFile = config.language === ProjectLanguage.TYPESCRIPT ? "api.ts" : "api.js"; const apiPath = config.addOns.httpClient === "axios" ? "src/lib/" : "src/utils/"; logger.list([`Check ${apiPath}${apiFile} for API utilities`]); } if (config.addOns.testingFramework) { logger.newLine(); logger.info(`๐Ÿงช ${config.addOns.testingFramework} testing environment is ready!`); logger.list([ "Run 'npm test' to run your tests", "Check src/__tests__/ for test examples", ]); } if (config.addOns.includeAuthentication) { logger.newLine(); logger.info("๐Ÿ” JWT Authentication is configured!"); logger.list([ "Check src/routes/auth.ts for authentication endpoints", "Update .env with your JWT_SECRET", ]); } if (config.addOns.database && config.addOns.orm) { logger.newLine(); logger.info(`๐Ÿ—„๏ธ ${config.addOns.database} + ${config.addOns.orm} is configured!`); logger.list([ "Update .env with your database connection string", "Run database migrations if using Prisma/Sequelize", ]); } if (config.addOns.includeLinting) { logger.newLine(); logger.info("๐Ÿ”ง ESLint and Prettier are configured for code quality."); logger.list([ "Run 'npm run lint' to check for issues", "Run 'npm run format' to format your code", ]); } if (config.addOns.includeDocker) { logger.newLine(); logger.info("๐Ÿณ Docker configuration is ready!"); logger.list([ "Run 'docker build -t my-app .' to build your image", "Run 'docker-compose up' for full stack development", ]); } logger.newLine(); logger.info("Happy coding! ๐Ÿš€"); } getAddOnsDisplayText(addOns) { const features = []; if (addOns.cssFramework && addOns.cssFramework !== "none") { let cssText = addOns.cssFramework; if (addOns.uiLibrary && addOns.uiLibrary !== "none") { cssText += ` + ${addOns.uiLibrary}`; } features.push(cssText); } if (addOns.includeLinting) { features.push("ESLint + Prettier"); } if (addOns.httpClient) { features.push(addOns.httpClient); } return features.length > 0 ? features.join(", ") : "None"; } async parseAndRun(argv) { await this.program.parseAsync(argv); } } async function main() { const cli = new FlashUpCLI(); await cli.parseAndRun(process.argv); } process.on("unhandledRejection", (reason, promise) => { logger.error(`Unhandled Rejection at: ${promise}, reason: ${reason}`); process.exit(1); }); process.on("uncaughtException", (error) => { logger.error(`Uncaught Exception: ${error.message}`); if (process.env["NODE_ENV"] === "development") { console.error(error.stack); } process.exit(1); }); main().catch((error) => { logger.error(`Fatal error: ${error.message}`); process.exit(1); }); //# sourceMappingURL=index.js.map