opticore-feature-component 1.0.8 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{core-XPB2Z5AR.js → chunk-22JBML4S.js} +11 -7
- package/dist/cli.cjs +3062 -0
- package/dist/cli.d.cts +13 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +28 -0
- package/dist/index.cjs +1298 -1619
- package/dist/index.js +4 -1
- package/package.json +13 -1
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,3062 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/cli/registerFeatureCommand.ts
|
|
31
|
+
var registerFeatureCommand_exports = {};
|
|
32
|
+
__export(registerFeatureCommand_exports, {
|
|
33
|
+
handleFeatureCliError: () => handleFeatureCliError,
|
|
34
|
+
registerFeatureCommand: () => registerFeatureCommand
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(registerFeatureCommand_exports);
|
|
37
|
+
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
38
|
+
|
|
39
|
+
// src/core/core.ts
|
|
40
|
+
var import_process2 = __toESM(require("process"), 1);
|
|
41
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
42
|
+
var import_path9 = __toESM(require("path"), 1);
|
|
43
|
+
var import_prompts9 = require("@clack/prompts");
|
|
44
|
+
|
|
45
|
+
// src/utils/welcomeMessage.utils.ts
|
|
46
|
+
var import_cfonts = __toESM(require("cfonts"), 1);
|
|
47
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
48
|
+
function UWelcomeMessage() {
|
|
49
|
+
import_cfonts.default.say("OpticoreJS", {
|
|
50
|
+
font: "block",
|
|
51
|
+
align: "left",
|
|
52
|
+
colors: ["#D4E4FF", "#427ff5"],
|
|
53
|
+
background: "transparent",
|
|
54
|
+
letterSpacing: 1,
|
|
55
|
+
lineHeight: 1,
|
|
56
|
+
space: true,
|
|
57
|
+
maxLength: "0"
|
|
58
|
+
});
|
|
59
|
+
const blue = import_chalk.default.bold.hex("#427ff5");
|
|
60
|
+
const name = import_chalk.default.bold.hex("#003A9F");
|
|
61
|
+
const dim = import_chalk.default.bold.hex("#D4E4FF");
|
|
62
|
+
const subTitle = ` ${blue("")} ${name(" OPTICORE F E A T U R E M O D U L E")}
|
|
63
|
+
${blue("")} ${dim(" Create \xB7 Structure \xB7 Scaffold \xB7 Generate \xB7 Organize ")} ${blue("")}
|
|
64
|
+
${blue("")}`;
|
|
65
|
+
const FOOTER = `
|
|
66
|
+
${import_chalk.default.bold.dim("Documentation")} ${import_chalk.default.underline.cyan("https://github.com/guyzoum77/opticore-feature-cli")}
|
|
67
|
+
`;
|
|
68
|
+
console.log(subTitle);
|
|
69
|
+
console.log(FOOTER);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/utils/choiceFeatureCleanModule.ts
|
|
73
|
+
var import_ansi_colors2 = __toESM(require("ansi-colors"), 1);
|
|
74
|
+
var import_prompts2 = require("@clack/prompts");
|
|
75
|
+
|
|
76
|
+
// src/utils/prompt.utils.ts
|
|
77
|
+
var import_prompts = require("@clack/prompts");
|
|
78
|
+
var import_ansi_colors = __toESM(require("ansi-colors"), 1);
|
|
79
|
+
|
|
80
|
+
// src/utils/fsModule.utils.ts
|
|
81
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
82
|
+
var UFsModule = class {
|
|
83
|
+
static createDirectoryRecursively(dirPath) {
|
|
84
|
+
try {
|
|
85
|
+
import_fs.default.mkdirSync(dirPath, { recursive: true });
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error(e.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
static createFile(filePath, fileContent) {
|
|
91
|
+
try {
|
|
92
|
+
import_fs.default.writeFileSync(filePath, fileContent);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error(e.message);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
static removeDir(directory, recursive, force) {
|
|
98
|
+
try {
|
|
99
|
+
if (import_fs.default.existsSync(directory)) {
|
|
100
|
+
import_fs.default.rmSync(directory, { recursive, force });
|
|
101
|
+
}
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error(`Error removing folder: ${e.message}`, e);
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/utils/prompt.utils.ts
|
|
110
|
+
var import_opticore_logger = require("opticore-logger");
|
|
111
|
+
var import_opticore_http_response = require("opticore-http-response");
|
|
112
|
+
var UPrompt = class {
|
|
113
|
+
static logger = new import_opticore_logger.LoggerCore();
|
|
114
|
+
static async text(message, placeholder, validate) {
|
|
115
|
+
return await (0, import_prompts.text)(
|
|
116
|
+
{
|
|
117
|
+
message,
|
|
118
|
+
placeholder,
|
|
119
|
+
validate
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
static async confirm(message, defaultValue = true) {
|
|
124
|
+
return await (0, import_prompts.confirm)({
|
|
125
|
+
message,
|
|
126
|
+
initialValue: defaultValue
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
static cancelOperation(controllerPath) {
|
|
130
|
+
console.log(`${import_ansi_colors.default.bgRed(`${import_ansi_colors.default.white("Operation cancelled.")}`)}`);
|
|
131
|
+
if (controllerPath) {
|
|
132
|
+
try {
|
|
133
|
+
UFsModule.removeDir(controllerPath, true, true);
|
|
134
|
+
this.logger.info({
|
|
135
|
+
title: "DIR_REMOVED",
|
|
136
|
+
message: `Removed controller directory: ${controllerPath}`
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.logger.error({
|
|
140
|
+
errorType: error.name,
|
|
141
|
+
httpCodeValue: import_opticore_http_response.HttpStatusCode.INTERNAL_SERVER_ERROR,
|
|
142
|
+
message: error.message,
|
|
143
|
+
stackTrace: error.stack,
|
|
144
|
+
title: "FAILED_TO_REMOVE_CONTROLLER_DIR"
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
static async select(message, defaultValue, options) {
|
|
151
|
+
const optSelected = await (0, import_prompts.select)({
|
|
152
|
+
message,
|
|
153
|
+
initialValue: defaultValue,
|
|
154
|
+
options
|
|
155
|
+
});
|
|
156
|
+
if ((0, import_prompts.isCancel)(optSelected)) {
|
|
157
|
+
return optSelected;
|
|
158
|
+
}
|
|
159
|
+
return optSelected[0];
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/utils/choiceFeatureCleanModule.ts
|
|
164
|
+
async function choiceFeatureCleanModule() {
|
|
165
|
+
const featureSelected = await UPrompt.select(
|
|
166
|
+
"Please choose the creation principle for your feature :",
|
|
167
|
+
["opticore_principle"],
|
|
168
|
+
[
|
|
169
|
+
{ label: "OptiCoreJs CLEAN Module", value: ["opticore_clean_module"], hint: "recommended" },
|
|
170
|
+
{ label: "MVC Architecture", value: ["mvc_architecture"] },
|
|
171
|
+
{ label: "Layered Architecture", value: ["layered_architecture"] },
|
|
172
|
+
{ label: "Build your own architecture", value: ["custom_feature"] }
|
|
173
|
+
]
|
|
174
|
+
);
|
|
175
|
+
if ((0, import_prompts2.isCancel)(featureSelected)) {
|
|
176
|
+
console.log(`${import_ansi_colors2.default.bgRed(`${import_ansi_colors2.default.white("Operation cancelled.")}`)}`);
|
|
177
|
+
process.exit(0);
|
|
178
|
+
} else {
|
|
179
|
+
return featureSelected;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/utils/customFeatureInfo.utils.ts
|
|
184
|
+
var import_ansi_colors3 = __toESM(require("ansi-colors"), 1);
|
|
185
|
+
function UCustomFeatureInfo(choice) {
|
|
186
|
+
console.log(
|
|
187
|
+
`
|
|
188
|
+
You have chosen : ${choice === "opticore_clean_module" ? import_ansi_colors3.default.cyan("OptiCoreJs CLEAN Module") : import_ansi_colors3.default.cyan("Custom feature")}
|
|
189
|
+
`
|
|
190
|
+
);
|
|
191
|
+
const I = (s) => import_ansi_colors3.default.yellow(s);
|
|
192
|
+
const A = (s) => import_ansi_colors3.default.cyan(s);
|
|
193
|
+
const D = (s) => import_ansi_colors3.default.yellowBright(s);
|
|
194
|
+
const E = (s) => import_ansi_colors3.default.white(s);
|
|
195
|
+
const st = (s) => import_ansi_colors3.default.gray(s);
|
|
196
|
+
const diagram = [
|
|
197
|
+
` ` + I(`INFRASTRUCTURE`),
|
|
198
|
+
` ` + I(`controllers \xB7 repositories \xB7 routes`),
|
|
199
|
+
` ` + I(`presenters \xB7 routes`),
|
|
200
|
+
``,
|
|
201
|
+
` ` + A(`APPLICATION`),
|
|
202
|
+
` ` + A(`ports \xB7 interfaces \xB7 dtos \xB7 use-cases`),
|
|
203
|
+
``,
|
|
204
|
+
` ` + D(`DOMAIN`),
|
|
205
|
+
` ` + D(`events \xB7 exceptions`),
|
|
206
|
+
``,
|
|
207
|
+
` ` + E(`ENTITIES`),
|
|
208
|
+
` ` + E(`core \xB7 no external deps`),
|
|
209
|
+
``,
|
|
210
|
+
` ` + import_ansi_colors3.default.dim(`Dependency direction : outer layers depend on inner layers`),
|
|
211
|
+
``
|
|
212
|
+
];
|
|
213
|
+
console.log(diagram.join(`
|
|
214
|
+
`));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/services/featureName.service.ts
|
|
218
|
+
var _featureName = async () => {
|
|
219
|
+
return await UPrompt.text(
|
|
220
|
+
"Enter feature's name :",
|
|
221
|
+
"login",
|
|
222
|
+
(value) => {
|
|
223
|
+
let pattern = new RegExp("^[a-z][A-Za-z_-]+$");
|
|
224
|
+
if (!value) {
|
|
225
|
+
return "Please enter a feature's name.";
|
|
226
|
+
}
|
|
227
|
+
if (!pattern.test(value)) {
|
|
228
|
+
return "Please enter a valide feature's name.";
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/services/base.service.ts
|
|
235
|
+
var import_path = __toESM(require("path"), 1);
|
|
236
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
237
|
+
var import_ansi_colors4 = __toESM(require("ansi-colors"), 1);
|
|
238
|
+
var import_process = __toESM(require("process"), 1);
|
|
239
|
+
var BaseService = class {
|
|
240
|
+
static _isSubdirectoryExists(subdirectory) {
|
|
241
|
+
const subdirectoryString = typeof subdirectory === "symbol" ? subdirectory.toString() : String(subdirectory);
|
|
242
|
+
const subdirectoryPath = import_path.default.join(import_process.default.cwd(), "src", "features", subdirectoryString);
|
|
243
|
+
try {
|
|
244
|
+
const stats = import_node_fs.default.statSync(subdirectoryPath);
|
|
245
|
+
return stats.isDirectory();
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
static _featureFounded(feature) {
|
|
251
|
+
console.log(`${import_ansi_colors4.default.bgCyan(`${import_ansi_colors4.default.white(`${import_ansi_colors4.default.bold(`feature ${feature} is already exists.`)}`)}`)}`);
|
|
252
|
+
import_process.default.exit(0);
|
|
253
|
+
}
|
|
254
|
+
static _checkFeatureDir() {
|
|
255
|
+
console.error(`${import_ansi_colors4.default.bgRed(`${import_ansi_colors4.default.white("Check if directory src/features exists, and try again.")}`)}`);
|
|
256
|
+
import_process.default.exit();
|
|
257
|
+
}
|
|
258
|
+
static _dirFounded(feature) {
|
|
259
|
+
console.error(`${import_ansi_colors4.default.bgRed(`${import_ansi_colors4.default.white(`The ${import_ansi_colors4.default.bgCyan(`${import_ansi_colors4.default.bold(`feature ${feature} is already exists.`)}`)}`)}`)}`);
|
|
260
|
+
import_process.default.exit();
|
|
261
|
+
}
|
|
262
|
+
static _cancelOperation(controllerPath) {
|
|
263
|
+
console.log(`${import_ansi_colors4.default.bgRed(`${import_ansi_colors4.default.white("Operation cancelled.")}`)}`);
|
|
264
|
+
UFsModule.removeDir(controllerPath, true, true);
|
|
265
|
+
import_process.default.exit(0);
|
|
266
|
+
}
|
|
267
|
+
static async _createFeatureModuleDir(featureDir, feature) {
|
|
268
|
+
let ora = (await import("ora")).default;
|
|
269
|
+
const spinner = ora("Feature's name creation...").start();
|
|
270
|
+
UFsModule.createDirectoryRecursively(featureDir);
|
|
271
|
+
spinner.succeed(`Your feature"s name ${import_ansi_colors4.default.bgCyan(import_ansi_colors4.default.white(`${feature}`))} has been created successfully.
|
|
272
|
+
`);
|
|
273
|
+
return { featureDir, feature };
|
|
274
|
+
}
|
|
275
|
+
static async _createApplicationDirComponent(feature) {
|
|
276
|
+
let application = "";
|
|
277
|
+
application = `${feature}/application`;
|
|
278
|
+
UFsModule.createDirectoryRecursively(application);
|
|
279
|
+
return application;
|
|
280
|
+
}
|
|
281
|
+
static async _createDomainDirComponent(feature) {
|
|
282
|
+
let domain = "";
|
|
283
|
+
domain = `${feature}/domain`;
|
|
284
|
+
UFsModule.createDirectoryRecursively(domain);
|
|
285
|
+
return domain;
|
|
286
|
+
}
|
|
287
|
+
static async _createInfraDirComponent(feature) {
|
|
288
|
+
let infrastructure = "";
|
|
289
|
+
infrastructure = `${feature}/infrastructure`;
|
|
290
|
+
UFsModule.createDirectoryRecursively(infrastructure);
|
|
291
|
+
return infrastructure;
|
|
292
|
+
}
|
|
293
|
+
static async _createPersistenceDirComponent(feature) {
|
|
294
|
+
let persistence = "";
|
|
295
|
+
persistence = `${feature}/persistence`;
|
|
296
|
+
UFsModule.createDirectoryRecursively(persistence);
|
|
297
|
+
return persistence;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// src/services/controllerType.service.ts
|
|
302
|
+
async function _controllerType(messageText, defaultValue, options) {
|
|
303
|
+
return await UPrompt.select(
|
|
304
|
+
messageText,
|
|
305
|
+
[defaultValue],
|
|
306
|
+
options
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/core/components/application/createController.application.ts
|
|
311
|
+
var import_prompts5 = require("@clack/prompts");
|
|
312
|
+
|
|
313
|
+
// src/core/domains/constants/cleanType.constant.ts
|
|
314
|
+
var CCleanType = {
|
|
315
|
+
"clean_component_by_step": "clean_component_by_step",
|
|
316
|
+
"full_clean_component": "full_clean_component",
|
|
317
|
+
"simple_component": "simple_component"
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// src/templates/simpleComponent/simpleComponent.template.ts
|
|
321
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
322
|
+
var import_ansi_colors6 = __toESM(require("ansi-colors"), 1);
|
|
323
|
+
|
|
324
|
+
// src/utils/featureComponentGenerator.utils.ts
|
|
325
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
326
|
+
var import_ansi_colors5 = __toESM(require("ansi-colors"), 1);
|
|
327
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
328
|
+
var import_prompts3 = require("@clack/prompts");
|
|
329
|
+
var import_opticore_logger2 = require("opticore-logger");
|
|
330
|
+
|
|
331
|
+
// src/templates/cleanComponents/controller.template.ts
|
|
332
|
+
var generateControllerTemplate = (controllerName, featureName, methods) => {
|
|
333
|
+
const capitalized = capitalize(featureName);
|
|
334
|
+
const useCaseName = `${capitalized}UseCase`;
|
|
335
|
+
const presenterName = `${capitalized}Presenter`;
|
|
336
|
+
const methodsCode = methods.map((method) => generateMethod(method, featureName, capitalized)).join("\n");
|
|
337
|
+
const errorHandler = errorHandlerAppend(capitalized);
|
|
338
|
+
return `import { Request, Response } from "express";
|
|
339
|
+
import { ResponseHandler, HttpStatusCode, IResponseHandlerSuccessData } from "opticore-http-response";
|
|
340
|
+
import { ${useCaseName} } from "../../../application/use-cases/${featureName}.usecase";
|
|
341
|
+
import { ${presenterName} } from "../presenters/${featureName}.presenter";
|
|
342
|
+
import { ${capitalized}Repository } from "../repositories/${featureName}.repository";
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* ${controllerName} \u2014 HTTP Controller (Infrastructure Layer)
|
|
346
|
+
*/
|
|
347
|
+
export class ${controllerName} {
|
|
348
|
+
private static buildUseCase(): ${useCaseName} {
|
|
349
|
+
const repository = new ${capitalized}Repository();
|
|
350
|
+
return new ${useCaseName}(repository);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private static buildPresenter(): ${presenterName} {
|
|
354
|
+
return new ${presenterName}();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
${methodsCode}
|
|
358
|
+
${errorHandler}
|
|
359
|
+
}
|
|
360
|
+
`;
|
|
361
|
+
};
|
|
362
|
+
var generateMethod = (method, featureName, capitalized) => {
|
|
363
|
+
const lower = method.toLowerCase();
|
|
364
|
+
if (lower.includes("findall") || lower.includes("getall")) {
|
|
365
|
+
return generateFindAllMethod(method, featureName, capitalized);
|
|
366
|
+
}
|
|
367
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) {
|
|
368
|
+
return generateFindByIdMethod(method, featureName, capitalized);
|
|
369
|
+
}
|
|
370
|
+
if (lower.includes("create") || lower.includes("add")) {
|
|
371
|
+
return generateCreateMethod(method, featureName, capitalized);
|
|
372
|
+
}
|
|
373
|
+
if (lower.includes("update") || lower.includes("edit")) {
|
|
374
|
+
return generateUpdateMethod(method, featureName, capitalized);
|
|
375
|
+
}
|
|
376
|
+
if (lower.includes("delete") || lower.includes("remove")) {
|
|
377
|
+
return generateDeleteMethod(method, featureName, capitalized);
|
|
378
|
+
}
|
|
379
|
+
return `
|
|
380
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
381
|
+
try {
|
|
382
|
+
const useCase = ${capitalized}Controller.buildUseCase();
|
|
383
|
+
const presenter = ${capitalized}Controller.buildPresenter();
|
|
384
|
+
// TODO: Implement ${method} logic
|
|
385
|
+
// const result = await useCase.${method}(...);
|
|
386
|
+
return ResponseHandler.success(presenter.presentOne({} as any), "success", HttpStatusCode.OK);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
return ${capitalized}Controller.handleError(error);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
`;
|
|
392
|
+
};
|
|
393
|
+
var generateFindAllMethod = (method, featureName, capitalized) => `
|
|
394
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
395
|
+
try {
|
|
396
|
+
const useCase = ${capitalized}Controller.buildUseCase();
|
|
397
|
+
const presenter = ${capitalized}Controller.buildPresenter();
|
|
398
|
+
|
|
399
|
+
const results = await useCase.findAll();
|
|
400
|
+
return ResponseHandler.success(presenter.presentMany(results), "success", HttpStatusCode.OK);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return ${capitalized}Controller.handleError(error);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
var generateFindByIdMethod = (method, featureName, capitalized) => `
|
|
407
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
408
|
+
try {
|
|
409
|
+
const { id } = req.params;
|
|
410
|
+
const useCase = ${capitalized}Controller.buildUseCase();
|
|
411
|
+
const presenter = ${capitalized}Controller.buildPresenter();
|
|
412
|
+
|
|
413
|
+
const result = await useCase.findById(id);
|
|
414
|
+
if (!result) {
|
|
415
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
416
|
+
}
|
|
417
|
+
return ResponseHandler.success(presenter.presentOne(result), "success", HttpStatusCode.OK);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return ${capitalized}Controller.handleError(error);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
`;
|
|
423
|
+
var generateCreateMethod = (method, featureName, capitalized) => `
|
|
424
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
425
|
+
try {
|
|
426
|
+
const dto = req.body; // TODO: Validate with your validation layer
|
|
427
|
+
const useCase = ${capitalized}Controller.buildUseCase();
|
|
428
|
+
const presenter = ${capitalized}Controller.buildPresenter();
|
|
429
|
+
|
|
430
|
+
const result = await useCase.create(dto);
|
|
431
|
+
return ResponseHandler.success(presenter.presentOne(result), "created", HttpStatusCode.CREATED);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
return ${capitalized}Controller.handleError(error);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
`;
|
|
437
|
+
var generateUpdateMethod = (method, featureName, capitalized) => `
|
|
438
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
439
|
+
try {
|
|
440
|
+
const { id } = req.params;
|
|
441
|
+
const dto = { ...req.body, id }; // TODO: Validate with your validation layer
|
|
442
|
+
const useCase = ${capitalized}Controller.buildUseCase();
|
|
443
|
+
const presenter = ${capitalized}Controller.buildPresenter();
|
|
444
|
+
|
|
445
|
+
const result = await useCase.update(dto);
|
|
446
|
+
if (!result) {
|
|
447
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
448
|
+
}
|
|
449
|
+
return ResponseHandler.success(presenter.presentOne(result), "success", HttpStatusCode.OK);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
return ${capitalized}Controller.handleError(error);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
`;
|
|
455
|
+
var generateDeleteMethod = (method, featureName, capitalized) => `
|
|
456
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
457
|
+
try {
|
|
458
|
+
const { id } = req.params;
|
|
459
|
+
const useCase = ${capitalized}Controller.buildUseCase();
|
|
460
|
+
|
|
461
|
+
const deleted = await useCase.delete(id);
|
|
462
|
+
if (!deleted) {
|
|
463
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
464
|
+
}
|
|
465
|
+
return ResponseHandler.success(null, "deleted", HttpStatusCode.NO_CONTENT);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
return ${capitalized}Controller.handleError(error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
`;
|
|
471
|
+
var errorHandlerAppend = (capitalized) => `
|
|
472
|
+
private static handleError(error: unknown) {
|
|
473
|
+
const message = error instanceof Error
|
|
474
|
+
? error.message
|
|
475
|
+
: "Internal server error";
|
|
476
|
+
return ResponseHandler.error(message, HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
477
|
+
}
|
|
478
|
+
`;
|
|
479
|
+
var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
480
|
+
|
|
481
|
+
// src/templates/cleanComponents/entity.template.ts
|
|
482
|
+
var generateEntityTemplate = (entityName, featureName) => {
|
|
483
|
+
const capitalized = capitalize2(featureName);
|
|
484
|
+
return `/**
|
|
485
|
+
* ${entityName} \u2014 Domain Entity
|
|
486
|
+
* Represents the core business object for the "${featureName}" feature.
|
|
487
|
+
* No framework dependency, pure business logic only.
|
|
488
|
+
*/
|
|
489
|
+
export class ${entityName} {
|
|
490
|
+
private readonly _id: string;
|
|
491
|
+
private _createdAt: Date;
|
|
492
|
+
private _updatedAt: Date;
|
|
493
|
+
|
|
494
|
+
constructor(
|
|
495
|
+
id: string,
|
|
496
|
+
// TODO: Add your business properties here
|
|
497
|
+
createdAt?: Date,
|
|
498
|
+
updatedAt?: Date,
|
|
499
|
+
) {
|
|
500
|
+
this._id = id;
|
|
501
|
+
this._createdAt = createdAt ?? new Date();
|
|
502
|
+
this._updatedAt = updatedAt ?? new Date();
|
|
503
|
+
|
|
504
|
+
this.validate();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
get id(): string {
|
|
508
|
+
return this._id;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
get createdAt(): Date {
|
|
512
|
+
return this._createdAt;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
get updatedAt(): Date {
|
|
516
|
+
return this._updatedAt;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Marks the entity as updated.
|
|
521
|
+
* Call this whenever a business mutation occurs.
|
|
522
|
+
*/
|
|
523
|
+
public touch(): void {
|
|
524
|
+
this._updatedAt = new Date();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Returns a plain object snapshot of the entity.
|
|
529
|
+
* Useful for persistence or presentation mapping.
|
|
530
|
+
*/
|
|
531
|
+
public toSnapshot(): Record<string, unknown> {
|
|
532
|
+
return {
|
|
533
|
+
id: this._id,
|
|
534
|
+
createdAt: this._createdAt,
|
|
535
|
+
updatedAt: this._updatedAt,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private validate(): void {
|
|
540
|
+
if (!this._id || this._id.trim().length === 0) {
|
|
541
|
+
throw new Error(\`[${entityName}] id must not be empty.\`);
|
|
542
|
+
}
|
|
543
|
+
// TODO: Add your domain invariant checks here
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
};
|
|
548
|
+
var capitalize2 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
549
|
+
|
|
550
|
+
// src/templates/cleanComponents/repositoryInterface.template.ts
|
|
551
|
+
var generateRepositoryInterfaceTemplate = (interfaceName, featureName) => {
|
|
552
|
+
const entityName = `${capitalize3(featureName)}Entity`;
|
|
553
|
+
const entityImport = `../../../domain/entities/${featureName}.entity`;
|
|
554
|
+
return `import { ${entityName} } from "${entityImport}";
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* ${interfaceName} \u2014 Repository Port (Application Layer)
|
|
558
|
+
*
|
|
559
|
+
* Defines the persistence contract. The infrastructure layer must implement this.
|
|
560
|
+
* The application layer depends on this abstraction \u2014 never on a concrete ORM or DB.
|
|
561
|
+
*/
|
|
562
|
+
export interface ${interfaceName} {
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Returns all ${featureName} entities.
|
|
566
|
+
*/
|
|
567
|
+
findAll(): Promise<${entityName}[]>;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Finds a single ${featureName} by its unique identifier.
|
|
571
|
+
* Returns null if not found (avoid throwing for "not found" in repositories).
|
|
572
|
+
*/
|
|
573
|
+
findById(id: string): Promise<${entityName} | null>;
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Persists a new ${featureName} entity.
|
|
577
|
+
* Returns the created entity (with generated id if applicable).
|
|
578
|
+
*/
|
|
579
|
+
create(entity: ${entityName}): Promise<${entityName}>;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Persists changes to an existing ${featureName} entity.
|
|
583
|
+
* Returns the updated entity, or null if not found.
|
|
584
|
+
*/
|
|
585
|
+
update(entity: ${entityName}): Promise<${entityName} | null>;
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Removes a ${featureName} entity by id.
|
|
589
|
+
* Returns true if deleted, false if not found.
|
|
590
|
+
*/
|
|
591
|
+
delete(id: string): Promise<boolean>;
|
|
592
|
+
}
|
|
593
|
+
`;
|
|
594
|
+
};
|
|
595
|
+
var capitalize3 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
596
|
+
|
|
597
|
+
// src/templates/cleanComponents/repositoryImpl.template.ts
|
|
598
|
+
var generateRepositoryImplTemplate = (featureName, interfaceImportPath) => {
|
|
599
|
+
const capitalized = capitalize4(featureName);
|
|
600
|
+
const implName = `${capitalized}Repository`;
|
|
601
|
+
const interfaceName = `I${capitalized}Repository`;
|
|
602
|
+
const entityName = `${capitalized}Entity`;
|
|
603
|
+
const entityImport = `../../../domain/entities/${featureName}.entity`;
|
|
604
|
+
return `import { ${interfaceName} } from "${interfaceImportPath}";
|
|
605
|
+
import { ${entityName} } from "${entityImport}";
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* ${implName} \u2014 Repository Implementation (Infrastructure Layer)
|
|
609
|
+
*
|
|
610
|
+
* Provides the actual persistence logic for ${featureName}.
|
|
611
|
+
* Replace the in-memory store below with your ORM (TypeORM, Prisma, Mongoose, etc.).
|
|
612
|
+
* This class depends on the ${interfaceName} contract defined in the application layer.
|
|
613
|
+
*/
|
|
614
|
+
export class ${implName} implements ${interfaceName} {
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* TODO: Inject your database client / ORM model here via the constructor.
|
|
618
|
+
* Example with TypeORM: constructor(private readonly repo: Repository<${capitalized}Model>) {}
|
|
619
|
+
*
|
|
620
|
+
* Example with Prisma: constructor(private readonly prisma: PrismaClient) {}
|
|
621
|
+
*/
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* In-memory store (replace with real persistence)
|
|
625
|
+
*/
|
|
626
|
+
private store: Map<string, ${entityName}> = new Map();
|
|
627
|
+
|
|
628
|
+
async findAll(): Promise<${entityName}[]> {
|
|
629
|
+
// TODO: Replace with your ORM query
|
|
630
|
+
// Example (TypeORM): return this.repo.find();
|
|
631
|
+
// Example (Prisma): const rows = await this.prisma.${featureName}.findMany();
|
|
632
|
+
// return rows.map(row => this.toDomain(row));
|
|
633
|
+
return Array.from(this.store.values());
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async findById(id: string): Promise<${entityName} | null> {
|
|
637
|
+
// TODO: Replace with your ORM query
|
|
638
|
+
// Example (TypeORM): return this.repo.findOneBy({ id }) ?? null;
|
|
639
|
+
// Example (Prisma): const row = await this.prisma.${featureName}.findUnique({ where: { id } });
|
|
640
|
+
// return row ? this.toDomain(row) : null;
|
|
641
|
+
return this.store.get(id) ?? null;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
async create(entity: ${entityName}): Promise<${entityName}> {
|
|
645
|
+
// TODO: Replace with your ORM save
|
|
646
|
+
// Example (TypeORM): const model = this.repo.create(this.toPersistence(entity));
|
|
647
|
+
// return this.toDomain(await this.repo.save(model));
|
|
648
|
+
// Example (Prisma): const row = await this.prisma.${featureName}.create({ data: this.toPersistence(entity) });
|
|
649
|
+
// return this.toDomain(row);
|
|
650
|
+
this.store.set(entity.id, entity);
|
|
651
|
+
return entity;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async update(entity: ${entityName}): Promise<${entityName} | null> {
|
|
655
|
+
// TODO: Replace with your ORM update
|
|
656
|
+
if (!this.store.has(entity.id)) return null;
|
|
657
|
+
this.store.set(entity.id, entity);
|
|
658
|
+
return entity;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async delete(id: string): Promise<boolean> {
|
|
662
|
+
// TODO: Replace with your ORM delete
|
|
663
|
+
// Example (TypeORM): const result = await this.repo.delete(id);
|
|
664
|
+
// return (result.affected ?? 0) > 0;
|
|
665
|
+
return this.store.delete(id);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Maps an ORM/DB row to a domain entity.
|
|
671
|
+
* TODO: Adapt field mapping to your actual persistence model.
|
|
672
|
+
*/
|
|
673
|
+
private toDomain(row: any): ${entityName} {
|
|
674
|
+
return new ${entityName}(
|
|
675
|
+
row.id,
|
|
676
|
+
// TODO: Map your ORM fields here
|
|
677
|
+
row.createdAt,
|
|
678
|
+
row.updatedAt,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Maps a domain entity to an ORM/DB-friendly plain object.
|
|
684
|
+
* TODO: Adapt to your schema.
|
|
685
|
+
*/
|
|
686
|
+
private toPersistence(entity: ${entityName}): Record<string, unknown> {
|
|
687
|
+
return {
|
|
688
|
+
id: entity.id,
|
|
689
|
+
// TODO: Map your domain fields here
|
|
690
|
+
createdAt: entity.createdAt,
|
|
691
|
+
updatedAt: entity.updatedAt,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
`;
|
|
696
|
+
};
|
|
697
|
+
var capitalize4 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
698
|
+
|
|
699
|
+
// src/templates/cleanComponents/useCase.template.ts
|
|
700
|
+
var generateUseCaseTemplate = (useCaseName, featureName) => {
|
|
701
|
+
const capitalized = capitalize5(featureName);
|
|
702
|
+
const interfaceName = `I${capitalized}Repository`;
|
|
703
|
+
const entityName = `${capitalized}Entity`;
|
|
704
|
+
return `import { ${interfaceName} } from "../ports/repositories/${featureName}.repository.interface";
|
|
705
|
+
import { ${entityName} } from "../../domain/entities/${featureName}.entity";
|
|
706
|
+
import {
|
|
707
|
+
Create${capitalized}Dto,
|
|
708
|
+
Update${capitalized}Dto,
|
|
709
|
+
${capitalized}ResponseDto,
|
|
710
|
+
${capitalized}DtoMapper,
|
|
711
|
+
} from "../dtos/${featureName}.dto";
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* ${useCaseName} \u2014 Application Use Case
|
|
715
|
+
*
|
|
716
|
+
* Orchestrates business operations for the "${featureName}" feature.
|
|
717
|
+
* It depends ONLY on the repository port (interface), never on a concrete implementation.
|
|
718
|
+
* The concrete repository is injected at the infrastructure level (DI container or manual wiring).
|
|
719
|
+
*/
|
|
720
|
+
export class ${useCaseName} {
|
|
721
|
+
|
|
722
|
+
constructor(private readonly repository: ${interfaceName}) {}
|
|
723
|
+
|
|
724
|
+
async findAll(): Promise<${capitalized}ResponseDto[]> {
|
|
725
|
+
const entities = await this.repository.findAll();
|
|
726
|
+
return ${capitalized}DtoMapper.toResponseList(entities);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async findById(id: string): Promise<${capitalized}ResponseDto | null> {
|
|
730
|
+
const entity = await this.repository.findById(id);
|
|
731
|
+
if (!entity) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
return ${capitalized}DtoMapper.toResponse(entity);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async create(dto: Create${capitalized}Dto): Promise<${capitalized}ResponseDto> {
|
|
738
|
+
// TODO: Map DTO to domain entity. Generate id using your preferred strategy (uuid, etc.)
|
|
739
|
+
const id = crypto.randomUUID();
|
|
740
|
+
const entity = new ${entityName}(
|
|
741
|
+
id,
|
|
742
|
+
// TODO: Pass your domain fields from dto
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
const saved = await this.repository.create(entity);
|
|
746
|
+
return ${capitalized}DtoMapper.toResponse(saved);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async update(dto: Update${capitalized}Dto): Promise<${capitalized}ResponseDto | null> {
|
|
750
|
+
const existing = await this.repository.findById(dto.id);
|
|
751
|
+
if (!existing) {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// TODO: Apply the update to the domain entity
|
|
756
|
+
// existing.updateName(dto.name);
|
|
757
|
+
existing.touch();
|
|
758
|
+
|
|
759
|
+
const updated = await this.repository.update(existing);
|
|
760
|
+
if (!updated) {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
return ${capitalized}DtoMapper.toResponse(updated);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async delete(id: string): Promise<boolean> {
|
|
767
|
+
return this.repository.delete(id);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
`;
|
|
771
|
+
};
|
|
772
|
+
var capitalize5 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
773
|
+
|
|
774
|
+
// src/templates/cleanComponents/dto.template.ts
|
|
775
|
+
var generateDtoTemplate = (featureName) => {
|
|
776
|
+
const capitalized = capitalize6(featureName);
|
|
777
|
+
return `/**
|
|
778
|
+
* ${capitalized}Dto \u2014 Data Transfer Objects (Application Layer)
|
|
779
|
+
*
|
|
780
|
+
* DTOs define the shape of data entering and leaving use cases.
|
|
781
|
+
* They decouple the domain model from HTTP requests/responses.
|
|
782
|
+
* Validation is handled at the infrastructure layer (e.g., in the controller).
|
|
783
|
+
*/
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
export interface Create${capitalized}Dto {
|
|
787
|
+
// TODO: Define the fields required to create a ${featureName}
|
|
788
|
+
// Example:
|
|
789
|
+
// name: string;
|
|
790
|
+
// email: string;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export interface Update${capitalized}Dto {
|
|
794
|
+
id: string;
|
|
795
|
+
// TODO: Define the fields allowed to be updated. All fields should be optional (partial update pattern)
|
|
796
|
+
// Example: name?: string;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export interface ${capitalized}ResponseDto {
|
|
800
|
+
id: string;
|
|
801
|
+
createdAt: Date;
|
|
802
|
+
updatedAt: Date;
|
|
803
|
+
// TODO: Mirror the fields you expose to clients. Avoid leaking internal domain model details (e.g., private fields, passwords)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
import { ${capitalized}Entity } from "../../domain/entities/${featureName}.entity";
|
|
807
|
+
|
|
808
|
+
export class ${capitalized}DtoMapper {
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Maps a domain entity to a safe response DTO.
|
|
812
|
+
* Use this in your presenter or use case output.
|
|
813
|
+
*/
|
|
814
|
+
static toResponse(entity: ${capitalized}Entity): ${capitalized}ResponseDto {
|
|
815
|
+
return {
|
|
816
|
+
id: entity.id,
|
|
817
|
+
createdAt: entity.createdAt,
|
|
818
|
+
updatedAt: entity.updatedAt,
|
|
819
|
+
// TODO: Map your additional fields here
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Maps an array of entities to response DTOs.
|
|
825
|
+
*/
|
|
826
|
+
static toResponseList(entities: ${capitalized}Entity[]): ${capitalized}ResponseDto[] {
|
|
827
|
+
return entities.map((entity) => this.toResponse(entity));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
`;
|
|
831
|
+
};
|
|
832
|
+
var capitalize6 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
833
|
+
|
|
834
|
+
// src/templates/cleanComponents/presenter.template.ts
|
|
835
|
+
var generatePresenterInterfaceTemplate = (featureName) => {
|
|
836
|
+
const capitalized = capitalize7(featureName);
|
|
837
|
+
const interfaceName = `I${capitalized}Presenter`;
|
|
838
|
+
return `import { ${capitalized}ResponseDto } from "../../dtos/${featureName}.dto";
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* ${interfaceName} \u2014 Presenter Port (Application Layer)
|
|
842
|
+
*
|
|
843
|
+
* Defines how a use case result is formatted for the client.
|
|
844
|
+
* The infrastructure layer provides the concrete implementation
|
|
845
|
+
* (e.g., JSON serialization, HTTP envelopes, pagination wrappers).
|
|
846
|
+
*/
|
|
847
|
+
export interface ${interfaceName} {
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Formats a single ${featureName} response.
|
|
851
|
+
*/
|
|
852
|
+
presentOne(data: ${capitalized}ResponseDto): unknown;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Formats a list of ${featureName} responses.
|
|
856
|
+
*/
|
|
857
|
+
presentMany(data: ${capitalized}ResponseDto[]): unknown;
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Formats a "not found" response.
|
|
861
|
+
*/
|
|
862
|
+
presentNotFound(id?: string): unknown;
|
|
863
|
+
}
|
|
864
|
+
`;
|
|
865
|
+
};
|
|
866
|
+
var generatePresenterImplTemplate = (featureName) => {
|
|
867
|
+
const capitalized = capitalize7(featureName);
|
|
868
|
+
const implName = `${capitalized}Presenter`;
|
|
869
|
+
const interfaceName = `I${capitalized}Presenter`;
|
|
870
|
+
return `import { ${interfaceName} } from "../../../application/ports/presenters/${featureName}.presenter.interface";
|
|
871
|
+
import { ${capitalized}ResponseDto } from "../../../application/dtos/${featureName}.dto";
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* ${implName} \u2014 Presenter Implementation (Infrastructure Layer)
|
|
875
|
+
*
|
|
876
|
+
* Formats use case output into a client-facing structure.
|
|
877
|
+
* Keeps HTTP/JSON concerns OUT of the domain and application layers.
|
|
878
|
+
*/
|
|
879
|
+
export class ${implName} implements ${interfaceName} {
|
|
880
|
+
|
|
881
|
+
presentOne(data: ${capitalized}ResponseDto): unknown {
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
data,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
presentMany(data: ${capitalized}ResponseDto[]): unknown {
|
|
889
|
+
return {
|
|
890
|
+
success: true,
|
|
891
|
+
count: data.length,
|
|
892
|
+
data,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
presentNotFound(id?: string): unknown {
|
|
897
|
+
return {
|
|
898
|
+
success: false,
|
|
899
|
+
message: id
|
|
900
|
+
? \`${capitalized} with id "\${id}" was not found.\`
|
|
901
|
+
: \`${capitalized} not found.\`,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
`;
|
|
906
|
+
};
|
|
907
|
+
var capitalize7 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
908
|
+
|
|
909
|
+
// src/templates/cleanComponents/router.template.ts
|
|
910
|
+
var resolveRoute = (methodName, featureName) => {
|
|
911
|
+
const lower = methodName.toLowerCase();
|
|
912
|
+
if (lower.includes("findall") || lower.includes("getall")) return { httpMethod: "get", path: `/${featureName}` };
|
|
913
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) return { httpMethod: "get", path: `/${featureName}/:id` };
|
|
914
|
+
if (lower.includes("create") || lower.includes("add")) return { httpMethod: "post", path: `/${featureName}` };
|
|
915
|
+
if (lower.includes("update") || lower.includes("edit")) return { httpMethod: "put", path: `/${featureName}/:id` };
|
|
916
|
+
if (lower.includes("delete") || lower.includes("remove")) return { httpMethod: "delete", path: `/${featureName}/:id` };
|
|
917
|
+
return { httpMethod: "get", path: `/${featureName}/${lower}` };
|
|
918
|
+
};
|
|
919
|
+
var generateRouterHandlerTemplate = (featureName, controllerMethods) => {
|
|
920
|
+
const capitalized = capitalize8(featureName);
|
|
921
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
922
|
+
const controllerName = `${capitalized}Controller`;
|
|
923
|
+
const routesCode = (controllerMethods || []).map((method) => {
|
|
924
|
+
const { httpMethod, path: path10 } = resolveRoute(method, featureName);
|
|
925
|
+
return `{
|
|
926
|
+
path: \`${path10}\`,
|
|
927
|
+
method: "${httpMethod}",
|
|
928
|
+
middlewares: [],
|
|
929
|
+
handler: async (ctx: ICustomContext) => await ${controllerName}.${method}(ctx.req, ctx.res)
|
|
930
|
+
}`;
|
|
931
|
+
}).join(",\n");
|
|
932
|
+
return `import { OpticoreRoutingFactory, ICustomContext, IMultipleRouteDefinition } from "opticore-router";
|
|
933
|
+
import { ${controllerName} } from "../adapters/controllers/${featureName}.controller";
|
|
934
|
+
|
|
935
|
+
export const ${handlerName}: () => IMultipleRouteDefinition = () => {
|
|
936
|
+
return OpticoreRoutingFactory.routes(
|
|
937
|
+
${controllerName},
|
|
938
|
+
[
|
|
939
|
+
${routesCode.replace(/\n/g, "\n ")}
|
|
940
|
+
]
|
|
941
|
+
);
|
|
942
|
+
};
|
|
943
|
+
`;
|
|
944
|
+
};
|
|
945
|
+
var generateRouterTemplate = (featureName) => {
|
|
946
|
+
const capitalized = capitalize8(featureName);
|
|
947
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
948
|
+
const routerName = `${capitalized}Router`;
|
|
949
|
+
return `import { ${handlerName} } from "./${featureName}.router.handler";
|
|
950
|
+
import { TFeatureRoutes } from "opticore-router";
|
|
951
|
+
|
|
952
|
+
export const ${routerName}: TFeatureRoutes = {
|
|
953
|
+
routes: [
|
|
954
|
+
{
|
|
955
|
+
path: ${handlerName}().path,
|
|
956
|
+
handler: ${handlerName}().handler
|
|
957
|
+
}
|
|
958
|
+
]
|
|
959
|
+
};
|
|
960
|
+
`;
|
|
961
|
+
};
|
|
962
|
+
var capitalize8 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
963
|
+
|
|
964
|
+
// src/templates/cleanComponents/event.template.ts
|
|
965
|
+
var generateEventTemplate = (featureName) => {
|
|
966
|
+
const capitalized = capitalize9(featureName);
|
|
967
|
+
return `/**
|
|
968
|
+
* ${capitalized}Event \u2014 Domain Events
|
|
969
|
+
*
|
|
970
|
+
* Domain events capture facts that happened in the domain.
|
|
971
|
+
* They are immutable, named in the past tense, and carry the data
|
|
972
|
+
* that was relevant at the moment the event occurred.
|
|
973
|
+
*
|
|
974
|
+
* Use cases:
|
|
975
|
+
* - Trigger side effects (send email, notify another bounded context)
|
|
976
|
+
* - Feed an event bus / message broker (RabbitMQ, Kafka, etc.)
|
|
977
|
+
* - Build an audit log or event sourcing stream
|
|
978
|
+
*/
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Unique event identifier (uuid)
|
|
983
|
+
* When the event occurred
|
|
984
|
+
* The aggregate that emitted this event
|
|
985
|
+
*/
|
|
986
|
+
export interface IDomainEvent {
|
|
987
|
+
readonly eventId: string;
|
|
988
|
+
readonly occurredAt: Date;
|
|
989
|
+
readonly aggregateId: string;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Concrete events for the "${featureName}" feature
|
|
994
|
+
*/
|
|
995
|
+
export class ${capitalized}CreatedEvent implements IDomainEvent {
|
|
996
|
+
readonly eventId: string;
|
|
997
|
+
readonly occurredAt: Date;
|
|
998
|
+
readonly aggregateId: string;
|
|
999
|
+
|
|
1000
|
+
constructor(
|
|
1001
|
+
aggregateId: string,
|
|
1002
|
+
// TODO: Add the fields that describe what was created
|
|
1003
|
+
) {
|
|
1004
|
+
this.eventId = crypto.randomUUID();
|
|
1005
|
+
this.occurredAt = new Date();
|
|
1006
|
+
this.aggregateId = aggregateId;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
export class ${capitalized}UpdatedEvent implements IDomainEvent {
|
|
1011
|
+
readonly eventId: string;
|
|
1012
|
+
readonly occurredAt: Date;
|
|
1013
|
+
readonly aggregateId: string;
|
|
1014
|
+
|
|
1015
|
+
constructor(
|
|
1016
|
+
aggregateId: string,
|
|
1017
|
+
// TODO: Add the fields that changed
|
|
1018
|
+
) {
|
|
1019
|
+
this.eventId = crypto.randomUUID();
|
|
1020
|
+
this.occurredAt = new Date();
|
|
1021
|
+
this.aggregateId = aggregateId;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export class ${capitalized}DeletedEvent implements IDomainEvent {
|
|
1026
|
+
readonly eventId: string;
|
|
1027
|
+
readonly occurredAt: Date;
|
|
1028
|
+
readonly aggregateId: string;
|
|
1029
|
+
|
|
1030
|
+
constructor(aggregateId: string) {
|
|
1031
|
+
this.eventId = crypto.randomUUID();
|
|
1032
|
+
this.occurredAt = new Date();
|
|
1033
|
+
this.aggregateId = aggregateId;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
`;
|
|
1037
|
+
};
|
|
1038
|
+
var capitalize9 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1039
|
+
|
|
1040
|
+
// src/templates/cleanComponents/exception.template.ts
|
|
1041
|
+
var generateExceptionTemplate = (featureName) => {
|
|
1042
|
+
const capitalized = capitalize10(featureName);
|
|
1043
|
+
return `/**
|
|
1044
|
+
* ${capitalized}Exception \u2014 Domain Exceptions
|
|
1045
|
+
*
|
|
1046
|
+
* Domain exceptions represent violations of business rules specific to "${featureName}".
|
|
1047
|
+
* They are thrown inside the domain or use case layer and caught at the infrastructure
|
|
1048
|
+
* layer (controller) to be translated into appropriate HTTP responses.
|
|
1049
|
+
*
|
|
1050
|
+
* Rule: never import HTTP status codes or Express here \u2014 this is pure domain logic.
|
|
1051
|
+
*/
|
|
1052
|
+
|
|
1053
|
+
export class ${capitalized}DomainException extends Error {
|
|
1054
|
+
/** Machine-readable error code for programmatic handling */
|
|
1055
|
+
readonly code: string;
|
|
1056
|
+
|
|
1057
|
+
constructor(message: string, code: string) {
|
|
1058
|
+
super(message);
|
|
1059
|
+
this.name = "${capitalized}DomainException";
|
|
1060
|
+
this.code = code;
|
|
1061
|
+
|
|
1062
|
+
// Maintains proper prototype chain in TypeScript
|
|
1063
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* Thrown when a ${featureName} cannot be found by its identifier.
|
|
1070
|
+
* The controller should translate this to HTTP 404.
|
|
1071
|
+
*/
|
|
1072
|
+
export class ${capitalized}NotFoundException extends ${capitalized}DomainException {
|
|
1073
|
+
constructor(id: string) {
|
|
1074
|
+
super(
|
|
1075
|
+
\`${capitalized} with id "\${id}" does not exist.\`,
|
|
1076
|
+
"${featureName.toUpperCase()}_NOT_FOUND",
|
|
1077
|
+
);
|
|
1078
|
+
this.name = "${capitalized}NotFoundException";
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Thrown when an operation violates a uniqueness constraint.
|
|
1084
|
+
* The controller should translate this to HTTP 409.
|
|
1085
|
+
*/
|
|
1086
|
+
export class ${capitalized}AlreadyExistsException extends ${capitalized}DomainException {
|
|
1087
|
+
constructor(field: string, value: string) {
|
|
1088
|
+
super(
|
|
1089
|
+
\`A ${featureName} with \${field} "\${value}" already exists.\`,
|
|
1090
|
+
"${featureName.toUpperCase()}_ALREADY_EXISTS",
|
|
1091
|
+
);
|
|
1092
|
+
this.name = "${capitalized}AlreadyExistsException";
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Thrown when the provided data violates a business invariant.
|
|
1098
|
+
* The controller should translate this to HTTP 422.
|
|
1099
|
+
*/
|
|
1100
|
+
export class ${capitalized}InvalidDataException extends ${capitalized}DomainException {
|
|
1101
|
+
constructor(reason: string) {
|
|
1102
|
+
super(
|
|
1103
|
+
\`Invalid ${featureName} data: \${reason}\`,
|
|
1104
|
+
"${featureName.toUpperCase()}_INVALID_DATA",
|
|
1105
|
+
);
|
|
1106
|
+
this.name = "${capitalized}InvalidDataException";
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Type guard to check if an unknown error is a ${capitalized}DomainException.
|
|
1112
|
+
* Use this in your controller's catch block to map domain errors to HTTP responses.
|
|
1113
|
+
*
|
|
1114
|
+
* @example
|
|
1115
|
+
* try {
|
|
1116
|
+
* await useCase.create(dto);
|
|
1117
|
+
* } catch (error) {
|
|
1118
|
+
* if (is${capitalized}DomainException(error)) {
|
|
1119
|
+
* switch (error.code) {
|
|
1120
|
+
* case "${featureName.toUpperCase()}_NOT_FOUND": return res.status(404).json(...);
|
|
1121
|
+
* case "${featureName.toUpperCase()}_ALREADY_EXISTS": return res.status(409).json(...);
|
|
1122
|
+
* case "${featureName.toUpperCase()}_INVALID_DATA": return res.status(422).json(...);
|
|
1123
|
+
* }
|
|
1124
|
+
* }
|
|
1125
|
+
* // Unknown error \u2014 rethrow or return 500
|
|
1126
|
+
* throw error;
|
|
1127
|
+
* }
|
|
1128
|
+
*/
|
|
1129
|
+
export const is${capitalized}DomainException = (error: unknown): error is ${capitalized}DomainException =>
|
|
1130
|
+
error instanceof ${capitalized}DomainException;
|
|
1131
|
+
`;
|
|
1132
|
+
};
|
|
1133
|
+
var capitalize10 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1134
|
+
|
|
1135
|
+
// src/templates/cleanComponents/service.template.ts
|
|
1136
|
+
var generateServiceTemplate = (featureName) => {
|
|
1137
|
+
const capitalized = capitalize11(featureName);
|
|
1138
|
+
return `import { ${capitalized}ResponseDto } from "../../dtos/${featureName}.dto";
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* I${capitalized}Service \u2014 Application Service Port
|
|
1142
|
+
*
|
|
1143
|
+
* Application services handle orchestration logic that sits ABOVE a single use case
|
|
1144
|
+
* but BELOW the HTTP layer. They are the right place for:
|
|
1145
|
+
*
|
|
1146
|
+
* - Cross-use-case workflows (e.g. "create + notify + log in one transaction")
|
|
1147
|
+
* - Business rules that span multiple aggregates
|
|
1148
|
+
* - Coordination with external systems (email, payment, 3rd-party APIs)
|
|
1149
|
+
* whose contracts are defined here as abstractions
|
|
1150
|
+
*
|
|
1151
|
+
* The infrastructure layer provides the concrete implementation.
|
|
1152
|
+
* Use cases depend on this interface \u2014 never on the concrete class.
|
|
1153
|
+
*/
|
|
1154
|
+
export interface I${capitalized}Service {
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Full creation workflow for a ${featureName}.
|
|
1158
|
+
* May involve validation, persistence, event publishing, notifications, etc.
|
|
1159
|
+
*
|
|
1160
|
+
* @param data - Raw input (already validated by the controller)
|
|
1161
|
+
* @returns The created ${featureName} as a response DTO
|
|
1162
|
+
*/
|
|
1163
|
+
handleCreate(data: Record<string, unknown>): Promise<${capitalized}ResponseDto>;
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Full update workflow for a ${featureName}.
|
|
1167
|
+
*
|
|
1168
|
+
* @param id - Identifier of the ${featureName} to update
|
|
1169
|
+
* @param data - Fields to update
|
|
1170
|
+
* @returns The updated ${featureName}, or null if not found
|
|
1171
|
+
*/
|
|
1172
|
+
handleUpdate(id: string, data: Record<string, unknown>): Promise<${capitalized}ResponseDto | null>;
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Full deletion workflow for a ${featureName}.
|
|
1176
|
+
* May involve cleanup tasks (remove related files, revoke tokens, etc.)
|
|
1177
|
+
*
|
|
1178
|
+
* @param id - Identifier of the ${featureName} to delete
|
|
1179
|
+
* @returns true if deleted, false if not found
|
|
1180
|
+
*/
|
|
1181
|
+
handleDelete(id: string): Promise<boolean>;
|
|
1182
|
+
|
|
1183
|
+
// TODO: Add feature-specific service methods below
|
|
1184
|
+
// Example:
|
|
1185
|
+
// sendWelcomeNotification(id: string): Promise<void>;
|
|
1186
|
+
// exportToCsv(filters: Record<string, unknown>): Promise<Buffer>;
|
|
1187
|
+
}
|
|
1188
|
+
`;
|
|
1189
|
+
};
|
|
1190
|
+
var capitalize11 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1191
|
+
|
|
1192
|
+
// src/templates/simpleComponent/model.template.ts
|
|
1193
|
+
var generateModelTemplate = (featureName) => {
|
|
1194
|
+
const capitalized = capitalize12(featureName);
|
|
1195
|
+
const modelName = `${capitalized}Model`;
|
|
1196
|
+
return `/**
|
|
1197
|
+
* ${modelName} \u2014 Data Model
|
|
1198
|
+
* Plain data structure for the "${featureName}" feature.
|
|
1199
|
+
*/
|
|
1200
|
+
export class ${modelName} {
|
|
1201
|
+
id: string;
|
|
1202
|
+
createdAt: Date;
|
|
1203
|
+
updatedAt: Date;
|
|
1204
|
+
|
|
1205
|
+
constructor(
|
|
1206
|
+
id: string,
|
|
1207
|
+
// TODO: Add your model properties here
|
|
1208
|
+
createdAt?: Date,
|
|
1209
|
+
updatedAt?: Date,
|
|
1210
|
+
) {
|
|
1211
|
+
this.id = id;
|
|
1212
|
+
this.createdAt = createdAt ?? new Date();
|
|
1213
|
+
this.updatedAt = updatedAt ?? new Date();
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
`;
|
|
1217
|
+
};
|
|
1218
|
+
var capitalize12 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1219
|
+
|
|
1220
|
+
// src/templates/simpleComponent/simpleRepository.template.ts
|
|
1221
|
+
var generateSimpleRepositoryTemplate = (featureName) => {
|
|
1222
|
+
const capitalized = capitalize13(featureName);
|
|
1223
|
+
const repoName = `${capitalized}Repository`;
|
|
1224
|
+
const modelName = `${capitalized}Model`;
|
|
1225
|
+
return `import { ${modelName} } from "../models/${featureName}.model";
|
|
1226
|
+
|
|
1227
|
+
export class ${repoName} {
|
|
1228
|
+
private store: Map<string, ${modelName}> = new Map();
|
|
1229
|
+
|
|
1230
|
+
async findAll(): Promise<${modelName}[]> {
|
|
1231
|
+
// TODO: Replace with your ORM query
|
|
1232
|
+
return Array.from(this.store.values());
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async findById(id: string): Promise<${modelName} | null> {
|
|
1236
|
+
// TODO: Replace with your ORM query
|
|
1237
|
+
return this.store.get(id) ?? null;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async create(model: ${modelName}): Promise<${modelName}> {
|
|
1241
|
+
// TODO: Replace with your ORM save
|
|
1242
|
+
this.store.set(model.id, model);
|
|
1243
|
+
return model;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
async update(model: ${modelName}): Promise<${modelName} | null> {
|
|
1247
|
+
// TODO: Replace with your ORM update
|
|
1248
|
+
if (!this.store.has(model.id)) return null;
|
|
1249
|
+
this.store.set(model.id, model);
|
|
1250
|
+
return model;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
async delete(id: string): Promise<boolean> {
|
|
1254
|
+
// TODO: Replace with your ORM delete
|
|
1255
|
+
return this.store.delete(id);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
`;
|
|
1259
|
+
};
|
|
1260
|
+
var capitalize13 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1261
|
+
|
|
1262
|
+
// src/templates/simpleComponent/simpleService.template.ts
|
|
1263
|
+
var generateSimpleServiceTemplate = (featureName) => {
|
|
1264
|
+
const capitalized = capitalize14(featureName);
|
|
1265
|
+
const serviceName = `${capitalized}Service`;
|
|
1266
|
+
const repoName = `${capitalized}Repository`;
|
|
1267
|
+
const modelName = `${capitalized}Model`;
|
|
1268
|
+
return `import { ${repoName} } from "../repositories/${featureName}.repository";
|
|
1269
|
+
import { ${modelName} } from "../models/${featureName}.model";
|
|
1270
|
+
|
|
1271
|
+
export class ${serviceName} {
|
|
1272
|
+
private readonly repository: ${repoName};
|
|
1273
|
+
|
|
1274
|
+
constructor() {
|
|
1275
|
+
this.repository = new ${repoName}();
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
async findAll(): Promise<${modelName}[]> {
|
|
1279
|
+
return this.repository.findAll();
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
async findById(id: string): Promise<${modelName} | null> {
|
|
1283
|
+
return this.repository.findById(id);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async create(data: Record<string, unknown>): Promise<${modelName}> {
|
|
1287
|
+
const model = new ${modelName}(String(Date.now()));
|
|
1288
|
+
// TODO: Map data fields onto model
|
|
1289
|
+
return this.repository.create(model);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
async update(id: string, data: Record<string, unknown>): Promise<${modelName} | null> {
|
|
1293
|
+
const existing = await this.repository.findById(id);
|
|
1294
|
+
if (!existing) {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
// TODO: Apply data fields onto existing model
|
|
1298
|
+
return this.repository.update(existing);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
async delete(id: string): Promise<boolean> {
|
|
1302
|
+
return this.repository.delete(id);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
`;
|
|
1306
|
+
};
|
|
1307
|
+
var capitalize14 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1308
|
+
|
|
1309
|
+
// src/templates/simpleComponent/simpleController.template.ts
|
|
1310
|
+
var generateSimpleControllerTemplate = (controllerName, featureName, methods) => {
|
|
1311
|
+
const capitalized = capitalize15(featureName);
|
|
1312
|
+
const serviceName = `${capitalized}Service`;
|
|
1313
|
+
const methodsCode = methods.map((method) => generateMethod2(method, capitalized, serviceName)).join("\n");
|
|
1314
|
+
return `import { Request, Response } from "express";
|
|
1315
|
+
import { ResponseHandler, HttpStatusCode, IResponseHandlerSuccessData } from "opticore-http-response";
|
|
1316
|
+
import { ${serviceName} } from "../services/${featureName}.service";
|
|
1317
|
+
|
|
1318
|
+
export class ${controllerName} {
|
|
1319
|
+
private static buildService(): ${serviceName} {
|
|
1320
|
+
return new ${serviceName}();
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
${methodsCode}
|
|
1324
|
+
private static handleError(error: unknown) {
|
|
1325
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
1326
|
+
return ResponseHandler.error(message, HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
`;
|
|
1330
|
+
};
|
|
1331
|
+
var generateMethod2 = (method, capitalized, serviceName) => {
|
|
1332
|
+
const lower = method.toLowerCase();
|
|
1333
|
+
if (lower.includes("findall") || lower.includes("getall")) {
|
|
1334
|
+
return generateFindAllMethod2(method, capitalized);
|
|
1335
|
+
}
|
|
1336
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) {
|
|
1337
|
+
return generateFindByIdMethod2(method, capitalized);
|
|
1338
|
+
}
|
|
1339
|
+
if (lower.includes("create") || lower.includes("add")) {
|
|
1340
|
+
return generateCreateMethod2(method, capitalized);
|
|
1341
|
+
}
|
|
1342
|
+
if (lower.includes("update") || lower.includes("edit")) {
|
|
1343
|
+
return generateUpdateMethod2(method, capitalized);
|
|
1344
|
+
}
|
|
1345
|
+
if (lower.includes("delete") || lower.includes("remove")) {
|
|
1346
|
+
return generateDeleteMethod2(method, capitalized);
|
|
1347
|
+
}
|
|
1348
|
+
return `
|
|
1349
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1350
|
+
try {
|
|
1351
|
+
const service = ${capitalized}Controller.buildService();
|
|
1352
|
+
// TODO: Implement ${method} logic
|
|
1353
|
+
return ResponseHandler.success({}, "success", HttpStatusCode.OK);
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
return ${capitalized}Controller.handleError(error);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
`;
|
|
1359
|
+
};
|
|
1360
|
+
var generateFindAllMethod2 = (method, capitalized) => `
|
|
1361
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1362
|
+
try {
|
|
1363
|
+
const service = ${capitalized}Controller.buildService();
|
|
1364
|
+
const results = await service.findAll();
|
|
1365
|
+
return ResponseHandler.success(results, "success", HttpStatusCode.OK);
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
return ${capitalized}Controller.handleError(error);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
`;
|
|
1371
|
+
var generateFindByIdMethod2 = (method, capitalized) => `
|
|
1372
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1373
|
+
try {
|
|
1374
|
+
const { id } = req.params;
|
|
1375
|
+
const service = ${capitalized}Controller.buildService();
|
|
1376
|
+
const result = await service.findById(id);
|
|
1377
|
+
if (!result) {
|
|
1378
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
1379
|
+
}
|
|
1380
|
+
return ResponseHandler.success(result, "success", HttpStatusCode.OK);
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
return ${capitalized}Controller.handleError(error);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
`;
|
|
1386
|
+
var generateCreateMethod2 = (method, capitalized) => `
|
|
1387
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1388
|
+
try {
|
|
1389
|
+
const service = ${capitalized}Controller.buildService();
|
|
1390
|
+
const result = await service.create(req.body);
|
|
1391
|
+
return ResponseHandler.success(result, "created", HttpStatusCode.CREATED);
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
return ${capitalized}Controller.handleError(error);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
`;
|
|
1397
|
+
var generateUpdateMethod2 = (method, capitalized) => `
|
|
1398
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1399
|
+
try {
|
|
1400
|
+
const { id } = req.params;
|
|
1401
|
+
const service = ${capitalized}Controller.buildService();
|
|
1402
|
+
const result = await service.update(id, req.body);
|
|
1403
|
+
if (!result) {
|
|
1404
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
1405
|
+
}
|
|
1406
|
+
return ResponseHandler.success(result, "success", HttpStatusCode.OK);
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
return ${capitalized}Controller.handleError(error);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
`;
|
|
1412
|
+
var generateDeleteMethod2 = (method, capitalized) => `
|
|
1413
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1414
|
+
try {
|
|
1415
|
+
const { id } = req.params;
|
|
1416
|
+
const service = ${capitalized}Controller.buildService();
|
|
1417
|
+
const deleted = await service.delete(id);
|
|
1418
|
+
if (!deleted) {
|
|
1419
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
1420
|
+
}
|
|
1421
|
+
return ResponseHandler.success(null, "deleted", HttpStatusCode.NO_CONTENT);
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
return ${capitalized}Controller.handleError(error);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
`;
|
|
1427
|
+
var capitalize15 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1428
|
+
|
|
1429
|
+
// src/templates/simpleComponent/simpleRouter.template.ts
|
|
1430
|
+
var resolveRoute2 = (methodName, featureName) => {
|
|
1431
|
+
const lower = methodName.toLowerCase();
|
|
1432
|
+
if (lower.includes("findall") || lower.includes("getall")) {
|
|
1433
|
+
return { httpMethod: "get", path: `/${featureName}` };
|
|
1434
|
+
}
|
|
1435
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) {
|
|
1436
|
+
return { httpMethod: "get", path: `/${featureName}/:id` };
|
|
1437
|
+
}
|
|
1438
|
+
if (lower.includes("create") || lower.includes("add")) {
|
|
1439
|
+
return { httpMethod: "post", path: `/${featureName}` };
|
|
1440
|
+
}
|
|
1441
|
+
if (lower.includes("update") || lower.includes("edit")) {
|
|
1442
|
+
return { httpMethod: "put", path: `/${featureName}/:id` };
|
|
1443
|
+
}
|
|
1444
|
+
if (lower.includes("delete") || lower.includes("remove")) {
|
|
1445
|
+
return { httpMethod: "delete", path: `/${featureName}/:id` };
|
|
1446
|
+
}
|
|
1447
|
+
return { httpMethod: "get", path: `/${featureName}/${lower}` };
|
|
1448
|
+
};
|
|
1449
|
+
var generateSimpleRouterHandlerTemplate = (featureName, controllerMethods) => {
|
|
1450
|
+
const capitalized = capitalize16(featureName);
|
|
1451
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
1452
|
+
const controllerName = `${capitalized}Controller`;
|
|
1453
|
+
const routesCode = (controllerMethods || []).map((method) => {
|
|
1454
|
+
const { httpMethod, path: path10 } = resolveRoute2(method, featureName);
|
|
1455
|
+
return `{
|
|
1456
|
+
path: \`${path10}\`,
|
|
1457
|
+
method: "${httpMethod}",
|
|
1458
|
+
middlewares: [],
|
|
1459
|
+
handler: async (ctx: ICustomContext) => await ${controllerName}.${method}(ctx.req, ctx.res)
|
|
1460
|
+
}`;
|
|
1461
|
+
}).join(",\n");
|
|
1462
|
+
return `import { OpticoreRoutingFactory, ICustomContext, IMultipleRouteDefinition } from "opticore-router";
|
|
1463
|
+
import { ${controllerName} } from "../controllers/${featureName}.controller";
|
|
1464
|
+
|
|
1465
|
+
export const ${handlerName}: () => IMultipleRouteDefinition = () => {
|
|
1466
|
+
return OpticoreRoutingFactory.routes(
|
|
1467
|
+
${controllerName},
|
|
1468
|
+
[
|
|
1469
|
+
${routesCode.replace(/\n/g, "\n ")}
|
|
1470
|
+
]
|
|
1471
|
+
);
|
|
1472
|
+
};
|
|
1473
|
+
`;
|
|
1474
|
+
};
|
|
1475
|
+
var generateSimpleRouterTemplate = (featureName) => {
|
|
1476
|
+
const capitalized = capitalize16(featureName);
|
|
1477
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
1478
|
+
const routerName = `${capitalized}Router`;
|
|
1479
|
+
return `import { ${handlerName} } from "./${featureName}.router.handler";
|
|
1480
|
+
import { TFeatureRoutes } from "opticore-router";
|
|
1481
|
+
|
|
1482
|
+
export const ${routerName}: TFeatureRoutes = {
|
|
1483
|
+
routes: [
|
|
1484
|
+
{
|
|
1485
|
+
path: ${handlerName}().path,
|
|
1486
|
+
handler: ${handlerName}().handler
|
|
1487
|
+
}
|
|
1488
|
+
]
|
|
1489
|
+
};
|
|
1490
|
+
`;
|
|
1491
|
+
};
|
|
1492
|
+
var capitalize16 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1493
|
+
|
|
1494
|
+
// src/utils/featureComponentGenerator.utils.ts
|
|
1495
|
+
var FeatureComponentGeneratorUtils = class {
|
|
1496
|
+
static logger = new import_opticore_logger2.LoggerCore();
|
|
1497
|
+
static REGISTER_ROUTER_PATH = import_path2.default.join(process.cwd(), "src/app/router/register.router.ts");
|
|
1498
|
+
static structure = "clean";
|
|
1499
|
+
static setStructure(s) {
|
|
1500
|
+
this.structure = s;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Registers a custom module router in register.router.ts.
|
|
1504
|
+
* Must be preceded by setStructure("custom").
|
|
1505
|
+
*/
|
|
1506
|
+
static async registerModuleRouter(moduleName) {
|
|
1507
|
+
await this.updateRegisterRouter(moduleName);
|
|
1508
|
+
this.structure = "clean";
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Creates the domain entity file.
|
|
1512
|
+
*/
|
|
1513
|
+
static async createEntity(featureName, entityDir) {
|
|
1514
|
+
const entityName = `${this.capitalize(featureName)}Entity`;
|
|
1515
|
+
const filePath = import_path2.default.join(entityDir, `${featureName}.entity.ts`);
|
|
1516
|
+
UFsModule.createFile(filePath, generateEntityTemplate(entityName, featureName));
|
|
1517
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Entity created: ${filePath}`));
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Creates the domain event file.
|
|
1521
|
+
*/
|
|
1522
|
+
static async createEvent(featureName, eventDir) {
|
|
1523
|
+
const filePath = import_path2.default.join(eventDir, `${featureName}.event.ts`);
|
|
1524
|
+
UFsModule.createFile(filePath, generateEventTemplate(featureName));
|
|
1525
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Event created: ${filePath}`));
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Creates the domain exception file.
|
|
1529
|
+
*/
|
|
1530
|
+
static async createException(featureName, exceptionDir) {
|
|
1531
|
+
const filePath = import_path2.default.join(exceptionDir, `${featureName}.exception.ts`);
|
|
1532
|
+
UFsModule.createFile(filePath, generateExceptionTemplate(featureName));
|
|
1533
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Exception created: ${filePath}`));
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Creates the repository port interface.
|
|
1537
|
+
*/
|
|
1538
|
+
static async createRepositoryInterface(featureName, interfaceDir) {
|
|
1539
|
+
const interfaceName = `I${this.capitalize(featureName)}Repository`;
|
|
1540
|
+
const filePath = import_path2.default.join(interfaceDir, `${featureName}.repository.interface.ts`);
|
|
1541
|
+
UFsModule.createFile(filePath, generateRepositoryInterfaceTemplate(interfaceName, featureName));
|
|
1542
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Repository interface created: ${filePath}`));
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Creates the presenter port interface.
|
|
1546
|
+
*/
|
|
1547
|
+
static async createPresenterInterface(featureName, presenterInterfaceDir) {
|
|
1548
|
+
const filePath = import_path2.default.join(presenterInterfaceDir, `${featureName}.presenter.interface.ts`);
|
|
1549
|
+
UFsModule.createFile(filePath, generatePresenterInterfaceTemplate(featureName));
|
|
1550
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Presenter interface created: ${filePath}`));
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Creates the application service port interface.
|
|
1554
|
+
*/
|
|
1555
|
+
static async createService(featureName, serviceDir) {
|
|
1556
|
+
const filePath = import_path2.default.join(serviceDir, `${featureName}.service.ts`);
|
|
1557
|
+
UFsModule.createFile(filePath, generateServiceTemplate(featureName));
|
|
1558
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Service interface created: ${filePath}`));
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Creates the DTO file (input + output shapes + mapper).
|
|
1562
|
+
*/
|
|
1563
|
+
static async createDto(featureName, dtoDir) {
|
|
1564
|
+
const filePath = import_path2.default.join(dtoDir, `${featureName}.dto.ts`);
|
|
1565
|
+
UFsModule.createFile(filePath, generateDtoTemplate(featureName));
|
|
1566
|
+
console.log(import_ansi_colors5.default.green(`\u2714 DTO created: ${filePath}`));
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Creates the use case file.
|
|
1570
|
+
*/
|
|
1571
|
+
static async createUseCase(featureName, useCaseDir) {
|
|
1572
|
+
const useCaseName = `${this.capitalize(featureName)}UseCase`;
|
|
1573
|
+
const filePath = import_path2.default.join(useCaseDir, `${featureName}.usecase.ts`);
|
|
1574
|
+
UFsModule.createFile(filePath, generateUseCaseTemplate(useCaseName, featureName));
|
|
1575
|
+
console.log(import_ansi_colors5.default.green(`\u2714 UseCase created: ${filePath}`));
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Creates the concrete repository implementation.
|
|
1579
|
+
*/
|
|
1580
|
+
static async createRepositoryImpl(featureName, repositoryImplDir) {
|
|
1581
|
+
const interfaceImportPath = `../../../application/ports/repositories/${featureName}.repository.interface`;
|
|
1582
|
+
const filePath = import_path2.default.join(repositoryImplDir, `${featureName}.repository.ts`);
|
|
1583
|
+
UFsModule.createFile(filePath, generateRepositoryImplTemplate(featureName, interfaceImportPath));
|
|
1584
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Repository implementation created: ${filePath}`));
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Creates the concrete presenter implementation.
|
|
1588
|
+
*/
|
|
1589
|
+
static async createPresenterImpl(featureName, presenterImplDir) {
|
|
1590
|
+
const filePath = import_path2.default.join(presenterImplDir, `${featureName}.presenter.ts`);
|
|
1591
|
+
UFsModule.createFile(filePath, generatePresenterImplTemplate(featureName));
|
|
1592
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Presenter implementation created: ${filePath}`));
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Creates the HTTP controller.
|
|
1596
|
+
* Prompts the user for method names, then generates the file.
|
|
1597
|
+
*
|
|
1598
|
+
* @returns The controller class name and the list of chosen methods.
|
|
1599
|
+
*/
|
|
1600
|
+
static async createController(featureName, controllerDir) {
|
|
1601
|
+
const controllerName = `${this.capitalize(featureName)}Controller`;
|
|
1602
|
+
const filePath = import_path2.default.join(controllerDir, `${featureName}.controller.ts`);
|
|
1603
|
+
const wantsMethods = await UPrompt.confirm("Do you want to add methods to the controller?");
|
|
1604
|
+
let methods = [];
|
|
1605
|
+
let content;
|
|
1606
|
+
if (wantsMethods && !(0, import_prompts3.isCancel)(wantsMethods)) {
|
|
1607
|
+
const methodsText = await UPrompt.text(
|
|
1608
|
+
"Enter the method names (comma separated):",
|
|
1609
|
+
"create, findAll, findById, update, delete",
|
|
1610
|
+
(v) => v ? void 0 : "You must enter at least one method."
|
|
1611
|
+
);
|
|
1612
|
+
if (!(0, import_prompts3.isCancel)(methodsText)) {
|
|
1613
|
+
methods = methodsText.split(",").map((m) => m.trim()).filter(Boolean);
|
|
1614
|
+
}
|
|
1615
|
+
content = generateControllerTemplate(controllerName, featureName, methods);
|
|
1616
|
+
} else {
|
|
1617
|
+
content = `import { Request, Response } from "express";
|
|
1618
|
+
|
|
1619
|
+
export class ${controllerName} {}
|
|
1620
|
+
`;
|
|
1621
|
+
}
|
|
1622
|
+
UFsModule.createFile(filePath, content);
|
|
1623
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Controller created: ${filePath}`));
|
|
1624
|
+
return { controllerName, methods };
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Creates the data model file (simple component).
|
|
1628
|
+
*/
|
|
1629
|
+
static async createModel(featureName, modelDir) {
|
|
1630
|
+
const filePath = import_path2.default.join(modelDir, `${featureName}.model.ts`);
|
|
1631
|
+
UFsModule.createFile(filePath, generateModelTemplate(featureName));
|
|
1632
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Model created: ${filePath}`));
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Creates the simple repository (no interface, uses the model directly).
|
|
1636
|
+
*/
|
|
1637
|
+
static async createSimpleRepository(featureName, repositoryDir) {
|
|
1638
|
+
const filePath = import_path2.default.join(repositoryDir, `${featureName}.repository.ts`);
|
|
1639
|
+
UFsModule.createFile(filePath, generateSimpleRepositoryTemplate(featureName));
|
|
1640
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Repository created: ${filePath}`));
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Creates the simple concrete service (no interface, wires the repository).
|
|
1644
|
+
*/
|
|
1645
|
+
static async createSimpleService(featureName, serviceDir) {
|
|
1646
|
+
const filePath = import_path2.default.join(serviceDir, `${featureName}.service.ts`);
|
|
1647
|
+
UFsModule.createFile(filePath, generateSimpleServiceTemplate(featureName));
|
|
1648
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Service created: ${filePath}`));
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Creates the simple controller (delegates to service, no use-case/presenter).
|
|
1652
|
+
*/
|
|
1653
|
+
static async createSimpleController(featureName, controllerDir) {
|
|
1654
|
+
const controllerName = `${this.capitalize(featureName)}Controller`;
|
|
1655
|
+
const filePath = import_path2.default.join(controllerDir, `${featureName}.controller.ts`);
|
|
1656
|
+
const wantsMethods = await UPrompt.confirm("Do you want to add methods to the controller?");
|
|
1657
|
+
let methods = [];
|
|
1658
|
+
let content;
|
|
1659
|
+
if (wantsMethods && !(0, import_prompts3.isCancel)(wantsMethods)) {
|
|
1660
|
+
const methodsText = await UPrompt.text(
|
|
1661
|
+
"Enter the method names (comma separated):",
|
|
1662
|
+
"create, findAll, findById, update, delete",
|
|
1663
|
+
(v) => v ? void 0 : "You must enter at least one method."
|
|
1664
|
+
);
|
|
1665
|
+
if (!(0, import_prompts3.isCancel)(methodsText)) {
|
|
1666
|
+
methods = methodsText.split(",").map((m) => m.trim()).filter(Boolean);
|
|
1667
|
+
}
|
|
1668
|
+
content = generateSimpleControllerTemplate(controllerName, featureName, methods);
|
|
1669
|
+
} else {
|
|
1670
|
+
content = `import { Request, Response } from "express";
|
|
1671
|
+
|
|
1672
|
+
export class ${controllerName} {}
|
|
1673
|
+
`;
|
|
1674
|
+
}
|
|
1675
|
+
UFsModule.createFile(filePath, content);
|
|
1676
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Controller created: ${filePath}`));
|
|
1677
|
+
return { controllerName, methods };
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Creates the route handler (HTTP verb + path → controller method bindings).
|
|
1681
|
+
*
|
|
1682
|
+
*/
|
|
1683
|
+
static async createRouterHandler(featureName, routerHandlerDir, controllerMethods) {
|
|
1684
|
+
const filePath = import_path2.default.join(routerHandlerDir, `${featureName}.router.handler.ts`);
|
|
1685
|
+
const content = this.structure === "simple" ? generateSimpleRouterHandlerTemplate(featureName, controllerMethods) : generateRouterHandlerTemplate(featureName, controllerMethods);
|
|
1686
|
+
UFsModule.createFile(filePath, content);
|
|
1687
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Router Handler created: ${filePath}`));
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Creates the feature router and registers it in register.router.ts.
|
|
1691
|
+
*
|
|
1692
|
+
*/
|
|
1693
|
+
static async createRouter(featureName, routerDir) {
|
|
1694
|
+
const filePath = import_path2.default.join(routerDir, `${featureName}.router.ts`);
|
|
1695
|
+
const content = this.structure === "simple" ? generateSimpleRouterTemplate(featureName) : generateRouterTemplate(featureName);
|
|
1696
|
+
UFsModule.createFile(filePath, content);
|
|
1697
|
+
await this.updateRegisterRouter(featureName);
|
|
1698
|
+
console.log(import_ansi_colors5.default.green(`\u2714 Router created: ${filePath}`));
|
|
1699
|
+
}
|
|
1700
|
+
static capitalize(str) {
|
|
1701
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Appends an import and registers the new router in register.router.ts.
|
|
1705
|
+
*/
|
|
1706
|
+
static async updateRegisterRouter(featureName) {
|
|
1707
|
+
const routerName = `${this.capitalize(featureName)}Router`;
|
|
1708
|
+
const routerPath = this.structure === "simple" || this.structure === "mvc" || this.structure === "layered" ? `../../features/${featureName}/routes/${featureName}.router` : this.structure === "custom" ? `../../features/${featureName}/${featureName}.router` : `../../features/${featureName}/infrastructure/routes/${featureName}.router`;
|
|
1709
|
+
const importStatement = `import { ${routerName} } from "${routerPath}"`;
|
|
1710
|
+
try {
|
|
1711
|
+
if (!import_fs2.default.existsSync(this.REGISTER_ROUTER_PATH)) {
|
|
1712
|
+
console.log(
|
|
1713
|
+
import_ansi_colors5.default.yellow(`\u26A0 register.router.ts not found at ${this.REGISTER_ROUTER_PATH}, skipping auto-registration.`)
|
|
1714
|
+
);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
let content = import_fs2.default.readFileSync(this.REGISTER_ROUTER_PATH, "utf-8");
|
|
1718
|
+
if (content.includes(`import { ${routerName} } from`)) {
|
|
1719
|
+
console.log(import_ansi_colors5.default.cyan(`\u2139 ${routerName} is already registered.`));
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
const lines = content.split("\n");
|
|
1723
|
+
let lastImportIndex = -1;
|
|
1724
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1725
|
+
if (lines[i].trim().startsWith("import ")) lastImportIndex = i;
|
|
1726
|
+
}
|
|
1727
|
+
if (lastImportIndex !== -1) {
|
|
1728
|
+
lines.splice(lastImportIndex + 1, 0, importStatement);
|
|
1729
|
+
content = lines.join("\n");
|
|
1730
|
+
} else {
|
|
1731
|
+
content = importStatement + "\n" + content;
|
|
1732
|
+
}
|
|
1733
|
+
const ARRAY_OPEN = "registered([";
|
|
1734
|
+
const ARRAY_CLOSE = "]);";
|
|
1735
|
+
const startIdx = content.indexOf(ARRAY_OPEN);
|
|
1736
|
+
if (startIdx === -1) {
|
|
1737
|
+
console.log(import_ansi_colors5.default.yellow(`\u26A0 Could not find 'registered([' in register.router.ts.`));
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const endIdx = content.indexOf(ARRAY_CLOSE, startIdx + ARRAY_OPEN.length);
|
|
1741
|
+
if (endIdx === -1) {
|
|
1742
|
+
console.log(import_ansi_colors5.default.yellow(`\u26A0 Could not find matching ']);' in register.router.ts.`));
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
const arrayContent = content.substring(startIdx + ARRAY_OPEN.length, endIdx);
|
|
1746
|
+
const existingRoutes = arrayContent.split("\n").map((l) => l.trim().replace(/,+$/, "")).filter(Boolean);
|
|
1747
|
+
if (!existingRoutes.includes(routerName)) {
|
|
1748
|
+
existingRoutes.push(routerName);
|
|
1749
|
+
}
|
|
1750
|
+
const indentedRoutes = existingRoutes.map((r) => ` ${r},`).join("\n");
|
|
1751
|
+
const newContent = content.substring(0, startIdx + ARRAY_OPEN.length) + `
|
|
1752
|
+
${indentedRoutes}
|
|
1753
|
+
` + content.substring(endIdx);
|
|
1754
|
+
import_fs2.default.writeFileSync(this.REGISTER_ROUTER_PATH, newContent, "utf-8");
|
|
1755
|
+
console.log(import_ansi_colors5.default.green(`\u2714 register.router.ts updated with ${routerName}.`));
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
console.error(import_ansi_colors5.default.red(`\u2718 Failed to update register.router.ts: ${error.message}`));
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
// src/templates/simpleComponent/simpleComponent.template.ts
|
|
1763
|
+
var simpleComponentTemplate = async (featureName) => {
|
|
1764
|
+
FeatureComponentGeneratorUtils.setStructure("simple");
|
|
1765
|
+
const basePath = import_path3.default.join("src", "features", featureName);
|
|
1766
|
+
const dirs = [
|
|
1767
|
+
import_path3.default.join(basePath, "routes"),
|
|
1768
|
+
import_path3.default.join(basePath, "controllers"),
|
|
1769
|
+
import_path3.default.join(basePath, "services"),
|
|
1770
|
+
import_path3.default.join(basePath, "repositories"),
|
|
1771
|
+
import_path3.default.join(basePath, "models")
|
|
1772
|
+
];
|
|
1773
|
+
dirs.forEach((dir) => UFsModule.createDirectoryRecursively(dir));
|
|
1774
|
+
const paths = {
|
|
1775
|
+
modelsDir: import_path3.default.join(basePath, "models"),
|
|
1776
|
+
repositoriesDir: import_path3.default.join(basePath, "repositories"),
|
|
1777
|
+
servicesDir: import_path3.default.join(basePath, "services"),
|
|
1778
|
+
controllersDir: import_path3.default.join(basePath, "controllers"),
|
|
1779
|
+
routesDir: import_path3.default.join(basePath, "routes")
|
|
1780
|
+
};
|
|
1781
|
+
await FeatureComponentGeneratorUtils.createModel(featureName, paths.modelsDir);
|
|
1782
|
+
await FeatureComponentGeneratorUtils.createSimpleRepository(featureName, paths.repositoriesDir);
|
|
1783
|
+
await FeatureComponentGeneratorUtils.createSimpleService(featureName, paths.servicesDir);
|
|
1784
|
+
const controller = await FeatureComponentGeneratorUtils.createSimpleController(
|
|
1785
|
+
featureName,
|
|
1786
|
+
paths.controllersDir
|
|
1787
|
+
);
|
|
1788
|
+
await FeatureComponentGeneratorUtils.createRouterHandler(featureName, paths.routesDir, controller.methods);
|
|
1789
|
+
await FeatureComponentGeneratorUtils.createRouter(featureName, paths.routesDir);
|
|
1790
|
+
console.log(
|
|
1791
|
+
import_ansi_colors6.default.bgGreen(
|
|
1792
|
+
import_ansi_colors6.default.white(`
|
|
1793
|
+
\u{1F389} Feature "${featureName}" scaffolded with Simple Component!
|
|
1794
|
+
`)
|
|
1795
|
+
)
|
|
1796
|
+
);
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// src/templates/fullCleanComponent/fullCleanComponent.template.ts
|
|
1800
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
1801
|
+
var import_ansi_colors7 = __toESM(require("ansi-colors"), 1);
|
|
1802
|
+
var fullCleanComponentTemplate = async (featureName) => {
|
|
1803
|
+
const basePath = import_path4.default.join("src", "features", featureName);
|
|
1804
|
+
const dirs = [
|
|
1805
|
+
import_path4.default.join(basePath, "domain", "entities"),
|
|
1806
|
+
import_path4.default.join(basePath, "domain", "events"),
|
|
1807
|
+
import_path4.default.join(basePath, "domain", "exceptions"),
|
|
1808
|
+
import_path4.default.join(basePath, "application", "use-cases"),
|
|
1809
|
+
import_path4.default.join(basePath, "application", "dtos"),
|
|
1810
|
+
import_path4.default.join(basePath, "application", "ports", "repositories"),
|
|
1811
|
+
import_path4.default.join(basePath, "application", "ports", "presenters"),
|
|
1812
|
+
import_path4.default.join(basePath, "application", "ports", "services"),
|
|
1813
|
+
import_path4.default.join(basePath, "infrastructure", "adapters", "controllers"),
|
|
1814
|
+
import_path4.default.join(basePath, "infrastructure", "adapters", "repositories"),
|
|
1815
|
+
import_path4.default.join(basePath, "infrastructure", "adapters", "presenters"),
|
|
1816
|
+
import_path4.default.join(basePath, "infrastructure", "routes")
|
|
1817
|
+
];
|
|
1818
|
+
dirs.forEach((dir) => UFsModule.createDirectoryRecursively(dir));
|
|
1819
|
+
const paths = {
|
|
1820
|
+
entityDir: import_path4.default.join(basePath, "domain", "entities"),
|
|
1821
|
+
eventDir: import_path4.default.join(basePath, "domain", "events"),
|
|
1822
|
+
exceptionDir: import_path4.default.join(basePath, "domain", "exceptions"),
|
|
1823
|
+
repositoryInterfaceDir: import_path4.default.join(basePath, "application", "ports", "repositories"),
|
|
1824
|
+
presenterInterfaceDir: import_path4.default.join(basePath, "application", "ports", "presenters"),
|
|
1825
|
+
serviceDir: import_path4.default.join(basePath, "application", "ports", "services"),
|
|
1826
|
+
dtoDir: import_path4.default.join(basePath, "application", "dtos"),
|
|
1827
|
+
useCaseDir: import_path4.default.join(basePath, "application", "use-cases"),
|
|
1828
|
+
repositoryImplDir: import_path4.default.join(basePath, "infrastructure", "adapters", "repositories"),
|
|
1829
|
+
presenterImplDir: import_path4.default.join(basePath, "infrastructure", "adapters", "presenters"),
|
|
1830
|
+
controllerDir: import_path4.default.join(basePath, "infrastructure", "adapters", "controllers"),
|
|
1831
|
+
routerDir: import_path4.default.join(basePath, "infrastructure", "routes")
|
|
1832
|
+
};
|
|
1833
|
+
await FeatureComponentGeneratorUtils.createEntity(featureName, paths.entityDir);
|
|
1834
|
+
await FeatureComponentGeneratorUtils.createEvent(featureName, paths.eventDir);
|
|
1835
|
+
await FeatureComponentGeneratorUtils.createException(featureName, paths.exceptionDir);
|
|
1836
|
+
await FeatureComponentGeneratorUtils.createRepositoryInterface(featureName, paths.repositoryInterfaceDir);
|
|
1837
|
+
await FeatureComponentGeneratorUtils.createPresenterInterface(featureName, paths.presenterInterfaceDir);
|
|
1838
|
+
await FeatureComponentGeneratorUtils.createService(featureName, paths.serviceDir);
|
|
1839
|
+
await FeatureComponentGeneratorUtils.createDto(featureName, paths.dtoDir);
|
|
1840
|
+
await FeatureComponentGeneratorUtils.createUseCase(featureName, paths.useCaseDir);
|
|
1841
|
+
await FeatureComponentGeneratorUtils.createRepositoryImpl(featureName, paths.repositoryImplDir);
|
|
1842
|
+
await FeatureComponentGeneratorUtils.createPresenterImpl(featureName, paths.presenterImplDir);
|
|
1843
|
+
const controller = await FeatureComponentGeneratorUtils.createController(
|
|
1844
|
+
featureName,
|
|
1845
|
+
paths.controllerDir
|
|
1846
|
+
);
|
|
1847
|
+
await FeatureComponentGeneratorUtils.createRouterHandler(featureName, paths.routerDir, controller.methods);
|
|
1848
|
+
await FeatureComponentGeneratorUtils.createRouter(featureName, paths.routerDir);
|
|
1849
|
+
console.log(
|
|
1850
|
+
import_ansi_colors7.default.bgGreen(
|
|
1851
|
+
import_ansi_colors7.default.white(`
|
|
1852
|
+
\u{1F389} Feature "${featureName}" scaffolded with Clean Architecture!
|
|
1853
|
+
`)
|
|
1854
|
+
)
|
|
1855
|
+
);
|
|
1856
|
+
};
|
|
1857
|
+
|
|
1858
|
+
// src/templates/cleanComponentByStep/cleanComponentByStep.template.ts
|
|
1859
|
+
var import_path5 = __toESM(require("path"), 1);
|
|
1860
|
+
var import_ansi_colors8 = __toESM(require("ansi-colors"), 1);
|
|
1861
|
+
var import_prompts4 = require("@clack/prompts");
|
|
1862
|
+
var cleanComponentByStepTemplate = async (featureName) => {
|
|
1863
|
+
const base = import_path5.default.join("src", "features", featureName);
|
|
1864
|
+
let created = 0;
|
|
1865
|
+
let controller = { controllerName: "", methods: [] };
|
|
1866
|
+
const ask = async (label) => {
|
|
1867
|
+
const answer = await UPrompt.confirm(label, false);
|
|
1868
|
+
if ((0, import_prompts4.isCancel)(answer)) {
|
|
1869
|
+
UFsModule.removeDir(base, true, true);
|
|
1870
|
+
process.exit(0);
|
|
1871
|
+
}
|
|
1872
|
+
return answer;
|
|
1873
|
+
};
|
|
1874
|
+
const track = () => {
|
|
1875
|
+
created++;
|
|
1876
|
+
};
|
|
1877
|
+
console.log(import_ansi_colors8.default.bold(import_ansi_colors8.default.cyan("\n Domain ")));
|
|
1878
|
+
const mkDir = (dir) => {
|
|
1879
|
+
UFsModule.createDirectoryRecursively(dir);
|
|
1880
|
+
return dir;
|
|
1881
|
+
};
|
|
1882
|
+
if (await ask(`Entity - ${featureName}.entity.ts`)) {
|
|
1883
|
+
await FeatureComponentGeneratorUtils.createEntity(featureName, mkDir(import_path5.default.join(base, "domain", "entities")));
|
|
1884
|
+
track();
|
|
1885
|
+
}
|
|
1886
|
+
if (await ask(`Event - ${featureName}.event.ts`)) {
|
|
1887
|
+
await FeatureComponentGeneratorUtils.createEvent(featureName, mkDir(import_path5.default.join(base, "domain", "events")));
|
|
1888
|
+
track();
|
|
1889
|
+
}
|
|
1890
|
+
if (await ask(`Exception - ${featureName}.exception.ts`)) {
|
|
1891
|
+
await FeatureComponentGeneratorUtils.createException(featureName, mkDir(import_path5.default.join(base, "domain", "exceptions")));
|
|
1892
|
+
track();
|
|
1893
|
+
}
|
|
1894
|
+
console.log(import_ansi_colors8.default.bold(import_ansi_colors8.default.cyan("\n Application ")));
|
|
1895
|
+
if (await ask(`Repo Interface - ${featureName}.repository.interface.ts`)) {
|
|
1896
|
+
await FeatureComponentGeneratorUtils.createRepositoryInterface(featureName, mkDir(import_path5.default.join(base, "application", "ports", "repositories")));
|
|
1897
|
+
track();
|
|
1898
|
+
}
|
|
1899
|
+
if (await ask(`Presenter Port - ${featureName}.presenter.interface.ts`)) {
|
|
1900
|
+
await FeatureComponentGeneratorUtils.createPresenterInterface(featureName, mkDir(import_path5.default.join(base, "application", "ports", "presenters")));
|
|
1901
|
+
track();
|
|
1902
|
+
}
|
|
1903
|
+
if (await ask(`Service Port - ${featureName}.service.ts`)) {
|
|
1904
|
+
await FeatureComponentGeneratorUtils.createService(featureName, mkDir(import_path5.default.join(base, "application", "ports", "services")));
|
|
1905
|
+
track();
|
|
1906
|
+
}
|
|
1907
|
+
if (await ask(`DTO - ${featureName}.dto.ts`)) {
|
|
1908
|
+
await FeatureComponentGeneratorUtils.createDto(featureName, mkDir(import_path5.default.join(base, "application", "dtos")));
|
|
1909
|
+
track();
|
|
1910
|
+
}
|
|
1911
|
+
if (await ask(`Use Case - ${featureName}.usecase.ts`)) {
|
|
1912
|
+
await FeatureComponentGeneratorUtils.createUseCase(featureName, mkDir(import_path5.default.join(base, "application", "use-cases")));
|
|
1913
|
+
track();
|
|
1914
|
+
}
|
|
1915
|
+
console.log(import_ansi_colors8.default.bold(import_ansi_colors8.default.cyan("\n Infrastructure ")));
|
|
1916
|
+
if (await ask(`Repo Impl - ${featureName}.repository.ts`)) {
|
|
1917
|
+
await FeatureComponentGeneratorUtils.createRepositoryImpl(featureName, mkDir(import_path5.default.join(base, "infrastructure", "adapters", "repositories")));
|
|
1918
|
+
track();
|
|
1919
|
+
}
|
|
1920
|
+
if (await ask(`Presenter Impl - ${featureName}.presenter.ts`)) {
|
|
1921
|
+
await FeatureComponentGeneratorUtils.createPresenterImpl(featureName, mkDir(import_path5.default.join(base, "infrastructure", "adapters", "presenters")));
|
|
1922
|
+
track();
|
|
1923
|
+
}
|
|
1924
|
+
if (await ask(`Controller - ${featureName}.controller.ts`)) {
|
|
1925
|
+
controller = await FeatureComponentGeneratorUtils.createController(featureName, mkDir(import_path5.default.join(base, "infrastructure", "adapters", "controllers")));
|
|
1926
|
+
track();
|
|
1927
|
+
}
|
|
1928
|
+
if (await ask(`Router Handler - ${featureName}.router.handler.ts`)) {
|
|
1929
|
+
await FeatureComponentGeneratorUtils.createRouterHandler(featureName, mkDir(import_path5.default.join(base, "infrastructure", "routes")), controller.methods);
|
|
1930
|
+
track();
|
|
1931
|
+
}
|
|
1932
|
+
if (await ask(`Router - ${featureName}.router.ts`)) {
|
|
1933
|
+
await FeatureComponentGeneratorUtils.createRouter(featureName, mkDir(import_path5.default.join(base, "infrastructure", "routes")));
|
|
1934
|
+
track();
|
|
1935
|
+
}
|
|
1936
|
+
if (created === 0) {
|
|
1937
|
+
console.log(import_ansi_colors8.default.yellow(`
|
|
1938
|
+
\u26A0 No files were created for feature "${featureName}".`));
|
|
1939
|
+
} else {
|
|
1940
|
+
console.log(
|
|
1941
|
+
import_ansi_colors8.default.bgGreen(import_ansi_colors8.default.white(`
|
|
1942
|
+
\u{1F389} Feature "${featureName}" \u2014 ${created} file(s) created step by step.
|
|
1943
|
+
`))
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
|
|
1948
|
+
// src/core/components/application/createController.application.ts
|
|
1949
|
+
var createComponentApplication = async (featureDirectory, featureName) => {
|
|
1950
|
+
const askComponentType = await _controllerType(
|
|
1951
|
+
"Choose a type of component :",
|
|
1952
|
+
CCleanType.full_clean_component,
|
|
1953
|
+
[
|
|
1954
|
+
{ label: "Simple component", value: [CCleanType.simple_component] },
|
|
1955
|
+
{ label: "CLEAN Architecture component by step", value: [CCleanType.clean_component_by_step] },
|
|
1956
|
+
{ label: "Full CLEAN Architecture component", value: [CCleanType.full_clean_component] }
|
|
1957
|
+
]
|
|
1958
|
+
);
|
|
1959
|
+
if ((0, import_prompts5.isCancel)(askComponentType) === true || askComponentType === void 0) {
|
|
1960
|
+
BaseService._cancelOperation(featureDirectory);
|
|
1961
|
+
} else {
|
|
1962
|
+
switch (askComponentType) {
|
|
1963
|
+
case CCleanType.simple_component:
|
|
1964
|
+
await simpleComponentTemplate(featureName);
|
|
1965
|
+
break;
|
|
1966
|
+
case CCleanType.clean_component_by_step:
|
|
1967
|
+
await cleanComponentByStepTemplate(featureName);
|
|
1968
|
+
break;
|
|
1969
|
+
case CCleanType.full_clean_component:
|
|
1970
|
+
await fullCleanComponentTemplate(featureName);
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
|
|
1976
|
+
// src/templates/customModule/buildYourOwnArchitectureScaffold.template.ts
|
|
1977
|
+
var import_path6 = __toESM(require("path"), 1);
|
|
1978
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
1979
|
+
var import_ansi_colors9 = __toESM(require("ansi-colors"), 1);
|
|
1980
|
+
var import_prompts6 = require("@clack/prompts");
|
|
1981
|
+
|
|
1982
|
+
// src/core/domains/constants/resolveRoute.constant.ts
|
|
1983
|
+
var CResolveRoute = (methodName, moduleName) => {
|
|
1984
|
+
const lower = methodName.toLowerCase();
|
|
1985
|
+
if (lower.includes("findall") || lower.includes("getall")) return { httpMethod: "get", path: `/${moduleName}` };
|
|
1986
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) return { httpMethod: "get", path: `/${moduleName}/:id` };
|
|
1987
|
+
if (lower.includes("create") || lower.includes("add")) return { httpMethod: "post", path: `/${moduleName}` };
|
|
1988
|
+
if (lower.includes("update") || lower.includes("edit")) return { httpMethod: "put", path: `/${moduleName}/:id` };
|
|
1989
|
+
if (lower.includes("delete") || lower.includes("remove")) return { httpMethod: "delete", path: `/${moduleName}/:id` };
|
|
1990
|
+
return { httpMethod: "get", path: `/${moduleName}/${lower}` };
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
// src/templates/customModule/buildYourOwnArchitecture.template.ts
|
|
1994
|
+
var capitalize17 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
1995
|
+
var generateBuildYourOwnControllerTemplate = (fileName, methods) => {
|
|
1996
|
+
const controllerName = `${capitalize17(fileName)}Controller`;
|
|
1997
|
+
const body = methods.length === 0 ? ` // TODO: add your handler methods here` : methods.map((method) => `
|
|
1998
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
1999
|
+
try {
|
|
2000
|
+
// TODO: implement ${method}
|
|
2001
|
+
return ResponseHandler.success({}, "success", HttpStatusCode.OK);
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
2004
|
+
return ResponseHandler.error(message, HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
2005
|
+
}
|
|
2006
|
+
}`).join("\n");
|
|
2007
|
+
return `import { Request, Response } from "opticore-express";
|
|
2008
|
+
import { ResponseHandler, HttpStatusCode, IResponseHandlerSuccessData } from "opticore-http-response";
|
|
2009
|
+
|
|
2010
|
+
export class ${controllerName} {
|
|
2011
|
+
${body}
|
|
2012
|
+
}`.trimEnd() + "\n";
|
|
2013
|
+
};
|
|
2014
|
+
var generateBuildYourOwnServiceClassTemplate = (fileName, methods) => {
|
|
2015
|
+
const serviceName = `${capitalize17(fileName)}Service`;
|
|
2016
|
+
const body = methods.length === 0 ? ` // TODO: add your service methods here` : methods.map((method) => `
|
|
2017
|
+
async ${method}(): Promise<unknown> {
|
|
2018
|
+
// TODO: implement ${method}
|
|
2019
|
+
throw new Error("Not implemented");
|
|
2020
|
+
}`).join("\n");
|
|
2021
|
+
return `export class ${serviceName} {
|
|
2022
|
+
${body}
|
|
2023
|
+
}`.trimEnd() + "\n";
|
|
2024
|
+
};
|
|
2025
|
+
var generateBuildYourOwnServiceFunctionTemplate = (fileName) => {
|
|
2026
|
+
const functionName = `${fileName}Service`;
|
|
2027
|
+
return `export async function ${functionName}(): Promise<unknown> {
|
|
2028
|
+
// TODO: implement ${functionName}
|
|
2029
|
+
throw new Error("Not implemented");
|
|
2030
|
+
}`.trimEnd() + "\n";
|
|
2031
|
+
};
|
|
2032
|
+
var generateBuildYourOwnDtoTemplate = (fileName, fields) => {
|
|
2033
|
+
const dtoName = `${capitalize17(fileName)}Dto`;
|
|
2034
|
+
const body = fields.length === 0 ? ` // TODO: define the fields of this DTO` : fields.map((field) => ` ${field.name}: ${field.type};`).join("\n");
|
|
2035
|
+
return `export interface ${dtoName} {
|
|
2036
|
+
${body}
|
|
2037
|
+
}`.trimEnd() + "\n";
|
|
2038
|
+
};
|
|
2039
|
+
var generateBuildYourOwnEntityTemplate = (fileName, properties) => {
|
|
2040
|
+
const entityName = `${capitalize17(fileName)}Entity`;
|
|
2041
|
+
const ctorParams = properties.length === 0 ? ` // TODO: define the properties of this entity` : properties.map((prop) => ` public readonly ${prop.name}: ${prop.type},`).join("\n");
|
|
2042
|
+
return `export class ${entityName} {
|
|
2043
|
+
constructor(
|
|
2044
|
+
${ctorParams}
|
|
2045
|
+
) {}
|
|
2046
|
+
}`.trimEnd() + "\n";
|
|
2047
|
+
};
|
|
2048
|
+
var generateBuildYourOwnRepositoryTemplate = (fileName, methods) => {
|
|
2049
|
+
const repositoryName = `${capitalize17(fileName)}Repository`;
|
|
2050
|
+
const body = methods.length === 0 ? ` // TODO: define the actions this repository can perform` : methods.map((method) => ` async ${method}(): Promise<unknown> { throw new Error("Not implemented"); }`).join("\n");
|
|
2051
|
+
return `export class ${repositoryName} {
|
|
2052
|
+
// TODO: inject your data source (ORM, DB client, etc.)
|
|
2053
|
+
${body}
|
|
2054
|
+
}`.trimEnd() + "\n";
|
|
2055
|
+
};
|
|
2056
|
+
var generateBuildYourOwnRouteTemplate = (fileName, actions) => {
|
|
2057
|
+
const capitalized = capitalize17(fileName);
|
|
2058
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
2059
|
+
const controllerName = `${capitalized}Controller`;
|
|
2060
|
+
const routes = actions.map((action) => {
|
|
2061
|
+
const { httpMethod, path: path10 } = CResolveRoute(action, fileName);
|
|
2062
|
+
return { httpMethod, path: path10, action };
|
|
2063
|
+
});
|
|
2064
|
+
const body = routes.length === 0 ? ` // TODO: define the routes for this module` : routes.map((route) => `{
|
|
2065
|
+
path: "${route.path}",
|
|
2066
|
+
method: "${route.httpMethod}",
|
|
2067
|
+
middlewares: [],
|
|
2068
|
+
handler: async (ctx: ICustomContext) => await ${controllerName}.${route.action}(ctx.req, ctx.res)
|
|
2069
|
+
}`).join(",\n").replace(/\n/g, "\n ");
|
|
2070
|
+
return `import { OpticoreRoutingFactory, ICustomContext, IMultipleRouteDefinition } from "opticore-router";
|
|
2071
|
+
// TODO: adjust this import if "${controllerName}" lives in a different folder
|
|
2072
|
+
import { ${controllerName} } from "./${fileName}.controller";
|
|
2073
|
+
|
|
2074
|
+
export const ${handlerName}: () => IMultipleRouteDefinition = () => {
|
|
2075
|
+
return OpticoreRoutingFactory.routes(
|
|
2076
|
+
${controllerName},
|
|
2077
|
+
[
|
|
2078
|
+
${body}
|
|
2079
|
+
]
|
|
2080
|
+
);
|
|
2081
|
+
};`.trimEnd() + "\n";
|
|
2082
|
+
};
|
|
2083
|
+
|
|
2084
|
+
// src/templates/customModule/buildYourOwnArchitectureScaffold.template.ts
|
|
2085
|
+
var ok = (msg) => {
|
|
2086
|
+
console.log(import_ansi_colors9.default.green(`\u2714 ${msg}`));
|
|
2087
|
+
};
|
|
2088
|
+
var info = (msg) => {
|
|
2089
|
+
console.log(import_ansi_colors9.default.cyan(`\u2139 ${msg}`));
|
|
2090
|
+
};
|
|
2091
|
+
var NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
2092
|
+
var askName = async (message, placeholder) => {
|
|
2093
|
+
const raw = await UPrompt.text(message, placeholder, (value) => {
|
|
2094
|
+
if (!value) {
|
|
2095
|
+
return "Please enter a name.";
|
|
2096
|
+
}
|
|
2097
|
+
if (!NAME_PATTERN.test(value)) {
|
|
2098
|
+
return "Use letters, numbers, - or _, starting with a letter.";
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
if ((0, import_prompts6.isCancel)(raw)) {
|
|
2102
|
+
return void 0;
|
|
2103
|
+
}
|
|
2104
|
+
return raw;
|
|
2105
|
+
};
|
|
2106
|
+
var askCommaSeparatedList = async (message, placeholder) => {
|
|
2107
|
+
const raw = await UPrompt.text(message, placeholder, () => void 0);
|
|
2108
|
+
if ((0, import_prompts6.isCancel)(raw) || !raw) return [];
|
|
2109
|
+
return raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
2110
|
+
};
|
|
2111
|
+
var parseFieldDefinitions = (raw) => raw.map((entry) => {
|
|
2112
|
+
const [name, type] = entry.split(":").map((v) => v.trim());
|
|
2113
|
+
return { name: name || entry, type: type || "string" };
|
|
2114
|
+
});
|
|
2115
|
+
var renderTree = (node, prefix = "") => {
|
|
2116
|
+
const lines = [];
|
|
2117
|
+
const children = node.children ?? [];
|
|
2118
|
+
children.forEach((child, index) => {
|
|
2119
|
+
const isLast = index === children.length - 1;
|
|
2120
|
+
const branch = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
2121
|
+
lines.push(`${prefix}${branch}${child.name}${child.type === "dir" ? "/" : ""}`);
|
|
2122
|
+
if (child.type === "dir") {
|
|
2123
|
+
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
2124
|
+
lines.push(...renderTree(child, nextPrefix));
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
return lines;
|
|
2128
|
+
};
|
|
2129
|
+
var printTree = (root) => {
|
|
2130
|
+
console.log(import_ansi_colors9.default.yellow(`${root.name}/`));
|
|
2131
|
+
renderTree(root).forEach((line) => console.log(import_ansi_colors9.default.yellow(line)));
|
|
2132
|
+
};
|
|
2133
|
+
async function handleFileCreation(dirPath) {
|
|
2134
|
+
const fileType = await UPrompt.select(
|
|
2135
|
+
"What type of file do you want to create?",
|
|
2136
|
+
["controller"],
|
|
2137
|
+
[
|
|
2138
|
+
{ label: "Controller", value: ["controller"] },
|
|
2139
|
+
{ label: "Service", value: ["service"] },
|
|
2140
|
+
{ label: "DTO", value: ["dto"] },
|
|
2141
|
+
{ label: "Entity", value: ["entity"] },
|
|
2142
|
+
{ label: "Repository", value: ["repository"] },
|
|
2143
|
+
{ label: "Route", value: ["route"] }
|
|
2144
|
+
]
|
|
2145
|
+
);
|
|
2146
|
+
if ((0, import_prompts6.isCancel)(fileType)) return void 0;
|
|
2147
|
+
const baseName = await askName(`Enter the name of this ${fileType} (without extension):`, "user");
|
|
2148
|
+
if (!baseName) return void 0;
|
|
2149
|
+
let fileName;
|
|
2150
|
+
let content;
|
|
2151
|
+
switch (fileType) {
|
|
2152
|
+
case "controller": {
|
|
2153
|
+
const wantsMethods = await UPrompt.confirm("Do you want to create methods for this controller?");
|
|
2154
|
+
let methods = [];
|
|
2155
|
+
if (wantsMethods === true) {
|
|
2156
|
+
methods = await askCommaSeparatedList(
|
|
2157
|
+
"Enter the method names (comma separated):",
|
|
2158
|
+
"create, findAll, findById, update, delete"
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
fileName = `${baseName}.controller.ts`;
|
|
2162
|
+
content = generateBuildYourOwnControllerTemplate(baseName, methods);
|
|
2163
|
+
break;
|
|
2164
|
+
}
|
|
2165
|
+
case "service": {
|
|
2166
|
+
const kind = await UPrompt.select(
|
|
2167
|
+
"Do you want a class with methods, or a service that is just a function?",
|
|
2168
|
+
["class"],
|
|
2169
|
+
[
|
|
2170
|
+
{ label: "Class with methods", value: ["class"] },
|
|
2171
|
+
{ label: "Single function", value: ["function"] }
|
|
2172
|
+
]
|
|
2173
|
+
);
|
|
2174
|
+
if ((0, import_prompts6.isCancel)(kind)) return void 0;
|
|
2175
|
+
fileName = `${baseName}.service.ts`;
|
|
2176
|
+
if (kind === "class") {
|
|
2177
|
+
const methods = await askCommaSeparatedList(
|
|
2178
|
+
"Enter the method names (comma separated):",
|
|
2179
|
+
"findAll, findById, create, update, delete"
|
|
2180
|
+
);
|
|
2181
|
+
content = generateBuildYourOwnServiceClassTemplate(baseName, methods);
|
|
2182
|
+
} else {
|
|
2183
|
+
content = generateBuildYourOwnServiceFunctionTemplate(baseName);
|
|
2184
|
+
}
|
|
2185
|
+
break;
|
|
2186
|
+
}
|
|
2187
|
+
case "dto": {
|
|
2188
|
+
const raw = await askCommaSeparatedList(
|
|
2189
|
+
"Enter the DTO fields as name:type pairs, comma separated:",
|
|
2190
|
+
"name:string, email:string"
|
|
2191
|
+
);
|
|
2192
|
+
fileName = `${baseName}.dto.ts`;
|
|
2193
|
+
content = generateBuildYourOwnDtoTemplate(baseName, parseFieldDefinitions(raw));
|
|
2194
|
+
break;
|
|
2195
|
+
}
|
|
2196
|
+
case "entity": {
|
|
2197
|
+
const raw = await askCommaSeparatedList(
|
|
2198
|
+
"Enter the entity properties as name:type pairs, comma separated:",
|
|
2199
|
+
"id:string, name:string, createdAt:Date"
|
|
2200
|
+
);
|
|
2201
|
+
fileName = `${baseName}.entity.ts`;
|
|
2202
|
+
content = generateBuildYourOwnEntityTemplate(baseName, parseFieldDefinitions(raw));
|
|
2203
|
+
break;
|
|
2204
|
+
}
|
|
2205
|
+
case "repository": {
|
|
2206
|
+
const methods = await askCommaSeparatedList(
|
|
2207
|
+
"Enter the actions this repository should perform (comma separated):",
|
|
2208
|
+
"findAll, findById, save, update, delete"
|
|
2209
|
+
);
|
|
2210
|
+
fileName = `${baseName}.repository.ts`;
|
|
2211
|
+
content = generateBuildYourOwnRepositoryTemplate(baseName, methods);
|
|
2212
|
+
break;
|
|
2213
|
+
}
|
|
2214
|
+
case "route": {
|
|
2215
|
+
const actions = await askCommaSeparatedList(
|
|
2216
|
+
"Enter the actions to expose as routes (comma separated):",
|
|
2217
|
+
"findAll, findById, create, update, delete"
|
|
2218
|
+
);
|
|
2219
|
+
fileName = `${baseName}.route.ts`;
|
|
2220
|
+
content = generateBuildYourOwnRouteTemplate(baseName, actions);
|
|
2221
|
+
break;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
const filePath = import_path6.default.join(dirPath, fileName);
|
|
2225
|
+
UFsModule.createFile(filePath, content);
|
|
2226
|
+
ok(fileName);
|
|
2227
|
+
return { name: fileName, type: "file" };
|
|
2228
|
+
}
|
|
2229
|
+
async function handleDirectory(dirPath, node) {
|
|
2230
|
+
let keepGoing = true;
|
|
2231
|
+
while (keepGoing) {
|
|
2232
|
+
const choice = await UPrompt.select(
|
|
2233
|
+
`Inside "${node.name}", do you want to create a file or a folder?`,
|
|
2234
|
+
["file"],
|
|
2235
|
+
[
|
|
2236
|
+
{ label: "File", value: ["file"] },
|
|
2237
|
+
{ label: "Folder", value: ["folder"] }
|
|
2238
|
+
]
|
|
2239
|
+
);
|
|
2240
|
+
if ((0, import_prompts6.isCancel)(choice)) return;
|
|
2241
|
+
if (choice === "folder") {
|
|
2242
|
+
const folderName = await askName("Enter the folder name:", "core");
|
|
2243
|
+
if (!folderName) return;
|
|
2244
|
+
const subDir = import_path6.default.join(dirPath, folderName);
|
|
2245
|
+
UFsModule.createDirectoryRecursively(subDir);
|
|
2246
|
+
const subNode = { name: folderName, type: "dir", children: [] };
|
|
2247
|
+
node.children.push(subNode);
|
|
2248
|
+
ok(`${folderName}/`);
|
|
2249
|
+
await handleDirectory(subDir, subNode);
|
|
2250
|
+
} else {
|
|
2251
|
+
const fileNode = await handleFileCreation(dirPath);
|
|
2252
|
+
if (fileNode) {
|
|
2253
|
+
node.children.push(fileNode);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
info(`Current structure of "${node.name}":`);
|
|
2257
|
+
printTree(node);
|
|
2258
|
+
const wantsContinue = await UPrompt.confirm(`Continue creating files/folders inside "${node.name}"?`);
|
|
2259
|
+
if ((0, import_prompts6.isCancel)(wantsContinue) || wantsContinue !== true) {
|
|
2260
|
+
keepGoing = false;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
var buildYourOwnArchitectureScaffoldTemplate = async () => {
|
|
2265
|
+
const featuresDir = import_path6.default.join(process.cwd(), "src", "features");
|
|
2266
|
+
const forest = [];
|
|
2267
|
+
let creatingNewRoot = true;
|
|
2268
|
+
while (creatingNewRoot) {
|
|
2269
|
+
const rootName = await askName("Enter the name of the root folder for your architecture:", "myArchitecture");
|
|
2270
|
+
if (!rootName) {
|
|
2271
|
+
console.log(import_ansi_colors9.default.bgRed(import_ansi_colors9.default.white("Operation cancelled.")));
|
|
2272
|
+
process.exit(0);
|
|
2273
|
+
}
|
|
2274
|
+
const rootDir = import_path6.default.join(featuresDir, rootName);
|
|
2275
|
+
if (import_fs3.default.existsSync(rootDir)) {
|
|
2276
|
+
info(`Folder "${rootName}" already exists, new files/folders will be added inside it.`);
|
|
2277
|
+
}
|
|
2278
|
+
UFsModule.createDirectoryRecursively(rootDir);
|
|
2279
|
+
const rootNode = { name: rootName, type: "dir", children: [] };
|
|
2280
|
+
forest.push(rootNode);
|
|
2281
|
+
await handleDirectory(rootDir, rootNode);
|
|
2282
|
+
const wantsNewRoot = await UPrompt.confirm(
|
|
2283
|
+
"Do you want to create a new folder at the root of your architecture?",
|
|
2284
|
+
false
|
|
2285
|
+
);
|
|
2286
|
+
if ((0, import_prompts6.isCancel)(wantsNewRoot) || wantsNewRoot !== true) {
|
|
2287
|
+
console.log(import_ansi_colors9.default.cyan("En disant non, ce sera la fin de la cr\xE9ation de votre propre architecture."));
|
|
2288
|
+
creatingNewRoot = false;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
console.log(`
|
|
2292
|
+
${import_ansi_colors9.default.bgGreen(import_ansi_colors9.default.white("\n\u{1F389} Your custom architecture has been created!\n"))}`);
|
|
2293
|
+
forest.forEach((root) => printTree(root));
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
// src/templates/mvcModule/mvcModuleScaffold.template.ts
|
|
2297
|
+
var import_path7 = __toESM(require("path"), 1);
|
|
2298
|
+
var import_ansi_colors10 = __toESM(require("ansi-colors"), 1);
|
|
2299
|
+
var import_prompts7 = require("@clack/prompts");
|
|
2300
|
+
|
|
2301
|
+
// src/templates/mvcModule/mvcModule.template.ts
|
|
2302
|
+
var capitalize18 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
2303
|
+
var generateMvcModelTemplate = (featureName) => {
|
|
2304
|
+
const capitalized = capitalize18(featureName);
|
|
2305
|
+
const modelName = `${capitalized}Model`;
|
|
2306
|
+
return `/**
|
|
2307
|
+
* ${modelName} \u2014 Model
|
|
2308
|
+
* Owns both the data shape and the data-access logic for "${featureName}".
|
|
2309
|
+
*/
|
|
2310
|
+
export class ${modelName} {
|
|
2311
|
+
id: string;
|
|
2312
|
+
createdAt: Date;
|
|
2313
|
+
updatedAt: Date;
|
|
2314
|
+
|
|
2315
|
+
constructor(
|
|
2316
|
+
id: string,
|
|
2317
|
+
// TODO: add your model properties here
|
|
2318
|
+
createdAt?: Date,
|
|
2319
|
+
updatedAt?: Date,
|
|
2320
|
+
) {
|
|
2321
|
+
this.id = id;
|
|
2322
|
+
this.createdAt = createdAt ?? new Date();
|
|
2323
|
+
this.updatedAt = updatedAt ?? new Date();
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
private static store: Map<string, ${modelName}> = new Map();
|
|
2327
|
+
|
|
2328
|
+
static async findAll(): Promise<${modelName}[]> {
|
|
2329
|
+
// TODO: Replace with your ORM query
|
|
2330
|
+
return Array.from(this.store.values());
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
static async findById(id: string): Promise<${modelName} | null> {
|
|
2334
|
+
// TODO: Replace with your ORM query
|
|
2335
|
+
return this.store.get(id) ?? null;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
static async create(data: Record<string, unknown>): Promise<${modelName}> {
|
|
2339
|
+
const model = new ${modelName}(String(Date.now()));
|
|
2340
|
+
// TODO: Map data fields onto model
|
|
2341
|
+
this.store.set(model.id, model);
|
|
2342
|
+
return model;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
static async update(id: string, data: Record<string, unknown>): Promise<${modelName} | null> {
|
|
2346
|
+
const existing = this.store.get(id);
|
|
2347
|
+
if (!existing) return null;
|
|
2348
|
+
// TODO: Apply data fields onto existing model
|
|
2349
|
+
existing.updatedAt = new Date();
|
|
2350
|
+
this.store.set(id, existing);
|
|
2351
|
+
return existing;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
static async delete(id: string): Promise<boolean> {
|
|
2355
|
+
// TODO: Replace with your ORM delete
|
|
2356
|
+
return this.store.delete(id);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
`;
|
|
2360
|
+
};
|
|
2361
|
+
var generateMvcViewTemplate = (featureName) => {
|
|
2362
|
+
const capitalized = capitalize18(featureName);
|
|
2363
|
+
const modelName = `${capitalized}Model`;
|
|
2364
|
+
const viewName = `${capitalized}View`;
|
|
2365
|
+
return `import { ${modelName} } from "../models/${featureName}.model";
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* ${viewName} \u2014 View
|
|
2369
|
+
* Shapes "${featureName}" data before it is sent back as JSON.
|
|
2370
|
+
*/
|
|
2371
|
+
export class ${viewName} {
|
|
2372
|
+
static toJSON(model: ${modelName}): Record<string, unknown> {
|
|
2373
|
+
return {
|
|
2374
|
+
id: model.id,
|
|
2375
|
+
createdAt: model.createdAt,
|
|
2376
|
+
updatedAt: model.updatedAt,
|
|
2377
|
+
// TODO: expose the fields API consumers should see (and only those)
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
static toJSONList(models: ${modelName}[]): Record<string, unknown>[] {
|
|
2382
|
+
return models.map((model: ${modelName}): Record<string, unknown> => this.toJSON(model));
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
`;
|
|
2386
|
+
};
|
|
2387
|
+
var generateMvcControllerTemplate = (controllerName, featureName, methods) => {
|
|
2388
|
+
const capitalized = capitalize18(featureName);
|
|
2389
|
+
const modelName = `${capitalized}Model`;
|
|
2390
|
+
const viewName = `${capitalized}View`;
|
|
2391
|
+
const methodsCode = methods.map((method) => generateMethod3(method, capitalized, modelName, viewName)).join("\n");
|
|
2392
|
+
return `import { Request, Response } from "opticore-express";
|
|
2393
|
+
import { ResponseHandler, HttpStatusCode, IResponseHandlerSuccessData } from "opticore-http-response";
|
|
2394
|
+
import { ${modelName} } from "../models/${featureName}.model";
|
|
2395
|
+
import { ${viewName} } from "../views/${featureName}.view";
|
|
2396
|
+
|
|
2397
|
+
export class ${controllerName} {
|
|
2398
|
+
${methodsCode}
|
|
2399
|
+
private static handleError(error: unknown) {
|
|
2400
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
2401
|
+
return ResponseHandler.error(message, HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
`;
|
|
2405
|
+
};
|
|
2406
|
+
var generateMethod3 = (method, capitalized, modelName, viewName) => {
|
|
2407
|
+
const lower = method.toLowerCase();
|
|
2408
|
+
if (lower.includes("findall") || lower.includes("getall")) {
|
|
2409
|
+
return generateFindAllMethod3(method, capitalized, modelName, viewName);
|
|
2410
|
+
}
|
|
2411
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) {
|
|
2412
|
+
return generateFindByIdMethod3(method, capitalized, modelName, viewName);
|
|
2413
|
+
}
|
|
2414
|
+
if (lower.includes("create") || lower.includes("add")) {
|
|
2415
|
+
return generateCreateMethod3(method, capitalized, modelName, viewName);
|
|
2416
|
+
}
|
|
2417
|
+
if (lower.includes("update") || lower.includes("edit")) {
|
|
2418
|
+
return generateUpdateMethod3(method, capitalized, modelName, viewName);
|
|
2419
|
+
}
|
|
2420
|
+
if (lower.includes("delete") || lower.includes("remove")) {
|
|
2421
|
+
return generateDeleteMethod3(method, capitalized, modelName);
|
|
2422
|
+
}
|
|
2423
|
+
return `
|
|
2424
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2425
|
+
try {
|
|
2426
|
+
// TODO: Implement ${method} logic
|
|
2427
|
+
return ResponseHandler.success({}, "success", HttpStatusCode.OK);
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
return ${capitalized}Controller.handleError(error);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
`;
|
|
2433
|
+
};
|
|
2434
|
+
var generateFindAllMethod3 = (method, capitalized, modelName, viewName) => `
|
|
2435
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2436
|
+
try {
|
|
2437
|
+
const models = await ${modelName}.findAll();
|
|
2438
|
+
return ResponseHandler.success(${viewName}.toJSONList(models), "success", HttpStatusCode.OK);
|
|
2439
|
+
} catch (error) {
|
|
2440
|
+
return ${capitalized}Controller.handleError(error);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
`;
|
|
2444
|
+
var generateFindByIdMethod3 = (method, capitalized, modelName, viewName) => `
|
|
2445
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2446
|
+
try {
|
|
2447
|
+
const { id } = req.params;
|
|
2448
|
+
const model = await ${modelName}.findById(id);
|
|
2449
|
+
if (!model) {
|
|
2450
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
2451
|
+
}
|
|
2452
|
+
return ResponseHandler.success(${viewName}.toJSON(model), "success", HttpStatusCode.OK);
|
|
2453
|
+
} catch (error) {
|
|
2454
|
+
return ${capitalized}Controller.handleError(error);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
`;
|
|
2458
|
+
var generateCreateMethod3 = (method, capitalized, modelName, viewName) => `
|
|
2459
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2460
|
+
try {
|
|
2461
|
+
const model = await ${modelName}.create(req.body);
|
|
2462
|
+
return ResponseHandler.success(${viewName}.toJSON(model), "created", HttpStatusCode.CREATED);
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
return ${capitalized}Controller.handleError(error);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
`;
|
|
2468
|
+
var generateUpdateMethod3 = (method, capitalized, modelName, viewName) => `
|
|
2469
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2470
|
+
try {
|
|
2471
|
+
const { id } = req.params;
|
|
2472
|
+
const model = await ${modelName}.update(id, req.body);
|
|
2473
|
+
if (!model) {
|
|
2474
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
2475
|
+
}
|
|
2476
|
+
return ResponseHandler.success(${viewName}.toJSON(model), "success", HttpStatusCode.OK);
|
|
2477
|
+
} catch (error) {
|
|
2478
|
+
return ${capitalized}Controller.handleError(error);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
`;
|
|
2482
|
+
var generateDeleteMethod3 = (method, capitalized, modelName) => `
|
|
2483
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2484
|
+
try {
|
|
2485
|
+
const { id } = req.params;
|
|
2486
|
+
const deleted = await ${modelName}.delete(id);
|
|
2487
|
+
if (!deleted) {
|
|
2488
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
2489
|
+
}
|
|
2490
|
+
return ResponseHandler.success(null, "deleted", HttpStatusCode.NO_CONTENT);
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
return ${capitalized}Controller.handleError(error);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
`;
|
|
2496
|
+
var generateMvcRouterHandlerTemplate = (featureName, controllerMethods) => {
|
|
2497
|
+
const capitalized = capitalize18(featureName);
|
|
2498
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
2499
|
+
const controllerName = `${capitalized}Controller`;
|
|
2500
|
+
const routesCode = (controllerMethods || []).map((method) => {
|
|
2501
|
+
const { httpMethod, path: path10 } = resolveRoute3(method, featureName);
|
|
2502
|
+
return `{
|
|
2503
|
+
path: \`${path10}\`,
|
|
2504
|
+
method: "${httpMethod}",
|
|
2505
|
+
middlewares: [],
|
|
2506
|
+
handler: async (ctx: ICustomContext) => await ${controllerName}.${method}(ctx.req, ctx.res)
|
|
2507
|
+
}`;
|
|
2508
|
+
}).join(",\n");
|
|
2509
|
+
return `import { OpticoreRoutingFactory, ICustomContext, IMultipleRouteDefinition } from "opticore-router";
|
|
2510
|
+
import { ${controllerName} } from "../controllers/${featureName}.controller";
|
|
2511
|
+
|
|
2512
|
+
export const ${handlerName}: () => IMultipleRouteDefinition = () => {
|
|
2513
|
+
return OpticoreRoutingFactory.routes(
|
|
2514
|
+
${controllerName},
|
|
2515
|
+
[
|
|
2516
|
+
${routesCode.replace(/\n/g, "\n ")}
|
|
2517
|
+
]
|
|
2518
|
+
);
|
|
2519
|
+
};
|
|
2520
|
+
`;
|
|
2521
|
+
};
|
|
2522
|
+
var generateMvcRouterTemplate = (featureName) => {
|
|
2523
|
+
const capitalized = capitalize18(featureName);
|
|
2524
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
2525
|
+
const routerName = `${capitalized}Router`;
|
|
2526
|
+
return `import { ${handlerName} } from "./${featureName}.router.handler";
|
|
2527
|
+
import { TFeatureRoutes } from "opticore-router";
|
|
2528
|
+
|
|
2529
|
+
export const ${routerName}: TFeatureRoutes = {
|
|
2530
|
+
routes: [
|
|
2531
|
+
{
|
|
2532
|
+
path: ${handlerName}().path,
|
|
2533
|
+
handler: ${handlerName}().handler
|
|
2534
|
+
}
|
|
2535
|
+
]
|
|
2536
|
+
};
|
|
2537
|
+
`;
|
|
2538
|
+
};
|
|
2539
|
+
var resolveRoute3 = (methodName, featureName) => {
|
|
2540
|
+
const lower = methodName.toLowerCase();
|
|
2541
|
+
if (lower.includes("findall") || lower.includes("getall")) {
|
|
2542
|
+
return { httpMethod: "get", path: `/${featureName}` };
|
|
2543
|
+
}
|
|
2544
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) {
|
|
2545
|
+
return { httpMethod: "get", path: `/${featureName}/:id` };
|
|
2546
|
+
}
|
|
2547
|
+
if (lower.includes("create") || lower.includes("add")) {
|
|
2548
|
+
return { httpMethod: "post", path: `/${featureName}` };
|
|
2549
|
+
}
|
|
2550
|
+
if (lower.includes("update") || lower.includes("edit")) {
|
|
2551
|
+
return { httpMethod: "put", path: `/${featureName}/:id` };
|
|
2552
|
+
}
|
|
2553
|
+
if (lower.includes("delete") || lower.includes("remove")) {
|
|
2554
|
+
return { httpMethod: "delete", path: `/${featureName}/:id` };
|
|
2555
|
+
}
|
|
2556
|
+
return { httpMethod: "get", path: `/${featureName}/${lower}` };
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
// src/templates/mvcModule/mvcModuleScaffold.template.ts
|
|
2560
|
+
var capitalize19 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
2561
|
+
var mvcModuleTemplate = async (featureName) => {
|
|
2562
|
+
FeatureComponentGeneratorUtils.setStructure("mvc");
|
|
2563
|
+
const basePath = import_path7.default.join("src", "features", featureName);
|
|
2564
|
+
const paths = {
|
|
2565
|
+
modelsDir: import_path7.default.join(basePath, "models"),
|
|
2566
|
+
viewsDir: import_path7.default.join(basePath, "views"),
|
|
2567
|
+
controllersDir: import_path7.default.join(basePath, "controllers"),
|
|
2568
|
+
routesDir: import_path7.default.join(basePath, "routes")
|
|
2569
|
+
};
|
|
2570
|
+
Object.values(paths).forEach((dir) => UFsModule.createDirectoryRecursively(dir));
|
|
2571
|
+
const controllerName = `${capitalize19(featureName)}Controller`;
|
|
2572
|
+
UFsModule.createFile(import_path7.default.join(paths.modelsDir, `${featureName}.model.ts`), generateMvcModelTemplate(featureName));
|
|
2573
|
+
console.log(import_ansi_colors10.default.green(`\u2714 Model created: ${import_path7.default.join(paths.modelsDir, `${featureName}.model.ts`)}`));
|
|
2574
|
+
UFsModule.createFile(import_path7.default.join(paths.viewsDir, `${featureName}.view.ts`), generateMvcViewTemplate(featureName));
|
|
2575
|
+
console.log(import_ansi_colors10.default.green(`\u2714 View created: ${import_path7.default.join(paths.viewsDir, `${featureName}.view.ts`)}`));
|
|
2576
|
+
const wantsMethods = await UPrompt.confirm("Do you want to add methods to the controller?");
|
|
2577
|
+
let methods = [];
|
|
2578
|
+
let controllerContent;
|
|
2579
|
+
if (wantsMethods && !(0, import_prompts7.isCancel)(wantsMethods)) {
|
|
2580
|
+
const methodsText = await UPrompt.text(
|
|
2581
|
+
"Enter the method names (comma separated):",
|
|
2582
|
+
"create, findAll, findById, update, delete",
|
|
2583
|
+
(v) => v ? void 0 : "You must enter at least one method."
|
|
2584
|
+
);
|
|
2585
|
+
if (!(0, import_prompts7.isCancel)(methodsText)) {
|
|
2586
|
+
methods = methodsText.split(",").map((m) => m.trim()).filter(Boolean);
|
|
2587
|
+
}
|
|
2588
|
+
controllerContent = generateMvcControllerTemplate(controllerName, featureName, methods);
|
|
2589
|
+
} else {
|
|
2590
|
+
controllerContent = `import { Request, Response } from "opticore-express";
|
|
2591
|
+
|
|
2592
|
+
export class ${controllerName} {}
|
|
2593
|
+
`;
|
|
2594
|
+
}
|
|
2595
|
+
const controllerPath = import_path7.default.join(paths.controllersDir, `${featureName}.controller.ts`);
|
|
2596
|
+
UFsModule.createFile(controllerPath, controllerContent);
|
|
2597
|
+
console.log(import_ansi_colors10.default.green(`\u2714 Controller created: ${controllerPath}`));
|
|
2598
|
+
UFsModule.createFile(
|
|
2599
|
+
import_path7.default.join(paths.routesDir, `${featureName}.router.handler.ts`),
|
|
2600
|
+
generateMvcRouterHandlerTemplate(featureName, methods)
|
|
2601
|
+
);
|
|
2602
|
+
console.log(import_ansi_colors10.default.green(`\u2714 Router Handler created: ${import_path7.default.join(paths.routesDir, `${featureName}.router.handler.ts`)}`));
|
|
2603
|
+
UFsModule.createFile(
|
|
2604
|
+
import_path7.default.join(paths.routesDir, `${featureName}.router.ts`),
|
|
2605
|
+
generateMvcRouterTemplate(featureName)
|
|
2606
|
+
);
|
|
2607
|
+
await FeatureComponentGeneratorUtils.registerModuleRouter(featureName);
|
|
2608
|
+
console.log(import_ansi_colors10.default.green(`\u2714 Router created: ${import_path7.default.join(paths.routesDir, `${featureName}.router.ts`)}`));
|
|
2609
|
+
console.log(
|
|
2610
|
+
import_ansi_colors10.default.bgGreen(
|
|
2611
|
+
import_ansi_colors10.default.white(`
|
|
2612
|
+
\u{1F389} Feature "${featureName}" scaffolded with MVC architecture!
|
|
2613
|
+
`)
|
|
2614
|
+
)
|
|
2615
|
+
);
|
|
2616
|
+
};
|
|
2617
|
+
|
|
2618
|
+
// src/templates/layeredModule/layeredModuleScaffold.template.ts
|
|
2619
|
+
var import_path8 = __toESM(require("path"), 1);
|
|
2620
|
+
var import_ansi_colors11 = __toESM(require("ansi-colors"), 1);
|
|
2621
|
+
var import_prompts8 = require("@clack/prompts");
|
|
2622
|
+
|
|
2623
|
+
// src/templates/layeredModule/layeredModule.template.ts
|
|
2624
|
+
var capitalize20 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
2625
|
+
var generateLayeredEntityTemplate = (featureName) => {
|
|
2626
|
+
const capitalized = capitalize20(featureName);
|
|
2627
|
+
const entityName = `${capitalized}Entity`;
|
|
2628
|
+
return `/**
|
|
2629
|
+
* ${entityName} \u2014 Entity
|
|
2630
|
+
* Plain data structure for the "${featureName}" domain. No data-access logic here.
|
|
2631
|
+
*/
|
|
2632
|
+
export class ${entityName} {
|
|
2633
|
+
constructor(
|
|
2634
|
+
public readonly id: string,
|
|
2635
|
+
// TODO: add your entity fields here
|
|
2636
|
+
public readonly createdAt: Date = new Date(),
|
|
2637
|
+
public readonly updatedAt: Date = new Date(),
|
|
2638
|
+
) {}
|
|
2639
|
+
}
|
|
2640
|
+
`;
|
|
2641
|
+
};
|
|
2642
|
+
var generateLayeredDtoTemplate = (featureName) => {
|
|
2643
|
+
const capitalized = capitalize20(featureName);
|
|
2644
|
+
const entityName = `${capitalized}Entity`;
|
|
2645
|
+
return `import { ${entityName} } from "../entities/${featureName}.entity";
|
|
2646
|
+
|
|
2647
|
+
export interface Create${capitalized}Dto {
|
|
2648
|
+
// TODO: define the fields required to create a ${featureName}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
export interface Update${capitalized}Dto {
|
|
2652
|
+
// TODO: define the fields that can be updated
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
export interface ${capitalized}ResponseDto {
|
|
2656
|
+
id: string;
|
|
2657
|
+
createdAt: Date;
|
|
2658
|
+
updatedAt: Date;
|
|
2659
|
+
// TODO: define the fields exposed to API consumers
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
export const to${capitalized}ResponseDto = (entity: ${entityName}): ${capitalized}ResponseDto => ({
|
|
2663
|
+
id: entity.id,
|
|
2664
|
+
createdAt: entity.createdAt,
|
|
2665
|
+
updatedAt: entity.updatedAt,
|
|
2666
|
+
});
|
|
2667
|
+
`;
|
|
2668
|
+
};
|
|
2669
|
+
var generateLayeredRepositoryTemplate = (featureName) => {
|
|
2670
|
+
const capitalized = capitalize20(featureName);
|
|
2671
|
+
const entityName = `${capitalized}Entity`;
|
|
2672
|
+
const repoName = `${capitalized}Repository`;
|
|
2673
|
+
return `import { ${entityName} } from "../entities/${featureName}.entity";
|
|
2674
|
+
|
|
2675
|
+
export class ${repoName} {
|
|
2676
|
+
private store: Map<string, ${entityName}> = new Map();
|
|
2677
|
+
|
|
2678
|
+
async findAll(): Promise<${entityName}[]> {
|
|
2679
|
+
// TODO: Replace with your ORM query
|
|
2680
|
+
return Array.from(this.store.values());
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
async findById(id: string): Promise<${entityName} | null> {
|
|
2684
|
+
// TODO: Replace with your ORM query
|
|
2685
|
+
return this.store.get(id) ?? null;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
async create(entity: ${entityName}): Promise<${entityName}> {
|
|
2689
|
+
// TODO: Replace with your ORM save
|
|
2690
|
+
this.store.set(entity.id, entity);
|
|
2691
|
+
return entity;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
async update(entity: ${entityName}): Promise<${entityName} | null> {
|
|
2695
|
+
// TODO: Replace with your ORM update
|
|
2696
|
+
if (!this.store.has(entity.id)) return null;
|
|
2697
|
+
this.store.set(entity.id, entity);
|
|
2698
|
+
return entity;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
async delete(id: string): Promise<boolean> {
|
|
2702
|
+
// TODO: Replace with your ORM delete
|
|
2703
|
+
return this.store.delete(id);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
`;
|
|
2707
|
+
};
|
|
2708
|
+
var generateLayeredServiceTemplate = (featureName) => {
|
|
2709
|
+
const capitalized = capitalize20(featureName);
|
|
2710
|
+
const entityName = `${capitalized}Entity`;
|
|
2711
|
+
const repoName = `${capitalized}Repository`;
|
|
2712
|
+
const serviceName = `${capitalized}Service`;
|
|
2713
|
+
return `import { ${repoName} } from "../repositories/${featureName}.repository";
|
|
2714
|
+
import { ${entityName} } from "../entities/${featureName}.entity";
|
|
2715
|
+
import { Create${capitalized}Dto, Update${capitalized}Dto, ${capitalized}ResponseDto, to${capitalized}ResponseDto } from "../dto/${featureName}.dto";
|
|
2716
|
+
|
|
2717
|
+
export class ${serviceName} {
|
|
2718
|
+
private readonly repository: ${repoName};
|
|
2719
|
+
|
|
2720
|
+
constructor() {
|
|
2721
|
+
this.repository = new ${repoName}();
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
async findAll(): Promise<${capitalized}ResponseDto[]> {
|
|
2725
|
+
const entities = await this.repository.findAll();
|
|
2726
|
+
return entities.map(to${capitalized}ResponseDto);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
async findById(id: string): Promise<${capitalized}ResponseDto | null> {
|
|
2730
|
+
const entity = await this.repository.findById(id);
|
|
2731
|
+
return entity ? to${capitalized}ResponseDto(entity) : null;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
async create(data: Create${capitalized}Dto): Promise<${capitalized}ResponseDto> {
|
|
2735
|
+
const entity = new ${entityName}(String(Date.now()));
|
|
2736
|
+
// TODO: Map data fields onto entity
|
|
2737
|
+
const created = await this.repository.create(entity);
|
|
2738
|
+
return to${capitalized}ResponseDto(created);
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
async update(id: string, data: Update${capitalized}Dto): Promise<${capitalized}ResponseDto | null> {
|
|
2742
|
+
const existing = await this.repository.findById(id);
|
|
2743
|
+
if (!existing) {
|
|
2744
|
+
return null;
|
|
2745
|
+
}
|
|
2746
|
+
// TODO: Apply data fields onto existing entity
|
|
2747
|
+
const updated = await this.repository.update(existing);
|
|
2748
|
+
return updated ? to${capitalized}ResponseDto(updated) : null;
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
async delete(id: string): Promise<boolean> {
|
|
2752
|
+
return this.repository.delete(id);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
`;
|
|
2756
|
+
};
|
|
2757
|
+
var generateLayeredControllerTemplate = (controllerName, featureName, methods) => {
|
|
2758
|
+
const capitalized = capitalize20(featureName);
|
|
2759
|
+
const serviceName = `${capitalized}Service`;
|
|
2760
|
+
const methodsCode = methods.map((method) => generateMethod4(method, capitalized, serviceName)).join("\n");
|
|
2761
|
+
return `import { Request, Response } from "opticore-express";
|
|
2762
|
+
import { ResponseHandler, HttpStatusCode, IResponseHandlerSuccessData } from "opticore-http-response";
|
|
2763
|
+
import { ${serviceName} } from "../services/${featureName}.service";
|
|
2764
|
+
|
|
2765
|
+
export class ${controllerName} {
|
|
2766
|
+
private static buildService(): ${serviceName} {
|
|
2767
|
+
return new ${serviceName}();
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
${methodsCode}
|
|
2771
|
+
private static handleError(error: unknown) {
|
|
2772
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
2773
|
+
return ResponseHandler.error(message, HttpStatusCode.INTERNAL_SERVER_ERROR);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
`;
|
|
2777
|
+
};
|
|
2778
|
+
var generateMethod4 = (method, capitalized, serviceName) => {
|
|
2779
|
+
const lower = method.toLowerCase();
|
|
2780
|
+
if (lower.includes("findall") || lower.includes("getall")) return generateFindAllMethod4(method, capitalized);
|
|
2781
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) return generateFindByIdMethod4(method, capitalized);
|
|
2782
|
+
if (lower.includes("create") || lower.includes("add")) return generateCreateMethod4(method, capitalized);
|
|
2783
|
+
if (lower.includes("update") || lower.includes("edit")) return generateUpdateMethod4(method, capitalized);
|
|
2784
|
+
if (lower.includes("delete") || lower.includes("remove")) return generateDeleteMethod4(method, capitalized);
|
|
2785
|
+
return `
|
|
2786
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2787
|
+
try {
|
|
2788
|
+
const service = ${capitalized}Controller.buildService();
|
|
2789
|
+
// TODO: Implement ${method} logic
|
|
2790
|
+
return ResponseHandler.success({}, "success", HttpStatusCode.OK);
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
return ${capitalized}Controller.handleError(error);
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
`;
|
|
2796
|
+
};
|
|
2797
|
+
var generateFindAllMethod4 = (method, capitalized) => `
|
|
2798
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2799
|
+
try {
|
|
2800
|
+
const service = ${capitalized}Controller.buildService();
|
|
2801
|
+
const results = await service.findAll();
|
|
2802
|
+
return ResponseHandler.success(results, "success", HttpStatusCode.OK);
|
|
2803
|
+
} catch (error) {
|
|
2804
|
+
return ${capitalized}Controller.handleError(error);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
`;
|
|
2808
|
+
var generateFindByIdMethod4 = (method, capitalized) => `
|
|
2809
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2810
|
+
try {
|
|
2811
|
+
const { id } = req.params;
|
|
2812
|
+
const service = ${capitalized}Controller.buildService();
|
|
2813
|
+
const result = await service.findById(id);
|
|
2814
|
+
if (!result) {
|
|
2815
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
2816
|
+
}
|
|
2817
|
+
return ResponseHandler.success(result, "success", HttpStatusCode.OK);
|
|
2818
|
+
} catch (error) {
|
|
2819
|
+
return ${capitalized}Controller.handleError(error);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
`;
|
|
2823
|
+
var generateCreateMethod4 = (method, capitalized) => `
|
|
2824
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2825
|
+
try {
|
|
2826
|
+
const service = ${capitalized}Controller.buildService();
|
|
2827
|
+
const result = await service.create(req.body);
|
|
2828
|
+
return ResponseHandler.success(result, "created", HttpStatusCode.CREATED);
|
|
2829
|
+
} catch (error) {
|
|
2830
|
+
return ${capitalized}Controller.handleError(error);
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
`;
|
|
2834
|
+
var generateUpdateMethod4 = (method, capitalized) => `
|
|
2835
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2836
|
+
try {
|
|
2837
|
+
const { id } = req.params;
|
|
2838
|
+
const service = ${capitalized}Controller.buildService();
|
|
2839
|
+
const result = await service.update(id, req.body);
|
|
2840
|
+
if (!result) {
|
|
2841
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
2842
|
+
}
|
|
2843
|
+
return ResponseHandler.success(result, "success", HttpStatusCode.OK);
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
return ${capitalized}Controller.handleError(error);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
`;
|
|
2849
|
+
var generateDeleteMethod4 = (method, capitalized) => `
|
|
2850
|
+
static async ${method}(req: Request, res: Response): Promise<IResponseHandlerSuccessData | ReturnType<typeof ResponseHandler.error>> {
|
|
2851
|
+
try {
|
|
2852
|
+
const { id } = req.params;
|
|
2853
|
+
const service = ${capitalized}Controller.buildService();
|
|
2854
|
+
const deleted = await service.delete(id);
|
|
2855
|
+
if (!deleted) {
|
|
2856
|
+
return ResponseHandler.error(\`Not found: \${id}\`, HttpStatusCode.NOT_FOUND);
|
|
2857
|
+
}
|
|
2858
|
+
return ResponseHandler.success(null, "deleted", HttpStatusCode.NO_CONTENT);
|
|
2859
|
+
} catch (error) {
|
|
2860
|
+
return ${capitalized}Controller.handleError(error);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
`;
|
|
2864
|
+
var generateLayeredRouterHandlerTemplate = (featureName, controllerMethods) => {
|
|
2865
|
+
const capitalized = capitalize20(featureName);
|
|
2866
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
2867
|
+
const controllerName = `${capitalized}Controller`;
|
|
2868
|
+
const routesCode = (controllerMethods || []).map((method) => {
|
|
2869
|
+
const { httpMethod, path: path10 } = resolveRoute4(method, featureName);
|
|
2870
|
+
return `{
|
|
2871
|
+
path: \`${path10}\`,
|
|
2872
|
+
method: "${httpMethod}",
|
|
2873
|
+
middlewares: [],
|
|
2874
|
+
handler: async (ctx: ICustomContext) => await ${controllerName}.${method}(ctx.req, ctx.res)
|
|
2875
|
+
}`;
|
|
2876
|
+
}).join(",\n");
|
|
2877
|
+
return `import { OpticoreRoutingFactory, ICustomContext, IMultipleRouteDefinition } from "opticore-router";
|
|
2878
|
+
import { ${controllerName} } from "../controllers/${featureName}.controller";
|
|
2879
|
+
|
|
2880
|
+
export const ${handlerName}: () => IMultipleRouteDefinition = () => {
|
|
2881
|
+
return OpticoreRoutingFactory.routes(
|
|
2882
|
+
${controllerName},
|
|
2883
|
+
[
|
|
2884
|
+
${routesCode.replace(/\n/g, "\n ")}
|
|
2885
|
+
]
|
|
2886
|
+
);
|
|
2887
|
+
};
|
|
2888
|
+
`;
|
|
2889
|
+
};
|
|
2890
|
+
var generateLayeredRouterTemplate = (featureName) => {
|
|
2891
|
+
const capitalized = capitalize20(featureName);
|
|
2892
|
+
const handlerName = `${capitalized}HandlerRouter`;
|
|
2893
|
+
const routerName = `${capitalized}Router`;
|
|
2894
|
+
return `import { ${handlerName} } from "./${featureName}.router.handler";
|
|
2895
|
+
import { TFeatureRoutes } from "opticore-router";
|
|
2896
|
+
|
|
2897
|
+
export const ${routerName}: TFeatureRoutes = {
|
|
2898
|
+
routes: [
|
|
2899
|
+
{
|
|
2900
|
+
path: ${handlerName}().path,
|
|
2901
|
+
handler: ${handlerName}().handler
|
|
2902
|
+
}
|
|
2903
|
+
]
|
|
2904
|
+
};
|
|
2905
|
+
`;
|
|
2906
|
+
};
|
|
2907
|
+
var resolveRoute4 = (methodName, featureName) => {
|
|
2908
|
+
const lower = methodName.toLowerCase();
|
|
2909
|
+
if (lower.includes("findall") || lower.includes("getall")) return { httpMethod: "get", path: `/${featureName}` };
|
|
2910
|
+
if (lower.includes("findbyid") || lower.includes("getbyid") || lower.includes("getone")) return { httpMethod: "get", path: `/${featureName}/:id` };
|
|
2911
|
+
if (lower.includes("create") || lower.includes("add")) return { httpMethod: "post", path: `/${featureName}` };
|
|
2912
|
+
if (lower.includes("update") || lower.includes("edit")) return { httpMethod: "put", path: `/${featureName}/:id` };
|
|
2913
|
+
if (lower.includes("delete") || lower.includes("remove")) return { httpMethod: "delete", path: `/${featureName}/:id` };
|
|
2914
|
+
return { httpMethod: "get", path: `/${featureName}/${lower}` };
|
|
2915
|
+
};
|
|
2916
|
+
|
|
2917
|
+
// src/templates/layeredModule/layeredModuleScaffold.template.ts
|
|
2918
|
+
var capitalize21 = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
2919
|
+
var layeredModuleTemplate = async (featureName) => {
|
|
2920
|
+
FeatureComponentGeneratorUtils.setStructure("layered");
|
|
2921
|
+
const basePath = import_path8.default.join("src", "features", featureName);
|
|
2922
|
+
const paths = {
|
|
2923
|
+
entitiesDir: import_path8.default.join(basePath, "entities"),
|
|
2924
|
+
dtoDir: import_path8.default.join(basePath, "dto"),
|
|
2925
|
+
repositoriesDir: import_path8.default.join(basePath, "repositories"),
|
|
2926
|
+
servicesDir: import_path8.default.join(basePath, "services"),
|
|
2927
|
+
controllersDir: import_path8.default.join(basePath, "controllers"),
|
|
2928
|
+
routesDir: import_path8.default.join(basePath, "routes")
|
|
2929
|
+
};
|
|
2930
|
+
Object.values(paths).forEach((dir) => UFsModule.createDirectoryRecursively(dir));
|
|
2931
|
+
const controllerName = `${capitalize21(featureName)}Controller`;
|
|
2932
|
+
UFsModule.createFile(import_path8.default.join(paths.entitiesDir, `${featureName}.entity.ts`), generateLayeredEntityTemplate(featureName));
|
|
2933
|
+
console.log(import_ansi_colors11.default.green(`\u2714 Entity created: ${import_path8.default.join(paths.entitiesDir, `${featureName}.entity.ts`)}`));
|
|
2934
|
+
UFsModule.createFile(import_path8.default.join(paths.dtoDir, `${featureName}.dto.ts`), generateLayeredDtoTemplate(featureName));
|
|
2935
|
+
console.log(import_ansi_colors11.default.green(`\u2714 DTO created: ${import_path8.default.join(paths.dtoDir, `${featureName}.dto.ts`)}`));
|
|
2936
|
+
UFsModule.createFile(import_path8.default.join(paths.repositoriesDir, `${featureName}.repository.ts`), generateLayeredRepositoryTemplate(featureName));
|
|
2937
|
+
console.log(import_ansi_colors11.default.green(`\u2714 Repository created: ${import_path8.default.join(paths.repositoriesDir, `${featureName}.repository.ts`)}`));
|
|
2938
|
+
UFsModule.createFile(import_path8.default.join(paths.servicesDir, `${featureName}.service.ts`), generateLayeredServiceTemplate(featureName));
|
|
2939
|
+
console.log(import_ansi_colors11.default.green(`\u2714 Service created: ${import_path8.default.join(paths.servicesDir, `${featureName}.service.ts`)}`));
|
|
2940
|
+
const wantsMethods = await UPrompt.confirm("Do you want to add methods to the controller?");
|
|
2941
|
+
let methods = [];
|
|
2942
|
+
let controllerContent;
|
|
2943
|
+
if (wantsMethods && !(0, import_prompts8.isCancel)(wantsMethods)) {
|
|
2944
|
+
const methodsText = await UPrompt.text(
|
|
2945
|
+
"Enter the method names (comma separated):",
|
|
2946
|
+
"create, findAll, findById, update, delete",
|
|
2947
|
+
(v) => v ? void 0 : "You must enter at least one method."
|
|
2948
|
+
);
|
|
2949
|
+
if (!(0, import_prompts8.isCancel)(methodsText)) {
|
|
2950
|
+
methods = methodsText.split(",").map((m) => m.trim()).filter(Boolean);
|
|
2951
|
+
}
|
|
2952
|
+
controllerContent = generateLayeredControllerTemplate(controllerName, featureName, methods);
|
|
2953
|
+
} else {
|
|
2954
|
+
controllerContent = `import { Request, Response } from "opticore-express";
|
|
2955
|
+
|
|
2956
|
+
export class ${controllerName} {}
|
|
2957
|
+
`;
|
|
2958
|
+
}
|
|
2959
|
+
const controllerPath = import_path8.default.join(paths.controllersDir, `${featureName}.controller.ts`);
|
|
2960
|
+
UFsModule.createFile(controllerPath, controllerContent);
|
|
2961
|
+
console.log(import_ansi_colors11.default.green(`\u2714 Controller created: ${controllerPath}`));
|
|
2962
|
+
UFsModule.createFile(
|
|
2963
|
+
import_path8.default.join(paths.routesDir, `${featureName}.router.handler.ts`),
|
|
2964
|
+
generateLayeredRouterHandlerTemplate(featureName, methods)
|
|
2965
|
+
);
|
|
2966
|
+
console.log(import_ansi_colors11.default.green(`\u2714 Router Handler created: ${import_path8.default.join(paths.routesDir, `${featureName}.router.handler.ts`)}`));
|
|
2967
|
+
UFsModule.createFile(
|
|
2968
|
+
import_path8.default.join(paths.routesDir, `${featureName}.router.ts`),
|
|
2969
|
+
generateLayeredRouterTemplate(featureName)
|
|
2970
|
+
);
|
|
2971
|
+
await FeatureComponentGeneratorUtils.registerModuleRouter(featureName);
|
|
2972
|
+
console.log(import_ansi_colors11.default.green(`\u2714 Router created: ${import_path8.default.join(paths.routesDir, `${featureName}.router.ts`)}`));
|
|
2973
|
+
console.log(
|
|
2974
|
+
import_ansi_colors11.default.bgGreen(
|
|
2975
|
+
import_ansi_colors11.default.white(`
|
|
2976
|
+
\u{1F389} Feature "${featureName}" scaffolded with Layered architecture!
|
|
2977
|
+
`)
|
|
2978
|
+
)
|
|
2979
|
+
);
|
|
2980
|
+
};
|
|
2981
|
+
|
|
2982
|
+
// src/core/core.ts
|
|
2983
|
+
async function runFeatureCommand() {
|
|
2984
|
+
UWelcomeMessage();
|
|
2985
|
+
const featureChoice = await choiceFeatureCleanModule();
|
|
2986
|
+
if (featureChoice === "custom_feature") {
|
|
2987
|
+
await buildYourOwnArchitectureScaffoldTemplate();
|
|
2988
|
+
return;
|
|
2989
|
+
}
|
|
2990
|
+
if (featureChoice === "mvc_architecture" || featureChoice === "layered_architecture") {
|
|
2991
|
+
const featureName2 = await _featureName();
|
|
2992
|
+
const directory2 = import_path9.default.join(import_process2.default.cwd(), "src", "features");
|
|
2993
|
+
const featureDirectory2 = import_path9.default.join(directory2, featureName2.toString());
|
|
2994
|
+
if (BaseService._isSubdirectoryExists(featureName2)) {
|
|
2995
|
+
BaseService._featureFounded(featureName2);
|
|
2996
|
+
}
|
|
2997
|
+
if ((0, import_prompts9.isCancel)(featureName2)) {
|
|
2998
|
+
BaseService._cancelOperation(featureDirectory2);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (!import_node_fs2.default.existsSync(directory2)) {
|
|
3002
|
+
BaseService._checkFeatureDir();
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
if (featureChoice === "mvc_architecture") {
|
|
3006
|
+
await mvcModuleTemplate(featureName2.toString());
|
|
3007
|
+
} else {
|
|
3008
|
+
await layeredModuleTemplate(featureName2.toString());
|
|
3009
|
+
}
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
if (!featureChoice || featureChoice !== "opticore_clean_module") {
|
|
3013
|
+
import_process2.default.exit(0);
|
|
3014
|
+
}
|
|
3015
|
+
UCustomFeatureInfo(featureChoice);
|
|
3016
|
+
const featureName = await _featureName();
|
|
3017
|
+
const directory = import_path9.default.join(import_process2.default.cwd(), "src", "features");
|
|
3018
|
+
const featureDirectory = import_path9.default.join(directory, featureName.toString());
|
|
3019
|
+
const featureCleanModule = BaseService._isSubdirectoryExists(featureName);
|
|
3020
|
+
if (featureCleanModule) {
|
|
3021
|
+
BaseService._featureFounded(featureName);
|
|
3022
|
+
}
|
|
3023
|
+
if ((0, import_prompts9.isCancel)(featureName)) {
|
|
3024
|
+
BaseService._cancelOperation(featureDirectory);
|
|
3025
|
+
} else {
|
|
3026
|
+
try {
|
|
3027
|
+
if (import_node_fs2.default.existsSync(directory)) {
|
|
3028
|
+
await createComponentApplication(featureDirectory, featureName);
|
|
3029
|
+
} else {
|
|
3030
|
+
BaseService._checkFeatureDir();
|
|
3031
|
+
}
|
|
3032
|
+
} catch (err) {
|
|
3033
|
+
console.error(err.message);
|
|
3034
|
+
import_process2.default.exit();
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
// src/cli/registerFeatureCommand.ts
|
|
3040
|
+
function handleFeatureCliError(err) {
|
|
3041
|
+
const msg = err instanceof Error ? err.message : "An unexpected error occurred";
|
|
3042
|
+
console.error("");
|
|
3043
|
+
console.error(
|
|
3044
|
+
import_chalk2.default.bgRed.white.bold(" ERROR ") + import_chalk2.default.red(` ${msg}`)
|
|
3045
|
+
);
|
|
3046
|
+
console.error("");
|
|
3047
|
+
process.exit(1);
|
|
3048
|
+
}
|
|
3049
|
+
function registerFeatureCommand(program) {
|
|
3050
|
+
return program.command("feature").description(import_chalk2.default.dim("Interactively scaffold a new feature module (clean/MVC/layered/custom architecture)")).action(async () => {
|
|
3051
|
+
try {
|
|
3052
|
+
await runFeatureCommand();
|
|
3053
|
+
} catch (e) {
|
|
3054
|
+
handleFeatureCliError(e);
|
|
3055
|
+
}
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3059
|
+
0 && (module.exports = {
|
|
3060
|
+
handleFeatureCliError,
|
|
3061
|
+
registerFeatureCommand
|
|
3062
|
+
});
|