do-ddd
Version:
Automates some of the Domain Driven Design pieces for faster development
195 lines (194 loc) • 7.38 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFiles = void 0;
// src/generator.ts
const ts_morph_1 = require("ts-morph");
const path = require("path");
const fs = require("fs");
function findClassNameAndAddImports(nameOfClass, sourceFiles, currentFile) {
sourceFiles.forEach((sourceFile) => {
const matchingEntity = sourceFile.getClass(nameOfClass) ||
sourceFile.getInterface(nameOfClass) ||
sourceFile.getEnum(nameOfClass);
let foundFilePath = sourceFile.getFilePath();
if (matchingEntity) {
const relativePath = './' +
path
.relative(path.dirname(currentFile.getFilePath()), foundFilePath)
.replace('.ts', '');
// Add import for the found class
currentFile.addImportDeclaration({
moduleSpecifier: relativePath,
namedImports: [nameOfClass]
});
}
});
}
function findTsConfigPath(startPath) {
let currentPath = startPath;
while (currentPath !== path.parse(currentPath).root) {
const tsConfigPath = path.resolve(currentPath, 'tsconfig.json');
if (fs.existsSync(tsConfigPath)) {
return tsConfigPath;
}
currentPath = path.resolve(currentPath, '..');
}
return null;
}
function generateFiles(identifier, repos) {
const tsConfigPath = findTsConfigPath(process.cwd());
if (!tsConfigPath) {
console.error('tsconfig.json not found in the current directory or any parent directories.');
return;
}
// const tsConfigPath = path.resolve(process.cwd(), 'tsconfig.json');
const project = new ts_morph_1.Project({
tsConfigFilePath: tsConfigPath // path.resolve(__dirname, '../tsconfig.json')
});
// Get Project Source Files
const sourceFiles = project.getSourceFiles();
generateUseCase(project, identifier, repos, sourceFiles);
generateController(project, identifier, sourceFiles);
project.save();
}
exports.generateFiles = generateFiles;
function generateUseCase(project, identifier, repos, sourceFiles) {
// Create UseCase file
const useCaseFile = project.createSourceFile(`${identifier}UseCase.ts`);
const requiredSharedClasses = ['UseCase', 'Result', 'ResultError', 'ErrorCodes'];
requiredSharedClasses.map((requiredClass) => {
findClassNameAndAddImports(requiredClass, sourceFiles, useCaseFile);
});
useCaseFile.addImportDeclaration({
moduleSpecifier: `./I${identifier}Request`,
namedImports: [`I${identifier}Request`]
});
useCaseFile.addImportDeclaration({
moduleSpecifier: `./I${identifier}Response`,
namedImports: [`I${identifier}Response`]
});
// Create UseCase class
const useCaseClass = useCaseFile.addClass({
name: `${identifier}UseCase`,
isExported: true,
implements: [`UseCase<I${identifier}Request, I${identifier}Response>`]
});
// Define the interface for repositories
if (repos && repos.length > 0) {
const reposInterface = useCaseFile.addInterface({
name: `I${identifier}Repos`,
isExported: false,
properties: repos.map((repo) => ({
name: repo.toLowerCase(),
type: repo
}))
});
// Iterate through repoNames and search for the class in all source files
repos.forEach((nameOfClass) => {
findClassNameAndAddImports(nameOfClass, sourceFiles, useCaseFile);
});
// Add private repos property
useCaseClass.addProperty({
name: 'repos',
type: reposInterface.getName(),
scope: ts_morph_1.Scope.Private
});
// Add constructor with dependency injection
useCaseClass.addConstructor({
parameters: [
{
name: 'repos',
type: reposInterface.getName()
}
],
scope: ts_morph_1.Scope.Private,
statements: 'this.repos = repos;' //
});
}
// Add execute method
useCaseClass.addMethod({
name: 'execute',
returnType: `Promise<Result<I${identifier}Response>>`,
parameters: [
{
name: 'request',
type: `I${identifier}Request`,
hasQuestionToken: true
}
],
statements: ['// Implementation goes here', 'return new Promise((resolve, reject)=>{})']
});
}
function generateController(project, identifier, sourceFiles) {
// Create Controller file
const controllerFile = project.createSourceFile(`${identifier}Controller.ts`);
const requiredSharedClasses = ['BaseController', 'Result', 'ResultError', 'ErrorCodes'];
requiredSharedClasses.map((requiredClass) => {
findClassNameAndAddImports(requiredClass, sourceFiles, controllerFile);
});
// Create BaseController class - NOT NEEDED
// const baseControllerClass = controllerFile.addClass({
// name: 'BaseController',
// isAbstract: true,
// methods: [
// {
// name: 'executeImpl',
// isAbstract: true,
// returnType: 'Promise<void | any>',
// parameters: [ { name: 'req', type: 'Request' }, { name: 'res', type: 'Response' } ]
// }
// // ... other methods as in the provided example ...
// ]
// });
// Create specific Controller class
const controllerClass = controllerFile.addClass({
name: `${identifier}Controller`,
isExported: true,
extends: 'BaseController',
properties: [
{
name: 'useCase',
type: `${identifier}UseCase`,
scope: ts_morph_1.Scope.Private
}
]
});
// Add constructor to the specific Controller class
controllerClass.addConstructor({
parameters: [
{
name: 'useCase',
type: `${identifier}UseCase`
}
],
statements: ['super()', 'this.useCase = useCase;']
});
// Add executeImpl method to the specific Controller class
controllerClass.addMethod({
name: 'executeImpl',
isAsync: true,
returnType: 'Promise<any>',
parameters: [{ name: 'req', type: 'Request' }, { name: 'res', type: 'Response' }],
statements: [
'const { body } = req;',
'try {',
` const responseOrError = await this.useCase.execute(body);`,
' if (responseOrError.isFailure) {',
' return this.fail(res, responseOrError.error);',
' }',
' return this.ok(res, responseOrError.getValue());',
'} catch (error) {',
' remoteLogger.error("Error while executing", error);',
' return this.fail(res, {',
` code: ErrorCodes.INTERNAL_SERVER_ERROR,`,
' message: (error as Error)?.message,',
' });',
'}'
]
});
controllerFile.addImportDeclarations([
{ moduleSpecifier: 'express', namedImports: ['Request', 'Response'] },
{ moduleSpecifier: `./${identifier}UseCase`, namedImports: [`${identifier}UseCase`] }
// ... other necessary imports ...
]);
}