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.
@@ -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 answers = await inquirer.prompt([
15
+ const { methodChoice } = await inquirer.prompt([
15
16
  {
16
- type: "input",
17
- name: "tableName",
18
- message: "Database table name:",
19
- default: `${resourceName.toLowerCase()}s`,
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 fieldAnswers = await inquirer.prompt([
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 (!fieldAnswers.fieldName) {
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 "${fieldAnswers.fieldName}":`,
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: fieldAnswers.fieldName,
95
+ name: fieldName,
65
96
  type: fieldConfig.type,
66
97
  required: fieldConfig.required,
67
98
  rules: [],
68
99
  });
69
- gray(` ✓ Added field: ${fieldAnswers.fieldName} (${fieldConfig.type})`);
100
+ gray(` ✓ Added field: ${fieldName} (${fieldConfig.type})`);
70
101
  }
71
102
  if (fields.length === 0) {
72
- error("At least one field is required to generate CRUD");
73
- process.exit(1);
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(config);
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
- gray(` GET /api/${resourceName.toLowerCase()} - List all`);
88
- gray(` GET /api/${resourceName.toLowerCase()}/:id - Get one`);
89
- gray(` POST /api/${resourceName.toLowerCase()} - Create`);
90
- gray(` PUT /api/${resourceName.toLowerCase()}/:id - Update`);
91
- gray(` DELETE /api/${resourceName.toLowerCase()}/:id - Delete\n`);
92
- if (config.addAuth) {
93
- warning("Remember to implement authentication middleware!");
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(config) {
104
- const { resourceName, fields } = config;
105
- const basePath = path.join(process.cwd(), "src", "api", resourceName.toLowerCase());
106
- const validatorContent = generateCRUDValidator(config);
107
- await writeFile(path.join(basePath, `${resourceName.toLowerCase()}.validator.ts`), validatorContent);
108
- const controllerContent = generateCRUDController(config);
109
- await writeFile(path.join(basePath, `${resourceName.toLowerCase()}.controller.ts`), controllerContent);
110
- const routesContent = generateCRUDRoutes(config);
111
- await writeFile(path.join(basePath, `${resourceName.toLowerCase()}.routes.ts`), routesContent);
112
- const { updateMainRouter } = await import("../generators/router-updater.js");
113
- await updateMainRouter(resourceName, {
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 generateCRUDValidator(config) {
123
- const { resourceName, fields } = config;
124
- const pascalName = toPascalCase(resourceName);
125
- const camelName = toCamelCase(resourceName);
126
- let content = `import { k, kataxInfer } from 'katax-core';\n\n`;
127
- content += `// ==================== SCHEMAS ====================\n\n`;
128
- content += `export const create${pascalName}Schema = k.object({\n`;
129
- fields.forEach((field, index) => {
130
- const isLast = index === fields.length - 1;
131
- let fieldSchema = field.type === "email" ? "k.string().email()" : `k.${field.type}()`;
132
- if (!field.required)
133
- fieldSchema += ".optional()";
134
- content += ` ${field.name}: ${fieldSchema}${isLast ? "" : ","}\n`;
135
- });
136
- content += `});\n\n`;
137
- content += `export const update${pascalName}Schema = k.object({\n`;
138
- fields.forEach((field, index) => {
139
- const isLast = index === fields.length - 1;
140
- const fieldSchema = field.type === "email"
141
- ? "k.string().email().optional()"
142
- : `k.${field.type}().optional()`;
143
- content += ` ${field.name}: ${fieldSchema}${isLast ? "" : ","}\n`;
144
- });
145
- content += `});\n\n`;
146
- content += `export type Create${pascalName}Data = kataxInfer<typeof create${pascalName}Schema>;\n`;
147
- content += `export type Update${pascalName}Data = kataxInfer<typeof update${pascalName}Schema>;\n`;
148
- return content;
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 generateCRUDController(config) {
151
- const { resourceName, tableName } = config;
152
- const pascalName = toPascalCase(resourceName);
153
- const camelName = toCamelCase(resourceName);
154
- return `import { Create${pascalName}Data, Update${pascalName}Data } from './${resourceName.toLowerCase()}.validator.js';
155
- import { ControllerResult, createSuccessResult, createErrorResult } from '../../shared/api.utils.js';
156
-
157
- // List all ${camelName}s
158
- export async function list${pascalName}s(): Promise<ControllerResult<any[]>> {
159
- try {
160
- // TODO: Implement database query
161
- // const result = await pool.query('SELECT * FROM ${tableName}');
162
-
163
- const mock${pascalName}s = [
164
- { id: 1, name: 'Sample ${pascalName} 1', createdAt: new Date().toISOString() },
165
- { id: 2, name: 'Sample ${pascalName} 2', createdAt: new Date().toISOString() }
166
- ];
167
-
168
- return createSuccessResult('${pascalName}s retrieved', mock${pascalName}s);
169
- } catch (error) {
170
- return createErrorResult('Failed to list ${camelName}s', error instanceof Error ? error.message : 'Unknown error', 500);
171
- }
172
- }
173
-
174
- // Get single ${camelName}
175
- export async function get${pascalName}(id: string): Promise<ControllerResult<any>> {
176
- try {
177
- // TODO: Implement database query
178
- // const result = await pool.query('SELECT * FROM ${tableName} WHERE id = $1', [id]);
179
-
180
- const mock${pascalName} = { id: parseInt(id), name: 'Sample ${pascalName}', createdAt: new Date().toISOString() };
181
- return createSuccessResult('${pascalName} retrieved', mock${pascalName});
182
- } catch (error) {
183
- return createErrorResult('Failed to get ${camelName}', error instanceof Error ? error.message : 'Unknown error', 500);
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 generateCRUDRoutes(config) {
221
- const { resourceName, addAuth } = config;
222
- const pascalName = toPascalCase(resourceName);
223
- return `import { Router, Request, Response } from 'express';
224
- import {
225
- list${pascalName}s,
226
- get${pascalName},
227
- create${pascalName},
228
- update${pascalName},
229
- delete${pascalName}
230
- } from './${resourceName.toLowerCase()}.controller.js';
231
- import { create${pascalName}Schema, update${pascalName}Schema } from './${resourceName.toLowerCase()}.validator.js';
232
- import { sendResponse } from '../../shared/api.utils.js';
233
- ${addAuth ? "// import { requireAuth } from '../auth/auth.middleware.js';\n" : ""}
234
- const router = Router();
235
-
236
- // List all
237
- router.get('/', ${addAuth ? "/* requireAuth, */ " : ""}async (req: Request, res: Response) => {
238
- try {
239
- const result = await list${pascalName}s();
240
- res.status(result.statusCode || 200).json(result);
241
- } catch (error) {
242
- res.status(500).json({ success: false, message: 'Internal error', error });
243
- }
244
- });
245
-
246
- // Get one
247
- router.get('/:id', ${addAuth ? "/* requireAuth, */ " : ""}async (req: Request, res: Response) => {
248
- try {
249
- const result = await get${pascalName}(req.params.id);
250
- res.status(result.statusCode || 200).json(result);
251
- } catch (error) {
252
- res.status(500).json({ success: false, message: 'Internal error', error });
253
- }
254
- });
255
-
256
- // Create
257
- router.post('/', ${addAuth ? "/* requireAuth, */ " : ""}async (req: Request, res: Response) => {
258
- await sendResponse(req, res, () => create${pascalName}Schema.safeParse(req.body), (data) => create${pascalName}(data));
259
- });
260
-
261
- // Update
262
- router.put('/:id', ${addAuth ? "/* requireAuth, */ " : ""}async (req: Request, res: Response) => {
263
- await sendResponse(req, res, () => update${pascalName}Schema.safeParse(req.body), (data) => update${pascalName}(req.params.id, data));
264
- });
265
-
266
- // Delete
267
- router.delete('/:id', ${addAuth ? "/* requireAuth, */ " : ""}async (req: Request, res: Response) => {
268
- try {
269
- const result = await delete${pascalName}(req.params.id);
270
- res.status(result.statusCode || 200).json(result);
271
- } catch (error) {
272
- res.status(500).json({ success: false, message: 'Internal error', error });
273
- }
274
- });
275
-
276
- export default router;
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