UNPKG

cl-generate

Version:

A cross-platform CLI tool to generate NestJS clean architecture modules

427 lines (383 loc) • 11.7 kB
#!/usr/bin/env node const path = require("path"); const { execSync } = require("child_process"); const prompts = require("prompts"); const fs = require("fs"); const { displayVersion } = require("../lib/utils/display"); const setupPostgres = require("../lib/commands/setup-postgres"); // Colors for output const COLORS = { GREEN: "\x1b[32m", YELLOW: "\x1b[33m", RED: "\x1b[31m", CYAN: "\x1b[36m", MAGENTA: "\x1b[35m", NC: "\x1b[0m", }; // Command-line arguments const args = process.argv.slice(2); // Script paths const scriptDir = path.join(__dirname); const scripts = { help: path.join(scriptDir, "./../lib/utils/help.js"), createResource: path.join(scriptDir, "./../lib/utils/create-resource.js"), removeModule: path.join(scriptDir, "./../lib/utils/remove-module.js"), setup: path.join(scriptDir, "./../lib/utils/setup.js"), setupNest: path.join(scriptDir, "./../lib/utils/nest-setup.js"), displayVersion: path.join(scriptDir, "./../lib/utils/display.js"), }; // Utility functions const utils = { validateName: (name) => { const isValid = /^[a-z][a-z-]*$/.test(name); if (!isValid) { console.error( `${COLORS.RED}āŒ Invalid name format: Use lowercase with hyphens (e.g., user-profile)${COLORS.NC}` ); } return isValid; }, processName: (name) => ({ lower: name, pascal: name .split("-") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(""), capitalized: name.replace(/(^|-)([a-z])/g, (_, __, char) => char.toUpperCase() ), }), showStructure: () => { try { console.log(`${COLORS.GREEN}āœ… Operation completed!${COLORS.NC}`); console.log(`${COLORS.YELLOW}šŸ“‚ Project structure:${COLORS.NC}`); const cmd = process.platform === "win32" ? "dir src /ad" : "tree src -d || find src -type d"; execSync(cmd, { stdio: "inherit", shell: true }); } catch (error) { console.error( `${COLORS.RED}āŒ Structure display failed:${COLORS.NC} ${error.message}` ); } }, checkProjectExists: (name) => { return fs.existsSync(path.join(process.cwd(), name)); }, }; async function setupProject() { try { while (true) { const { project } = await prompts({ type: "text", name: "project", message: "Enter your project name:", validate: utils.validateName, hint: "Use lowercase with hyphens (e.g., my-project)", onState: (state) => { if (state.aborted) { console.log(`${COLORS.CYAN}Exiting setup process${COLORS.NC}`); console.clear(); process.exit(0); } }, }); if (!project) continue; if (utils.checkProjectExists(project)) { console.error( `${COLORS.RED}āŒ Project '${project}' already exists${COLORS.NC}` ); continue; } const db = await setupDB(); if (!db) { console.error( `${COLORS.RED}āŒ Database setup failed:${COLORS.NC} ${error.message}` ); continue; } if (db === "postgres") { const packageManager = (await setupPackage()) ?? "npm"; console.log(` ${COLORS.CYAN}✨ Project Details ✨${COLORS.NC} ā”œā”€ šŸ“‹ ${COLORS.YELLOW}Name:${COLORS.NC} ${project} ā”œā”€ šŸ“¦ ${COLORS.YELLOW}Package:${COLORS.NC} ${packageManager} └─ šŸ’¾ ${COLORS.YELLOW}Database:${COLORS.NC} PostgreSQL `); await require(scripts.setupNest)(project, packageManager); await setupPostgres(packageManager); console.log(`\nšŸŽ‰ Project setup complete!`); console.log(`\nšŸš€ To get started:`); console.log(` ${COLORS.CYAN}cd ${project}${COLORS.NC}`); console.log(` ${COLORS.CYAN}npm run start:dev${COLORS.NC}`); } if (db === "mongo") { console.log(` ${COLORS.CYAN}✨ Project Details ✨${COLORS.NC} ā”œā”€ šŸ“‹ ${COLORS.YELLOW}Name:${COLORS.NC} ${project} ā”œā”€ šŸ“¦ ${COLORS.YELLOW}Package:${COLORS.NC} ${packageManager} └─ šŸ’¾ ${COLORS.YELLOW}Database:${COLORS.NC} MongoDB `); } // utils.showStructure(); break; } } catch (error) { console.error(`${COLORS.RED}āŒ Setup failed:${COLORS.NC} ${error.message}`); } } async function setupPackage() { try { const listPackage = [ { title: "npm", value: "npm" }, { title: "pnpm", value: "pnpm" }, { title: "yarn", value: "yarn" }, ]; const { selection } = await prompts({ type: "select", name: "selection", message: "šŸ“¦ Which package manager would you ā¤ļø to use?", choices: listPackage, initial: 0, onState: (state) => { if (state.aborted) { console.log(`${COLORS.CYAN}Exiting setup process${COLORS.NC}`); console.clear(); process.exit(0); } }, }); if (!selection) return; return selection; } catch (error) { console.error( `${COLORS.RED}āŒ Selection Package failed:${COLORS.NC} ${error.message}` ); } } async function setupDB() { try { const listDB = [ { title: "postgres", value: "postgres" }, { title: "mongo", value: "mongo" }, ]; const { selection } = await prompts({ type: "select", name: "selection", message: "Choose database type:", choices: listDB, initial: 0, onState: (state) => { if (state.aborted) { console.log(`${COLORS.CYAN}Exiting setup process${COLORS.NC}`); console.clear(); process.exit(0); } }, }); if (!selection) return; return selection; } catch (error) { console.error( `${COLORS.RED}āŒ postgres setup failed:${COLORS.NC} ${error.message}` ); } } async function generateResource() { try { const listDB = [ { title: "GRPC", value: "grpc" }, { title: "RestFul", value: "restful" }, ]; const { selection } = await prompts({ type: "select", name: "selection", message: "Choose resource type:", choices: listDB, initial: 0, onState: (state) => { if (state.aborted) { console.log(`${COLORS.CYAN}Exiting setup process${COLORS.NC}`); console.clear(); process.exit(0); } }, }); if (!selection) return; if (selection === "grpc") { const { name } = await prompts({ type: "text", name: "name", message: `Enter resource name:`, validate: utils.validateName, hint: "Use lowercase with hyphens (e.g., user-profile)", }); if (!name) returne; const folders = getRepositoryFolders(); const findmodule = folders.find((folder) => folder === name); if (findmodule) { console.error( `${COLORS.RED}āŒ Resource '${name}' already exists${COLORS.NC}` ); return; } const names = utils.processName(name); await require(scripts.createResource)( names.lower, names.pascal, names.capitalized ); // utils.showStructure(); return; } else if (selection === "restful") { console.log("restful"); return; } else { console.error( `${COLORS.RED}āŒ Resource setup failed:${COLORS.NC} ${error.message}` ); return; } } catch (error) { console.error( `${COLORS.RED}āŒ Resource setup failed:${COLORS.NC} ${error.message}` ); } } // Function to List All Modules from Folder const getRepositoryFolders = () => { try { const repoPath = "src/infrastructure/repositories"; let command; if (process.platform === "win32") { // Windows command: dir with /ad (directories only) and /b (bare format) command = `dir "${repoPath}" /ad /b`; } else { // Unix-like command: ls -d or find command = `ls -d "${repoPath}"/*/ 2>/dev/null || find "${repoPath}" -maxdepth 1 -type d`; } const output = execSync(command, { stdio: "pipe", shell: true, encoding: "utf8", }).trim(); if (!output) { return ["No resource found"]; } const folders = output .split("\n") .map((line) => { if (process.platform === "win32") { return line.trim(); } // For Unix-like, get just the folder name const parts = line.split("/"); return parts[parts.length - 2].trim(); }) .filter((folder) => folder && folder !== "resource"); return folders.length > 0 ? folders : ["No resource found"]; } catch (error) { console.error( `${COLORS.RED}āŒ Error listing resource:${COLORS.RESET} ${error.message}` ); return ["Error listing resource"]; } }; const listModules = async () => { const folders = getRepositoryFolders(); if (folders[0].startsWith("No") || folders[0].startsWith("Error")) { console.log(`${COLORS.YELLOW}${folders[0]}${COLORS.RESET}`); return null; } const { selectedRepo } = await prompts({ type: "select", name: "selectedRepo", message: "Select a resource to remove:", choices: folders.map((folder) => ({ title: folder, value: folder, })), initial: 0, onState: (state) => { if (state.aborted) { console.log(`${COLORS.CYAN}Exiting setup process${COLORS.NC}`); console.clear(); process.exit(0); } }, }); if (!selectedRepo) { console.log(`${COLORS.CYAN}Selection cancelled${COLORS.RESET}`); return null; } return selectedRepo; }; // Command-line mode async function commandLineMode() { if (args[0] === "-v" || args[0] === "--version") { displayVersion(); return; } if (args[0] === "-h" || args[0] === "--help") { require(scripts.help); return; } try { switch (args[0]) { case "g": if (!args[1]) throw new Error("Usage: clean g res"); if (args[1] === "res" || args[1] === "resource") { await generateResource(); } else { throw new Error("Usage: clean g res"); } // if (args[1] !== "res") throw new Error("Usage: clean g res"); // await setupResource(); // if (!utils.validateName(args[2])) return; break; case "rm": if (!args[1]) throw new Error("Usage: clean rm res"); if (args[1] === "res" || args[1] === "resource") { const moduleName = await listModules(); if (moduleName) { const rmNames = utils.processName(moduleName); await require(scripts.removeModule)( rmNames.lower, rmNames.pascal, rmNames.capitalized ); } } else { throw new Error("Usage: clean g res"); } break; case "list": case "ls": const selected = await listModules(); if (selected) { console.log(`${COLORS.GREEN}Selected resource: ${selected}`); } break; case "new": case "n": if (args[1]) throw new Error("'setup' takes no arguments"); await setupProject(); break; default: throw new Error(`Unknown command: ${args[0]}`); } } catch (error) { console.error(`${COLORS.RED}āŒ Error: ${error.message}${COLORS.NC}`); console.log(`${COLORS.CYAN}Use --help for command info${COLORS.NC}`); process.exit(1); } } // Run with error handling if (require.main === module) { commandLineMode().catch((error) => { console.error(`${COLORS.RED}āŒ Fatal error:${COLORS.NC} ${error.message}`); process.stdout.write("\x1B[?25h"); process.exit(1); }); }