katax-cli 1.1.3 → 1.2.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/dist/commands/add-endpoint.d.ts.map +1 -1
- package/dist/commands/add-endpoint.js +121 -97
- package/dist/commands/add-endpoint.js.map +1 -1
- package/dist/commands/deploy.d.ts +3 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +287 -153
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/generate-crud.d.ts.map +1 -1
- package/dist/commands/generate-crud.js +60 -56
- package/dist/commands/generate-crud.js.map +1 -1
- package/dist/commands/generate-docs.d.ts +8 -0
- package/dist/commands/generate-docs.d.ts.map +1 -0
- package/dist/commands/generate-docs.js +159 -0
- package/dist/commands/generate-docs.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +506 -244
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +83 -69
- package/dist/index.js.map +1 -1
- package/dist/services/openapi-generator.service.d.ts +37 -0
- package/dist/services/openapi-generator.service.d.ts.map +1 -0
- package/dist/services/openapi-generator.service.js +333 -0
- package/dist/services/openapi-generator.service.js.map +1 -0
- package/dist/services/project-structure-generator.d.ts +1 -1
- package/dist/services/project-structure-generator.d.ts.map +1 -1
- package/dist/services/project-structure-generator.js +443 -445
- package/dist/services/project-structure-generator.js.map +1 -1
- package/dist/templates/generators/swagger-template.d.ts +3 -0
- package/dist/templates/generators/swagger-template.d.ts.map +1 -0
- package/dist/templates/generators/swagger-template.js +117 -0
- package/dist/templates/generators/swagger-template.js.map +1 -0
- package/dist/types/index.d.ts +19 -10
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1,221 +1,284 @@
|
|
|
1
|
-
import inquirer from
|
|
2
|
-
import ora from
|
|
3
|
-
import path from
|
|
4
|
-
import crypto from
|
|
5
|
-
import { execa } from
|
|
6
|
-
import { success, error, gray, title, info } from
|
|
7
|
-
import { directoryExists, ensureDir, writeFile } from
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import { execa } from "execa";
|
|
6
|
+
import { success, error, gray, title, info } from "../utils/logger.js";
|
|
7
|
+
import { directoryExists, ensureDir, writeFile, } from "../utils/file-utils.js";
|
|
8
|
+
import { generateSwaggerSetup } from "../templates/generators/swagger-template.js";
|
|
8
9
|
export async function initCommand(projectName, options = {}) {
|
|
9
|
-
title(
|
|
10
|
-
let finalProjectName = projectName ||
|
|
10
|
+
title("🚀 Katax CLI - Initialize API Project");
|
|
11
|
+
let finalProjectName = projectName || "";
|
|
11
12
|
if (!finalProjectName) {
|
|
12
13
|
const answer = await inquirer.prompt([
|
|
13
14
|
{
|
|
14
|
-
type:
|
|
15
|
-
name:
|
|
16
|
-
message:
|
|
17
|
-
default:
|
|
15
|
+
type: "input",
|
|
16
|
+
name: "projectName",
|
|
17
|
+
message: "Project name:",
|
|
18
|
+
default: "my-api",
|
|
18
19
|
validate: (input) => {
|
|
19
20
|
if (!/^[a-z0-9-_]+$/i.test(input)) {
|
|
20
|
-
return
|
|
21
|
+
return "Project name can only contain letters, numbers, hyphens, and underscores";
|
|
21
22
|
}
|
|
22
23
|
return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
24
|
+
},
|
|
25
|
+
},
|
|
25
26
|
]);
|
|
26
27
|
finalProjectName = answer.projectName;
|
|
27
28
|
}
|
|
28
29
|
const projectPath = path.join(process.cwd(), finalProjectName);
|
|
29
30
|
if (directoryExists(projectPath) && !options.force) {
|
|
30
31
|
error(`Directory "${finalProjectName}" already exists!`);
|
|
31
|
-
gray(
|
|
32
|
+
gray("Use --force to overwrite\n");
|
|
32
33
|
process.exit(1);
|
|
33
34
|
}
|
|
34
35
|
const answers = await inquirer.prompt([
|
|
35
36
|
{
|
|
36
|
-
type:
|
|
37
|
-
name:
|
|
38
|
-
message:
|
|
39
|
-
default:
|
|
37
|
+
type: "input",
|
|
38
|
+
name: "description",
|
|
39
|
+
message: "Project description:",
|
|
40
|
+
default: "REST API built with Express and TypeScript",
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
|
-
type:
|
|
43
|
-
name:
|
|
44
|
-
message:
|
|
43
|
+
type: "list",
|
|
44
|
+
name: "database",
|
|
45
|
+
message: "Select database:",
|
|
45
46
|
choices: [
|
|
46
|
-
{ name:
|
|
47
|
-
{ name:
|
|
48
|
-
{ name:
|
|
49
|
-
{ name:
|
|
47
|
+
{ name: "PostgreSQL", value: "postgresql" },
|
|
48
|
+
{ name: "MySQL", value: "mysql" },
|
|
49
|
+
{ name: "MongoDB", value: "mongodb" },
|
|
50
|
+
{ name: "None (no database)", value: "none" },
|
|
50
51
|
],
|
|
51
|
-
default:
|
|
52
|
+
default: "postgresql",
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
|
-
type:
|
|
55
|
-
name:
|
|
56
|
-
message:
|
|
55
|
+
type: "list",
|
|
56
|
+
name: "authentication",
|
|
57
|
+
message: "Add authentication?",
|
|
57
58
|
choices: [
|
|
58
|
-
{ name:
|
|
59
|
-
{ name:
|
|
59
|
+
{ name: "JWT Authentication", value: "jwt" },
|
|
60
|
+
{ name: "None", value: "none" },
|
|
60
61
|
],
|
|
61
|
-
default:
|
|
62
|
+
default: "jwt",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "confirm",
|
|
66
|
+
name: "validation",
|
|
67
|
+
message: "Use katax-core for validation?",
|
|
68
|
+
default: true,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: "confirm",
|
|
72
|
+
name: "swagger",
|
|
73
|
+
message: "Install Swagger/OpenAPI documentation?",
|
|
74
|
+
default: true,
|
|
62
75
|
},
|
|
63
76
|
{
|
|
64
|
-
type:
|
|
65
|
-
name:
|
|
66
|
-
message:
|
|
67
|
-
default: true
|
|
77
|
+
type: "confirm",
|
|
78
|
+
name: "useKataxServiceManager",
|
|
79
|
+
message: "Use katax-service-manager for services? (logger, database, etc.)",
|
|
80
|
+
default: true,
|
|
68
81
|
},
|
|
69
82
|
{
|
|
70
|
-
type:
|
|
71
|
-
name:
|
|
72
|
-
message:
|
|
73
|
-
default:
|
|
83
|
+
type: "confirm",
|
|
84
|
+
name: "useRedis",
|
|
85
|
+
message: "Add Redis cache support?",
|
|
86
|
+
default: false,
|
|
87
|
+
when: (answers) => answers.useKataxServiceManager,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: "input",
|
|
91
|
+
name: "port",
|
|
92
|
+
message: "Server port:",
|
|
93
|
+
default: "3000",
|
|
74
94
|
validate: (input) => {
|
|
75
95
|
const port = parseInt(input);
|
|
76
96
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
77
|
-
return
|
|
97
|
+
return "Port must be a number between 1 and 65535";
|
|
78
98
|
}
|
|
79
99
|
return true;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
100
|
+
},
|
|
101
|
+
},
|
|
82
102
|
]);
|
|
83
103
|
let dbConfig = {};
|
|
84
|
-
if (answers.database !==
|
|
104
|
+
if (answers.database !== "none") {
|
|
85
105
|
const dbQuestions = [];
|
|
86
|
-
if (answers.database ===
|
|
106
|
+
if (answers.database === "postgresql" || answers.database === "mysql") {
|
|
87
107
|
dbQuestions.push({
|
|
88
|
-
type:
|
|
89
|
-
name:
|
|
90
|
-
message: `${answers.database ===
|
|
91
|
-
default:
|
|
108
|
+
type: "input",
|
|
109
|
+
name: "host",
|
|
110
|
+
message: `${answers.database === "postgresql" ? "PostgreSQL" : "MySQL"} host:`,
|
|
111
|
+
default: "localhost",
|
|
92
112
|
}, {
|
|
93
|
-
type:
|
|
94
|
-
name:
|
|
95
|
-
message: `${answers.database ===
|
|
96
|
-
default: answers.database ===
|
|
113
|
+
type: "input",
|
|
114
|
+
name: "port",
|
|
115
|
+
message: `${answers.database === "postgresql" ? "PostgreSQL" : "MySQL"} port:`,
|
|
116
|
+
default: answers.database === "postgresql" ? "5432" : "3306",
|
|
97
117
|
}, {
|
|
98
|
-
type:
|
|
99
|
-
name:
|
|
100
|
-
message:
|
|
101
|
-
default:
|
|
118
|
+
type: "input",
|
|
119
|
+
name: "user",
|
|
120
|
+
message: "Database user:",
|
|
121
|
+
default: "postgres",
|
|
102
122
|
}, {
|
|
103
|
-
type:
|
|
104
|
-
name:
|
|
105
|
-
message:
|
|
106
|
-
default:
|
|
123
|
+
type: "password",
|
|
124
|
+
name: "password",
|
|
125
|
+
message: "Database password:",
|
|
126
|
+
default: "password",
|
|
107
127
|
}, {
|
|
108
|
-
type:
|
|
109
|
-
name:
|
|
110
|
-
message:
|
|
111
|
-
default: finalProjectName.toLowerCase().replace(/-/g,
|
|
128
|
+
type: "input",
|
|
129
|
+
name: "database",
|
|
130
|
+
message: "Database name:",
|
|
131
|
+
default: finalProjectName.toLowerCase().replace(/-/g, "_"),
|
|
112
132
|
});
|
|
113
133
|
}
|
|
114
|
-
else if (answers.database ===
|
|
134
|
+
else if (answers.database === "mongodb") {
|
|
115
135
|
dbQuestions.push({
|
|
116
|
-
type:
|
|
117
|
-
name:
|
|
118
|
-
message:
|
|
119
|
-
default:
|
|
136
|
+
type: "input",
|
|
137
|
+
name: "host",
|
|
138
|
+
message: "MongoDB host:",
|
|
139
|
+
default: "localhost",
|
|
120
140
|
}, {
|
|
121
|
-
type:
|
|
122
|
-
name:
|
|
123
|
-
message:
|
|
124
|
-
default:
|
|
141
|
+
type: "input",
|
|
142
|
+
name: "port",
|
|
143
|
+
message: "MongoDB port:",
|
|
144
|
+
default: "27017",
|
|
125
145
|
}, {
|
|
126
|
-
type:
|
|
127
|
-
name:
|
|
128
|
-
message:
|
|
129
|
-
default: finalProjectName.toLowerCase().replace(/-/g,
|
|
146
|
+
type: "input",
|
|
147
|
+
name: "database",
|
|
148
|
+
message: "Database name:",
|
|
149
|
+
default: finalProjectName.toLowerCase().replace(/-/g, "_"),
|
|
130
150
|
}, {
|
|
131
|
-
type:
|
|
132
|
-
name:
|
|
133
|
-
message:
|
|
134
|
-
default: false
|
|
151
|
+
type: "confirm",
|
|
152
|
+
name: "useAuth",
|
|
153
|
+
message: "Use authentication?",
|
|
154
|
+
default: false,
|
|
135
155
|
});
|
|
136
156
|
}
|
|
137
157
|
dbConfig = await inquirer.prompt(dbQuestions);
|
|
138
|
-
if (answers.database ===
|
|
158
|
+
if (answers.database === "mongodb" && dbConfig.useAuth) {
|
|
139
159
|
const authConfig = await inquirer.prompt([
|
|
140
160
|
{
|
|
141
|
-
type:
|
|
142
|
-
name:
|
|
143
|
-
message:
|
|
144
|
-
default:
|
|
161
|
+
type: "input",
|
|
162
|
+
name: "user",
|
|
163
|
+
message: "MongoDB user:",
|
|
164
|
+
default: "admin",
|
|
145
165
|
},
|
|
146
166
|
{
|
|
147
|
-
type:
|
|
148
|
-
name:
|
|
149
|
-
message:
|
|
150
|
-
default:
|
|
151
|
-
}
|
|
167
|
+
type: "password",
|
|
168
|
+
name: "password",
|
|
169
|
+
message: "MongoDB password:",
|
|
170
|
+
default: "password",
|
|
171
|
+
},
|
|
152
172
|
]);
|
|
153
173
|
dbConfig.user = authConfig.user;
|
|
154
174
|
dbConfig.password = authConfig.password;
|
|
155
175
|
}
|
|
156
176
|
}
|
|
177
|
+
let redisConfig = {};
|
|
178
|
+
if (answers.useRedis) {
|
|
179
|
+
redisConfig = await inquirer.prompt([
|
|
180
|
+
{
|
|
181
|
+
type: "input",
|
|
182
|
+
name: "host",
|
|
183
|
+
message: "Redis host:",
|
|
184
|
+
default: "localhost",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: "input",
|
|
188
|
+
name: "port",
|
|
189
|
+
message: "Redis port:",
|
|
190
|
+
default: "6379",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
type: "password",
|
|
194
|
+
name: "password",
|
|
195
|
+
message: "Redis password (leave empty for no password):",
|
|
196
|
+
default: "",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: "input",
|
|
200
|
+
name: "db",
|
|
201
|
+
message: "Redis database number:",
|
|
202
|
+
default: "0",
|
|
203
|
+
},
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
157
206
|
const config = {
|
|
158
207
|
name: finalProjectName,
|
|
159
208
|
description: answers.description,
|
|
160
|
-
type:
|
|
209
|
+
type: "rest-api",
|
|
161
210
|
typescript: true,
|
|
162
211
|
database: answers.database,
|
|
163
212
|
authentication: answers.authentication,
|
|
164
|
-
validation: answers.validation ?
|
|
165
|
-
|
|
213
|
+
validation: answers.validation ? "katax-core" : "none",
|
|
214
|
+
swagger: answers.swagger,
|
|
215
|
+
orm: "none",
|
|
166
216
|
port: parseInt(answers.port),
|
|
167
|
-
|
|
217
|
+
useKataxServiceManager: answers.useKataxServiceManager,
|
|
218
|
+
useRedis: answers.useRedis || false,
|
|
219
|
+
redisConfig,
|
|
220
|
+
dbConfig,
|
|
168
221
|
};
|
|
169
222
|
let generateJwtSecrets = false;
|
|
170
|
-
if (config.authentication ===
|
|
223
|
+
if (config.authentication === "jwt") {
|
|
171
224
|
const jwtAnswer = await inquirer.prompt([
|
|
172
225
|
{
|
|
173
|
-
type:
|
|
174
|
-
name:
|
|
175
|
-
message:
|
|
176
|
-
default: true
|
|
177
|
-
}
|
|
226
|
+
type: "confirm",
|
|
227
|
+
name: "generate",
|
|
228
|
+
message: "Generate JWT secrets automatically?",
|
|
229
|
+
default: true,
|
|
230
|
+
},
|
|
178
231
|
]);
|
|
179
232
|
generateJwtSecrets = jwtAnswer.generate;
|
|
180
233
|
}
|
|
181
|
-
gray(
|
|
234
|
+
gray("\n📋 Project Configuration:");
|
|
182
235
|
gray(` Name: ${config.name}`);
|
|
183
236
|
gray(` Database: ${config.database}`);
|
|
184
237
|
gray(` Auth: ${config.authentication}`);
|
|
185
238
|
gray(` Validation: ${config.validation}`);
|
|
239
|
+
gray(` Swagger: ${config.swagger ? "Yes" : "No"}`);
|
|
240
|
+
gray(` Service Manager: ${config.useKataxServiceManager ? "katax-service-manager" : "manual"}`);
|
|
241
|
+
if (config.useKataxServiceManager) {
|
|
242
|
+
gray(` Redis Cache: ${config.useRedis ? "Yes" : "No"}`);
|
|
243
|
+
}
|
|
186
244
|
gray(` Port: ${config.port}\n`);
|
|
187
|
-
const spinner = ora(
|
|
245
|
+
const spinner = ora("Creating project structure...").start();
|
|
188
246
|
try {
|
|
189
247
|
await createProjectStructure(projectPath, config, generateJwtSecrets);
|
|
190
|
-
spinner.succeed(
|
|
191
|
-
spinner.start(
|
|
248
|
+
spinner.succeed("Project structure created");
|
|
249
|
+
spinner.start("Installing dependencies...");
|
|
192
250
|
await installDependencies(projectPath);
|
|
193
|
-
spinner.succeed(
|
|
251
|
+
spinner.succeed("Dependencies installed");
|
|
194
252
|
success(`\n✨ Project "${finalProjectName}" created successfully!\n`);
|
|
195
|
-
info(
|
|
253
|
+
info("Next steps:");
|
|
196
254
|
gray(` cd ${finalProjectName}`);
|
|
197
255
|
gray(` npm run dev\n`);
|
|
198
|
-
info(
|
|
256
|
+
info("Available commands:");
|
|
199
257
|
gray(` katax add endpoint <name> - Add a new endpoint`);
|
|
200
258
|
gray(` katax generate crud <name> - Generate CRUD resource`);
|
|
201
259
|
gray(` katax info - Show project structure\n`);
|
|
260
|
+
if (config.swagger) {
|
|
261
|
+
info("📖 API Documentation:");
|
|
262
|
+
gray(` Open http://localhost:${config.port}/docs after starting the server`);
|
|
263
|
+
gray(` Swagger UI is pre-configured and ready to use!\n`);
|
|
264
|
+
}
|
|
202
265
|
}
|
|
203
266
|
catch (err) {
|
|
204
|
-
spinner.fail(
|
|
205
|
-
error(err instanceof Error ? err.message :
|
|
267
|
+
spinner.fail("Failed to create project");
|
|
268
|
+
error(err instanceof Error ? err.message : "Unknown error");
|
|
206
269
|
process.exit(1);
|
|
207
270
|
}
|
|
208
271
|
}
|
|
209
272
|
async function installDependencies(projectPath) {
|
|
210
|
-
await execa(
|
|
273
|
+
await execa("npm", ["install"], {
|
|
211
274
|
cwd: projectPath,
|
|
212
|
-
stdio:
|
|
275
|
+
stdio: "ignore",
|
|
213
276
|
});
|
|
214
277
|
}
|
|
215
278
|
async function createDatabaseConnection(projectPath, config) {
|
|
216
|
-
const destPath = path.join(projectPath,
|
|
217
|
-
let content =
|
|
218
|
-
if (config.database ===
|
|
279
|
+
const destPath = path.join(projectPath, "src/database/connection.ts");
|
|
280
|
+
let content = "";
|
|
281
|
+
if (config.database === "postgresql") {
|
|
219
282
|
content = [
|
|
220
283
|
"import { Pool } from 'pg';",
|
|
221
284
|
"import dotenv from 'dotenv';",
|
|
@@ -268,10 +331,10 @@ async function createDatabaseConnection(projectPath, config) {
|
|
|
268
331
|
" };",
|
|
269
332
|
" ",
|
|
270
333
|
" return client;",
|
|
271
|
-
"}"
|
|
272
|
-
].join(
|
|
334
|
+
"}",
|
|
335
|
+
].join("\n");
|
|
273
336
|
}
|
|
274
|
-
else if (config.database ===
|
|
337
|
+
else if (config.database === "mysql") {
|
|
275
338
|
content = [
|
|
276
339
|
"import mysql from 'mysql2/promise';",
|
|
277
340
|
"",
|
|
@@ -294,10 +357,10 @@ async function createDatabaseConnection(projectPath, config) {
|
|
|
294
357
|
"",
|
|
295
358
|
"export async function getConnection() {",
|
|
296
359
|
" return await pool.getConnection();",
|
|
297
|
-
"}"
|
|
298
|
-
].join(
|
|
360
|
+
"}",
|
|
361
|
+
].join("\n");
|
|
299
362
|
}
|
|
300
|
-
else if (config.database ===
|
|
363
|
+
else if (config.database === "mongodb") {
|
|
301
364
|
content = [
|
|
302
365
|
"import { MongoClient, Db } from 'mongodb';",
|
|
303
366
|
"",
|
|
@@ -344,103 +407,105 @@ async function createDatabaseConnection(projectPath, config) {
|
|
|
344
407
|
" return db;",
|
|
345
408
|
"}",
|
|
346
409
|
"",
|
|
347
|
-
"export default { connect, disconnect, getDb };"
|
|
348
|
-
].join(
|
|
410
|
+
"export default { connect, disconnect, getDb };",
|
|
411
|
+
].join("\n");
|
|
349
412
|
}
|
|
350
413
|
await writeFile(destPath, content);
|
|
351
414
|
}
|
|
352
415
|
async function createProjectStructure(projectPath, config, generateJwtSecrets) {
|
|
353
416
|
const dirs = [
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
417
|
+
"src",
|
|
418
|
+
"src/api",
|
|
419
|
+
"src/config",
|
|
420
|
+
"src/middleware",
|
|
421
|
+
"src/shared",
|
|
422
|
+
"src/types",
|
|
360
423
|
];
|
|
361
|
-
if (config.database !==
|
|
362
|
-
dirs.push(
|
|
424
|
+
if (config.database !== "none") {
|
|
425
|
+
dirs.push("src/database");
|
|
363
426
|
}
|
|
364
|
-
dirs.push(
|
|
427
|
+
dirs.push("src/api/hello");
|
|
365
428
|
for (const dir of dirs) {
|
|
366
429
|
await ensureDir(path.join(projectPath, dir));
|
|
367
430
|
}
|
|
368
431
|
const packageJson = {
|
|
369
432
|
name: config.name,
|
|
370
|
-
version:
|
|
433
|
+
version: "1.0.0",
|
|
371
434
|
description: config.description,
|
|
372
|
-
type:
|
|
373
|
-
main:
|
|
435
|
+
type: "module",
|
|
436
|
+
main: "dist/index.js",
|
|
374
437
|
scripts: {
|
|
375
|
-
dev:
|
|
376
|
-
build:
|
|
377
|
-
start:
|
|
378
|
-
lint:
|
|
379
|
-
format: 'prettier --write "src/**/*.ts"'
|
|
438
|
+
dev: "nodemon --watch src --exec tsx src/index.ts",
|
|
439
|
+
build: "tsc",
|
|
440
|
+
start: "node dist/index.js",
|
|
441
|
+
lint: "eslint . --ext .ts",
|
|
442
|
+
format: 'prettier --write "src/**/*.ts"',
|
|
380
443
|
},
|
|
381
|
-
keywords: [
|
|
382
|
-
author:
|
|
383
|
-
license:
|
|
444
|
+
keywords: ["api", "express", "typescript"],
|
|
445
|
+
author: "",
|
|
446
|
+
license: "MIT",
|
|
384
447
|
dependencies: {
|
|
385
|
-
express:
|
|
386
|
-
cors:
|
|
387
|
-
dotenv:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
...(config.
|
|
392
|
-
|
|
393
|
-
|
|
448
|
+
express: "^4.18.2",
|
|
449
|
+
cors: "^2.8.5",
|
|
450
|
+
dotenv: "^16.3.1",
|
|
451
|
+
...(config.useKataxServiceManager
|
|
452
|
+
? { "katax-service-manager": "^0.1.0" }
|
|
453
|
+
: { pino: "^8.17.2", "pino-pretty": "^10.3.1" }),
|
|
454
|
+
...(config.validation === "katax-core" && { "katax-core": "^1.5.0" }),
|
|
455
|
+
...(config.authentication === "jwt" && {
|
|
456
|
+
jsonwebtoken: "^9.0.2",
|
|
457
|
+
bcrypt: "^5.1.1",
|
|
394
458
|
}),
|
|
395
|
-
...(config.
|
|
396
|
-
...(config.database ===
|
|
397
|
-
...(config.database ===
|
|
459
|
+
...(config.swagger && { "swagger-ui-express": "^5.0.0" }),
|
|
460
|
+
...(config.database === "postgresql" && { pg: "^8.11.3" }),
|
|
461
|
+
...(config.database === "mysql" && { mysql2: "^3.6.5" }),
|
|
462
|
+
...(config.database === "mongodb" && { mongodb: "^6.3.0" }),
|
|
463
|
+
...(config.useRedis && { ioredis: "^5.3.2" }),
|
|
398
464
|
},
|
|
399
465
|
devDependencies: {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
...(config.authentication ===
|
|
404
|
-
|
|
405
|
-
|
|
466
|
+
"@types/express": "^4.17.21",
|
|
467
|
+
"@types/cors": "^2.8.17",
|
|
468
|
+
"@types/node": "^22.10.5",
|
|
469
|
+
...(config.authentication === "jwt" && {
|
|
470
|
+
"@types/jsonwebtoken": "^9.0.5",
|
|
471
|
+
"@types/bcrypt": "^5.0.2",
|
|
406
472
|
}),
|
|
407
|
-
...(config.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
473
|
+
...(config.swagger && { "@types/swagger-ui-express": "^4.1.6" }),
|
|
474
|
+
...(config.database === "postgresql" && { "@types/pg": "^8.10.9" }),
|
|
475
|
+
typescript: "^5.3.3",
|
|
476
|
+
tsx: "^4.7.0",
|
|
477
|
+
nodemon: "^3.0.2",
|
|
478
|
+
eslint: "^8.56.0",
|
|
479
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
480
|
+
"@typescript-eslint/parser": "^6.19.0",
|
|
481
|
+
prettier: "^3.2.4",
|
|
482
|
+
},
|
|
416
483
|
};
|
|
417
|
-
await writeFile(path.join(projectPath,
|
|
484
|
+
await writeFile(path.join(projectPath, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
418
485
|
const tsConfig = {
|
|
419
486
|
compilerOptions: {
|
|
420
|
-
target:
|
|
421
|
-
module:
|
|
422
|
-
lib: [
|
|
423
|
-
moduleResolution:
|
|
487
|
+
target: "ES2022",
|
|
488
|
+
module: "ESNext",
|
|
489
|
+
lib: ["ES2022"],
|
|
490
|
+
moduleResolution: "node",
|
|
424
491
|
esModuleInterop: true,
|
|
425
492
|
allowSyntheticDefaultImports: true,
|
|
426
493
|
forceConsistentCasingInFileNames: true,
|
|
427
494
|
strict: true,
|
|
428
495
|
skipLibCheck: true,
|
|
429
496
|
resolveJsonModule: true,
|
|
430
|
-
declaration: true,
|
|
431
|
-
declarationMap: true,
|
|
432
497
|
sourceMap: true,
|
|
433
498
|
removeComments: true,
|
|
434
|
-
outDir:
|
|
435
|
-
rootDir:
|
|
499
|
+
outDir: "./dist",
|
|
500
|
+
rootDir: "./src",
|
|
436
501
|
},
|
|
437
|
-
include: [
|
|
438
|
-
exclude: [
|
|
502
|
+
include: ["src/**/*"],
|
|
503
|
+
exclude: ["node_modules", "dist"],
|
|
439
504
|
};
|
|
440
|
-
await writeFile(path.join(projectPath,
|
|
441
|
-
let databaseUrl =
|
|
442
|
-
let dbEnvVars =
|
|
443
|
-
if (config.database ===
|
|
505
|
+
await writeFile(path.join(projectPath, "tsconfig.json"), JSON.stringify(tsConfig, null, 2));
|
|
506
|
+
let databaseUrl = "";
|
|
507
|
+
let dbEnvVars = "";
|
|
508
|
+
if (config.database === "postgresql" && config.dbConfig) {
|
|
444
509
|
const { host, port, user, password, database } = config.dbConfig;
|
|
445
510
|
databaseUrl = `DATABASE_URL=postgresql://${user}:${password}@${host}:${port}/${database}`;
|
|
446
511
|
dbEnvVars = `DB_HOST=${host}
|
|
@@ -449,7 +514,7 @@ DB_NAME=${database}
|
|
|
449
514
|
DB_USER=${user}
|
|
450
515
|
DB_PASSWORD=${password}`;
|
|
451
516
|
}
|
|
452
|
-
else if (config.database ===
|
|
517
|
+
else if (config.database === "mysql" && config.dbConfig) {
|
|
453
518
|
const { host, port, user, password, database } = config.dbConfig;
|
|
454
519
|
databaseUrl = `DATABASE_URL=mysql://${user}:${password}@${host}:${port}/${database}`;
|
|
455
520
|
dbEnvVars = `DB_HOST=${host}
|
|
@@ -458,7 +523,7 @@ DB_NAME=${database}
|
|
|
458
523
|
DB_USER=${user}
|
|
459
524
|
DB_PASSWORD=${password}`;
|
|
460
525
|
}
|
|
461
|
-
else if (config.database ===
|
|
526
|
+
else if (config.database === "mongodb" && config.dbConfig) {
|
|
462
527
|
const { host, port, database, user, password } = config.dbConfig;
|
|
463
528
|
if (user && password) {
|
|
464
529
|
databaseUrl = `DATABASE_URL=mongodb://${user}:${password}@${host}:${port}/${database}`;
|
|
@@ -475,11 +540,11 @@ DB_PORT=${port}
|
|
|
475
540
|
DB_NAME=${database}`;
|
|
476
541
|
}
|
|
477
542
|
}
|
|
478
|
-
let jwtConfig =
|
|
479
|
-
if (config.authentication ===
|
|
543
|
+
let jwtConfig = "";
|
|
544
|
+
if (config.authentication === "jwt") {
|
|
480
545
|
if (generateJwtSecrets) {
|
|
481
|
-
const jwtSecret = crypto.randomBytes(64).toString(
|
|
482
|
-
const jwtRefreshSecret = crypto.randomBytes(64).toString(
|
|
546
|
+
const jwtSecret = crypto.randomBytes(64).toString("hex");
|
|
547
|
+
const jwtRefreshSecret = crypto.randomBytes(64).toString("hex");
|
|
483
548
|
jwtConfig = `JWT_SECRET=${jwtSecret}
|
|
484
549
|
JWT_EXPIRES_IN=24h
|
|
485
550
|
JWT_REFRESH_SECRET=${jwtRefreshSecret}
|
|
@@ -492,6 +557,16 @@ JWT_REFRESH_SECRET=your-refresh-secret-here
|
|
|
492
557
|
JWT_REFRESH_EXPIRES_IN=7d`;
|
|
493
558
|
}
|
|
494
559
|
}
|
|
560
|
+
let redisEnvVars = "";
|
|
561
|
+
if (config.useRedis && config.redisConfig) {
|
|
562
|
+
const { host, port, password, db } = config.redisConfig;
|
|
563
|
+
redisEnvVars = `
|
|
564
|
+
# Redis Configuration
|
|
565
|
+
REDIS_HOST=${host || 'localhost'}
|
|
566
|
+
REDIS_PORT=${port || '6379'}
|
|
567
|
+
REDIS_PASSWORD=${password || ''}
|
|
568
|
+
REDIS_DB=${db || '0'}`;
|
|
569
|
+
}
|
|
495
570
|
const envContent = `# Server Configuration
|
|
496
571
|
PORT=${config.port}
|
|
497
572
|
NODE_ENV=development
|
|
@@ -499,16 +574,15 @@ LOG_LEVEL=info
|
|
|
499
574
|
|
|
500
575
|
# CORS Configuration
|
|
501
576
|
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
|
502
|
-
|
|
577
|
+
${config.database !== "none" ? `
|
|
503
578
|
# Database Configuration
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
# JWT Configuration
|
|
508
|
-
${jwtConfig}
|
|
579
|
+
${databaseUrl}
|
|
580
|
+
${dbEnvVars ? "\n# DB connection variables for pool\n" + dbEnvVars : ""}` : ""}
|
|
581
|
+
${redisEnvVars}
|
|
582
|
+
${config.authentication === "jwt" ? `\n# JWT Configuration\n${jwtConfig}` : ""}
|
|
509
583
|
`;
|
|
510
|
-
await writeFile(path.join(projectPath,
|
|
511
|
-
await writeFile(path.join(projectPath,
|
|
584
|
+
await writeFile(path.join(projectPath, ".env.example"), envContent);
|
|
585
|
+
await writeFile(path.join(projectPath, ".env"), envContent);
|
|
512
586
|
const gitignoreContent = `node_modules/
|
|
513
587
|
dist/
|
|
514
588
|
.env
|
|
@@ -517,7 +591,7 @@ dist/
|
|
|
517
591
|
coverage/
|
|
518
592
|
.vscode/
|
|
519
593
|
`;
|
|
520
|
-
await writeFile(path.join(projectPath,
|
|
594
|
+
await writeFile(path.join(projectPath, ".gitignore"), gitignoreContent);
|
|
521
595
|
const gitattributesContent = `# Auto normalize line endings to LF on checkout (critical for Ubuntu deployment)
|
|
522
596
|
* text=auto eol=lf
|
|
523
597
|
|
|
@@ -537,8 +611,66 @@ coverage/
|
|
|
537
611
|
*.ico binary
|
|
538
612
|
*.pdf binary
|
|
539
613
|
`;
|
|
540
|
-
await writeFile(path.join(projectPath,
|
|
541
|
-
|
|
614
|
+
await writeFile(path.join(projectPath, ".gitattributes"), gitattributesContent);
|
|
615
|
+
let indexContent;
|
|
616
|
+
if (config.useKataxServiceManager) {
|
|
617
|
+
const dbInitCode = config.database !== "none" ? `
|
|
618
|
+
// Initialize database
|
|
619
|
+
await katax.database({
|
|
620
|
+
name: 'main',
|
|
621
|
+
type: '${config.database}',
|
|
622
|
+
connection: {
|
|
623
|
+
host: katax.envRequired('DB_HOST'),
|
|
624
|
+
port: parseInt(katax.env('DB_PORT', '${config.database === 'postgresql' ? '5432' : config.database === 'mysql' ? '3306' : '27017'}')),
|
|
625
|
+
database: katax.envRequired('DB_NAME'),
|
|
626
|
+
user: katax.envRequired('DB_USER'),
|
|
627
|
+
password: katax.envRequired('DB_PASSWORD'),
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
` : '';
|
|
631
|
+
const cacheInitCode = config.useRedis ? `
|
|
632
|
+
// Initialize Redis cache
|
|
633
|
+
await katax.cache({
|
|
634
|
+
name: 'main',
|
|
635
|
+
host: katax.env('REDIS_HOST', 'localhost'),
|
|
636
|
+
port: parseInt(katax.env('REDIS_PORT', '6379')),
|
|
637
|
+
password: katax.env('REDIS_PASSWORD'),
|
|
638
|
+
db: parseInt(katax.env('REDIS_DB', '0')),
|
|
639
|
+
});
|
|
640
|
+
` : '';
|
|
641
|
+
indexContent = `import { katax } from 'katax-service-manager';
|
|
642
|
+
import app from './app.js';
|
|
643
|
+
import dotenv from 'dotenv';
|
|
644
|
+
|
|
645
|
+
dotenv.config();
|
|
646
|
+
|
|
647
|
+
// Initialize katax and start server
|
|
648
|
+
katax.init({
|
|
649
|
+
logger: {
|
|
650
|
+
level: katax.env('LOG_LEVEL', 'info') as 'info' | 'debug' | 'warn' | 'error',
|
|
651
|
+
prettyPrint: katax.isDev,
|
|
652
|
+
}
|
|
653
|
+
}).then(async () => {
|
|
654
|
+
${dbInitCode}${cacheInitCode}
|
|
655
|
+
const PORT = katax.env('PORT', '${config.port}');
|
|
656
|
+
|
|
657
|
+
app.listen(PORT, () => {
|
|
658
|
+
katax.logger.info({ message: \`Server running on http://localhost:\${PORT}\` });
|
|
659
|
+
katax.logger.info({ message: \`API endpoints available at http://localhost:\${PORT}/api\` });
|
|
660
|
+
katax.logger.info({ message: \`Health check: http://localhost:\${PORT}/api/health\` });
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// Graceful shutdown
|
|
664
|
+
process.on('SIGTERM', async () => {
|
|
665
|
+
katax.logger.info({ message: 'SIGTERM received, shutting down...' });
|
|
666
|
+
await katax.shutdown();
|
|
667
|
+
process.exit(0);
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
`;
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
indexContent = `import app from './app.js';
|
|
542
674
|
import dotenv from 'dotenv';
|
|
543
675
|
import { logger } from './shared/logger.utils.js';
|
|
544
676
|
import { validateEnvironment } from './config/env.validator.js';
|
|
@@ -556,13 +688,14 @@ app.listen(PORT, () => {
|
|
|
556
688
|
logger.info(\`Health check: http://localhost:\${PORT}/api/health\`);
|
|
557
689
|
});
|
|
558
690
|
`;
|
|
559
|
-
|
|
691
|
+
}
|
|
692
|
+
await writeFile(path.join(projectPath, "src/index.ts"), indexContent);
|
|
560
693
|
const appContent = `import express from 'express';
|
|
561
694
|
import cors from 'cors';
|
|
562
695
|
import router from './api/routes.js';
|
|
563
696
|
import { errorMiddleware } from './middleware/error.middleware.js';
|
|
564
697
|
import { requestLogger } from './middleware/logger.middleware.js';
|
|
565
|
-
import { corsOptions } from './config/cors.config.js';
|
|
698
|
+
import { corsOptions } from './config/cors.config.js';${config.swagger ? "\nimport { setupSwagger } from './config/swagger.config.js';" : ""}
|
|
566
699
|
|
|
567
700
|
const app = express();
|
|
568
701
|
|
|
@@ -572,13 +705,13 @@ app.use(express.json());
|
|
|
572
705
|
app.use(express.urlencoded({ extended: true }));
|
|
573
706
|
app.use(requestLogger);
|
|
574
707
|
|
|
575
|
-
// Routes
|
|
708
|
+
${config.swagger ? "// API Documentation\nsetupSwagger(app);\n\n" : ""}// Routes
|
|
576
709
|
app.get('/', (req, res) => {
|
|
577
710
|
res.json({
|
|
578
711
|
message: 'Welcome to ${config.name} API',
|
|
579
712
|
version: '1.0.0',
|
|
580
713
|
endpoints: '/api',
|
|
581
|
-
health: '/api/health'
|
|
714
|
+
health: '/api/health'${config.swagger ? ",\n docs: '/docs'" : ""}
|
|
582
715
|
});
|
|
583
716
|
});
|
|
584
717
|
|
|
@@ -589,7 +722,7 @@ app.use(errorMiddleware);
|
|
|
589
722
|
|
|
590
723
|
export default app;
|
|
591
724
|
`;
|
|
592
|
-
await writeFile(path.join(projectPath,
|
|
725
|
+
await writeFile(path.join(projectPath, "src/app.ts"), appContent);
|
|
593
726
|
const routesContent = `import { Router } from 'express';
|
|
594
727
|
import helloRouter from './hello/hello.routes.js';
|
|
595
728
|
import { healthCheckHandler } from './health/health.handler.js';
|
|
@@ -604,7 +737,7 @@ router.use('/hello', helloRouter);
|
|
|
604
737
|
|
|
605
738
|
export default router;
|
|
606
739
|
`;
|
|
607
|
-
await writeFile(path.join(projectPath,
|
|
740
|
+
await writeFile(path.join(projectPath, "src/api/routes.ts"), routesContent);
|
|
608
741
|
const errorMiddlewareContent = `import { Request, Response, NextFunction } from 'express';
|
|
609
742
|
import { logger } from '../shared/logger.utils.js';
|
|
610
743
|
|
|
@@ -638,11 +771,11 @@ export function errorMiddleware(
|
|
|
638
771
|
});
|
|
639
772
|
}
|
|
640
773
|
`;
|
|
641
|
-
await writeFile(path.join(projectPath,
|
|
642
|
-
if (config.database !==
|
|
774
|
+
await writeFile(path.join(projectPath, "src/middleware/error.middleware.ts"), errorMiddlewareContent);
|
|
775
|
+
if (config.database !== "none" && !config.useKataxServiceManager) {
|
|
643
776
|
await createDatabaseConnection(projectPath, config);
|
|
644
777
|
}
|
|
645
|
-
if (config.validation ===
|
|
778
|
+
if (config.validation === "katax-core") {
|
|
646
779
|
const apiUtilsContent = `import { Request, Response } from 'express';
|
|
647
780
|
import { logger } from './logger.utils.js';
|
|
648
781
|
|
|
@@ -741,9 +874,9 @@ export async function sendResponse<TValidation = any, TResponse = any>(
|
|
|
741
874
|
}
|
|
742
875
|
}
|
|
743
876
|
`;
|
|
744
|
-
await writeFile(path.join(projectPath,
|
|
877
|
+
await writeFile(path.join(projectPath, "src/shared/api.utils.ts"), apiUtilsContent);
|
|
745
878
|
}
|
|
746
|
-
if (config.authentication ===
|
|
879
|
+
if (config.authentication === "jwt") {
|
|
747
880
|
const jwtUtilsContent = `import jwt from 'jsonwebtoken';
|
|
748
881
|
import { Request, Response, NextFunction } from 'express';
|
|
749
882
|
|
|
@@ -897,9 +1030,66 @@ export function requireRole(...roles: string[]) {
|
|
|
897
1030
|
};
|
|
898
1031
|
}
|
|
899
1032
|
`;
|
|
900
|
-
await writeFile(path.join(projectPath,
|
|
1033
|
+
await writeFile(path.join(projectPath, "src/shared/jwt.utils.ts"), jwtUtilsContent);
|
|
901
1034
|
}
|
|
902
|
-
|
|
1035
|
+
let loggerUtilsContent;
|
|
1036
|
+
if (config.useKataxServiceManager) {
|
|
1037
|
+
loggerUtilsContent = `import { katax } from 'katax-service-manager';
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Re-export katax logger for convenience
|
|
1041
|
+
* Uses katax-service-manager's built-in pino logger
|
|
1042
|
+
*/
|
|
1043
|
+
export const logger = katax.logger;
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Log HTTP request
|
|
1047
|
+
*/
|
|
1048
|
+
export function logRequest(method: string, url: string, statusCode: number, duration: number): void {
|
|
1049
|
+
katax.logger.info({
|
|
1050
|
+
message: \`\${method} \${url} - \${statusCode} (\${duration}ms)\`,
|
|
1051
|
+
method,
|
|
1052
|
+
url,
|
|
1053
|
+
statusCode,
|
|
1054
|
+
duration: \`\${duration}ms\`
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Log error with context
|
|
1060
|
+
*/
|
|
1061
|
+
export function logError(error: Error, context?: Record<string, any>): void {
|
|
1062
|
+
katax.logger.error({
|
|
1063
|
+
message: error.message,
|
|
1064
|
+
err: error,
|
|
1065
|
+
...context
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Log info message
|
|
1071
|
+
*/
|
|
1072
|
+
export function logInfo(message: string, data?: Record<string, any>): void {
|
|
1073
|
+
katax.logger.info({ message, ...data });
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Log warning message
|
|
1078
|
+
*/
|
|
1079
|
+
export function logWarning(message: string, data?: Record<string, any>): void {
|
|
1080
|
+
katax.logger.warn({ message, ...data });
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Log debug message
|
|
1085
|
+
*/
|
|
1086
|
+
export function logDebug(message: string, data?: Record<string, any>): void {
|
|
1087
|
+
katax.logger.debug({ message, ...data });
|
|
1088
|
+
}
|
|
1089
|
+
`;
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
loggerUtilsContent = `import pino from 'pino';
|
|
903
1093
|
|
|
904
1094
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
|
905
1095
|
|
|
@@ -970,7 +1160,8 @@ export function logDebug(message: string, data?: Record<string, any>): void {
|
|
|
970
1160
|
logger.debug(data, message);
|
|
971
1161
|
}
|
|
972
1162
|
`;
|
|
973
|
-
|
|
1163
|
+
}
|
|
1164
|
+
await writeFile(path.join(projectPath, "src/shared/logger.utils.ts"), loggerUtilsContent);
|
|
974
1165
|
const loggerMiddlewareContent = `import { Request, Response, NextFunction } from 'express';
|
|
975
1166
|
import { logRequest } from '../shared/logger.utils.js';
|
|
976
1167
|
|
|
@@ -989,7 +1180,7 @@ export function requestLogger(req: Request, res: Response, next: NextFunction):
|
|
|
989
1180
|
next();
|
|
990
1181
|
}
|
|
991
1182
|
`;
|
|
992
|
-
await writeFile(path.join(projectPath,
|
|
1183
|
+
await writeFile(path.join(projectPath, "src/middleware/logger.middleware.ts"), loggerMiddlewareContent);
|
|
993
1184
|
const corsConfigContent = `import { CorsOptions } from 'cors';
|
|
994
1185
|
|
|
995
1186
|
const allowedOrigins = process.env.ALLOWED_ORIGINS
|
|
@@ -1013,7 +1204,7 @@ export const corsOptions: CorsOptions = {
|
|
|
1013
1204
|
allowedHeaders: ['Content-Type', 'Authorization']
|
|
1014
1205
|
};
|
|
1015
1206
|
`;
|
|
1016
|
-
await writeFile(path.join(projectPath,
|
|
1207
|
+
await writeFile(path.join(projectPath, "src/config/cors.config.ts"), corsConfigContent);
|
|
1017
1208
|
const envValidatorContent = `import { logger } from '../shared/logger.utils.js';
|
|
1018
1209
|
|
|
1019
1210
|
interface RequiredEnvVars {
|
|
@@ -1029,7 +1220,7 @@ export function validateEnvironment(): void {
|
|
|
1029
1220
|
NODE_ENV: process.env.NODE_ENV || ''
|
|
1030
1221
|
};
|
|
1031
1222
|
|
|
1032
|
-
${config.database !==
|
|
1223
|
+
${config.database !== "none" ? ` // Database variables\n required.DATABASE_URL = process.env.DATABASE_URL || '';\n` : ""}${config.authentication === "jwt" ? ` // JWT variables\n required.JWT_SECRET = process.env.JWT_SECRET || '';\n required.JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || '';\n` : ""}
|
|
1033
1224
|
const missing: string[] = [];
|
|
1034
1225
|
|
|
1035
1226
|
for (const [key, value] of Object.entries(required)) {
|
|
@@ -1047,7 +1238,78 @@ ${config.database !== 'none' ? ` // Database variables\n required.DATABASE_URL
|
|
|
1047
1238
|
logger.info('Environment variables validated successfully');
|
|
1048
1239
|
}
|
|
1049
1240
|
`;
|
|
1050
|
-
await writeFile(path.join(projectPath,
|
|
1241
|
+
await writeFile(path.join(projectPath, "src/config/env.validator.ts"), envValidatorContent);
|
|
1242
|
+
if (config.swagger) {
|
|
1243
|
+
await writeFile(path.join(projectPath, "src/config/swagger.config.ts"), generateSwaggerSetup());
|
|
1244
|
+
const initialOpenAPISpec = {
|
|
1245
|
+
openapi: "3.0.0",
|
|
1246
|
+
info: {
|
|
1247
|
+
title: config.name,
|
|
1248
|
+
version: "1.0.0",
|
|
1249
|
+
description: config.description || "API Documentation",
|
|
1250
|
+
},
|
|
1251
|
+
servers: [
|
|
1252
|
+
{
|
|
1253
|
+
url: `http://localhost:${config.port}`,
|
|
1254
|
+
description: "Development server",
|
|
1255
|
+
},
|
|
1256
|
+
],
|
|
1257
|
+
paths: {
|
|
1258
|
+
"/": {
|
|
1259
|
+
get: {
|
|
1260
|
+
tags: ["General"],
|
|
1261
|
+
summary: "Welcome endpoint",
|
|
1262
|
+
responses: {
|
|
1263
|
+
"200": {
|
|
1264
|
+
description: "Welcome message",
|
|
1265
|
+
content: {
|
|
1266
|
+
"application/json": {
|
|
1267
|
+
schema: {
|
|
1268
|
+
type: "object",
|
|
1269
|
+
properties: {
|
|
1270
|
+
message: { type: "string" },
|
|
1271
|
+
version: { type: "string" },
|
|
1272
|
+
endpoints: { type: "string" },
|
|
1273
|
+
health: { type: "string" },
|
|
1274
|
+
docs: { type: "string" },
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
},
|
|
1281
|
+
},
|
|
1282
|
+
},
|
|
1283
|
+
"/api/health": {
|
|
1284
|
+
get: {
|
|
1285
|
+
tags: ["Health"],
|
|
1286
|
+
summary: "Health check endpoint",
|
|
1287
|
+
responses: {
|
|
1288
|
+
"200": {
|
|
1289
|
+
description: "Health status",
|
|
1290
|
+
content: {
|
|
1291
|
+
"application/json": {
|
|
1292
|
+
schema: {
|
|
1293
|
+
type: "object",
|
|
1294
|
+
properties: {
|
|
1295
|
+
status: { type: "string" },
|
|
1296
|
+
timestamp: { type: "string" },
|
|
1297
|
+
uptime: { type: "number" },
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
},
|
|
1303
|
+
},
|
|
1304
|
+
},
|
|
1305
|
+
},
|
|
1306
|
+
},
|
|
1307
|
+
components: {
|
|
1308
|
+
schemas: {},
|
|
1309
|
+
},
|
|
1310
|
+
};
|
|
1311
|
+
await writeFile(path.join(projectPath, "src/openapi.json"), JSON.stringify(initialOpenAPISpec, null, 2));
|
|
1312
|
+
}
|
|
1051
1313
|
const healthHandlerContent = `import { Request, Response } from 'express';
|
|
1052
1314
|
import os from 'os';
|
|
1053
1315
|
|
|
@@ -1120,8 +1382,8 @@ function formatBytes(bytes: number): string {
|
|
|
1120
1382
|
return \`\${size.toFixed(2)} \${units[unitIndex]}\`;
|
|
1121
1383
|
}
|
|
1122
1384
|
`;
|
|
1123
|
-
await ensureDir(path.join(projectPath,
|
|
1124
|
-
await writeFile(path.join(projectPath,
|
|
1385
|
+
await ensureDir(path.join(projectPath, "src/api/health"));
|
|
1386
|
+
await writeFile(path.join(projectPath, "src/api/health/health.handler.ts"), healthHandlerContent);
|
|
1125
1387
|
const readmeContent = `# ${config.name}
|
|
1126
1388
|
|
|
1127
1389
|
${config.description}
|
|
@@ -1157,15 +1419,15 @@ src/
|
|
|
1157
1419
|
|
|
1158
1420
|
- **Express** - Web framework
|
|
1159
1421
|
- **TypeScript** - Type safety
|
|
1160
|
-
${config.validation ===
|
|
1422
|
+
${config.validation === "katax-core" ? "- **katax-core** - Schema validation\n" : ""}${config.authentication === "jwt" ? "- **JWT** - Authentication\n" : ""}${config.swagger ? "- **Swagger** - API Documentation\n" : ""}${config.database !== "none" ? `- **${config.database}** - Database\n` : ""}
|
|
1161
1423
|
## 📚 API Documentation
|
|
1162
1424
|
|
|
1163
1425
|
Server runs on \`http://localhost:${config.port}\`
|
|
1164
|
-
|
|
1426
|
+
${config.swagger ? `\n### Interactive Documentation\n\nSwagger UI is available at: **http://localhost:${config.port}/docs**\n\n- View all endpoints\n- Test API calls directly\n- See request/response schemas\n- Auto-updated when you add endpoints\n\n` : ""}
|
|
1165
1427
|
### Endpoints
|
|
1166
1428
|
|
|
1167
1429
|
- \`GET /\` - Welcome message
|
|
1168
|
-
- \`GET /api/health\` - Health check
|
|
1430
|
+
- \`GET /api/health\` - Health check${config.swagger ? "\n- `GET /docs` - Swagger UI\n- `GET /openapi.json` - OpenAPI Specification" : ""}
|
|
1169
1431
|
|
|
1170
1432
|
## 🔧 Development
|
|
1171
1433
|
|
|
@@ -1183,11 +1445,11 @@ katax generate crud products
|
|
|
1183
1445
|
|
|
1184
1446
|
MIT
|
|
1185
1447
|
`;
|
|
1186
|
-
await writeFile(path.join(projectPath,
|
|
1448
|
+
await writeFile(path.join(projectPath, "README.md"), readmeContent);
|
|
1187
1449
|
await createHelloEndpoint(projectPath, config);
|
|
1188
1450
|
}
|
|
1189
1451
|
async function createHelloEndpoint(projectPath, config) {
|
|
1190
|
-
const helloPath = path.join(projectPath,
|
|
1452
|
+
const helloPath = path.join(projectPath, "src/api/hello");
|
|
1191
1453
|
const controllerContent = [
|
|
1192
1454
|
"import { ControllerResult, createSuccessResult, createErrorResult } from '../../shared/api.utils.js';",
|
|
1193
1455
|
"import { HelloQuery } from './hello.validator.js';",
|
|
@@ -1216,9 +1478,9 @@ async function createHelloEndpoint(projectPath, config) {
|
|
|
1216
1478
|
" 500",
|
|
1217
1479
|
" );",
|
|
1218
1480
|
" }",
|
|
1219
|
-
"}"
|
|
1220
|
-
].join(
|
|
1221
|
-
await writeFile(path.join(helloPath,
|
|
1481
|
+
"}",
|
|
1482
|
+
].join("\n");
|
|
1483
|
+
await writeFile(path.join(helloPath, "hello.controller.ts"), controllerContent);
|
|
1222
1484
|
const handlerContent = [
|
|
1223
1485
|
"import { Request, Response } from 'express';",
|
|
1224
1486
|
"import { getHello } from './hello.controller.js';",
|
|
@@ -1240,9 +1502,9 @@ async function createHelloEndpoint(projectPath, config) {
|
|
|
1240
1502
|
" // validData is automatically: HelloQuery (not any)",
|
|
1241
1503
|
" (validData) => getHello(validData)",
|
|
1242
1504
|
" );",
|
|
1243
|
-
"}"
|
|
1244
|
-
].join(
|
|
1245
|
-
await writeFile(path.join(helloPath,
|
|
1505
|
+
"}",
|
|
1506
|
+
].join("\n");
|
|
1507
|
+
await writeFile(path.join(helloPath, "hello.handler.ts"), handlerContent);
|
|
1246
1508
|
const routesContent = [
|
|
1247
1509
|
"import { Router } from 'express';",
|
|
1248
1510
|
"import { getHelloHandler } from './hello.handler.js';",
|
|
@@ -1257,10 +1519,10 @@ async function createHelloEndpoint(projectPath, config) {
|
|
|
1257
1519
|
" */",
|
|
1258
1520
|
"router.get('/', getHelloHandler);",
|
|
1259
1521
|
"",
|
|
1260
|
-
"export default router;"
|
|
1261
|
-
].join(
|
|
1262
|
-
await writeFile(path.join(helloPath,
|
|
1263
|
-
if (config.validation ===
|
|
1522
|
+
"export default router;",
|
|
1523
|
+
].join("\n");
|
|
1524
|
+
await writeFile(path.join(helloPath, "hello.routes.ts"), routesContent);
|
|
1525
|
+
if (config.validation === "katax-core") {
|
|
1264
1526
|
const validatorContent = [
|
|
1265
1527
|
"import { k, kataxInfer } from 'katax-core';",
|
|
1266
1528
|
"import type { ValidationResult } from '../../shared/api.utils.js';",
|
|
@@ -1301,9 +1563,9 @@ async function createHelloEndpoint(projectPath, config) {
|
|
|
1301
1563
|
" isValid: true,",
|
|
1302
1564
|
" data: result.data",
|
|
1303
1565
|
" };",
|
|
1304
|
-
"}"
|
|
1305
|
-
].join(
|
|
1306
|
-
await writeFile(path.join(helloPath,
|
|
1566
|
+
"}",
|
|
1567
|
+
].join("\n");
|
|
1568
|
+
await writeFile(path.join(helloPath, "hello.validator.ts"), validatorContent);
|
|
1307
1569
|
}
|
|
1308
1570
|
}
|
|
1309
1571
|
//# sourceMappingURL=init.js.map
|