katax-cli 1.2.0 → 1.2.2
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/commands/add-endpoint.d.ts +3 -4
- package/dist/commands/add-endpoint.d.ts.map +1 -1
- package/dist/commands/add-endpoint.js +286 -141
- package/dist/commands/add-endpoint.js.map +1 -1
- package/dist/commands/generate-crud.d.ts.map +1 -1
- package/dist/commands/generate-crud.js +287 -199
- package/dist/commands/generate-crud.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +62 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +0 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/project-structure-generator.d.ts +0 -1
- package/dist/services/project-structure-generator.d.ts.map +1 -1
- package/dist/services/project-structure-generator.js +0 -9
- package/dist/services/project-structure-generator.js.map +1 -1
- package/dist/templates/generators/swagger-template.d.ts.map +1 -1
- package/dist/templates/generators/swagger-template.js +5 -0
- package/dist/templates/generators/swagger-template.js.map +1 -1
- package/dist/templates/index.d.ts +0 -2
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +0 -2
- package/dist/templates/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import ora from "ora";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
4
5
|
import { success, error, warning, gray, title, info } from "../utils/logger.js";
|
|
5
|
-
import { fileExists, writeFile, toPascalCase, toCamelCase, } from "../utils/file-utils.js";
|
|
6
|
+
import { fileExists, writeFile, ensureDir, toPascalCase, toCamelCase, } from "../utils/file-utils.js";
|
|
6
7
|
import { autoRegenerateDocs } from "./generate-docs.js";
|
|
7
8
|
export async function generateCrudCommand(resourceName, options = {}) {
|
|
8
9
|
title(`🔧 Generate CRUD: ${resourceName}`);
|
|
@@ -11,13 +12,43 @@ export async function generateCrudCommand(resourceName, options = {}) {
|
|
|
11
12
|
gray("Run this command from your project root\n");
|
|
12
13
|
process.exit(1);
|
|
13
14
|
}
|
|
14
|
-
const
|
|
15
|
+
const { methodChoice } = await inquirer.prompt([
|
|
15
16
|
{
|
|
16
|
-
type: "
|
|
17
|
-
name: "
|
|
18
|
-
message: "
|
|
19
|
-
|
|
17
|
+
type: "list",
|
|
18
|
+
name: "methodChoice",
|
|
19
|
+
message: "Which HTTP methods do you want to generate?",
|
|
20
|
+
choices: [
|
|
21
|
+
{ name: "All CRUD (GET, POST, PUT, DELETE)", value: "all" },
|
|
22
|
+
{ name: "Select specific methods", value: "select" },
|
|
23
|
+
],
|
|
24
|
+
default: "all",
|
|
20
25
|
},
|
|
26
|
+
]);
|
|
27
|
+
let selectedMethods = ["GET", "POST", "PUT", "DELETE"];
|
|
28
|
+
if (methodChoice === "select") {
|
|
29
|
+
const { methods } = await inquirer.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: "checkbox",
|
|
32
|
+
name: "methods",
|
|
33
|
+
message: "Select HTTP methods:",
|
|
34
|
+
choices: [
|
|
35
|
+
{ name: "GET (list + get by id)", value: "GET", checked: true },
|
|
36
|
+
{ name: "POST (create)", value: "POST", checked: true },
|
|
37
|
+
{ name: "PUT (full update)", value: "PUT", checked: false },
|
|
38
|
+
{ name: "PATCH (partial update)", value: "PATCH", checked: false },
|
|
39
|
+
{ name: "DELETE", value: "DELETE", checked: false },
|
|
40
|
+
],
|
|
41
|
+
validate: (input) => {
|
|
42
|
+
if (input.length === 0) {
|
|
43
|
+
return "You must select at least one method";
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
selectedMethods = methods;
|
|
50
|
+
}
|
|
51
|
+
const { addAuth } = await inquirer.prompt([
|
|
21
52
|
{
|
|
22
53
|
type: "confirm",
|
|
23
54
|
name: "addAuth",
|
|
@@ -29,7 +60,7 @@ export async function generateCrudCommand(resourceName, options = {}) {
|
|
|
29
60
|
const fields = [];
|
|
30
61
|
let addingFields = true;
|
|
31
62
|
while (addingFields) {
|
|
32
|
-
const
|
|
63
|
+
const { fieldName } = await inquirer.prompt([
|
|
33
64
|
{
|
|
34
65
|
type: "input",
|
|
35
66
|
name: "fieldName",
|
|
@@ -42,7 +73,7 @@ export async function generateCrudCommand(resourceName, options = {}) {
|
|
|
42
73
|
},
|
|
43
74
|
},
|
|
44
75
|
]);
|
|
45
|
-
if (!
|
|
76
|
+
if (!fieldName) {
|
|
46
77
|
addingFields = false;
|
|
47
78
|
break;
|
|
48
79
|
}
|
|
@@ -50,7 +81,7 @@ export async function generateCrudCommand(resourceName, options = {}) {
|
|
|
50
81
|
{
|
|
51
82
|
type: "list",
|
|
52
83
|
name: "type",
|
|
53
|
-
message: `Type for "${
|
|
84
|
+
message: `Type for "${fieldName}":`,
|
|
54
85
|
choices: ["string", "number", "boolean", "date", "email"],
|
|
55
86
|
},
|
|
56
87
|
{
|
|
@@ -61,219 +92,276 @@ export async function generateCrudCommand(resourceName, options = {}) {
|
|
|
61
92
|
},
|
|
62
93
|
]);
|
|
63
94
|
fields.push({
|
|
64
|
-
name:
|
|
95
|
+
name: fieldName,
|
|
65
96
|
type: fieldConfig.type,
|
|
66
97
|
required: fieldConfig.required,
|
|
67
98
|
rules: [],
|
|
68
99
|
});
|
|
69
|
-
gray(` ✓ Added field: ${
|
|
100
|
+
gray(` ✓ Added field: ${fieldName} (${fieldConfig.type})`);
|
|
70
101
|
}
|
|
71
102
|
if (fields.length === 0) {
|
|
72
|
-
|
|
73
|
-
|
|
103
|
+
warning("No fields defined. Adding default 'name' field.");
|
|
104
|
+
fields.push({
|
|
105
|
+
name: "name",
|
|
106
|
+
type: "string",
|
|
107
|
+
required: true,
|
|
108
|
+
rules: [],
|
|
109
|
+
});
|
|
74
110
|
}
|
|
75
|
-
const config = {
|
|
76
|
-
resourceName,
|
|
77
|
-
tableName: answers.tableName,
|
|
78
|
-
fields,
|
|
79
|
-
addAuth: answers.addAuth,
|
|
80
|
-
};
|
|
81
111
|
const spinner = ora("Generating CRUD endpoints...").start();
|
|
82
112
|
try {
|
|
83
|
-
await generateCRUDFiles(
|
|
113
|
+
await generateCRUDFiles(resourceName, selectedMethods, fields, addAuth);
|
|
84
114
|
spinner.succeed("CRUD endpoints generated");
|
|
85
115
|
success(`\n✨ CRUD for "${resourceName}" created successfully!\n`);
|
|
116
|
+
info("Generated files:");
|
|
117
|
+
gray(` src/api/${resourceName.toLowerCase()}/${resourceName.toLowerCase()}.validator.ts`);
|
|
118
|
+
gray(` src/api/${resourceName.toLowerCase()}/${resourceName.toLowerCase()}.controller.ts`);
|
|
119
|
+
gray(` src/api/${resourceName.toLowerCase()}/${resourceName.toLowerCase()}.handler.ts`);
|
|
120
|
+
gray(` src/api/${resourceName.toLowerCase()}/${resourceName.toLowerCase()}.routes.ts`);
|
|
121
|
+
gray(` Updated: src/api/routes.ts\n`);
|
|
86
122
|
info("Generated endpoints:");
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
123
|
+
selectedMethods.forEach((method) => {
|
|
124
|
+
if (method === "GET") {
|
|
125
|
+
gray(` GET /api/${resourceName.toLowerCase()} - List all`);
|
|
126
|
+
gray(` GET /api/${resourceName.toLowerCase()}/:id - Get by ID`);
|
|
127
|
+
}
|
|
128
|
+
else if (method === "POST") {
|
|
129
|
+
gray(` POST /api/${resourceName.toLowerCase()} - Create`);
|
|
130
|
+
}
|
|
131
|
+
else if (method === "PUT" || method === "PATCH") {
|
|
132
|
+
gray(` ${method.padEnd(6)} /api/${resourceName.toLowerCase()}/:id - Update`);
|
|
133
|
+
}
|
|
134
|
+
else if (method === "DELETE") {
|
|
135
|
+
gray(` DELETE /api/${resourceName.toLowerCase()}/:id - Delete`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
console.log();
|
|
139
|
+
if (addAuth) {
|
|
140
|
+
warning("Remember to implement authentication middleware!\n");
|
|
94
141
|
}
|
|
95
142
|
await autoRegenerateDocs(process.cwd());
|
|
96
143
|
}
|
|
97
144
|
catch (err) {
|
|
98
145
|
spinner.fail("Failed to generate CRUD");
|
|
99
146
|
error(err instanceof Error ? err.message : "Unknown error");
|
|
147
|
+
console.error(err);
|
|
100
148
|
process.exit(1);
|
|
101
149
|
}
|
|
102
150
|
}
|
|
103
|
-
async function generateCRUDFiles(
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
await writeFile(path.join(basePath, `${
|
|
110
|
-
|
|
111
|
-
await writeFile(path.join(basePath, `${
|
|
112
|
-
|
|
113
|
-
await updateMainRouter(
|
|
114
|
-
name: resourceName,
|
|
115
|
-
method: "GET",
|
|
116
|
-
path: `/api/${resourceName.toLowerCase()}`,
|
|
117
|
-
addValidation: true,
|
|
118
|
-
fields,
|
|
119
|
-
addAsyncValidators: false,
|
|
120
|
-
});
|
|
151
|
+
async function generateCRUDFiles(name, methods, fields, addAuth) {
|
|
152
|
+
const basePath = path.join(process.cwd(), "src", "api", name.toLowerCase());
|
|
153
|
+
await ensureDir(basePath);
|
|
154
|
+
const pascalName = toPascalCase(name);
|
|
155
|
+
const camelName = toCamelCase(name);
|
|
156
|
+
const lowerName = name.toLowerCase();
|
|
157
|
+
await writeFile(path.join(basePath, `${lowerName}.validator.ts`), generateValidator(pascalName, camelName, methods, fields));
|
|
158
|
+
await writeFile(path.join(basePath, `${lowerName}.controller.ts`), generateController(pascalName, camelName, methods, fields));
|
|
159
|
+
await writeFile(path.join(basePath, `${lowerName}.handler.ts`), generateHandler(pascalName, camelName, lowerName, methods));
|
|
160
|
+
await writeFile(path.join(basePath, `${lowerName}.routes.ts`), generateRoutes(pascalName, camelName, lowerName, methods, addAuth));
|
|
161
|
+
await updateMainRouter(name);
|
|
121
162
|
}
|
|
122
|
-
function
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
function generateValidator(pascalName, camelName, methods, fields) {
|
|
164
|
+
const lines = [
|
|
165
|
+
"import { k, kataxInfer } from 'katax-core';",
|
|
166
|
+
"import type { ValidationResult } from '../../shared/api.utils.js';",
|
|
167
|
+
"",
|
|
168
|
+
"// ==================== SCHEMAS ====================",
|
|
169
|
+
"",
|
|
170
|
+
];
|
|
171
|
+
if (methods.some((m) => ["GET", "PUT", "PATCH", "DELETE"].includes(m))) {
|
|
172
|
+
lines.push("/**", ` * Schema for ${camelName} ID params`, " */", `export const ${camelName}IdSchema = k.object({`, " id: k.string().minLength(1)", "});", "", `export type ${pascalName}IdParams = kataxInfer<typeof ${camelName}IdSchema>;`, "");
|
|
173
|
+
}
|
|
174
|
+
if (methods.includes("GET")) {
|
|
175
|
+
lines.push("/**", ` * Schema for ${camelName} query params (list)`, " */", `export const ${camelName}QuerySchema = k.object({`, " page: k.number().optional(),", " limit: k.number().optional(),", " search: k.string().optional()", "});", "", `export type ${pascalName}Query = kataxInfer<typeof ${camelName}QuerySchema>;`, "");
|
|
176
|
+
}
|
|
177
|
+
if (methods.includes("POST")) {
|
|
178
|
+
lines.push("/**", ` * Schema for creating ${camelName}`, " */", `export const create${pascalName}Schema = k.object({`);
|
|
179
|
+
fields.forEach((field, i) => {
|
|
180
|
+
let schema = field.type === "email" ? "k.string().email()" : `k.${field.type}()`;
|
|
181
|
+
if (!field.required)
|
|
182
|
+
schema += ".optional()";
|
|
183
|
+
lines.push(` ${field.name}: ${schema}${i < fields.length - 1 ? "," : ""}`);
|
|
184
|
+
});
|
|
185
|
+
lines.push("});", "", `export type Create${pascalName}Data = kataxInfer<typeof create${pascalName}Schema>;`, "");
|
|
186
|
+
}
|
|
187
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m))) {
|
|
188
|
+
lines.push("/**", ` * Schema for updating ${camelName}`, " */", `export const update${pascalName}Schema = k.object({`);
|
|
189
|
+
fields.forEach((field, i) => {
|
|
190
|
+
const schema = field.type === "email" ? "k.string().email().optional()" : `k.${field.type}().optional()`;
|
|
191
|
+
lines.push(` ${field.name}: ${schema}${i < fields.length - 1 ? "," : ""}`);
|
|
192
|
+
});
|
|
193
|
+
lines.push("});", "", `export type Update${pascalName}Data = kataxInfer<typeof update${pascalName}Schema>;`, "");
|
|
194
|
+
}
|
|
195
|
+
lines.push("// ==================== VALIDATORS ====================", "");
|
|
196
|
+
if (methods.some((m) => ["GET", "PUT", "PATCH", "DELETE"].includes(m))) {
|
|
197
|
+
lines.push(`export async function validate${pascalName}Id(data: unknown): Promise<ValidationResult<${pascalName}IdParams>> {`, ` const result = ${camelName}IdSchema.safeParse(data);`, " if (!result.success) {", " return {", " isValid: false,", " errors: result.issues.map(i => ({ field: i.path.join('.'), message: i.message }))", " };", " }", " return { isValid: true, data: result.data };", "}", "");
|
|
198
|
+
}
|
|
199
|
+
if (methods.includes("GET")) {
|
|
200
|
+
lines.push(`export async function validate${pascalName}Query(data: unknown): Promise<ValidationResult<${pascalName}Query>> {`, ` const result = ${camelName}QuerySchema.safeParse(data);`, " if (!result.success) {", " return {", " isValid: false,", " errors: result.issues.map(i => ({ field: i.path.join('.'), message: i.message }))", " };", " }", " return { isValid: true, data: result.data };", "}", "");
|
|
201
|
+
}
|
|
202
|
+
if (methods.includes("POST")) {
|
|
203
|
+
lines.push(`export async function validate${pascalName}Create(data: unknown): Promise<ValidationResult<Create${pascalName}Data>> {`, ` const result = create${pascalName}Schema.safeParse(data);`, " if (!result.success) {", " return {", " isValid: false,", " errors: result.issues.map(i => ({ field: i.path.join('.'), message: i.message }))", " };", " }", " return { isValid: true, data: result.data };", "}", "");
|
|
204
|
+
}
|
|
205
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m))) {
|
|
206
|
+
lines.push(`export async function validate${pascalName}Update(data: unknown): Promise<ValidationResult<Update${pascalName}Data>> {`, ` const result = update${pascalName}Schema.safeParse(data);`, " if (!result.success) {", " return {", " isValid: false,", " errors: result.issues.map(i => ({ field: i.path.join('.'), message: i.message }))", " };", " }", " return { isValid: true, data: result.data };", "}", "");
|
|
207
|
+
}
|
|
208
|
+
return lines.join("\n");
|
|
149
209
|
}
|
|
150
|
-
function
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
// TODO: Implement database
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return createErrorResult('Failed to
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// Create ${camelName}
|
|
188
|
-
export async function create${pascalName}(data: Create${pascalName}Data): Promise<ControllerResult<any>> {
|
|
189
|
-
try {
|
|
190
|
-
// TODO: Implement database insertion
|
|
191
|
-
const new${pascalName} = { id: Math.floor(Math.random() * 1000), ...data, createdAt: new Date().toISOString() };
|
|
192
|
-
return createSuccessResult('${pascalName} created', new${pascalName}, undefined, 201);
|
|
193
|
-
} catch (error) {
|
|
194
|
-
return createErrorResult('Failed to create ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Update ${camelName}
|
|
199
|
-
export async function update${pascalName}(id: string, data: Update${pascalName}Data): Promise<ControllerResult<any>> {
|
|
200
|
-
try {
|
|
201
|
-
// TODO: Implement database update
|
|
202
|
-
const updated${pascalName} = { id: parseInt(id), ...data, updatedAt: new Date().toISOString() };
|
|
203
|
-
return createSuccessResult('${pascalName} updated', updated${pascalName});
|
|
204
|
-
} catch (error) {
|
|
205
|
-
return createErrorResult('Failed to update ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Delete ${camelName}
|
|
210
|
-
export async function delete${pascalName}(id: string): Promise<ControllerResult<void>> {
|
|
211
|
-
try {
|
|
212
|
-
// TODO: Implement database deletion
|
|
213
|
-
return createSuccessResult('${pascalName} deleted');
|
|
214
|
-
} catch (error) {
|
|
215
|
-
return createErrorResult('Failed to delete ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
`;
|
|
210
|
+
function generateController(pascalName, camelName, methods, fields) {
|
|
211
|
+
const lines = [
|
|
212
|
+
"import { ControllerResult, createSuccessResult, createErrorResult } from '../../shared/api.utils.js';",
|
|
213
|
+
"import { logger } from '../../shared/logger.utils.js';",
|
|
214
|
+
];
|
|
215
|
+
const types = [];
|
|
216
|
+
if (methods.includes("GET"))
|
|
217
|
+
types.push(`${pascalName}Query`, `${pascalName}IdParams`);
|
|
218
|
+
if (methods.includes("POST"))
|
|
219
|
+
types.push(`Create${pascalName}Data`);
|
|
220
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m))) {
|
|
221
|
+
types.push(`Update${pascalName}Data`);
|
|
222
|
+
if (!types.includes(`${pascalName}IdParams`))
|
|
223
|
+
types.push(`${pascalName}IdParams`);
|
|
224
|
+
}
|
|
225
|
+
if (methods.includes("DELETE") && !types.includes(`${pascalName}IdParams`)) {
|
|
226
|
+
types.push(`${pascalName}IdParams`);
|
|
227
|
+
}
|
|
228
|
+
if (types.length > 0) {
|
|
229
|
+
lines.push(`import { ${types.join(", ")} } from './${camelName}.validator.js';`);
|
|
230
|
+
}
|
|
231
|
+
lines.push("", "// ==================== CONTROLLERS ====================", "");
|
|
232
|
+
if (methods.includes("GET")) {
|
|
233
|
+
lines.push("/**", ` * List all ${camelName}s`, " */", `export async function list${pascalName}s(query: ${pascalName}Query): Promise<ControllerResult<any[]>> {`, " try {", ` logger.debug({ query }, 'Listing ${camelName}s');`, "", " // TODO: Implement database query", ` const mock${pascalName}s = [`, ` { id: '1', ${fields.length > 0 ? `${fields[0].name}: 'Sample 1',` : ""} createdAt: new Date().toISOString() },`, ` { id: '2', ${fields.length > 0 ? `${fields[0].name}: 'Sample 2',` : ""} createdAt: new Date().toISOString() }`, " ];", "", ` return createSuccessResult('${pascalName}s retrieved', mock${pascalName}s);`, " } catch (error) {", ` logger.error({ err: error }, 'Error listing ${camelName}s');`, ` return createErrorResult('Failed to list ${camelName}s', error instanceof Error ? error.message : 'Unknown error', 500);`, " }", "}", "");
|
|
234
|
+
lines.push("/**", ` * Get ${camelName} by ID`, " */", `export async function get${pascalName}(params: ${pascalName}IdParams): Promise<ControllerResult<any>> {`, " try {", ` logger.debug({ id: params.id }, 'Getting ${camelName}');`, "", " // TODO: Implement database query", ` const mock${pascalName} = { id: params.id, ${fields.length > 0 ? `${fields[0].name}: 'Sample',` : ""} createdAt: new Date().toISOString() };`, "", ` return createSuccessResult('${pascalName} retrieved', mock${pascalName});`, " } catch (error) {", ` logger.error({ err: error }, 'Error getting ${camelName}');`, ` return createErrorResult('Failed to get ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);`, " }", "}", "");
|
|
235
|
+
}
|
|
236
|
+
if (methods.includes("POST")) {
|
|
237
|
+
lines.push("/**", ` * Create ${camelName}`, " */", `export async function create${pascalName}(data: Create${pascalName}Data): Promise<ControllerResult<any>> {`, " try {", ` logger.debug({ data }, 'Creating ${camelName}');`, "", " // TODO: Implement database insertion", ` const new${pascalName} = { id: Math.random().toString(36).substr(2, 9), ...data, createdAt: new Date().toISOString() };`, "", ` return createSuccessResult('${pascalName} created', new${pascalName}, undefined, 201);`, " } catch (error) {", ` logger.error({ err: error }, 'Error creating ${camelName}');`, ` return createErrorResult('Failed to create ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);`, " }", "}", "");
|
|
238
|
+
}
|
|
239
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m))) {
|
|
240
|
+
lines.push("/**", ` * Update ${camelName}`, " */", `export async function update${pascalName}(params: ${pascalName}IdParams, data: Update${pascalName}Data): Promise<ControllerResult<any>> {`, " try {", ` logger.debug({ id: params.id, data }, 'Updating ${camelName}');`, "", " // TODO: Implement database update", ` const updated${pascalName} = { id: params.id, ...data, updatedAt: new Date().toISOString() };`, "", ` return createSuccessResult('${pascalName} updated', updated${pascalName});`, " } catch (error) {", ` logger.error({ err: error }, 'Error updating ${camelName}');`, ` return createErrorResult('Failed to update ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);`, " }", "}", "");
|
|
241
|
+
}
|
|
242
|
+
if (methods.includes("DELETE")) {
|
|
243
|
+
lines.push("/**", ` * Delete ${camelName}`, " */", `export async function delete${pascalName}(params: ${pascalName}IdParams): Promise<ControllerResult<void>> {`, " try {", ` logger.debug({ id: params.id }, 'Deleting ${camelName}');`, "", " // TODO: Implement database deletion", "", ` return createSuccessResult('${pascalName} deleted');`, " } catch (error) {", ` logger.error({ err: error }, 'Error deleting ${camelName}');`, ` return createErrorResult('Failed to delete ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);`, " }", "}", "");
|
|
244
|
+
}
|
|
245
|
+
return lines.join("\n");
|
|
219
246
|
}
|
|
220
|
-
function
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
${
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
await sendResponse(req, res, () =>
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
247
|
+
function generateHandler(pascalName, camelName, lowerName, methods) {
|
|
248
|
+
const lines = [
|
|
249
|
+
"import { Request, Response } from 'express';",
|
|
250
|
+
"import { sendResponse } from '../../shared/api.utils.js';",
|
|
251
|
+
];
|
|
252
|
+
const controllerImports = [];
|
|
253
|
+
if (methods.includes("GET"))
|
|
254
|
+
controllerImports.push(`list${pascalName}s`, `get${pascalName}`);
|
|
255
|
+
if (methods.includes("POST"))
|
|
256
|
+
controllerImports.push(`create${pascalName}`);
|
|
257
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m)))
|
|
258
|
+
controllerImports.push(`update${pascalName}`);
|
|
259
|
+
if (methods.includes("DELETE"))
|
|
260
|
+
controllerImports.push(`delete${pascalName}`);
|
|
261
|
+
lines.push(`import { ${controllerImports.join(", ")} } from './${lowerName}.controller.js';`);
|
|
262
|
+
const validatorImports = [];
|
|
263
|
+
if (methods.includes("GET"))
|
|
264
|
+
validatorImports.push(`validate${pascalName}Query`, `validate${pascalName}Id`);
|
|
265
|
+
if (methods.includes("POST"))
|
|
266
|
+
validatorImports.push(`validate${pascalName}Create`);
|
|
267
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m))) {
|
|
268
|
+
validatorImports.push(`validate${pascalName}Update`);
|
|
269
|
+
if (!validatorImports.includes(`validate${pascalName}Id`))
|
|
270
|
+
validatorImports.push(`validate${pascalName}Id`);
|
|
271
|
+
}
|
|
272
|
+
if (methods.includes("DELETE") && !validatorImports.includes(`validate${pascalName}Id`)) {
|
|
273
|
+
validatorImports.push(`validate${pascalName}Id`);
|
|
274
|
+
}
|
|
275
|
+
lines.push(`import { ${validatorImports.join(", ")} } from './${lowerName}.validator.js';`);
|
|
276
|
+
lines.push("", "// ==================== HANDLERS ====================", "");
|
|
277
|
+
if (methods.includes("GET")) {
|
|
278
|
+
lines.push("/**", ` * Handler for GET /api/${lowerName}`, " */", `export async function list${pascalName}sHandler(req: Request, res: Response): Promise<void> {`, " await sendResponse(", " req,", " res,", ` () => validate${pascalName}Query(req.query),`, ` (validData) => list${pascalName}s(validData)`, " );", "}", "");
|
|
279
|
+
lines.push("/**", ` * Handler for GET /api/${lowerName}/:id`, " */", `export async function get${pascalName}Handler(req: Request, res: Response): Promise<void> {`, " await sendResponse(", " req,", " res,", ` () => validate${pascalName}Id(req.params),`, ` (validData) => get${pascalName}(validData)`, " );", "}", "");
|
|
280
|
+
}
|
|
281
|
+
if (methods.includes("POST")) {
|
|
282
|
+
lines.push("/**", ` * Handler for POST /api/${lowerName}`, " */", `export async function create${pascalName}Handler(req: Request, res: Response): Promise<void> {`, " await sendResponse(", " req,", " res,", ` () => validate${pascalName}Create(req.body),`, ` (validData) => create${pascalName}(validData)`, " );", "}", "");
|
|
283
|
+
}
|
|
284
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m))) {
|
|
285
|
+
lines.push("/**", ` * Handler for PUT/PATCH /api/${lowerName}/:id`, " */", `export async function update${pascalName}Handler(req: Request, res: Response): Promise<void> {`, " // First validate params", ` const paramsResult = await validate${pascalName}Id(req.params);`, " if (!paramsResult.isValid) {", " res.status(400).json({", " success: false,", " message: 'Invalid params',", " errors: paramsResult.errors", " });", " return;", " }", "", " // Then validate body and execute", " await sendResponse(", " req,", " res,", ` () => validate${pascalName}Update(req.body),`, ` (validData) => update${pascalName}(paramsResult.data!, validData)`, " );", "}", "");
|
|
286
|
+
}
|
|
287
|
+
if (methods.includes("DELETE")) {
|
|
288
|
+
lines.push("/**", ` * Handler for DELETE /api/${lowerName}/:id`, " */", `export async function delete${pascalName}Handler(req: Request, res: Response): Promise<void> {`, " await sendResponse(", " req,", " res,", ` () => validate${pascalName}Id(req.params),`, ` (validData) => delete${pascalName}(validData)`, " );", "}", "");
|
|
289
|
+
}
|
|
290
|
+
return lines.join("\n");
|
|
291
|
+
}
|
|
292
|
+
function generateRoutes(pascalName, camelName, lowerName, methods, addAuth) {
|
|
293
|
+
const lines = [
|
|
294
|
+
"import { Router } from 'express';",
|
|
295
|
+
];
|
|
296
|
+
const handlerImports = [];
|
|
297
|
+
if (methods.includes("GET"))
|
|
298
|
+
handlerImports.push(`list${pascalName}sHandler`, `get${pascalName}Handler`);
|
|
299
|
+
if (methods.includes("POST"))
|
|
300
|
+
handlerImports.push(`create${pascalName}Handler`);
|
|
301
|
+
if (methods.some((m) => ["PUT", "PATCH"].includes(m)))
|
|
302
|
+
handlerImports.push(`update${pascalName}Handler`);
|
|
303
|
+
if (methods.includes("DELETE"))
|
|
304
|
+
handlerImports.push(`delete${pascalName}Handler`);
|
|
305
|
+
lines.push(`import { ${handlerImports.join(", ")} } from './${lowerName}.handler.js';`);
|
|
306
|
+
if (addAuth) {
|
|
307
|
+
lines.push("// import { requireAuth } from '../../middleware/auth.middleware.js';");
|
|
308
|
+
}
|
|
309
|
+
lines.push("", "const router = Router();", "", "// ==================== ROUTES ====================", "");
|
|
310
|
+
const authMiddleware = addAuth ? "/* requireAuth, */ " : "";
|
|
311
|
+
if (methods.includes("GET")) {
|
|
312
|
+
lines.push("/**", ` * @route GET /api/${lowerName}`, ` * @desc List all ${lowerName}s`, " */", `router.get('/', ${authMiddleware}list${pascalName}sHandler);`, "", "/**", ` * @route GET /api/${lowerName}/:id`, ` * @desc Get ${lowerName} by ID`, " */", `router.get('/:id', ${authMiddleware}get${pascalName}Handler);`, "");
|
|
313
|
+
}
|
|
314
|
+
if (methods.includes("POST")) {
|
|
315
|
+
lines.push("/**", ` * @route POST /api/${lowerName}`, ` * @desc Create new ${lowerName}`, " */", `router.post('/', ${authMiddleware}create${pascalName}Handler);`, "");
|
|
316
|
+
}
|
|
317
|
+
if (methods.includes("PUT")) {
|
|
318
|
+
lines.push("/**", ` * @route PUT /api/${lowerName}/:id`, ` * @desc Update ${lowerName}`, " */", `router.put('/:id', ${authMiddleware}update${pascalName}Handler);`, "");
|
|
319
|
+
}
|
|
320
|
+
if (methods.includes("PATCH")) {
|
|
321
|
+
lines.push("/**", ` * @route PATCH /api/${lowerName}/:id`, ` * @desc Partial update ${lowerName}`, " */", `router.patch('/:id', ${authMiddleware}update${pascalName}Handler);`, "");
|
|
322
|
+
}
|
|
323
|
+
if (methods.includes("DELETE")) {
|
|
324
|
+
lines.push("/**", ` * @route DELETE /api/${lowerName}/:id`, ` * @desc Delete ${lowerName}`, " */", `router.delete('/:id', ${authMiddleware}delete${pascalName}Handler);`, "");
|
|
325
|
+
}
|
|
326
|
+
lines.push("export default router;");
|
|
327
|
+
return lines.join("\n");
|
|
328
|
+
}
|
|
329
|
+
async function updateMainRouter(name) {
|
|
330
|
+
const routerPath = path.join(process.cwd(), "src", "api", "routes.ts");
|
|
331
|
+
const lowerName = name.toLowerCase();
|
|
332
|
+
try {
|
|
333
|
+
let content = await fs.readFile(routerPath, "utf-8");
|
|
334
|
+
const importLine = `import ${lowerName}Router from './${lowerName}/${lowerName}.routes.js';`;
|
|
335
|
+
if (!content.includes(importLine)) {
|
|
336
|
+
const importRegex = /^import\s+.+from\s+.+;$/gm;
|
|
337
|
+
let lastImportMatch = null;
|
|
338
|
+
let match;
|
|
339
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
340
|
+
lastImportMatch = match;
|
|
341
|
+
}
|
|
342
|
+
if (lastImportMatch) {
|
|
343
|
+
const insertPos = lastImportMatch.index + lastImportMatch[0].length;
|
|
344
|
+
content =
|
|
345
|
+
content.slice(0, insertPos) +
|
|
346
|
+
"\n" +
|
|
347
|
+
importLine +
|
|
348
|
+
content.slice(insertPos);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const useLine = `router.use('/${lowerName}', ${lowerName}Router);`;
|
|
352
|
+
if (!content.includes(useLine)) {
|
|
353
|
+
const exportIndex = content.indexOf("export default router");
|
|
354
|
+
if (exportIndex !== -1) {
|
|
355
|
+
content =
|
|
356
|
+
content.slice(0, exportIndex) +
|
|
357
|
+
useLine +
|
|
358
|
+
"\n\n" +
|
|
359
|
+
content.slice(exportIndex);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
await fs.writeFile(routerPath, content, "utf-8");
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
}
|
|
278
366
|
}
|
|
279
367
|
//# sourceMappingURL=generate-crud.js.map
|