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
JavaScript
;
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();