cl-generate
Version:
A cross-platform CLI tool to generate NestJS clean architecture modules
1,490 lines (1,332 loc) • 71.2 kB
JavaScript
#!/usr/bin/env node
const fs = require("fs").promises;
const path = require("path");
const createDir = require("../shares/createDir");
const createFile = require("../shares/createFile");
// Colors for output
const COLORS = {
GREEN: "\x1b[32m",
YELLOW: "\x1b[33m",
RED: "\x1b[31m",
NC: "\x1b[0m",
};
class ModuleGenerator {
constructor(moduleNameLower, moduleNamePascal, moduleNameCapitalized) {
this.moduleNameLower = moduleNameLower;
this.moduleNamePascal = moduleNamePascal;
this.moduleNameCapitalized = moduleNameCapitalized;
this.rootDir = "src";
this.domainDir = path.join(this.rootDir, "domain");
this.infraDir = path.join(this.rootDir, "infrastructure");
this.useCasesDir = path.join(this.rootDir, "usecases");
this.modelName = `${this.moduleNamePascal}Model`;
this.dtoName = `${this.moduleNamePascal}Dto`;
this.entityName = `${this.moduleNamePascal}Entity`;
this.entityFilePath = `@infrastructure/entities/${this.moduleNameLower}.entity`;
this.repositoryName = `${this.moduleNamePascal}Repo`;
this.repositoryImplName = `${this.moduleNamePascal}RepoImpl`;
this.repositoryInterfaceName = `${this.moduleNamePascal}Interface`;
this.useCaseName = `${this.moduleNamePascal}Usecase`;
this.controllerName = `${this.moduleNamePascal}Controller`;
this.serviceName = `${this.moduleNamePascal}Service`;
this.moduleNameUpper = this.moduleNameLower.replace("-", "_").toUpperCase();
this.createModelName = `Create${this.moduleNamePascal}Model`;
this.createValidationName = `Create${this.moduleNamePascal}Validation`;
this.createActionName = `Create${this.moduleNamePascal}Action`;
this.createRepositoryName = `Create${this.moduleNamePascal}Repository`;
this.createRepositoryImplName = `Create${this.moduleNamePascal}RepositoryImpl`;
this.createRepositoryInterfaceName = `Create${this.moduleNamePascal}RepositoryInterface`;
this.createFunctionName = `Create${this.moduleNamePascal}`;
this.responseModelName = `Response${this.moduleNamePascal}Model`;
this.updateModelName = `Update${this.moduleNamePascal}Model`;
this.updateValidationName = `Update${this.moduleNamePascal}Validation`;
this.updateActionName = `Update${this.moduleNamePascal}Action`;
this.updateRepositoryName = `Update${this.moduleNamePascal}Repository`;
this.updateRepositoryImplName = `Update${this.moduleNamePascal}RepositoryImpl`;
this.updateRepositoryInterfaceName = `Update${this.moduleNamePascal}RepositoryInterface`;
this.updateFunctionName = `Update${this.moduleNamePascal}`;
this.deleteModelName = `Delete${this.moduleNamePascal}Model`;
this.deleteValidationName = `Delete${this.moduleNamePascal}Validation`;
this.deleteActionName = `Delete${this.moduleNamePascal}Action`;
this.deleteRepositoryName = `Delete${this.moduleNamePascal}Repository`;
this.deleteRepositoryImplName = `Delete${this.moduleNamePascal}RepositoryImpl`;
this.deleteRepositoryInterfaceName = `Delete${this.moduleNamePascal}RepositoryInterface`;
this.deleteFunctionName = `Delete${this.moduleNamePascal}`;
this.loadAllModelName = `LoadAll${this.moduleNamePascal}Model`;
this.loadAllValidationName = `LoadAll${this.moduleNamePascal}Validation`;
this.loadAllActionName = `LoadAll${this.moduleNamePascal}Action`;
this.loadAllRepositoryName = `LoadAll${this.moduleNamePascal}Repository`;
this.loadAllRepositoryImplName = `LoadAll${this.moduleNamePascal}RepositoryImpl`;
this.loadAllRepositoryInterfaceName = `LoadAll${this.moduleNamePascal}RepositoryInterface`;
this.loadAllFunctionName = `LoadAll${this.moduleNamePascal}`;
this.loadByIdModelName = `Load${this.moduleNamePascal}ByIdModel`;
this.loadByIdValidationName = `Load${this.moduleNamePascal}ByIdValidation`;
this.loadByIdActionName = `Load${this.moduleNamePascal}ByIdAction`;
this.loadByIdRepositoryName = `Load${this.moduleNamePascal}ByIdRepository`;
this.loadByIdRepositoryImplName = `Load${this.moduleNamePascal}ByIdRepositoryImpl`;
this.loadByIdRepositoryInterfaceName = `Load${this.moduleNamePascal}ByIdRepositoryInterface`;
this.loadByIdFunctionName = `Load${this.moduleNamePascal}ById`;
this.createUseCaseName = `Create${this.moduleNamePascal}Usecase`;
this.updateUseCaseName = `Update${this.moduleNamePascal}Usecase`;
this.deleteUseCaseName = `Delete${this.moduleNamePascal}Usecase`;
this.loadAllUseCaseName = `LoadAll${this.moduleNamePascal}Usecase`;
this.loadByIdUseCaseName = `Load${this.moduleNamePascal}ByIdUsecase`;
this.moduleUsecaseProxyName = `${this.moduleNamePascal}UsecaseProxy`;
this.createPostUseCaseProxyName = `POST_CREATE_${this.moduleNameUpper}_USECASE_PROXY`;
this.updatePostUseCaseProxyName = `POST_UPDATE_${this.moduleNameUpper}_USECASE_PROXY`;
this.deletePostUseCaseProxyName = `POST_DELETE_${this.moduleNameUpper}_USECASE_PROXY`;
this.loadAllPostUseCaseProxyName = `POST_LOAD_ALL_${this.moduleNameUpper}_USECASE_PROXY`;
this.loadByIdPostUseCaseProxyName = `POST_LOAD_BY_ID_${this.moduleNameUpper}_USECASE_PROXY`;
this.createPostUsecaseProxyValue = `create${this.moduleNamePascal}UsecaseProxy`;
this.updatePostUsecaseProxyValue = `update${this.moduleNamePascal}UsecaseProxy`;
this.deletePostUsecaseProxyValue = `delete${this.moduleNamePascal}UsecaseProxy`;
this.loadAllPostUsecaseProxyValue = `loadAll${this.moduleNamePascal}UsecaseProxy`;
this.loadByIdPostUsecaseProxyValue = `load${this.moduleNamePascal}ByIdUsecaseProxy`;
}
async setupDirectories() {
const dirs = [
path.join(this.domainDir, "dtos"),
// path.join(this.domainDir, "repositories"),
path.join(this.domainDir, "models"),
path.join(this.infraDir, "controllers", this.moduleNameLower),
path.join(this.infraDir, "entities"),
path.join(this.infraDir, "repositories", this.moduleNameLower),
// this.useCasesDir,
];
await Promise.all(dirs.map((dir) => createDir(dir)));
}
async setupSubDirectories() {
const dirs = [
{
path: path.join(
this.infraDir,
"repositories",
this.moduleNameLower,
`create${this.moduleNamePascal}`
),
action: this.getCreateActionContent(),
validation: this.getCreateValidationContent(),
name: `create${this.moduleNamePascal}`,
},
{
path: path.join(
this.infraDir,
"repositories",
this.moduleNameLower,
`update${this.moduleNamePascal}`
),
action: this.getUpdateActionContent(),
validation: this.getUpdateValidationContent(),
name: `update${this.moduleNamePascal}`,
},
{
path: path.join(
this.infraDir,
"repositories",
this.moduleNameLower,
`delete${this.moduleNamePascal}`
),
action: this.getDeleteActionContent(),
validation: this.getDeleteValidationContent(),
name: `delete${this.moduleNamePascal}`,
},
{
path: path.join(
this.infraDir,
"repositories",
this.moduleNameLower,
`loadAll${this.moduleNamePascal}`
),
action: this.getLoadAllActionContent(),
validation: this.getLoadAllValidationContent(),
name: `loadAll${this.moduleNamePascal}`,
},
{
path: path.join(
this.infraDir,
"repositories",
this.moduleNameLower,
`load${this.moduleNamePascal}ById`
),
action: this.getLoadByIdActionContent(),
validation: this.getLoadByIdValidationContent(),
name: `load${this.moduleNamePascal}ById`,
},
];
await Promise.all(dirs.map((dir) => createDir(dir.path)));
await Promise.all(
dirs.map((dir) => {
createFile(
`${path.join(dir.path, `${dir.name}.action.ts`)}`,
dir.action
);
createFile(
`${path.join(dir.path, `${dir.name}.validation.ts`)}`,
dir.validation
);
})
);
}
async generateFiles() {
const files = [
{
path: path.join(
this.domainDir,
"dtos",
`${this.moduleNameLower}.dto.ts`
),
content: this.getDtoContent(),
},
{
path: path.join(
this.domainDir,
"repositories",
`${this.moduleNameLower}.interface.ts`
),
content: this.getRepositoryInterfaceContent(),
},
{
path: path.join(
this.domainDir,
"models",
`${this.moduleNameLower}.model.ts`
),
content: this.getModelContent(),
},
{
path: path.join(
this.infraDir,
"controllers",
this.moduleNameLower,
`${this.moduleNameLower}.controller.ts`
),
content: this.getControllerContent(),
},
{
path: path.join(
this.infraDir,
"entities",
`${this.moduleNameLower}.entity.ts`
),
content: this.getEntityContent(),
},
{
path: path.join(
this.infraDir,
"repositories",
this.moduleNameLower,
`${this.moduleNameLower}.repository.ts`
),
content: this.getRepositoryImplContent(),
},
{
path: path.join(this.useCasesDir, `${this.moduleNameLower}.usecase.ts`),
content: this.getUsecaseContent(),
},
{
path: path.join(
this.infraDir,
"usecases-proxy",
`${this.moduleNameLower}.usecase.proxy.ts`
),
content: this.getUsecaseProxyContent(),
},
// {
// path: path.join(
// this.infraDir,
// "repositories",
// this.moduleNameLower,
// `${this.moduleNameLower}.module.ts`
// ),
// content: this.getModuleContent(),
// },
// {
// path: path.join(
// this.infraDir,
// "repositories",
// `repositories.module.ts`
// ),
// content: this.getRepoModuleContent(),
// },
];
await Promise.all(files.map((file) => createFile(file.path, file.content)));
}
converted() {
return this.moduleNameLower.replace(/-([a-z])/g, (_, letter) =>
letter.toUpperCase()
);
}
async updateAppModule() {
const appModulePath = path.join(this.rootDir, "app.module.ts");
const importLine = `import { ${this.moduleNamePascal}Module } from './infrastructure/repositories/${this.moduleNameLower}/${this.moduleNameLower}.module';`;
const moduleEntry = `${this.moduleNamePascal}Module`;
try {
let content = await fs.readFile(appModulePath, "utf8").catch(() => "");
if (!content) {
content = this.getDefaultAppModuleContent(importLine, moduleEntry);
await createFile(appModulePath, content);
return;
}
if (!content.includes(moduleEntry)) {
const lines = content.split("\n");
const importIndex = lines.findIndex((line) =>
line.startsWith("import { Module }")
);
lines.splice(importIndex + 1, 0, importLine);
const importsIndex = lines.findIndex((line) =>
line.includes("imports: [")
);
if (importsIndex !== -1) {
const importLine = lines[importsIndex];
lines[importsIndex] = importLine.includes("[],")
? ` imports: [${moduleEntry}],`
: importLine.replace("imports: [", `imports: [${moduleEntry}, `);
}
await fs.writeFile(appModulePath, lines.join("\n"), "utf8");
console.log(`${COLORS.GREEN}✔ Updated ${appModulePath}${COLORS.NC}`);
} else {
console.log(
`${COLORS.YELLOW}⚠ Module already in ${appModulePath}${COLORS.NC}`
);
}
} catch (error) {
throw new Error(`Failed to update app.module.ts: ${error.message}`);
}
}
async updateRepoModule() {
const repoModulePath = path.join(
this.infraDir,
"repositories",
`repositories.module.ts`
);
const importLine = `import { ${this.repositoryImplName} } from './${this.moduleNameLower}/${this.moduleNameLower}.repository';`;
const importEntityLine = `import { ${this.entityName} } from '@infrastructure/entities/${this.moduleNameLower}.entity';`;
const typeOrmImportLine = `import { TypeOrmModule } from '@nestjs/typeorm';`;
try {
let content = await fs.readFile(repoModulePath, "utf8").catch(() => "");
if (!content) {
content = this.getRepoModuleContent();
await createFile(repoModulePath, content);
console.log(`${COLORS.GREEN}✔ Created ${repoModulePath}${COLORS.NC}`);
return;
}
// Skip if repository is already imported
if (content.includes(importLine)) {
console.log(
`${COLORS.YELLOW}⚠ Repository already in ${repoModulePath}${COLORS.NC}`
);
return;
}
const lines = content.split("\n");
// Add import statements
const importIndex = lines.findIndex((line) =>
line.startsWith("import { Module }")
);
if (importIndex === -1) {
throw new Error("Could not find Module import statement");
}
// Make sure TypeOrmModule is imported
if (!content.includes("TypeOrmModule")) {
lines.splice(
importIndex + 1,
0,
typeOrmImportLine,
importLine,
importEntityLine
);
} else {
lines.splice(importIndex + 1, 0, importLine, importEntityLine);
}
// Update imports array - specifically focusing on TypeOrmModule.forFeature
const importsIndex = lines.findIndex((line) =>
line.includes("imports: [")
);
if (importsIndex !== -1) {
// Find TypeOrmModule.forFeature or add it
const typeOrmFeatureIndex = lines.findIndex((line) =>
line.includes("TypeOrmModule.forFeature([")
);
if (typeOrmFeatureIndex !== -1) {
// TypeOrmModule.forFeature exists, update it to include the new entity
if (!lines[typeOrmFeatureIndex].includes(this.entityName)) {
lines[typeOrmFeatureIndex] = lines[typeOrmFeatureIndex].replace(
"TypeOrmModule.forFeature([",
`TypeOrmModule.forFeature([${this.entityName}, `
);
}
} else {
// TypeOrmModule.forFeature doesn't exist, add it to imports
const importLine = lines[importsIndex];
if (importLine.includes("[],")) {
lines[
importsIndex
] = ` imports: [TypeOrmModule.forFeature([${this.entityName}])],`;
} else {
lines[importsIndex] = importLine.replace(
"imports: [",
`imports: [TypeOrmModule.forFeature([${this.entityName}]), `
);
}
}
} else {
throw new Error("Could not find imports array in module");
}
// Update providers array
const providersIndex = lines.findIndex((line) =>
line.includes("providers: [")
);
if (providersIndex !== -1) {
const providerLine = lines[providersIndex];
if (providerLine.includes("[],")) {
lines[providersIndex] = ` providers: [${this.repositoryImplName}],`;
} else if (!providerLine.includes(this.repositoryImplName)) {
lines[providersIndex] = providerLine.replace(
"providers: [",
`providers: [${this.repositoryImplName}, `
);
}
}
// Update exports array
const exportsIndex = lines.findIndex((line) =>
line.includes("exports: [")
);
if (exportsIndex !== -1) {
const exportLine = lines[exportsIndex];
if (exportLine.includes("[],")) {
lines[exportsIndex] = ` exports: [${this.repositoryImplName}],`;
} else if (!exportLine.includes(this.repositoryImplName)) {
lines[exportsIndex] = exportLine.replace(
"exports: [",
`exports: [${this.repositoryImplName}, `
);
}
}
await fs.writeFile(repoModulePath, lines.join("\n"), "utf8");
console.log(`${COLORS.GREEN}✔ Updated ${repoModulePath}${COLORS.NC}`);
} catch (error) {
console.error(
`${COLORS.RED}Error updating repositories.module.ts: ${error.message}${COLORS.NC}`
);
throw new Error(
`Failed to update repositories.module.ts: ${error.message}`
);
}
}
async updateUseCaseProxyModule() {
const useCaseProxyModulePath = path.join(
this.infraDir,
"usecases-proxy",
"usecases-proxy.module.ts"
);
const importLine = `import { ${this.moduleUsecaseProxyName} } from './${this.moduleNameLower}.usecase.proxy';`;
const proxyProviderLine = `...new ${this.moduleUsecaseProxyName}().providers()`;
// Creating array of static constant definitions
const staticConstantDefs = [
`static ${this.createPostUseCaseProxyName} = '${this.createPostUsecaseProxyValue}';`,
`static ${this.updatePostUseCaseProxyName} = '${this.updatePostUsecaseProxyValue}';`,
`static ${this.deletePostUseCaseProxyName} = '${this.deletePostUsecaseProxyValue}';`,
`static ${this.loadAllPostUseCaseProxyName} = '${this.loadAllPostUsecaseProxyValue}';`,
`static ${this.loadByIdPostUseCaseProxyName} = '${this.loadByIdPostUsecaseProxyValue}';`,
];
// Creating array of export lines
const exportLines = [
`UsecasesProxyModule.${this.createPostUseCaseProxyName},`,
`UsecasesProxyModule.${this.updatePostUseCaseProxyName},`,
`UsecasesProxyModule.${this.deletePostUseCaseProxyName},`,
`UsecasesProxyModule.${this.loadAllPostUseCaseProxyName},`,
`UsecasesProxyModule.${this.loadByIdPostUseCaseProxyName},`,
];
try {
let content = await fs
.readFile(useCaseProxyModulePath, "utf8")
.catch((error) => {
throw new Error(
`usecase-proxy.module.ts not found: ${error.message}`
);
});
let modified = false;
// Check if usecase is already imported
if (content.includes(importLine)) {
// Check if all constants are already defined
const allConstantsExist = staticConstantDefs.every((constant) =>
content.includes(constant.split(" = ")[0])
);
if (allConstantsExist) {
console.log(
`${COLORS.YELLOW}⚠ Usecase already in usecase-proxy.module.ts${COLORS.NC}`
);
return;
}
}
// Add import at the top, after the first import statement
const firstImportEndIndex = content.indexOf(
"\n",
content.indexOf("import")
);
content =
content.substring(0, firstImportEndIndex + 1) +
importLine +
"\n" +
content.substring(firstImportEndIndex + 1);
modified = true;
// Add static constants before the register method
// Find the appropriate location - before static register()
const registerIndex = content.indexOf("static register()");
if (registerIndex === -1) {
throw new Error(
"Could not find register method in usecase-proxy.module.ts"
);
}
// Add comment section and constants
let insertText = "";
// Check if there's already a section for this module type
const sectionComment = `// ${this.moduleNameLower}`;
if (!content.includes(sectionComment)) {
insertText = ` ${sectionComment}\n`;
}
// Add each constant definition
for (const constDef of staticConstantDefs) {
if (!content.includes(constDef.split(" = ")[0])) {
insertText += ` ${constDef}\n`;
}
}
if (insertText) {
insertText += "\n";
// Find the last line before register method
let insertPosition = content.lastIndexOf("\n", registerIndex);
if (content.substring(insertPosition - 1, insertPosition) === "\n") {
// Already has a blank line
insertPosition = content.lastIndexOf("\n", insertPosition - 1);
}
content =
content.substring(0, insertPosition + 1) +
insertText +
content.substring(insertPosition + 1);
modified = true;
}
// Add provider to the providers array
if (!content.includes(proxyProviderLine)) {
const providersStart = content.indexOf(
"providers: [",
content.indexOf("static register()")
);
if (providersStart === -1) {
throw new Error("Could not find providers array in register method");
}
const providersClosingBracket = content.indexOf("]", providersStart);
const lastProvider = content.lastIndexOf(",", providersClosingBracket);
if (lastProvider > providersStart) {
// There are existing providers
content =
content.substring(0, lastProvider + 1) +
"\n " +
proxyProviderLine +
"," +
content.substring(lastProvider + 1);
} else {
// Empty providers array or only one provider without comma
const openingBracket = content.indexOf("[", providersStart);
content =
content.substring(0, openingBracket + 1) +
"\n " +
proxyProviderLine +
",\n " +
content.substring(openingBracket + 1);
}
modified = true;
}
// Add exports to the exports array
const exportsStart = content.indexOf(
"exports: [",
content.indexOf("static register()")
);
if (exportsStart === -1) {
throw new Error("Could not find exports array in register method");
}
// Add comment for export section if it doesn't exist
const exportSectionComment = `// export ${this.moduleNameLower}`;
let exportsToAdd = "";
if (!content.includes(exportSectionComment)) {
exportsToAdd += `\n ${exportSectionComment}`;
}
// Add each export line that doesn't already exist
for (const exportLine of exportLines) {
if (!content.includes(exportLine.split(",")[0])) {
exportsToAdd += `\n ${exportLine}`;
}
}
if (exportsToAdd) {
// Add at the end before closing bracket
const exportsEndIndex = content.indexOf("]", exportsStart);
content =
content.substring(0, exportsEndIndex) +
exportsToAdd +
content.substring(exportsEndIndex);
modified = true;
}
if (modified) {
await fs.writeFile(useCaseProxyModulePath, content, "utf8");
console.log(
`${COLORS.GREEN}✔ Updated usecase-proxy.module.ts${COLORS.NC}`
);
}
} catch (error) {
console.error(
`${COLORS.RED}Error updating usecase-proxy.module.ts: ${error.message}${COLORS.NC}`
);
throw new Error(
`Failed to update usecase-proxy.module.ts: ${error.message}`
);
}
}
async updateControllerModule() {
const controllerModulePath = path.join(
this.infraDir,
"controllers",
"controllers.module.ts"
);
const importLine = `import { ${this.controllerName} } from './${this.moduleNameLower}/${this.moduleNameLower}.controller';`;
try {
// Read the current content of the module file
const fileBuffer = await fs.readFile(controllerModulePath);
let moduleContent = fileBuffer.toString("utf8");
// Check if this controller is already imported
if (!moduleContent.includes(importLine)) {
// Split content into parts
const importSection = moduleContent.substring(
0,
moduleContent.indexOf("@Module")
);
const moduleSection = moduleContent.substring(
moduleContent.indexOf("@Module")
);
// Add new import line if not already there
const updatedImports =
importSection.trim() + "\n" + importLine + "\n\n";
// Find controllers section
const controllersStartIndex = moduleSection.indexOf("controllers:");
const controllersEndIndex = moduleSection.indexOf(
"]",
controllersStartIndex
);
// Extract controller names from current array
const controllersArrayContent = moduleSection
.substring(
moduleSection.indexOf("[", controllersStartIndex) + 1,
controllersEndIndex
)
.trim();
// Build new controllers array
let newControllersArray;
if (!controllersArrayContent) {
// Empty array case
newControllersArray = `[${this.controllerName}]`;
} else if (!controllersArrayContent.includes(this.controllerName)) {
// Add to existing array
const controllers = controllersArrayContent
.split(",")
.map((c) => c.trim())
.filter((c) => c); // Remove empty entries
controllers.push(this.controllerName);
newControllersArray = `[${controllers.join(", ")}]`;
} else {
// Controller already in array
newControllersArray = `[${controllersArrayContent}]`;
}
// Build the start of the module section
const moduleStart = moduleSection.substring(0, controllersStartIndex);
// Build the end of the module section
const moduleEnd = moduleSection.substring(controllersEndIndex + 1);
// Combine everything
const newModuleContent =
updatedImports +
moduleStart +
`controllers: ${newControllersArray}` +
moduleEnd;
// Write the updated content back to the file
await fs.writeFile(controllerModulePath, newModuleContent);
console.log(
`Successfully added ${this.controllerName} to controllers.module.ts`
);
} else {
console.log(
`${this.controllerName} is already in controllers.module.ts`
);
}
} catch (error) {
console.error("Error updating controllers.module.ts:", error);
throw error;
}
}
async updateEntityModule(entityName, entityFilePath) {
const typeOrmModulePath = path.join(
this.infraDir,
"config",
"typeorm",
"typeorm.module.ts"
);
const importLine = `import { ${entityName} } from '${entityFilePath}';`;
try {
// Read the current content of the module file
const fileBuffer = await fs.readFile(typeOrmModulePath);
let moduleContent = fileBuffer.toString("utf8");
// Check if this entity is already imported
if (!moduleContent.includes(importLine)) {
// Split content into parts
const importSection = moduleContent.substring(
0,
moduleContent.indexOf("export const getTypeOrmModuleOptions")
);
const restOfFile = moduleContent.substring(
moduleContent.indexOf("export const getTypeOrmModuleOptions")
);
// Add new import line if not already there
const updatedImports =
importSection.trim() + "\n" + importLine + "\n\n";
// Find entities array in getTypeOrmModuleOptions
const entitiesStartIndex = restOfFile.indexOf("entities:");
const entitiesEndIndex = restOfFile.indexOf("]", entitiesStartIndex);
// Extract entity names from current array
const entitiesArrayContent = restOfFile
.substring(
restOfFile.indexOf("[", entitiesStartIndex) + 1,
entitiesEndIndex
)
.trim();
// Build new entities array
let newEntitiesArray;
if (!entitiesArrayContent) {
// Empty array case
newEntitiesArray = `[${entityName}]`;
} else if (!entitiesArrayContent.includes(entityName)) {
// Add to existing array
const entities = entitiesArrayContent
.split(",")
.map((e) => e.trim())
.filter((e) => e); // Remove empty entries
entities.push(entityName);
newEntitiesArray = `[${entities.join(", ")}]`;
} else {
// Entity already in array
newEntitiesArray = `[${entitiesArrayContent}]`;
}
// Build the start of the function
const functionStart = restOfFile.substring(0, entitiesStartIndex);
// Build the end of the function
const functionEnd = restOfFile.substring(entitiesEndIndex + 1);
// Combine everything
const newModuleContent =
updatedImports +
functionStart +
`entities: ${newEntitiesArray}` +
functionEnd;
// Write the updated content back to the file
await fs.writeFile(typeOrmModulePath, newModuleContent);
console.log(
`Successfully added ${entityName} to typeorm.module.ts entities`
);
} else {
console.log(`${entityName} is already in typeorm.module.ts`);
}
} catch (error) {
console.error("Error updating typeorm.module.ts:", error);
throw error;
}
}
async execute() {
try {
console.log(
`${COLORS.YELLOW}Generating resource: ${this.moduleNameLower}${COLORS.NC}`
);
await this.setupDirectories();
await this.generateFiles();
await this.setupSubDirectories();
await this.updateRepoModule();
await this.updateUseCaseProxyModule();
// await this.updateAppModule();
await this.updateControllerModule();
await this.updateEntityModule(this.entityName, this.entityFilePath);
console.log(
`${COLORS.GREEN}✔ Resource generation completed successfully${COLORS.NC}`
);
} catch (error) {
console.error(`${COLORS.RED}❌ Error:${COLORS.NC} ${error.message}`);
process.exit(1);
}
}
getUsecaseProxyContent() {
return `
import { LoggerService } from '@infrastructure/logger/logger.service';
import { UseCaseProxy } from './usecases-proxy';
import { UsecasesProxyModule } from './usecases-proxy.module';
import { ${this.repositoryImplName} } from './../repositories/${
this.moduleNameLower
}/${this.moduleNameLower}.repository';
import {
${this.createUseCaseName},
${this.updateUseCaseName},
${this.deleteUseCaseName},
${this.loadAllUseCaseName},
${this.loadByIdUseCaseName}
} from '@usecases/${this.moduleNameLower}.usecase';
export class ${this.moduleUsecaseProxyName} {
constructor(){}
providers() {
return [
{
inject:[LoggerService, ${this.repositoryImplName}],
provide: UsecasesProxyModule.${this.createPostUseCaseProxyName},
useFactory: (logger: LoggerService, ${this.converted()}: ${
this.repositoryImplName
}) => {
return new UseCaseProxy(new ${
this.createUseCaseName
}(logger, ${this.converted()}));
}
},
{
inject: [LoggerService, ${this.repositoryImplName}],
provide: UsecasesProxyModule.${this.updatePostUseCaseProxyName},
useFactory: (logger: LoggerService, ${this.converted()}: ${
this.repositoryImplName
}) => {
return new UseCaseProxy(new ${
this.updateUseCaseName
}(logger, ${this.converted()}));
}
},
{
inject: [LoggerService, ${this.repositoryImplName}],
provide: UsecasesProxyModule.${this.deletePostUseCaseProxyName},
useFactory: (logger: LoggerService, ${this.converted()}: ${
this.repositoryImplName
}) => {
return new UseCaseProxy(new ${
this.deleteUseCaseName
}(logger, ${this.converted()}));
}
},
{
inject: [LoggerService, ${this.repositoryImplName}],
provide: UsecasesProxyModule.${this.loadAllPostUseCaseProxyName},
useFactory: (logger: LoggerService, ${this.converted()}: ${
this.repositoryImplName
}) => {
return new UseCaseProxy(new ${
this.loadAllUseCaseName
}(logger, ${this.converted()}));
}
},
{
inject: [LoggerService, ${this.repositoryImplName}],
provide: UsecasesProxyModule.${this.loadByIdPostUseCaseProxyName},
useFactory: (logger: LoggerService, ${this.converted()}: ${
this.repositoryImplName
}) => {
return new UseCaseProxy(new ${
this.loadByIdUseCaseName
}(logger, ${this.converted()}));
}
}
]
}
}
`;
}
getCreateActionContent() {
return `
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
import { ${this.createModelName} } from '@domain/models/${this.moduleNameLower}.model';
import { ${this.entityName} } from '@infrastructure/entities/${this.moduleNameLower}.entity';
import { QueryRunner } from 'typeorm';
export class ${this.createActionName} extends ${this.createModelName} {
constructor(private readonly session: QueryRunner) {
super();
}
public Init(params: ${this.createModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.${this.createFunctionName}();
const response = {
name: this.name,
};
resolve(response);
} catch (error) {
reject(new Error(error));
}
});
}
private InitParams(params: ${this.createModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
// system
// this.backendKey = params.backendKey;
// this.platform = params.platform;
this.name = params.name;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(new Error(error?.message));
}
});
}
private ${this.createFunctionName}(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const model: ${this.createModelName} = {
name: this.name,
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
};
await this.session.manager.insert(${this.entityName}, model);
resolve("${this.moduleNamePascal} created successfully");
} catch (error) {
console.log('ERROR Create', error?.message);
reject(new Error(error?.message));
}
});
}
}
`;
}
getCreateValidationContent() {
return `
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
import { ${this.createModelName} } from '@domain/models/${
this.moduleNameLower
}.model';
import { ${this.entityName} } from '@infrastructure/entities/${
this.moduleNameLower
}.entity';
import { Repository } from 'typeorm';
export class ${this.createValidationName} extends ${this.createModelName} {
constructor(private readonly ${this.converted()}Repository: Repository<${
this.entityName
}>) {
super();
}
public Init(params: ${this.createModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.ValidateParams();
const response = {};
resolve(response);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
private InitParams(params: ${this.createModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
this.name = params.name;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(new Error(error?.message || 'Unknown error'));
}
});
}
private ValidateParams(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
if (!this.name) {
throw new Error('Invalid params');
}
resolve('ValidateParams completed');
} catch (error) {
console.log('ERROR ValidateParams', error?.message);
reject(new Error(error?.message || 'Unknown error'));
}
});
}
}
`;
}
getUpdateActionContent() {
return `
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable prettier/prettier */
import { ${this.updateModelName} } from '@domain/models/${this.moduleNameLower}.model';
import { ${this.entityName} } from '@infrastructure/entities/${this.moduleNameLower}.entity';
import { QueryRunner } from 'typeorm';
export class ${this.updateActionName} extends ${this.updateModelName} {
constructor(private readonly session: QueryRunner) {
super();
}
public Init(params: ${this.updateModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.${this.updateFunctionName}();
const response = {
name: this.name,
};
resolve(response);
} catch (error) {
reject(error instanceof Error ? error : new Error(error));
}
});
}
private InitParams(params: ${this.updateModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
// system
// this.backendKey = params.backendKey;
// this.platform = params.platform;
this.name = params.name;
this._id = params._id;
this.isActive = params.isActive;
this.createdAt = params.createdAt;
this.updatedAt = params.updatedAt;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(error?.message);
}
});
}
private ${this.updateFunctionName}(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const condition = {
_id: this._id,
};
const model: ${this.updateModelName} = {
_id: this._id,
name: this.name,
isActive: this.isActive,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
};
await this.session.manager.update(${this.entityName}, condition, model);
resolve("${this.moduleNamePascal} updated successfully");
} catch (error) {
console.log('ERROR Update', error?.message);
reject(error instanceof Error ? error : new Error(error?.message));
}
});
}
}
`;
}
getUpdateValidationContent() {
return `
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
import { ${this.updateModelName} } from '@domain/models/${
this.moduleNameLower
}.model';
import { ${this.entityName} } from '@infrastructure/entities/${
this.moduleNameLower
}.entity';
import { Repository } from 'typeorm';
export class ${this.updateValidationName} extends ${this.updateModelName} {
constructor(private readonly ${this.converted()}Repository: Repository<${
this.entityName
}>) {
super();
}
public Init(params: ${this.updateModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.ValidateParams();
const response = {};
resolve(response);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
private InitParams(params: ${this.updateModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
this.name = params.name;
this._id = params._id;
this.isActive = params.isActive;
this.createdAt = params.createdAt;
this.updatedAt = params.updatedAt;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(new Error(error?.message));
}
});
}
private ValidateParams(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
if (!this.name) {
throw new Error('Invalid params');
}
resolve('ValidateParams completed');
} catch (error) {
console.log('ERROR ValidateParams', error?.message);
reject(new Error(error?.message));
}
});
}
}
`;
}
getDeleteActionContent() {
return `
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable prettier/prettier */
import { ${this.deleteModelName} } from '@domain/models/${this.moduleNameLower}.model';
import { ${this.entityName} } from '@infrastructure/entities/${this.moduleNameLower}.entity';
import { QueryRunner } from 'typeorm';
export class ${this.deleteActionName} extends ${this.deleteModelName} {
constructor(private readonly session: QueryRunner) {
super();
}
public Init(params: ${this.deleteModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.${this.deleteFunctionName}();
const response = {
_id: this._id,
};
resolve(response);
}
catch (error) {
reject(error instanceof Error ? error : new Error(error));
}
}
);
}
private InitParams(params: ${this.deleteModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
this._id = params._id;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(error?.message);
}
});
}
private ${this.deleteFunctionName}(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const condition = {
_id: this._id,
};
await this.session.manager.delete(${this.entityName}, condition);
resolve("${this.moduleNamePascal} deleted successfully");
} catch (error) {
console.log('ERROR Delete', error?.message);
reject(error instanceof Error ? error : new Error(error?.message));
}
});
}
}
`;
}
getDeleteValidationContent() {
return `
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable prettier/prettier */
import { ${this.deleteModelName} } from '@domain/models/${
this.moduleNameLower
}.model';
import { ${this.entityName} } from '@infrastructure/entities/${
this.moduleNameLower
}.entity';
import { Repository } from 'typeorm';
export class ${this.deleteValidationName} extends ${this.deleteModelName} {
constructor(private readonly ${this.converted()}Repository: Repository<${
this.entityName
}>) {
super();
}
public Init(params: ${this.deleteModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.ValidateParams();
const response = {};
resolve(response);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
private InitParams(params: ${this.deleteModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
this._id = params._id;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(error?.message);
}
});
}
private ValidateParams(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
if (!this._id) {
throw new Error('Invalid params');
}
resolve('ValidateParams completed');
} catch (error) {
console.log('ERROR ValidateParams', error?.message);
reject(error?.message);
}
});
}
}
`;
}
getLoadAllActionContent() {
return `
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable prettier/prettier */
import { ${this.loadAllModelName}, ${this.responseModelName} } from '@domain/models/${this.moduleNameLower}.model';
import { ${this.entityName} } from '@infrastructure/entities/${this.moduleNameLower}.entity';
import { QueryRunner } from 'typeorm';
export class ${this.loadAllActionName} extends ${this.loadAllModelName} {
constructor(private readonly session: QueryRunner) {
super();
}
public Init(): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.${this.loadAllFunctionName}();
const response = {
name: this.name,
};
resolve(response);
} catch (error) {
reject(new Error(error));
}
});
}
private ${this.loadAllFunctionName}(): Promise<${this.responseModelName}[]> {
return new Promise<${this.responseModelName}[]>(async (resolve, reject) => {
try {
const entities = await this.session.manager.find(${this.entityName});
const result: ${this.responseModelName}[] = entities.map((entity) => ({
_id: entity._id,
name: entity.name,
isActive: entity.isActive,
createdAt: entity.createdAt,
updatedAt: entity.updatedAt,
}));
resolve(result);
} catch (error) {
console.log('ERROR LoadAll', error?.message);
reject(new Error(error?.message));
}
});
}
}
`;
}
getLoadAllValidationContent() {
return `
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable prettier/prettier */
import { ${this.loadAllModelName} } from '@domain/models/${
this.moduleNameLower
}.model';
import { ${this.entityName} } from '@infrastructure/entities/${
this.moduleNameLower
}.entity';
import { Repository } from 'typeorm';
export class ${this.loadAllValidationName} extends ${this.loadAllModelName} {
constructor(private readonly ${this.converted()}Repository: Repository<${
this.entityName
}>) {
super();
}
public Init(params: ${this.loadAllModelName}): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
await this.InitParams(params);
await this.ValidateParams();
const response = {};
resolve(response);
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
private InitParams(params: ${this.loadAllModelName}): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
this.name = params.name;
resolve('InitParams completed');
} catch (error) {
console.log('ERROR InitParams', error?.message);
reject(error?.message);
}
});
}
private ValidateParams(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
if (!this.name) {
throw new Error('Invalid params');
}
resolve('ValidateParams completed');
} catch (error) {
console.log('ERROR ValidateParams', error?.message);
reject(error?.message);
}
});
}
}
`;
}
getLoadByIdActionContent() {
return `
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable prettier/