servcraft 0.2.0 → 0.4.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/.github/workflows/ci.yml +9 -4
- package/README.md +70 -2
- package/ROADMAP.md +124 -47
- package/dist/cli/index.cjs +1331 -407
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1298 -389
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/completion.ts +146 -0
- package/src/cli/commands/doctor.ts +116 -1
- package/src/cli/commands/generate.ts +52 -7
- package/src/cli/commands/list.ts +1 -1
- package/src/cli/commands/scaffold.ts +211 -0
- package/src/cli/commands/templates.ts +147 -0
- package/src/cli/commands/update.ts +221 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/templates/controller-test.ts +110 -0
- package/src/cli/templates/integration-test.ts +139 -0
- package/src/cli/templates/service-test.ts +100 -0
- package/src/cli/utils/template-loader.ts +80 -0
- package/tests/cli/add.test.ts +32 -0
- package/tests/cli/completion.test.ts +35 -0
- package/tests/cli/doctor.test.ts +23 -0
- package/tests/cli/dry-run.test.ts +39 -0
- package/tests/cli/errors.test.ts +29 -0
- package/tests/cli/generate.test.ts +39 -0
- package/tests/cli/init.test.ts +63 -0
- package/tests/cli/list.test.ts +25 -0
- package/tests/cli/remove.test.ts +28 -0
- package/tests/cli/update.test.ts +34 -0
package/dist/cli/index.cjs
CHANGED
|
@@ -6,13 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __esm = (fn, res) => function __init() {
|
|
10
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
-
};
|
|
12
|
-
var __export = (target, all) => {
|
|
13
|
-
for (var name in all)
|
|
14
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
-
};
|
|
16
9
|
var __copyProps = (to, from, except, desc) => {
|
|
17
10
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
11
|
for (let key of __getOwnPropNames(from))
|
|
@@ -29,197 +22,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
22
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
23
|
mod
|
|
31
24
|
));
|
|
32
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
25
|
|
|
34
26
|
// node_modules/tsup/assets/cjs_shims.js
|
|
35
|
-
var getImportMetaUrl,
|
|
36
|
-
var
|
|
37
|
-
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
38
|
-
"use strict";
|
|
39
|
-
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
40
|
-
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// src/cli/utils/error-handler.ts
|
|
45
|
-
var error_handler_exports = {};
|
|
46
|
-
__export(error_handler_exports, {
|
|
47
|
-
ErrorTypes: () => ErrorTypes,
|
|
48
|
-
ServCraftError: () => ServCraftError,
|
|
49
|
-
displayError: () => displayError,
|
|
50
|
-
handleSystemError: () => handleSystemError,
|
|
51
|
-
validateProject: () => validateProject
|
|
52
|
-
});
|
|
53
|
-
function displayError(error2) {
|
|
54
|
-
console.error("\n" + import_chalk4.default.red.bold("\u2717 Error: ") + import_chalk4.default.red(error2.message));
|
|
55
|
-
if (error2 instanceof ServCraftError) {
|
|
56
|
-
if (error2.suggestions.length > 0) {
|
|
57
|
-
console.log("\n" + import_chalk4.default.yellow.bold("\u{1F4A1} Suggestions:"));
|
|
58
|
-
error2.suggestions.forEach((suggestion) => {
|
|
59
|
-
console.log(import_chalk4.default.yellow(" \u2022 ") + suggestion);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
if (error2.docsLink) {
|
|
63
|
-
console.log("\n" + import_chalk4.default.blue.bold("\u{1F4DA} Documentation: ") + import_chalk4.default.blue.underline(error2.docsLink));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
function handleSystemError(err) {
|
|
69
|
-
switch (err.code) {
|
|
70
|
-
case "ENOENT":
|
|
71
|
-
return new ServCraftError(
|
|
72
|
-
`File or directory not found: ${err.path}`,
|
|
73
|
-
[`Check if the path exists`, `Create the directory first`]
|
|
74
|
-
);
|
|
75
|
-
case "EACCES":
|
|
76
|
-
case "EPERM":
|
|
77
|
-
return new ServCraftError(
|
|
78
|
-
`Permission denied: ${err.path}`,
|
|
79
|
-
[
|
|
80
|
-
`Check file permissions`,
|
|
81
|
-
`Try running with elevated privileges (not recommended)`,
|
|
82
|
-
`Change ownership of the directory`
|
|
83
|
-
]
|
|
84
|
-
);
|
|
85
|
-
case "EEXIST":
|
|
86
|
-
return new ServCraftError(
|
|
87
|
-
`File or directory already exists: ${err.path}`,
|
|
88
|
-
[`Use a different name`, `Remove the existing file first`, `Use ${import_chalk4.default.cyan("--force")} to overwrite`]
|
|
89
|
-
);
|
|
90
|
-
case "ENOTDIR":
|
|
91
|
-
return new ServCraftError(
|
|
92
|
-
`Not a directory: ${err.path}`,
|
|
93
|
-
[`Check the path`, `A file exists where a directory is expected`]
|
|
94
|
-
);
|
|
95
|
-
case "EISDIR":
|
|
96
|
-
return new ServCraftError(
|
|
97
|
-
`Is a directory: ${err.path}`,
|
|
98
|
-
[`Cannot perform this operation on a directory`, `Did you mean to target a file?`]
|
|
99
|
-
);
|
|
100
|
-
default:
|
|
101
|
-
return new ServCraftError(
|
|
102
|
-
err.message,
|
|
103
|
-
[`Check system error code: ${err.code}`, `Review the error details above`]
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function validateProject() {
|
|
108
|
-
try {
|
|
109
|
-
const fs10 = require("fs");
|
|
110
|
-
const path11 = require("path");
|
|
111
|
-
if (!fs10.existsSync("package.json")) {
|
|
112
|
-
return ErrorTypes.NOT_IN_PROJECT();
|
|
113
|
-
}
|
|
114
|
-
const packageJson = JSON.parse(fs10.readFileSync("package.json", "utf-8"));
|
|
115
|
-
if (!packageJson.dependencies?.fastify) {
|
|
116
|
-
return new ServCraftError(
|
|
117
|
-
"This does not appear to be a ServCraft project",
|
|
118
|
-
[
|
|
119
|
-
`ServCraft projects require Fastify`,
|
|
120
|
-
`Run ${import_chalk4.default.cyan("servcraft init")} to create a new project`
|
|
121
|
-
]
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
return null;
|
|
125
|
-
} catch (err) {
|
|
126
|
-
return new ServCraftError(
|
|
127
|
-
"Failed to validate project",
|
|
128
|
-
[`Ensure you are in the project root directory`, `Check if ${import_chalk4.default.yellow("package.json")} is valid`]
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
var import_chalk4, ServCraftError, ErrorTypes;
|
|
133
|
-
var init_error_handler = __esm({
|
|
134
|
-
"src/cli/utils/error-handler.ts"() {
|
|
135
|
-
"use strict";
|
|
136
|
-
init_cjs_shims();
|
|
137
|
-
import_chalk4 = __toESM(require("chalk"), 1);
|
|
138
|
-
ServCraftError = class extends Error {
|
|
139
|
-
suggestions;
|
|
140
|
-
docsLink;
|
|
141
|
-
constructor(message, suggestions = [], docsLink) {
|
|
142
|
-
super(message);
|
|
143
|
-
this.name = "ServCraftError";
|
|
144
|
-
this.suggestions = suggestions;
|
|
145
|
-
this.docsLink = docsLink;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
ErrorTypes = {
|
|
149
|
-
MODULE_NOT_FOUND: (moduleName) => new ServCraftError(
|
|
150
|
-
`Module "${moduleName}" not found`,
|
|
151
|
-
[
|
|
152
|
-
`Run ${import_chalk4.default.cyan("servcraft list")} to see available modules`,
|
|
153
|
-
`Check the spelling of the module name`,
|
|
154
|
-
`Visit ${import_chalk4.default.blue("https://github.com/Le-Sourcier/servcraft#modules")} for module list`
|
|
155
|
-
],
|
|
156
|
-
"https://github.com/Le-Sourcier/servcraft#add-pre-built-modules"
|
|
157
|
-
),
|
|
158
|
-
MODULE_ALREADY_EXISTS: (moduleName) => new ServCraftError(
|
|
159
|
-
`Module "${moduleName}" already exists`,
|
|
160
|
-
[
|
|
161
|
-
`Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --force")} to overwrite`,
|
|
162
|
-
`Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --update")} to update`,
|
|
163
|
-
`Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --skip-existing")} to skip`
|
|
164
|
-
]
|
|
165
|
-
),
|
|
166
|
-
NOT_IN_PROJECT: () => new ServCraftError(
|
|
167
|
-
"Not in a ServCraft project directory",
|
|
168
|
-
[
|
|
169
|
-
`Run ${import_chalk4.default.cyan("servcraft init")} to create a new project`,
|
|
170
|
-
`Navigate to your ServCraft project directory`,
|
|
171
|
-
`Check if ${import_chalk4.default.yellow("package.json")} exists`
|
|
172
|
-
],
|
|
173
|
-
"https://github.com/Le-Sourcier/servcraft#initialize-project"
|
|
174
|
-
),
|
|
175
|
-
FILE_ALREADY_EXISTS: (fileName) => new ServCraftError(
|
|
176
|
-
`File "${fileName}" already exists`,
|
|
177
|
-
[
|
|
178
|
-
`Use ${import_chalk4.default.cyan("--force")} flag to overwrite`,
|
|
179
|
-
`Choose a different name`,
|
|
180
|
-
`Delete the existing file first`
|
|
181
|
-
]
|
|
182
|
-
),
|
|
183
|
-
INVALID_DATABASE: (database) => new ServCraftError(
|
|
184
|
-
`Invalid database type: "${database}"`,
|
|
185
|
-
[
|
|
186
|
-
`Valid options: ${import_chalk4.default.cyan("postgresql, mysql, sqlite, mongodb, none")}`,
|
|
187
|
-
`Use ${import_chalk4.default.cyan("servcraft init --db postgresql")} for PostgreSQL`
|
|
188
|
-
]
|
|
189
|
-
),
|
|
190
|
-
INVALID_VALIDATOR: (validator) => new ServCraftError(
|
|
191
|
-
`Invalid validator type: "${validator}"`,
|
|
192
|
-
[`Valid options: ${import_chalk4.default.cyan("zod, joi, yup")}`, `Default is ${import_chalk4.default.cyan("zod")}`]
|
|
193
|
-
),
|
|
194
|
-
MISSING_DEPENDENCY: (dependency, command) => new ServCraftError(
|
|
195
|
-
`Missing dependency: "${dependency}"`,
|
|
196
|
-
[`Run ${import_chalk4.default.cyan(command)} to install`, `Check your ${import_chalk4.default.yellow("package.json")}`]
|
|
197
|
-
),
|
|
198
|
-
INVALID_FIELD_FORMAT: (field) => new ServCraftError(
|
|
199
|
-
`Invalid field format: "${field}"`,
|
|
200
|
-
[
|
|
201
|
-
`Expected format: ${import_chalk4.default.cyan("name:type")}`,
|
|
202
|
-
`Example: ${import_chalk4.default.cyan("name:string age:number isActive:boolean")}`,
|
|
203
|
-
`Supported types: string, number, boolean, date`
|
|
204
|
-
]
|
|
205
|
-
),
|
|
206
|
-
GIT_NOT_INITIALIZED: () => new ServCraftError(
|
|
207
|
-
"Git repository not initialized",
|
|
208
|
-
[
|
|
209
|
-
`Run ${import_chalk4.default.cyan("git init")} to initialize git`,
|
|
210
|
-
`This is required for some ServCraft features`
|
|
211
|
-
]
|
|
212
|
-
)
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
});
|
|
27
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
28
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
216
29
|
|
|
217
30
|
// src/cli/index.ts
|
|
218
|
-
|
|
219
|
-
var import_commander9 = require("commander");
|
|
31
|
+
var import_commander13 = require("commander");
|
|
220
32
|
|
|
221
33
|
// src/cli/commands/init.ts
|
|
222
|
-
init_cjs_shims();
|
|
223
34
|
var import_commander = require("commander");
|
|
224
35
|
var import_path3 = __toESM(require("path"), 1);
|
|
225
36
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
@@ -229,13 +40,11 @@ var import_chalk3 = __toESM(require("chalk"), 1);
|
|
|
229
40
|
var import_child_process = require("child_process");
|
|
230
41
|
|
|
231
42
|
// src/cli/utils/helpers.ts
|
|
232
|
-
init_cjs_shims();
|
|
233
43
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
234
44
|
var import_path2 = __toESM(require("path"), 1);
|
|
235
45
|
var import_chalk2 = __toESM(require("chalk"), 1);
|
|
236
46
|
|
|
237
47
|
// src/cli/utils/dry-run.ts
|
|
238
|
-
init_cjs_shims();
|
|
239
48
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
240
49
|
var import_path = __toESM(require("path"), 1);
|
|
241
50
|
var DryRunManager = class _DryRunManager {
|
|
@@ -1416,15 +1225,13 @@ declare module 'fastify' {
|
|
|
1416
1225
|
}
|
|
1417
1226
|
|
|
1418
1227
|
// src/cli/commands/generate.ts
|
|
1419
|
-
init_cjs_shims();
|
|
1420
1228
|
var import_commander2 = require("commander");
|
|
1421
|
-
var
|
|
1229
|
+
var import_path5 = __toESM(require("path"), 1);
|
|
1422
1230
|
var import_ora2 = __toESM(require("ora"), 1);
|
|
1423
1231
|
var import_inquirer2 = __toESM(require("inquirer"), 1);
|
|
1424
1232
|
var import_chalk5 = __toESM(require("chalk"), 1);
|
|
1425
1233
|
|
|
1426
1234
|
// src/cli/utils/field-parser.ts
|
|
1427
|
-
init_cjs_shims();
|
|
1428
1235
|
var tsTypeMap = {
|
|
1429
1236
|
string: "string",
|
|
1430
1237
|
number: "number",
|
|
@@ -1566,11 +1373,109 @@ function parseFields(fieldsStr) {
|
|
|
1566
1373
|
return fieldsStr.split(/\s+/).filter(Boolean).map(parseField);
|
|
1567
1374
|
}
|
|
1568
1375
|
|
|
1569
|
-
// src/cli/
|
|
1570
|
-
|
|
1376
|
+
// src/cli/utils/error-handler.ts
|
|
1377
|
+
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
1378
|
+
var ServCraftError = class extends Error {
|
|
1379
|
+
suggestions;
|
|
1380
|
+
docsLink;
|
|
1381
|
+
constructor(message, suggestions = [], docsLink) {
|
|
1382
|
+
super(message);
|
|
1383
|
+
this.name = "ServCraftError";
|
|
1384
|
+
this.suggestions = suggestions;
|
|
1385
|
+
this.docsLink = docsLink;
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
var ErrorTypes = {
|
|
1389
|
+
MODULE_NOT_FOUND: (moduleName) => new ServCraftError(
|
|
1390
|
+
`Module "${moduleName}" not found`,
|
|
1391
|
+
[
|
|
1392
|
+
`Run ${import_chalk4.default.cyan("servcraft list")} to see available modules`,
|
|
1393
|
+
`Check the spelling of the module name`,
|
|
1394
|
+
`Visit ${import_chalk4.default.blue("https://github.com/Le-Sourcier/servcraft#modules")} for module list`
|
|
1395
|
+
],
|
|
1396
|
+
"https://github.com/Le-Sourcier/servcraft#add-pre-built-modules"
|
|
1397
|
+
),
|
|
1398
|
+
MODULE_ALREADY_EXISTS: (moduleName) => new ServCraftError(`Module "${moduleName}" already exists`, [
|
|
1399
|
+
`Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --force")} to overwrite`,
|
|
1400
|
+
`Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --update")} to update`,
|
|
1401
|
+
`Use ${import_chalk4.default.cyan("servcraft add " + moduleName + " --skip-existing")} to skip`
|
|
1402
|
+
]),
|
|
1403
|
+
NOT_IN_PROJECT: () => new ServCraftError(
|
|
1404
|
+
"Not in a ServCraft project directory",
|
|
1405
|
+
[
|
|
1406
|
+
`Run ${import_chalk4.default.cyan("servcraft init")} to create a new project`,
|
|
1407
|
+
`Navigate to your ServCraft project directory`,
|
|
1408
|
+
`Check if ${import_chalk4.default.yellow("package.json")} exists`
|
|
1409
|
+
],
|
|
1410
|
+
"https://github.com/Le-Sourcier/servcraft#initialize-project"
|
|
1411
|
+
),
|
|
1412
|
+
FILE_ALREADY_EXISTS: (fileName) => new ServCraftError(`File "${fileName}" already exists`, [
|
|
1413
|
+
`Use ${import_chalk4.default.cyan("--force")} flag to overwrite`,
|
|
1414
|
+
`Choose a different name`,
|
|
1415
|
+
`Delete the existing file first`
|
|
1416
|
+
]),
|
|
1417
|
+
INVALID_DATABASE: (database) => new ServCraftError(`Invalid database type: "${database}"`, [
|
|
1418
|
+
`Valid options: ${import_chalk4.default.cyan("postgresql, mysql, sqlite, mongodb, none")}`,
|
|
1419
|
+
`Use ${import_chalk4.default.cyan("servcraft init --db postgresql")} for PostgreSQL`
|
|
1420
|
+
]),
|
|
1421
|
+
INVALID_VALIDATOR: (validator) => new ServCraftError(`Invalid validator type: "${validator}"`, [
|
|
1422
|
+
`Valid options: ${import_chalk4.default.cyan("zod, joi, yup")}`,
|
|
1423
|
+
`Default is ${import_chalk4.default.cyan("zod")}`
|
|
1424
|
+
]),
|
|
1425
|
+
MISSING_DEPENDENCY: (dependency, command) => new ServCraftError(`Missing dependency: "${dependency}"`, [
|
|
1426
|
+
`Run ${import_chalk4.default.cyan(command)} to install`,
|
|
1427
|
+
`Check your ${import_chalk4.default.yellow("package.json")}`
|
|
1428
|
+
]),
|
|
1429
|
+
INVALID_FIELD_FORMAT: (field) => new ServCraftError(`Invalid field format: "${field}"`, [
|
|
1430
|
+
`Expected format: ${import_chalk4.default.cyan("name:type")}`,
|
|
1431
|
+
`Example: ${import_chalk4.default.cyan("name:string age:number isActive:boolean")}`,
|
|
1432
|
+
`Supported types: string, number, boolean, date`
|
|
1433
|
+
]),
|
|
1434
|
+
GIT_NOT_INITIALIZED: () => new ServCraftError("Git repository not initialized", [
|
|
1435
|
+
`Run ${import_chalk4.default.cyan("git init")} to initialize git`,
|
|
1436
|
+
`This is required for some ServCraft features`
|
|
1437
|
+
])
|
|
1438
|
+
};
|
|
1439
|
+
function displayError(error2) {
|
|
1440
|
+
console.error("\n" + import_chalk4.default.red.bold("\u2717 Error: ") + import_chalk4.default.red(error2.message));
|
|
1441
|
+
if (error2 instanceof ServCraftError) {
|
|
1442
|
+
if (error2.suggestions.length > 0) {
|
|
1443
|
+
console.log("\n" + import_chalk4.default.yellow.bold("\u{1F4A1} Suggestions:"));
|
|
1444
|
+
error2.suggestions.forEach((suggestion) => {
|
|
1445
|
+
console.log(import_chalk4.default.yellow(" \u2022 ") + suggestion);
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
if (error2.docsLink) {
|
|
1449
|
+
console.log(
|
|
1450
|
+
"\n" + import_chalk4.default.blue.bold("\u{1F4DA} Documentation: ") + import_chalk4.default.blue.underline(error2.docsLink)
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
console.log();
|
|
1455
|
+
}
|
|
1456
|
+
function validateProject() {
|
|
1457
|
+
try {
|
|
1458
|
+
const fs14 = require("fs");
|
|
1459
|
+
if (!fs14.existsSync("package.json")) {
|
|
1460
|
+
return ErrorTypes.NOT_IN_PROJECT();
|
|
1461
|
+
}
|
|
1462
|
+
const packageJson = JSON.parse(fs14.readFileSync("package.json", "utf-8"));
|
|
1463
|
+
if (!packageJson.dependencies?.fastify) {
|
|
1464
|
+
return new ServCraftError("This does not appear to be a ServCraft project", [
|
|
1465
|
+
`ServCraft projects require Fastify`,
|
|
1466
|
+
`Run ${import_chalk4.default.cyan("servcraft init")} to create a new project`
|
|
1467
|
+
]);
|
|
1468
|
+
}
|
|
1469
|
+
return null;
|
|
1470
|
+
} catch {
|
|
1471
|
+
return new ServCraftError("Failed to validate project", [
|
|
1472
|
+
`Ensure you are in the project root directory`,
|
|
1473
|
+
`Check if ${import_chalk4.default.yellow("package.json")} is valid`
|
|
1474
|
+
]);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1571
1477
|
|
|
1572
1478
|
// src/cli/templates/controller.ts
|
|
1573
|
-
init_cjs_shims();
|
|
1574
1479
|
function controllerTemplate(name, pascalName, camelName) {
|
|
1575
1480
|
return `import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
1576
1481
|
import type { ${pascalName}Service } from './${name}.service.js';
|
|
@@ -1640,7 +1545,6 @@ export function create${pascalName}Controller(${camelName}Service: ${pascalName}
|
|
|
1640
1545
|
}
|
|
1641
1546
|
|
|
1642
1547
|
// src/cli/templates/service.ts
|
|
1643
|
-
init_cjs_shims();
|
|
1644
1548
|
function serviceTemplate(name, pascalName, camelName) {
|
|
1645
1549
|
return `import type { PaginatedResult, PaginationParams } from '../../types/index.js';
|
|
1646
1550
|
import { NotFoundError, ConflictError } from '../../utils/errors.js';
|
|
@@ -1701,7 +1605,6 @@ export function create${pascalName}Service(repository?: ${pascalName}Repository)
|
|
|
1701
1605
|
}
|
|
1702
1606
|
|
|
1703
1607
|
// src/cli/templates/repository.ts
|
|
1704
|
-
init_cjs_shims();
|
|
1705
1608
|
function repositoryTemplate(name, pascalName, camelName, pluralName) {
|
|
1706
1609
|
return `import { randomUUID } from 'crypto';
|
|
1707
1610
|
import type { PaginatedResult, PaginationParams } from '../../types/index.js';
|
|
@@ -1808,7 +1711,6 @@ export function create${pascalName}Repository(): ${pascalName}Repository {
|
|
|
1808
1711
|
}
|
|
1809
1712
|
|
|
1810
1713
|
// src/cli/templates/types.ts
|
|
1811
|
-
init_cjs_shims();
|
|
1812
1714
|
function typesTemplate(name, pascalName) {
|
|
1813
1715
|
return `import type { BaseEntity } from '../../types/index.js';
|
|
1814
1716
|
|
|
@@ -1838,7 +1740,6 @@ export interface ${pascalName}Filters {
|
|
|
1838
1740
|
}
|
|
1839
1741
|
|
|
1840
1742
|
// src/cli/templates/schemas.ts
|
|
1841
|
-
init_cjs_shims();
|
|
1842
1743
|
function schemasTemplate(name, pascalName, camelName) {
|
|
1843
1744
|
return `import { z } from 'zod';
|
|
1844
1745
|
|
|
@@ -1867,7 +1768,6 @@ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
|
|
|
1867
1768
|
}
|
|
1868
1769
|
|
|
1869
1770
|
// src/cli/templates/routes.ts
|
|
1870
|
-
init_cjs_shims();
|
|
1871
1771
|
function routesTemplate(name, pascalName, camelName, pluralName) {
|
|
1872
1772
|
return `import type { FastifyInstance } from 'fastify';
|
|
1873
1773
|
import type { ${pascalName}Controller } from './${name}.controller.js';
|
|
@@ -1920,7 +1820,6 @@ export function register${pascalName}Routes(
|
|
|
1920
1820
|
}
|
|
1921
1821
|
|
|
1922
1822
|
// src/cli/templates/module-index.ts
|
|
1923
|
-
init_cjs_shims();
|
|
1924
1823
|
function moduleIndexTemplate(name, pascalName, camelName) {
|
|
1925
1824
|
return `import type { FastifyInstance } from 'fastify';
|
|
1926
1825
|
import { logger } from '../../core/logger.js';
|
|
@@ -1956,7 +1855,6 @@ export * from './${name}.schemas.js';
|
|
|
1956
1855
|
}
|
|
1957
1856
|
|
|
1958
1857
|
// src/cli/templates/prisma-model.ts
|
|
1959
|
-
init_cjs_shims();
|
|
1960
1858
|
function prismaModelTemplate(name, pascalName, tableName) {
|
|
1961
1859
|
return `
|
|
1962
1860
|
// Add this model to your prisma/schema.prisma file
|
|
@@ -1976,7 +1874,6 @@ model ${pascalName} {
|
|
|
1976
1874
|
}
|
|
1977
1875
|
|
|
1978
1876
|
// src/cli/templates/dynamic-types.ts
|
|
1979
|
-
init_cjs_shims();
|
|
1980
1877
|
function dynamicTypesTemplate(name, pascalName, fields) {
|
|
1981
1878
|
const fieldLines = fields.map((field) => {
|
|
1982
1879
|
const tsType = tsTypeMap[field.type];
|
|
@@ -2021,7 +1918,6 @@ ${fields.filter((f) => ["string", "enum", "boolean"].includes(f.type)).map((f) =
|
|
|
2021
1918
|
}
|
|
2022
1919
|
|
|
2023
1920
|
// src/cli/templates/dynamic-schemas.ts
|
|
2024
|
-
init_cjs_shims();
|
|
2025
1921
|
function dynamicSchemasTemplate(name, pascalName, camelName, fields, validator = "zod") {
|
|
2026
1922
|
switch (validator) {
|
|
2027
1923
|
case "joi":
|
|
@@ -2200,7 +2096,6 @@ function getJsType(field) {
|
|
|
2200
2096
|
}
|
|
2201
2097
|
|
|
2202
2098
|
// src/cli/templates/dynamic-prisma.ts
|
|
2203
|
-
init_cjs_shims();
|
|
2204
2099
|
function dynamicPrismaTemplate(modelName, tableName, fields) {
|
|
2205
2100
|
const fieldLines = [];
|
|
2206
2101
|
for (const field of fields) {
|
|
@@ -2268,6 +2163,386 @@ ${indexLines.join("\n")}
|
|
|
2268
2163
|
`;
|
|
2269
2164
|
}
|
|
2270
2165
|
|
|
2166
|
+
// src/cli/templates/controller-test.ts
|
|
2167
|
+
function controllerTestTemplate(name, pascalName, camelName) {
|
|
2168
|
+
return `import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2169
|
+
import { build } from '../../app.js';
|
|
2170
|
+
import { FastifyInstance } from 'fastify';
|
|
2171
|
+
|
|
2172
|
+
describe('${pascalName}Controller', () => {
|
|
2173
|
+
let app: FastifyInstance;
|
|
2174
|
+
|
|
2175
|
+
beforeAll(async () => {
|
|
2176
|
+
app = await build();
|
|
2177
|
+
await app.ready();
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
afterAll(async () => {
|
|
2181
|
+
await app.close();
|
|
2182
|
+
});
|
|
2183
|
+
|
|
2184
|
+
describe('GET /${name}', () => {
|
|
2185
|
+
it('should return list of ${name}', async () => {
|
|
2186
|
+
const response = await app.inject({
|
|
2187
|
+
method: 'GET',
|
|
2188
|
+
url: '/${name}',
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
expect(response.statusCode).toBe(200);
|
|
2192
|
+
expect(response.json()).toHaveProperty('data');
|
|
2193
|
+
});
|
|
2194
|
+
});
|
|
2195
|
+
|
|
2196
|
+
describe('GET /${name}/:id', () => {
|
|
2197
|
+
it('should return a single ${camelName}', async () => {
|
|
2198
|
+
// TODO: Create test ${camelName} first
|
|
2199
|
+
const response = await app.inject({
|
|
2200
|
+
method: 'GET',
|
|
2201
|
+
url: '/${name}/1',
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
expect(response.statusCode).toBe(200);
|
|
2205
|
+
expect(response.json()).toHaveProperty('data');
|
|
2206
|
+
});
|
|
2207
|
+
|
|
2208
|
+
it('should return 404 for non-existent ${camelName}', async () => {
|
|
2209
|
+
const response = await app.inject({
|
|
2210
|
+
method: 'GET',
|
|
2211
|
+
url: '/${name}/999999',
|
|
2212
|
+
});
|
|
2213
|
+
|
|
2214
|
+
expect(response.statusCode).toBe(404);
|
|
2215
|
+
});
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
describe('POST /${name}', () => {
|
|
2219
|
+
it('should create a new ${camelName}', async () => {
|
|
2220
|
+
const response = await app.inject({
|
|
2221
|
+
method: 'POST',
|
|
2222
|
+
url: '/${name}',
|
|
2223
|
+
payload: {
|
|
2224
|
+
// TODO: Add required fields
|
|
2225
|
+
},
|
|
2226
|
+
});
|
|
2227
|
+
|
|
2228
|
+
expect(response.statusCode).toBe(201);
|
|
2229
|
+
expect(response.json()).toHaveProperty('data');
|
|
2230
|
+
});
|
|
2231
|
+
|
|
2232
|
+
it('should return 400 for invalid data', async () => {
|
|
2233
|
+
const response = await app.inject({
|
|
2234
|
+
method: 'POST',
|
|
2235
|
+
url: '/${name}',
|
|
2236
|
+
payload: {},
|
|
2237
|
+
});
|
|
2238
|
+
|
|
2239
|
+
expect(response.statusCode).toBe(400);
|
|
2240
|
+
});
|
|
2241
|
+
});
|
|
2242
|
+
|
|
2243
|
+
describe('PUT /${name}/:id', () => {
|
|
2244
|
+
it('should update a ${camelName}', async () => {
|
|
2245
|
+
// TODO: Create test ${camelName} first
|
|
2246
|
+
const response = await app.inject({
|
|
2247
|
+
method: 'PUT',
|
|
2248
|
+
url: '/${name}/1',
|
|
2249
|
+
payload: {
|
|
2250
|
+
// TODO: Add fields to update
|
|
2251
|
+
},
|
|
2252
|
+
});
|
|
2253
|
+
|
|
2254
|
+
expect(response.statusCode).toBe(200);
|
|
2255
|
+
expect(response.json()).toHaveProperty('data');
|
|
2256
|
+
});
|
|
2257
|
+
});
|
|
2258
|
+
|
|
2259
|
+
describe('DELETE /${name}/:id', () => {
|
|
2260
|
+
it('should delete a ${camelName}', async () => {
|
|
2261
|
+
// TODO: Create test ${camelName} first
|
|
2262
|
+
const response = await app.inject({
|
|
2263
|
+
method: 'DELETE',
|
|
2264
|
+
url: '/${name}/1',
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
expect(response.statusCode).toBe(204);
|
|
2268
|
+
});
|
|
2269
|
+
});
|
|
2270
|
+
});
|
|
2271
|
+
`;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
// src/cli/templates/service-test.ts
|
|
2275
|
+
function serviceTestTemplate(name, pascalName, camelName) {
|
|
2276
|
+
return `import { describe, it, expect, beforeEach } from 'vitest';
|
|
2277
|
+
import { ${pascalName}Service } from '../${name}.service.js';
|
|
2278
|
+
|
|
2279
|
+
describe('${pascalName}Service', () => {
|
|
2280
|
+
let service: ${pascalName}Service;
|
|
2281
|
+
|
|
2282
|
+
beforeEach(() => {
|
|
2283
|
+
service = new ${pascalName}Service();
|
|
2284
|
+
});
|
|
2285
|
+
|
|
2286
|
+
describe('getAll', () => {
|
|
2287
|
+
it('should return all ${name}', async () => {
|
|
2288
|
+
const result = await service.getAll();
|
|
2289
|
+
|
|
2290
|
+
expect(result).toBeDefined();
|
|
2291
|
+
expect(Array.isArray(result)).toBe(true);
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
it('should apply pagination', async () => {
|
|
2295
|
+
const result = await service.getAll({ page: 1, limit: 10 });
|
|
2296
|
+
|
|
2297
|
+
expect(result).toBeDefined();
|
|
2298
|
+
expect(result.length).toBeLessThanOrEqual(10);
|
|
2299
|
+
});
|
|
2300
|
+
});
|
|
2301
|
+
|
|
2302
|
+
describe('getById', () => {
|
|
2303
|
+
it('should return a ${camelName} by id', async () => {
|
|
2304
|
+
// TODO: Create test ${camelName} first
|
|
2305
|
+
const id = '1';
|
|
2306
|
+
const result = await service.getById(id);
|
|
2307
|
+
|
|
2308
|
+
expect(result).toBeDefined();
|
|
2309
|
+
expect(result.id).toBe(id);
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2312
|
+
it('should return null for non-existent id', async () => {
|
|
2313
|
+
const result = await service.getById('999999');
|
|
2314
|
+
|
|
2315
|
+
expect(result).toBeNull();
|
|
2316
|
+
});
|
|
2317
|
+
});
|
|
2318
|
+
|
|
2319
|
+
describe('create', () => {
|
|
2320
|
+
it('should create a new ${camelName}', async () => {
|
|
2321
|
+
const data = {
|
|
2322
|
+
// TODO: Add required fields
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
const result = await service.create(data);
|
|
2326
|
+
|
|
2327
|
+
expect(result).toBeDefined();
|
|
2328
|
+
expect(result.id).toBeDefined();
|
|
2329
|
+
});
|
|
2330
|
+
|
|
2331
|
+
it('should throw error for invalid data', async () => {
|
|
2332
|
+
await expect(service.create({} as any)).rejects.toThrow();
|
|
2333
|
+
});
|
|
2334
|
+
});
|
|
2335
|
+
|
|
2336
|
+
describe('update', () => {
|
|
2337
|
+
it('should update a ${camelName}', async () => {
|
|
2338
|
+
// TODO: Create test ${camelName} first
|
|
2339
|
+
const id = '1';
|
|
2340
|
+
const updates = {
|
|
2341
|
+
// TODO: Add fields to update
|
|
2342
|
+
};
|
|
2343
|
+
|
|
2344
|
+
const result = await service.update(id, updates);
|
|
2345
|
+
|
|
2346
|
+
expect(result).toBeDefined();
|
|
2347
|
+
expect(result.id).toBe(id);
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
it('should return null for non-existent id', async () => {
|
|
2351
|
+
const result = await service.update('999999', {});
|
|
2352
|
+
|
|
2353
|
+
expect(result).toBeNull();
|
|
2354
|
+
});
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
describe('delete', () => {
|
|
2358
|
+
it('should delete a ${camelName}', async () => {
|
|
2359
|
+
// TODO: Create test ${camelName} first
|
|
2360
|
+
const id = '1';
|
|
2361
|
+
const result = await service.delete(id);
|
|
2362
|
+
|
|
2363
|
+
expect(result).toBe(true);
|
|
2364
|
+
});
|
|
2365
|
+
|
|
2366
|
+
it('should return false for non-existent id', async () => {
|
|
2367
|
+
const result = await service.delete('999999');
|
|
2368
|
+
|
|
2369
|
+
expect(result).toBe(false);
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2372
|
+
});
|
|
2373
|
+
`;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/cli/templates/integration-test.ts
|
|
2377
|
+
function integrationTestTemplate(name, pascalName, camelName) {
|
|
2378
|
+
return `import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
2379
|
+
import { build } from '../../app.js';
|
|
2380
|
+
import { FastifyInstance } from 'fastify';
|
|
2381
|
+
import { prisma } from '../../lib/prisma.js';
|
|
2382
|
+
|
|
2383
|
+
describe('${pascalName} Integration Tests', () => {
|
|
2384
|
+
let app: FastifyInstance;
|
|
2385
|
+
|
|
2386
|
+
beforeAll(async () => {
|
|
2387
|
+
app = await build();
|
|
2388
|
+
await app.ready();
|
|
2389
|
+
});
|
|
2390
|
+
|
|
2391
|
+
afterAll(async () => {
|
|
2392
|
+
await app.close();
|
|
2393
|
+
await prisma.$disconnect();
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2396
|
+
beforeEach(async () => {
|
|
2397
|
+
// Clean up test data
|
|
2398
|
+
// await prisma.${camelName}.deleteMany();
|
|
2399
|
+
});
|
|
2400
|
+
|
|
2401
|
+
describe('Full CRUD workflow', () => {
|
|
2402
|
+
it('should create, read, update, and delete a ${camelName}', async () => {
|
|
2403
|
+
// Create
|
|
2404
|
+
const createResponse = await app.inject({
|
|
2405
|
+
method: 'POST',
|
|
2406
|
+
url: '/${name}',
|
|
2407
|
+
payload: {
|
|
2408
|
+
// TODO: Add required fields
|
|
2409
|
+
},
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
expect(createResponse.statusCode).toBe(201);
|
|
2413
|
+
const created = createResponse.json().data;
|
|
2414
|
+
expect(created.id).toBeDefined();
|
|
2415
|
+
|
|
2416
|
+
// Read
|
|
2417
|
+
const readResponse = await app.inject({
|
|
2418
|
+
method: 'GET',
|
|
2419
|
+
url: \`/${name}/\${created.id}\`,
|
|
2420
|
+
});
|
|
2421
|
+
|
|
2422
|
+
expect(readResponse.statusCode).toBe(200);
|
|
2423
|
+
const read = readResponse.json().data;
|
|
2424
|
+
expect(read.id).toBe(created.id);
|
|
2425
|
+
|
|
2426
|
+
// Update
|
|
2427
|
+
const updateResponse = await app.inject({
|
|
2428
|
+
method: 'PUT',
|
|
2429
|
+
url: \`/${name}/\${created.id}\`,
|
|
2430
|
+
payload: {
|
|
2431
|
+
// TODO: Add fields to update
|
|
2432
|
+
},
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
expect(updateResponse.statusCode).toBe(200);
|
|
2436
|
+
const updated = updateResponse.json().data;
|
|
2437
|
+
expect(updated.id).toBe(created.id);
|
|
2438
|
+
|
|
2439
|
+
// Delete
|
|
2440
|
+
const deleteResponse = await app.inject({
|
|
2441
|
+
method: 'DELETE',
|
|
2442
|
+
url: \`/${name}/\${created.id}\`,
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
expect(deleteResponse.statusCode).toBe(204);
|
|
2446
|
+
|
|
2447
|
+
// Verify deletion
|
|
2448
|
+
const verifyResponse = await app.inject({
|
|
2449
|
+
method: 'GET',
|
|
2450
|
+
url: \`/${name}/\${created.id}\`,
|
|
2451
|
+
});
|
|
2452
|
+
|
|
2453
|
+
expect(verifyResponse.statusCode).toBe(404);
|
|
2454
|
+
});
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
describe('List and pagination', () => {
|
|
2458
|
+
it('should list ${name} with pagination', async () => {
|
|
2459
|
+
// Create multiple ${name}
|
|
2460
|
+
const count = 5;
|
|
2461
|
+
for (let i = 0; i < count; i++) {
|
|
2462
|
+
await app.inject({
|
|
2463
|
+
method: 'POST',
|
|
2464
|
+
url: '/${name}',
|
|
2465
|
+
payload: {
|
|
2466
|
+
// TODO: Add required fields
|
|
2467
|
+
},
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// Test pagination
|
|
2472
|
+
const response = await app.inject({
|
|
2473
|
+
method: 'GET',
|
|
2474
|
+
url: '/${name}?page=1&limit=3',
|
|
2475
|
+
});
|
|
2476
|
+
|
|
2477
|
+
expect(response.statusCode).toBe(200);
|
|
2478
|
+
const result = response.json();
|
|
2479
|
+
expect(result.data).toBeDefined();
|
|
2480
|
+
expect(result.data.length).toBeLessThanOrEqual(3);
|
|
2481
|
+
expect(result.total).toBeGreaterThanOrEqual(count);
|
|
2482
|
+
});
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
describe('Validation', () => {
|
|
2486
|
+
it('should validate required fields on create', async () => {
|
|
2487
|
+
const response = await app.inject({
|
|
2488
|
+
method: 'POST',
|
|
2489
|
+
url: '/${name}',
|
|
2490
|
+
payload: {},
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
expect(response.statusCode).toBe(400);
|
|
2494
|
+
expect(response.json()).toHaveProperty('error');
|
|
2495
|
+
});
|
|
2496
|
+
|
|
2497
|
+
it('should validate data types', async () => {
|
|
2498
|
+
const response = await app.inject({
|
|
2499
|
+
method: 'POST',
|
|
2500
|
+
url: '/${name}',
|
|
2501
|
+
payload: {
|
|
2502
|
+
// TODO: Add invalid field types
|
|
2503
|
+
},
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
expect(response.statusCode).toBe(400);
|
|
2507
|
+
});
|
|
2508
|
+
});
|
|
2509
|
+
});
|
|
2510
|
+
`;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// src/cli/utils/template-loader.ts
|
|
2514
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
2515
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
2516
|
+
async function loadCustomTemplate(templateType) {
|
|
2517
|
+
const projectRoot = getProjectRoot();
|
|
2518
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
2519
|
+
const locations = [
|
|
2520
|
+
import_path4.default.join(projectRoot, ".servcraft", "templates", `${templateType}.ts`),
|
|
2521
|
+
import_path4.default.join(homeDir, ".servcraft", "templates", `${templateType}.ts`)
|
|
2522
|
+
];
|
|
2523
|
+
for (const location of locations) {
|
|
2524
|
+
try {
|
|
2525
|
+
await import_promises3.default.access(location);
|
|
2526
|
+
const templateModule = await import(`file://${location}`);
|
|
2527
|
+
const functionName = `${templateType.replace(/-/g, "")}Template`;
|
|
2528
|
+
if (templateModule[functionName]) {
|
|
2529
|
+
return templateModule;
|
|
2530
|
+
}
|
|
2531
|
+
} catch {
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
return null;
|
|
2536
|
+
}
|
|
2537
|
+
async function getTemplate(templateType, builtInTemplate) {
|
|
2538
|
+
const customTemplate = await loadCustomTemplate(templateType);
|
|
2539
|
+
if (customTemplate) {
|
|
2540
|
+
const functionName = `${templateType.replace(/-/g, "")}Template`;
|
|
2541
|
+
return customTemplate[functionName];
|
|
2542
|
+
}
|
|
2543
|
+
return builtInTemplate;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2271
2546
|
// src/cli/commands/generate.ts
|
|
2272
2547
|
function enableDryRunIfNeeded(options) {
|
|
2273
2548
|
const dryRun = DryRunManager.getInstance();
|
|
@@ -2284,7 +2559,7 @@ function showDryRunSummary(options) {
|
|
|
2284
2559
|
var generateCommand = new import_commander2.Command("generate").alias("g").description("Generate resources (module, controller, service, etc.)");
|
|
2285
2560
|
generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
2286
2561
|
"Generate a complete module with controller, service, repository, types, schemas, and routes"
|
|
2287
|
-
).option("--no-routes", "Skip routes generation").option("--no-repository", "Skip repository generation").option("--prisma", "Generate Prisma model suggestion").option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("-i, --interactive", "Interactive mode to define fields").option("--dry-run", "Preview changes without writing files").action(async (name, fieldsArgs, options) => {
|
|
2562
|
+
).option("--no-routes", "Skip routes generation").option("--no-repository", "Skip repository generation").option("--prisma", "Generate Prisma model suggestion").option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("-i, --interactive", "Interactive mode to define fields").option("--with-tests", "Generate test files (__tests__ directory)").option("--dry-run", "Preview changes without writing files").action(async (name, fieldsArgs, options) => {
|
|
2288
2563
|
enableDryRunIfNeeded(options);
|
|
2289
2564
|
let fields = [];
|
|
2290
2565
|
if (options.interactive) {
|
|
@@ -2300,46 +2575,71 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
2300
2575
|
const pluralName = pluralize(kebabName);
|
|
2301
2576
|
const tableName = pluralize(kebabName.replace(/-/g, "_"));
|
|
2302
2577
|
const validatorType = options.validator || "zod";
|
|
2303
|
-
const moduleDir =
|
|
2578
|
+
const moduleDir = import_path5.default.join(getModulesDir(), kebabName);
|
|
2304
2579
|
if (await fileExists(moduleDir)) {
|
|
2305
2580
|
spinner.stop();
|
|
2306
2581
|
error(`Module "${kebabName}" already exists`);
|
|
2307
2582
|
return;
|
|
2308
2583
|
}
|
|
2309
2584
|
const hasFields = fields.length > 0;
|
|
2585
|
+
const controllerTpl = await getTemplate("controller", controllerTemplate);
|
|
2586
|
+
const serviceTpl = await getTemplate("service", serviceTemplate);
|
|
2587
|
+
const repositoryTpl = await getTemplate("repository", repositoryTemplate);
|
|
2588
|
+
const typesTpl = await getTemplate("types", typesTemplate);
|
|
2589
|
+
const schemasTpl = await getTemplate("schemas", schemasTemplate);
|
|
2590
|
+
const routesTpl = await getTemplate("routes", routesTemplate);
|
|
2591
|
+
const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
|
|
2310
2592
|
const files = [
|
|
2311
2593
|
{
|
|
2312
2594
|
name: `${kebabName}.types.ts`,
|
|
2313
|
-
content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) :
|
|
2595
|
+
content: hasFields ? dynamicTypesTemplate(kebabName, pascalName, fields) : typesTpl(kebabName, pascalName)
|
|
2314
2596
|
},
|
|
2315
2597
|
{
|
|
2316
2598
|
name: `${kebabName}.schemas.ts`,
|
|
2317
|
-
content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) :
|
|
2599
|
+
content: hasFields ? dynamicSchemasTemplate(kebabName, pascalName, camelName, fields, validatorType) : schemasTpl(kebabName, pascalName, camelName)
|
|
2318
2600
|
},
|
|
2319
2601
|
{
|
|
2320
2602
|
name: `${kebabName}.service.ts`,
|
|
2321
|
-
content:
|
|
2603
|
+
content: serviceTpl(kebabName, pascalName, camelName)
|
|
2322
2604
|
},
|
|
2323
2605
|
{
|
|
2324
2606
|
name: `${kebabName}.controller.ts`,
|
|
2325
|
-
content:
|
|
2607
|
+
content: controllerTpl(kebabName, pascalName, camelName)
|
|
2326
2608
|
},
|
|
2327
|
-
{ name: "index.ts", content:
|
|
2609
|
+
{ name: "index.ts", content: moduleIndexTpl(kebabName, pascalName, camelName) }
|
|
2328
2610
|
];
|
|
2329
2611
|
if (options.repository !== false) {
|
|
2330
2612
|
files.push({
|
|
2331
2613
|
name: `${kebabName}.repository.ts`,
|
|
2332
|
-
content:
|
|
2614
|
+
content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
|
|
2333
2615
|
});
|
|
2334
2616
|
}
|
|
2335
2617
|
if (options.routes !== false) {
|
|
2336
2618
|
files.push({
|
|
2337
2619
|
name: `${kebabName}.routes.ts`,
|
|
2338
|
-
content:
|
|
2620
|
+
content: routesTpl(kebabName, pascalName, camelName, pluralName)
|
|
2339
2621
|
});
|
|
2340
2622
|
}
|
|
2341
2623
|
for (const file of files) {
|
|
2342
|
-
await writeFile(
|
|
2624
|
+
await writeFile(import_path5.default.join(moduleDir, file.name), file.content);
|
|
2625
|
+
}
|
|
2626
|
+
if (options.withTests) {
|
|
2627
|
+
const testDir = import_path5.default.join(moduleDir, "__tests__");
|
|
2628
|
+
const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
|
|
2629
|
+
const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
|
|
2630
|
+
const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
|
|
2631
|
+
await writeFile(
|
|
2632
|
+
import_path5.default.join(testDir, `${kebabName}.controller.test.ts`),
|
|
2633
|
+
controllerTestTpl(kebabName, pascalName, camelName)
|
|
2634
|
+
);
|
|
2635
|
+
await writeFile(
|
|
2636
|
+
import_path5.default.join(testDir, `${kebabName}.service.test.ts`),
|
|
2637
|
+
serviceTestTpl(kebabName, pascalName, camelName)
|
|
2638
|
+
);
|
|
2639
|
+
await writeFile(
|
|
2640
|
+
import_path5.default.join(testDir, `${kebabName}.integration.test.ts`),
|
|
2641
|
+
integrationTestTpl(kebabName, pascalName, camelName)
|
|
2642
|
+
);
|
|
2343
2643
|
}
|
|
2344
2644
|
spinner.succeed(`Module "${pascalName}" generated successfully!`);
|
|
2345
2645
|
if (options.prisma || hasFields) {
|
|
@@ -2364,6 +2664,11 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
2364
2664
|
}
|
|
2365
2665
|
console.log("\n\u{1F4C1} Files created:");
|
|
2366
2666
|
files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
|
|
2667
|
+
if (options.withTests) {
|
|
2668
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
|
|
2669
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
|
|
2670
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
|
|
2671
|
+
}
|
|
2367
2672
|
console.log("\n\u{1F4CC} Next steps:");
|
|
2368
2673
|
if (!hasFields) {
|
|
2369
2674
|
info(` 1. Update the types in ${kebabName}.types.ts`);
|
|
@@ -2391,8 +2696,8 @@ generateCommand.command("controller <name>").alias("c").description("Generate a
|
|
|
2391
2696
|
const pascalName = toPascalCase(name);
|
|
2392
2697
|
const camelName = toCamelCase(name);
|
|
2393
2698
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2394
|
-
const moduleDir =
|
|
2395
|
-
const filePath =
|
|
2699
|
+
const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
|
|
2700
|
+
const filePath = import_path5.default.join(moduleDir, `${kebabName}.controller.ts`);
|
|
2396
2701
|
if (await fileExists(filePath)) {
|
|
2397
2702
|
spinner.stop();
|
|
2398
2703
|
displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
|
|
@@ -2415,8 +2720,8 @@ generateCommand.command("service <name>").alias("s").description("Generate a ser
|
|
|
2415
2720
|
const pascalName = toPascalCase(name);
|
|
2416
2721
|
const camelName = toCamelCase(name);
|
|
2417
2722
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2418
|
-
const moduleDir =
|
|
2419
|
-
const filePath =
|
|
2723
|
+
const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
|
|
2724
|
+
const filePath = import_path5.default.join(moduleDir, `${kebabName}.service.ts`);
|
|
2420
2725
|
if (await fileExists(filePath)) {
|
|
2421
2726
|
spinner.stop();
|
|
2422
2727
|
error(`Service "${kebabName}" already exists`);
|
|
@@ -2440,8 +2745,8 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
|
|
|
2440
2745
|
const camelName = toCamelCase(name);
|
|
2441
2746
|
const pluralName = pluralize(kebabName);
|
|
2442
2747
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2443
|
-
const moduleDir =
|
|
2444
|
-
const filePath =
|
|
2748
|
+
const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
|
|
2749
|
+
const filePath = import_path5.default.join(moduleDir, `${kebabName}.repository.ts`);
|
|
2445
2750
|
if (await fileExists(filePath)) {
|
|
2446
2751
|
spinner.stop();
|
|
2447
2752
|
error(`Repository "${kebabName}" already exists`);
|
|
@@ -2463,8 +2768,8 @@ generateCommand.command("types <name>").alias("t").description("Generate types/i
|
|
|
2463
2768
|
const kebabName = toKebabCase(name);
|
|
2464
2769
|
const pascalName = toPascalCase(name);
|
|
2465
2770
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2466
|
-
const moduleDir =
|
|
2467
|
-
const filePath =
|
|
2771
|
+
const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
|
|
2772
|
+
const filePath = import_path5.default.join(moduleDir, `${kebabName}.types.ts`);
|
|
2468
2773
|
if (await fileExists(filePath)) {
|
|
2469
2774
|
spinner.stop();
|
|
2470
2775
|
error(`Types file "${kebabName}.types.ts" already exists`);
|
|
@@ -2487,8 +2792,8 @@ generateCommand.command("schema <name>").alias("v").description("Generate valida
|
|
|
2487
2792
|
const pascalName = toPascalCase(name);
|
|
2488
2793
|
const camelName = toCamelCase(name);
|
|
2489
2794
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2490
|
-
const moduleDir =
|
|
2491
|
-
const filePath =
|
|
2795
|
+
const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
|
|
2796
|
+
const filePath = import_path5.default.join(moduleDir, `${kebabName}.schemas.ts`);
|
|
2492
2797
|
if (await fileExists(filePath)) {
|
|
2493
2798
|
spinner.stop();
|
|
2494
2799
|
error(`Schemas file "${kebabName}.schemas.ts" already exists`);
|
|
@@ -2512,8 +2817,8 @@ generateCommand.command("routes <name>").description("Generate routes").option("
|
|
|
2512
2817
|
const camelName = toCamelCase(name);
|
|
2513
2818
|
const pluralName = pluralize(kebabName);
|
|
2514
2819
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2515
|
-
const moduleDir =
|
|
2516
|
-
const filePath =
|
|
2820
|
+
const moduleDir = import_path5.default.join(getModulesDir(), moduleName);
|
|
2821
|
+
const filePath = import_path5.default.join(moduleDir, `${kebabName}.routes.ts`);
|
|
2517
2822
|
if (await fileExists(filePath)) {
|
|
2518
2823
|
spinner.stop();
|
|
2519
2824
|
error(`Routes file "${kebabName}.routes.ts" already exists`);
|
|
@@ -2600,24 +2905,22 @@ async function promptForFields() {
|
|
|
2600
2905
|
}
|
|
2601
2906
|
|
|
2602
2907
|
// src/cli/commands/add-module.ts
|
|
2603
|
-
init_cjs_shims();
|
|
2604
2908
|
var import_commander3 = require("commander");
|
|
2605
|
-
var
|
|
2909
|
+
var import_path6 = __toESM(require("path"), 1);
|
|
2606
2910
|
var import_ora3 = __toESM(require("ora"), 1);
|
|
2607
2911
|
var import_chalk7 = __toESM(require("chalk"), 1);
|
|
2608
|
-
var
|
|
2912
|
+
var fs6 = __toESM(require("fs/promises"), 1);
|
|
2609
2913
|
|
|
2610
2914
|
// src/cli/utils/env-manager.ts
|
|
2611
|
-
|
|
2612
|
-
var
|
|
2613
|
-
var path5 = __toESM(require("path"), 1);
|
|
2915
|
+
var fs4 = __toESM(require("fs/promises"), 1);
|
|
2916
|
+
var path6 = __toESM(require("path"), 1);
|
|
2614
2917
|
var import_fs = require("fs");
|
|
2615
2918
|
var EnvManager = class {
|
|
2616
2919
|
envPath;
|
|
2617
2920
|
envExamplePath;
|
|
2618
2921
|
constructor(projectRoot) {
|
|
2619
|
-
this.envPath =
|
|
2620
|
-
this.envExamplePath =
|
|
2922
|
+
this.envPath = path6.join(projectRoot, ".env");
|
|
2923
|
+
this.envExamplePath = path6.join(projectRoot, ".env.example");
|
|
2621
2924
|
}
|
|
2622
2925
|
/**
|
|
2623
2926
|
* Add environment variables to .env file
|
|
@@ -2628,7 +2931,7 @@ var EnvManager = class {
|
|
|
2628
2931
|
let created2 = false;
|
|
2629
2932
|
let envContent = "";
|
|
2630
2933
|
if ((0, import_fs.existsSync)(this.envPath)) {
|
|
2631
|
-
envContent = await
|
|
2934
|
+
envContent = await fs4.readFile(this.envPath, "utf-8");
|
|
2632
2935
|
} else {
|
|
2633
2936
|
created2 = true;
|
|
2634
2937
|
}
|
|
@@ -2657,7 +2960,7 @@ var EnvManager = class {
|
|
|
2657
2960
|
}
|
|
2658
2961
|
newContent += "\n";
|
|
2659
2962
|
}
|
|
2660
|
-
await
|
|
2963
|
+
await fs4.writeFile(this.envPath, newContent, "utf-8");
|
|
2661
2964
|
if ((0, import_fs.existsSync)(this.envExamplePath)) {
|
|
2662
2965
|
await this.updateEnvExample(sections);
|
|
2663
2966
|
}
|
|
@@ -2669,7 +2972,7 @@ var EnvManager = class {
|
|
|
2669
2972
|
async updateEnvExample(sections) {
|
|
2670
2973
|
let exampleContent = "";
|
|
2671
2974
|
if ((0, import_fs.existsSync)(this.envExamplePath)) {
|
|
2672
|
-
exampleContent = await
|
|
2975
|
+
exampleContent = await fs4.readFile(this.envExamplePath, "utf-8");
|
|
2673
2976
|
}
|
|
2674
2977
|
const existingKeys = this.parseExistingKeys(exampleContent);
|
|
2675
2978
|
let newContent = exampleContent;
|
|
@@ -2693,7 +2996,7 @@ var EnvManager = class {
|
|
|
2693
2996
|
}
|
|
2694
2997
|
newContent += "\n";
|
|
2695
2998
|
}
|
|
2696
|
-
await
|
|
2999
|
+
await fs4.writeFile(this.envExamplePath, newContent, "utf-8");
|
|
2697
3000
|
}
|
|
2698
3001
|
/**
|
|
2699
3002
|
* Parse existing environment variable keys
|
|
@@ -3267,35 +3570,34 @@ var EnvManager = class {
|
|
|
3267
3570
|
};
|
|
3268
3571
|
|
|
3269
3572
|
// src/cli/utils/template-manager.ts
|
|
3270
|
-
|
|
3271
|
-
var
|
|
3272
|
-
var path6 = __toESM(require("path"), 1);
|
|
3573
|
+
var fs5 = __toESM(require("fs/promises"), 1);
|
|
3574
|
+
var path7 = __toESM(require("path"), 1);
|
|
3273
3575
|
var import_crypto = require("crypto");
|
|
3274
3576
|
var import_fs2 = require("fs");
|
|
3275
3577
|
var TemplateManager = class {
|
|
3276
3578
|
templatesDir;
|
|
3277
3579
|
manifestsDir;
|
|
3278
3580
|
constructor(projectRoot) {
|
|
3279
|
-
this.templatesDir =
|
|
3280
|
-
this.manifestsDir =
|
|
3581
|
+
this.templatesDir = path7.join(projectRoot, ".servcraft", "templates");
|
|
3582
|
+
this.manifestsDir = path7.join(projectRoot, ".servcraft", "manifests");
|
|
3281
3583
|
}
|
|
3282
3584
|
/**
|
|
3283
3585
|
* Initialize template system
|
|
3284
3586
|
*/
|
|
3285
3587
|
async initialize() {
|
|
3286
|
-
await
|
|
3287
|
-
await
|
|
3588
|
+
await fs5.mkdir(this.templatesDir, { recursive: true });
|
|
3589
|
+
await fs5.mkdir(this.manifestsDir, { recursive: true });
|
|
3288
3590
|
}
|
|
3289
3591
|
/**
|
|
3290
3592
|
* Save module template
|
|
3291
3593
|
*/
|
|
3292
3594
|
async saveTemplate(moduleName, files) {
|
|
3293
3595
|
await this.initialize();
|
|
3294
|
-
const moduleTemplateDir =
|
|
3295
|
-
await
|
|
3596
|
+
const moduleTemplateDir = path7.join(this.templatesDir, moduleName);
|
|
3597
|
+
await fs5.mkdir(moduleTemplateDir, { recursive: true });
|
|
3296
3598
|
for (const [fileName, content] of Object.entries(files)) {
|
|
3297
|
-
const filePath =
|
|
3298
|
-
await
|
|
3599
|
+
const filePath = path7.join(moduleTemplateDir, fileName);
|
|
3600
|
+
await fs5.writeFile(filePath, content, "utf-8");
|
|
3299
3601
|
}
|
|
3300
3602
|
}
|
|
3301
3603
|
/**
|
|
@@ -3303,8 +3605,8 @@ var TemplateManager = class {
|
|
|
3303
3605
|
*/
|
|
3304
3606
|
async getTemplate(moduleName, fileName) {
|
|
3305
3607
|
try {
|
|
3306
|
-
const filePath =
|
|
3307
|
-
return await
|
|
3608
|
+
const filePath = path7.join(this.templatesDir, moduleName, fileName);
|
|
3609
|
+
return await fs5.readFile(filePath, "utf-8");
|
|
3308
3610
|
} catch {
|
|
3309
3611
|
return null;
|
|
3310
3612
|
}
|
|
@@ -3328,16 +3630,16 @@ var TemplateManager = class {
|
|
|
3328
3630
|
installedAt: /* @__PURE__ */ new Date(),
|
|
3329
3631
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3330
3632
|
};
|
|
3331
|
-
const manifestPath =
|
|
3332
|
-
await
|
|
3633
|
+
const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
|
|
3634
|
+
await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
3333
3635
|
}
|
|
3334
3636
|
/**
|
|
3335
3637
|
* Get module manifest
|
|
3336
3638
|
*/
|
|
3337
3639
|
async getManifest(moduleName) {
|
|
3338
3640
|
try {
|
|
3339
|
-
const manifestPath =
|
|
3340
|
-
const content = await
|
|
3641
|
+
const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
|
|
3642
|
+
const content = await fs5.readFile(manifestPath, "utf-8");
|
|
3341
3643
|
return JSON.parse(content);
|
|
3342
3644
|
} catch {
|
|
3343
3645
|
return null;
|
|
@@ -3365,7 +3667,7 @@ var TemplateManager = class {
|
|
|
3365
3667
|
}
|
|
3366
3668
|
const results = [];
|
|
3367
3669
|
for (const [fileName, fileInfo] of Object.entries(manifest.files)) {
|
|
3368
|
-
const filePath =
|
|
3670
|
+
const filePath = path7.join(moduleDir, fileName);
|
|
3369
3671
|
if (!(0, import_fs2.existsSync)(filePath)) {
|
|
3370
3672
|
results.push({
|
|
3371
3673
|
fileName,
|
|
@@ -3375,7 +3677,7 @@ var TemplateManager = class {
|
|
|
3375
3677
|
});
|
|
3376
3678
|
continue;
|
|
3377
3679
|
}
|
|
3378
|
-
const currentContent = await
|
|
3680
|
+
const currentContent = await fs5.readFile(filePath, "utf-8");
|
|
3379
3681
|
const currentHash = this.hashContent(currentContent);
|
|
3380
3682
|
results.push({
|
|
3381
3683
|
fileName,
|
|
@@ -3391,7 +3693,7 @@ var TemplateManager = class {
|
|
|
3391
3693
|
*/
|
|
3392
3694
|
async createBackup(moduleName, moduleDir) {
|
|
3393
3695
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
3394
|
-
const backupDir =
|
|
3696
|
+
const backupDir = path7.join(path7.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
|
|
3395
3697
|
await this.copyDirectory(moduleDir, backupDir);
|
|
3396
3698
|
return backupDir;
|
|
3397
3699
|
}
|
|
@@ -3399,15 +3701,15 @@ var TemplateManager = class {
|
|
|
3399
3701
|
* Copy directory recursively
|
|
3400
3702
|
*/
|
|
3401
3703
|
async copyDirectory(src, dest) {
|
|
3402
|
-
await
|
|
3403
|
-
const entries = await
|
|
3704
|
+
await fs5.mkdir(dest, { recursive: true });
|
|
3705
|
+
const entries = await fs5.readdir(src, { withFileTypes: true });
|
|
3404
3706
|
for (const entry of entries) {
|
|
3405
|
-
const srcPath =
|
|
3406
|
-
const destPath =
|
|
3707
|
+
const srcPath = path7.join(src, entry.name);
|
|
3708
|
+
const destPath = path7.join(dest, entry.name);
|
|
3407
3709
|
if (entry.isDirectory()) {
|
|
3408
3710
|
await this.copyDirectory(srcPath, destPath);
|
|
3409
3711
|
} else {
|
|
3410
|
-
await
|
|
3712
|
+
await fs5.copyFile(srcPath, destPath);
|
|
3411
3713
|
}
|
|
3412
3714
|
}
|
|
3413
3715
|
}
|
|
@@ -3504,13 +3806,12 @@ var TemplateManager = class {
|
|
|
3504
3806
|
}
|
|
3505
3807
|
manifest.files = fileHashes;
|
|
3506
3808
|
manifest.updatedAt = /* @__PURE__ */ new Date();
|
|
3507
|
-
const manifestPath =
|
|
3508
|
-
await
|
|
3809
|
+
const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
|
|
3810
|
+
await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
3509
3811
|
}
|
|
3510
3812
|
};
|
|
3511
3813
|
|
|
3512
3814
|
// src/cli/utils/interactive-prompt.ts
|
|
3513
|
-
init_cjs_shims();
|
|
3514
3815
|
var import_inquirer3 = __toESM(require("inquirer"), 1);
|
|
3515
3816
|
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
3516
3817
|
var InteractivePrompt = class {
|
|
@@ -3696,7 +3997,6 @@ var InteractivePrompt = class {
|
|
|
3696
3997
|
};
|
|
3697
3998
|
|
|
3698
3999
|
// src/cli/commands/add-module.ts
|
|
3699
|
-
init_error_handler();
|
|
3700
4000
|
var AVAILABLE_MODULES = {
|
|
3701
4001
|
auth: {
|
|
3702
4002
|
name: "Authentication",
|
|
@@ -3864,7 +4164,7 @@ var addModuleCommand = new import_commander3.Command("add").description("Add a p
|
|
|
3864
4164
|
}
|
|
3865
4165
|
const spinner = (0, import_ora3.default)(`Adding ${module2.name} module...`).start();
|
|
3866
4166
|
try {
|
|
3867
|
-
const moduleDir =
|
|
4167
|
+
const moduleDir = import_path6.default.join(getModulesDir(), moduleName);
|
|
3868
4168
|
const templateManager = new TemplateManager(process.cwd());
|
|
3869
4169
|
const moduleExists = await fileExists(moduleDir);
|
|
3870
4170
|
if (moduleExists) {
|
|
@@ -3897,7 +4197,7 @@ var addModuleCommand = new import_commander3.Command("add").description("Add a p
|
|
|
3897
4197
|
const backupPath = await templateManager.createBackup(moduleName, moduleDir);
|
|
3898
4198
|
InteractivePrompt.showBackupCreated(backupPath);
|
|
3899
4199
|
}
|
|
3900
|
-
await
|
|
4200
|
+
await fs6.rm(moduleDir, { recursive: true, force: true });
|
|
3901
4201
|
await ensureDir(moduleDir);
|
|
3902
4202
|
await generateModuleFiles(moduleName, moduleDir);
|
|
3903
4203
|
const files = await getModuleFiles(moduleName, moduleDir);
|
|
@@ -4008,7 +4308,7 @@ export * from './auth.schemas.js';
|
|
|
4008
4308
|
`
|
|
4009
4309
|
};
|
|
4010
4310
|
for (const [name, content] of Object.entries(files)) {
|
|
4011
|
-
await writeFile(
|
|
4311
|
+
await writeFile(import_path6.default.join(dir, name), content);
|
|
4012
4312
|
}
|
|
4013
4313
|
}
|
|
4014
4314
|
async function generateUsersModule(dir) {
|
|
@@ -4048,7 +4348,7 @@ export * from './user.schemas.js';
|
|
|
4048
4348
|
`
|
|
4049
4349
|
};
|
|
4050
4350
|
for (const [name, content] of Object.entries(files)) {
|
|
4051
|
-
await writeFile(
|
|
4351
|
+
await writeFile(import_path6.default.join(dir, name), content);
|
|
4052
4352
|
}
|
|
4053
4353
|
}
|
|
4054
4354
|
async function generateEmailModule(dir) {
|
|
@@ -4105,7 +4405,7 @@ export { EmailService, emailService } from './email.service.js';
|
|
|
4105
4405
|
`
|
|
4106
4406
|
};
|
|
4107
4407
|
for (const [name, content] of Object.entries(files)) {
|
|
4108
|
-
await writeFile(
|
|
4408
|
+
await writeFile(import_path6.default.join(dir, name), content);
|
|
4109
4409
|
}
|
|
4110
4410
|
}
|
|
4111
4411
|
async function generateAuditModule(dir) {
|
|
@@ -4149,7 +4449,7 @@ export { AuditService, auditService } from './audit.service.js';
|
|
|
4149
4449
|
`
|
|
4150
4450
|
};
|
|
4151
4451
|
for (const [name, content] of Object.entries(files)) {
|
|
4152
|
-
await writeFile(
|
|
4452
|
+
await writeFile(import_path6.default.join(dir, name), content);
|
|
4153
4453
|
}
|
|
4154
4454
|
}
|
|
4155
4455
|
async function generateUploadModule(dir) {
|
|
@@ -4175,7 +4475,7 @@ export interface UploadOptions {
|
|
|
4175
4475
|
`
|
|
4176
4476
|
};
|
|
4177
4477
|
for (const [name, content] of Object.entries(files)) {
|
|
4178
|
-
await writeFile(
|
|
4478
|
+
await writeFile(import_path6.default.join(dir, name), content);
|
|
4179
4479
|
}
|
|
4180
4480
|
}
|
|
4181
4481
|
async function generateCacheModule(dir) {
|
|
@@ -4221,7 +4521,7 @@ export { CacheService, cacheService } from './cache.service.js';
|
|
|
4221
4521
|
`
|
|
4222
4522
|
};
|
|
4223
4523
|
for (const [name, content] of Object.entries(files)) {
|
|
4224
|
-
await writeFile(
|
|
4524
|
+
await writeFile(import_path6.default.join(dir, name), content);
|
|
4225
4525
|
}
|
|
4226
4526
|
}
|
|
4227
4527
|
async function generateGenericModule(dir, name) {
|
|
@@ -4235,22 +4535,22 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
|
|
|
4235
4535
|
`
|
|
4236
4536
|
};
|
|
4237
4537
|
for (const [fileName, content] of Object.entries(files)) {
|
|
4238
|
-
await writeFile(
|
|
4538
|
+
await writeFile(import_path6.default.join(dir, fileName), content);
|
|
4239
4539
|
}
|
|
4240
4540
|
}
|
|
4241
4541
|
async function findServercraftModules() {
|
|
4242
|
-
const scriptDir =
|
|
4542
|
+
const scriptDir = import_path6.default.dirname(new URL(importMetaUrl).pathname);
|
|
4243
4543
|
const possiblePaths = [
|
|
4244
4544
|
// Local node_modules (when servcraft is a dependency)
|
|
4245
|
-
|
|
4545
|
+
import_path6.default.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
|
|
4246
4546
|
// From dist/cli/index.js -> src/modules (npx or global install)
|
|
4247
|
-
|
|
4547
|
+
import_path6.default.resolve(scriptDir, "..", "..", "src", "modules"),
|
|
4248
4548
|
// From src/cli/commands/add-module.ts -> src/modules (development)
|
|
4249
|
-
|
|
4549
|
+
import_path6.default.resolve(scriptDir, "..", "..", "modules")
|
|
4250
4550
|
];
|
|
4251
4551
|
for (const p of possiblePaths) {
|
|
4252
4552
|
try {
|
|
4253
|
-
const stats = await
|
|
4553
|
+
const stats = await fs6.stat(p);
|
|
4254
4554
|
if (stats.isDirectory()) {
|
|
4255
4555
|
return p;
|
|
4256
4556
|
}
|
|
@@ -4270,7 +4570,7 @@ async function generateModuleFiles(moduleName, moduleDir) {
|
|
|
4270
4570
|
const sourceDirName = moduleNameMap[moduleName] || moduleName;
|
|
4271
4571
|
const servercraftModulesDir = await findServercraftModules();
|
|
4272
4572
|
if (servercraftModulesDir) {
|
|
4273
|
-
const sourceModuleDir =
|
|
4573
|
+
const sourceModuleDir = import_path6.default.join(servercraftModulesDir, sourceDirName);
|
|
4274
4574
|
if (await fileExists(sourceModuleDir)) {
|
|
4275
4575
|
await copyModuleFromSource(sourceModuleDir, moduleDir);
|
|
4276
4576
|
return;
|
|
@@ -4300,26 +4600,26 @@ async function generateModuleFiles(moduleName, moduleDir) {
|
|
|
4300
4600
|
}
|
|
4301
4601
|
}
|
|
4302
4602
|
async function copyModuleFromSource(sourceDir, targetDir) {
|
|
4303
|
-
const entries = await
|
|
4603
|
+
const entries = await fs6.readdir(sourceDir, { withFileTypes: true });
|
|
4304
4604
|
for (const entry of entries) {
|
|
4305
|
-
const sourcePath =
|
|
4306
|
-
const targetPath =
|
|
4605
|
+
const sourcePath = import_path6.default.join(sourceDir, entry.name);
|
|
4606
|
+
const targetPath = import_path6.default.join(targetDir, entry.name);
|
|
4307
4607
|
if (entry.isDirectory()) {
|
|
4308
|
-
await
|
|
4608
|
+
await fs6.mkdir(targetPath, { recursive: true });
|
|
4309
4609
|
await copyModuleFromSource(sourcePath, targetPath);
|
|
4310
4610
|
} else {
|
|
4311
|
-
await
|
|
4611
|
+
await fs6.copyFile(sourcePath, targetPath);
|
|
4312
4612
|
}
|
|
4313
4613
|
}
|
|
4314
4614
|
}
|
|
4315
4615
|
async function getModuleFiles(moduleName, moduleDir) {
|
|
4316
4616
|
const files = {};
|
|
4317
|
-
const entries = await
|
|
4617
|
+
const entries = await fs6.readdir(moduleDir);
|
|
4318
4618
|
for (const entry of entries) {
|
|
4319
|
-
const filePath =
|
|
4320
|
-
const stat2 = await
|
|
4619
|
+
const filePath = import_path6.default.join(moduleDir, entry);
|
|
4620
|
+
const stat2 = await fs6.stat(filePath);
|
|
4321
4621
|
if (stat2.isFile() && entry.endsWith(".ts")) {
|
|
4322
|
-
const content = await
|
|
4622
|
+
const content = await fs6.readFile(filePath, "utf-8");
|
|
4323
4623
|
files[entry] = content;
|
|
4324
4624
|
}
|
|
4325
4625
|
}
|
|
@@ -4334,8 +4634,8 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
|
|
|
4334
4634
|
if (file.isModified) {
|
|
4335
4635
|
console.log(import_chalk7.default.yellow(`
|
|
4336
4636
|
\u{1F4C4} ${file.fileName}:`));
|
|
4337
|
-
const currentPath =
|
|
4338
|
-
const currentContent = await
|
|
4637
|
+
const currentPath = import_path6.default.join(moduleDir, file.fileName);
|
|
4638
|
+
const currentContent = await fs6.readFile(currentPath, "utf-8");
|
|
4339
4639
|
const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
|
|
4340
4640
|
if (originalContent) {
|
|
4341
4641
|
const diff = templateManager.generateDiff(originalContent, currentContent);
|
|
@@ -4347,11 +4647,11 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
|
|
|
4347
4647
|
async function performSmartMerge(templateManager, moduleName, moduleDir, _displayName) {
|
|
4348
4648
|
const spinner = (0, import_ora3.default)("Analyzing files for merge...").start();
|
|
4349
4649
|
const newFiles = {};
|
|
4350
|
-
const templateDir =
|
|
4650
|
+
const templateDir = import_path6.default.join(templateManager["templatesDir"], moduleName);
|
|
4351
4651
|
try {
|
|
4352
|
-
const entries = await
|
|
4652
|
+
const entries = await fs6.readdir(templateDir);
|
|
4353
4653
|
for (const entry of entries) {
|
|
4354
|
-
const content = await
|
|
4654
|
+
const content = await fs6.readFile(import_path6.default.join(templateDir, entry), "utf-8");
|
|
4355
4655
|
newFiles[entry] = content;
|
|
4356
4656
|
}
|
|
4357
4657
|
} catch {
|
|
@@ -4369,7 +4669,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4369
4669
|
};
|
|
4370
4670
|
for (const fileInfo of modifiedFiles) {
|
|
4371
4671
|
const fileName = fileInfo.fileName;
|
|
4372
|
-
const filePath =
|
|
4672
|
+
const filePath = import_path6.default.join(moduleDir, fileName);
|
|
4373
4673
|
const newContent = newFiles[fileName];
|
|
4374
4674
|
if (!newContent) {
|
|
4375
4675
|
continue;
|
|
@@ -4382,7 +4682,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4382
4682
|
} else if (batchAction === "overwrite-all") {
|
|
4383
4683
|
fileAction = "overwrite";
|
|
4384
4684
|
} else {
|
|
4385
|
-
const currentContent = await
|
|
4685
|
+
const currentContent = await fs6.readFile(filePath, "utf-8");
|
|
4386
4686
|
const yourLines = currentContent.split("\n").length;
|
|
4387
4687
|
const newLines = newContent.split("\n").length;
|
|
4388
4688
|
const choice = await InteractivePrompt.askFileAction(
|
|
@@ -4406,20 +4706,20 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4406
4706
|
continue;
|
|
4407
4707
|
}
|
|
4408
4708
|
if (fileAction === "overwrite") {
|
|
4409
|
-
await
|
|
4709
|
+
await fs6.writeFile(filePath, newContent, "utf-8");
|
|
4410
4710
|
stats.overwritten++;
|
|
4411
4711
|
continue;
|
|
4412
4712
|
}
|
|
4413
4713
|
if (fileAction === "merge") {
|
|
4414
4714
|
const originalContent = await templateManager.getTemplate(moduleName, fileName);
|
|
4415
|
-
const currentContent = await
|
|
4715
|
+
const currentContent = await fs6.readFile(filePath, "utf-8");
|
|
4416
4716
|
if (originalContent) {
|
|
4417
4717
|
const mergeResult = await templateManager.mergeFiles(
|
|
4418
4718
|
originalContent,
|
|
4419
4719
|
currentContent,
|
|
4420
4720
|
newContent
|
|
4421
4721
|
);
|
|
4422
|
-
await
|
|
4722
|
+
await fs6.writeFile(filePath, mergeResult.merged, "utf-8");
|
|
4423
4723
|
if (mergeResult.hasConflicts) {
|
|
4424
4724
|
stats.conflicts++;
|
|
4425
4725
|
InteractivePrompt.displayConflicts(mergeResult.conflicts);
|
|
@@ -4427,7 +4727,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4427
4727
|
stats.merged++;
|
|
4428
4728
|
}
|
|
4429
4729
|
} else {
|
|
4430
|
-
await
|
|
4730
|
+
await fs6.writeFile(filePath, newContent, "utf-8");
|
|
4431
4731
|
stats.overwritten++;
|
|
4432
4732
|
}
|
|
4433
4733
|
}
|
|
@@ -4438,7 +4738,6 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4438
4738
|
}
|
|
4439
4739
|
|
|
4440
4740
|
// src/cli/commands/db.ts
|
|
4441
|
-
init_cjs_shims();
|
|
4442
4741
|
var import_commander4 = require("commander");
|
|
4443
4742
|
var import_child_process2 = require("child_process");
|
|
4444
4743
|
var import_ora4 = __toESM(require("ora"), 1);
|
|
@@ -4532,25 +4831,21 @@ dbCommand.command("status").description("Show migration status").action(async ()
|
|
|
4532
4831
|
});
|
|
4533
4832
|
|
|
4534
4833
|
// src/cli/commands/docs.ts
|
|
4535
|
-
init_cjs_shims();
|
|
4536
4834
|
var import_commander5 = require("commander");
|
|
4537
|
-
var
|
|
4538
|
-
var
|
|
4835
|
+
var import_path8 = __toESM(require("path"), 1);
|
|
4836
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
4539
4837
|
var import_ora6 = __toESM(require("ora"), 1);
|
|
4540
4838
|
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
4541
4839
|
|
|
4542
4840
|
// src/cli/utils/docs-generator.ts
|
|
4543
|
-
|
|
4544
|
-
var
|
|
4545
|
-
var import_path6 = __toESM(require("path"), 1);
|
|
4841
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
4842
|
+
var import_path7 = __toESM(require("path"), 1);
|
|
4546
4843
|
var import_ora5 = __toESM(require("ora"), 1);
|
|
4547
4844
|
|
|
4548
4845
|
// src/core/server.ts
|
|
4549
|
-
init_cjs_shims();
|
|
4550
4846
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4551
4847
|
|
|
4552
4848
|
// src/core/logger.ts
|
|
4553
|
-
init_cjs_shims();
|
|
4554
4849
|
var import_pino = __toESM(require("pino"), 1);
|
|
4555
4850
|
var defaultConfig = {
|
|
4556
4851
|
level: process.env.LOG_LEVEL || "info",
|
|
@@ -4683,14 +4978,7 @@ function createServer(config2 = {}) {
|
|
|
4683
4978
|
return new Server(config2);
|
|
4684
4979
|
}
|
|
4685
4980
|
|
|
4686
|
-
// src/middleware/index.ts
|
|
4687
|
-
init_cjs_shims();
|
|
4688
|
-
|
|
4689
|
-
// src/middleware/error-handler.ts
|
|
4690
|
-
init_cjs_shims();
|
|
4691
|
-
|
|
4692
4981
|
// src/utils/errors.ts
|
|
4693
|
-
init_cjs_shims();
|
|
4694
4982
|
var AppError = class _AppError extends Error {
|
|
4695
4983
|
statusCode;
|
|
4696
4984
|
isOperational;
|
|
@@ -4744,11 +5032,7 @@ function isAppError(error2) {
|
|
|
4744
5032
|
return error2 instanceof AppError;
|
|
4745
5033
|
}
|
|
4746
5034
|
|
|
4747
|
-
// src/config/index.ts
|
|
4748
|
-
init_cjs_shims();
|
|
4749
|
-
|
|
4750
5035
|
// src/config/env.ts
|
|
4751
|
-
init_cjs_shims();
|
|
4752
5036
|
var import_zod = require("zod");
|
|
4753
5037
|
var import_dotenv = __toESM(require("dotenv"), 1);
|
|
4754
5038
|
import_dotenv.default.config();
|
|
@@ -4894,7 +5178,6 @@ function registerErrorHandler(app) {
|
|
|
4894
5178
|
}
|
|
4895
5179
|
|
|
4896
5180
|
// src/middleware/security.ts
|
|
4897
|
-
init_cjs_shims();
|
|
4898
5181
|
var import_helmet = __toESM(require("@fastify/helmet"), 1);
|
|
4899
5182
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4900
5183
|
var import_rate_limit = __toESM(require("@fastify/rate-limit"), 1);
|
|
@@ -4954,11 +5237,7 @@ async function registerSecurity(app, options = {}) {
|
|
|
4954
5237
|
}
|
|
4955
5238
|
}
|
|
4956
5239
|
|
|
4957
|
-
// src/modules/swagger/index.ts
|
|
4958
|
-
init_cjs_shims();
|
|
4959
|
-
|
|
4960
5240
|
// src/modules/swagger/swagger.service.ts
|
|
4961
|
-
init_cjs_shims();
|
|
4962
5241
|
var import_swagger = __toESM(require("@fastify/swagger"), 1);
|
|
4963
5242
|
var import_swagger_ui = __toESM(require("@fastify/swagger-ui"), 1);
|
|
4964
5243
|
var defaultConfig3 = {
|
|
@@ -5018,16 +5297,11 @@ async function registerSwagger(app, customConfig) {
|
|
|
5018
5297
|
logger.info("Swagger documentation registered at /docs");
|
|
5019
5298
|
}
|
|
5020
5299
|
|
|
5021
|
-
// src/modules/swagger/schema-builder.ts
|
|
5022
|
-
init_cjs_shims();
|
|
5023
|
-
|
|
5024
5300
|
// src/modules/auth/index.ts
|
|
5025
|
-
init_cjs_shims();
|
|
5026
5301
|
var import_jwt = __toESM(require("@fastify/jwt"), 1);
|
|
5027
5302
|
var import_cookie = __toESM(require("@fastify/cookie"), 1);
|
|
5028
5303
|
|
|
5029
5304
|
// src/modules/auth/auth.service.ts
|
|
5030
|
-
init_cjs_shims();
|
|
5031
5305
|
var import_bcryptjs = __toESM(require("bcryptjs"), 1);
|
|
5032
5306
|
var import_ioredis = require("ioredis");
|
|
5033
5307
|
var AuthService = class {
|
|
@@ -5226,11 +5500,7 @@ function createAuthService(app) {
|
|
|
5226
5500
|
return new AuthService(app);
|
|
5227
5501
|
}
|
|
5228
5502
|
|
|
5229
|
-
// src/modules/auth/auth.controller.ts
|
|
5230
|
-
init_cjs_shims();
|
|
5231
|
-
|
|
5232
5503
|
// src/modules/auth/schemas.ts
|
|
5233
|
-
init_cjs_shims();
|
|
5234
5504
|
var import_zod2 = require("zod");
|
|
5235
5505
|
var loginSchema = import_zod2.z.object({
|
|
5236
5506
|
email: import_zod2.z.string().email("Invalid email address"),
|
|
@@ -5257,7 +5527,6 @@ var changePasswordSchema = import_zod2.z.object({
|
|
|
5257
5527
|
});
|
|
5258
5528
|
|
|
5259
5529
|
// src/utils/response.ts
|
|
5260
|
-
init_cjs_shims();
|
|
5261
5530
|
function success2(reply, data, statusCode = 200) {
|
|
5262
5531
|
const response = {
|
|
5263
5532
|
success: true,
|
|
@@ -5273,7 +5542,6 @@ function noContent(reply) {
|
|
|
5273
5542
|
}
|
|
5274
5543
|
|
|
5275
5544
|
// src/modules/validation/validator.ts
|
|
5276
|
-
init_cjs_shims();
|
|
5277
5545
|
var import_zod3 = require("zod");
|
|
5278
5546
|
function validateBody(schema, data) {
|
|
5279
5547
|
const result = schema.safeParse(data);
|
|
@@ -5292,11 +5560,11 @@ function validateQuery(schema, data) {
|
|
|
5292
5560
|
function formatZodErrors(error2) {
|
|
5293
5561
|
const errors = {};
|
|
5294
5562
|
for (const issue of error2.issues) {
|
|
5295
|
-
const
|
|
5296
|
-
if (!errors[
|
|
5297
|
-
errors[
|
|
5563
|
+
const path15 = issue.path.join(".") || "root";
|
|
5564
|
+
if (!errors[path15]) {
|
|
5565
|
+
errors[path15] = [];
|
|
5298
5566
|
}
|
|
5299
|
-
errors[
|
|
5567
|
+
errors[path15].push(issue.message);
|
|
5300
5568
|
}
|
|
5301
5569
|
return errors;
|
|
5302
5570
|
}
|
|
@@ -5444,11 +5712,7 @@ function createAuthController(authService, userService) {
|
|
|
5444
5712
|
return new AuthController(authService, userService);
|
|
5445
5713
|
}
|
|
5446
5714
|
|
|
5447
|
-
// src/modules/auth/auth.routes.ts
|
|
5448
|
-
init_cjs_shims();
|
|
5449
|
-
|
|
5450
5715
|
// src/modules/auth/auth.middleware.ts
|
|
5451
|
-
init_cjs_shims();
|
|
5452
5716
|
function createAuthMiddleware(authService) {
|
|
5453
5717
|
return async function authenticate(request, _reply) {
|
|
5454
5718
|
const authHeader = request.headers.authorization;
|
|
@@ -5491,14 +5755,7 @@ function registerAuthRoutes(app, controller, authService) {
|
|
|
5491
5755
|
);
|
|
5492
5756
|
}
|
|
5493
5757
|
|
|
5494
|
-
// src/modules/user/user.service.ts
|
|
5495
|
-
init_cjs_shims();
|
|
5496
|
-
|
|
5497
|
-
// src/modules/user/user.repository.ts
|
|
5498
|
-
init_cjs_shims();
|
|
5499
|
-
|
|
5500
5758
|
// src/database/prisma.ts
|
|
5501
|
-
init_cjs_shims();
|
|
5502
5759
|
var import_client = require("@prisma/client");
|
|
5503
5760
|
var prismaClientSingleton = () => {
|
|
5504
5761
|
return new import_client.PrismaClient({
|
|
@@ -5512,7 +5769,6 @@ if (!isProduction()) {
|
|
|
5512
5769
|
}
|
|
5513
5770
|
|
|
5514
5771
|
// src/utils/pagination.ts
|
|
5515
|
-
init_cjs_shims();
|
|
5516
5772
|
var DEFAULT_PAGE = 1;
|
|
5517
5773
|
var DEFAULT_LIMIT = 20;
|
|
5518
5774
|
var MAX_LIMIT = 100;
|
|
@@ -5777,7 +6033,6 @@ function createUserRepository() {
|
|
|
5777
6033
|
}
|
|
5778
6034
|
|
|
5779
6035
|
// src/modules/user/types.ts
|
|
5780
|
-
init_cjs_shims();
|
|
5781
6036
|
var DEFAULT_ROLE_PERMISSIONS = {
|
|
5782
6037
|
user: ["profile:read", "profile:update"],
|
|
5783
6038
|
moderator: [
|
|
@@ -5908,9 +6163,6 @@ function createUserService(repository) {
|
|
|
5908
6163
|
return new UserService(repository || createUserRepository());
|
|
5909
6164
|
}
|
|
5910
6165
|
|
|
5911
|
-
// src/modules/auth/types.ts
|
|
5912
|
-
init_cjs_shims();
|
|
5913
|
-
|
|
5914
6166
|
// src/modules/auth/index.ts
|
|
5915
6167
|
async function registerAuthModule(app) {
|
|
5916
6168
|
await app.register(import_jwt.default, {
|
|
@@ -5930,14 +6182,7 @@ async function registerAuthModule(app) {
|
|
|
5930
6182
|
logger.info("Auth module registered");
|
|
5931
6183
|
}
|
|
5932
6184
|
|
|
5933
|
-
// src/modules/user/index.ts
|
|
5934
|
-
init_cjs_shims();
|
|
5935
|
-
|
|
5936
|
-
// src/modules/user/user.controller.ts
|
|
5937
|
-
init_cjs_shims();
|
|
5938
|
-
|
|
5939
6185
|
// src/modules/user/schemas.ts
|
|
5940
|
-
init_cjs_shims();
|
|
5941
6186
|
var import_zod4 = require("zod");
|
|
5942
6187
|
var userStatusEnum = import_zod4.z.enum(["active", "inactive", "suspended", "banned"]);
|
|
5943
6188
|
var userRoleEnum = import_zod4.z.enum(["user", "admin", "moderator", "super_admin"]);
|
|
@@ -6064,7 +6309,6 @@ function createUserController(userService) {
|
|
|
6064
6309
|
}
|
|
6065
6310
|
|
|
6066
6311
|
// src/modules/user/user.routes.ts
|
|
6067
|
-
init_cjs_shims();
|
|
6068
6312
|
var idParamsSchema = {
|
|
6069
6313
|
type: "object",
|
|
6070
6314
|
properties: {
|
|
@@ -6153,9 +6397,9 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
|
|
|
6153
6397
|
await registerUserModule(app, authService);
|
|
6154
6398
|
await app.ready();
|
|
6155
6399
|
const spec = app.swagger();
|
|
6156
|
-
const absoluteOutput =
|
|
6157
|
-
await
|
|
6158
|
-
await
|
|
6400
|
+
const absoluteOutput = import_path7.default.resolve(outputPath);
|
|
6401
|
+
await import_promises4.default.mkdir(import_path7.default.dirname(absoluteOutput), { recursive: true });
|
|
6402
|
+
await import_promises4.default.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
|
|
6159
6403
|
spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
|
|
6160
6404
|
await app.close();
|
|
6161
6405
|
return absoluteOutput;
|
|
@@ -6171,10 +6415,10 @@ docsCommand.command("generate").alias("gen").description("Generate OpenAPI/Swagg
|
|
|
6171
6415
|
try {
|
|
6172
6416
|
const outputPath = await generateDocs(options.output, false);
|
|
6173
6417
|
if (options.format === "yaml") {
|
|
6174
|
-
const jsonContent = await
|
|
6418
|
+
const jsonContent = await import_promises5.default.readFile(outputPath, "utf-8");
|
|
6175
6419
|
const spec = JSON.parse(jsonContent);
|
|
6176
6420
|
const yamlPath = outputPath.replace(".json", ".yaml");
|
|
6177
|
-
await
|
|
6421
|
+
await import_promises5.default.writeFile(yamlPath, jsonToYaml(spec));
|
|
6178
6422
|
success(`YAML documentation generated: ${yamlPath}`);
|
|
6179
6423
|
}
|
|
6180
6424
|
console.log("\n\u{1F4DA} Documentation URLs:");
|
|
@@ -6199,14 +6443,14 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
|
|
|
6199
6443
|
const spinner = (0, import_ora6.default)("Exporting documentation...").start();
|
|
6200
6444
|
try {
|
|
6201
6445
|
const projectRoot = getProjectRoot();
|
|
6202
|
-
const specPath =
|
|
6446
|
+
const specPath = import_path8.default.join(projectRoot, "openapi.json");
|
|
6203
6447
|
try {
|
|
6204
|
-
await
|
|
6448
|
+
await import_promises5.default.access(specPath);
|
|
6205
6449
|
} catch {
|
|
6206
6450
|
spinner.text = "Generating OpenAPI spec first...";
|
|
6207
6451
|
await generateDocs("openapi.json", true);
|
|
6208
6452
|
}
|
|
6209
|
-
const specContent = await
|
|
6453
|
+
const specContent = await import_promises5.default.readFile(specPath, "utf-8");
|
|
6210
6454
|
const spec = JSON.parse(specContent);
|
|
6211
6455
|
let output;
|
|
6212
6456
|
let defaultName;
|
|
@@ -6226,8 +6470,8 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
|
|
|
6226
6470
|
default:
|
|
6227
6471
|
throw new Error(`Unknown format: ${options.format}`);
|
|
6228
6472
|
}
|
|
6229
|
-
const outPath =
|
|
6230
|
-
await
|
|
6473
|
+
const outPath = import_path8.default.join(projectRoot, options.output || defaultName);
|
|
6474
|
+
await import_promises5.default.writeFile(outPath, output);
|
|
6231
6475
|
spinner.succeed(`Exported to: ${options.output || defaultName}`);
|
|
6232
6476
|
if (options.format === "postman") {
|
|
6233
6477
|
info("\n Import in Postman: File > Import > Select file");
|
|
@@ -6240,13 +6484,13 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
|
|
|
6240
6484
|
docsCommand.command("status").description("Show documentation status").action(async () => {
|
|
6241
6485
|
const projectRoot = getProjectRoot();
|
|
6242
6486
|
console.log(import_chalk9.default.bold("\n\u{1F4CA} Documentation Status\n"));
|
|
6243
|
-
const specPath =
|
|
6487
|
+
const specPath = import_path8.default.join(projectRoot, "openapi.json");
|
|
6244
6488
|
try {
|
|
6245
|
-
const stat2 = await
|
|
6489
|
+
const stat2 = await import_promises5.default.stat(specPath);
|
|
6246
6490
|
success(
|
|
6247
6491
|
`openapi.json exists (${formatBytes(stat2.size)}, modified ${formatDate(stat2.mtime)})`
|
|
6248
6492
|
);
|
|
6249
|
-
const content = await
|
|
6493
|
+
const content = await import_promises5.default.readFile(specPath, "utf-8");
|
|
6250
6494
|
const spec = JSON.parse(content);
|
|
6251
6495
|
const pathCount = Object.keys(spec.paths || {}).length;
|
|
6252
6496
|
info(` ${pathCount} endpoints documented`);
|
|
@@ -6356,10 +6600,9 @@ function formatDate(date) {
|
|
|
6356
6600
|
}
|
|
6357
6601
|
|
|
6358
6602
|
// src/cli/commands/list.ts
|
|
6359
|
-
init_cjs_shims();
|
|
6360
6603
|
var import_commander6 = require("commander");
|
|
6361
6604
|
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
6362
|
-
var
|
|
6605
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
6363
6606
|
var AVAILABLE_MODULES2 = {
|
|
6364
6607
|
// Core
|
|
6365
6608
|
auth: {
|
|
@@ -6474,7 +6717,7 @@ var AVAILABLE_MODULES2 = {
|
|
|
6474
6717
|
async function getInstalledModules() {
|
|
6475
6718
|
try {
|
|
6476
6719
|
const modulesDir = getModulesDir();
|
|
6477
|
-
const entries = await
|
|
6720
|
+
const entries = await import_promises6.default.readdir(modulesDir, { withFileTypes: true });
|
|
6478
6721
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
6479
6722
|
} catch {
|
|
6480
6723
|
return [];
|
|
@@ -6514,7 +6757,7 @@ var listCommand = new import_commander6.Command("list").alias("ls").description(
|
|
|
6514
6757
|
if (!byCategory[mod.category]) {
|
|
6515
6758
|
byCategory[mod.category] = [];
|
|
6516
6759
|
}
|
|
6517
|
-
byCategory[mod.category]
|
|
6760
|
+
byCategory[mod.category]?.push({
|
|
6518
6761
|
id: key,
|
|
6519
6762
|
name: mod.name,
|
|
6520
6763
|
description: mod.description,
|
|
@@ -6582,14 +6825,12 @@ var listCommand = new import_commander6.Command("list").alias("ls").description(
|
|
|
6582
6825
|
);
|
|
6583
6826
|
|
|
6584
6827
|
// src/cli/commands/remove.ts
|
|
6585
|
-
init_cjs_shims();
|
|
6586
6828
|
var import_commander7 = require("commander");
|
|
6587
|
-
var
|
|
6829
|
+
var import_path9 = __toESM(require("path"), 1);
|
|
6588
6830
|
var import_ora7 = __toESM(require("ora"), 1);
|
|
6589
6831
|
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
6590
|
-
var
|
|
6832
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
6591
6833
|
var import_inquirer4 = __toESM(require("inquirer"), 1);
|
|
6592
|
-
init_error_handler();
|
|
6593
6834
|
var removeCommand = new import_commander7.Command("remove").alias("rm").description("Remove an installed module from your project").argument("<module>", "Module to remove").option("-y, --yes", "Skip confirmation prompt").option("--keep-env", "Keep environment variables").action(async (moduleName, options) => {
|
|
6594
6835
|
const projectError = validateProject();
|
|
6595
6836
|
if (projectError) {
|
|
@@ -6597,22 +6838,19 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
|
|
|
6597
6838
|
return;
|
|
6598
6839
|
}
|
|
6599
6840
|
console.log(import_chalk11.default.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
|
|
6600
|
-
const moduleDir =
|
|
6841
|
+
const moduleDir = import_path9.default.join(getModulesDir(), moduleName);
|
|
6601
6842
|
try {
|
|
6602
|
-
const exists = await
|
|
6843
|
+
const exists = await import_promises7.default.access(moduleDir).then(() => true).catch(() => false);
|
|
6603
6844
|
if (!exists) {
|
|
6604
6845
|
displayError(
|
|
6605
|
-
new (
|
|
6606
|
-
`
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
`Check the spelling of the module name`
|
|
6610
|
-
]
|
|
6611
|
-
)
|
|
6846
|
+
new ServCraftError(`Module "${moduleName}" is not installed`, [
|
|
6847
|
+
`Run ${import_chalk11.default.cyan("servcraft list --installed")} to see installed modules`,
|
|
6848
|
+
`Check the spelling of the module name`
|
|
6849
|
+
])
|
|
6612
6850
|
);
|
|
6613
6851
|
return;
|
|
6614
6852
|
}
|
|
6615
|
-
const files = await
|
|
6853
|
+
const files = await import_promises7.default.readdir(moduleDir);
|
|
6616
6854
|
const fileCount = files.length;
|
|
6617
6855
|
if (!options?.yes) {
|
|
6618
6856
|
console.log(import_chalk11.default.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
|
|
@@ -6633,7 +6871,7 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
|
|
|
6633
6871
|
}
|
|
6634
6872
|
}
|
|
6635
6873
|
const spinner = (0, import_ora7.default)("Removing module...").start();
|
|
6636
|
-
await
|
|
6874
|
+
await import_promises7.default.rm(moduleDir, { recursive: true, force: true });
|
|
6637
6875
|
spinner.succeed(`Module "${moduleName}" removed successfully!`);
|
|
6638
6876
|
console.log("\n" + import_chalk11.default.bold("\u2713 Removed:"));
|
|
6639
6877
|
success(` src/modules/${moduleName}/ (${fileCount} files)`);
|
|
@@ -6657,15 +6895,697 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
|
|
|
6657
6895
|
});
|
|
6658
6896
|
|
|
6659
6897
|
// src/cli/commands/doctor.ts
|
|
6660
|
-
init_cjs_shims();
|
|
6661
6898
|
var import_commander8 = require("commander");
|
|
6662
6899
|
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
6900
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
6901
|
+
async function checkNodeVersion() {
|
|
6902
|
+
const version = process.version;
|
|
6903
|
+
const major = parseInt(version.slice(1).split(".")[0] || "0", 10);
|
|
6904
|
+
if (major >= 18) {
|
|
6905
|
+
return { name: "Node.js", status: "pass", message: `${version} \u2713` };
|
|
6906
|
+
}
|
|
6907
|
+
return {
|
|
6908
|
+
name: "Node.js",
|
|
6909
|
+
status: "fail",
|
|
6910
|
+
message: `${version} (< 18)`,
|
|
6911
|
+
suggestion: "Upgrade to Node.js 18+"
|
|
6912
|
+
};
|
|
6913
|
+
}
|
|
6914
|
+
async function checkPackageJson() {
|
|
6915
|
+
const checks = [];
|
|
6916
|
+
try {
|
|
6917
|
+
const content = await import_promises8.default.readFile("package.json", "utf-8");
|
|
6918
|
+
const pkg = JSON.parse(content);
|
|
6919
|
+
checks.push({ name: "package.json", status: "pass", message: "Found" });
|
|
6920
|
+
if (pkg.dependencies?.fastify) {
|
|
6921
|
+
checks.push({ name: "Fastify", status: "pass", message: "Installed" });
|
|
6922
|
+
} else {
|
|
6923
|
+
checks.push({
|
|
6924
|
+
name: "Fastify",
|
|
6925
|
+
status: "fail",
|
|
6926
|
+
message: "Missing",
|
|
6927
|
+
suggestion: "npm install fastify"
|
|
6928
|
+
});
|
|
6929
|
+
}
|
|
6930
|
+
} catch {
|
|
6931
|
+
checks.push({
|
|
6932
|
+
name: "package.json",
|
|
6933
|
+
status: "fail",
|
|
6934
|
+
message: "Not found",
|
|
6935
|
+
suggestion: "Run servcraft init"
|
|
6936
|
+
});
|
|
6937
|
+
}
|
|
6938
|
+
return checks;
|
|
6939
|
+
}
|
|
6940
|
+
async function checkDirectories() {
|
|
6941
|
+
const checks = [];
|
|
6942
|
+
const dirs = ["src", "node_modules", ".git", ".env"];
|
|
6943
|
+
for (const dir of dirs) {
|
|
6944
|
+
try {
|
|
6945
|
+
await import_promises8.default.access(dir);
|
|
6946
|
+
checks.push({ name: dir, status: "pass", message: "Exists" });
|
|
6947
|
+
} catch {
|
|
6948
|
+
const isCritical = dir === "src" || dir === "node_modules";
|
|
6949
|
+
checks.push({
|
|
6950
|
+
name: dir,
|
|
6951
|
+
status: isCritical ? "fail" : "warn",
|
|
6952
|
+
message: "Not found",
|
|
6953
|
+
suggestion: dir === "node_modules" ? "npm install" : dir === ".env" ? "Create .env file" : void 0
|
|
6954
|
+
});
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
return checks;
|
|
6958
|
+
}
|
|
6663
6959
|
var doctorCommand = new import_commander8.Command("doctor").description("Diagnose project configuration and dependencies").action(async () => {
|
|
6664
|
-
console.log(import_chalk12.default.bold.cyan("\
|
|
6960
|
+
console.log(import_chalk12.default.bold.cyan("\n\u{1F50D} ServCraft Doctor\n"));
|
|
6961
|
+
const allChecks = [];
|
|
6962
|
+
allChecks.push(await checkNodeVersion());
|
|
6963
|
+
allChecks.push(...await checkPackageJson());
|
|
6964
|
+
allChecks.push(...await checkDirectories());
|
|
6965
|
+
allChecks.forEach((check) => {
|
|
6966
|
+
const icon = check.status === "pass" ? import_chalk12.default.green("\u2713") : check.status === "warn" ? import_chalk12.default.yellow("\u26A0") : import_chalk12.default.red("\u2717");
|
|
6967
|
+
const color = check.status === "pass" ? import_chalk12.default.green : check.status === "warn" ? import_chalk12.default.yellow : import_chalk12.default.red;
|
|
6968
|
+
console.log(`${icon} ${check.name.padEnd(20)} ${color(check.message)}`);
|
|
6969
|
+
if (check.suggestion) {
|
|
6970
|
+
console.log(import_chalk12.default.gray(` \u2192 ${check.suggestion}`));
|
|
6971
|
+
}
|
|
6972
|
+
});
|
|
6973
|
+
const pass = allChecks.filter((c) => c.status === "pass").length;
|
|
6974
|
+
const warn2 = allChecks.filter((c) => c.status === "warn").length;
|
|
6975
|
+
const fail = allChecks.filter((c) => c.status === "fail").length;
|
|
6976
|
+
console.log(import_chalk12.default.gray("\n" + "\u2500".repeat(60)));
|
|
6977
|
+
console.log(
|
|
6978
|
+
`
|
|
6979
|
+
${import_chalk12.default.green(pass + " passed")} | ${import_chalk12.default.yellow(warn2 + " warnings")} | ${import_chalk12.default.red(fail + " failed")}
|
|
6980
|
+
`
|
|
6981
|
+
);
|
|
6982
|
+
if (fail === 0 && warn2 === 0) {
|
|
6983
|
+
console.log(import_chalk12.default.green.bold("\u2728 Everything looks good!\n"));
|
|
6984
|
+
} else if (fail > 0) {
|
|
6985
|
+
console.log(import_chalk12.default.red.bold("\u2717 Fix critical issues before using ServCraft.\n"));
|
|
6986
|
+
} else {
|
|
6987
|
+
console.log(import_chalk12.default.yellow.bold("\u26A0 Some warnings, but should work.\n"));
|
|
6988
|
+
}
|
|
6989
|
+
});
|
|
6990
|
+
|
|
6991
|
+
// src/cli/commands/update.ts
|
|
6992
|
+
var import_commander9 = require("commander");
|
|
6993
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
6994
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
6995
|
+
var import_path10 = __toESM(require("path"), 1);
|
|
6996
|
+
var import_url = require("url");
|
|
6997
|
+
var import_inquirer5 = __toESM(require("inquirer"), 1);
|
|
6998
|
+
var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
6999
|
+
var __dirname = import_path10.default.dirname(__filename2);
|
|
7000
|
+
var AVAILABLE_MODULES3 = [
|
|
7001
|
+
"auth",
|
|
7002
|
+
"users",
|
|
7003
|
+
"email",
|
|
7004
|
+
"mfa",
|
|
7005
|
+
"oauth",
|
|
7006
|
+
"rate-limit",
|
|
7007
|
+
"cache",
|
|
7008
|
+
"upload",
|
|
7009
|
+
"search",
|
|
7010
|
+
"notification",
|
|
7011
|
+
"webhook",
|
|
7012
|
+
"websocket",
|
|
7013
|
+
"queue",
|
|
7014
|
+
"payment",
|
|
7015
|
+
"i18n",
|
|
7016
|
+
"feature-flag",
|
|
7017
|
+
"analytics",
|
|
7018
|
+
"media-processing",
|
|
7019
|
+
"api-versioning",
|
|
7020
|
+
"audit",
|
|
7021
|
+
"swagger",
|
|
7022
|
+
"validation"
|
|
7023
|
+
];
|
|
7024
|
+
async function getInstalledModules2() {
|
|
7025
|
+
try {
|
|
7026
|
+
const modulesDir = getModulesDir();
|
|
7027
|
+
const entries = await import_promises9.default.readdir(modulesDir, { withFileTypes: true });
|
|
7028
|
+
const installedModules = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => AVAILABLE_MODULES3.includes(name));
|
|
7029
|
+
return installedModules;
|
|
7030
|
+
} catch {
|
|
7031
|
+
return [];
|
|
7032
|
+
}
|
|
7033
|
+
}
|
|
7034
|
+
async function copyModuleFiles(moduleName, _projectRoot) {
|
|
7035
|
+
const cliRoot = import_path10.default.resolve(__dirname, "../../../");
|
|
7036
|
+
const sourceModulePath = import_path10.default.join(cliRoot, "src", "modules", moduleName);
|
|
7037
|
+
const targetModulesDir = getModulesDir();
|
|
7038
|
+
const targetModulePath = import_path10.default.join(targetModulesDir, moduleName);
|
|
7039
|
+
try {
|
|
7040
|
+
await import_promises9.default.access(sourceModulePath);
|
|
7041
|
+
} catch {
|
|
7042
|
+
throw new Error(`Module source not found: ${moduleName}`);
|
|
7043
|
+
}
|
|
7044
|
+
await import_promises9.default.cp(sourceModulePath, targetModulePath, { recursive: true });
|
|
7045
|
+
}
|
|
7046
|
+
async function updateModule(moduleName, options) {
|
|
7047
|
+
const projectError = validateProject();
|
|
7048
|
+
if (projectError) {
|
|
7049
|
+
displayError(projectError);
|
|
7050
|
+
return;
|
|
7051
|
+
}
|
|
7052
|
+
const projectRoot = getProjectRoot();
|
|
7053
|
+
const installedModules = await getInstalledModules2();
|
|
7054
|
+
if (!installedModules.includes(moduleName)) {
|
|
7055
|
+
console.log(import_chalk13.default.yellow(`
|
|
7056
|
+
\u26A0 Module "${moduleName}" is not installed
|
|
7057
|
+
`));
|
|
7058
|
+
console.log(
|
|
7059
|
+
import_chalk13.default.gray(`Run ${import_chalk13.default.cyan(`servcraft add ${moduleName}`)} to install it first.
|
|
7060
|
+
`)
|
|
7061
|
+
);
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
7064
|
+
if (options.check) {
|
|
7065
|
+
console.log(import_chalk13.default.cyan(`
|
|
7066
|
+
\u{1F4E6} Checking updates for "${moduleName}"...
|
|
7067
|
+
`));
|
|
7068
|
+
console.log(import_chalk13.default.gray("Note: Version tracking will be implemented in a future release."));
|
|
7069
|
+
console.log(import_chalk13.default.gray("Currently, update will always reinstall the latest version.\n"));
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
const { confirmed } = await import_inquirer5.default.prompt([
|
|
7073
|
+
{
|
|
7074
|
+
type: "confirm",
|
|
7075
|
+
name: "confirmed",
|
|
7076
|
+
message: `Update "${moduleName}" module? This will overwrite existing files.`,
|
|
7077
|
+
default: false
|
|
7078
|
+
}
|
|
7079
|
+
]);
|
|
7080
|
+
if (!confirmed) {
|
|
7081
|
+
console.log(import_chalk13.default.yellow("\n\u26A0 Update cancelled\n"));
|
|
7082
|
+
return;
|
|
7083
|
+
}
|
|
7084
|
+
console.log(import_chalk13.default.cyan(`
|
|
7085
|
+
\u{1F504} Updating "${moduleName}" module...
|
|
7086
|
+
`));
|
|
7087
|
+
try {
|
|
7088
|
+
await copyModuleFiles(moduleName, projectRoot);
|
|
7089
|
+
console.log(import_chalk13.default.green(`\u2714 Module "${moduleName}" updated successfully!
|
|
7090
|
+
`));
|
|
7091
|
+
console.log(
|
|
7092
|
+
import_chalk13.default.gray("Note: Remember to review any breaking changes in the documentation.\n")
|
|
7093
|
+
);
|
|
7094
|
+
} catch (error2) {
|
|
7095
|
+
if (error2 instanceof Error) {
|
|
7096
|
+
console.error(import_chalk13.default.red(`
|
|
7097
|
+
\u2717 Failed to update module: ${error2.message}
|
|
7098
|
+
`));
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
async function updateAllModules(options) {
|
|
7103
|
+
const projectError = validateProject();
|
|
7104
|
+
if (projectError) {
|
|
7105
|
+
displayError(projectError);
|
|
7106
|
+
return;
|
|
7107
|
+
}
|
|
7108
|
+
const installedModules = await getInstalledModules2();
|
|
7109
|
+
if (installedModules.length === 0) {
|
|
7110
|
+
console.log(import_chalk13.default.yellow("\n\u26A0 No modules installed\n"));
|
|
7111
|
+
console.log(import_chalk13.default.gray(`Run ${import_chalk13.default.cyan("servcraft list")} to see available modules.
|
|
7112
|
+
`));
|
|
7113
|
+
return;
|
|
7114
|
+
}
|
|
7115
|
+
if (options.check) {
|
|
7116
|
+
console.log(import_chalk13.default.cyan("\n\u{1F4E6} Checking updates for all modules...\n"));
|
|
7117
|
+
console.log(import_chalk13.default.bold("Installed modules:"));
|
|
7118
|
+
installedModules.forEach((mod) => {
|
|
7119
|
+
console.log(` \u2022 ${import_chalk13.default.cyan(mod)}`);
|
|
7120
|
+
});
|
|
7121
|
+
console.log();
|
|
7122
|
+
console.log(import_chalk13.default.gray("Note: Version tracking will be implemented in a future release."));
|
|
7123
|
+
console.log(import_chalk13.default.gray("Currently, update will always reinstall the latest version.\n"));
|
|
7124
|
+
return;
|
|
7125
|
+
}
|
|
7126
|
+
console.log(import_chalk13.default.cyan(`
|
|
7127
|
+
\u{1F4E6} Found ${installedModules.length} installed module(s):
|
|
7128
|
+
`));
|
|
7129
|
+
installedModules.forEach((mod) => {
|
|
7130
|
+
console.log(` \u2022 ${import_chalk13.default.cyan(mod)}`);
|
|
7131
|
+
});
|
|
7132
|
+
console.log();
|
|
7133
|
+
const { confirmed } = await import_inquirer5.default.prompt([
|
|
7134
|
+
{
|
|
7135
|
+
type: "confirm",
|
|
7136
|
+
name: "confirmed",
|
|
7137
|
+
message: "Update all modules? This will overwrite existing files.",
|
|
7138
|
+
default: false
|
|
7139
|
+
}
|
|
7140
|
+
]);
|
|
7141
|
+
if (!confirmed) {
|
|
7142
|
+
console.log(import_chalk13.default.yellow("\n\u26A0 Update cancelled\n"));
|
|
7143
|
+
return;
|
|
7144
|
+
}
|
|
7145
|
+
console.log(import_chalk13.default.cyan("\n\u{1F504} Updating all modules...\n"));
|
|
7146
|
+
const projectRoot = getProjectRoot();
|
|
7147
|
+
let successCount = 0;
|
|
7148
|
+
let failCount = 0;
|
|
7149
|
+
for (const moduleName of installedModules) {
|
|
7150
|
+
try {
|
|
7151
|
+
await copyModuleFiles(moduleName, projectRoot);
|
|
7152
|
+
console.log(import_chalk13.default.green(`\u2714 Updated: ${moduleName}`));
|
|
7153
|
+
successCount++;
|
|
7154
|
+
} catch {
|
|
7155
|
+
console.error(import_chalk13.default.red(`\u2717 Failed: ${moduleName}`));
|
|
7156
|
+
failCount++;
|
|
7157
|
+
}
|
|
7158
|
+
}
|
|
7159
|
+
console.log();
|
|
7160
|
+
console.log(
|
|
7161
|
+
import_chalk13.default.bold(
|
|
7162
|
+
`
|
|
7163
|
+
\u2714 Update complete: ${import_chalk13.default.green(successCount)} succeeded, ${import_chalk13.default.red(failCount)} failed
|
|
7164
|
+
`
|
|
7165
|
+
)
|
|
7166
|
+
);
|
|
7167
|
+
if (successCount > 0) {
|
|
7168
|
+
console.log(
|
|
7169
|
+
import_chalk13.default.gray("Note: Remember to review any breaking changes in the documentation.\n")
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
var updateCommand = new import_commander9.Command("update").description("Update installed modules to latest version").argument("[module]", "Specific module to update").option("--check", "Check for updates without applying").option("-y, --yes", "Skip confirmation prompt").action(async (moduleName, options) => {
|
|
7174
|
+
if (moduleName) {
|
|
7175
|
+
await updateModule(moduleName, { check: options?.check });
|
|
7176
|
+
} else {
|
|
7177
|
+
await updateAllModules({ check: options?.check });
|
|
7178
|
+
}
|
|
7179
|
+
});
|
|
7180
|
+
|
|
7181
|
+
// src/cli/commands/completion.ts
|
|
7182
|
+
var import_commander10 = require("commander");
|
|
7183
|
+
var bashScript = `
|
|
7184
|
+
# servcraft bash completion script
|
|
7185
|
+
_servcraft_completions() {
|
|
7186
|
+
local cur prev words cword
|
|
7187
|
+
_init_completion || return
|
|
7188
|
+
|
|
7189
|
+
# Main commands
|
|
7190
|
+
local commands="init add generate list remove doctor update completion docs --version --help"
|
|
7191
|
+
|
|
7192
|
+
# Generate subcommands
|
|
7193
|
+
local generate_subcommands="module controller service repository types schema routes m c s r t"
|
|
7194
|
+
|
|
7195
|
+
case "\${words[1]}" in
|
|
7196
|
+
generate|g)
|
|
7197
|
+
if [[ \${cword} -eq 2 ]]; then
|
|
7198
|
+
COMPREPLY=( $(compgen -W "\${generate_subcommands}" -- "\${cur}") )
|
|
7199
|
+
fi
|
|
7200
|
+
;;
|
|
7201
|
+
add|remove|rm|update)
|
|
7202
|
+
if [[ \${cword} -eq 2 ]]; then
|
|
7203
|
+
# Get available modules
|
|
7204
|
+
local modules="auth cache rate-limit notification payment oauth mfa queue websocket upload"
|
|
7205
|
+
COMPREPLY=( $(compgen -W "\${modules}" -- "\${cur}") )
|
|
7206
|
+
fi
|
|
7207
|
+
;;
|
|
7208
|
+
completion)
|
|
7209
|
+
if [[ \${cword} -eq 2 ]]; then
|
|
7210
|
+
COMPREPLY=( $(compgen -W "bash zsh" -- "\${cur}") )
|
|
7211
|
+
fi
|
|
7212
|
+
;;
|
|
7213
|
+
*)
|
|
7214
|
+
if [[ \${cword} -eq 1 ]]; then
|
|
7215
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
7216
|
+
fi
|
|
7217
|
+
;;
|
|
7218
|
+
esac
|
|
7219
|
+
}
|
|
7220
|
+
|
|
7221
|
+
complete -F _servcraft_completions servcraft
|
|
7222
|
+
`;
|
|
7223
|
+
var zshScript = `
|
|
7224
|
+
#compdef servcraft
|
|
7225
|
+
|
|
7226
|
+
_servcraft() {
|
|
7227
|
+
local context state state_descr line
|
|
7228
|
+
typeset -A opt_args
|
|
7229
|
+
|
|
7230
|
+
_arguments -C \\
|
|
7231
|
+
'1: :_servcraft_commands' \\
|
|
7232
|
+
'*::arg:->args'
|
|
7233
|
+
|
|
7234
|
+
case $state in
|
|
7235
|
+
args)
|
|
7236
|
+
case $line[1] in
|
|
7237
|
+
generate|g)
|
|
7238
|
+
_servcraft_generate
|
|
7239
|
+
;;
|
|
7240
|
+
add|remove|rm|update)
|
|
7241
|
+
_servcraft_modules
|
|
7242
|
+
;;
|
|
7243
|
+
completion)
|
|
7244
|
+
_arguments '1: :(bash zsh)'
|
|
7245
|
+
;;
|
|
7246
|
+
esac
|
|
7247
|
+
;;
|
|
7248
|
+
esac
|
|
7249
|
+
}
|
|
7250
|
+
|
|
7251
|
+
_servcraft_commands() {
|
|
7252
|
+
local commands
|
|
7253
|
+
commands=(
|
|
7254
|
+
'init:Initialize a new ServCraft project'
|
|
7255
|
+
'add:Add a pre-built module to your project'
|
|
7256
|
+
'generate:Generate code files (controller, service, etc.)'
|
|
7257
|
+
'list:List available and installed modules'
|
|
7258
|
+
'remove:Remove an installed module'
|
|
7259
|
+
'doctor:Diagnose project configuration'
|
|
7260
|
+
'update:Update installed modules'
|
|
7261
|
+
'completion:Generate shell completion scripts'
|
|
7262
|
+
'docs:Open documentation'
|
|
7263
|
+
'--version:Show version'
|
|
7264
|
+
'--help:Show help'
|
|
7265
|
+
)
|
|
7266
|
+
_describe 'command' commands
|
|
7267
|
+
}
|
|
7268
|
+
|
|
7269
|
+
_servcraft_generate() {
|
|
7270
|
+
local subcommands
|
|
7271
|
+
subcommands=(
|
|
7272
|
+
'module:Generate a complete module (controller + service + routes)'
|
|
7273
|
+
'controller:Generate a controller'
|
|
7274
|
+
'service:Generate a service'
|
|
7275
|
+
'repository:Generate a repository'
|
|
7276
|
+
'types:Generate TypeScript types'
|
|
7277
|
+
'schema:Generate validation schema'
|
|
7278
|
+
'routes:Generate routes file'
|
|
7279
|
+
'm:Alias for module'
|
|
7280
|
+
'c:Alias for controller'
|
|
7281
|
+
's:Alias for service'
|
|
7282
|
+
'r:Alias for repository'
|
|
7283
|
+
't:Alias for types'
|
|
7284
|
+
)
|
|
7285
|
+
_describe 'subcommand' subcommands
|
|
7286
|
+
}
|
|
7287
|
+
|
|
7288
|
+
_servcraft_modules() {
|
|
7289
|
+
local modules
|
|
7290
|
+
modules=(
|
|
7291
|
+
'auth:Authentication & Authorization'
|
|
7292
|
+
'cache:Redis caching'
|
|
7293
|
+
'rate-limit:Rate limiting'
|
|
7294
|
+
'notification:Email/SMS notifications'
|
|
7295
|
+
'payment:Payment integration'
|
|
7296
|
+
'oauth:OAuth providers'
|
|
7297
|
+
'mfa:Multi-factor authentication'
|
|
7298
|
+
'queue:Background jobs'
|
|
7299
|
+
'websocket:WebSocket support'
|
|
7300
|
+
'upload:File upload handling'
|
|
7301
|
+
)
|
|
7302
|
+
_describe 'module' modules
|
|
7303
|
+
}
|
|
7304
|
+
|
|
7305
|
+
_servcraft "$@"
|
|
7306
|
+
`;
|
|
7307
|
+
var completionCommand = new import_commander10.Command("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
|
|
7308
|
+
const shellLower = shell.toLowerCase();
|
|
7309
|
+
if (shellLower === "bash") {
|
|
7310
|
+
console.log(bashScript);
|
|
7311
|
+
} else if (shellLower === "zsh") {
|
|
7312
|
+
console.log(zshScript);
|
|
7313
|
+
} else {
|
|
7314
|
+
console.error(`Unsupported shell: ${shell}`);
|
|
7315
|
+
console.error("Supported shells: bash, zsh");
|
|
7316
|
+
process.exit(1);
|
|
7317
|
+
}
|
|
6665
7318
|
});
|
|
6666
7319
|
|
|
7320
|
+
// src/cli/commands/scaffold.ts
|
|
7321
|
+
var import_commander11 = require("commander");
|
|
7322
|
+
var import_path11 = __toESM(require("path"), 1);
|
|
7323
|
+
var import_ora8 = __toESM(require("ora"), 1);
|
|
7324
|
+
var import_chalk14 = __toESM(require("chalk"), 1);
|
|
7325
|
+
var scaffoldCommand = new import_commander11.Command("scaffold").description("Generate complete CRUD with Prisma model").argument("<name>", "Resource name (e.g., product, user)").option(
|
|
7326
|
+
"--fields <fields>",
|
|
7327
|
+
'Field definitions: "name:string email:string? age:number category:relation"'
|
|
7328
|
+
).option("--validator <type>", "Validator type: zod, joi, yup", "zod").option("--dry-run", "Preview changes without writing files").action(
|
|
7329
|
+
async (name, options) => {
|
|
7330
|
+
const dryRun = DryRunManager.getInstance();
|
|
7331
|
+
if (options.dryRun) {
|
|
7332
|
+
dryRun.enable();
|
|
7333
|
+
console.log(import_chalk14.default.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
|
|
7334
|
+
}
|
|
7335
|
+
if (!options.fields) {
|
|
7336
|
+
console.log(import_chalk14.default.red("\n\u2717 Error: --fields option is required\n"));
|
|
7337
|
+
console.log(import_chalk14.default.gray("Example:"));
|
|
7338
|
+
console.log(
|
|
7339
|
+
import_chalk14.default.cyan(
|
|
7340
|
+
' servcraft scaffold product --fields "name:string price:number category:relation"'
|
|
7341
|
+
)
|
|
7342
|
+
);
|
|
7343
|
+
process.exit(1);
|
|
7344
|
+
}
|
|
7345
|
+
const spinner = (0, import_ora8.default)("Scaffolding resource...").start();
|
|
7346
|
+
try {
|
|
7347
|
+
const fields = parseFields(options.fields || "");
|
|
7348
|
+
if (!fields || fields.length === 0) {
|
|
7349
|
+
spinner.fail("No valid fields provided");
|
|
7350
|
+
console.log(import_chalk14.default.gray(`
|
|
7351
|
+
Received: ${options.fields}`));
|
|
7352
|
+
console.log(import_chalk14.default.gray(`Parsed: ${JSON.stringify(fields)}`));
|
|
7353
|
+
process.exit(1);
|
|
7354
|
+
}
|
|
7355
|
+
const kebabName = toKebabCase(name);
|
|
7356
|
+
const pascalName = toPascalCase(name);
|
|
7357
|
+
const camelName = toCamelCase(name);
|
|
7358
|
+
const pluralName = pluralize(camelName);
|
|
7359
|
+
const tableName = pluralize(kebabName);
|
|
7360
|
+
const modulesDir = getModulesDir();
|
|
7361
|
+
const moduleDir = import_path11.default.join(modulesDir, kebabName);
|
|
7362
|
+
const controllerTpl = await getTemplate("controller", controllerTemplate);
|
|
7363
|
+
const serviceTpl = await getTemplate("service", serviceTemplate);
|
|
7364
|
+
const repositoryTpl = await getTemplate("repository", repositoryTemplate);
|
|
7365
|
+
const routesTpl = await getTemplate("routes", routesTemplate);
|
|
7366
|
+
const moduleIndexTpl = await getTemplate("module-index", moduleIndexTemplate);
|
|
7367
|
+
const files = [
|
|
7368
|
+
{
|
|
7369
|
+
name: `${kebabName}.types.ts`,
|
|
7370
|
+
content: dynamicTypesTemplate(kebabName, pascalName, fields)
|
|
7371
|
+
},
|
|
7372
|
+
{
|
|
7373
|
+
name: `${kebabName}.schemas.ts`,
|
|
7374
|
+
content: dynamicSchemasTemplate(
|
|
7375
|
+
kebabName,
|
|
7376
|
+
pascalName,
|
|
7377
|
+
camelName,
|
|
7378
|
+
fields,
|
|
7379
|
+
options.validator || "zod"
|
|
7380
|
+
)
|
|
7381
|
+
},
|
|
7382
|
+
{
|
|
7383
|
+
name: `${kebabName}.service.ts`,
|
|
7384
|
+
content: serviceTpl(kebabName, pascalName, camelName)
|
|
7385
|
+
},
|
|
7386
|
+
{
|
|
7387
|
+
name: `${kebabName}.controller.ts`,
|
|
7388
|
+
content: controllerTpl(kebabName, pascalName, camelName)
|
|
7389
|
+
},
|
|
7390
|
+
{
|
|
7391
|
+
name: "index.ts",
|
|
7392
|
+
content: moduleIndexTpl(kebabName, pascalName, camelName)
|
|
7393
|
+
},
|
|
7394
|
+
{
|
|
7395
|
+
name: `${kebabName}.repository.ts`,
|
|
7396
|
+
content: repositoryTpl(kebabName, pascalName, camelName, pluralName)
|
|
7397
|
+
},
|
|
7398
|
+
{
|
|
7399
|
+
name: `${kebabName}.routes.ts`,
|
|
7400
|
+
content: routesTpl(kebabName, pascalName, camelName, pluralName)
|
|
7401
|
+
}
|
|
7402
|
+
];
|
|
7403
|
+
for (const file of files) {
|
|
7404
|
+
await writeFile(import_path11.default.join(moduleDir, file.name), file.content);
|
|
7405
|
+
}
|
|
7406
|
+
const testDir = import_path11.default.join(moduleDir, "__tests__");
|
|
7407
|
+
const controllerTestTpl = await getTemplate("controller-test", controllerTestTemplate);
|
|
7408
|
+
const serviceTestTpl = await getTemplate("service-test", serviceTestTemplate);
|
|
7409
|
+
const integrationTestTpl = await getTemplate("integration-test", integrationTestTemplate);
|
|
7410
|
+
await writeFile(
|
|
7411
|
+
import_path11.default.join(testDir, `${kebabName}.controller.test.ts`),
|
|
7412
|
+
controllerTestTpl(kebabName, pascalName, camelName)
|
|
7413
|
+
);
|
|
7414
|
+
await writeFile(
|
|
7415
|
+
import_path11.default.join(testDir, `${kebabName}.service.test.ts`),
|
|
7416
|
+
serviceTestTpl(kebabName, pascalName, camelName)
|
|
7417
|
+
);
|
|
7418
|
+
await writeFile(
|
|
7419
|
+
import_path11.default.join(testDir, `${kebabName}.integration.test.ts`),
|
|
7420
|
+
integrationTestTpl(kebabName, pascalName, camelName)
|
|
7421
|
+
);
|
|
7422
|
+
spinner.succeed(`Resource "${pascalName}" scaffolded successfully!`);
|
|
7423
|
+
console.log("\n" + "\u2500".repeat(70));
|
|
7424
|
+
info("\u{1F4CB} Prisma model to add to schema.prisma:");
|
|
7425
|
+
console.log(import_chalk14.default.gray("\n// Copy this to your schema.prisma file:\n"));
|
|
7426
|
+
console.log(dynamicPrismaTemplate(pascalName, tableName, fields));
|
|
7427
|
+
console.log("\u2500".repeat(70));
|
|
7428
|
+
console.log("\n\u{1F4CB} Fields scaffolded:");
|
|
7429
|
+
fields.forEach((f) => {
|
|
7430
|
+
const opts = [];
|
|
7431
|
+
if (f.isOptional) opts.push("optional");
|
|
7432
|
+
if (f.isArray) opts.push("array");
|
|
7433
|
+
if (f.isUnique) opts.push("unique");
|
|
7434
|
+
if (f.relation) opts.push(`relation: ${f.relation.model}`);
|
|
7435
|
+
const optsStr = opts.length > 0 ? ` (${opts.join(", ")})` : "";
|
|
7436
|
+
success(` ${f.name}: ${f.type}${optsStr}`);
|
|
7437
|
+
});
|
|
7438
|
+
console.log("\n\u{1F4C1} Files created:");
|
|
7439
|
+
files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
|
|
7440
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
|
|
7441
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
|
|
7442
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
|
|
7443
|
+
console.log("\n\u{1F4CC} Next steps:");
|
|
7444
|
+
info(" 1. Add the Prisma model to your schema.prisma file");
|
|
7445
|
+
info(" 2. Run: npx prisma db push (or prisma migrate dev)");
|
|
7446
|
+
info(" 3. Run: npx prisma generate");
|
|
7447
|
+
info(" 4. Register the module routes in your app");
|
|
7448
|
+
info(" 5. Update the test files with actual test data");
|
|
7449
|
+
console.log(
|
|
7450
|
+
import_chalk14.default.gray("\n\u{1F4A1} Tip: Use --dry-run to preview changes before applying them\n")
|
|
7451
|
+
);
|
|
7452
|
+
if (options.dryRun) {
|
|
7453
|
+
dryRun.printSummary();
|
|
7454
|
+
}
|
|
7455
|
+
} catch (error2) {
|
|
7456
|
+
spinner.fail("Failed to scaffold resource");
|
|
7457
|
+
if (error2 instanceof Error) {
|
|
7458
|
+
console.error(import_chalk14.default.red(`
|
|
7459
|
+
\u2717 ${error2.message}
|
|
7460
|
+
`));
|
|
7461
|
+
console.error(import_chalk14.default.gray(error2.stack));
|
|
7462
|
+
}
|
|
7463
|
+
process.exit(1);
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
);
|
|
7467
|
+
|
|
7468
|
+
// src/cli/commands/templates.ts
|
|
7469
|
+
var import_commander12 = require("commander");
|
|
7470
|
+
var import_chalk15 = __toESM(require("chalk"), 1);
|
|
7471
|
+
var import_promises10 = __toESM(require("fs/promises"), 1);
|
|
7472
|
+
var import_path12 = __toESM(require("path"), 1);
|
|
7473
|
+
var TEMPLATE_TYPES = [
|
|
7474
|
+
"controller",
|
|
7475
|
+
"service",
|
|
7476
|
+
"repository",
|
|
7477
|
+
"types",
|
|
7478
|
+
"schemas",
|
|
7479
|
+
"routes",
|
|
7480
|
+
"module-index",
|
|
7481
|
+
"controller-test",
|
|
7482
|
+
"service-test",
|
|
7483
|
+
"integration-test"
|
|
7484
|
+
];
|
|
7485
|
+
async function initTemplates() {
|
|
7486
|
+
const projectError = validateProject();
|
|
7487
|
+
if (projectError) {
|
|
7488
|
+
displayError(projectError);
|
|
7489
|
+
return;
|
|
7490
|
+
}
|
|
7491
|
+
const projectRoot = getProjectRoot();
|
|
7492
|
+
const templatesDir = import_path12.default.join(projectRoot, ".servcraft", "templates");
|
|
7493
|
+
try {
|
|
7494
|
+
await import_promises10.default.mkdir(templatesDir, { recursive: true });
|
|
7495
|
+
console.log(import_chalk15.default.cyan("\n\u{1F4C1} Creating custom template directory...\n"));
|
|
7496
|
+
const exampleController = `// Custom controller template
|
|
7497
|
+
// Available variables: name, pascalName, camelName, pluralName
|
|
7498
|
+
export function controllerTemplate(name: string, pascalName: string, camelName: string): string {
|
|
7499
|
+
return \`import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
7500
|
+
import type { \${pascalName}Service } from './\${name}.service.js';
|
|
7501
|
+
|
|
7502
|
+
export class \${pascalName}Controller {
|
|
7503
|
+
constructor(private \${camelName}Service: \${pascalName}Service) {}
|
|
7504
|
+
|
|
7505
|
+
// Add your custom controller methods here
|
|
7506
|
+
async getAll(request: FastifyRequest, reply: FastifyReply) {
|
|
7507
|
+
const data = await this.\${camelName}Service.getAll();
|
|
7508
|
+
return reply.send({ data });
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
\`;
|
|
7512
|
+
}
|
|
7513
|
+
`;
|
|
7514
|
+
await import_promises10.default.writeFile(
|
|
7515
|
+
import_path12.default.join(templatesDir, "controller.example.ts"),
|
|
7516
|
+
exampleController,
|
|
7517
|
+
"utf-8"
|
|
7518
|
+
);
|
|
7519
|
+
console.log(import_chalk15.default.green("\u2714 Created template directory: .servcraft/templates/"));
|
|
7520
|
+
console.log(import_chalk15.default.green("\u2714 Created example template: controller.example.ts\n"));
|
|
7521
|
+
console.log(import_chalk15.default.bold("\u{1F4CB} Available template types:\n"));
|
|
7522
|
+
TEMPLATE_TYPES.forEach((type) => {
|
|
7523
|
+
console.log(import_chalk15.default.gray(` \u2022 ${type}.ts`));
|
|
7524
|
+
});
|
|
7525
|
+
console.log(import_chalk15.default.yellow("\n\u{1F4A1} To customize a template:"));
|
|
7526
|
+
console.log(import_chalk15.default.gray(" 1. Copy the example template"));
|
|
7527
|
+
console.log(import_chalk15.default.gray(" 2. Rename it (remove .example)"));
|
|
7528
|
+
console.log(import_chalk15.default.gray(" 3. Modify the template code"));
|
|
7529
|
+
console.log(import_chalk15.default.gray(" 4. Use --template flag when generating\n"));
|
|
7530
|
+
} catch (error2) {
|
|
7531
|
+
if (error2 instanceof Error) {
|
|
7532
|
+
console.error(import_chalk15.default.red(`
|
|
7533
|
+
\u2717 Failed to initialize templates: ${error2.message}
|
|
7534
|
+
`));
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
}
|
|
7538
|
+
async function listTemplates() {
|
|
7539
|
+
const projectError = validateProject();
|
|
7540
|
+
if (projectError) {
|
|
7541
|
+
displayError(projectError);
|
|
7542
|
+
return;
|
|
7543
|
+
}
|
|
7544
|
+
const projectRoot = getProjectRoot();
|
|
7545
|
+
const projectTemplatesDir = import_path12.default.join(projectRoot, ".servcraft", "templates");
|
|
7546
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
7547
|
+
const userTemplatesDir = import_path12.default.join(homeDir, ".servcraft", "templates");
|
|
7548
|
+
console.log(import_chalk15.default.bold.cyan("\n\u{1F4CB} Available Templates\n"));
|
|
7549
|
+
console.log(import_chalk15.default.bold("Project templates (.servcraft/templates/):"));
|
|
7550
|
+
try {
|
|
7551
|
+
const files = await import_promises10.default.readdir(projectTemplatesDir);
|
|
7552
|
+
const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
|
|
7553
|
+
if (templates.length > 0) {
|
|
7554
|
+
templates.forEach((t) => {
|
|
7555
|
+
console.log(import_chalk15.default.green(` \u2713 ${t}`));
|
|
7556
|
+
});
|
|
7557
|
+
} else {
|
|
7558
|
+
console.log(import_chalk15.default.gray(" (none)"));
|
|
7559
|
+
}
|
|
7560
|
+
} catch {
|
|
7561
|
+
console.log(import_chalk15.default.gray(" (directory not found)"));
|
|
7562
|
+
}
|
|
7563
|
+
console.log(import_chalk15.default.bold("\nUser templates (~/.servcraft/templates/):"));
|
|
7564
|
+
try {
|
|
7565
|
+
const files = await import_promises10.default.readdir(userTemplatesDir);
|
|
7566
|
+
const templates = files.filter((f) => f.endsWith(".ts") && !f.endsWith(".example.ts"));
|
|
7567
|
+
if (templates.length > 0) {
|
|
7568
|
+
templates.forEach((t) => {
|
|
7569
|
+
console.log(import_chalk15.default.green(` \u2713 ${t}`));
|
|
7570
|
+
});
|
|
7571
|
+
} else {
|
|
7572
|
+
console.log(import_chalk15.default.gray(" (none)"));
|
|
7573
|
+
}
|
|
7574
|
+
} catch {
|
|
7575
|
+
console.log(import_chalk15.default.gray(" (directory not found)"));
|
|
7576
|
+
}
|
|
7577
|
+
console.log(import_chalk15.default.bold("\nBuilt-in templates:"));
|
|
7578
|
+
TEMPLATE_TYPES.forEach((t) => {
|
|
7579
|
+
console.log(import_chalk15.default.cyan(` \u2022 ${t}.ts`));
|
|
7580
|
+
});
|
|
7581
|
+
console.log(import_chalk15.default.gray('\n\u{1F4A1} Run "servcraft templates init" to create custom templates\n'));
|
|
7582
|
+
}
|
|
7583
|
+
var templatesCommand = new import_commander12.Command("templates").description("Manage code generation templates").addCommand(
|
|
7584
|
+
new import_commander12.Command("init").description("Initialize custom templates directory").action(initTemplates)
|
|
7585
|
+
).addCommand(new import_commander12.Command("list").description("List available templates").action(listTemplates));
|
|
7586
|
+
|
|
6667
7587
|
// src/cli/index.ts
|
|
6668
|
-
var program = new
|
|
7588
|
+
var program = new import_commander13.Command();
|
|
6669
7589
|
program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
|
|
6670
7590
|
program.addCommand(initCommand);
|
|
6671
7591
|
program.addCommand(generateCommand);
|
|
@@ -6675,5 +7595,9 @@ program.addCommand(docsCommand);
|
|
|
6675
7595
|
program.addCommand(listCommand);
|
|
6676
7596
|
program.addCommand(removeCommand);
|
|
6677
7597
|
program.addCommand(doctorCommand);
|
|
7598
|
+
program.addCommand(updateCommand);
|
|
7599
|
+
program.addCommand(completionCommand);
|
|
7600
|
+
program.addCommand(scaffoldCommand);
|
|
7601
|
+
program.addCommand(templatesCommand);
|
|
6678
7602
|
program.parse();
|
|
6679
7603
|
//# sourceMappingURL=index.cjs.map
|