servcraft 0.2.0 → 0.3.1
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 +44 -2
- package/ROADMAP.md +72 -34
- package/dist/cli/index.cjs +901 -289
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +984 -387
- 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 +30 -0
- package/src/cli/commands/list.ts +1 -1
- package/src/cli/commands/update.ts +221 -0
- package/src/cli/index.ts +8 -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/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_commander11 = 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,7 +1225,6 @@ 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
1229
|
var import_path4 = __toESM(require("path"), 1);
|
|
1422
1230
|
var import_ora2 = __toESM(require("ora"), 1);
|
|
@@ -1424,7 +1232,6 @@ 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 fs12 = require("fs");
|
|
1459
|
+
if (!fs12.existsSync("package.json")) {
|
|
1460
|
+
return ErrorTypes.NOT_IN_PROJECT();
|
|
1461
|
+
}
|
|
1462
|
+
const packageJson = JSON.parse(fs12.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,353 @@ ${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
|
+
|
|
2271
2513
|
// src/cli/commands/generate.ts
|
|
2272
2514
|
function enableDryRunIfNeeded(options) {
|
|
2273
2515
|
const dryRun = DryRunManager.getInstance();
|
|
@@ -2284,7 +2526,7 @@ function showDryRunSummary(options) {
|
|
|
2284
2526
|
var generateCommand = new import_commander2.Command("generate").alias("g").description("Generate resources (module, controller, service, etc.)");
|
|
2285
2527
|
generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
2286
2528
|
"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) => {
|
|
2529
|
+
).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
2530
|
enableDryRunIfNeeded(options);
|
|
2289
2531
|
let fields = [];
|
|
2290
2532
|
if (options.interactive) {
|
|
@@ -2341,6 +2583,21 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
2341
2583
|
for (const file of files) {
|
|
2342
2584
|
await writeFile(import_path4.default.join(moduleDir, file.name), file.content);
|
|
2343
2585
|
}
|
|
2586
|
+
if (options.withTests) {
|
|
2587
|
+
const testDir = import_path4.default.join(moduleDir, "__tests__");
|
|
2588
|
+
await writeFile(
|
|
2589
|
+
import_path4.default.join(testDir, `${kebabName}.controller.test.ts`),
|
|
2590
|
+
controllerTestTemplate(kebabName, pascalName, camelName)
|
|
2591
|
+
);
|
|
2592
|
+
await writeFile(
|
|
2593
|
+
import_path4.default.join(testDir, `${kebabName}.service.test.ts`),
|
|
2594
|
+
serviceTestTemplate(kebabName, pascalName, camelName)
|
|
2595
|
+
);
|
|
2596
|
+
await writeFile(
|
|
2597
|
+
import_path4.default.join(testDir, `${kebabName}.integration.test.ts`),
|
|
2598
|
+
integrationTestTemplate(kebabName, pascalName, camelName)
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2344
2601
|
spinner.succeed(`Module "${pascalName}" generated successfully!`);
|
|
2345
2602
|
if (options.prisma || hasFields) {
|
|
2346
2603
|
console.log("\n" + "\u2500".repeat(50));
|
|
@@ -2364,6 +2621,11 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
2364
2621
|
}
|
|
2365
2622
|
console.log("\n\u{1F4C1} Files created:");
|
|
2366
2623
|
files.forEach((f) => success(` src/modules/${kebabName}/${f.name}`));
|
|
2624
|
+
if (options.withTests) {
|
|
2625
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.controller.test.ts`);
|
|
2626
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.service.test.ts`);
|
|
2627
|
+
success(` src/modules/${kebabName}/__tests__/${kebabName}.integration.test.ts`);
|
|
2628
|
+
}
|
|
2367
2629
|
console.log("\n\u{1F4CC} Next steps:");
|
|
2368
2630
|
if (!hasFields) {
|
|
2369
2631
|
info(` 1. Update the types in ${kebabName}.types.ts`);
|
|
@@ -2600,7 +2862,6 @@ async function promptForFields() {
|
|
|
2600
2862
|
}
|
|
2601
2863
|
|
|
2602
2864
|
// src/cli/commands/add-module.ts
|
|
2603
|
-
init_cjs_shims();
|
|
2604
2865
|
var import_commander3 = require("commander");
|
|
2605
2866
|
var import_path5 = __toESM(require("path"), 1);
|
|
2606
2867
|
var import_ora3 = __toESM(require("ora"), 1);
|
|
@@ -2608,7 +2869,6 @@ var import_chalk7 = __toESM(require("chalk"), 1);
|
|
|
2608
2869
|
var fs5 = __toESM(require("fs/promises"), 1);
|
|
2609
2870
|
|
|
2610
2871
|
// src/cli/utils/env-manager.ts
|
|
2611
|
-
init_cjs_shims();
|
|
2612
2872
|
var fs3 = __toESM(require("fs/promises"), 1);
|
|
2613
2873
|
var path5 = __toESM(require("path"), 1);
|
|
2614
2874
|
var import_fs = require("fs");
|
|
@@ -3267,7 +3527,6 @@ var EnvManager = class {
|
|
|
3267
3527
|
};
|
|
3268
3528
|
|
|
3269
3529
|
// src/cli/utils/template-manager.ts
|
|
3270
|
-
init_cjs_shims();
|
|
3271
3530
|
var fs4 = __toESM(require("fs/promises"), 1);
|
|
3272
3531
|
var path6 = __toESM(require("path"), 1);
|
|
3273
3532
|
var import_crypto = require("crypto");
|
|
@@ -3510,7 +3769,6 @@ var TemplateManager = class {
|
|
|
3510
3769
|
};
|
|
3511
3770
|
|
|
3512
3771
|
// src/cli/utils/interactive-prompt.ts
|
|
3513
|
-
init_cjs_shims();
|
|
3514
3772
|
var import_inquirer3 = __toESM(require("inquirer"), 1);
|
|
3515
3773
|
var import_chalk6 = __toESM(require("chalk"), 1);
|
|
3516
3774
|
var InteractivePrompt = class {
|
|
@@ -3696,7 +3954,6 @@ var InteractivePrompt = class {
|
|
|
3696
3954
|
};
|
|
3697
3955
|
|
|
3698
3956
|
// src/cli/commands/add-module.ts
|
|
3699
|
-
init_error_handler();
|
|
3700
3957
|
var AVAILABLE_MODULES = {
|
|
3701
3958
|
auth: {
|
|
3702
3959
|
name: "Authentication",
|
|
@@ -4438,7 +4695,6 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4438
4695
|
}
|
|
4439
4696
|
|
|
4440
4697
|
// src/cli/commands/db.ts
|
|
4441
|
-
init_cjs_shims();
|
|
4442
4698
|
var import_commander4 = require("commander");
|
|
4443
4699
|
var import_child_process2 = require("child_process");
|
|
4444
4700
|
var import_ora4 = __toESM(require("ora"), 1);
|
|
@@ -4532,7 +4788,6 @@ dbCommand.command("status").description("Show migration status").action(async ()
|
|
|
4532
4788
|
});
|
|
4533
4789
|
|
|
4534
4790
|
// src/cli/commands/docs.ts
|
|
4535
|
-
init_cjs_shims();
|
|
4536
4791
|
var import_commander5 = require("commander");
|
|
4537
4792
|
var import_path7 = __toESM(require("path"), 1);
|
|
4538
4793
|
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
@@ -4540,17 +4795,14 @@ var import_ora6 = __toESM(require("ora"), 1);
|
|
|
4540
4795
|
var import_chalk9 = __toESM(require("chalk"), 1);
|
|
4541
4796
|
|
|
4542
4797
|
// src/cli/utils/docs-generator.ts
|
|
4543
|
-
init_cjs_shims();
|
|
4544
4798
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
4545
4799
|
var import_path6 = __toESM(require("path"), 1);
|
|
4546
4800
|
var import_ora5 = __toESM(require("ora"), 1);
|
|
4547
4801
|
|
|
4548
4802
|
// src/core/server.ts
|
|
4549
|
-
init_cjs_shims();
|
|
4550
4803
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4551
4804
|
|
|
4552
4805
|
// src/core/logger.ts
|
|
4553
|
-
init_cjs_shims();
|
|
4554
4806
|
var import_pino = __toESM(require("pino"), 1);
|
|
4555
4807
|
var defaultConfig = {
|
|
4556
4808
|
level: process.env.LOG_LEVEL || "info",
|
|
@@ -4683,14 +4935,7 @@ function createServer(config2 = {}) {
|
|
|
4683
4935
|
return new Server(config2);
|
|
4684
4936
|
}
|
|
4685
4937
|
|
|
4686
|
-
// src/middleware/index.ts
|
|
4687
|
-
init_cjs_shims();
|
|
4688
|
-
|
|
4689
|
-
// src/middleware/error-handler.ts
|
|
4690
|
-
init_cjs_shims();
|
|
4691
|
-
|
|
4692
4938
|
// src/utils/errors.ts
|
|
4693
|
-
init_cjs_shims();
|
|
4694
4939
|
var AppError = class _AppError extends Error {
|
|
4695
4940
|
statusCode;
|
|
4696
4941
|
isOperational;
|
|
@@ -4744,11 +4989,7 @@ function isAppError(error2) {
|
|
|
4744
4989
|
return error2 instanceof AppError;
|
|
4745
4990
|
}
|
|
4746
4991
|
|
|
4747
|
-
// src/config/index.ts
|
|
4748
|
-
init_cjs_shims();
|
|
4749
|
-
|
|
4750
4992
|
// src/config/env.ts
|
|
4751
|
-
init_cjs_shims();
|
|
4752
4993
|
var import_zod = require("zod");
|
|
4753
4994
|
var import_dotenv = __toESM(require("dotenv"), 1);
|
|
4754
4995
|
import_dotenv.default.config();
|
|
@@ -4894,7 +5135,6 @@ function registerErrorHandler(app) {
|
|
|
4894
5135
|
}
|
|
4895
5136
|
|
|
4896
5137
|
// src/middleware/security.ts
|
|
4897
|
-
init_cjs_shims();
|
|
4898
5138
|
var import_helmet = __toESM(require("@fastify/helmet"), 1);
|
|
4899
5139
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4900
5140
|
var import_rate_limit = __toESM(require("@fastify/rate-limit"), 1);
|
|
@@ -4954,11 +5194,7 @@ async function registerSecurity(app, options = {}) {
|
|
|
4954
5194
|
}
|
|
4955
5195
|
}
|
|
4956
5196
|
|
|
4957
|
-
// src/modules/swagger/index.ts
|
|
4958
|
-
init_cjs_shims();
|
|
4959
|
-
|
|
4960
5197
|
// src/modules/swagger/swagger.service.ts
|
|
4961
|
-
init_cjs_shims();
|
|
4962
5198
|
var import_swagger = __toESM(require("@fastify/swagger"), 1);
|
|
4963
5199
|
var import_swagger_ui = __toESM(require("@fastify/swagger-ui"), 1);
|
|
4964
5200
|
var defaultConfig3 = {
|
|
@@ -5018,16 +5254,11 @@ async function registerSwagger(app, customConfig) {
|
|
|
5018
5254
|
logger.info("Swagger documentation registered at /docs");
|
|
5019
5255
|
}
|
|
5020
5256
|
|
|
5021
|
-
// src/modules/swagger/schema-builder.ts
|
|
5022
|
-
init_cjs_shims();
|
|
5023
|
-
|
|
5024
5257
|
// src/modules/auth/index.ts
|
|
5025
|
-
init_cjs_shims();
|
|
5026
5258
|
var import_jwt = __toESM(require("@fastify/jwt"), 1);
|
|
5027
5259
|
var import_cookie = __toESM(require("@fastify/cookie"), 1);
|
|
5028
5260
|
|
|
5029
5261
|
// src/modules/auth/auth.service.ts
|
|
5030
|
-
init_cjs_shims();
|
|
5031
5262
|
var import_bcryptjs = __toESM(require("bcryptjs"), 1);
|
|
5032
5263
|
var import_ioredis = require("ioredis");
|
|
5033
5264
|
var AuthService = class {
|
|
@@ -5226,11 +5457,7 @@ function createAuthService(app) {
|
|
|
5226
5457
|
return new AuthService(app);
|
|
5227
5458
|
}
|
|
5228
5459
|
|
|
5229
|
-
// src/modules/auth/auth.controller.ts
|
|
5230
|
-
init_cjs_shims();
|
|
5231
|
-
|
|
5232
5460
|
// src/modules/auth/schemas.ts
|
|
5233
|
-
init_cjs_shims();
|
|
5234
5461
|
var import_zod2 = require("zod");
|
|
5235
5462
|
var loginSchema = import_zod2.z.object({
|
|
5236
5463
|
email: import_zod2.z.string().email("Invalid email address"),
|
|
@@ -5257,7 +5484,6 @@ var changePasswordSchema = import_zod2.z.object({
|
|
|
5257
5484
|
});
|
|
5258
5485
|
|
|
5259
5486
|
// src/utils/response.ts
|
|
5260
|
-
init_cjs_shims();
|
|
5261
5487
|
function success2(reply, data, statusCode = 200) {
|
|
5262
5488
|
const response = {
|
|
5263
5489
|
success: true,
|
|
@@ -5273,7 +5499,6 @@ function noContent(reply) {
|
|
|
5273
5499
|
}
|
|
5274
5500
|
|
|
5275
5501
|
// src/modules/validation/validator.ts
|
|
5276
|
-
init_cjs_shims();
|
|
5277
5502
|
var import_zod3 = require("zod");
|
|
5278
5503
|
function validateBody(schema, data) {
|
|
5279
5504
|
const result = schema.safeParse(data);
|
|
@@ -5292,11 +5517,11 @@ function validateQuery(schema, data) {
|
|
|
5292
5517
|
function formatZodErrors(error2) {
|
|
5293
5518
|
const errors = {};
|
|
5294
5519
|
for (const issue of error2.issues) {
|
|
5295
|
-
const
|
|
5296
|
-
if (!errors[
|
|
5297
|
-
errors[
|
|
5520
|
+
const path12 = issue.path.join(".") || "root";
|
|
5521
|
+
if (!errors[path12]) {
|
|
5522
|
+
errors[path12] = [];
|
|
5298
5523
|
}
|
|
5299
|
-
errors[
|
|
5524
|
+
errors[path12].push(issue.message);
|
|
5300
5525
|
}
|
|
5301
5526
|
return errors;
|
|
5302
5527
|
}
|
|
@@ -5444,11 +5669,7 @@ function createAuthController(authService, userService) {
|
|
|
5444
5669
|
return new AuthController(authService, userService);
|
|
5445
5670
|
}
|
|
5446
5671
|
|
|
5447
|
-
// src/modules/auth/auth.routes.ts
|
|
5448
|
-
init_cjs_shims();
|
|
5449
|
-
|
|
5450
5672
|
// src/modules/auth/auth.middleware.ts
|
|
5451
|
-
init_cjs_shims();
|
|
5452
5673
|
function createAuthMiddleware(authService) {
|
|
5453
5674
|
return async function authenticate(request, _reply) {
|
|
5454
5675
|
const authHeader = request.headers.authorization;
|
|
@@ -5491,14 +5712,7 @@ function registerAuthRoutes(app, controller, authService) {
|
|
|
5491
5712
|
);
|
|
5492
5713
|
}
|
|
5493
5714
|
|
|
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
5715
|
// src/database/prisma.ts
|
|
5501
|
-
init_cjs_shims();
|
|
5502
5716
|
var import_client = require("@prisma/client");
|
|
5503
5717
|
var prismaClientSingleton = () => {
|
|
5504
5718
|
return new import_client.PrismaClient({
|
|
@@ -5512,7 +5726,6 @@ if (!isProduction()) {
|
|
|
5512
5726
|
}
|
|
5513
5727
|
|
|
5514
5728
|
// src/utils/pagination.ts
|
|
5515
|
-
init_cjs_shims();
|
|
5516
5729
|
var DEFAULT_PAGE = 1;
|
|
5517
5730
|
var DEFAULT_LIMIT = 20;
|
|
5518
5731
|
var MAX_LIMIT = 100;
|
|
@@ -5777,7 +5990,6 @@ function createUserRepository() {
|
|
|
5777
5990
|
}
|
|
5778
5991
|
|
|
5779
5992
|
// src/modules/user/types.ts
|
|
5780
|
-
init_cjs_shims();
|
|
5781
5993
|
var DEFAULT_ROLE_PERMISSIONS = {
|
|
5782
5994
|
user: ["profile:read", "profile:update"],
|
|
5783
5995
|
moderator: [
|
|
@@ -5908,9 +6120,6 @@ function createUserService(repository) {
|
|
|
5908
6120
|
return new UserService(repository || createUserRepository());
|
|
5909
6121
|
}
|
|
5910
6122
|
|
|
5911
|
-
// src/modules/auth/types.ts
|
|
5912
|
-
init_cjs_shims();
|
|
5913
|
-
|
|
5914
6123
|
// src/modules/auth/index.ts
|
|
5915
6124
|
async function registerAuthModule(app) {
|
|
5916
6125
|
await app.register(import_jwt.default, {
|
|
@@ -5930,14 +6139,7 @@ async function registerAuthModule(app) {
|
|
|
5930
6139
|
logger.info("Auth module registered");
|
|
5931
6140
|
}
|
|
5932
6141
|
|
|
5933
|
-
// src/modules/user/index.ts
|
|
5934
|
-
init_cjs_shims();
|
|
5935
|
-
|
|
5936
|
-
// src/modules/user/user.controller.ts
|
|
5937
|
-
init_cjs_shims();
|
|
5938
|
-
|
|
5939
6142
|
// src/modules/user/schemas.ts
|
|
5940
|
-
init_cjs_shims();
|
|
5941
6143
|
var import_zod4 = require("zod");
|
|
5942
6144
|
var userStatusEnum = import_zod4.z.enum(["active", "inactive", "suspended", "banned"]);
|
|
5943
6145
|
var userRoleEnum = import_zod4.z.enum(["user", "admin", "moderator", "super_admin"]);
|
|
@@ -6064,7 +6266,6 @@ function createUserController(userService) {
|
|
|
6064
6266
|
}
|
|
6065
6267
|
|
|
6066
6268
|
// src/modules/user/user.routes.ts
|
|
6067
|
-
init_cjs_shims();
|
|
6068
6269
|
var idParamsSchema = {
|
|
6069
6270
|
type: "object",
|
|
6070
6271
|
properties: {
|
|
@@ -6356,7 +6557,6 @@ function formatDate(date) {
|
|
|
6356
6557
|
}
|
|
6357
6558
|
|
|
6358
6559
|
// src/cli/commands/list.ts
|
|
6359
|
-
init_cjs_shims();
|
|
6360
6560
|
var import_commander6 = require("commander");
|
|
6361
6561
|
var import_chalk10 = __toESM(require("chalk"), 1);
|
|
6362
6562
|
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
@@ -6514,7 +6714,7 @@ var listCommand = new import_commander6.Command("list").alias("ls").description(
|
|
|
6514
6714
|
if (!byCategory[mod.category]) {
|
|
6515
6715
|
byCategory[mod.category] = [];
|
|
6516
6716
|
}
|
|
6517
|
-
byCategory[mod.category]
|
|
6717
|
+
byCategory[mod.category]?.push({
|
|
6518
6718
|
id: key,
|
|
6519
6719
|
name: mod.name,
|
|
6520
6720
|
description: mod.description,
|
|
@@ -6582,14 +6782,12 @@ var listCommand = new import_commander6.Command("list").alias("ls").description(
|
|
|
6582
6782
|
);
|
|
6583
6783
|
|
|
6584
6784
|
// src/cli/commands/remove.ts
|
|
6585
|
-
init_cjs_shims();
|
|
6586
6785
|
var import_commander7 = require("commander");
|
|
6587
6786
|
var import_path8 = __toESM(require("path"), 1);
|
|
6588
6787
|
var import_ora7 = __toESM(require("ora"), 1);
|
|
6589
6788
|
var import_chalk11 = __toESM(require("chalk"), 1);
|
|
6590
6789
|
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
6591
6790
|
var import_inquirer4 = __toESM(require("inquirer"), 1);
|
|
6592
|
-
init_error_handler();
|
|
6593
6791
|
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
6792
|
const projectError = validateProject();
|
|
6595
6793
|
if (projectError) {
|
|
@@ -6602,13 +6800,10 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
|
|
|
6602
6800
|
const exists = await import_promises6.default.access(moduleDir).then(() => true).catch(() => false);
|
|
6603
6801
|
if (!exists) {
|
|
6604
6802
|
displayError(
|
|
6605
|
-
new (
|
|
6606
|
-
`
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
`Check the spelling of the module name`
|
|
6610
|
-
]
|
|
6611
|
-
)
|
|
6803
|
+
new ServCraftError(`Module "${moduleName}" is not installed`, [
|
|
6804
|
+
`Run ${import_chalk11.default.cyan("servcraft list --installed")} to see installed modules`,
|
|
6805
|
+
`Check the spelling of the module name`
|
|
6806
|
+
])
|
|
6612
6807
|
);
|
|
6613
6808
|
return;
|
|
6614
6809
|
}
|
|
@@ -6657,15 +6852,430 @@ var removeCommand = new import_commander7.Command("remove").alias("rm").descript
|
|
|
6657
6852
|
});
|
|
6658
6853
|
|
|
6659
6854
|
// src/cli/commands/doctor.ts
|
|
6660
|
-
init_cjs_shims();
|
|
6661
6855
|
var import_commander8 = require("commander");
|
|
6662
6856
|
var import_chalk12 = __toESM(require("chalk"), 1);
|
|
6857
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
6858
|
+
async function checkNodeVersion() {
|
|
6859
|
+
const version = process.version;
|
|
6860
|
+
const major = parseInt(version.slice(1).split(".")[0] || "0", 10);
|
|
6861
|
+
if (major >= 18) {
|
|
6862
|
+
return { name: "Node.js", status: "pass", message: `${version} \u2713` };
|
|
6863
|
+
}
|
|
6864
|
+
return {
|
|
6865
|
+
name: "Node.js",
|
|
6866
|
+
status: "fail",
|
|
6867
|
+
message: `${version} (< 18)`,
|
|
6868
|
+
suggestion: "Upgrade to Node.js 18+"
|
|
6869
|
+
};
|
|
6870
|
+
}
|
|
6871
|
+
async function checkPackageJson() {
|
|
6872
|
+
const checks = [];
|
|
6873
|
+
try {
|
|
6874
|
+
const content = await import_promises7.default.readFile("package.json", "utf-8");
|
|
6875
|
+
const pkg = JSON.parse(content);
|
|
6876
|
+
checks.push({ name: "package.json", status: "pass", message: "Found" });
|
|
6877
|
+
if (pkg.dependencies?.fastify) {
|
|
6878
|
+
checks.push({ name: "Fastify", status: "pass", message: "Installed" });
|
|
6879
|
+
} else {
|
|
6880
|
+
checks.push({
|
|
6881
|
+
name: "Fastify",
|
|
6882
|
+
status: "fail",
|
|
6883
|
+
message: "Missing",
|
|
6884
|
+
suggestion: "npm install fastify"
|
|
6885
|
+
});
|
|
6886
|
+
}
|
|
6887
|
+
} catch {
|
|
6888
|
+
checks.push({
|
|
6889
|
+
name: "package.json",
|
|
6890
|
+
status: "fail",
|
|
6891
|
+
message: "Not found",
|
|
6892
|
+
suggestion: "Run servcraft init"
|
|
6893
|
+
});
|
|
6894
|
+
}
|
|
6895
|
+
return checks;
|
|
6896
|
+
}
|
|
6897
|
+
async function checkDirectories() {
|
|
6898
|
+
const checks = [];
|
|
6899
|
+
const dirs = ["src", "node_modules", ".git", ".env"];
|
|
6900
|
+
for (const dir of dirs) {
|
|
6901
|
+
try {
|
|
6902
|
+
await import_promises7.default.access(dir);
|
|
6903
|
+
checks.push({ name: dir, status: "pass", message: "Exists" });
|
|
6904
|
+
} catch {
|
|
6905
|
+
const isCritical = dir === "src" || dir === "node_modules";
|
|
6906
|
+
checks.push({
|
|
6907
|
+
name: dir,
|
|
6908
|
+
status: isCritical ? "fail" : "warn",
|
|
6909
|
+
message: "Not found",
|
|
6910
|
+
suggestion: dir === "node_modules" ? "npm install" : dir === ".env" ? "Create .env file" : void 0
|
|
6911
|
+
});
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
return checks;
|
|
6915
|
+
}
|
|
6663
6916
|
var doctorCommand = new import_commander8.Command("doctor").description("Diagnose project configuration and dependencies").action(async () => {
|
|
6664
|
-
console.log(import_chalk12.default.bold.cyan("\
|
|
6917
|
+
console.log(import_chalk12.default.bold.cyan("\n\u{1F50D} ServCraft Doctor\n"));
|
|
6918
|
+
const allChecks = [];
|
|
6919
|
+
allChecks.push(await checkNodeVersion());
|
|
6920
|
+
allChecks.push(...await checkPackageJson());
|
|
6921
|
+
allChecks.push(...await checkDirectories());
|
|
6922
|
+
allChecks.forEach((check) => {
|
|
6923
|
+
const icon = check.status === "pass" ? import_chalk12.default.green("\u2713") : check.status === "warn" ? import_chalk12.default.yellow("\u26A0") : import_chalk12.default.red("\u2717");
|
|
6924
|
+
const color = check.status === "pass" ? import_chalk12.default.green : check.status === "warn" ? import_chalk12.default.yellow : import_chalk12.default.red;
|
|
6925
|
+
console.log(`${icon} ${check.name.padEnd(20)} ${color(check.message)}`);
|
|
6926
|
+
if (check.suggestion) {
|
|
6927
|
+
console.log(import_chalk12.default.gray(` \u2192 ${check.suggestion}`));
|
|
6928
|
+
}
|
|
6929
|
+
});
|
|
6930
|
+
const pass = allChecks.filter((c) => c.status === "pass").length;
|
|
6931
|
+
const warn2 = allChecks.filter((c) => c.status === "warn").length;
|
|
6932
|
+
const fail = allChecks.filter((c) => c.status === "fail").length;
|
|
6933
|
+
console.log(import_chalk12.default.gray("\n" + "\u2500".repeat(60)));
|
|
6934
|
+
console.log(
|
|
6935
|
+
`
|
|
6936
|
+
${import_chalk12.default.green(pass + " passed")} | ${import_chalk12.default.yellow(warn2 + " warnings")} | ${import_chalk12.default.red(fail + " failed")}
|
|
6937
|
+
`
|
|
6938
|
+
);
|
|
6939
|
+
if (fail === 0 && warn2 === 0) {
|
|
6940
|
+
console.log(import_chalk12.default.green.bold("\u2728 Everything looks good!\n"));
|
|
6941
|
+
} else if (fail > 0) {
|
|
6942
|
+
console.log(import_chalk12.default.red.bold("\u2717 Fix critical issues before using ServCraft.\n"));
|
|
6943
|
+
} else {
|
|
6944
|
+
console.log(import_chalk12.default.yellow.bold("\u26A0 Some warnings, but should work.\n"));
|
|
6945
|
+
}
|
|
6946
|
+
});
|
|
6947
|
+
|
|
6948
|
+
// src/cli/commands/update.ts
|
|
6949
|
+
var import_commander9 = require("commander");
|
|
6950
|
+
var import_chalk13 = __toESM(require("chalk"), 1);
|
|
6951
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
6952
|
+
var import_path9 = __toESM(require("path"), 1);
|
|
6953
|
+
var import_url = require("url");
|
|
6954
|
+
var import_inquirer5 = __toESM(require("inquirer"), 1);
|
|
6955
|
+
var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
6956
|
+
var __dirname = import_path9.default.dirname(__filename2);
|
|
6957
|
+
var AVAILABLE_MODULES3 = [
|
|
6958
|
+
"auth",
|
|
6959
|
+
"users",
|
|
6960
|
+
"email",
|
|
6961
|
+
"mfa",
|
|
6962
|
+
"oauth",
|
|
6963
|
+
"rate-limit",
|
|
6964
|
+
"cache",
|
|
6965
|
+
"upload",
|
|
6966
|
+
"search",
|
|
6967
|
+
"notification",
|
|
6968
|
+
"webhook",
|
|
6969
|
+
"websocket",
|
|
6970
|
+
"queue",
|
|
6971
|
+
"payment",
|
|
6972
|
+
"i18n",
|
|
6973
|
+
"feature-flag",
|
|
6974
|
+
"analytics",
|
|
6975
|
+
"media-processing",
|
|
6976
|
+
"api-versioning",
|
|
6977
|
+
"audit",
|
|
6978
|
+
"swagger",
|
|
6979
|
+
"validation"
|
|
6980
|
+
];
|
|
6981
|
+
async function getInstalledModules2() {
|
|
6982
|
+
try {
|
|
6983
|
+
const modulesDir = getModulesDir();
|
|
6984
|
+
const entries = await import_promises8.default.readdir(modulesDir, { withFileTypes: true });
|
|
6985
|
+
const installedModules = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => AVAILABLE_MODULES3.includes(name));
|
|
6986
|
+
return installedModules;
|
|
6987
|
+
} catch {
|
|
6988
|
+
return [];
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
async function copyModuleFiles(moduleName, _projectRoot) {
|
|
6992
|
+
const cliRoot = import_path9.default.resolve(__dirname, "../../../");
|
|
6993
|
+
const sourceModulePath = import_path9.default.join(cliRoot, "src", "modules", moduleName);
|
|
6994
|
+
const targetModulesDir = getModulesDir();
|
|
6995
|
+
const targetModulePath = import_path9.default.join(targetModulesDir, moduleName);
|
|
6996
|
+
try {
|
|
6997
|
+
await import_promises8.default.access(sourceModulePath);
|
|
6998
|
+
} catch {
|
|
6999
|
+
throw new Error(`Module source not found: ${moduleName}`);
|
|
7000
|
+
}
|
|
7001
|
+
await import_promises8.default.cp(sourceModulePath, targetModulePath, { recursive: true });
|
|
7002
|
+
}
|
|
7003
|
+
async function updateModule(moduleName, options) {
|
|
7004
|
+
const projectError = validateProject();
|
|
7005
|
+
if (projectError) {
|
|
7006
|
+
displayError(projectError);
|
|
7007
|
+
return;
|
|
7008
|
+
}
|
|
7009
|
+
const projectRoot = getProjectRoot();
|
|
7010
|
+
const installedModules = await getInstalledModules2();
|
|
7011
|
+
if (!installedModules.includes(moduleName)) {
|
|
7012
|
+
console.log(import_chalk13.default.yellow(`
|
|
7013
|
+
\u26A0 Module "${moduleName}" is not installed
|
|
7014
|
+
`));
|
|
7015
|
+
console.log(
|
|
7016
|
+
import_chalk13.default.gray(`Run ${import_chalk13.default.cyan(`servcraft add ${moduleName}`)} to install it first.
|
|
7017
|
+
`)
|
|
7018
|
+
);
|
|
7019
|
+
return;
|
|
7020
|
+
}
|
|
7021
|
+
if (options.check) {
|
|
7022
|
+
console.log(import_chalk13.default.cyan(`
|
|
7023
|
+
\u{1F4E6} Checking updates for "${moduleName}"...
|
|
7024
|
+
`));
|
|
7025
|
+
console.log(import_chalk13.default.gray("Note: Version tracking will be implemented in a future release."));
|
|
7026
|
+
console.log(import_chalk13.default.gray("Currently, update will always reinstall the latest version.\n"));
|
|
7027
|
+
return;
|
|
7028
|
+
}
|
|
7029
|
+
const { confirmed } = await import_inquirer5.default.prompt([
|
|
7030
|
+
{
|
|
7031
|
+
type: "confirm",
|
|
7032
|
+
name: "confirmed",
|
|
7033
|
+
message: `Update "${moduleName}" module? This will overwrite existing files.`,
|
|
7034
|
+
default: false
|
|
7035
|
+
}
|
|
7036
|
+
]);
|
|
7037
|
+
if (!confirmed) {
|
|
7038
|
+
console.log(import_chalk13.default.yellow("\n\u26A0 Update cancelled\n"));
|
|
7039
|
+
return;
|
|
7040
|
+
}
|
|
7041
|
+
console.log(import_chalk13.default.cyan(`
|
|
7042
|
+
\u{1F504} Updating "${moduleName}" module...
|
|
7043
|
+
`));
|
|
7044
|
+
try {
|
|
7045
|
+
await copyModuleFiles(moduleName, projectRoot);
|
|
7046
|
+
console.log(import_chalk13.default.green(`\u2714 Module "${moduleName}" updated successfully!
|
|
7047
|
+
`));
|
|
7048
|
+
console.log(
|
|
7049
|
+
import_chalk13.default.gray("Note: Remember to review any breaking changes in the documentation.\n")
|
|
7050
|
+
);
|
|
7051
|
+
} catch (error2) {
|
|
7052
|
+
if (error2 instanceof Error) {
|
|
7053
|
+
console.error(import_chalk13.default.red(`
|
|
7054
|
+
\u2717 Failed to update module: ${error2.message}
|
|
7055
|
+
`));
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
async function updateAllModules(options) {
|
|
7060
|
+
const projectError = validateProject();
|
|
7061
|
+
if (projectError) {
|
|
7062
|
+
displayError(projectError);
|
|
7063
|
+
return;
|
|
7064
|
+
}
|
|
7065
|
+
const installedModules = await getInstalledModules2();
|
|
7066
|
+
if (installedModules.length === 0) {
|
|
7067
|
+
console.log(import_chalk13.default.yellow("\n\u26A0 No modules installed\n"));
|
|
7068
|
+
console.log(import_chalk13.default.gray(`Run ${import_chalk13.default.cyan("servcraft list")} to see available modules.
|
|
7069
|
+
`));
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
if (options.check) {
|
|
7073
|
+
console.log(import_chalk13.default.cyan("\n\u{1F4E6} Checking updates for all modules...\n"));
|
|
7074
|
+
console.log(import_chalk13.default.bold("Installed modules:"));
|
|
7075
|
+
installedModules.forEach((mod) => {
|
|
7076
|
+
console.log(` \u2022 ${import_chalk13.default.cyan(mod)}`);
|
|
7077
|
+
});
|
|
7078
|
+
console.log();
|
|
7079
|
+
console.log(import_chalk13.default.gray("Note: Version tracking will be implemented in a future release."));
|
|
7080
|
+
console.log(import_chalk13.default.gray("Currently, update will always reinstall the latest version.\n"));
|
|
7081
|
+
return;
|
|
7082
|
+
}
|
|
7083
|
+
console.log(import_chalk13.default.cyan(`
|
|
7084
|
+
\u{1F4E6} Found ${installedModules.length} installed module(s):
|
|
7085
|
+
`));
|
|
7086
|
+
installedModules.forEach((mod) => {
|
|
7087
|
+
console.log(` \u2022 ${import_chalk13.default.cyan(mod)}`);
|
|
7088
|
+
});
|
|
7089
|
+
console.log();
|
|
7090
|
+
const { confirmed } = await import_inquirer5.default.prompt([
|
|
7091
|
+
{
|
|
7092
|
+
type: "confirm",
|
|
7093
|
+
name: "confirmed",
|
|
7094
|
+
message: "Update all modules? This will overwrite existing files.",
|
|
7095
|
+
default: false
|
|
7096
|
+
}
|
|
7097
|
+
]);
|
|
7098
|
+
if (!confirmed) {
|
|
7099
|
+
console.log(import_chalk13.default.yellow("\n\u26A0 Update cancelled\n"));
|
|
7100
|
+
return;
|
|
7101
|
+
}
|
|
7102
|
+
console.log(import_chalk13.default.cyan("\n\u{1F504} Updating all modules...\n"));
|
|
7103
|
+
const projectRoot = getProjectRoot();
|
|
7104
|
+
let successCount = 0;
|
|
7105
|
+
let failCount = 0;
|
|
7106
|
+
for (const moduleName of installedModules) {
|
|
7107
|
+
try {
|
|
7108
|
+
await copyModuleFiles(moduleName, projectRoot);
|
|
7109
|
+
console.log(import_chalk13.default.green(`\u2714 Updated: ${moduleName}`));
|
|
7110
|
+
successCount++;
|
|
7111
|
+
} catch {
|
|
7112
|
+
console.error(import_chalk13.default.red(`\u2717 Failed: ${moduleName}`));
|
|
7113
|
+
failCount++;
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
console.log();
|
|
7117
|
+
console.log(
|
|
7118
|
+
import_chalk13.default.bold(
|
|
7119
|
+
`
|
|
7120
|
+
\u2714 Update complete: ${import_chalk13.default.green(successCount)} succeeded, ${import_chalk13.default.red(failCount)} failed
|
|
7121
|
+
`
|
|
7122
|
+
)
|
|
7123
|
+
);
|
|
7124
|
+
if (successCount > 0) {
|
|
7125
|
+
console.log(
|
|
7126
|
+
import_chalk13.default.gray("Note: Remember to review any breaking changes in the documentation.\n")
|
|
7127
|
+
);
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
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) => {
|
|
7131
|
+
if (moduleName) {
|
|
7132
|
+
await updateModule(moduleName, { check: options?.check });
|
|
7133
|
+
} else {
|
|
7134
|
+
await updateAllModules({ check: options?.check });
|
|
7135
|
+
}
|
|
7136
|
+
});
|
|
7137
|
+
|
|
7138
|
+
// src/cli/commands/completion.ts
|
|
7139
|
+
var import_commander10 = require("commander");
|
|
7140
|
+
var bashScript = `
|
|
7141
|
+
# servcraft bash completion script
|
|
7142
|
+
_servcraft_completions() {
|
|
7143
|
+
local cur prev words cword
|
|
7144
|
+
_init_completion || return
|
|
7145
|
+
|
|
7146
|
+
# Main commands
|
|
7147
|
+
local commands="init add generate list remove doctor update completion docs --version --help"
|
|
7148
|
+
|
|
7149
|
+
# Generate subcommands
|
|
7150
|
+
local generate_subcommands="module controller service repository types schema routes m c s r t"
|
|
7151
|
+
|
|
7152
|
+
case "\${words[1]}" in
|
|
7153
|
+
generate|g)
|
|
7154
|
+
if [[ \${cword} -eq 2 ]]; then
|
|
7155
|
+
COMPREPLY=( $(compgen -W "\${generate_subcommands}" -- "\${cur}") )
|
|
7156
|
+
fi
|
|
7157
|
+
;;
|
|
7158
|
+
add|remove|rm|update)
|
|
7159
|
+
if [[ \${cword} -eq 2 ]]; then
|
|
7160
|
+
# Get available modules
|
|
7161
|
+
local modules="auth cache rate-limit notification payment oauth mfa queue websocket upload"
|
|
7162
|
+
COMPREPLY=( $(compgen -W "\${modules}" -- "\${cur}") )
|
|
7163
|
+
fi
|
|
7164
|
+
;;
|
|
7165
|
+
completion)
|
|
7166
|
+
if [[ \${cword} -eq 2 ]]; then
|
|
7167
|
+
COMPREPLY=( $(compgen -W "bash zsh" -- "\${cur}") )
|
|
7168
|
+
fi
|
|
7169
|
+
;;
|
|
7170
|
+
*)
|
|
7171
|
+
if [[ \${cword} -eq 1 ]]; then
|
|
7172
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
7173
|
+
fi
|
|
7174
|
+
;;
|
|
7175
|
+
esac
|
|
7176
|
+
}
|
|
7177
|
+
|
|
7178
|
+
complete -F _servcraft_completions servcraft
|
|
7179
|
+
`;
|
|
7180
|
+
var zshScript = `
|
|
7181
|
+
#compdef servcraft
|
|
7182
|
+
|
|
7183
|
+
_servcraft() {
|
|
7184
|
+
local context state state_descr line
|
|
7185
|
+
typeset -A opt_args
|
|
7186
|
+
|
|
7187
|
+
_arguments -C \\
|
|
7188
|
+
'1: :_servcraft_commands' \\
|
|
7189
|
+
'*::arg:->args'
|
|
7190
|
+
|
|
7191
|
+
case $state in
|
|
7192
|
+
args)
|
|
7193
|
+
case $line[1] in
|
|
7194
|
+
generate|g)
|
|
7195
|
+
_servcraft_generate
|
|
7196
|
+
;;
|
|
7197
|
+
add|remove|rm|update)
|
|
7198
|
+
_servcraft_modules
|
|
7199
|
+
;;
|
|
7200
|
+
completion)
|
|
7201
|
+
_arguments '1: :(bash zsh)'
|
|
7202
|
+
;;
|
|
7203
|
+
esac
|
|
7204
|
+
;;
|
|
7205
|
+
esac
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
_servcraft_commands() {
|
|
7209
|
+
local commands
|
|
7210
|
+
commands=(
|
|
7211
|
+
'init:Initialize a new ServCraft project'
|
|
7212
|
+
'add:Add a pre-built module to your project'
|
|
7213
|
+
'generate:Generate code files (controller, service, etc.)'
|
|
7214
|
+
'list:List available and installed modules'
|
|
7215
|
+
'remove:Remove an installed module'
|
|
7216
|
+
'doctor:Diagnose project configuration'
|
|
7217
|
+
'update:Update installed modules'
|
|
7218
|
+
'completion:Generate shell completion scripts'
|
|
7219
|
+
'docs:Open documentation'
|
|
7220
|
+
'--version:Show version'
|
|
7221
|
+
'--help:Show help'
|
|
7222
|
+
)
|
|
7223
|
+
_describe 'command' commands
|
|
7224
|
+
}
|
|
7225
|
+
|
|
7226
|
+
_servcraft_generate() {
|
|
7227
|
+
local subcommands
|
|
7228
|
+
subcommands=(
|
|
7229
|
+
'module:Generate a complete module (controller + service + routes)'
|
|
7230
|
+
'controller:Generate a controller'
|
|
7231
|
+
'service:Generate a service'
|
|
7232
|
+
'repository:Generate a repository'
|
|
7233
|
+
'types:Generate TypeScript types'
|
|
7234
|
+
'schema:Generate validation schema'
|
|
7235
|
+
'routes:Generate routes file'
|
|
7236
|
+
'm:Alias for module'
|
|
7237
|
+
'c:Alias for controller'
|
|
7238
|
+
's:Alias for service'
|
|
7239
|
+
'r:Alias for repository'
|
|
7240
|
+
't:Alias for types'
|
|
7241
|
+
)
|
|
7242
|
+
_describe 'subcommand' subcommands
|
|
7243
|
+
}
|
|
7244
|
+
|
|
7245
|
+
_servcraft_modules() {
|
|
7246
|
+
local modules
|
|
7247
|
+
modules=(
|
|
7248
|
+
'auth:Authentication & Authorization'
|
|
7249
|
+
'cache:Redis caching'
|
|
7250
|
+
'rate-limit:Rate limiting'
|
|
7251
|
+
'notification:Email/SMS notifications'
|
|
7252
|
+
'payment:Payment integration'
|
|
7253
|
+
'oauth:OAuth providers'
|
|
7254
|
+
'mfa:Multi-factor authentication'
|
|
7255
|
+
'queue:Background jobs'
|
|
7256
|
+
'websocket:WebSocket support'
|
|
7257
|
+
'upload:File upload handling'
|
|
7258
|
+
)
|
|
7259
|
+
_describe 'module' modules
|
|
7260
|
+
}
|
|
7261
|
+
|
|
7262
|
+
_servcraft "$@"
|
|
7263
|
+
`;
|
|
7264
|
+
var completionCommand = new import_commander10.Command("completion").description("Generate shell completion scripts").argument("<shell>", "Shell type (bash or zsh)").action((shell) => {
|
|
7265
|
+
const shellLower = shell.toLowerCase();
|
|
7266
|
+
if (shellLower === "bash") {
|
|
7267
|
+
console.log(bashScript);
|
|
7268
|
+
} else if (shellLower === "zsh") {
|
|
7269
|
+
console.log(zshScript);
|
|
7270
|
+
} else {
|
|
7271
|
+
console.error(`Unsupported shell: ${shell}`);
|
|
7272
|
+
console.error("Supported shells: bash, zsh");
|
|
7273
|
+
process.exit(1);
|
|
7274
|
+
}
|
|
6665
7275
|
});
|
|
6666
7276
|
|
|
6667
7277
|
// src/cli/index.ts
|
|
6668
|
-
var program = new
|
|
7278
|
+
var program = new import_commander11.Command();
|
|
6669
7279
|
program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
|
|
6670
7280
|
program.addCommand(initCommand);
|
|
6671
7281
|
program.addCommand(generateCommand);
|
|
@@ -6675,5 +7285,7 @@ program.addCommand(docsCommand);
|
|
|
6675
7285
|
program.addCommand(listCommand);
|
|
6676
7286
|
program.addCommand(removeCommand);
|
|
6677
7287
|
program.addCommand(doctorCommand);
|
|
7288
|
+
program.addCommand(updateCommand);
|
|
7289
|
+
program.addCommand(completionCommand);
|
|
6678
7290
|
program.parse();
|
|
6679
7291
|
//# sourceMappingURL=index.cjs.map
|