cl-generate
Version:
A cross-platform CLI tool to generate NestJS clean architecture modules
427 lines (383 loc) ⢠11.7 kB
JavaScript
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);
});
}