@asad_dev/leo-generator 1.6.0
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/CHANGELOG.md +194 -0
- package/COMMAND_REFERENCE.md +412 -0
- package/README.md +486 -0
- package/dist/app/modules/imagemodule/imagemodule.constants.js +18 -0
- package/dist/app/modules/imagemodule/imagemodule.controller.js +98 -0
- package/dist/app/modules/imagemodule/imagemodule.interface.js +2 -0
- package/dist/app/modules/imagemodule/imagemodule.model.js +10 -0
- package/dist/app/modules/imagemodule/imagemodule.route.js +20 -0
- package/dist/app/modules/imagemodule/imagemodule.service.js +137 -0
- package/dist/app/modules/imagemodule/imagemodule.validation.js +12 -0
- package/dist/app/modules/skiptest/skiptest.controller.js +81 -0
- package/dist/app/modules/skiptest/skiptest.route.js +19 -0
- package/dist/app/modules/skiptest/skiptest.service.js +129 -0
- package/dist/app/modules/skiptest/skiptest.validation.js +12 -0
- package/dist/app/modules/testmodule/testmodule.constants.js +18 -0
- package/dist/app/modules/testmodule/testmodule.controller.js +81 -0
- package/dist/app/modules/testmodule/testmodule.interface.js +2 -0
- package/dist/app/modules/testmodule/testmodule.model.js +11 -0
- package/dist/app/modules/testmodule/testmodule.route.js +19 -0
- package/dist/app/modules/testmodule/testmodule.service.js +129 -0
- package/dist/app/modules/testmodule/testmodule.validation.js +14 -0
- package/dist/helpers/fileHelper.js +44 -0
- package/dist/index.js +586 -0
- package/dist/templates/constants.template.js +24 -0
- package/dist/templates/controller.template.js +108 -0
- package/dist/templates/route.template.js +68 -0
- package/dist/templates/service.template.js +184 -0
- package/dist/types.js +2 -0
- package/dist/utils/documentationUpdater.js +430 -0
- package/dist/utils/fieldParser.js +163 -0
- package/dist/utils/helperGenerator.js +87 -0
- package/dist/utils/interfaceGenerator.js +158 -0
- package/dist/utils/modelGenerator.js +140 -0
- package/dist/utils/postmanApi.js +113 -0
- package/dist/utils/postmanGenerator.js +283 -0
- package/dist/utils/swaggerGenerator.js +444 -0
- package/dist/utils/validationGenerator.js +170 -0
- package/package.json +58 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TestmoduleValidations = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
exports.TestmoduleValidations = {
|
|
6
|
+
create: zod_1.z.object({
|
|
7
|
+
name: zod_1.z.string(),
|
|
8
|
+
email: zod_1.z.string(),
|
|
9
|
+
}),
|
|
10
|
+
update: zod_1.z.object({
|
|
11
|
+
name: zod_1.z.string().optional(),
|
|
12
|
+
email: zod_1.z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
function removeFile(filenames) {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
if (!filenames || (Array.isArray(filenames) && filenames.length === 0))
|
|
20
|
+
return;
|
|
21
|
+
// Normalize to array for consistent handling
|
|
22
|
+
const files = Array.isArray(filenames) ? filenames : [filenames];
|
|
23
|
+
for (const filename of files) {
|
|
24
|
+
if (!filename)
|
|
25
|
+
continue;
|
|
26
|
+
// Remove leading '/images/' if included
|
|
27
|
+
const cleanedName = filename.replace(/^\/?images\//, '');
|
|
28
|
+
const filePath = path_1.default.join(process.cwd(), 'uploads', 'images', cleanedName);
|
|
29
|
+
try {
|
|
30
|
+
yield promises_1.default.unlink(filePath);
|
|
31
|
+
console.log(`Deleted image: ${cleanedName}`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err.code === 'ENOENT') {
|
|
35
|
+
console.warn(`File not found: ${cleanedName}`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.error(`Error deleting file ${cleanedName}:`, err);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
exports.default = removeFile;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
const commander_1 = require("commander");
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
// Import new utilities
|
|
50
|
+
const fieldParser_1 = require("./utils/fieldParser");
|
|
51
|
+
const interfaceGenerator_1 = require("./utils/interfaceGenerator");
|
|
52
|
+
const documentationUpdater_1 = require("./utils/documentationUpdater");
|
|
53
|
+
const helperGenerator_1 = require("./utils/helperGenerator");
|
|
54
|
+
// Import template generators
|
|
55
|
+
const route_template_1 = require("./templates/route.template");
|
|
56
|
+
const constants_template_1 = require("./templates/constants.template");
|
|
57
|
+
const controller_template_1 = require("./templates/controller.template");
|
|
58
|
+
const service_template_1 = require("./templates/service.template");
|
|
59
|
+
// Default configuration
|
|
60
|
+
const defaultConfig = {
|
|
61
|
+
modulesDir: "src/app/modules",
|
|
62
|
+
routesFile: "src/routes/index.ts",
|
|
63
|
+
};
|
|
64
|
+
// Load configuration from package.json or use defaults
|
|
65
|
+
function loadConfig() {
|
|
66
|
+
const config = Object.assign({}, defaultConfig);
|
|
67
|
+
// 1. Try to load from .env file in the current directory
|
|
68
|
+
try {
|
|
69
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
70
|
+
if (fs.existsSync(envPath)) {
|
|
71
|
+
const envContent = fs.readFileSync(envPath, "utf-8");
|
|
72
|
+
const lines = envContent.split("\n");
|
|
73
|
+
lines.forEach(line => {
|
|
74
|
+
const [key, ...valueParts] = line.split("=");
|
|
75
|
+
if (key && valueParts.length > 0) {
|
|
76
|
+
const value = valueParts.join("=").trim().replace(/^['"]|['"]$/g, "");
|
|
77
|
+
if (key.trim() === "POSTMAN_API_KEY")
|
|
78
|
+
config.postmanApiKey = value;
|
|
79
|
+
if (key.trim() === "POSTMAN_COLLECTION_ID")
|
|
80
|
+
config.postmanCollectionId = value;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Ignore env loading errors
|
|
87
|
+
}
|
|
88
|
+
// 2. Try to load from package.json
|
|
89
|
+
try {
|
|
90
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
91
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
92
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
93
|
+
const userConfig = packageJson.moduleGenerator || {};
|
|
94
|
+
if (userConfig.modulesDir)
|
|
95
|
+
config.modulesDir = userConfig.modulesDir;
|
|
96
|
+
if (userConfig.routesFile)
|
|
97
|
+
config.routesFile = userConfig.routesFile;
|
|
98
|
+
if (userConfig.postmanApiKey)
|
|
99
|
+
config.postmanApiKey = userConfig.postmanApiKey;
|
|
100
|
+
if (userConfig.postmanCollectionId)
|
|
101
|
+
config.postmanCollectionId = userConfig.postmanCollectionId;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.warn("Could not load configuration from package.json, using defaults");
|
|
106
|
+
}
|
|
107
|
+
return config;
|
|
108
|
+
}
|
|
109
|
+
function toCamelCase(str) {
|
|
110
|
+
return str
|
|
111
|
+
.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => index === 0 ? match.toUpperCase() : match.toLowerCase())
|
|
112
|
+
.replace(/\s+/g, "");
|
|
113
|
+
}
|
|
114
|
+
// Enhanced model generation with better nested schema support
|
|
115
|
+
function generateModelContent(camelCaseName, folderName, fields) {
|
|
116
|
+
// Collect enum imports
|
|
117
|
+
const enumImports = fields
|
|
118
|
+
.filter(f => { var _a; return f.type === 'enum' && ((_a = f.enumValues) === null || _a === void 0 ? void 0 : _a.length); })
|
|
119
|
+
.map(f => `${toCamelCase(f.name)}Enum`);
|
|
120
|
+
let modelContent = `import { Schema, model } from 'mongoose';\nimport { I${camelCaseName}, ${camelCaseName}Model`;
|
|
121
|
+
if (enumImports.length > 0) {
|
|
122
|
+
modelContent += `, ${enumImports.join(', ')}`;
|
|
123
|
+
}
|
|
124
|
+
modelContent += ` } from './${folderName}.interface'; \n\n`;
|
|
125
|
+
// Generate nested schemas
|
|
126
|
+
const nestedSchemas = generateNestedSchemas(fields);
|
|
127
|
+
modelContent += nestedSchemas;
|
|
128
|
+
modelContent += `const ${folderName}Schema = new Schema<I${camelCaseName}, ${camelCaseName}Model>({\n`;
|
|
129
|
+
// Add fields to schema
|
|
130
|
+
if (fields.length > 0) {
|
|
131
|
+
fields.forEach((field) => {
|
|
132
|
+
let schemaType = mapToMongooseType(field);
|
|
133
|
+
let additionalProps = "";
|
|
134
|
+
// Add required property if marked as required
|
|
135
|
+
if (field.isRequired) {
|
|
136
|
+
additionalProps += ", required: true";
|
|
137
|
+
}
|
|
138
|
+
modelContent += ` ${field.name}: ${schemaType}${additionalProps},\n`;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
modelContent += " // Define schema fields here\n";
|
|
143
|
+
}
|
|
144
|
+
modelContent += `}, {\n timestamps: true\n});\n\nexport const ${camelCaseName} = model<I${camelCaseName}, ${camelCaseName}Model>('${camelCaseName}', ${folderName}Schema);\n`;
|
|
145
|
+
return modelContent;
|
|
146
|
+
}
|
|
147
|
+
// Helper function to generate nested schemas
|
|
148
|
+
function generateNestedSchemas(fields) {
|
|
149
|
+
let schemas = "";
|
|
150
|
+
fields.forEach((field) => {
|
|
151
|
+
var _a, _b;
|
|
152
|
+
if (field.type.toLowerCase() === "array" &&
|
|
153
|
+
((_a = field.ref) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "object" &&
|
|
154
|
+
((_b = field.objectProperties) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
155
|
+
// Check for nested objects within this schema - GENERATE CHILDREN FIRST
|
|
156
|
+
const nestedSchemas = generateNestedSchemas(field.objectProperties);
|
|
157
|
+
schemas += nestedSchemas;
|
|
158
|
+
const nestedSchemaName = `${field.name}ItemSchema`;
|
|
159
|
+
schemas += `const ${nestedSchemaName} = new Schema({\n`;
|
|
160
|
+
// Add properties
|
|
161
|
+
field.objectProperties.forEach((prop) => {
|
|
162
|
+
let schemaType = mapToMongooseType(prop);
|
|
163
|
+
let additionalProps = "";
|
|
164
|
+
// Add required property if marked as required
|
|
165
|
+
if (prop.isRequired) {
|
|
166
|
+
additionalProps += ", required: true";
|
|
167
|
+
}
|
|
168
|
+
schemas += ` ${prop.name}: ${schemaType}${additionalProps},\n`;
|
|
169
|
+
});
|
|
170
|
+
schemas += `}, { _id: false });\n\n`;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return schemas;
|
|
174
|
+
}
|
|
175
|
+
// Helper function to map field definitions to Mongoose schema types
|
|
176
|
+
function mapToMongooseType(field) {
|
|
177
|
+
var _a, _b, _c;
|
|
178
|
+
switch (field.type.toLowerCase()) {
|
|
179
|
+
case "string":
|
|
180
|
+
return "{ type: String }";
|
|
181
|
+
case "number":
|
|
182
|
+
return "{ type: Number }";
|
|
183
|
+
case "boolean":
|
|
184
|
+
return "{ type: Boolean }";
|
|
185
|
+
case "date":
|
|
186
|
+
return "{ type: Date }";
|
|
187
|
+
case "enum":
|
|
188
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
189
|
+
const enumName = `${toCamelCase(field.name)}Enum`;
|
|
190
|
+
return `{ type: String, enum: Object.values(${enumName}) }`;
|
|
191
|
+
}
|
|
192
|
+
return "{ type: String }";
|
|
193
|
+
case "array":
|
|
194
|
+
if (((_a = field.ref) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "object" &&
|
|
195
|
+
((_b = field.objectProperties) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
196
|
+
// Array of objects with defined structure
|
|
197
|
+
const nestedSchemaName = `${field.name}ItemSchema`;
|
|
198
|
+
return `[${nestedSchemaName}]`;
|
|
199
|
+
}
|
|
200
|
+
else if (field.arrayItemType) {
|
|
201
|
+
// Array with specified item type
|
|
202
|
+
switch (field.arrayItemType.toLowerCase()) {
|
|
203
|
+
case "string":
|
|
204
|
+
return "{ type: [String] }";
|
|
205
|
+
case "number":
|
|
206
|
+
return "{ type: [Number] }";
|
|
207
|
+
case "boolean":
|
|
208
|
+
return "{ type: [Boolean] }";
|
|
209
|
+
case "date":
|
|
210
|
+
return "{ type: [Date] }";
|
|
211
|
+
case "objectid":
|
|
212
|
+
case "id":
|
|
213
|
+
return `{ type: [Schema.Types.ObjectId], ref: '${field.ref || "Document"}' }`;
|
|
214
|
+
default:
|
|
215
|
+
return "{ type: [String] }";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
return "{ type: [String] }";
|
|
220
|
+
}
|
|
221
|
+
case "object":
|
|
222
|
+
if ((_c = field.objectProperties) === null || _c === void 0 ? void 0 : _c.length) {
|
|
223
|
+
// Object with defined properties
|
|
224
|
+
return `{ ${field.objectProperties
|
|
225
|
+
.map((prop) => {
|
|
226
|
+
return `${prop.name}: ${mapToMongooseType(prop)}`;
|
|
227
|
+
})
|
|
228
|
+
.join(", ")} }`;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
return "{ type: Schema.Types.Mixed }";
|
|
232
|
+
}
|
|
233
|
+
case "objectid":
|
|
234
|
+
case "id":
|
|
235
|
+
return field.ref
|
|
236
|
+
? `{ type: Schema.Types.ObjectId, ref: '${field.ref}' }`
|
|
237
|
+
: "{ type: Schema.Types.ObjectId }";
|
|
238
|
+
default:
|
|
239
|
+
return "{ type: String }";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Enhanced validation generation
|
|
243
|
+
function generateValidationContent(camelCaseName, fields) {
|
|
244
|
+
const folderName = camelCaseName.toLowerCase();
|
|
245
|
+
let validationContent = `import { z } from 'zod';\n`;
|
|
246
|
+
// Collect enum imports
|
|
247
|
+
const enumImports = fields
|
|
248
|
+
.filter(f => { var _a; return f.type === 'enum' && ((_a = f.enumValues) === null || _a === void 0 ? void 0 : _a.length); })
|
|
249
|
+
.map(f => `${toCamelCase(f.name)}Enum`);
|
|
250
|
+
if (enumImports.length > 0) {
|
|
251
|
+
validationContent += `import { ${enumImports.join(', ')} } from './${folderName}.interface';\n`;
|
|
252
|
+
}
|
|
253
|
+
validationContent += `\n`;
|
|
254
|
+
// Add nested schemas for array of objects
|
|
255
|
+
fields.forEach((field) => {
|
|
256
|
+
var _a, _b;
|
|
257
|
+
if (field.type.toLowerCase() === "array" &&
|
|
258
|
+
((_a = field.ref) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "object" &&
|
|
259
|
+
((_b = field.objectProperties) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
260
|
+
const nestedSchemaName = `${field.name}ItemSchema`;
|
|
261
|
+
validationContent += `const ${nestedSchemaName} = z.object({\n`;
|
|
262
|
+
// Add properties from the objectProperties array
|
|
263
|
+
field.objectProperties.forEach((prop) => {
|
|
264
|
+
let zodType = mapToZodType(prop.type, prop);
|
|
265
|
+
// Add required/optional modifiers
|
|
266
|
+
if (prop.isRequired) {
|
|
267
|
+
// Already required by default in Zod
|
|
268
|
+
}
|
|
269
|
+
else if (prop.isOptional) {
|
|
270
|
+
zodType += ".optional()";
|
|
271
|
+
}
|
|
272
|
+
validationContent += ` ${prop.name}: ${zodType},\n`;
|
|
273
|
+
});
|
|
274
|
+
validationContent += `});\n\n`;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
validationContent += `export const ${camelCaseName}Validations = {\n`;
|
|
278
|
+
// Create validation schema
|
|
279
|
+
validationContent += ` create: z.object({\n`;
|
|
280
|
+
// Add validation for each field
|
|
281
|
+
if (fields.length > 0) {
|
|
282
|
+
fields.forEach((field) => {
|
|
283
|
+
let zodType = mapToZodType(field.type, field);
|
|
284
|
+
// Add required/optional modifiers
|
|
285
|
+
if (field.isRequired) {
|
|
286
|
+
// Already required by default in Zod
|
|
287
|
+
}
|
|
288
|
+
else if (field.isOptional) {
|
|
289
|
+
zodType += ".optional()";
|
|
290
|
+
}
|
|
291
|
+
validationContent += ` ${field.name}: ${zodType},\n`;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
validationContent += ` // Add validation fields\n`;
|
|
296
|
+
}
|
|
297
|
+
validationContent += ` }),\n\n`;
|
|
298
|
+
// Add update validation schema (similar to create but all fields optional)
|
|
299
|
+
validationContent += ` update: z.object({\n`;
|
|
300
|
+
if (fields.length > 0) {
|
|
301
|
+
fields.forEach((field) => {
|
|
302
|
+
let zodType = mapToZodType(field.type, field);
|
|
303
|
+
// All fields are optional in update
|
|
304
|
+
zodType += ".optional()";
|
|
305
|
+
validationContent += ` ${field.name}: ${zodType},\n`;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
validationContent += ` // Add validation fields\n`;
|
|
310
|
+
}
|
|
311
|
+
validationContent += ` }),\n};\n`;
|
|
312
|
+
return validationContent;
|
|
313
|
+
}
|
|
314
|
+
// Helper function to map types to Zod validators
|
|
315
|
+
function mapToZodType(type, field) {
|
|
316
|
+
var _a;
|
|
317
|
+
switch (type.toLowerCase()) {
|
|
318
|
+
case "string":
|
|
319
|
+
return "z.string()";
|
|
320
|
+
case "number":
|
|
321
|
+
return "z.number()";
|
|
322
|
+
case "boolean":
|
|
323
|
+
return "z.boolean()";
|
|
324
|
+
case "date":
|
|
325
|
+
return "z.string().datetime()";
|
|
326
|
+
case "enum":
|
|
327
|
+
if ((field === null || field === void 0 ? void 0 : field.enumValues) && field.enumValues.length > 0) {
|
|
328
|
+
const enumName = `${toCamelCase(field.name)}Enum`;
|
|
329
|
+
return `z.nativeEnum(${enumName})`;
|
|
330
|
+
}
|
|
331
|
+
return "z.string()";
|
|
332
|
+
case "array":
|
|
333
|
+
if (field === null || field === void 0 ? void 0 : field.ref) {
|
|
334
|
+
if (field.ref.toLowerCase() === "object" &&
|
|
335
|
+
((_a = field.objectProperties) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
336
|
+
// Array of objects with defined structure
|
|
337
|
+
const nestedSchemaName = `${field.name}ItemSchema`;
|
|
338
|
+
return `z.array(${nestedSchemaName})`;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// Array of references to other models
|
|
342
|
+
return "z.array(z.string())";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
return "z.array(z.string())";
|
|
347
|
+
}
|
|
348
|
+
case "object":
|
|
349
|
+
return "z.record(z.string(), z.any())";
|
|
350
|
+
case "objectid":
|
|
351
|
+
case "id":
|
|
352
|
+
return "z.string()"; // ObjectId as string
|
|
353
|
+
default:
|
|
354
|
+
return "z.string()";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Route generation is now handled by the imported template
|
|
358
|
+
// Enhanced createModule function with documentation generation
|
|
359
|
+
function createModule(name, fields, skipFiles, config, docOptions = {}, hasFile = false) {
|
|
360
|
+
const camelCaseName = toCamelCase(name);
|
|
361
|
+
const folderName = camelCaseName.toLowerCase();
|
|
362
|
+
const folderPath = path.join(process.cwd(), config.modulesDir, folderName);
|
|
363
|
+
// Check if file removal helper is needed
|
|
364
|
+
if (hasFile) {
|
|
365
|
+
// Generate the helper file in src/helpers
|
|
366
|
+
(0, helperGenerator_1.generateFileHelper)();
|
|
367
|
+
}
|
|
368
|
+
// Check if the folder already exists
|
|
369
|
+
if (!fs.existsSync(folderPath)) {
|
|
370
|
+
fs.mkdirSync(folderPath, { recursive: true });
|
|
371
|
+
console.log(`✅ Created folder: ${folderName}`);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
console.log(`⚠️ Folder ${folderName} already exists.`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Generate content using enhanced generators
|
|
378
|
+
const templates = {
|
|
379
|
+
interface: (0, interfaceGenerator_1.generateInterfaceContent)(camelCaseName, fields),
|
|
380
|
+
model: generateModelContent(camelCaseName, folderName, fields),
|
|
381
|
+
controller: (0, controller_template_1.generateControllerContent)(camelCaseName, folderName, fields),
|
|
382
|
+
service: (0, service_template_1.generateServiceContent)(camelCaseName, folderName, fields, hasFile),
|
|
383
|
+
route: (0, route_template_1.generateRouteContent)(camelCaseName, folderName, fields),
|
|
384
|
+
validation: generateValidationContent(camelCaseName, fields),
|
|
385
|
+
constants: (0, constants_template_1.generateConstantsContent)(camelCaseName, folderName, fields),
|
|
386
|
+
};
|
|
387
|
+
// Create each file, skipping those specified in skipFiles
|
|
388
|
+
Object.entries(templates).forEach(([key, content]) => {
|
|
389
|
+
// Skip if this file type is in the skipFiles array
|
|
390
|
+
if (skipFiles.includes(key)) {
|
|
391
|
+
console.log(`⏭️ Skipping file: ${folderName}.${key}.ts`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const filePath = path.join(folderPath, `${folderName}.${key}.ts`);
|
|
395
|
+
fs.writeFileSync(filePath, content);
|
|
396
|
+
console.log(`✅ Created file: ${filePath}`);
|
|
397
|
+
});
|
|
398
|
+
// Add the new module to the central router file
|
|
399
|
+
updateRouterFile(folderName, camelCaseName, config);
|
|
400
|
+
// Generate documentation
|
|
401
|
+
if (docOptions.updatePostman || docOptions.updateSwagger) {
|
|
402
|
+
console.log(`\n📚 Generating documentation for ${camelCaseName}...`);
|
|
403
|
+
(0, documentationUpdater_1.updateAllDocumentation)(camelCaseName, fields, docOptions);
|
|
404
|
+
}
|
|
405
|
+
console.log(`\n🎉 Module '${camelCaseName}' created successfully!`);
|
|
406
|
+
}
|
|
407
|
+
// Update router file function
|
|
408
|
+
function updateRouterFile(folderName, camelCaseName, config) {
|
|
409
|
+
const routerFilePath = path.join(process.cwd(), config.routesFile);
|
|
410
|
+
// Check if the router file exists
|
|
411
|
+
if (!fs.existsSync(routerFilePath)) {
|
|
412
|
+
console.warn(`⚠️ Router file not found: ${routerFilePath}`);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
let routerContent = fs.readFileSync(routerFilePath, "utf-8");
|
|
417
|
+
// Check if the import already exists
|
|
418
|
+
const importStatement = `import { ${camelCaseName}Routes } from '../app/modules/${folderName}/${folderName}.route'`;
|
|
419
|
+
if (!routerContent.includes(importStatement)) {
|
|
420
|
+
// Find the last import statement
|
|
421
|
+
const lastImportIndex = routerContent.lastIndexOf("import ");
|
|
422
|
+
const lastImportEndIndex = routerContent.indexOf("\n", lastImportIndex);
|
|
423
|
+
if (lastImportIndex !== -1) {
|
|
424
|
+
// Insert the new import after the last import
|
|
425
|
+
routerContent =
|
|
426
|
+
routerContent.slice(0, lastImportEndIndex + 1) +
|
|
427
|
+
importStatement +
|
|
428
|
+
"\n" +
|
|
429
|
+
routerContent.slice(lastImportEndIndex + 1);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// No imports found, add at the beginning
|
|
433
|
+
routerContent = importStatement + "\n" + routerContent;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Check if the route registration already exists in the apiRoutes array
|
|
437
|
+
const routeRegistration = `{ path: '/${folderName}', route: ${camelCaseName}Routes }`;
|
|
438
|
+
if (!routerContent.includes(routeRegistration)) {
|
|
439
|
+
// Find the apiRoutes array initialization (after the equals sign)
|
|
440
|
+
const apiRoutesDeclaration = routerContent.indexOf("const apiRoutes");
|
|
441
|
+
if (apiRoutesDeclaration !== -1) {
|
|
442
|
+
// Find the equals sign
|
|
443
|
+
const equalsSignIndex = routerContent.indexOf("=", apiRoutesDeclaration);
|
|
444
|
+
if (equalsSignIndex !== -1) {
|
|
445
|
+
// Find the opening bracket of the array initialization
|
|
446
|
+
const arrayStartIndex = routerContent.indexOf("[", equalsSignIndex);
|
|
447
|
+
// Find the closing bracket of the array
|
|
448
|
+
const arrayEndIndex = routerContent.indexOf("]", arrayStartIndex);
|
|
449
|
+
if (arrayStartIndex !== -1 && arrayEndIndex !== -1) {
|
|
450
|
+
// Check if there are existing routes
|
|
451
|
+
const arrayContent = routerContent.substring(arrayStartIndex, arrayEndIndex);
|
|
452
|
+
const hasRoutes = arrayContent.includes("{");
|
|
453
|
+
// Insert the new route at the end of the array
|
|
454
|
+
const insertPosition = arrayEndIndex;
|
|
455
|
+
const insertText = hasRoutes
|
|
456
|
+
? `,\n ${routeRegistration}`
|
|
457
|
+
: ` ${routeRegistration}`;
|
|
458
|
+
routerContent =
|
|
459
|
+
routerContent.slice(0, insertPosition) +
|
|
460
|
+
insertText +
|
|
461
|
+
routerContent.slice(insertPosition);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
console.warn("Could not find apiRoutes array in router file");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// Write the updated content back to the file
|
|
470
|
+
fs.writeFileSync(routerFilePath, routerContent);
|
|
471
|
+
console.log(`✅ Updated router file: ${routerFilePath}`);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
console.error(`❌ Error updating router file: ${error}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Main function with enhanced CLI
|
|
478
|
+
function main() {
|
|
479
|
+
try {
|
|
480
|
+
const config = loadConfig();
|
|
481
|
+
const program = new commander_1.Command();
|
|
482
|
+
program
|
|
483
|
+
.name("leo-generate")
|
|
484
|
+
.description("Enhanced Express module generator with Mongoose models")
|
|
485
|
+
.version("1.4.1");
|
|
486
|
+
// Main module generation command
|
|
487
|
+
program
|
|
488
|
+
.command("generate")
|
|
489
|
+
.alias("g")
|
|
490
|
+
.argument("<name>", "Module name")
|
|
491
|
+
.option("-c, --config <path>", "Path to custom config file")
|
|
492
|
+
.option("--modules-dir <path>", "Path to modules directory")
|
|
493
|
+
.option("--routes-file <path>", "Path to routes file")
|
|
494
|
+
.option("--no-postman", "Skip Postman collection generation")
|
|
495
|
+
.option("--no-swagger", "Skip Swagger documentation generation")
|
|
496
|
+
.option("--postman-dir <path>", "Custom Postman output directory", "postman")
|
|
497
|
+
.option("--swagger-file <path>", "Custom Swagger file path", "swagger.json")
|
|
498
|
+
.option("--postman-api-key <string>", "Postman API Key")
|
|
499
|
+
.option("--postman-collection-id <string>", "Postman Collection ID")
|
|
500
|
+
.allowUnknownOption(true)
|
|
501
|
+
.action((name, options) => {
|
|
502
|
+
// Override config with CLI options
|
|
503
|
+
if (options.modulesDir) {
|
|
504
|
+
config.modulesDir = options.modulesDir;
|
|
505
|
+
}
|
|
506
|
+
if (options.routesFile) {
|
|
507
|
+
config.routesFile = options.routesFile;
|
|
508
|
+
}
|
|
509
|
+
if (options.postmanApiKey) {
|
|
510
|
+
config.postmanApiKey = options.postmanApiKey;
|
|
511
|
+
}
|
|
512
|
+
if (options.postmanCollectionId) {
|
|
513
|
+
config.postmanCollectionId = options.postmanCollectionId;
|
|
514
|
+
}
|
|
515
|
+
// Get field definitions from remaining arguments
|
|
516
|
+
const fieldArgs = program.args.slice(2); // Skip 'generate' and module name
|
|
517
|
+
console.log("Processing field arguments:", fieldArgs);
|
|
518
|
+
const { fields, skipFiles, hasFile } = (0, fieldParser_1.parseFieldDefinitions)(fieldArgs);
|
|
519
|
+
if (fields.length === 0) {
|
|
520
|
+
console.log("⚠️ No fields were parsed. Check your command syntax.");
|
|
521
|
+
console.log("Example: leo-generate generate User name:string email:string age:number");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
createModule(name, fields, skipFiles, config, {
|
|
525
|
+
updatePostman: options.postman !== false,
|
|
526
|
+
updateSwagger: options.swagger !== false,
|
|
527
|
+
postmanDir: options.postmanDir,
|
|
528
|
+
swaggerFile: options.swaggerFile,
|
|
529
|
+
postmanApiKey: config.postmanApiKey,
|
|
530
|
+
postmanCollectionId: config.postmanCollectionId
|
|
531
|
+
}, hasFile);
|
|
532
|
+
});
|
|
533
|
+
// Documentation update command
|
|
534
|
+
program
|
|
535
|
+
.command("update-docs")
|
|
536
|
+
.alias("docs")
|
|
537
|
+
.description("Update Postman and Swagger documentation for existing modules")
|
|
538
|
+
.option("--modules-dir <path>", "Path to modules directory", "src/app/modules")
|
|
539
|
+
.option("--no-postman", "Skip Postman collection generation")
|
|
540
|
+
.option("--no-swagger", "Skip Swagger documentation generation")
|
|
541
|
+
.option("--postman-dir <path>", "Custom Postman output directory", "postman")
|
|
542
|
+
.option("--swagger-file <path>", "Custom Swagger file path", "swagger.json")
|
|
543
|
+
.action((options) => __awaiter(this, void 0, void 0, function* () {
|
|
544
|
+
yield (0, documentationUpdater_1.updateExistingModulesDocumentation)(options.modulesDir, {
|
|
545
|
+
updatePostman: options.postman !== false,
|
|
546
|
+
updateSwagger: options.swagger !== false,
|
|
547
|
+
postmanDir: options.postmanDir,
|
|
548
|
+
swaggerFile: options.swaggerFile,
|
|
549
|
+
postmanApiKey: config.postmanApiKey,
|
|
550
|
+
postmanCollectionId: config.postmanCollectionId
|
|
551
|
+
});
|
|
552
|
+
}));
|
|
553
|
+
// Legacy support - direct module generation (backward compatibility)
|
|
554
|
+
program
|
|
555
|
+
.argument("[name]", "Module name (legacy support)")
|
|
556
|
+
.allowUnknownOption(true)
|
|
557
|
+
.action((name) => {
|
|
558
|
+
if (name && !["generate", "g", "update-docs", "docs"].includes(name)) {
|
|
559
|
+
// Legacy mode - treat first argument as module name
|
|
560
|
+
const fieldArgs = program.args.slice(1);
|
|
561
|
+
console.log("Legacy mode - Processing field arguments:", fieldArgs);
|
|
562
|
+
const { fields, skipFiles, hasFile } = (0, fieldParser_1.parseFieldDefinitions)(fieldArgs);
|
|
563
|
+
if (fields.length === 0) {
|
|
564
|
+
console.log("⚠️ No fields were parsed. Check your command syntax.");
|
|
565
|
+
console.log("Example: leo-generate User name:string email:string age:number");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
createModule(name, fields, skipFiles, config, {
|
|
569
|
+
updatePostman: true,
|
|
570
|
+
updateSwagger: true,
|
|
571
|
+
postmanDir: "postman",
|
|
572
|
+
swaggerFile: "swagger.json",
|
|
573
|
+
postmanApiKey: config.postmanApiKey,
|
|
574
|
+
postmanCollectionId: config.postmanCollectionId
|
|
575
|
+
}, hasFile);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
program.parse();
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
console.error("❌ Error executing command:", error);
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// Call the main function to start the CLI
|
|
586
|
+
main();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateConstantsContent = void 0;
|
|
4
|
+
const generateConstantsContent = (camelCaseName, folderName, fields) => {
|
|
5
|
+
// Generate filterable fields (string and enum types)
|
|
6
|
+
const filterableFields = fields.filter(f => f.type.toLowerCase() === "string" || f.type.toLowerCase() === "enum");
|
|
7
|
+
// Generate searchable fields (string types only)
|
|
8
|
+
const searchableFields = fields.filter(f => f.type.toLowerCase() === "string");
|
|
9
|
+
return `// Filterable fields for ${camelCaseName}
|
|
10
|
+
export const ${folderName}Filterables = [${filterableFields.map(f => `'${f.name}'`).join(', ')}];
|
|
11
|
+
|
|
12
|
+
// Searchable fields for ${camelCaseName}
|
|
13
|
+
export const ${folderName}SearchableFields = [${searchableFields.map(f => `'${f.name}'`).join(', ')}];
|
|
14
|
+
|
|
15
|
+
// Helper function for set comparison
|
|
16
|
+
export const isSetEqual = (setA: Set<string>, setB: Set<string>): boolean => {
|
|
17
|
+
if (setA.size !== setB.size) return false;
|
|
18
|
+
for (const item of setA) {
|
|
19
|
+
if (!setB.has(item)) return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
};`;
|
|
23
|
+
};
|
|
24
|
+
exports.generateConstantsContent = generateConstantsContent;
|