UNPKG

emirapp-cli

Version:

A CLI tool to scaffold modern Expo React Native applications with authentication, tab navigation, and TypeScript

434 lines (433 loc) 18.1 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const chalk_1 = __importDefault(require("chalk")); const execa_1 = require("execa"); const figlet_1 = __importDefault(require("figlet")); const fs_extra_1 = __importDefault(require("fs-extra")); const gradient_string_1 = __importDefault(require("gradient-string")); const ora_1 = __importDefault(require("ora")); const path_1 = __importDefault(require("path")); const prompts_1 = __importDefault(require("prompts")); // Import templates const simple_1 = require("./mobile/ReactNative_Expo/simple"); const enterprise_1 = require("./website/Next-Js/enterprise"); const simple_2 = require("./website/Next-Js/simple"); // Helper function to create colorful banners function createBanner() { const banner = figlet_1.default.textSync("EmirApp CLI", { font: "ANSI Shadow", horizontalLayout: "default", verticalLayout: "default", }); console.log((0, gradient_string_1.default)([ "#ff0000", "#ff7f00", "#ffff00", "#00ff00", "#0000ff", "#4b0082", "#9400d3", ])(banner)); console.log(chalk_1.default.cyan.bold("🚀 A powerful CLI to scaffold modern applications")); console.log(chalk_1.default.gray(" Next.js • React Native • Angular • Flutter • TypeScript • Enterprise Ready")); console.log(); } // Template creation functions async function createAppFiles(platform, framework, complexity) { let templates = {}; let dependencies = []; // Select templates based on platform, framework, and complexity if (platform === "website" && framework === "nextjs" && complexity === "simple") { templates = simple_2.nextJsSimpleTemplates; dependencies = simple_2.nextJsSimpleDependencies; } else if (platform === "website" && framework === "nextjs" && complexity === "enterprise") { templates = enterprise_1.nextJsEnterpriseTemplates; dependencies = enterprise_1.nextJsEnterpriseDependencies; } else if (platform === "mobile" && framework === "expo" && complexity === "simple") { templates = simple_1.reactNativeExpoSimpleTemplates; dependencies = simple_1.reactNativeExpoSimpleDependencies; } // Write all template files for (const [filePath, content] of Object.entries(templates)) { await fs_extra_1.default.writeFile(filePath, content); } return dependencies; } async function run() { // Handle process interruption gracefully process.on("SIGINT", () => { console.log(); console.log(chalk_1.default.yellow("👋 Process cancelled by user. Goodbye!")); process.exit(0); }); // Show welcome banner createBanner(); // 1. Choose platform const { platform } = await (0, prompts_1.default)({ type: "select", name: "platform", message: chalk_1.default.yellow("🎯 Choose a platform"), choices: [ { title: chalk_1.default.green("📱 Mobile") + chalk_1.default.gray(" - React Native & Flutter"), value: "mobile", }, { title: chalk_1.default.green("🌐 Website") + chalk_1.default.gray(" - Next.js & Angular"), value: "website", }, ], }); if (!platform) { console.log(chalk_1.default.yellow("👋 Process cancelled. Goodbye!")); process.exit(0); } // 2. Choose framework based on platform let frameworkChoices = []; if (platform === "mobile") { frameworkChoices = [ { title: chalk_1.default.green("Expo React Native") + chalk_1.default.gray(" - Modern React Native framework"), value: "expo", }, { title: chalk_1.default.gray("Flutter") + chalk_1.default.dim(" (Coming Soon)"), value: "flutter", disabled: true, }, ]; } else if (platform === "website") { frameworkChoices = [ { title: chalk_1.default.green("Next.js") + chalk_1.default.gray(" - Modern React framework"), value: "nextjs", }, { title: chalk_1.default.gray("Angular") + chalk_1.default.dim(" (Coming Soon)"), value: "angular", disabled: true, }, ]; } const { framework } = await (0, prompts_1.default)({ type: "select", name: "framework", message: chalk_1.default.yellow("🔧 Choose a framework"), choices: frameworkChoices, }); if (!framework) { console.log(chalk_1.default.yellow("👋 Process cancelled. Goodbye!")); process.exit(0); } // 3. Choose complexity const { complexity } = await (0, prompts_1.default)({ type: "select", name: "complexity", message: chalk_1.default.yellow("📁 Choose project complexity"), choices: [ { title: chalk_1.default.green("Simple") + chalk_1.default.gray(" - Perfect for getting started"), value: "simple", }, { title: chalk_1.default.gray("Enterprise") + chalk_1.default.dim(" (Coming Soon)"), value: "enterprise", disabled: true, }, ], }); if (!complexity) { console.log(chalk_1.default.yellow("👋 Process cancelled. Goodbye!")); process.exit(0); } // 3. Ask for project name const { appName } = await (0, prompts_1.default)({ type: "text", name: "appName", message: chalk_1.default.yellow("📝 Enter your app name"), initial: "my-awesome-app", validate: (name) => name && /^[a-zA-Z0-9-_ ]+$/.test(name) ? true : chalk_1.default.red("App name must be alphanumeric with optional spaces, dashes or underscores"), }); if (!appName) { console.log(chalk_1.default.yellow("👋 Process cancelled. Goodbye!")); process.exit(0); } // Sanitize app name: lowercase and replace spaces with dashes const sanitizedAppName = appName.toLowerCase().replace(/\s+/g, "-"); console.log(); console.log(chalk_1.default.cyan.bold(`🏗️ Creating ${platform === "website" ? "Next.js" : "Expo React Native"} app "${sanitizedAppName}"`)); console.log(chalk_1.default.gray(" This might take a moment...")); console.log(); // 4. Create app based on platform and framework const createAppSpinner = (0, ora_1.default)({ text: chalk_1.default.blue(`Setting up ${platform === "website" ? "Next.js with TypeScript and Tailwind" : "Expo React Native with TypeScript"}...`), spinner: "dots12", }).start(); try { if (platform === "website" && framework === "nextjs") { await (0, execa_1.execa)("npx", [ "create-next-app@latest", sanitizedAppName, "--typescript", "--tailwind", "--app", "--src-dir", "--eslint", "--yes", ], { stdio: "pipe" }); } else if (platform === "mobile" && framework === "expo") { await (0, execa_1.execa)("yarn", [ "create", "expo-app", sanitizedAppName, "--template", "blank-typescript", ], { stdio: "pipe" }); } createAppSpinner.succeed(chalk_1.default.green(`✅ ${platform === "website" ? "Next.js" : "Expo React Native"} app created successfully!`)); } catch (error) { createAppSpinner.fail(chalk_1.default.red(`❌ Failed to create ${platform === "website" ? "Next.js" : "Expo React Native"} app`)); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } // 5. Change working directory to the created app const appPath = path_1.default.resolve(process.cwd(), sanitizedAppName); process.chdir(appPath); // 6. Create platform-specific folder structure if (platform === "website" && framework === "nextjs") { // Create Next.js enterprise folder structure const foldersToCreate = [ "src/app/(auth)", "src/app/(protected)", "src/app/(unprotected)", "src/components/ui", "src/components/layout", "src/components/features", "src/components/modals", "src/components/icons", "src/components/common", "src/api/hooks", "src/api/services", "src/context", "src/data", "src/data/stores", "src/lib", "src/types", ]; const folderSpinner = (0, ora_1.default)({ text: chalk_1.default.blue("Creating enterprise folder structure..."), spinner: "bouncingBar", }).start(); try { await Promise.all(foldersToCreate.map((folder) => fs_extra_1.default.ensureDir(folder))); folderSpinner.succeed(chalk_1.default.green("✅ Folder structure created!")); } catch (error) { folderSpinner.fail(chalk_1.default.red("❌ Failed to create folder structure")); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } } // 7. Install platform-specific dependencies if (platform === "mobile" && framework === "expo") { const routerSpinner = (0, ora_1.default)({ text: chalk_1.default.blue("Installing Expo Router and dependencies..."), spinner: "bouncingBar", }).start(); try { await (0, execa_1.execa)("npx", ["expo", "install", "expo-router"], { stdio: "pipe" }); routerSpinner.succeed(chalk_1.default.green("✅ Expo Router installed!")); } catch (error) { routerSpinner.fail(chalk_1.default.red("❌ Failed to install Expo Router")); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } } // 7. Create Expo Router app structure const foldersToCreate = [ "app/(auth)", "app/(protected)", "app/(protected)/main", "app/(protected)/main/home", "app/(protected)/main/explore", "src/components/ui", "src/components/business", "src/components/forms", "src/components/layout", "src/components/screens", "src/components/feetback", "src/context/providers", "src/api/hooks", "src/api/services", "src/data", "src/lib", "src/types", "src/utils", ]; const folderSpinner = (0, ora_1.default)({ text: chalk_1.default.blue("Creating Expo Router app structure..."), spinner: "bouncingBar", }).start(); try { await Promise.all(foldersToCreate.map((folder) => fs_extra_1.default.ensureDir(folder))); folderSpinner.succeed(chalk_1.default.green("✅ App structure created!")); } catch (error) { folderSpinner.fail(chalk_1.default.red("❌ Failed to create app structure")); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } // 8. Create index.ts barrel files with template content const barrelTemplate = `// Barrel export file for this folder // Example: export { default as Button } from "./Button"; // Example: export { default as Input } from "./Input"; `; const barrelSpinner = (0, ora_1.default)({ text: chalk_1.default.blue("Creating barrel export files..."), spinner: "dots2", }).start(); let indexPaths = []; if (platform === "website" && framework === "nextjs") { // Next.js barrel export paths indexPaths = [ "src/components/ui/index.ts", "src/components/layout/index.ts", "src/components/features/index.ts", "src/components/modals/index.ts", "src/components/icons/index.ts", "src/components/common/index.ts", "src/api/hooks/index.ts", "src/api/services/index.ts", "src/context/index.ts", "src/data/index.ts", "src/data/stores/index.ts", "src/lib/index.ts", "src/types/index.ts", ]; } else if (platform === "mobile" && framework === "expo") { // React Native Expo barrel export paths indexPaths = [ "src/components/ui/index.ts", "src/components/business/index.ts", "src/components/forms/index.ts", "src/components/layout/index.ts", "src/components/screens/index.ts", "src/components/feetback/index.ts", "src/context/index.ts", "src/context/providers/index.ts", "src/api/hooks/index.ts", "src/api/services/index.ts", "src/data/index.ts", "src/lib/index.ts", "src/types/index.ts", "src/utils/index.ts", ]; } try { await Promise.all(indexPaths.map((filePath) => fs_extra_1.default.writeFile(filePath, barrelTemplate))); barrelSpinner.succeed(chalk_1.default.green("✅ Barrel export files created!")); } catch (error) { barrelSpinner.fail(chalk_1.default.red("❌ Failed to create barrel files")); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } // Dependencies are now handled in the createAppFiles function // 10. Create app template files const templateSpinner = (0, ora_1.default)({ text: chalk_1.default.blue("Creating app template files..."), spinner: "dots", }).start(); try { const dependencies = await createAppFiles(platform, framework, complexity); templateSpinner.succeed(chalk_1.default.green("✅ App template files created!")); // Install dependencies if any if (dependencies.length > 0) { const depsSpinner = (0, ora_1.default)({ text: chalk_1.default.blue(platform === "website" ? "Installing Zod and TanStack React Query..." : "Installing React Native dependencies..."), spinner: "arc", }).start(); try { if (platform === "website") { await (0, execa_1.execa)("yarn", ["add", ...dependencies], { stdio: "pipe" }); } else { await (0, execa_1.execa)("npm", ["install", ...dependencies], { stdio: "pipe" }); } depsSpinner.succeed(chalk_1.default.green("✅ Dependencies installed successfully!")); } catch (error) { depsSpinner.fail(chalk_1.default.red("❌ Failed to install dependencies")); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } } } catch (error) { templateSpinner.fail(chalk_1.default.red("❌ Failed to create template files")); console.error(chalk_1.default.red("Error details:"), error); process.exit(1); } // Success banner console.log(); console.log(chalk_1.default.green("🎉 ") + chalk_1.default.bold.green("SUCCESS!")); console.log(); console.log(chalk_1.default.cyan.bold(`📦 Project "${sanitizedAppName}" created successfully!`)); console.log(); console.log(chalk_1.default.yellow.bold("🚀 Next steps:")); console.log(chalk_1.default.white(` 1. ${chalk_1.default.cyan(`cd ${sanitizedAppName}`)}`)); if (platform === "website") { console.log(chalk_1.default.white(` 2. ${chalk_1.default.cyan("npm run dev")}`)); console.log(chalk_1.default.white(` 3. ${chalk_1.default.cyan("Open http://localhost:3000")}`)); console.log(); console.log(chalk_1.default.gray("📚 Your project includes:")); console.log(chalk_1.default.gray(" • Next.js 14+ with App Router")); console.log(chalk_1.default.gray(" • TypeScript configuration")); console.log(chalk_1.default.gray(" • Tailwind CSS styling")); console.log(chalk_1.default.gray(" • React Query for data fetching")); console.log(chalk_1.default.gray(" • Zod for schema validation")); console.log(chalk_1.default.gray(" • Enterprise folder structure")); console.log(chalk_1.default.gray(" • Barrel export files")); console.log(chalk_1.default.gray(" • Route groups (auth/protected/unprotected)")); } else { console.log(chalk_1.default.white(` 2. ${chalk_1.default.cyan("npm start")}`)); console.log(chalk_1.default.white(` 3. ${chalk_1.default.cyan("Scan QR code with Expo Go app")}`)); console.log(); console.log(chalk_1.default.gray("📚 Your project includes:")); console.log(chalk_1.default.gray(" • Expo React Native with TypeScript")); console.log(chalk_1.default.gray(" • Expo Router for navigation")); console.log(chalk_1.default.gray(" • Tab navigation with beautiful icons")); console.log(chalk_1.default.gray(" • Authentication flow (Phone + OTP)")); console.log(chalk_1.default.gray(" • React Query for data fetching")); console.log(chalk_1.default.gray(" • Complete app structure")); console.log(chalk_1.default.gray(" • Ready-to-use components")); } console.log(); console.log((0, gradient_string_1.default)(["#ff6b6b", "#4ecdc4", "#45b7d1", "#96ceb4", "#feca57"])("Happy coding! 🚀✨")); } run();