From cdddaa1a29466d93369d1052ae9db65e0dc8b4c8 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 26 Mar 2024 14:26:16 -0700 Subject: [PATCH 01/17] refactor: restructure all generate scaffold methods --- .vscode/settings.json | 42 +- expressots.config.ts | 6 +- src/generate/cli.ts | 4 +- src/generate/form.ts | 543 ++++++++++++++++-- src/generate/templates/{ => common}/dto.tpl | 0 .../templates/{ => common}/middleware.tpl | 0 .../{module-default.tpl => common/module.tpl} | 0 .../templates/{ => common}/provider.tpl | 0 src/generate/templates/dto-op.tpl | 7 - .../templates/nonopinionated/entity.tpl | 4 + .../controller-service-delete.tpl | 4 +- .../controller-service-get.tpl} | 4 +- .../controller-service-patch.tpl | 4 +- .../controller-service-post.tpl | 4 +- .../controller-service-put.tpl | 4 +- .../opinionated/controller-service.tpl | 10 + .../templates/{ => opinionated}/entity.tpl | 0 .../module-service.tpl} | 0 .../usecase-service.tpl} | 0 src/generate/templates/usecase-post.tpl | 9 - src/generate/utils/opinionated-cmd.ts | 485 ++++++++++++++++ src/utils/cli-ui.ts | 18 +- src/utils/verify-file-exists.ts | 14 +- 23 files changed, 1052 insertions(+), 110 deletions(-) rename src/generate/templates/{ => common}/dto.tpl (100%) rename src/generate/templates/{ => common}/middleware.tpl (100%) rename src/generate/templates/{module-default.tpl => common/module.tpl} (100%) rename src/generate/templates/{ => common}/provider.tpl (100%) delete mode 100644 src/generate/templates/dto-op.tpl create mode 100644 src/generate/templates/nonopinionated/entity.tpl rename src/generate/templates/{ => opinionated}/controller-service-delete.tpl (85%) rename src/generate/templates/{controller-service.tpl => opinionated/controller-service-get.tpl} (85%) rename src/generate/templates/{ => opinionated}/controller-service-patch.tpl (86%) rename src/generate/templates/{ => opinionated}/controller-service-post.tpl (86%) rename src/generate/templates/{ => opinionated}/controller-service-put.tpl (86%) create mode 100644 src/generate/templates/opinionated/controller-service.tpl rename src/generate/templates/{ => opinionated}/entity.tpl (100%) rename src/generate/templates/{module.tpl => opinionated/module-service.tpl} (100%) rename src/generate/templates/{usecase-op.tpl => opinionated/usecase-service.tpl} (100%) delete mode 100644 src/generate/templates/usecase-post.tpl create mode 100644 src/generate/utils/opinionated-cmd.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index df36bac..e7707e1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,23 +1,21 @@ { - "cSpell.ignoreWords": [ - "Expresso" - ], - "workbench.colorCustomizations": { - // "activityBar.foreground": "#38e715", - // "activityBarBadge.background": "#248d0f", - // "activityBarBadge.foreground": "#fcfcfc", - // "sideBar.border": "#38e715", - // "sideBarTitle.foreground": "#38e715", - // "sideBarSectionHeader.border": "#38e715", - // "editorGroupHeader.border": "#38e715", - // "editorGroupHeader.tabsBorder": "#38e715", - // "tab.border": "#38e715", - // "tab.activeBorderTop": "#38e715", - // "panel.border": "#38e715", - // "statusBar.border": "#38e715", - // "statusBar.foreground": "#38e715" - }, - "editor.rulers": [ - 120 - ], -} \ No newline at end of file + "cSpell.ignoreWords": ["Expresso"], + "workbench.colorCustomizations": { + // "activityBar.foreground": "#38e715", + // "activityBarBadge.background": "#248d0f", + // "activityBarBadge.foreground": "#fcfcfc", + // "sideBar.border": "#38e715", + // "sideBarTitle.foreground": "#38e715", + // "sideBarSectionHeader.border": "#38e715", + // "editorGroupHeader.border": "#38e715", + // "editorGroupHeader.tabsBorder": "#38e715", + // "tab.border": "#38e715", + // "tab.activeBorderTop": "#38e715", + // "panel.border": "#38e715", + // "statusBar.border": "#38e715", + // "statusBar.foreground": "#38e715" + }, + "editor.rulers": [120], + "cSpell.words": ["usecase"], + "typescript.tsdk": "node_modules\\typescript\\lib" +} diff --git a/expressots.config.ts b/expressots.config.ts index 4d77fb1..7b91008 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -1,9 +1,9 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { - sourceRoot: "src", - scaffoldPattern: Pattern.KEBAB_CASE, - opinionated: false + sourceRoot: "src", + scaffoldPattern: Pattern.KEBAB_CASE, + opinionated: true, }; export default config; diff --git a/src/generate/cli.ts b/src/generate/cli.ts index bb533d2..4bfc81c 100644 --- a/src/generate/cli.ts +++ b/src/generate/cli.ts @@ -20,7 +20,7 @@ const coerceSchematicAliases = (arg: string) => { return "entity"; case "mo": return "module"; - case "m": + case "mi": return "middleware"; default: return arg; @@ -29,7 +29,7 @@ const coerceSchematicAliases = (arg: string) => { const generateProject = (): CommandModule => { return { - command: "generate [schematic] [path]", + command: "generate [schematic] [path] [method]", describe: "Scaffold a new resource", aliases: ["g"], builder: (yargs: Argv): Argv => { diff --git a/src/generate/form.ts b/src/generate/form.ts index 3ede542..054b2a9 100644 --- a/src/generate/form.ts +++ b/src/generate/form.ts @@ -14,18 +14,65 @@ import { Pattern } from "../types"; import { addControllerToModule } from "../utils/add-controller-to-module"; import { verifyIfFileExists } from "../utils/verify-file-exists"; import { addModuleToContainer } from "../utils/add-module-to-container"; -import { printError } from "../utils/cli-ui"; +import { printError, printGenerateSuccess } from "../utils/cli-ui"; + +/** + * File preparation + * @param schematic + * @param target + * @param method + * @param opinionated + * @param sourceRoot + * @returns the file output + */ +type FilePreparation = { + schematic: string; + target: string; + method: string; + opinionated: boolean; + sourceRoot: string; +}; -function getFileNameWithoutExtension(filePath: string) { - return filePath.split(".")[0]; -} +/** + * File output + * @param path + * @param file + * @param className + * @param moduleName + * @param modulePath + * @param outputPath + * @param folderToScaffold + */ +type FileOutput = { + path: string; + file: string; + className: string; + moduleName: string; + modulePath: string; + outputPath: string; + folderToScaffold: string; + fileName: string; +}; +/** + * Create a template props + * @param schematic + * @param path + * @param method + */ type CreateTemplateProps = { schematic: string; path: string; method: string; }; +/** + * Create a template based on the schematic + * @param schematic + * @param path + * @param method + * @returns the file created + */ export const createTemplate = async ({ schematic, path: target, @@ -33,45 +80,143 @@ export const createTemplate = async ({ }: CreateTemplateProps) => { const { opinionated, sourceRoot } = await Compiler.loadConfig(); - if (sourceRoot === "") { - printError( - "You must specify a source root in your expressots.config.ts", - "sourceRoot", - ); - process.exit(1); - } - - let folderMatch = ""; + let returnFile = ""; if (opinionated) { - folderMatch = schematicFolder(schematic); + returnFile = await opinionatedProcess( + schematic, + target, + method, + opinionated, + sourceRoot, + ); } else { - folderMatch = ""; + // pass folder = "" to avoid the creation of the module + returnFile = await nonOpinionatedProcess(); } - const { path, file, className, moduleName, modulePath } = await splitTarget( - { target, schematic }, - ); + return returnFile; +}; - const usecaseDir = `${sourceRoot}/${folderMatch}`; +async function opinionatedProcess( + schematic: string, + target: string, + method: string, + opinionated: boolean, + sourceRoot: string, +): Promise { + let f: FileOutput = await validateAndPrepareFile({ + schematic, + target, + method, + opinionated, + sourceRoot, + }); + switch (schematic) { + case "service": + await generateControllerService( + f.outputPath, + f.className, + f.path, + method, + f.file, + ); - await verifyIfFileExists(`${usecaseDir}/${path}/${file}`); + f = await validateAndPrepareFile({ + schematic: "usecase", + target, + method, + opinionated, + sourceRoot, + }); - mkdirSync(`${usecaseDir}/${path}`, { recursive: true }); + await generateUseCase( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.fileName, + "./templates/opinionated/usecase-service.tpl", + ); - if (schematic !== "service") { - // add to guarantee that the routing will always be the last part of the path - let routeSchema = ""; + f = await validateAndPrepareFile({ + schematic: "dto", + target, + method, + opinionated, + sourceRoot, + }); + await generateDTO(f.outputPath, f.className, f.moduleName, f.path); - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - routeSchema = path.split("/").pop(); - } else { - routeSchema = path.replace(/\/$/, ""); - } + await printGenerateSuccess("controller", f.file); + await printGenerateSuccess("usecase", f.file); + await printGenerateSuccess("dto", f.file); + break; + case "usecase": + await generateUseCase( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.fileName, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "controller": + await generateController( + f.outputPath, + f.className, + f.path, + method, + f.file, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "dto": + await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + await printGenerateSuccess(schematic, f.file); + break; + case "provider": + await generateProvider( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "entity": + await generateEntity( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "middleware": + await generateMiddleware( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "module": + await generateModule( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + } + + /* if (schematic !== "service") { + // add to guarantee that the routing will always be the last part of the path + const routeSchema = nodePath.basename(path); let templateBasedSchematic = schematic; if (schematic === "module") { @@ -79,7 +224,7 @@ export const createTemplate = async ({ } writeTemplate({ - outputPath: `${usecaseDir}/${path}/${file}`, + outputPath, template: { path: `./templates/${templateBasedSchematic}.tpl`, data: { @@ -151,7 +296,7 @@ export const createTemplate = async ({ } writeTemplate({ - outputPath: `${usecaseDir}/${path}/${schematicFile}`, + outputPath, template: { path: templateBasedMethod, data: { @@ -179,29 +324,29 @@ export const createTemplate = async ({ ) { if (modulePath === "") { moduleExist = existsSync( - `${usecaseDir}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; } else { moduleExist = existsSync( - `${usecaseDir}/${modulePath}/${moduleName}.module.ts`, + `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${modulePath}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`; } } else { moduleExist = existsSync( - `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, ); if (modulePath === "") { moduleExist = existsSync( - `${usecaseDir}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; } else { moduleExist = existsSync( - `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${moduleName}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`; } } @@ -234,20 +379,20 @@ export const createTemplate = async ({ target.includes("//") ) { await addControllerToModule( - `${usecaseDir}/${modulePath}/${moduleName}.module.ts`, + `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, `${className}Controller`, controllerPath, ); } else { if (modulePath === "") { await addControllerToModule( - `${usecaseDir}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}.module.ts`, `${className}Controller`, controllerPath, ); } else { await addControllerToModule( - `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, `${className}Controller`, controllerPath, ); @@ -297,9 +442,305 @@ export const createTemplate = async ({ chalk.greenBright(`[${schematic}]`.padEnd(14)), chalk.bold.white(`${file.split(".")[0]} ${schematic} created! ✔️`), ); + } */ + return f.file; +} + +async function nonOpinionatedProcess(): Promise { + // Non opinionated + return ""; +} + +/* Generate Resource */ + +/** + * Generate a controller service + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + * @param method - The method + * @param file - The file + */ +async function generateControllerService( + outputPath: string, + className: string, + path: string, + method: string, + file: string, +): Promise { + let templateBasedMethod = ""; + + switch (method) { + case "put": + templateBasedMethod = + "./templates/opinionated/controller-service-put.tpl"; + break; + case "patch": + templateBasedMethod = + "./templates/opinionated/controller-service-patch.tpl"; + break; + case "post": + templateBasedMethod = + "./templates/opinionated/controller-service-post.tpl"; + break; + case "delete": + templateBasedMethod = + "./templates/opinionated/controller-service-delete.tpl"; + break; + default: + templateBasedMethod = + "./templates/opinionated/controller-service-get.tpl"; + break; } - return file; -}; + + writeTemplate({ + outputPath, + template: { + path: templateBasedMethod, + data: { + className, + fileName: getFileNameWithoutExtension(file), + useCase: anyCaseToCamelCase(className), + route: path.replace(/\/$/, ""), + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); +} + +/** + * Generate a use case + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + * @param template - The template + */ +async function generateUseCase( + outputPath: string, + className: string, + moduleName: string, + path: string, + fileName: string, + template?: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: template ? template : "./templates/usecase.tpl", + data: { + className, + moduleName, + path, + fileName, + }, + }, + }); +} + +/** + * Generate a controller + * @param outputPath - The output path + * @param className - The class name + * @param path - The path + * @param method - The method + * @param file - The file + */ +async function generateController( + outputPath: string, + className: string, + path: string, + method: string, + file: string, +): Promise { + const templateBasedMethod = + "./templates/opinionated/controller-service.tpl"; + + writeTemplate({ + outputPath, + template: { + path: templateBasedMethod, + data: { + className, + fileName: getFileNameWithoutExtension(file), + useCase: anyCaseToCamelCase(className), + route: path.replace(/\/$/, ""), + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); +} + +/** + * Generate a DTO + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateDTO( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "./templates/common/dto.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a provider + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateProvider( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "./templates/common/provider.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate an entity + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateEntity( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "./templates/opinionated/entity.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a middleware + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateMiddleware( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "./templates/common/middleware.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a module + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateModule( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "./templates/common/module.tpl", + data: { + className, + moduleName: anyCaseToPascalCase(moduleName), + path, + }, + }, + }); +} + +/* Utility functions */ +async function validateAndPrepareFile(fp: FilePreparation) { + if (fp.sourceRoot === "") { + printError( + "You must specify a source root in your expressots.config.ts", + "sourceRoot", + ); + process.exit(1); + } + + const folderSchematic = schematicFolder(fp.schematic); + const folderToScaffold = `${fp.sourceRoot}/${folderSchematic}`; + const { path, file, className, moduleName, modulePath } = await splitTarget( + { + target: fp.target, + schematic: fp.schematic, + }, + ); + const outputPath = `${folderToScaffold}/${path}/${file}`; + await verifyIfFileExists(outputPath, fp.schematic); + mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); + + return { + path, + file, + className, + moduleName, + modulePath, + outputPath, + folderToScaffold, + fileName: getFileNameWithoutExtension(file), + }; +} + +function getFileNameWithoutExtension(filePath: string) { + return filePath.split(".")[0]; +} const splitTarget = async ({ target, @@ -433,6 +874,10 @@ const writeTemplate = ({ ); }; +/** + * Returns the folder where the schematic should be placed + * @param schematic + */ const schematicFolder = (schematic: string): string | undefined => { switch (schematic) { case "usecase": diff --git a/src/generate/templates/dto.tpl b/src/generate/templates/common/dto.tpl similarity index 100% rename from src/generate/templates/dto.tpl rename to src/generate/templates/common/dto.tpl diff --git a/src/generate/templates/middleware.tpl b/src/generate/templates/common/middleware.tpl similarity index 100% rename from src/generate/templates/middleware.tpl rename to src/generate/templates/common/middleware.tpl diff --git a/src/generate/templates/module-default.tpl b/src/generate/templates/common/module.tpl similarity index 100% rename from src/generate/templates/module-default.tpl rename to src/generate/templates/common/module.tpl diff --git a/src/generate/templates/provider.tpl b/src/generate/templates/common/provider.tpl similarity index 100% rename from src/generate/templates/provider.tpl rename to src/generate/templates/common/provider.tpl diff --git a/src/generate/templates/dto-op.tpl b/src/generate/templates/dto-op.tpl deleted file mode 100644 index 767e15d..0000000 --- a/src/generate/templates/dto-op.tpl +++ /dev/null @@ -1,7 +0,0 @@ -export interface I{{className}}RequestDTO { - id: string; -} - -export interface I{{className}}ResponseDTO { } - - diff --git a/src/generate/templates/nonopinionated/entity.tpl b/src/generate/templates/nonopinionated/entity.tpl new file mode 100644 index 0000000..6bf3cb8 --- /dev/null +++ b/src/generate/templates/nonopinionated/entity.tpl @@ -0,0 +1,4 @@ +import { provide } from "inversify-binding-decorators"; + +@provide({{className}}) +export class {{className}} {} diff --git a/src/generate/templates/controller-service-delete.tpl b/src/generate/templates/opinionated/controller-service-delete.tpl similarity index 85% rename from src/generate/templates/controller-service-delete.tpl rename to src/generate/templates/opinionated/controller-service-delete.tpl index e4c9206..7d6c0e2 100644 --- a/src/generate/templates/controller-service-delete.tpl +++ b/src/generate/templates/opinionated/controller-service-delete.tpl @@ -1,5 +1,5 @@ import { BaseController, StatusCode } from "@expressots/core"; -import { controller, {{method}}, param, response } from "@expressots/adapter-express"; +import { controller, Delete, param, response } from "@expressots/adapter-express"; import { Response } from "express"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @@ -10,7 +10,7 @@ export class {{className}}Controller extends BaseController { super(); } - @{{method}}("/:id") + @Delete("/:id") execute(@param("id") id: string, @response() res: Response): I{{className}}ResponseDTO { return this.callUseCase( this.{{useCase}}UseCase.execute(id), diff --git a/src/generate/templates/controller-service.tpl b/src/generate/templates/opinionated/controller-service-get.tpl similarity index 85% rename from src/generate/templates/controller-service.tpl rename to src/generate/templates/opinionated/controller-service-get.tpl index 13790b2..ed8ad1f 100644 --- a/src/generate/templates/controller-service.tpl +++ b/src/generate/templates/opinionated/controller-service-get.tpl @@ -1,5 +1,5 @@ import { BaseController, StatusCode } from "@expressots/core"; -import { controller, {{method}}, response } from "@expressots/adapter-express"; +import { controller, Get, response } from "@expressots/adapter-express"; import { Response } from "express"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; import { I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @@ -10,7 +10,7 @@ export class {{className}}Controller extends BaseController { super(); } - @{{method}}("/") + @Get("/") execute(@response() res: Response): I{{className}}ResponseDTO { return this.callUseCase( this.{{useCase}}UseCase.execute(), diff --git a/src/generate/templates/controller-service-patch.tpl b/src/generate/templates/opinionated/controller-service-patch.tpl similarity index 86% rename from src/generate/templates/controller-service-patch.tpl rename to src/generate/templates/opinionated/controller-service-patch.tpl index d689f08..c7dd160 100644 --- a/src/generate/templates/controller-service-patch.tpl +++ b/src/generate/templates/opinionated/controller-service-patch.tpl @@ -1,5 +1,5 @@ import { BaseController, StatusCode } from "@expressots/core"; -import { controller, {{method}}, body, param, response } from "@expressots/adapter-express"; +import { controller, Patch, body, param, response } from "@expressots/adapter-express"; import { Response } from "express"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @@ -10,7 +10,7 @@ export class {{className}}Controller extends BaseController { super(); } - @{{method}}("/") + @Patch("/") execute( @body() payload: I{{className}}RequestDTO, @response() res: Response, diff --git a/src/generate/templates/controller-service-post.tpl b/src/generate/templates/opinionated/controller-service-post.tpl similarity index 86% rename from src/generate/templates/controller-service-post.tpl rename to src/generate/templates/opinionated/controller-service-post.tpl index 39d54e1..ac8481d 100644 --- a/src/generate/templates/controller-service-post.tpl +++ b/src/generate/templates/opinionated/controller-service-post.tpl @@ -1,5 +1,5 @@ import { BaseController, StatusCode } from "@expressots/core"; -import { controller, {{method}}, body, response } from "@expressots/adapter-express"; +import { controller, Post, body, response } from "@expressots/adapter-express"; import { Response } from "express"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @@ -10,7 +10,7 @@ export class {{className}}Controller extends BaseController { super(); } - @{{method}}("/") + @Post("/") execute(@body() payload: I{{className}}RequestDTO, @response() res: Response): I{{className}}ResponseDTO { return this.callUseCase( this.{{useCase}}UseCase.execute(payload), diff --git a/src/generate/templates/controller-service-put.tpl b/src/generate/templates/opinionated/controller-service-put.tpl similarity index 86% rename from src/generate/templates/controller-service-put.tpl rename to src/generate/templates/opinionated/controller-service-put.tpl index d689f08..806c782 100644 --- a/src/generate/templates/controller-service-put.tpl +++ b/src/generate/templates/opinionated/controller-service-put.tpl @@ -1,5 +1,5 @@ import { BaseController, StatusCode } from "@expressots/core"; -import { controller, {{method}}, body, param, response } from "@expressots/adapter-express"; +import { controller, Put, body, param, response } from "@expressots/adapter-express"; import { Response } from "express"; import { {{className}}UseCase } from "./{{fileName}}.usecase"; import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; @@ -10,7 +10,7 @@ export class {{className}}Controller extends BaseController { super(); } - @{{method}}("/") + @Put("/") execute( @body() payload: I{{className}}RequestDTO, @response() res: Response, diff --git a/src/generate/templates/opinionated/controller-service.tpl b/src/generate/templates/opinionated/controller-service.tpl new file mode 100644 index 0000000..cad0b78 --- /dev/null +++ b/src/generate/templates/opinionated/controller-service.tpl @@ -0,0 +1,10 @@ +import { BaseController } from "@expressots/core"; +import { controller, {{method}} } from "@expressots/adapter-express"; + +@controller("/{{{route}}}") +export class {{className}}Controller extends BaseController { + @{{method}}("/") + execute() { + return "Ok"; + } +} diff --git a/src/generate/templates/entity.tpl b/src/generate/templates/opinionated/entity.tpl similarity index 100% rename from src/generate/templates/entity.tpl rename to src/generate/templates/opinionated/entity.tpl diff --git a/src/generate/templates/module.tpl b/src/generate/templates/opinionated/module-service.tpl similarity index 100% rename from src/generate/templates/module.tpl rename to src/generate/templates/opinionated/module-service.tpl diff --git a/src/generate/templates/usecase-op.tpl b/src/generate/templates/opinionated/usecase-service.tpl similarity index 100% rename from src/generate/templates/usecase-op.tpl rename to src/generate/templates/opinionated/usecase-service.tpl diff --git a/src/generate/templates/usecase-post.tpl b/src/generate/templates/usecase-post.tpl deleted file mode 100644 index 5c391e8..0000000 --- a/src/generate/templates/usecase-post.tpl +++ /dev/null @@ -1,9 +0,0 @@ -import { provide } from "inversify-binding-decorators"; -import { I{{className}}RequestDTO, I{{className}}ResponseDTO } from "./{{fileName}}.dto"; - -@provide({{className}}UseCase) -export class {{className}}UseCase { - execute(payload: I{{className}}RequestDTO): I{{className}}ResponseDTO { - return "Use Case"; - } -} \ No newline at end of file diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts new file mode 100644 index 0000000..260297a --- /dev/null +++ b/src/generate/utils/opinionated-cmd.ts @@ -0,0 +1,485 @@ +/* import * as nodePath from "path"; +import { mkdirSync, readFileSync } from "node:fs"; +import { render } from "mustache"; +import { writeFileSync, existsSync } from "fs"; +import chalk from "chalk"; +import { + anyCaseToCamelCase, + anyCaseToKebabCase, + anyCaseToPascalCase, + anyCaseToLowerCase, +} from "@expressots/boost-ts"; +import Compiler from "../utils/compiler"; +import { Pattern } from "../types"; +import { addControllerToModule } from "../utils/add-controller-to-module"; +import { verifyIfFileExists } from "../utils/verify-file-exists"; +import { addModuleToContainer } from "../utils/add-module-to-container"; +import { printError } from "../utils/cli-ui"; + +function getFileNameWithoutExtension(filePath: string) { + return filePath.split(".")[0]; +} + +type CreateTemplateProps = { + schematic: string; + path: string; + method: string; +}; + +export const createTemplate = async ({ + schematic, + path: target, + method, +}: CreateTemplateProps) => { + const { opinionated, sourceRoot } = await Compiler.loadConfig(); + + if (sourceRoot === "") { + printError( + "You must specify a source root in your expressots.config.ts", + "sourceRoot", + ); + process.exit(1); + } + + let folderMatch = ""; + + if (opinionated) { + folderMatch = schematicFolder(schematic); + } else { + folderMatch = ""; + } + + const { path, file, className, moduleName, modulePath } = await splitTarget( + { target, schematic }, + ); + + console.log(path, file, className, moduleName, modulePath); + console.log(target, schematic); + + const usecaseDir = `${sourceRoot}/${folderMatch}`; + + await verifyIfFileExists(`${usecaseDir}/${path}/${file}`); + + mkdirSync(`${usecaseDir}/${path}`, { recursive: true }); + + if (schematic !== "service") { + // add to guarantee that the routing will always be the last part of the path + let routeSchema = ""; + + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + routeSchema = path.split("/").pop(); + } else { + routeSchema = path.replace(/\/$/, ""); + } + + let templateBasedSchematic = schematic; + if (schematic === "module") { + templateBasedSchematic = "module-default"; + } + + writeTemplate({ + outputPath: `${usecaseDir}/${path}/${file}`, + template: { + path: `./templates/${templateBasedSchematic}.tpl`, + data: { + className, + moduleName: className, + route: routeSchema, + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); + } else { + for await (const resource of ["controller-service", "usecase", "dto"]) { + const currentSchematic = resource.replace( + "controller-service", + "controller", + ); + + const schematicFile = file.replace( + `controller.ts`, + `${currentSchematic}.ts`, + ); + + console.log( + " ", + chalk.greenBright(`[${currentSchematic}]`.padEnd(14)), + chalk.bold.white(`${schematicFile} created! ✔️`), + ); + + let templateBasedMethod = ""; + if (method) { + if ( + resource === "controller-service" || + resource === "controller" + ) { + if (method === "get") + templateBasedMethod = `./templates/${resource}.tpl`; + else + templateBasedMethod = `./templates/${resource}-${method}.tpl`; + } else { + templateBasedMethod = `./templates/${resource}.tpl`; + } + + if (resource === "usecase") { + templateBasedMethod = `./templates/${resource}-op.tpl`; + } + + if (resource === "usecase") { + if (method === "get") + templateBasedMethod = `./templates/${resource}.tpl`; + if (method === "post") + templateBasedMethod = `./templates/${resource}-${method}.tpl`; + } + } else { + templateBasedMethod = `./templates/${resource}.tpl`; + } + + // add to guarantee that the routing will always be the last part of the path + let routeSchema = ""; + + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + routeSchema = path.split("/").pop(); + } else { + routeSchema = path.replace(/\/$/, ""); + } + + writeTemplate({ + outputPath: `${usecaseDir}/${path}/${schematicFile}`, + template: { + path: templateBasedMethod, + data: { + className, + fileName: getFileNameWithoutExtension(file), + useCase: anyCaseToCamelCase(className), + route: routeSchema, //path.replace(/\/$/, ''), + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); + } + } + + // Module generation + if (["controller", "service"].includes(schematic)) { + let moduleExist = false; + let moduleOutPath = ""; + + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + if (modulePath === "") { + moduleExist = existsSync( + `${usecaseDir}/${moduleName}.module.ts`, + ); + moduleOutPath = `${usecaseDir}/${moduleName}.module.ts`; + } else { + moduleExist = existsSync( + `${usecaseDir}/${modulePath}/${moduleName}.module.ts`, + ); + moduleOutPath = `${usecaseDir}/${modulePath}/${moduleName}.module.ts`; + } + } else { + moduleExist = existsSync( + `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + ); + if (modulePath === "") { + moduleExist = existsSync( + `${usecaseDir}/${moduleName}.module.ts`, + ); + moduleOutPath = `${usecaseDir}/${moduleName}.module.ts`; + } else { + moduleExist = existsSync( + `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + ); + moduleOutPath = `${usecaseDir}/${moduleName}/${moduleName}.module.ts`; + } + } + + let controllerPath = "./"; + const pathCount = path.split("/").length; + + if (path === "") { + controllerPath += `${file.slice(0, file.lastIndexOf("."))}`; + } else if (pathCount === 1) { + controllerPath += `${path}/${file.slice(0, file.lastIndexOf("."))}`; + } else if (pathCount === 2) { + controllerPath += `${path.split("/")[1]}/${file.slice( + 0, + file.lastIndexOf("."), + )}`; + } else { + const segments: string[] = path + .split("/") + .filter((segment) => segment !== ""); + controllerPath += `${segments[segments.length - 1]}/${file.slice( + 0, + file.lastIndexOf("."), + )}`; + } + + if (moduleExist) { + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + await addControllerToModule( + `${usecaseDir}/${modulePath}/${moduleName}.module.ts`, + `${className}Controller`, + controllerPath, + ); + } else { + if (modulePath === "") { + await addControllerToModule( + `${usecaseDir}/${moduleName}.module.ts`, + `${className}Controller`, + controllerPath, + ); + } else { + await addControllerToModule( + `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${className}Controller`, + controllerPath, + ); + } + } + } else { + writeTemplate({ + outputPath: moduleOutPath, + template: { + path: `./templates/module.tpl`, + data: { + moduleName: + moduleName[0].toUpperCase() + moduleName.slice(1), + className, + path: controllerPath, + }, + }, + }); + + console.log( + " ", + chalk.greenBright(`[module]`.padEnd(14)), + chalk.bold.white(`${moduleName}.module created! ✔️`), + ); + + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + await addModuleToContainer(moduleName, modulePath, path); + } else { + await addModuleToContainer(moduleName, moduleName, path); + } + } + } + + if (schematic === "service") { + console.log( + " ", + chalk.greenBright(`[${schematic}]`.padEnd(14)), + chalk.bold.yellow(`${file.split(".")[0]} created! ✔️`), + ); + } else { + console.log( + " ", + chalk.greenBright(`[${schematic}]`.padEnd(14)), + chalk.bold.white(`${file.split(".")[0]} ${schematic} created! ✔️`), + ); + } + return file; +}; + +const splitTarget = async ({ + target, + schematic, +}: { + target: string; + schematic: string; +}): Promise<{ + path: string; + file: string; + className: string; + moduleName: string; + modulePath: string; +}> => { + const pathContent: string[] = target + .split("/") + .filter((item) => item !== ""); + const endsWithSlash: boolean = target.endsWith("/"); + let path = ""; + let fileName = ""; + let module = ""; + let modulePath = ""; + + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + if (schematic === "service") schematic = "controller"; + if ( + schematic === "service" || + (schematic === "controller" && pathContent.length > 4) + ) { + printError("Max path depth is 4.", pathContent.join("/")); + process.exit(1); + } + + if (endsWithSlash) { + fileName = pathContent[pathContent.length - 1]; + path = pathContent.join("/"); + module = + pathContent.length == 1 + ? pathContent[pathContent.length - 1] + : pathContent[pathContent.length - 2]; + modulePath = pathContent.slice(0, -1).join("/"); + } else { + fileName = pathContent[pathContent.length - 1]; + path = pathContent.slice(0, -1).join("/"); + module = + pathContent.length == 2 + ? pathContent[pathContent.length - 2] + : pathContent[pathContent.length - 3]; + modulePath = pathContent.slice(0, -2).join("/"); + } + + return { + path, + file: `${await getNameWithScaffoldPattern( + fileName, + )}.${schematic}.ts`, + className: anyCaseToPascalCase(fileName), + moduleName: module, + modulePath, + }; + } else { + if (schematic === "service") schematic = "controller"; + // 1. Extract the name (first part of the target) + const [name, ...remainingPath] = target.split("/"); + // 2. Check if the name is camelCase or kebab-case + const camelCaseRegex = /[A-Z]/; + const kebabCaseRegex = /[_\-\s]+/; + const isCamelCase = camelCaseRegex.test(name); + const isKebabCase = kebabCaseRegex.test(name); + if (isCamelCase || isKebabCase) { + const [wordName, ...path] = name + ?.split(isCamelCase ? /(?=[A-Z])/ : kebabCaseRegex) + .map((word) => word.toLowerCase()); + + return { + path: `${wordName}/${pathEdgeCase(path)}${pathEdgeCase( + remainingPath, + )}`, + file: `${await getNameWithScaffoldPattern( + name, + )}.${schematic}.ts`, + className: anyCaseToPascalCase(name), + moduleName: wordName, + modulePath: pathContent[0].split("-")[1], + }; + } + + // 3. Return the base case + return { + path: "", + file: `${await getNameWithScaffoldPattern(name)}.${schematic}.ts`, + className: anyCaseToPascalCase(name), + moduleName: name, + modulePath: "", + }; + } +}; + +const getHttpMethod = (method: string): string => { + switch (method) { + case "put": + return "Put"; + case "post": + return "Post"; + case "patch": + return "Patch"; + case "delete": + return "Delete"; + default: + return "Get"; + } +}; + +const writeTemplate = ({ + outputPath, + template: { path, data }, +}: { + outputPath: string; + template: { + path: string; + data: Record; + }; +}) => { + writeFileSync( + outputPath, + render(readFileSync(nodePath.join(__dirname, path), "utf8"), data), + ); +}; */ + +/** + * Returns the folder where the schematic should be placed + * @param schematic + */ + +/* +const schematicFolder = (schematic: string): string | undefined => { + switch (schematic) { + case "usecase": + return "useCases"; + case "controller": + return "useCases"; + case "dto": + return "useCases"; + case "service": + return "useCases"; + case "provider": + return "providers"; + case "entity": + return "entities"; + case "middleware": + return "providers/middlewares"; + case "module": + return "useCases"; + } + + return undefined; +}; + +const getNameWithScaffoldPattern = async (name: string) => { + const configObject = await Compiler.loadConfig(); + + switch (configObject.scaffoldPattern) { + case Pattern.LOWER_CASE: + return anyCaseToLowerCase(name); + case Pattern.KEBAB_CASE: + return anyCaseToKebabCase(name); + case Pattern.PASCAL_CASE: + return anyCaseToPascalCase(name); + case Pattern.CAMEL_CASE: + return anyCaseToCamelCase(name); + } +}; + +const pathEdgeCase = (path: string[]): string => { + return `${path.join("/")}${path.length > 0 ? "/" : ""}`; +}; */ diff --git a/src/utils/cli-ui.ts b/src/utils/cli-ui.ts index 163bfd8..e5bb64a 100644 --- a/src/utils/cli-ui.ts +++ b/src/utils/cli-ui.ts @@ -2,6 +2,22 @@ import chalk from "chalk"; export function printError(message: string, component: string): void { console.error( - chalk.red(`\n\n😞 ${message}:`, chalk.white(`[${component}]`)), + chalk.red(`${message}:`, chalk.bold(chalk.white(`[${component}] ❌`))), + ); +} + +export async function printGenerateError(schematic: string, file: string) { + console.error( + " ", + chalk.redBright(`[${schematic}]`.padEnd(14)), + chalk.bold.white(`${file.split(".")[0]} not created! ❌`), + ); +} + +export async function printGenerateSuccess(schematic: string, file: string) { + console.log( + " ", + chalk.greenBright(`[${schematic}]`.padEnd(14)), + chalk.bold.white(`${file.split(".")[0]} created! ✔️`), ); } diff --git a/src/utils/verify-file-exists.ts b/src/utils/verify-file-exists.ts index 453c01d..97b66f5 100644 --- a/src/utils/verify-file-exists.ts +++ b/src/utils/verify-file-exists.ts @@ -1,24 +1,24 @@ import inquirer from "inquirer"; import fs from "node:fs"; -import { printError } from "./cli-ui"; +import { printError, printGenerateError } from "./cli-ui"; -async function verifyIfFileExists(path: string) { +async function verifyIfFileExists(path: string, schematic?: string) { const fileExists = fs.existsSync(path); + const fileName = path.split("/").pop(); if (fileExists) { const answer = await inquirer.prompt([ { type: "confirm", name: "confirm", - message: - "File with this path already exists. Do you want to create it anyway?", + message: `File [${fileName}] exists. Overwrite?`, default: true, }, ]); - - const fileName = path.split("/").pop(); if (!answer.confirm) { - printError("File not created!", fileName); + schematic + ? printGenerateError(schematic, fileName) + : printError("File not created!", fileName); process.exit(1); } } From 0c63f67a7e3da729dc6cceff40a89f6aa3adb7cf Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 26 Mar 2024 14:46:12 -0700 Subject: [PATCH 02/17] refactor: create nonop & op command file --- src/generate/form.ts | 878 +----------------------- src/generate/utils/command-utils.ts | 304 ++++++++ src/generate/utils/nonopininated-cmd.ts | 4 + src/generate/utils/opinionated-cmd.ts | 623 ++++++++++------- 4 files changed, 686 insertions(+), 1123 deletions(-) create mode 100644 src/generate/utils/command-utils.ts create mode 100644 src/generate/utils/nonopininated-cmd.ts diff --git a/src/generate/form.ts b/src/generate/form.ts index 054b2a9..71cfc34 100644 --- a/src/generate/form.ts +++ b/src/generate/form.ts @@ -1,58 +1,6 @@ -import * as nodePath from "path"; -import { mkdirSync, readFileSync } from "node:fs"; -import { render } from "mustache"; -import { writeFileSync, existsSync } from "fs"; -import chalk from "chalk"; -import { - anyCaseToCamelCase, - anyCaseToKebabCase, - anyCaseToPascalCase, - anyCaseToLowerCase, -} from "@expressots/boost-ts"; import Compiler from "../utils/compiler"; -import { Pattern } from "../types"; -import { addControllerToModule } from "../utils/add-controller-to-module"; -import { verifyIfFileExists } from "../utils/verify-file-exists"; -import { addModuleToContainer } from "../utils/add-module-to-container"; -import { printError, printGenerateSuccess } from "../utils/cli-ui"; - -/** - * File preparation - * @param schematic - * @param target - * @param method - * @param opinionated - * @param sourceRoot - * @returns the file output - */ -type FilePreparation = { - schematic: string; - target: string; - method: string; - opinionated: boolean; - sourceRoot: string; -}; - -/** - * File output - * @param path - * @param file - * @param className - * @param moduleName - * @param modulePath - * @param outputPath - * @param folderToScaffold - */ -type FileOutput = { - path: string; - file: string; - className: string; - moduleName: string; - modulePath: string; - outputPath: string; - folderToScaffold: string; - fileName: string; -}; +import { nonOpinionatedProcess } from "./utils/nonopininated-cmd"; +import { opinionatedProcess } from "./utils/opinionated-cmd"; /** * Create a template props @@ -97,825 +45,3 @@ export const createTemplate = async ({ return returnFile; }; - -async function opinionatedProcess( - schematic: string, - target: string, - method: string, - opinionated: boolean, - sourceRoot: string, -): Promise { - let f: FileOutput = await validateAndPrepareFile({ - schematic, - target, - method, - opinionated, - sourceRoot, - }); - switch (schematic) { - case "service": - await generateControllerService( - f.outputPath, - f.className, - f.path, - method, - f.file, - ); - - f = await validateAndPrepareFile({ - schematic: "usecase", - target, - method, - opinionated, - sourceRoot, - }); - - await generateUseCase( - f.outputPath, - f.className, - f.moduleName, - f.path, - f.fileName, - "./templates/opinionated/usecase-service.tpl", - ); - - f = await validateAndPrepareFile({ - schematic: "dto", - target, - method, - opinionated, - sourceRoot, - }); - await generateDTO(f.outputPath, f.className, f.moduleName, f.path); - - await printGenerateSuccess("controller", f.file); - await printGenerateSuccess("usecase", f.file); - await printGenerateSuccess("dto", f.file); - break; - case "usecase": - await generateUseCase( - f.outputPath, - f.className, - f.moduleName, - f.path, - f.fileName, - ); - await printGenerateSuccess(schematic, f.file); - break; - case "controller": - await generateController( - f.outputPath, - f.className, - f.path, - method, - f.file, - ); - await printGenerateSuccess(schematic, f.file); - break; - case "dto": - await generateDTO(f.outputPath, f.className, f.moduleName, f.path); - await printGenerateSuccess(schematic, f.file); - break; - case "provider": - await generateProvider( - f.outputPath, - f.className, - f.moduleName, - f.path, - ); - await printGenerateSuccess(schematic, f.file); - break; - case "entity": - await generateEntity( - f.outputPath, - f.className, - f.moduleName, - f.path, - ); - await printGenerateSuccess(schematic, f.file); - break; - case "middleware": - await generateMiddleware( - f.outputPath, - f.className, - f.moduleName, - f.path, - ); - await printGenerateSuccess(schematic, f.file); - break; - case "module": - await generateModule( - f.outputPath, - f.className, - f.moduleName, - f.path, - ); - await printGenerateSuccess(schematic, f.file); - break; - } - - /* if (schematic !== "service") { - // add to guarantee that the routing will always be the last part of the path - const routeSchema = nodePath.basename(path); - - let templateBasedSchematic = schematic; - if (schematic === "module") { - templateBasedSchematic = "module-default"; - } - - writeTemplate({ - outputPath, - template: { - path: `./templates/${templateBasedSchematic}.tpl`, - data: { - className, - moduleName: className, - route: routeSchema, - construct: anyCaseToKebabCase(className), - method: getHttpMethod(method), - }, - }, - }); - } else { - for await (const resource of ["controller-service", "usecase", "dto"]) { - const currentSchematic = resource.replace( - "controller-service", - "controller", - ); - - const schematicFile = file.replace( - `controller.ts`, - `${currentSchematic}.ts`, - ); - - console.log( - " ", - chalk.greenBright(`[${currentSchematic}]`.padEnd(14)), - chalk.bold.white(`${schematicFile} created! ✔️`), - ); - - let templateBasedMethod = ""; - if (method) { - if ( - resource === "controller-service" || - resource === "controller" - ) { - if (method === "get") - templateBasedMethod = `./templates/${resource}.tpl`; - else - templateBasedMethod = `./templates/${resource}-${method}.tpl`; - } else { - templateBasedMethod = `./templates/${resource}.tpl`; - } - - if (resource === "usecase") { - templateBasedMethod = `./templates/${resource}-op.tpl`; - } - - if (resource === "usecase") { - if (method === "get") - templateBasedMethod = `./templates/${resource}.tpl`; - if (method === "post") - templateBasedMethod = `./templates/${resource}-${method}.tpl`; - } - } else { - templateBasedMethod = `./templates/${resource}.tpl`; - } - - // add to guarantee that the routing will always be the last part of the path - let routeSchema = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - routeSchema = path.split("/").pop(); - } else { - routeSchema = path.replace(/\/$/, ""); - } - - writeTemplate({ - outputPath, - template: { - path: templateBasedMethod, - data: { - className, - fileName: getFileNameWithoutExtension(file), - useCase: anyCaseToCamelCase(className), - route: routeSchema, //path.replace(/\/$/, ''), - construct: anyCaseToKebabCase(className), - method: getHttpMethod(method), - }, - }, - }); - } - } - - // Module generation - if (["controller", "service"].includes(schematic)) { - let moduleExist = false; - let moduleOutPath = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - if (modulePath === "") { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; - } else { - moduleExist = existsSync( - `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`; - } - } else { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, - ); - if (modulePath === "") { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; - } else { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`; - } - } - - let controllerPath = "./"; - const pathCount = path.split("/").length; - - if (path === "") { - controllerPath += `${file.slice(0, file.lastIndexOf("."))}`; - } else if (pathCount === 1) { - controllerPath += `${path}/${file.slice(0, file.lastIndexOf("."))}`; - } else if (pathCount === 2) { - controllerPath += `${path.split("/")[1]}/${file.slice( - 0, - file.lastIndexOf("."), - )}`; - } else { - const segments: string[] = path - .split("/") - .filter((segment) => segment !== ""); - controllerPath += `${segments[segments.length - 1]}/${file.slice( - 0, - file.lastIndexOf("."), - )}`; - } - - if (moduleExist) { - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - await addControllerToModule( - `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, - `${className}Controller`, - controllerPath, - ); - } else { - if (modulePath === "") { - await addControllerToModule( - `${folderToScaffold}/${moduleName}.module.ts`, - `${className}Controller`, - controllerPath, - ); - } else { - await addControllerToModule( - `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, - `${className}Controller`, - controllerPath, - ); - } - } - } else { - writeTemplate({ - outputPath: moduleOutPath, - template: { - path: `./templates/module.tpl`, - data: { - moduleName: - moduleName[0].toUpperCase() + moduleName.slice(1), - className, - path: controllerPath, - }, - }, - }); - - console.log( - " ", - chalk.greenBright(`[module]`.padEnd(14)), - chalk.bold.white(`${moduleName}.module created! ✔️`), - ); - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - await addModuleToContainer(moduleName, modulePath, path); - } else { - await addModuleToContainer(moduleName, moduleName, path); - } - } - } - - if (schematic === "service") { - console.log( - " ", - chalk.greenBright(`[${schematic}]`.padEnd(14)), - chalk.bold.yellow(`${file.split(".")[0]} created! ✔️`), - ); - } else { - console.log( - " ", - chalk.greenBright(`[${schematic}]`.padEnd(14)), - chalk.bold.white(`${file.split(".")[0]} ${schematic} created! ✔️`), - ); - } */ - return f.file; -} - -async function nonOpinionatedProcess(): Promise { - // Non opinionated - return ""; -} - -/* Generate Resource */ - -/** - * Generate a controller service - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - * @param method - The method - * @param file - The file - */ -async function generateControllerService( - outputPath: string, - className: string, - path: string, - method: string, - file: string, -): Promise { - let templateBasedMethod = ""; - - switch (method) { - case "put": - templateBasedMethod = - "./templates/opinionated/controller-service-put.tpl"; - break; - case "patch": - templateBasedMethod = - "./templates/opinionated/controller-service-patch.tpl"; - break; - case "post": - templateBasedMethod = - "./templates/opinionated/controller-service-post.tpl"; - break; - case "delete": - templateBasedMethod = - "./templates/opinionated/controller-service-delete.tpl"; - break; - default: - templateBasedMethod = - "./templates/opinionated/controller-service-get.tpl"; - break; - } - - writeTemplate({ - outputPath, - template: { - path: templateBasedMethod, - data: { - className, - fileName: getFileNameWithoutExtension(file), - useCase: anyCaseToCamelCase(className), - route: path.replace(/\/$/, ""), - construct: anyCaseToKebabCase(className), - method: getHttpMethod(method), - }, - }, - }); -} - -/** - * Generate a use case - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - * @param template - The template - */ -async function generateUseCase( - outputPath: string, - className: string, - moduleName: string, - path: string, - fileName: string, - template?: string, -): Promise { - writeTemplate({ - outputPath, - template: { - path: template ? template : "./templates/usecase.tpl", - data: { - className, - moduleName, - path, - fileName, - }, - }, - }); -} - -/** - * Generate a controller - * @param outputPath - The output path - * @param className - The class name - * @param path - The path - * @param method - The method - * @param file - The file - */ -async function generateController( - outputPath: string, - className: string, - path: string, - method: string, - file: string, -): Promise { - const templateBasedMethod = - "./templates/opinionated/controller-service.tpl"; - - writeTemplate({ - outputPath, - template: { - path: templateBasedMethod, - data: { - className, - fileName: getFileNameWithoutExtension(file), - useCase: anyCaseToCamelCase(className), - route: path.replace(/\/$/, ""), - construct: anyCaseToKebabCase(className), - method: getHttpMethod(method), - }, - }, - }); -} - -/** - * Generate a DTO - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - */ -async function generateDTO( - outputPath: string, - className: string, - moduleName: string, - path: string, -): Promise { - writeTemplate({ - outputPath, - template: { - path: "./templates/common/dto.tpl", - data: { - className, - moduleName, - path, - }, - }, - }); -} - -/** - * Generate a provider - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - */ -async function generateProvider( - outputPath: string, - className: string, - moduleName: string, - path: string, -): Promise { - writeTemplate({ - outputPath, - template: { - path: "./templates/common/provider.tpl", - data: { - className, - moduleName, - path, - }, - }, - }); -} - -/** - * Generate an entity - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - */ -async function generateEntity( - outputPath: string, - className: string, - moduleName: string, - path: string, -): Promise { - writeTemplate({ - outputPath, - template: { - path: "./templates/opinionated/entity.tpl", - data: { - className, - moduleName, - path, - }, - }, - }); -} - -/** - * Generate a middleware - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - */ -async function generateMiddleware( - outputPath: string, - className: string, - moduleName: string, - path: string, -): Promise { - writeTemplate({ - outputPath, - template: { - path: "./templates/common/middleware.tpl", - data: { - className, - moduleName, - path, - }, - }, - }); -} - -/** - * Generate a module - * @param outputPath - The output path - * @param className - The class name - * @param moduleName - The module name - * @param path - The path - */ -async function generateModule( - outputPath: string, - className: string, - moduleName: string, - path: string, -): Promise { - writeTemplate({ - outputPath, - template: { - path: "./templates/common/module.tpl", - data: { - className, - moduleName: anyCaseToPascalCase(moduleName), - path, - }, - }, - }); -} - -/* Utility functions */ -async function validateAndPrepareFile(fp: FilePreparation) { - if (fp.sourceRoot === "") { - printError( - "You must specify a source root in your expressots.config.ts", - "sourceRoot", - ); - process.exit(1); - } - - const folderSchematic = schematicFolder(fp.schematic); - const folderToScaffold = `${fp.sourceRoot}/${folderSchematic}`; - const { path, file, className, moduleName, modulePath } = await splitTarget( - { - target: fp.target, - schematic: fp.schematic, - }, - ); - const outputPath = `${folderToScaffold}/${path}/${file}`; - await verifyIfFileExists(outputPath, fp.schematic); - mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); - - return { - path, - file, - className, - moduleName, - modulePath, - outputPath, - folderToScaffold, - fileName: getFileNameWithoutExtension(file), - }; -} - -function getFileNameWithoutExtension(filePath: string) { - return filePath.split(".")[0]; -} - -const splitTarget = async ({ - target, - schematic, -}: { - target: string; - schematic: string; -}): Promise<{ - path: string; - file: string; - className: string; - moduleName: string; - modulePath: string; -}> => { - const pathContent: string[] = target - .split("/") - .filter((item) => item !== ""); - const endsWithSlash: boolean = target.endsWith("/"); - let path = ""; - let fileName = ""; - let module = ""; - let modulePath = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - if (schematic === "service") schematic = "controller"; - if ( - schematic === "service" || - (schematic === "controller" && pathContent.length > 4) - ) { - printError("Max path depth is 4.", pathContent.join("/")); - process.exit(1); - } - - if (endsWithSlash) { - fileName = pathContent[pathContent.length - 1]; - path = pathContent.join("/"); - module = - pathContent.length == 1 - ? pathContent[pathContent.length - 1] - : pathContent[pathContent.length - 2]; - modulePath = pathContent.slice(0, -1).join("/"); - } else { - fileName = pathContent[pathContent.length - 1]; - path = pathContent.slice(0, -1).join("/"); - module = - pathContent.length == 2 - ? pathContent[pathContent.length - 2] - : pathContent[pathContent.length - 3]; - modulePath = pathContent.slice(0, -2).join("/"); - } - - return { - path, - file: `${await getNameWithScaffoldPattern( - fileName, - )}.${schematic}.ts`, - className: anyCaseToPascalCase(fileName), - moduleName: module, - modulePath, - }; - } else { - if (schematic === "service") schematic = "controller"; - // 1. Extract the name (first part of the target) - const [name, ...remainingPath] = target.split("/"); - // 2. Check if the name is camelCase or kebab-case - const camelCaseRegex = /[A-Z]/; - const kebabCaseRegex = /[_\-\s]+/; - const isCamelCase = camelCaseRegex.test(name); - const isKebabCase = kebabCaseRegex.test(name); - if (isCamelCase || isKebabCase) { - const [wordName, ...path] = name - ?.split(isCamelCase ? /(?=[A-Z])/ : kebabCaseRegex) - .map((word) => word.toLowerCase()); - - return { - path: `${wordName}/${pathEdgeCase(path)}${pathEdgeCase( - remainingPath, - )}`, - file: `${await getNameWithScaffoldPattern( - name, - )}.${schematic}.ts`, - className: anyCaseToPascalCase(name), - moduleName: wordName, - modulePath: pathContent[0].split("-")[1], - }; - } - - // 3. Return the base case - return { - path: "", - file: `${await getNameWithScaffoldPattern(name)}.${schematic}.ts`, - className: anyCaseToPascalCase(name), - moduleName: name, - modulePath: "", - }; - } -}; - -const getHttpMethod = (method: string): string => { - switch (method) { - case "put": - return "Put"; - case "post": - return "Post"; - case "patch": - return "Patch"; - case "delete": - return "Delete"; - default: - return "Get"; - } -}; - -const writeTemplate = ({ - outputPath, - template: { path, data }, -}: { - outputPath: string; - template: { - path: string; - data: Record; - }; -}) => { - writeFileSync( - outputPath, - render(readFileSync(nodePath.join(__dirname, path), "utf8"), data), - ); -}; - -/** - * Returns the folder where the schematic should be placed - * @param schematic - */ -const schematicFolder = (schematic: string): string | undefined => { - switch (schematic) { - case "usecase": - return "useCases"; - case "controller": - return "useCases"; - case "dto": - return "useCases"; - case "service": - return "useCases"; - case "provider": - return "providers"; - case "entity": - return "entities"; - case "middleware": - return "providers/middlewares"; - case "module": - return "useCases"; - } - - return undefined; -}; - -const getNameWithScaffoldPattern = async (name: string) => { - const configObject = await Compiler.loadConfig(); - - switch (configObject.scaffoldPattern) { - case Pattern.LOWER_CASE: - return anyCaseToLowerCase(name); - case Pattern.KEBAB_CASE: - return anyCaseToKebabCase(name); - case Pattern.PASCAL_CASE: - return anyCaseToPascalCase(name); - case Pattern.CAMEL_CASE: - return anyCaseToCamelCase(name); - } -}; - -const pathEdgeCase = (path: string[]): string => { - return `${path.join("/")}${path.length > 0 ? "/" : ""}`; -}; diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts new file mode 100644 index 0000000..1dd3e07 --- /dev/null +++ b/src/generate/utils/command-utils.ts @@ -0,0 +1,304 @@ +import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs"; +import * as nodePath from "node:path"; +import { render } from "mustache"; +import { + anyCaseToCamelCase, + anyCaseToKebabCase, + anyCaseToPascalCase, + anyCaseToLowerCase, +} from "@expressots/boost-ts"; + +import { printError } from "../../utils/cli-ui"; +import { verifyIfFileExists } from "../../utils/verify-file-exists"; +import Compiler from "../../utils/compiler"; +import { Pattern } from "../../types"; + +/** + * File preparation + * @param schematic + * @param target + * @param method + * @param opinionated + * @param sourceRoot + * @returns the file output + */ +export type FilePreparation = { + schematic: string; + target: string; + method: string; + opinionated: boolean; + sourceRoot: string; +}; + +/** + * File output + * @param path + * @param file + * @param className + * @param moduleName + * @param modulePath + * @param outputPath + * @param folderToScaffold + */ +export type FileOutput = { + path: string; + file: string; + className: string; + moduleName: string; + modulePath: string; + outputPath: string; + folderToScaffold: string; + fileName: string; +}; + +/** + * Create a template based on the schematic + * @param fp + * @returns the file created + */ +export async function validateAndPrepareFile(fp: FilePreparation) { + if (fp.sourceRoot === "") { + printError( + "You must specify a source root in your expressots.config.ts", + "sourceRoot", + ); + process.exit(1); + } + + const folderSchematic = schematicFolder(fp.schematic); + const folderToScaffold = `${fp.sourceRoot}/${folderSchematic}`; + const { path, file, className, moduleName, modulePath } = await splitTarget( + { + target: fp.target, + schematic: fp.schematic, + }, + ); + const outputPath = `${folderToScaffold}/${path}/${file}`; + await verifyIfFileExists(outputPath, fp.schematic); + mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); + + return { + path, + file, + className, + moduleName, + modulePath, + outputPath, + folderToScaffold, + fileName: getFileNameWithoutExtension(file), + }; +} + +/** + * Get the file name without the extension + * @param filePath + * @returns the file name + */ +export function getFileNameWithoutExtension(filePath: string) { + return filePath.split(".")[0]; +} + +/** + * Split the target into path, file, class name, module name and module path + * @param target + * @param schematic + * @returns the split target + */ +export const splitTarget = async ({ + target, + schematic, +}: { + target: string; + schematic: string; +}): Promise<{ + path: string; + file: string; + className: string; + moduleName: string; + modulePath: string; +}> => { + const pathContent: string[] = target + .split("/") + .filter((item) => item !== ""); + const endsWithSlash: boolean = target.endsWith("/"); + let path = ""; + let fileName = ""; + let module = ""; + let modulePath = ""; + + if ( + target.includes("/") || + target.includes("\\") || + target.includes("//") + ) { + if (schematic === "service") schematic = "controller"; + if ( + schematic === "service" || + (schematic === "controller" && pathContent.length > 4) + ) { + printError("Max path depth is 4.", pathContent.join("/")); + process.exit(1); + } + + if (endsWithSlash) { + fileName = pathContent[pathContent.length - 1]; + path = pathContent.join("/"); + module = + pathContent.length == 1 + ? pathContent[pathContent.length - 1] + : pathContent[pathContent.length - 2]; + modulePath = pathContent.slice(0, -1).join("/"); + } else { + fileName = pathContent[pathContent.length - 1]; + path = pathContent.slice(0, -1).join("/"); + module = + pathContent.length == 2 + ? pathContent[pathContent.length - 2] + : pathContent[pathContent.length - 3]; + modulePath = pathContent.slice(0, -2).join("/"); + } + + return { + path, + file: `${await getNameWithScaffoldPattern( + fileName, + )}.${schematic}.ts`, + className: anyCaseToPascalCase(fileName), + moduleName: module, + modulePath, + }; + } else { + if (schematic === "service") schematic = "controller"; + // 1. Extract the name (first part of the target) + const [name, ...remainingPath] = target.split("/"); + // 2. Check if the name is camelCase or kebab-case + const camelCaseRegex = /[A-Z]/; + const kebabCaseRegex = /[_\-\s]+/; + const isCamelCase = camelCaseRegex.test(name); + const isKebabCase = kebabCaseRegex.test(name); + if (isCamelCase || isKebabCase) { + const [wordName, ...path] = name + ?.split(isCamelCase ? /(?=[A-Z])/ : kebabCaseRegex) + .map((word) => word.toLowerCase()); + + return { + path: `${wordName}/${pathEdgeCase(path)}${pathEdgeCase( + remainingPath, + )}`, + file: `${await getNameWithScaffoldPattern( + name, + )}.${schematic}.ts`, + className: anyCaseToPascalCase(name), + moduleName: wordName, + modulePath: pathContent[0].split("-")[1], + }; + } + + // 3. Return the base case + return { + path: "", + file: `${await getNameWithScaffoldPattern(name)}.${schematic}.ts`, + className: anyCaseToPascalCase(name), + moduleName: name, + modulePath: "", + }; + } +}; + +/** + * Write the template based on the http method + * @param method - the http method + * @returns decorator - the decorator to be used + */ +export const getHttpMethod = (method: string): string => { + switch (method) { + case "put": + return "Put"; + case "post": + return "Post"; + case "patch": + return "Patch"; + case "delete": + return "Delete"; + default: + return "Get"; + } +}; + +/** + * Write the template based on the schematics + * @param outputPath - the output path + * @param template - the template to be used + * @returns void + */ +export const writeTemplate = ({ + outputPath, + template: { path, data }, +}: { + outputPath: string; + template: { + path: string; + data: Record; + }; +}) => { + writeFileSync( + outputPath, + render(readFileSync(nodePath.join(__dirname, path), "utf8"), data), + ); +}; + +/** + * Returns the folder where the schematic should be placed + * @param schematic + */ +export const schematicFolder = (schematic: string): string | undefined => { + switch (schematic) { + case "usecase": + return "useCases"; + case "controller": + return "useCases"; + case "dto": + return "useCases"; + case "service": + return "useCases"; + case "provider": + return "providers"; + case "entity": + return "entities"; + case "middleware": + return "providers/middlewares"; + case "module": + return "useCases"; + } + + return undefined; +}; + +/** + * Get the name with the scaffold pattern + * @param name + * @returns the name in the scaffold pattern + */ +export const getNameWithScaffoldPattern = async (name: string) => { + const configObject = await Compiler.loadConfig(); + + switch (configObject.scaffoldPattern) { + case Pattern.LOWER_CASE: + return anyCaseToLowerCase(name); + case Pattern.KEBAB_CASE: + return anyCaseToKebabCase(name); + case Pattern.PASCAL_CASE: + return anyCaseToPascalCase(name); + case Pattern.CAMEL_CASE: + return anyCaseToCamelCase(name); + } +}; + +/** + * Get the path edge case + * @param path + * @returns the path edge case from the last element of the path + */ +const pathEdgeCase = (path: string[]): string => { + return `${path.join("/")}${path.length > 0 ? "/" : ""}`; +}; diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts new file mode 100644 index 0000000..d153f8e --- /dev/null +++ b/src/generate/utils/nonopininated-cmd.ts @@ -0,0 +1,4 @@ +export async function nonOpinionatedProcess(): Promise { + // Non opinionated + return ""; +} diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 260297a..0e6b03a 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -1,80 +1,136 @@ -/* import * as nodePath from "path"; -import { mkdirSync, readFileSync } from "node:fs"; -import { render } from "mustache"; -import { writeFileSync, existsSync } from "fs"; -import chalk from "chalk"; import { anyCaseToCamelCase, anyCaseToKebabCase, anyCaseToPascalCase, - anyCaseToLowerCase, } from "@expressots/boost-ts"; -import Compiler from "../utils/compiler"; -import { Pattern } from "../types"; -import { addControllerToModule } from "../utils/add-controller-to-module"; -import { verifyIfFileExists } from "../utils/verify-file-exists"; -import { addModuleToContainer } from "../utils/add-module-to-container"; -import { printError } from "../utils/cli-ui"; - -function getFileNameWithoutExtension(filePath: string) { - return filePath.split(".")[0]; -} - -type CreateTemplateProps = { - schematic: string; - path: string; - method: string; -}; - -export const createTemplate = async ({ - schematic, - path: target, - method, -}: CreateTemplateProps) => { - const { opinionated, sourceRoot } = await Compiler.loadConfig(); - - if (sourceRoot === "") { - printError( - "You must specify a source root in your expressots.config.ts", - "sourceRoot", - ); - process.exit(1); - } - - let folderMatch = ""; - - if (opinionated) { - folderMatch = schematicFolder(schematic); - } else { - folderMatch = ""; - } - - const { path, file, className, moduleName, modulePath } = await splitTarget( - { target, schematic }, - ); +import { printGenerateSuccess } from "../../utils/cli-ui"; +import { + FileOutput, + getFileNameWithoutExtension, + getHttpMethod, + validateAndPrepareFile, + writeTemplate, +} from "./command-utils"; + +export async function opinionatedProcess( + schematic: string, + target: string, + method: string, + opinionated: boolean, + sourceRoot: string, +): Promise { + let f: FileOutput = await validateAndPrepareFile({ + schematic, + target, + method, + opinionated, + sourceRoot, + }); + switch (schematic) { + case "service": + await generateControllerService( + f.outputPath, + f.className, + f.path, + method, + f.file, + ); - console.log(path, file, className, moduleName, modulePath); - console.log(target, schematic); + f = await validateAndPrepareFile({ + schematic: "usecase", + target, + method, + opinionated, + sourceRoot, + }); - const usecaseDir = `${sourceRoot}/${folderMatch}`; + await generateUseCase( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.fileName, + "../templates/opinionated/usecase-service.tpl", + ); - await verifyIfFileExists(`${usecaseDir}/${path}/${file}`); + f = await validateAndPrepareFile({ + schematic: "dto", + target, + method, + opinionated, + sourceRoot, + }); + await generateDTO(f.outputPath, f.className, f.moduleName, f.path); - mkdirSync(`${usecaseDir}/${path}`, { recursive: true }); + await printGenerateSuccess("controller", f.file); + await printGenerateSuccess("usecase", f.file); + await printGenerateSuccess("dto", f.file); + break; + case "usecase": + await generateUseCase( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.fileName, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "controller": + await generateController( + f.outputPath, + f.className, + f.path, + method, + f.file, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "dto": + await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + await printGenerateSuccess(schematic, f.file); + break; + case "provider": + await generateProvider( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "entity": + await generateEntity( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "middleware": + await generateMiddleware( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "module": + await generateModule( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + } - if (schematic !== "service") { + /* if (schematic !== "service") { // add to guarantee that the routing will always be the last part of the path - let routeSchema = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - routeSchema = path.split("/").pop(); - } else { - routeSchema = path.replace(/\/$/, ""); - } + const routeSchema = nodePath.basename(path); let templateBasedSchematic = schematic; if (schematic === "module") { @@ -82,7 +138,7 @@ export const createTemplate = async ({ } writeTemplate({ - outputPath: `${usecaseDir}/${path}/${file}`, + outputPath, template: { path: `./templates/${templateBasedSchematic}.tpl`, data: { @@ -154,7 +210,7 @@ export const createTemplate = async ({ } writeTemplate({ - outputPath: `${usecaseDir}/${path}/${schematicFile}`, + outputPath, template: { path: templateBasedMethod, data: { @@ -182,29 +238,29 @@ export const createTemplate = async ({ ) { if (modulePath === "") { moduleExist = existsSync( - `${usecaseDir}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; } else { moduleExist = existsSync( - `${usecaseDir}/${modulePath}/${moduleName}.module.ts`, + `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${modulePath}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`; } } else { moduleExist = existsSync( - `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, ); if (modulePath === "") { moduleExist = existsSync( - `${usecaseDir}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; } else { moduleExist = existsSync( - `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, ); - moduleOutPath = `${usecaseDir}/${moduleName}/${moduleName}.module.ts`; + moduleOutPath = `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`; } } @@ -237,20 +293,20 @@ export const createTemplate = async ({ target.includes("//") ) { await addControllerToModule( - `${usecaseDir}/${modulePath}/${moduleName}.module.ts`, + `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, `${className}Controller`, controllerPath, ); } else { if (modulePath === "") { await addControllerToModule( - `${usecaseDir}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}.module.ts`, `${className}Controller`, controllerPath, ); } else { await addControllerToModule( - `${usecaseDir}/${moduleName}/${moduleName}.module.ts`, + `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, `${className}Controller`, controllerPath, ); @@ -300,186 +356,259 @@ export const createTemplate = async ({ chalk.greenBright(`[${schematic}]`.padEnd(14)), chalk.bold.white(`${file.split(".")[0]} ${schematic} created! ✔️`), ); - } - return file; -}; - -const splitTarget = async ({ - target, - schematic, -}: { - target: string; - schematic: string; -}): Promise<{ - path: string; - file: string; - className: string; - moduleName: string; - modulePath: string; -}> => { - const pathContent: string[] = target - .split("/") - .filter((item) => item !== ""); - const endsWithSlash: boolean = target.endsWith("/"); - let path = ""; - let fileName = ""; - let module = ""; - let modulePath = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - if (schematic === "service") schematic = "controller"; - if ( - schematic === "service" || - (schematic === "controller" && pathContent.length > 4) - ) { - printError("Max path depth is 4.", pathContent.join("/")); - process.exit(1); - } + } */ + return f.file; +} - if (endsWithSlash) { - fileName = pathContent[pathContent.length - 1]; - path = pathContent.join("/"); - module = - pathContent.length == 1 - ? pathContent[pathContent.length - 1] - : pathContent[pathContent.length - 2]; - modulePath = pathContent.slice(0, -1).join("/"); - } else { - fileName = pathContent[pathContent.length - 1]; - path = pathContent.slice(0, -1).join("/"); - module = - pathContent.length == 2 - ? pathContent[pathContent.length - 2] - : pathContent[pathContent.length - 3]; - modulePath = pathContent.slice(0, -2).join("/"); - } +/* Generate Resource */ - return { - path, - file: `${await getNameWithScaffoldPattern( - fileName, - )}.${schematic}.ts`, - className: anyCaseToPascalCase(fileName), - moduleName: module, - modulePath, - }; - } else { - if (schematic === "service") schematic = "controller"; - // 1. Extract the name (first part of the target) - const [name, ...remainingPath] = target.split("/"); - // 2. Check if the name is camelCase or kebab-case - const camelCaseRegex = /[A-Z]/; - const kebabCaseRegex = /[_\-\s]+/; - const isCamelCase = camelCaseRegex.test(name); - const isKebabCase = kebabCaseRegex.test(name); - if (isCamelCase || isKebabCase) { - const [wordName, ...path] = name - ?.split(isCamelCase ? /(?=[A-Z])/ : kebabCaseRegex) - .map((word) => word.toLowerCase()); - - return { - path: `${wordName}/${pathEdgeCase(path)}${pathEdgeCase( - remainingPath, - )}`, - file: `${await getNameWithScaffoldPattern( - name, - )}.${schematic}.ts`, - className: anyCaseToPascalCase(name), - moduleName: wordName, - modulePath: pathContent[0].split("-")[1], - }; - } - - // 3. Return the base case - return { - path: "", - file: `${await getNameWithScaffoldPattern(name)}.${schematic}.ts`, - className: anyCaseToPascalCase(name), - moduleName: name, - modulePath: "", - }; - } -}; +/** + * Generate a controller service + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + * @param method - The method + * @param file - The file + */ +async function generateControllerService( + outputPath: string, + className: string, + path: string, + method: string, + file: string, +): Promise { + let templateBasedMethod = ""; -const getHttpMethod = (method: string): string => { switch (method) { case "put": - return "Put"; - case "post": - return "Post"; + templateBasedMethod = + "../templates/opinionated/controller-service-put.tpl"; + break; case "patch": - return "Patch"; + templateBasedMethod = + "../templates/opinionated/controller-service-patch.tpl"; + break; + case "post": + templateBasedMethod = + "../templates/opinionated/controller-service-post.tpl"; + break; case "delete": - return "Delete"; + templateBasedMethod = + "../templates/opinionated/controller-service-delete.tpl"; + break; default: - return "Get"; + templateBasedMethod = + "../templates/opinionated/controller-service-get.tpl"; + break; } -}; - -const writeTemplate = ({ - outputPath, - template: { path, data }, -}: { - outputPath: string; - template: { - path: string; - data: Record; - }; -}) => { - writeFileSync( + + writeTemplate({ outputPath, - render(readFileSync(nodePath.join(__dirname, path), "utf8"), data), - ); -}; */ + template: { + path: templateBasedMethod, + data: { + className, + fileName: getFileNameWithoutExtension(file), + useCase: anyCaseToCamelCase(className), + route: path.replace(/\/$/, ""), + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); +} /** - * Returns the folder where the schematic should be placed - * @param schematic + * Generate a use case + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + * @param template - The template */ +async function generateUseCase( + outputPath: string, + className: string, + moduleName: string, + path: string, + fileName: string, + template?: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: template ? template : "../templates/usecase.tpl", + data: { + className, + moduleName, + path, + fileName, + }, + }, + }); +} -/* -const schematicFolder = (schematic: string): string | undefined => { - switch (schematic) { - case "usecase": - return "useCases"; - case "controller": - return "useCases"; - case "dto": - return "useCases"; - case "service": - return "useCases"; - case "provider": - return "providers"; - case "entity": - return "entities"; - case "middleware": - return "providers/middlewares"; - case "module": - return "useCases"; - } +/** + * Generate a controller + * @param outputPath - The output path + * @param className - The class name + * @param path - The path + * @param method - The method + * @param file - The file + */ +async function generateController( + outputPath: string, + className: string, + path: string, + method: string, + file: string, +): Promise { + const templateBasedMethod = + "../templates/opinionated/controller-service.tpl"; + + writeTemplate({ + outputPath, + template: { + path: templateBasedMethod, + data: { + className, + fileName: getFileNameWithoutExtension(file), + useCase: anyCaseToCamelCase(className), + route: path.replace(/\/$/, ""), + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); +} - return undefined; -}; - -const getNameWithScaffoldPattern = async (name: string) => { - const configObject = await Compiler.loadConfig(); - - switch (configObject.scaffoldPattern) { - case Pattern.LOWER_CASE: - return anyCaseToLowerCase(name); - case Pattern.KEBAB_CASE: - return anyCaseToKebabCase(name); - case Pattern.PASCAL_CASE: - return anyCaseToPascalCase(name); - case Pattern.CAMEL_CASE: - return anyCaseToCamelCase(name); - } -}; +/** + * Generate a DTO + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateDTO( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/dto.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a provider + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateProvider( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/provider.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate an entity + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateEntity( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/opinionated/entity.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} -const pathEdgeCase = (path: string[]): string => { - return `${path.join("/")}${path.length > 0 ? "/" : ""}`; -}; */ +/** + * Generate a middleware + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateMiddleware( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/middleware.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a module + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateModule( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/module.tpl", + data: { + className, + moduleName: anyCaseToPascalCase(moduleName), + path, + }, + }, + }); +} From f634c132d7d2fe4ded5116ce594b75980eb320e6 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 26 Mar 2024 22:37:20 -0700 Subject: [PATCH 03/17] refactor: add generate module service scaffold --- User | 5 + expressots.config.ts | 2 +- src/generate/utils/command-utils.ts | 21 +- src/generate/utils/opinionated-cmd.ts | 287 ++++++-------------------- src/utils/add-controller-to-module.ts | 4 +- 5 files changed, 85 insertions(+), 234 deletions(-) create mode 100644 User diff --git a/User b/User new file mode 100644 index 0000000..ecd06eb --- /dev/null +++ b/User @@ -0,0 +1,5 @@ +import { ContainerModule } from "inversify"; +import { CreateModule } from "@expressots/core"; +import { UserCreateController } from "user/create/"; + +export const UserModule: ContainerModule = CreateModule([UserCreateController]); diff --git a/expressots.config.ts b/expressots.config.ts index 7b91008..45b7835 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -2,7 +2,7 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { sourceRoot: "src", - scaffoldPattern: Pattern.KEBAB_CASE, + scaffoldPattern: Pattern.CAMEL_CASE, opinionated: true, }; diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index 1dd3e07..2ee0780 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -73,7 +73,7 @@ export async function validateAndPrepareFile(fp: FilePreparation) { schematic: fp.schematic, }, ); - const outputPath = `${folderToScaffold}/${path}/${file}`; + const outputPath = `${folderToScaffold}/${path}${file}`; await verifyIfFileExists(outputPath, fp.schematic); mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); @@ -302,3 +302,22 @@ export const getNameWithScaffoldPattern = async (name: string) => { const pathEdgeCase = (path: string[]): string => { return `${path.join("/")}${path.length > 0 ? "/" : ""}`; }; + +export async function extractFirstWord(file: string) { + const f = file.split(".")[0]; + + const regex = /(?:-|(?<=[a-z])(?=[A-Z]))/; + const firstWord = f.split(regex)[0]; + + const config = await Compiler.loadConfig(); + switch (config.scaffoldPattern) { + case Pattern.LOWER_CASE: + return anyCaseToLowerCase(firstWord); + case Pattern.KEBAB_CASE: + return anyCaseToKebabCase(firstWord); + case Pattern.PASCAL_CASE: + return anyCaseToPascalCase(firstWord); + case Pattern.CAMEL_CASE: + return anyCaseToCamelCase(firstWord); + } +} diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 0e6b03a..a039afb 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -3,8 +3,10 @@ import { anyCaseToKebabCase, anyCaseToPascalCase, } from "@expressots/boost-ts"; +import * as nodePath from "node:path"; import { printGenerateSuccess } from "../../utils/cli-ui"; import { + extractFirstWord, FileOutput, getFileNameWithoutExtension, getHttpMethod, @@ -62,9 +64,26 @@ export async function opinionatedProcess( }); await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + f = await validateAndPrepareFile({ + schematic: "module", + target, + method, + opinionated, + sourceRoot, + }); + + await generateModuleService( + f.className, + f.moduleName, + f.path, + f.file, + f.folderToScaffold, + ); + await printGenerateSuccess("controller", f.file); await printGenerateSuccess("usecase", f.file); await printGenerateSuccess("dto", f.file); + await printGenerateSuccess("module", f.file); break; case "usecase": await generateUseCase( @@ -128,235 +147,6 @@ export async function opinionatedProcess( break; } - /* if (schematic !== "service") { - // add to guarantee that the routing will always be the last part of the path - const routeSchema = nodePath.basename(path); - - let templateBasedSchematic = schematic; - if (schematic === "module") { - templateBasedSchematic = "module-default"; - } - - writeTemplate({ - outputPath, - template: { - path: `./templates/${templateBasedSchematic}.tpl`, - data: { - className, - moduleName: className, - route: routeSchema, - construct: anyCaseToKebabCase(className), - method: getHttpMethod(method), - }, - }, - }); - } else { - for await (const resource of ["controller-service", "usecase", "dto"]) { - const currentSchematic = resource.replace( - "controller-service", - "controller", - ); - - const schematicFile = file.replace( - `controller.ts`, - `${currentSchematic}.ts`, - ); - - console.log( - " ", - chalk.greenBright(`[${currentSchematic}]`.padEnd(14)), - chalk.bold.white(`${schematicFile} created! ✔️`), - ); - - let templateBasedMethod = ""; - if (method) { - if ( - resource === "controller-service" || - resource === "controller" - ) { - if (method === "get") - templateBasedMethod = `./templates/${resource}.tpl`; - else - templateBasedMethod = `./templates/${resource}-${method}.tpl`; - } else { - templateBasedMethod = `./templates/${resource}.tpl`; - } - - if (resource === "usecase") { - templateBasedMethod = `./templates/${resource}-op.tpl`; - } - - if (resource === "usecase") { - if (method === "get") - templateBasedMethod = `./templates/${resource}.tpl`; - if (method === "post") - templateBasedMethod = `./templates/${resource}-${method}.tpl`; - } - } else { - templateBasedMethod = `./templates/${resource}.tpl`; - } - - // add to guarantee that the routing will always be the last part of the path - let routeSchema = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - routeSchema = path.split("/").pop(); - } else { - routeSchema = path.replace(/\/$/, ""); - } - - writeTemplate({ - outputPath, - template: { - path: templateBasedMethod, - data: { - className, - fileName: getFileNameWithoutExtension(file), - useCase: anyCaseToCamelCase(className), - route: routeSchema, //path.replace(/\/$/, ''), - construct: anyCaseToKebabCase(className), - method: getHttpMethod(method), - }, - }, - }); - } - } - - // Module generation - if (["controller", "service"].includes(schematic)) { - let moduleExist = false; - let moduleOutPath = ""; - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - if (modulePath === "") { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; - } else { - moduleExist = existsSync( - `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`; - } - } else { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, - ); - if (modulePath === "") { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${moduleName}.module.ts`; - } else { - moduleExist = existsSync( - `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, - ); - moduleOutPath = `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`; - } - } - - let controllerPath = "./"; - const pathCount = path.split("/").length; - - if (path === "") { - controllerPath += `${file.slice(0, file.lastIndexOf("."))}`; - } else if (pathCount === 1) { - controllerPath += `${path}/${file.slice(0, file.lastIndexOf("."))}`; - } else if (pathCount === 2) { - controllerPath += `${path.split("/")[1]}/${file.slice( - 0, - file.lastIndexOf("."), - )}`; - } else { - const segments: string[] = path - .split("/") - .filter((segment) => segment !== ""); - controllerPath += `${segments[segments.length - 1]}/${file.slice( - 0, - file.lastIndexOf("."), - )}`; - } - - if (moduleExist) { - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - await addControllerToModule( - `${folderToScaffold}/${modulePath}/${moduleName}.module.ts`, - `${className}Controller`, - controllerPath, - ); - } else { - if (modulePath === "") { - await addControllerToModule( - `${folderToScaffold}/${moduleName}.module.ts`, - `${className}Controller`, - controllerPath, - ); - } else { - await addControllerToModule( - `${folderToScaffold}/${moduleName}/${moduleName}.module.ts`, - `${className}Controller`, - controllerPath, - ); - } - } - } else { - writeTemplate({ - outputPath: moduleOutPath, - template: { - path: `./templates/module.tpl`, - data: { - moduleName: - moduleName[0].toUpperCase() + moduleName.slice(1), - className, - path: controllerPath, - }, - }, - }); - - console.log( - " ", - chalk.greenBright(`[module]`.padEnd(14)), - chalk.bold.white(`${moduleName}.module created! ✔️`), - ); - - if ( - target.includes("/") || - target.includes("\\") || - target.includes("//") - ) { - await addModuleToContainer(moduleName, modulePath, path); - } else { - await addModuleToContainer(moduleName, moduleName, path); - } - } - } - - if (schematic === "service") { - console.log( - " ", - chalk.greenBright(`[${schematic}]`.padEnd(14)), - chalk.bold.yellow(`${file.split(".")[0]} created! ✔️`), - ); - } else { - console.log( - " ", - chalk.greenBright(`[${schematic}]`.padEnd(14)), - chalk.bold.white(`${file.split(".")[0]} ${schematic} created! ✔️`), - ); - } */ return f.file; } @@ -587,6 +377,45 @@ async function generateMiddleware( }); } +/** + * Generate a module for service scaffolding + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateModuleService( + className: string, + moduleName: string, + path: string, + file: string, + folderToScaffold: string, +): Promise { + const newModuleFile = await extractFirstWord(file); + const newModulePath = nodePath + .join(folderToScaffold, path, "..") + .normalize(); + const newModuleOutputPath = `${newModulePath}/${newModuleFile}.module.ts`; + + const controllerPathLength = path.split("/").length - 1 - 1; + const controllerPath = path.split("/")[controllerPathLength]; + const controllerFileName = `./${controllerPath}/${file + .replace("module", "controller") + .replace(".ts", "")}`; + + writeTemplate({ + outputPath: newModuleOutputPath, + template: { + path: "../templates/opinionated/module-service.tpl", + data: { + className, + moduleName: anyCaseToPascalCase(moduleName), + path: controllerFileName, + }, + }, + }); +} + /** * Generate a module * @param outputPath - The output path diff --git a/src/utils/add-controller-to-module.ts b/src/utils/add-controller-to-module.ts index dce6560..362078b 100644 --- a/src/utils/add-controller-to-module.ts +++ b/src/utils/add-controller-to-module.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; -async function addControllerToModule( +export async function addControllerToModule( filePath: string, controllerName: string, controllerPath: string, @@ -54,5 +54,3 @@ async function addControllerToModule( await fs.promises.writeFile(filePath, newFileContent, "utf8"); } - -export { addControllerToModule }; From 1b7b94a04671b3c0c06ca42d3015cf00ce00da60 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Tue, 26 Mar 2024 23:37:39 -0700 Subject: [PATCH 04/17] refactor: add controller service to module --- expressots.config.ts | 2 +- src/generate/utils/command-utils.ts | 5 +++++ src/generate/utils/opinionated-cmd.ts | 23 ++++++++++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/expressots.config.ts b/expressots.config.ts index 45b7835..7b91008 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -2,7 +2,7 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { sourceRoot: "src", - scaffoldPattern: Pattern.CAMEL_CASE, + scaffoldPattern: Pattern.KEBAB_CASE, opinionated: true, }; diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index 2ee0780..e345ef0 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -303,6 +303,11 @@ const pathEdgeCase = (path: string[]): string => { return `${path.join("/")}${path.length > 0 ? "/" : ""}`; }; +/** + * Extract the first word from a file and convert it to the scaffold pattern + * @param file + * @returns the first word in the scaffold pattern + */ export async function extractFirstWord(file: string) { const f = file.split(".")[0]; diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index a039afb..1af8593 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -4,6 +4,7 @@ import { anyCaseToPascalCase, } from "@expressots/boost-ts"; import * as nodePath from "node:path"; +import fs from "fs"; import { printGenerateSuccess } from "../../utils/cli-ui"; import { extractFirstWord, @@ -13,6 +14,7 @@ import { validateAndPrepareFile, writeTemplate, } from "./command-utils"; +import { addControllerToModule } from "../../utils/add-controller-to-module"; export async function opinionatedProcess( schematic: string, @@ -395,13 +397,28 @@ async function generateModuleService( const newModulePath = nodePath .join(folderToScaffold, path, "..") .normalize(); - const newModuleOutputPath = `${newModulePath}/${newModuleFile}.module.ts`; + const newModuleName = `${newModuleFile}.module.ts`; + const newModuleOutputPath = `${newModulePath}/${newModuleName}`; const controllerPathLength = path.split("/").length - 1 - 1; const controllerPath = path.split("/")[controllerPathLength]; - const controllerFileName = `./${controllerPath}/${file + const controllerName = file .replace("module", "controller") - .replace(".ts", "")}`; + .replace(".ts", ""); + const controllerFileName = `./${controllerPath}/${controllerName}`; + const controllerFullPath = nodePath + .join(folderToScaffold, path, "..", newModuleName) + .normalize(); + + // Verify if the module file is already created + if (fs.existsSync(newModuleOutputPath)) { + await addControllerToModule( + controllerFullPath, + `${className}Controller`, + controllerFileName, + ); + return; + } writeTemplate({ outputPath: newModuleOutputPath, From c2768fb652c901080602650c45ebd662f17f8d2e Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Wed, 27 Mar 2024 00:22:57 -0700 Subject: [PATCH 05/17] refactor: add module to container --- src/generate/utils/opinionated-cmd.ts | 8 +++++++- src/utils/add-module-to-container.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 1af8593..0a3b550 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -15,6 +15,7 @@ import { writeTemplate, } from "./command-utils"; import { addControllerToModule } from "../../utils/add-controller-to-module"; +import { addModuleToContainer } from "../../utils/add-module-to-container"; export async function opinionatedProcess( schematic: string, @@ -410,7 +411,6 @@ async function generateModuleService( .join(folderToScaffold, path, "..", newModuleName) .normalize(); - // Verify if the module file is already created if (fs.existsSync(newModuleOutputPath)) { await addControllerToModule( controllerFullPath, @@ -431,6 +431,12 @@ async function generateModuleService( }, }, }); + + await addModuleToContainer( + anyCaseToPascalCase(moduleName), + `${moduleName}/${file.replace(".ts", "")}`, + path, + ); } /** diff --git a/src/utils/add-module-to-container.ts b/src/utils/add-module-to-container.ts index f913eea..f83789f 100644 --- a/src/utils/add-module-to-container.ts +++ b/src/utils/add-module-to-container.ts @@ -90,7 +90,7 @@ async function addModuleToContainer( if (!modulePathRegex.test(modulePath)) { if (path.split("/").length > 1) { - newImport = `import { ${moduleName}Module } from "${usecaseDir}${modulePath}/${name}.module";`; + newImport = `import { ${moduleName}Module } from "${usecaseDir}${name.toLowerCase()}/${name.toLowerCase()}.module";`; } else { newImport = `import { ${moduleName}Module } from "${usecaseDir}${name}.module";`; } From 249c1bdde2ad9187f05f85e43f25bd1b92f3c162 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Wed, 27 Mar 2024 14:28:25 -0700 Subject: [PATCH 06/17] refactor: redo all nonop generator resources --- .vscode/settings.json | 2 +- User | 5 - expressots.config.ts | 2 +- src/generate/form.ts | 10 +- .../{ => nonopinionated}/controller.tpl | 2 +- .../{ => nonopinionated}/usecase.tpl | 0 src/generate/utils/command-utils.ts | 8 +- src/generate/utils/nonopininated-cmd.ts | 348 +++++++++++++++++- src/generate/utils/opinionated-cmd.ts | 2 - 9 files changed, 362 insertions(+), 17 deletions(-) delete mode 100644 User rename src/generate/templates/{ => nonopinionated}/controller.tpl (77%) rename src/generate/templates/{ => nonopinionated}/usecase.tpl (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index e7707e1..0b14051 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,6 @@ // "statusBar.foreground": "#38e715" }, "editor.rulers": [120], - "cSpell.words": ["usecase"], + "cSpell.words": ["nonopinionated", "usecase"], "typescript.tsdk": "node_modules\\typescript\\lib" } diff --git a/User b/User deleted file mode 100644 index ecd06eb..0000000 --- a/User +++ /dev/null @@ -1,5 +0,0 @@ -import { ContainerModule } from "inversify"; -import { CreateModule } from "@expressots/core"; -import { UserCreateController } from "user/create/"; - -export const UserModule: ContainerModule = CreateModule([UserCreateController]); diff --git a/expressots.config.ts b/expressots.config.ts index 7b91008..b6b99c8 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -3,7 +3,7 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, - opinionated: true, + opinionated: false, }; export default config; diff --git a/src/generate/form.ts b/src/generate/form.ts index 71cfc34..2f19287 100644 --- a/src/generate/form.ts +++ b/src/generate/form.ts @@ -1,3 +1,4 @@ +import { s } from "vitest/dist/reporters-cb94c88b"; import Compiler from "../utils/compiler"; import { nonOpinionatedProcess } from "./utils/nonopininated-cmd"; import { opinionatedProcess } from "./utils/opinionated-cmd"; @@ -39,8 +40,13 @@ export const createTemplate = async ({ sourceRoot, ); } else { - // pass folder = "" to avoid the creation of the module - returnFile = await nonOpinionatedProcess(); + returnFile = await nonOpinionatedProcess( + schematic, + target, + method, + opinionated, + sourceRoot, + ); } return returnFile; diff --git a/src/generate/templates/controller.tpl b/src/generate/templates/nonopinionated/controller.tpl similarity index 77% rename from src/generate/templates/controller.tpl rename to src/generate/templates/nonopinionated/controller.tpl index cad0b78..53e596a 100644 --- a/src/generate/templates/controller.tpl +++ b/src/generate/templates/nonopinionated/controller.tpl @@ -2,7 +2,7 @@ import { BaseController } from "@expressots/core"; import { controller, {{method}} } from "@expressots/adapter-express"; @controller("/{{{route}}}") -export class {{className}}Controller extends BaseController { +export class {{className}}Controller { @{{method}}("/") execute() { return "Ok"; diff --git a/src/generate/templates/usecase.tpl b/src/generate/templates/nonopinionated/usecase.tpl similarity index 100% rename from src/generate/templates/usecase.tpl rename to src/generate/templates/nonopinionated/usecase.tpl diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index e345ef0..fad406a 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -65,7 +65,11 @@ export async function validateAndPrepareFile(fp: FilePreparation) { process.exit(1); } - const folderSchematic = schematicFolder(fp.schematic); + let folderSchematic = ""; + if (fp.opinionated) { + folderSchematic = schematicFolder(fp.schematic); + } + const folderToScaffold = `${fp.sourceRoot}/${folderSchematic}`; const { path, file, className, moduleName, modulePath } = await splitTarget( { @@ -73,7 +77,7 @@ export async function validateAndPrepareFile(fp: FilePreparation) { schematic: fp.schematic, }, ); - const outputPath = `${folderToScaffold}/${path}${file}`; + const outputPath = `${folderToScaffold}/${path}/${file}`; await verifyIfFileExists(outputPath, fp.schematic); mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts index d153f8e..1336e33 100644 --- a/src/generate/utils/nonopininated-cmd.ts +++ b/src/generate/utils/nonopininated-cmd.ts @@ -1,4 +1,346 @@ -export async function nonOpinionatedProcess(): Promise { - // Non opinionated - return ""; +import { + anyCaseToCamelCase, + anyCaseToKebabCase, + anyCaseToPascalCase, +} from "@expressots/boost-ts"; + +import { printGenerateSuccess } from "../../utils/cli-ui"; +import { + FileOutput, + getFileNameWithoutExtension, + getHttpMethod, + validateAndPrepareFile, + writeTemplate, +} from "./command-utils"; + +export async function nonOpinionatedProcess( + schematic: string, + target: string, + method: string, + opinionated: boolean, + sourceRoot: string, +): Promise { + let f: FileOutput = await validateAndPrepareFile({ + schematic, + target, + method, + opinionated, + sourceRoot, + }); + switch (schematic) { + case "service": + await generateController( + f.outputPath, + f.className, + f.path, + method, + f.file, + ); + + f = await validateAndPrepareFile({ + schematic: "usecase", + target, + method, + opinionated, + sourceRoot, + }); + await generateUseCase( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.fileName, + "../templates/nonopinionated/usecase.tpl", + ); + + f = await validateAndPrepareFile({ + schematic: "dto", + target, + method, + opinionated, + sourceRoot, + }); + await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + + f = await validateAndPrepareFile({ + schematic: "module", + target, + method, + opinionated, + sourceRoot, + }); + await generateModule( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + + await printGenerateSuccess("controller", f.file); + await printGenerateSuccess("usecase", f.file); + await printGenerateSuccess("dto", f.file); + await printGenerateSuccess("module", f.file); + break; + case "usecase": + await generateUseCase( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.fileName, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "controller": + await generateController( + f.outputPath, + f.className, + f.path, + method, + f.file, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "dto": + await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + await printGenerateSuccess(schematic, f.file); + break; + case "provider": + await generateProvider( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "entity": + await generateEntity( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "middleware": + await generateMiddleware( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + case "module": + await generateModule( + f.outputPath, + f.className, + f.moduleName, + f.path, + ); + await printGenerateSuccess(schematic, f.file); + break; + } + + return f.file; +} + +/* Generate Resource */ +/** + * Generate a use case + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + * @param template - The template + */ +async function generateUseCase( + outputPath: string, + className: string, + moduleName: string, + path: string, + fileName: string, + template?: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: template + ? template + : "../templates/nonopinionated/usecase.tpl", + data: { + className, + moduleName, + path, + fileName, + }, + }, + }); +} + +/** + * Generate a controller + * @param outputPath - The output path + * @param className - The class name + * @param path - The path + * @param method - The method + * @param file - The file + */ +async function generateController( + outputPath: string, + className: string, + path: string, + method: string, + file: string, +): Promise { + const templateBasedMethod = "../templates/nonopinionated/controller.tpl"; + writeTemplate({ + outputPath, + template: { + path: templateBasedMethod, + data: { + className, + fileName: getFileNameWithoutExtension(file), + useCase: anyCaseToCamelCase(className), + route: className + ? className.toLowerCase() + : path.replace(/\/$/, ""), + construct: anyCaseToKebabCase(className), + method: getHttpMethod(method), + }, + }, + }); +} + +/** + * Generate a DTO + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateDTO( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/dto.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a provider + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateProvider( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/provider.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate an entity + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateEntity( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/nonopinionated/entity.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a middleware + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateMiddleware( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/middleware.tpl", + data: { + className, + moduleName, + path, + }, + }, + }); +} + +/** + * Generate a module + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ +async function generateModule( + outputPath: string, + className: string, + moduleName: string, + path: string, +): Promise { + writeTemplate({ + outputPath, + template: { + path: "../templates/common/module.tpl", + data: { + className, + moduleName: className + ? anyCaseToPascalCase(className) + : anyCaseToPascalCase(moduleName), + path, + }, + }, + }); } diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 0a3b550..3fff7f0 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -48,7 +48,6 @@ export async function opinionatedProcess( opinionated, sourceRoot, }); - await generateUseCase( f.outputPath, f.className, @@ -74,7 +73,6 @@ export async function opinionatedProcess( opinionated, sourceRoot, }); - await generateModuleService( f.className, f.moduleName, From 964804f05d2d234f2ab87cf02ac158a583d2d102 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Wed, 27 Mar 2024 21:38:05 -0700 Subject: [PATCH 07/17] feat: add expressotsconfig scaffoldName schematics changeable by user --- expressots.config.ts | 9 ++ src/@types/config.ts | 9 ++ src/generate/form.ts | 10 +-- src/generate/templates/common/dto.tpl | 4 +- src/generate/templates/common/middleware.tpl | 4 +- src/generate/templates/common/module.tpl | 2 +- src/generate/templates/common/provider.tpl | 4 +- .../templates/nonopinionated/controller.tpl | 4 +- .../templates/nonopinionated/entity.tpl | 4 +- .../templates/nonopinionated/usecase.tpl | 6 +- src/generate/utils/command-utils.ts | 23 +++-- src/generate/utils/nonopininated-cmd.ts | 84 +++++++++++++------ src/generate/utils/opinionated-cmd.ts | 16 ++-- 13 files changed, 118 insertions(+), 61 deletions(-) diff --git a/expressots.config.ts b/expressots.config.ts index b6b99c8..fb905f8 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -4,6 +4,15 @@ const config: ExpressoConfig = { sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, opinionated: false, + scaffoldSchematics: { + entity: "model", + provider: "adapter", + controller: "controller", + usecase: "operation", + dto: "payload", + module: "group", + middleware: "exjs", + }, }; export default config; diff --git a/src/@types/config.ts b/src/@types/config.ts index 14da83b..9d788c4 100644 --- a/src/@types/config.ts +++ b/src/@types/config.ts @@ -28,4 +28,13 @@ export interface ExpressoConfig { sourceRoot: string; opinionated: boolean; providers?: IProviders; + scaffoldSchematics?: { + entity?: string; + controller?: string; + usecase?: string; + dto?: string; + module?: string; + provider?: string; + middleware?: string; + }; } diff --git a/src/generate/form.ts b/src/generate/form.ts index 2f19287..db33394 100644 --- a/src/generate/form.ts +++ b/src/generate/form.ts @@ -27,25 +27,23 @@ export const createTemplate = async ({ path: target, method, }: CreateTemplateProps) => { - const { opinionated, sourceRoot } = await Compiler.loadConfig(); + const config = await Compiler.loadConfig(); let returnFile = ""; - if (opinionated) { + if (config.opinionated) { returnFile = await opinionatedProcess( schematic, target, method, - opinionated, - sourceRoot, + config, ); } else { returnFile = await nonOpinionatedProcess( schematic, target, method, - opinionated, - sourceRoot, + config, ); } diff --git a/src/generate/templates/common/dto.tpl b/src/generate/templates/common/dto.tpl index 91c7cd7..5fe9346 100644 --- a/src/generate/templates/common/dto.tpl +++ b/src/generate/templates/common/dto.tpl @@ -1,3 +1,3 @@ -export interface I{{className}}RequestDTO {} +export interface I{{className}}Request{{schematic}} {} -export interface I{{className}}ResponseDTO {} +export interface I{{className}}Response{{schematic}} {} diff --git a/src/generate/templates/common/middleware.tpl b/src/generate/templates/common/middleware.tpl index 1dac6d1..a96c4b7 100644 --- a/src/generate/templates/common/middleware.tpl +++ b/src/generate/templates/common/middleware.tpl @@ -2,8 +2,8 @@ import { ExpressoMiddleware } from "@expressots/core"; import { NextFunction, Request, Response } from "express"; import { provide } from "inversify-binding-decorators"; -@provide({{className}}Middleware) -export class {{className}}Middleware extends ExpressoMiddleware { +@provide({{className}}{{schematic}}) +export class {{className}}{{schematic}} extends ExpressoMiddleware { use(req: Request, res: Response, next: NextFunction): void | Promise { throw new Error("Method not implemented."); } diff --git a/src/generate/templates/common/module.tpl b/src/generate/templates/common/module.tpl index 45df72d..beef305 100644 --- a/src/generate/templates/common/module.tpl +++ b/src/generate/templates/common/module.tpl @@ -1,4 +1,4 @@ import { ContainerModule } from "inversify"; import { CreateModule } from "@expressots/core"; -export const {{moduleName}}Module: ContainerModule = CreateModule([]); +export const {{moduleName}}{{schematic}}: ContainerModule = CreateModule([]); diff --git a/src/generate/templates/common/provider.tpl b/src/generate/templates/common/provider.tpl index b27ed01..808f1d1 100644 --- a/src/generate/templates/common/provider.tpl +++ b/src/generate/templates/common/provider.tpl @@ -1,4 +1,4 @@ import { provide } from "inversify-binding-decorators"; -@provide({{className}}Provider) -export class {{className}}Provider {} \ No newline at end of file +@provide({{className}}{{schematic}}) +export class {{className}}{{schematic}} {} \ No newline at end of file diff --git a/src/generate/templates/nonopinionated/controller.tpl b/src/generate/templates/nonopinionated/controller.tpl index 53e596a..6aad6e4 100644 --- a/src/generate/templates/nonopinionated/controller.tpl +++ b/src/generate/templates/nonopinionated/controller.tpl @@ -2,9 +2,9 @@ import { BaseController } from "@expressots/core"; import { controller, {{method}} } from "@expressots/adapter-express"; @controller("/{{{route}}}") -export class {{className}}Controller { +export class {{className}}{{schematic}} { @{{method}}("/") execute() { - return "Ok"; + return "{{schematic}}"; } } diff --git a/src/generate/templates/nonopinionated/entity.tpl b/src/generate/templates/nonopinionated/entity.tpl index 6bf3cb8..514cb5f 100644 --- a/src/generate/templates/nonopinionated/entity.tpl +++ b/src/generate/templates/nonopinionated/entity.tpl @@ -1,4 +1,4 @@ import { provide } from "inversify-binding-decorators"; -@provide({{className}}) -export class {{className}} {} +@provide({{className}}{{schematic}}) +export class {{className}}{{schematic}} {} diff --git a/src/generate/templates/nonopinionated/usecase.tpl b/src/generate/templates/nonopinionated/usecase.tpl index 738d40d..12fa4e7 100644 --- a/src/generate/templates/nonopinionated/usecase.tpl +++ b/src/generate/templates/nonopinionated/usecase.tpl @@ -1,8 +1,8 @@ import { provide } from "inversify-binding-decorators"; -@provide({{className}}UseCase) -export class {{className}}UseCase { +@provide({{className}}{{schematic}}) +export class {{className}}{{schematic}} { execute() { - return "Use Case"; + return "{{schematic}}"; } } diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index fad406a..5afe40a 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -11,7 +11,7 @@ import { import { printError } from "../../utils/cli-ui"; import { verifyIfFileExists } from "../../utils/verify-file-exists"; import Compiler from "../../utils/compiler"; -import { Pattern } from "../../types"; +import { ExpressoConfig, Pattern } from "../../types"; /** * File preparation @@ -26,8 +26,7 @@ export type FilePreparation = { schematic: string; target: string; method: string; - opinionated: boolean; - sourceRoot: string; + expressoConfig: ExpressoConfig; }; /** @@ -49,6 +48,7 @@ export type FileOutput = { outputPath: string; folderToScaffold: string; fileName: string; + schematic: string; }; /** @@ -57,7 +57,8 @@ export type FileOutput = { * @returns the file created */ export async function validateAndPrepareFile(fp: FilePreparation) { - if (fp.sourceRoot === "") { + const { sourceRoot, scaffoldSchematics, opinionated } = fp.expressoConfig; + if (sourceRoot === "") { printError( "You must specify a source root in your expressots.config.ts", "sourceRoot", @@ -66,18 +67,25 @@ export async function validateAndPrepareFile(fp: FilePreparation) { } let folderSchematic = ""; - if (fp.opinionated) { + if (opinionated) { folderSchematic = schematicFolder(fp.schematic); } - const folderToScaffold = `${fp.sourceRoot}/${folderSchematic}`; + const folderToScaffold = `${sourceRoot}/${folderSchematic}`; const { path, file, className, moduleName, modulePath } = await splitTarget( { target: fp.target, schematic: fp.schematic, }, ); - const outputPath = `${folderToScaffold}/${path}/${file}`; + const fileBaseSchema = + scaffoldSchematics?.[fp.schematic as keyof typeof scaffoldSchematics]; + const validateFileSchema = + fileBaseSchema !== undefined + ? file.replace(fp.schematic, fileBaseSchema) + : file; + + const outputPath = `${folderToScaffold}/${path}/${validateFileSchema}`; await verifyIfFileExists(outputPath, fp.schematic); mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); @@ -90,6 +98,7 @@ export async function validateAndPrepareFile(fp: FilePreparation) { outputPath, folderToScaffold, fileName: getFileNameWithoutExtension(file), + schematic: fileBaseSchema !== undefined ? fileBaseSchema : fp.schematic, }; } diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts index 1336e33..4d73a8c 100644 --- a/src/generate/utils/nonopininated-cmd.ts +++ b/src/generate/utils/nonopininated-cmd.ts @@ -3,6 +3,7 @@ import { anyCaseToKebabCase, anyCaseToPascalCase, } from "@expressots/boost-ts"; +import { ExpressoConfig } from "../../@types"; import { printGenerateSuccess } from "../../utils/cli-ui"; import { @@ -17,32 +18,37 @@ export async function nonOpinionatedProcess( schematic: string, target: string, method: string, - opinionated: boolean, - sourceRoot: string, + expressoConfig: ExpressoConfig, ): Promise { let f: FileOutput = await validateAndPrepareFile({ schematic, target, method, - opinionated, - sourceRoot, + expressoConfig, }); switch (schematic) { case "service": + f = await validateAndPrepareFile({ + schematic: "controller", + target, + method, + expressoConfig, + }); await generateController( f.outputPath, f.className, f.path, method, f.file, + f.schematic, ); + await printGenerateSuccess(f.schematic, f.file); f = await validateAndPrepareFile({ schematic: "usecase", target, method, - opinionated, - sourceRoot, + expressoConfig, }); await generateUseCase( f.outputPath, @@ -50,36 +56,40 @@ export async function nonOpinionatedProcess( f.moduleName, f.path, f.fileName, + f.schematic, "../templates/nonopinionated/usecase.tpl", ); + await printGenerateSuccess(f.schematic, f.file); f = await validateAndPrepareFile({ schematic: "dto", target, method, - opinionated, - sourceRoot, + expressoConfig, }); - await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + await generateDTO( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.schematic, + ); + await printGenerateSuccess(f.schematic, f.file); f = await validateAndPrepareFile({ schematic: "module", target, method, - opinionated, - sourceRoot, + expressoConfig, }); await generateModule( f.outputPath, f.className, f.moduleName, f.path, + f.schematic, ); - - await printGenerateSuccess("controller", f.file); - await printGenerateSuccess("usecase", f.file); - await printGenerateSuccess("dto", f.file); - await printGenerateSuccess("module", f.file); + await printGenerateSuccess(f.schematic, f.file); break; case "usecase": await generateUseCase( @@ -88,8 +98,9 @@ export async function nonOpinionatedProcess( f.moduleName, f.path, f.fileName, + f.schematic, ); - await printGenerateSuccess(schematic, f.file); + await printGenerateSuccess(f.schematic, f.file); break; case "controller": await generateController( @@ -98,12 +109,19 @@ export async function nonOpinionatedProcess( f.path, method, f.file, + f.schematic, ); - await printGenerateSuccess(schematic, f.file); + await printGenerateSuccess(f.schematic, f.file); break; case "dto": - await generateDTO(f.outputPath, f.className, f.moduleName, f.path); - await printGenerateSuccess(schematic, f.file); + await generateDTO( + f.outputPath, + f.className, + f.moduleName, + f.path, + f.schematic, + ); + await printGenerateSuccess(f.schematic, f.file); break; case "provider": await generateProvider( @@ -111,8 +129,9 @@ export async function nonOpinionatedProcess( f.className, f.moduleName, f.path, + f.schematic, ); - await printGenerateSuccess(schematic, f.file); + await printGenerateSuccess(f.schematic, f.file); break; case "entity": await generateEntity( @@ -120,8 +139,9 @@ export async function nonOpinionatedProcess( f.className, f.moduleName, f.path, + f.schematic, ); - await printGenerateSuccess(schematic, f.file); + await printGenerateSuccess(f.schematic, f.file); break; case "middleware": await generateMiddleware( @@ -129,8 +149,9 @@ export async function nonOpinionatedProcess( f.className, f.moduleName, f.path, + f.schematic, ); - await printGenerateSuccess(schematic, f.file); + await printGenerateSuccess(f.schematic, f.file); break; case "module": await generateModule( @@ -138,8 +159,9 @@ export async function nonOpinionatedProcess( f.className, f.moduleName, f.path, + f.schematic, ); - await printGenerateSuccess(schematic, f.file); + await printGenerateSuccess(f.schematic, f.file); break; } @@ -161,6 +183,7 @@ async function generateUseCase( moduleName: string, path: string, fileName: string, + schematic: string, template?: string, ): Promise { writeTemplate({ @@ -174,6 +197,7 @@ async function generateUseCase( moduleName, path, fileName, + schematic: anyCaseToPascalCase(schematic), }, }, }); @@ -193,6 +217,7 @@ async function generateController( path: string, method: string, file: string, + schematic: string, ): Promise { const templateBasedMethod = "../templates/nonopinionated/controller.tpl"; writeTemplate({ @@ -208,6 +233,7 @@ async function generateController( : path.replace(/\/$/, ""), construct: anyCaseToKebabCase(className), method: getHttpMethod(method), + schematic: anyCaseToPascalCase(schematic), }, }, }); @@ -225,6 +251,7 @@ async function generateDTO( className: string, moduleName: string, path: string, + schematic: string, ): Promise { writeTemplate({ outputPath, @@ -234,6 +261,7 @@ async function generateDTO( className, moduleName, path, + schematic: anyCaseToPascalCase(schematic), }, }, }); @@ -251,6 +279,7 @@ async function generateProvider( className: string, moduleName: string, path: string, + schematic: string, ): Promise { writeTemplate({ outputPath, @@ -260,6 +289,7 @@ async function generateProvider( className, moduleName, path, + schematic: anyCaseToPascalCase(schematic), }, }, }); @@ -277,6 +307,7 @@ async function generateEntity( className: string, moduleName: string, path: string, + schematic: string, ): Promise { writeTemplate({ outputPath, @@ -286,6 +317,7 @@ async function generateEntity( className, moduleName, path, + schematic: anyCaseToPascalCase(schematic), }, }, }); @@ -303,6 +335,7 @@ async function generateMiddleware( className: string, moduleName: string, path: string, + schematic: string, ): Promise { writeTemplate({ outputPath, @@ -312,6 +345,7 @@ async function generateMiddleware( className, moduleName, path, + schematic: anyCaseToPascalCase(schematic), }, }, }); @@ -329,6 +363,7 @@ async function generateModule( className: string, moduleName: string, path: string, + schematic: string, ): Promise { writeTemplate({ outputPath, @@ -340,6 +375,7 @@ async function generateModule( ? anyCaseToPascalCase(className) : anyCaseToPascalCase(moduleName), path, + schematic: anyCaseToPascalCase(schematic), }, }, }); diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 3fff7f0..47d7f8f 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -16,20 +16,19 @@ import { } from "./command-utils"; import { addControllerToModule } from "../../utils/add-controller-to-module"; import { addModuleToContainer } from "../../utils/add-module-to-container"; +import { ExpressoConfig } from "../../@types"; export async function opinionatedProcess( schematic: string, target: string, method: string, - opinionated: boolean, - sourceRoot: string, + expressoConfig: ExpressoConfig, ): Promise { let f: FileOutput = await validateAndPrepareFile({ schematic, target, method, - opinionated, - sourceRoot, + expressoConfig, }); switch (schematic) { case "service": @@ -45,8 +44,7 @@ export async function opinionatedProcess( schematic: "usecase", target, method, - opinionated, - sourceRoot, + expressoConfig, }); await generateUseCase( f.outputPath, @@ -61,8 +59,7 @@ export async function opinionatedProcess( schematic: "dto", target, method, - opinionated, - sourceRoot, + expressoConfig, }); await generateDTO(f.outputPath, f.className, f.moduleName, f.path); @@ -70,8 +67,7 @@ export async function opinionatedProcess( schematic: "module", target, method, - opinionated, - sourceRoot, + expressoConfig, }); await generateModuleService( f.className, From fda129f3c931711859eae12ec245653c09f37af7 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Wed, 27 Mar 2024 22:02:53 -0700 Subject: [PATCH 08/17] refactor: adjust templates --- expressots.config.ts | 2 +- .../templates/{common => nonopinionated}/dto.tpl | 0 .../{common => nonopinionated}/middleware.tpl | 0 .../templates/{common => nonopinionated}/module.tpl | 0 .../templates/{common => nonopinionated}/provider.tpl | 0 src/generate/templates/opinionated/dto.tpl | 3 +++ src/generate/templates/opinionated/middleware.tpl | 10 ++++++++++ src/generate/templates/opinionated/module.tpl | 4 ++++ src/generate/templates/opinionated/provider.tpl | 4 ++++ src/generate/templates/opinionated/usecase.tpl | 8 ++++++++ src/generate/utils/nonopininated-cmd.ts | 8 ++++---- src/generate/utils/opinionated-cmd.ts | 10 +++++----- 12 files changed, 39 insertions(+), 10 deletions(-) rename src/generate/templates/{common => nonopinionated}/dto.tpl (100%) rename src/generate/templates/{common => nonopinionated}/middleware.tpl (100%) rename src/generate/templates/{common => nonopinionated}/module.tpl (100%) rename src/generate/templates/{common => nonopinionated}/provider.tpl (100%) create mode 100644 src/generate/templates/opinionated/dto.tpl create mode 100644 src/generate/templates/opinionated/middleware.tpl create mode 100644 src/generate/templates/opinionated/module.tpl create mode 100644 src/generate/templates/opinionated/provider.tpl create mode 100644 src/generate/templates/opinionated/usecase.tpl diff --git a/expressots.config.ts b/expressots.config.ts index fb905f8..0fbbcfe 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -3,7 +3,7 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, - opinionated: false, + opinionated: true, scaffoldSchematics: { entity: "model", provider: "adapter", diff --git a/src/generate/templates/common/dto.tpl b/src/generate/templates/nonopinionated/dto.tpl similarity index 100% rename from src/generate/templates/common/dto.tpl rename to src/generate/templates/nonopinionated/dto.tpl diff --git a/src/generate/templates/common/middleware.tpl b/src/generate/templates/nonopinionated/middleware.tpl similarity index 100% rename from src/generate/templates/common/middleware.tpl rename to src/generate/templates/nonopinionated/middleware.tpl diff --git a/src/generate/templates/common/module.tpl b/src/generate/templates/nonopinionated/module.tpl similarity index 100% rename from src/generate/templates/common/module.tpl rename to src/generate/templates/nonopinionated/module.tpl diff --git a/src/generate/templates/common/provider.tpl b/src/generate/templates/nonopinionated/provider.tpl similarity index 100% rename from src/generate/templates/common/provider.tpl rename to src/generate/templates/nonopinionated/provider.tpl diff --git a/src/generate/templates/opinionated/dto.tpl b/src/generate/templates/opinionated/dto.tpl new file mode 100644 index 0000000..f29b988 --- /dev/null +++ b/src/generate/templates/opinionated/dto.tpl @@ -0,0 +1,3 @@ +export interface I{{className}}RequestDto {} + +export interface I{{className}}ResponseDto {} diff --git a/src/generate/templates/opinionated/middleware.tpl b/src/generate/templates/opinionated/middleware.tpl new file mode 100644 index 0000000..1dac6d1 --- /dev/null +++ b/src/generate/templates/opinionated/middleware.tpl @@ -0,0 +1,10 @@ +import { ExpressoMiddleware } from "@expressots/core"; +import { NextFunction, Request, Response } from "express"; +import { provide } from "inversify-binding-decorators"; + +@provide({{className}}Middleware) +export class {{className}}Middleware extends ExpressoMiddleware { + use(req: Request, res: Response, next: NextFunction): void | Promise { + throw new Error("Method not implemented."); + } +} \ No newline at end of file diff --git a/src/generate/templates/opinionated/module.tpl b/src/generate/templates/opinionated/module.tpl new file mode 100644 index 0000000..45df72d --- /dev/null +++ b/src/generate/templates/opinionated/module.tpl @@ -0,0 +1,4 @@ +import { ContainerModule } from "inversify"; +import { CreateModule } from "@expressots/core"; + +export const {{moduleName}}Module: ContainerModule = CreateModule([]); diff --git a/src/generate/templates/opinionated/provider.tpl b/src/generate/templates/opinionated/provider.tpl new file mode 100644 index 0000000..b27ed01 --- /dev/null +++ b/src/generate/templates/opinionated/provider.tpl @@ -0,0 +1,4 @@ +import { provide } from "inversify-binding-decorators"; + +@provide({{className}}Provider) +export class {{className}}Provider {} \ No newline at end of file diff --git a/src/generate/templates/opinionated/usecase.tpl b/src/generate/templates/opinionated/usecase.tpl new file mode 100644 index 0000000..1bcd952 --- /dev/null +++ b/src/generate/templates/opinionated/usecase.tpl @@ -0,0 +1,8 @@ +import { provide } from "inversify-binding-decorators"; + +@provide({{className}}UseCase) +export class {{className}}UseCase { + execute() { + return "UseCase"; + } +} diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts index 4d73a8c..b4539e3 100644 --- a/src/generate/utils/nonopininated-cmd.ts +++ b/src/generate/utils/nonopininated-cmd.ts @@ -256,7 +256,7 @@ async function generateDTO( writeTemplate({ outputPath, template: { - path: "../templates/common/dto.tpl", + path: "../templates/nonopinionated/dto.tpl", data: { className, moduleName, @@ -284,7 +284,7 @@ async function generateProvider( writeTemplate({ outputPath, template: { - path: "../templates/common/provider.tpl", + path: "../templates/nonopinionated/provider.tpl", data: { className, moduleName, @@ -340,7 +340,7 @@ async function generateMiddleware( writeTemplate({ outputPath, template: { - path: "../templates/common/middleware.tpl", + path: "../templates/nonopinionated/middleware.tpl", data: { className, moduleName, @@ -368,7 +368,7 @@ async function generateModule( writeTemplate({ outputPath, template: { - path: "../templates/common/module.tpl", + path: "../templates/nonopinionated/module.tpl", data: { className, moduleName: className diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 47d7f8f..57a3a8b 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -225,7 +225,7 @@ async function generateUseCase( writeTemplate({ outputPath, template: { - path: template ? template : "../templates/usecase.tpl", + path: template ? template : "../templates/opinionated/usecase.tpl", data: { className, moduleName, @@ -286,7 +286,7 @@ async function generateDTO( writeTemplate({ outputPath, template: { - path: "../templates/common/dto.tpl", + path: "../templates/opinionated/dto.tpl", data: { className, moduleName, @@ -312,7 +312,7 @@ async function generateProvider( writeTemplate({ outputPath, template: { - path: "../templates/common/provider.tpl", + path: "../templates/opinionated/provider.tpl", data: { className, moduleName, @@ -364,7 +364,7 @@ async function generateMiddleware( writeTemplate({ outputPath, template: { - path: "../templates/common/middleware.tpl", + path: "../templates/opinionated/middleware.tpl", data: { className, moduleName, @@ -449,7 +449,7 @@ async function generateModule( writeTemplate({ outputPath, template: { - path: "../templates/common/module.tpl", + path: "../templates/opinionated/module.tpl", data: { className, moduleName: anyCaseToPascalCase(moduleName), From e9b382235f90f118786b2a9e270a229015cf1297 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 28 Mar 2024 12:57:09 -0700 Subject: [PATCH 09/17] refactor: adjust opinionated path module insertion --- expressots.config.ts | 2 +- src/generate/templates/opinionated/dto.tpl | 4 +- src/generate/templates/opinionated/entity.tpl | 4 +- .../opinionated/usecase-service-delete.tpl | 8 ++ .../templates/opinionated/usecase.tpl | 2 +- src/generate/utils/command-utils.ts | 29 ++++- src/generate/utils/opinionated-cmd.ts | 106 +++++++++++++----- src/utils/add-module-to-container.ts | 4 +- 8 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 src/generate/templates/opinionated/usecase-service-delete.tpl diff --git a/expressots.config.ts b/expressots.config.ts index 0fbbcfe..fb905f8 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -3,7 +3,7 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, - opinionated: true, + opinionated: false, scaffoldSchematics: { entity: "model", provider: "adapter", diff --git a/src/generate/templates/opinionated/dto.tpl b/src/generate/templates/opinionated/dto.tpl index f29b988..91c7cd7 100644 --- a/src/generate/templates/opinionated/dto.tpl +++ b/src/generate/templates/opinionated/dto.tpl @@ -1,3 +1,3 @@ -export interface I{{className}}RequestDto {} +export interface I{{className}}RequestDTO {} -export interface I{{className}}ResponseDto {} +export interface I{{className}}ResponseDTO {} diff --git a/src/generate/templates/opinionated/entity.tpl b/src/generate/templates/opinionated/entity.tpl index 2c6121e..28c4d3b 100644 --- a/src/generate/templates/opinionated/entity.tpl +++ b/src/generate/templates/opinionated/entity.tpl @@ -1,8 +1,8 @@ import { provide } from "inversify-binding-decorators"; import { randomUUID } from "node:crypto"; -@provide({{className}}) -export class {{className}} { +@provide({{className}}Entity) +export class {{className}}Entity { id: string; constructor() { diff --git a/src/generate/templates/opinionated/usecase-service-delete.tpl b/src/generate/templates/opinionated/usecase-service-delete.tpl new file mode 100644 index 0000000..6c8d1cc --- /dev/null +++ b/src/generate/templates/opinionated/usecase-service-delete.tpl @@ -0,0 +1,8 @@ +import { provide } from "inversify-binding-decorators"; + +@provide({{className}}UseCase) +export class {{className}}UseCase { + execute(id: string) { + return "Use Case"; + } +} diff --git a/src/generate/templates/opinionated/usecase.tpl b/src/generate/templates/opinionated/usecase.tpl index 1bcd952..738d40d 100644 --- a/src/generate/templates/opinionated/usecase.tpl +++ b/src/generate/templates/opinionated/usecase.tpl @@ -3,6 +3,6 @@ import { provide } from "inversify-binding-decorators"; @provide({{className}}UseCase) export class {{className}}UseCase { execute() { - return "UseCase"; + return "Use Case"; } } diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index 5afe40a..b080d68 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -66,11 +66,35 @@ export async function validateAndPrepareFile(fp: FilePreparation) { process.exit(1); } - let folderSchematic = ""; if (opinionated) { - folderSchematic = schematicFolder(fp.schematic); + const folderSchematic = schematicFolder(fp.schematic); + + const folderToScaffold = `${sourceRoot}/${folderSchematic}`; + const { path, file, className, moduleName, modulePath } = + await splitTarget({ + target: fp.target, + schematic: fp.schematic, + }); + + const outputPath = `${folderToScaffold}/${path}/${file}`; + await verifyIfFileExists(outputPath, fp.schematic); + mkdirSync(`${folderToScaffold}/${path}`, { recursive: true }); + + return { + path, + file, + className, + moduleName, + modulePath, + outputPath, + folderToScaffold, + fileName: getFileNameWithoutExtension(file), + schematic: fp.schematic, + }; } + const folderSchematic = ""; + const folderToScaffold = `${sourceRoot}/${folderSchematic}`; const { path, file, className, moduleName, modulePath } = await splitTarget( { @@ -78,6 +102,7 @@ export async function validateAndPrepareFile(fp: FilePreparation) { schematic: fp.schematic, }, ); + const fileBaseSchema = scaffoldSchematics?.[fp.schematic as keyof typeof scaffoldSchematics]; const validateFileSchema = diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 57a3a8b..4a412d8 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -24,7 +24,7 @@ export async function opinionatedProcess( method: string, expressoConfig: ExpressoConfig, ): Promise { - let f: FileOutput = await validateAndPrepareFile({ + const f: FileOutput = await validateAndPrepareFile({ schematic, target, method, @@ -40,41 +40,42 @@ export async function opinionatedProcess( f.file, ); - f = await validateAndPrepareFile({ + const u = await validateAndPrepareFile({ schematic: "usecase", target, method, expressoConfig, }); - await generateUseCase( - f.outputPath, - f.className, - f.moduleName, - f.path, - f.fileName, - "../templates/opinionated/usecase-service.tpl", + await generateUseCaseService( + u.outputPath, + u.className, + method, + u.moduleName, + u.path, + u.fileName, ); - f = await validateAndPrepareFile({ + const d = await validateAndPrepareFile({ schematic: "dto", target, method, expressoConfig, }); - await generateDTO(f.outputPath, f.className, f.moduleName, f.path); + await generateDTO(d.outputPath, d.className, d.moduleName, d.path); - f = await validateAndPrepareFile({ + const m = await validateAndPrepareFile({ schematic: "module", target, method, expressoConfig, }); await generateModuleService( - f.className, - f.moduleName, - f.path, - f.file, - f.folderToScaffold, + f.outputPath, + m.className, + m.moduleName, + m.path, + m.file, + m.folderToScaffold, ); await printGenerateSuccess("controller", f.file); @@ -214,18 +215,62 @@ async function generateControllerService( * @param path - The path * @param template - The template */ +async function generateUseCaseService( + outputPath: string, + className: string, + method: string, + moduleName: string, + path: string, + fileName: string, +): Promise { + let templateBasedMethod = ""; + + switch (method) { + case "put": + templateBasedMethod = + "../templates/opinionated/usecase-service.tpl"; + break; + case "patch": + templateBasedMethod = + "../templates/opinionated/usecase-service.tpl"; + break; + case "post": + templateBasedMethod = + "../templates/opinionated/usecase-service.tpl"; + break; + case "delete": + templateBasedMethod = + "../templates/opinionated/usecase-service-delete.tpl"; + break; + default: + templateBasedMethod = "../templates/opinionated/usecase.tpl"; + break; + } + writeTemplate({ + outputPath, + template: { + path: templateBasedMethod, + data: { + className, + moduleName, + path, + fileName, + }, + }, + }); +} + async function generateUseCase( outputPath: string, className: string, moduleName: string, path: string, fileName: string, - template?: string, ): Promise { writeTemplate({ outputPath, template: { - path: template ? template : "../templates/opinionated/usecase.tpl", + path: "../templates/opinionated/usecase.tpl", data: { className, moduleName, @@ -382,6 +427,7 @@ async function generateMiddleware( * @param path - The path */ async function generateModuleService( + outputPathController: string, className: string, moduleName: string, path: string, @@ -393,14 +439,18 @@ async function generateModuleService( .join(folderToScaffold, path, "..") .normalize(); const newModuleName = `${newModuleFile}.module.ts`; - const newModuleOutputPath = `${newModulePath}/${newModuleName}`; + const newModuleOutputPath = `${newModulePath}/${newModuleName}`.replace( + "\\", + "/", + ); + + const controllerToModule = nodePath + .relative(newModuleOutputPath, outputPathController) + .normalize() + .replace(/\.ts$/, "") + .replace(/\\/g, "/") + .replace(/\.\./g, "."); - const controllerPathLength = path.split("/").length - 1 - 1; - const controllerPath = path.split("/")[controllerPathLength]; - const controllerName = file - .replace("module", "controller") - .replace(".ts", ""); - const controllerFileName = `./${controllerPath}/${controllerName}`; const controllerFullPath = nodePath .join(folderToScaffold, path, "..", newModuleName) .normalize(); @@ -409,7 +459,7 @@ async function generateModuleService( await addControllerToModule( controllerFullPath, `${className}Controller`, - controllerFileName, + controllerToModule, ); return; } @@ -421,7 +471,7 @@ async function generateModuleService( data: { className, moduleName: anyCaseToPascalCase(moduleName), - path: controllerFileName, + path: controllerToModule, }, }, }); diff --git a/src/utils/add-module-to-container.ts b/src/utils/add-module-to-container.ts index f83789f..e9cc292 100644 --- a/src/utils/add-module-to-container.ts +++ b/src/utils/add-module-to-container.ts @@ -92,10 +92,10 @@ async function addModuleToContainer( if (path.split("/").length > 1) { newImport = `import { ${moduleName}Module } from "${usecaseDir}${name.toLowerCase()}/${name.toLowerCase()}.module";`; } else { - newImport = `import { ${moduleName}Module } from "${usecaseDir}${name}.module";`; + newImport = `import { ${moduleName}Module } from "${usecaseDir}${name.toLowerCase()}.module";`; } } else { - newImport = `import { ${moduleName}Module } from "${usecaseDir}${name}/${name}.module";`; + newImport = `import { ${moduleName}Module } from "${usecaseDir}${name}/${name.toLowerCase()}.module";`; } if ( From 44f17f1d053adead04ba607920499b1b5408af5b Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 28 Mar 2024 20:20:13 -0700 Subject: [PATCH 10/17] refactor: adjust form console msg on error in existing project --- expressots.config.ts | 2 +- package.json | 1 + src/new/form.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/expressots.config.ts b/expressots.config.ts index fb905f8..0fbbcfe 100644 --- a/expressots.config.ts +++ b/expressots.config.ts @@ -3,7 +3,7 @@ import { ExpressoConfig, Pattern } from "./src/types"; const config: ExpressoConfig = { sourceRoot: "src", scaffoldPattern: Pattern.KEBAB_CASE, - opinionated: false, + opinionated: true, scaffoldSchematics: { entity: "model", provider: "adapter", diff --git a/package.json b/package.json index 55d7bbe..a55129d 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "build": "npm run clean && tsc -p tsconfig.json && yarn cp:templates && chmod +x ./bin/cli.js", "cp:templates": "cp -r ./src/generate/templates ./bin/generate/templates && cp -r ./src/providers/prisma/templates ./bin/providers/prisma/templates", "clean": "rimraf ./bin", + "prepublish": "npm run build && npm pack", "format": "prettier --write \"./src/**/*.ts\" --cache", "lint": "eslint \"./src/**/*.ts\"", "lint:fix": "eslint \"./src/**/*.ts\" --fix", diff --git a/src/new/form.ts b/src/new/form.ts index 00d00d6..f2430f8 100644 --- a/src/new/form.ts +++ b/src/new/form.ts @@ -211,6 +211,7 @@ const projectForm = async (projectName: string, args: any[]): Promise => { await emitter.clone(answer.name); } catch (err: any) { + console.log("\n"); printError( "Project already exists or Folder is not empty", answer.name, From 3dea624160a689ee82e75d4171752c98d5506b16 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Thu, 28 Mar 2024 20:51:25 -0700 Subject: [PATCH 11/17] feat: add path command style to opinionated services --- src/generate/form.ts | 5 +++-- src/generate/utils/command-utils.ts | 25 +++++++++++++++++++++++++ src/generate/utils/opinionated-cmd.ts | 25 ++++++++++++++++--------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/generate/form.ts b/src/generate/form.ts index db33394..be71c60 100644 --- a/src/generate/form.ts +++ b/src/generate/form.ts @@ -1,5 +1,5 @@ -import { s } from "vitest/dist/reporters-cb94c88b"; import Compiler from "../utils/compiler"; +import { checkPathStyle } from "./utils/command-utils"; import { nonOpinionatedProcess } from "./utils/nonopininated-cmd"; import { opinionatedProcess } from "./utils/opinionated-cmd"; @@ -28,15 +28,16 @@ export const createTemplate = async ({ method, }: CreateTemplateProps) => { const config = await Compiler.loadConfig(); + const pathStyle = checkPathStyle(target); let returnFile = ""; - if (config.opinionated) { returnFile = await opinionatedProcess( schematic, target, method, config, + pathStyle, ); } else { returnFile = await nonOpinionatedProcess( diff --git a/src/generate/utils/command-utils.ts b/src/generate/utils/command-utils.ts index b080d68..3fd1633 100644 --- a/src/generate/utils/command-utils.ts +++ b/src/generate/utils/command-utils.ts @@ -13,6 +13,13 @@ import { verifyIfFileExists } from "../../utils/verify-file-exists"; import Compiler from "../../utils/compiler"; import { ExpressoConfig, Pattern } from "../../types"; +export const enum PathStyle { + None = "none", + Single = "single", + Nested = "nested", + Sugar = "sugar", +} + /** * File preparation * @param schematic @@ -364,3 +371,21 @@ export async function extractFirstWord(file: string) { return anyCaseToCamelCase(firstWord); } } + +/** + * Check if the path is a nested path, a single path or a sugar path + * @param path + * @returns the path style + */ +export const checkPathStyle = (path: string): PathStyle => { + const singleOrNestedPathRegex = /\/|\\/; + const sugarPathRegex = /^\w+-\w+$/; + + if (singleOrNestedPathRegex.test(path)) { + return PathStyle.Nested; + } else if (sugarPathRegex.test(path)) { + return PathStyle.Sugar; + } else { + return PathStyle.Single; + } +}; diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 4a412d8..7d2019e 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -11,6 +11,7 @@ import { FileOutput, getFileNameWithoutExtension, getHttpMethod, + PathStyle, validateAndPrepareFile, writeTemplate, } from "./command-utils"; @@ -23,6 +24,7 @@ export async function opinionatedProcess( target: string, method: string, expressoConfig: ExpressoConfig, + pathStyle: string, ): Promise { const f: FileOutput = await validateAndPrepareFile({ schematic, @@ -69,14 +71,19 @@ export async function opinionatedProcess( method, expressoConfig, }); - await generateModuleService( - f.outputPath, - m.className, - m.moduleName, - m.path, - m.file, - m.folderToScaffold, - ); + + if (pathStyle === PathStyle.Sugar) { + await generateModuleServiceSugarPath( + f.outputPath, + m.className, + m.moduleName, + m.path, + m.file, + m.folderToScaffold, + ); + } else if (pathStyle === PathStyle.Nested) { + } else if (pathStyle === PathStyle.Single) { + } await printGenerateSuccess("controller", f.file); await printGenerateSuccess("usecase", f.file); @@ -426,7 +433,7 @@ async function generateMiddleware( * @param moduleName - The module name * @param path - The path */ -async function generateModuleService( +async function generateModuleServiceSugarPath( outputPathController: string, className: string, moduleName: string, From b5738e50cfc6d26a068bffa2dd7e9e342e4f0c76 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 29 Mar 2024 02:01:43 -0700 Subject: [PATCH 12/17] feat: add single and sugar path validation --- src/generate/utils/opinionated-cmd.ts | 131 ++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 7d2019e..6522637 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -73,6 +73,7 @@ export async function opinionatedProcess( }); if (pathStyle === PathStyle.Sugar) { + console.log("Sugar"); await generateModuleServiceSugarPath( f.outputPath, m.className, @@ -82,7 +83,25 @@ export async function opinionatedProcess( m.folderToScaffold, ); } else if (pathStyle === PathStyle.Nested) { + console.log("Nested"); + await generateModuleServiceNestedPath( + f.outputPath, + m.className, + m.moduleName, + m.path, + m.file, + m.folderToScaffold, + ); } else if (pathStyle === PathStyle.Single) { + console.log("Single"); + await generateModuleServiceSinglePath( + f.outputPath, + m.className, + m.moduleName, + m.path, + m.file, + m.folderToScaffold, + ); } await printGenerateSuccess("controller", f.file); @@ -490,6 +509,118 @@ async function generateModuleServiceSugarPath( ); } +async function generateModuleServiceSinglePath( + outputPathController: string, + className: string, + moduleName: string, + path: string, + file: string, + folderToScaffold: string, +): Promise { + const newModuleFile = await extractFirstWord(file); + const newModulePath = nodePath.join(folderToScaffold, path).normalize(); + const newModuleName = `${newModuleFile}.module.ts`; + const newModuleOutputPath = `${newModulePath}/${newModuleName}`.replace( + "\\", + "/", + ); + + const controllerToModule = nodePath + .relative(newModuleOutputPath, outputPathController) + .normalize() + .replace(/\.ts$/, "") + .replace(/\\/g, "/") + .replace(/\.\./g, "."); + + const controllerFullPath = nodePath + .join(folderToScaffold, path, "..", newModuleName) + .normalize(); + + if (fs.existsSync(newModuleOutputPath)) { + await addControllerToModule( + controllerFullPath, + `${className}Controller`, + controllerToModule, + ); + return; + } + + writeTemplate({ + outputPath: newModuleOutputPath, + template: { + path: "../templates/opinionated/module-service.tpl", + data: { + className, + moduleName: anyCaseToPascalCase(moduleName), + path: controllerToModule, + }, + }, + }); + + await addModuleToContainer( + anyCaseToPascalCase(moduleName), + `${moduleName}/${file.replace(".ts", "")}`, + path, + ); +} + +async function generateModuleServiceNestedPath( + outputPathController: string, + className: string, + moduleName: string, + path: string, + file: string, + folderToScaffold: string, +): Promise { + const newModuleFile = await extractFirstWord(file); + const newModulePath = nodePath + .join(folderToScaffold, path, "..") + .normalize(); + const newModuleName = `${newModuleFile}.module.ts`; + const newModuleOutputPath = `${newModulePath}/${newModuleName}`.replace( + "\\", + "/", + ); + + const controllerToModule = nodePath + .relative(newModuleOutputPath, outputPathController) + .normalize() + .replace(/\.ts$/, "") + .replace(/\\/g, "/") + .replace(/\.\./g, "."); + + const controllerFullPath = nodePath + .join(folderToScaffold, path, "..", newModuleName) + .normalize(); + + if (fs.existsSync(newModuleOutputPath)) { + await addControllerToModule( + controllerFullPath, + `${className}Controller`, + controllerToModule, + ); + return; + } + + writeTemplate({ + outputPath: newModuleOutputPath, + template: { + path: "../templates/opinionated/module-service.tpl", + data: { + className, + moduleName: anyCaseToPascalCase(moduleName), + path: controllerToModule, + }, + }, + }); + + await addModuleToContainer( + anyCaseToPascalCase(moduleName), + `${moduleName}/${file.replace(".ts", "")}`, + path, + ); +} + /** * Generate a module * @param outputPath - The output path From 0a105bb6abacc2a9b62f948df304ab34f5fb7f19 Mon Sep 17 00:00:00 2001 From: Juliano Leonardo Soares Date: Fri, 29 Mar 2024 14:43:17 -0300 Subject: [PATCH 13/17] feat: add Nested path validation --- src/generate/utils/opinionated-cmd.ts | 18 ++++----- src/utils/add-module-to-container.ts | 53 ++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index 6522637..c41d823 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -16,7 +16,7 @@ import { writeTemplate, } from "./command-utils"; import { addControllerToModule } from "../../utils/add-controller-to-module"; -import { addModuleToContainer } from "../../utils/add-module-to-container"; +import { addModuleToContainer, addModuleToContainerNestedPath } from "../../utils/add-module-to-container"; import { ExpressoConfig } from "../../@types"; export async function opinionatedProcess( @@ -87,9 +87,7 @@ export async function opinionatedProcess( await generateModuleServiceNestedPath( f.outputPath, m.className, - m.moduleName, m.path, - m.file, m.folderToScaffold, ); } else if (pathStyle === PathStyle.Single) { @@ -567,16 +565,15 @@ async function generateModuleServiceSinglePath( async function generateModuleServiceNestedPath( outputPathController: string, className: string, - moduleName: string, path: string, - file: string, folderToScaffold: string, ): Promise { - const newModuleFile = await extractFirstWord(file); + const moduleFileName = nodePath.basename(path, '/'); const newModulePath = nodePath .join(folderToScaffold, path, "..") .normalize(); - const newModuleName = `${newModuleFile}.module.ts`; + + const newModuleName = `${moduleFileName}.module.ts`; const newModuleOutputPath = `${newModulePath}/${newModuleName}`.replace( "\\", "/", @@ -608,15 +605,14 @@ async function generateModuleServiceNestedPath( path: "../templates/opinionated/module-service.tpl", data: { className, - moduleName: anyCaseToPascalCase(moduleName), + moduleName: anyCaseToPascalCase(moduleFileName), path: controllerToModule, }, }, }); - await addModuleToContainer( - anyCaseToPascalCase(moduleName), - `${moduleName}/${file.replace(".ts", "")}`, + await addModuleToContainerNestedPath( + anyCaseToPascalCase(moduleFileName), path, ); } diff --git a/src/utils/add-module-to-container.ts b/src/utils/add-module-to-container.ts index e9cc292..da3e1bb 100644 --- a/src/utils/add-module-to-container.ts +++ b/src/utils/add-module-to-container.ts @@ -127,4 +127,55 @@ async function addModuleToContainer( await fs.promises.writeFile(containerData.path, newFileContent, "utf8"); } -export { addModuleToContainer }; +async function addModuleToContainerNestedPath( + name: string, + path?: string, +) { + const containerData: AppContainerType = await validateAppContainer(); + + const moduleName = (name[0].toUpperCase() + name.slice(1)).trimStart(); + const { opinionated } = await Compiler.loadConfig(); + + let usecaseDir: string; + if (opinionated) { + usecaseDir = `@useCases/`; + } else { + usecaseDir = `./`; + } + + if (path.endsWith('/')) { + path = path.slice(0, -1); + } + + const newImport = `import { ${moduleName}Module } from "${usecaseDir}${path}.module";`; + + if ( + containerData.imports.includes(newImport) && + containerData.modules.includes(`${moduleName}Module`) + ) { + return; + } + + containerData.imports.push(newImport); + containerData.modules.push(`${moduleName}Module`); + + const newModule = containerData.modules.join(", "); + const newModuleDeclaration = `.create([${newModule}]`; + + const newFileContent = [ + ...containerData.imports, + ...containerData.notImports, + ] + .join("\n") + .replace(containerData.regex, newModuleDeclaration); + + console.log( + " ", + chalk.greenBright(`[container]`.padEnd(14)), + chalk.bold.white(`${moduleName}Module added to ${APP_CONTAINER}! ✔️`), + ); + + await fs.promises.writeFile(containerData.path, newFileContent, "utf8"); +} + +export { addModuleToContainer, addModuleToContainerNestedPath }; From 3a2c26ce46b3491db41b327ba3377e422e3fd91b Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 29 Mar 2024 13:29:31 -0700 Subject: [PATCH 14/17] feat: fixed nested resource gen e add fn comments --- src/generate/form.ts | 6 ++-- src/generate/utils/nonopininated-cmd.ts | 7 ++++ src/generate/utils/opinionated-cmd.ts | 44 +++++++++++++++++++++---- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/generate/form.ts b/src/generate/form.ts index be71c60..8691332 100644 --- a/src/generate/form.ts +++ b/src/generate/form.ts @@ -17,9 +17,9 @@ type CreateTemplateProps = { /** * Create a template based on the schematic - * @param schematic - * @param path - * @param method + * @param schematic - the schematic to create + * @param path - the path to create the schematic + * @param method - the http method * @returns the file created */ export const createTemplate = async ({ diff --git a/src/generate/utils/nonopininated-cmd.ts b/src/generate/utils/nonopininated-cmd.ts index b4539e3..2a3f44b 100644 --- a/src/generate/utils/nonopininated-cmd.ts +++ b/src/generate/utils/nonopininated-cmd.ts @@ -14,6 +14,13 @@ import { writeTemplate, } from "./command-utils"; +/** + * Process the non-opinionated command + * @param schematic - The schematic + * @param target - The target + * @param method - The method + * @param expressoConfig - The expresso config + */ export async function nonOpinionatedProcess( schematic: string, target: string, diff --git a/src/generate/utils/opinionated-cmd.ts b/src/generate/utils/opinionated-cmd.ts index c41d823..2688eb1 100644 --- a/src/generate/utils/opinionated-cmd.ts +++ b/src/generate/utils/opinionated-cmd.ts @@ -16,9 +16,21 @@ import { writeTemplate, } from "./command-utils"; import { addControllerToModule } from "../../utils/add-controller-to-module"; -import { addModuleToContainer, addModuleToContainerNestedPath } from "../../utils/add-module-to-container"; +import { + addModuleToContainer, + addModuleToContainerNestedPath, +} from "../../utils/add-module-to-container"; import { ExpressoConfig } from "../../@types"; +/** + * Process commands for opinionated service scaffolding + * @param schematic - Resource to scaffold + * @param target - Target path + * @param method - HTTP method + * @param expressoConfig - Expresso configuration [expressots.config.ts] + * @param pathStyle - Path command style [sugar, nested, single] + * @returns + */ export async function opinionatedProcess( schematic: string, target: string, @@ -73,7 +85,6 @@ export async function opinionatedProcess( }); if (pathStyle === PathStyle.Sugar) { - console.log("Sugar"); await generateModuleServiceSugarPath( f.outputPath, m.className, @@ -83,7 +94,6 @@ export async function opinionatedProcess( m.folderToScaffold, ); } else if (pathStyle === PathStyle.Nested) { - console.log("Nested"); await generateModuleServiceNestedPath( f.outputPath, m.className, @@ -91,7 +101,6 @@ export async function opinionatedProcess( m.folderToScaffold, ); } else if (pathStyle === PathStyle.Single) { - console.log("Single"); await generateModuleServiceSinglePath( f.outputPath, m.className, @@ -284,6 +293,14 @@ async function generateUseCaseService( }); } +/** + * Generate a use case + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + * @param fileName - The file name + */ async function generateUseCase( outputPath: string, className: string, @@ -444,7 +461,7 @@ async function generateMiddleware( } /** - * Generate a module for service scaffolding + * Generate a module for service scaffolding with sugar path * @param outputPath - The output path * @param className - The class name * @param moduleName - The module name @@ -507,6 +524,13 @@ async function generateModuleServiceSugarPath( ); } +/** + * Generate a module for service scaffolding with single path + * @param outputPath - The output path + * @param className - The class name + * @param moduleName - The module name + * @param path - The path + */ async function generateModuleServiceSinglePath( outputPathController: string, className: string, @@ -562,13 +586,21 @@ async function generateModuleServiceSinglePath( ); } +/** + * Generate a module for service scaffolding with nested path + * @param outputPathController + * @param className + * @param path + * @param folderToScaffold + * @returns + */ async function generateModuleServiceNestedPath( outputPathController: string, className: string, path: string, folderToScaffold: string, ): Promise { - const moduleFileName = nodePath.basename(path, '/'); + const moduleFileName = nodePath.basename(path, "/"); const newModulePath = nodePath .join(folderToScaffold, path, "..") .normalize(); From 8ef1714ed6f03a5b7d7ebd960597069ab8fa05ab Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 29 Mar 2024 13:31:00 -0700 Subject: [PATCH 15/17] fix: adjust linter --- src/utils/add-module-to-container.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/utils/add-module-to-container.ts b/src/utils/add-module-to-container.ts index da3e1bb..0700757 100644 --- a/src/utils/add-module-to-container.ts +++ b/src/utils/add-module-to-container.ts @@ -127,10 +127,7 @@ async function addModuleToContainer( await fs.promises.writeFile(containerData.path, newFileContent, "utf8"); } -async function addModuleToContainerNestedPath( - name: string, - path?: string, -) { +async function addModuleToContainerNestedPath(name: string, path?: string) { const containerData: AppContainerType = await validateAppContainer(); const moduleName = (name[0].toUpperCase() + name.slice(1)).trimStart(); @@ -143,12 +140,12 @@ async function addModuleToContainerNestedPath( usecaseDir = `./`; } - if (path.endsWith('/')) { + if (path.endsWith("/")) { path = path.slice(0, -1); } - + const newImport = `import { ${moduleName}Module } from "${usecaseDir}${path}.module";`; - + if ( containerData.imports.includes(newImport) && containerData.modules.includes(`${moduleName}Module`) From 249cc73fd5251274e2742aada1a68b4c457c4f8e Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 29 Mar 2024 15:27:22 -0700 Subject: [PATCH 16/17] feat: resource list panel --- package.json | 1 + src/cli.ts | 4 ++-- src/help/cli.ts | 18 ++++++++++++++++++ src/help/form.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/help/index.ts | 0 src/info/form.ts | 46 ++++++++++++++++++++++++++++++++++------------ 6 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 src/help/cli.ts create mode 100644 src/help/form.ts create mode 100644 src/help/index.ts diff --git a/package.json b/package.json index a55129d..6ac121f 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@expressots/boost-ts": "1.1.1", "chalk-animation": "2.0.3", "cli-progress": "3.11.2", + "cli-table3": "^0.6.4", "degit": "2.8.4", "glob": "10.2.6", "inquirer": "8.0.0", diff --git a/src/cli.ts b/src/cli.ts index cfaf9eb..8873c20 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,12 +4,11 @@ import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { runCommandModule } from "./commands/project.commands"; import { generateProject } from "./generate"; +import { helpCommand } from "./help/cli"; import { infoProject } from "./info"; import { createProject } from "./new"; import { generateProviders } from "./providers"; -export const CLI_VERSION = "1.3.4"; - console.log(`\n[🐎 Expressots]\n`); yargs(hideBin(process.argv)) @@ -19,6 +18,7 @@ yargs(hideBin(process.argv)) .command(generateProviders()) .command(generateProject()) .command(infoProject()) + .command(helpCommand()) .example("$0 new expressots-demo", "Create interactively") .example("$0 new expressots-demo -d ./", "Create interactively with path") .example("$0 new expressots-demo -p yarn -t opinionated", "Create silently") diff --git a/src/help/cli.ts b/src/help/cli.ts new file mode 100644 index 0000000..489ce58 --- /dev/null +++ b/src/help/cli.ts @@ -0,0 +1,18 @@ +import { CommandModule } from "yargs"; +import { helpForm } from "./form"; + +// eslint-disable-next-line @typescript-eslint/ban-types +type CommandModuleArgs = {}; + +const helpCommand = (): CommandModule => { + return { + command: "resources", + describe: "Resource list", + aliases: ["r"], + handler: async () => { + await helpForm(); + }, + }; +}; + +export { helpCommand }; diff --git a/src/help/form.ts b/src/help/form.ts new file mode 100644 index 0000000..3e689e3 --- /dev/null +++ b/src/help/form.ts @@ -0,0 +1,46 @@ +import chalk from "chalk"; +import CliTable3 from "cli-table3"; + +const helpForm = async (): Promise => { + const table = new CliTable3({ + head: [ + chalk.green("Name"), + chalk.green("Alias"), + chalk.green("Description"), + ], + colWidths: [15, 10, 60], + }); + + table.push( + ["new project", "new", "Generate a new project"], + ["info", "i", "Provides project information"], + ["resources", "r", "Displays cli commands and resources"], + ["help", "h", "Show command help"], + [ + "service", + "g s", + "Generate a service [controller, usecase, dto, module]", + ], + ["controller", "g c", "Generate a controller"], + ["usecase", "g u", "Generate a usecase"], + ["dto", "g d", "Generate a dto"], + ["entity", "g e", "Generate an entity"], + ["provider", "g p", "Generate a provider"], + ["module", "g mo", "Generate a module"], + ["middleware", "g mi", "Generate a middleware"], + ); + console.log( + chalk.bold.white("ExpressoTS:", `${chalk.green("Resources List")}`), + ); + console.log(chalk.whiteBright(table.toString())); + console.log( + chalk.bold.white( + `📝 More info: ${chalk.green( + "https://doc.expresso-ts.com/docs/category/cli", + )}`, + ), + ); + console.log("\n"); +}; + +export { helpForm }; diff --git a/src/help/index.ts b/src/help/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/info/form.ts b/src/info/form.ts index 321417d..1e217ba 100644 --- a/src/info/form.ts +++ b/src/info/form.ts @@ -2,8 +2,27 @@ import chalk from "chalk"; import path from "path"; import fs from "fs"; import os from "os"; -import { CLI_VERSION } from "../cli"; import { printError } from "../utils/cli-ui"; +import { exec } from "child_process"; +import util from "util"; + +const execPromisified = util.promisify(exec); + +async function getCLIVersion(): Promise { + try { + const { stdout } = await execPromisified("npm list -g @expressots/cli"); + const version = stdout; + const versionRegex = /@expressots\/cli@(\d+\.\d+\.\d+)/; + const versionMatch: string = version.match(versionRegex)?.[1]; + return versionMatch; + } catch (error) { + printError( + "CLI version not available.", + "Unable to get the CLI version.", + ); + } + return ""; +} function getInfosFromPackage() { try { @@ -16,10 +35,12 @@ function getInfosFromPackage() { const packageJson = JSON.parse(fileContents); console.log(chalk.green("ExpressoTS Project:")); - console.log(chalk.bold(`\tName: ${packageJson.name}`)); - console.log(chalk.bold(`\tDescription: ${packageJson.description}`)); - console.log(chalk.bold(`\tVersion: ${packageJson.version}`)); - console.log(chalk.bold(`\tAuthor: ${packageJson.author}`)); + console.log(chalk.white(`\tName: ${packageJson.name}`)); + console.log(chalk.white(`\tDescription: ${packageJson.description}`)); + console.log(chalk.white(`\tVersion: ${packageJson.version}`)); + console.log(chalk.white(`\tAuthor: ${packageJson.author}`)); + + //getCLIVersion(); } catch (error) { printError( "No project information available.", @@ -28,15 +49,16 @@ function getInfosFromPackage() { } } -const infoForm = async (): Promise => { - console.log(chalk.green("System informations:")); - console.log(chalk.bold(`\tOS Version: ${os.version()}`)); - console.log(chalk.bold(`\tNodeJS version: ${process.version}`)); +const infoForm = (): void => { + const cliVersion = "1.6.0"; //await getCLIVersion(); + getInfosFromPackage(); + + console.log(chalk.green("System information:")); + console.log(chalk.white(`\tOS Version: ${os.version()}`)); + console.log(chalk.white(`\tNodeJS version: ${process.version}`)); console.log(chalk.green("CLI Version:")); - console.log(chalk.bold(`\tCurrent version: v${CLI_VERSION}`)); - - getInfosFromPackage(); + console.log(chalk.white(`\tCurrent version: v${cliVersion}`)); }; export { infoForm }; From 4056a5e0d2e85a43ff44ce8da571c0af1acd0785 Mon Sep 17 00:00:00 2001 From: Richard Zampieri Date: Fri, 29 Mar 2024 15:32:35 -0700 Subject: [PATCH 17/17] refactor: remove the cli version from info cmd --- src/info/form.ts | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/info/form.ts b/src/info/form.ts index 1e217ba..43016bc 100644 --- a/src/info/form.ts +++ b/src/info/form.ts @@ -3,26 +3,6 @@ import path from "path"; import fs from "fs"; import os from "os"; import { printError } from "../utils/cli-ui"; -import { exec } from "child_process"; -import util from "util"; - -const execPromisified = util.promisify(exec); - -async function getCLIVersion(): Promise { - try { - const { stdout } = await execPromisified("npm list -g @expressots/cli"); - const version = stdout; - const versionRegex = /@expressots\/cli@(\d+\.\d+\.\d+)/; - const versionMatch: string = version.match(versionRegex)?.[1]; - return versionMatch; - } catch (error) { - printError( - "CLI version not available.", - "Unable to get the CLI version.", - ); - } - return ""; -} function getInfosFromPackage() { try { @@ -39,8 +19,6 @@ function getInfosFromPackage() { console.log(chalk.white(`\tDescription: ${packageJson.description}`)); console.log(chalk.white(`\tVersion: ${packageJson.version}`)); console.log(chalk.white(`\tAuthor: ${packageJson.author}`)); - - //getCLIVersion(); } catch (error) { printError( "No project information available.", @@ -50,15 +28,11 @@ function getInfosFromPackage() { } const infoForm = (): void => { - const cliVersion = "1.6.0"; //await getCLIVersion(); getInfosFromPackage(); console.log(chalk.green("System information:")); console.log(chalk.white(`\tOS Version: ${os.version()}`)); console.log(chalk.white(`\tNodeJS version: ${process.version}`)); - - console.log(chalk.green("CLI Version:")); - console.log(chalk.white(`\tCurrent version: v${cliVersion}`)); }; export { infoForm };