servcraft 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/ROADMAP.md +15 -8
- package/dist/cli/index.cjs +900 -174
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +919 -172
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/add-module.ts +36 -7
- package/src/cli/commands/doctor.ts +8 -0
- package/src/cli/commands/generate.ts +43 -1
- package/src/cli/commands/init.ts +29 -10
- package/src/cli/commands/list.ts +274 -0
- package/src/cli/commands/remove.ts +102 -0
- package/src/cli/index.ts +12 -0
- package/src/cli/utils/dry-run.ts +155 -0
- package/src/cli/utils/error-handler.ts +184 -0
- package/src/cli/utils/helpers.ts +13 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,21 +1,316 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
7
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
8
|
+
}) : x)(function(x) {
|
|
9
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
10
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
11
|
+
});
|
|
12
|
+
var __esm = (fn, res) => function __init() {
|
|
13
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
14
|
+
};
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
30
|
+
import path from "path";
|
|
31
|
+
import { fileURLToPath } from "url";
|
|
32
|
+
var init_esm_shims = __esm({
|
|
33
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
34
|
+
"use strict";
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// src/cli/utils/error-handler.ts
|
|
39
|
+
var error_handler_exports = {};
|
|
40
|
+
__export(error_handler_exports, {
|
|
41
|
+
ErrorTypes: () => ErrorTypes,
|
|
42
|
+
ServCraftError: () => ServCraftError,
|
|
43
|
+
displayError: () => displayError,
|
|
44
|
+
handleSystemError: () => handleSystemError,
|
|
45
|
+
validateProject: () => validateProject
|
|
46
|
+
});
|
|
47
|
+
import chalk4 from "chalk";
|
|
48
|
+
function displayError(error2) {
|
|
49
|
+
console.error("\n" + chalk4.red.bold("\u2717 Error: ") + chalk4.red(error2.message));
|
|
50
|
+
if (error2 instanceof ServCraftError) {
|
|
51
|
+
if (error2.suggestions.length > 0) {
|
|
52
|
+
console.log("\n" + chalk4.yellow.bold("\u{1F4A1} Suggestions:"));
|
|
53
|
+
error2.suggestions.forEach((suggestion) => {
|
|
54
|
+
console.log(chalk4.yellow(" \u2022 ") + suggestion);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (error2.docsLink) {
|
|
58
|
+
console.log("\n" + chalk4.blue.bold("\u{1F4DA} Documentation: ") + chalk4.blue.underline(error2.docsLink));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
}
|
|
63
|
+
function handleSystemError(err) {
|
|
64
|
+
switch (err.code) {
|
|
65
|
+
case "ENOENT":
|
|
66
|
+
return new ServCraftError(
|
|
67
|
+
`File or directory not found: ${err.path}`,
|
|
68
|
+
[`Check if the path exists`, `Create the directory first`]
|
|
69
|
+
);
|
|
70
|
+
case "EACCES":
|
|
71
|
+
case "EPERM":
|
|
72
|
+
return new ServCraftError(
|
|
73
|
+
`Permission denied: ${err.path}`,
|
|
74
|
+
[
|
|
75
|
+
`Check file permissions`,
|
|
76
|
+
`Try running with elevated privileges (not recommended)`,
|
|
77
|
+
`Change ownership of the directory`
|
|
78
|
+
]
|
|
79
|
+
);
|
|
80
|
+
case "EEXIST":
|
|
81
|
+
return new ServCraftError(
|
|
82
|
+
`File or directory already exists: ${err.path}`,
|
|
83
|
+
[`Use a different name`, `Remove the existing file first`, `Use ${chalk4.cyan("--force")} to overwrite`]
|
|
84
|
+
);
|
|
85
|
+
case "ENOTDIR":
|
|
86
|
+
return new ServCraftError(
|
|
87
|
+
`Not a directory: ${err.path}`,
|
|
88
|
+
[`Check the path`, `A file exists where a directory is expected`]
|
|
89
|
+
);
|
|
90
|
+
case "EISDIR":
|
|
91
|
+
return new ServCraftError(
|
|
92
|
+
`Is a directory: ${err.path}`,
|
|
93
|
+
[`Cannot perform this operation on a directory`, `Did you mean to target a file?`]
|
|
94
|
+
);
|
|
95
|
+
default:
|
|
96
|
+
return new ServCraftError(
|
|
97
|
+
err.message,
|
|
98
|
+
[`Check system error code: ${err.code}`, `Review the error details above`]
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function validateProject() {
|
|
103
|
+
try {
|
|
104
|
+
const fs10 = __require("fs");
|
|
105
|
+
const path12 = __require("path");
|
|
106
|
+
if (!fs10.existsSync("package.json")) {
|
|
107
|
+
return ErrorTypes.NOT_IN_PROJECT();
|
|
108
|
+
}
|
|
109
|
+
const packageJson = JSON.parse(fs10.readFileSync("package.json", "utf-8"));
|
|
110
|
+
if (!packageJson.dependencies?.fastify) {
|
|
111
|
+
return new ServCraftError(
|
|
112
|
+
"This does not appear to be a ServCraft project",
|
|
113
|
+
[
|
|
114
|
+
`ServCraft projects require Fastify`,
|
|
115
|
+
`Run ${chalk4.cyan("servcraft init")} to create a new project`
|
|
116
|
+
]
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
return new ServCraftError(
|
|
122
|
+
"Failed to validate project",
|
|
123
|
+
[`Ensure you are in the project root directory`, `Check if ${chalk4.yellow("package.json")} is valid`]
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
var ServCraftError, ErrorTypes;
|
|
128
|
+
var init_error_handler = __esm({
|
|
129
|
+
"src/cli/utils/error-handler.ts"() {
|
|
130
|
+
"use strict";
|
|
131
|
+
init_esm_shims();
|
|
132
|
+
ServCraftError = class extends Error {
|
|
133
|
+
suggestions;
|
|
134
|
+
docsLink;
|
|
135
|
+
constructor(message, suggestions = [], docsLink) {
|
|
136
|
+
super(message);
|
|
137
|
+
this.name = "ServCraftError";
|
|
138
|
+
this.suggestions = suggestions;
|
|
139
|
+
this.docsLink = docsLink;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
ErrorTypes = {
|
|
143
|
+
MODULE_NOT_FOUND: (moduleName) => new ServCraftError(
|
|
144
|
+
`Module "${moduleName}" not found`,
|
|
145
|
+
[
|
|
146
|
+
`Run ${chalk4.cyan("servcraft list")} to see available modules`,
|
|
147
|
+
`Check the spelling of the module name`,
|
|
148
|
+
`Visit ${chalk4.blue("https://github.com/Le-Sourcier/servcraft#modules")} for module list`
|
|
149
|
+
],
|
|
150
|
+
"https://github.com/Le-Sourcier/servcraft#add-pre-built-modules"
|
|
151
|
+
),
|
|
152
|
+
MODULE_ALREADY_EXISTS: (moduleName) => new ServCraftError(
|
|
153
|
+
`Module "${moduleName}" already exists`,
|
|
154
|
+
[
|
|
155
|
+
`Use ${chalk4.cyan("servcraft add " + moduleName + " --force")} to overwrite`,
|
|
156
|
+
`Use ${chalk4.cyan("servcraft add " + moduleName + " --update")} to update`,
|
|
157
|
+
`Use ${chalk4.cyan("servcraft add " + moduleName + " --skip-existing")} to skip`
|
|
158
|
+
]
|
|
159
|
+
),
|
|
160
|
+
NOT_IN_PROJECT: () => new ServCraftError(
|
|
161
|
+
"Not in a ServCraft project directory",
|
|
162
|
+
[
|
|
163
|
+
`Run ${chalk4.cyan("servcraft init")} to create a new project`,
|
|
164
|
+
`Navigate to your ServCraft project directory`,
|
|
165
|
+
`Check if ${chalk4.yellow("package.json")} exists`
|
|
166
|
+
],
|
|
167
|
+
"https://github.com/Le-Sourcier/servcraft#initialize-project"
|
|
168
|
+
),
|
|
169
|
+
FILE_ALREADY_EXISTS: (fileName) => new ServCraftError(
|
|
170
|
+
`File "${fileName}" already exists`,
|
|
171
|
+
[
|
|
172
|
+
`Use ${chalk4.cyan("--force")} flag to overwrite`,
|
|
173
|
+
`Choose a different name`,
|
|
174
|
+
`Delete the existing file first`
|
|
175
|
+
]
|
|
176
|
+
),
|
|
177
|
+
INVALID_DATABASE: (database) => new ServCraftError(
|
|
178
|
+
`Invalid database type: "${database}"`,
|
|
179
|
+
[
|
|
180
|
+
`Valid options: ${chalk4.cyan("postgresql, mysql, sqlite, mongodb, none")}`,
|
|
181
|
+
`Use ${chalk4.cyan("servcraft init --db postgresql")} for PostgreSQL`
|
|
182
|
+
]
|
|
183
|
+
),
|
|
184
|
+
INVALID_VALIDATOR: (validator) => new ServCraftError(
|
|
185
|
+
`Invalid validator type: "${validator}"`,
|
|
186
|
+
[`Valid options: ${chalk4.cyan("zod, joi, yup")}`, `Default is ${chalk4.cyan("zod")}`]
|
|
187
|
+
),
|
|
188
|
+
MISSING_DEPENDENCY: (dependency, command) => new ServCraftError(
|
|
189
|
+
`Missing dependency: "${dependency}"`,
|
|
190
|
+
[`Run ${chalk4.cyan(command)} to install`, `Check your ${chalk4.yellow("package.json")}`]
|
|
191
|
+
),
|
|
192
|
+
INVALID_FIELD_FORMAT: (field) => new ServCraftError(
|
|
193
|
+
`Invalid field format: "${field}"`,
|
|
194
|
+
[
|
|
195
|
+
`Expected format: ${chalk4.cyan("name:type")}`,
|
|
196
|
+
`Example: ${chalk4.cyan("name:string age:number isActive:boolean")}`,
|
|
197
|
+
`Supported types: string, number, boolean, date`
|
|
198
|
+
]
|
|
199
|
+
),
|
|
200
|
+
GIT_NOT_INITIALIZED: () => new ServCraftError(
|
|
201
|
+
"Git repository not initialized",
|
|
202
|
+
[
|
|
203
|
+
`Run ${chalk4.cyan("git init")} to initialize git`,
|
|
204
|
+
`This is required for some ServCraft features`
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
});
|
|
2
210
|
|
|
3
211
|
// src/cli/index.ts
|
|
4
|
-
|
|
212
|
+
init_esm_shims();
|
|
213
|
+
import { Command as Command9 } from "commander";
|
|
5
214
|
|
|
6
215
|
// src/cli/commands/init.ts
|
|
216
|
+
init_esm_shims();
|
|
7
217
|
import { Command } from "commander";
|
|
8
|
-
import
|
|
218
|
+
import path4 from "path";
|
|
9
219
|
import fs2 from "fs/promises";
|
|
10
220
|
import ora from "ora";
|
|
11
221
|
import inquirer from "inquirer";
|
|
12
|
-
import
|
|
222
|
+
import chalk3 from "chalk";
|
|
13
223
|
import { execSync } from "child_process";
|
|
14
224
|
|
|
15
225
|
// src/cli/utils/helpers.ts
|
|
226
|
+
init_esm_shims();
|
|
16
227
|
import fs from "fs/promises";
|
|
17
|
-
import
|
|
228
|
+
import path3 from "path";
|
|
229
|
+
import chalk2 from "chalk";
|
|
230
|
+
|
|
231
|
+
// src/cli/utils/dry-run.ts
|
|
232
|
+
init_esm_shims();
|
|
18
233
|
import chalk from "chalk";
|
|
234
|
+
import path2 from "path";
|
|
235
|
+
var DryRunManager = class _DryRunManager {
|
|
236
|
+
static instance;
|
|
237
|
+
enabled = false;
|
|
238
|
+
operations = [];
|
|
239
|
+
constructor() {
|
|
240
|
+
}
|
|
241
|
+
static getInstance() {
|
|
242
|
+
if (!_DryRunManager.instance) {
|
|
243
|
+
_DryRunManager.instance = new _DryRunManager();
|
|
244
|
+
}
|
|
245
|
+
return _DryRunManager.instance;
|
|
246
|
+
}
|
|
247
|
+
enable() {
|
|
248
|
+
this.enabled = true;
|
|
249
|
+
this.operations = [];
|
|
250
|
+
}
|
|
251
|
+
disable() {
|
|
252
|
+
this.enabled = false;
|
|
253
|
+
this.operations = [];
|
|
254
|
+
}
|
|
255
|
+
isEnabled() {
|
|
256
|
+
return this.enabled;
|
|
257
|
+
}
|
|
258
|
+
addOperation(operation) {
|
|
259
|
+
if (this.enabled) {
|
|
260
|
+
this.operations.push(operation);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
getOperations() {
|
|
264
|
+
return [...this.operations];
|
|
265
|
+
}
|
|
266
|
+
printSummary() {
|
|
267
|
+
if (!this.enabled || this.operations.length === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
console.log(chalk.bold.yellow("\n\u{1F4CB} Dry Run - Preview of changes:\n"));
|
|
271
|
+
console.log(chalk.gray("No files will be written. Remove --dry-run to apply changes.\n"));
|
|
272
|
+
const createOps = this.operations.filter((op) => op.type === "create");
|
|
273
|
+
const modifyOps = this.operations.filter((op) => op.type === "modify");
|
|
274
|
+
const deleteOps = this.operations.filter((op) => op.type === "delete");
|
|
275
|
+
if (createOps.length > 0) {
|
|
276
|
+
console.log(chalk.green.bold(`
|
|
277
|
+
\u2713 Files to be created (${createOps.length}):`));
|
|
278
|
+
createOps.forEach((op) => {
|
|
279
|
+
const size = op.content ? `${op.content.length} bytes` : "unknown size";
|
|
280
|
+
console.log(` ${chalk.green("+")} ${chalk.cyan(op.path)} ${chalk.gray(`(${size})`)}`);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (modifyOps.length > 0) {
|
|
284
|
+
console.log(chalk.yellow.bold(`
|
|
285
|
+
~ Files to be modified (${modifyOps.length}):`));
|
|
286
|
+
modifyOps.forEach((op) => {
|
|
287
|
+
console.log(` ${chalk.yellow("~")} ${chalk.cyan(op.path)}`);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (deleteOps.length > 0) {
|
|
291
|
+
console.log(chalk.red.bold(`
|
|
292
|
+
- Files to be deleted (${deleteOps.length}):`));
|
|
293
|
+
deleteOps.forEach((op) => {
|
|
294
|
+
console.log(` ${chalk.red("-")} ${chalk.cyan(op.path)}`);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
console.log(chalk.gray("\n" + "\u2500".repeat(60)));
|
|
298
|
+
console.log(
|
|
299
|
+
chalk.bold(` Total operations: ${this.operations.length}`) + chalk.gray(
|
|
300
|
+
` (${createOps.length} create, ${modifyOps.length} modify, ${deleteOps.length} delete)`
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
console.log(chalk.gray("\u2500".repeat(60)));
|
|
304
|
+
console.log(chalk.yellow("\n\u26A0 This was a dry run. No files were created or modified."));
|
|
305
|
+
console.log(chalk.gray(" Remove --dry-run to apply these changes.\n"));
|
|
306
|
+
}
|
|
307
|
+
// Helper to format file path relative to cwd
|
|
308
|
+
relativePath(filePath) {
|
|
309
|
+
return path2.relative(process.cwd(), filePath);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/cli/utils/helpers.ts
|
|
19
314
|
function toPascalCase(str) {
|
|
20
315
|
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
21
316
|
}
|
|
@@ -47,39 +342,54 @@ async function ensureDir(dirPath) {
|
|
|
47
342
|
await fs.mkdir(dirPath, { recursive: true });
|
|
48
343
|
}
|
|
49
344
|
async function writeFile(filePath, content) {
|
|
50
|
-
|
|
345
|
+
const dryRun = DryRunManager.getInstance();
|
|
346
|
+
if (dryRun.isEnabled()) {
|
|
347
|
+
dryRun.addOperation({
|
|
348
|
+
type: "create",
|
|
349
|
+
path: dryRun.relativePath(filePath),
|
|
350
|
+
content,
|
|
351
|
+
size: content.length
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
await ensureDir(path3.dirname(filePath));
|
|
51
356
|
await fs.writeFile(filePath, content, "utf-8");
|
|
52
357
|
}
|
|
53
358
|
function success(message) {
|
|
54
|
-
console.log(
|
|
359
|
+
console.log(chalk2.green("\u2713"), message);
|
|
55
360
|
}
|
|
56
361
|
function error(message) {
|
|
57
|
-
console.error(
|
|
362
|
+
console.error(chalk2.red("\u2717"), message);
|
|
58
363
|
}
|
|
59
364
|
function warn(message) {
|
|
60
|
-
console.log(
|
|
365
|
+
console.log(chalk2.yellow("\u26A0"), message);
|
|
61
366
|
}
|
|
62
367
|
function info(message) {
|
|
63
|
-
console.log(
|
|
368
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
64
369
|
}
|
|
65
370
|
function getProjectRoot() {
|
|
66
371
|
return process.cwd();
|
|
67
372
|
}
|
|
68
373
|
function getSourceDir() {
|
|
69
|
-
return
|
|
374
|
+
return path3.join(getProjectRoot(), "src");
|
|
70
375
|
}
|
|
71
376
|
function getModulesDir() {
|
|
72
|
-
return
|
|
377
|
+
return path3.join(getSourceDir(), "modules");
|
|
73
378
|
}
|
|
74
379
|
|
|
75
380
|
// src/cli/commands/init.ts
|
|
76
|
-
var initCommand = new Command("init").alias("new").description("Initialize a new Servcraft project").argument("[name]", "Project name").option("-y, --yes", "Skip prompts and use defaults").option("--ts, --typescript", "Use TypeScript (default)").option("--js, --javascript", "Use JavaScript").option("--esm", "Use ES Modules (import/export) - default").option("--cjs, --commonjs", "Use CommonJS (require/module.exports)").option("--db <database>", "Database type (postgresql, mysql, sqlite, mongodb, none)").action(
|
|
381
|
+
var initCommand = new Command("init").alias("new").description("Initialize a new Servcraft project").argument("[name]", "Project name").option("-y, --yes", "Skip prompts and use defaults").option("--ts, --typescript", "Use TypeScript (default)").option("--js, --javascript", "Use JavaScript").option("--esm", "Use ES Modules (import/export) - default").option("--cjs, --commonjs", "Use CommonJS (require/module.exports)").option("--db <database>", "Database type (postgresql, mysql, sqlite, mongodb, none)").option("--dry-run", "Preview changes without writing files").action(
|
|
77
382
|
async (name, cmdOptions) => {
|
|
383
|
+
const dryRun = DryRunManager.getInstance();
|
|
384
|
+
if (cmdOptions?.dryRun) {
|
|
385
|
+
dryRun.enable();
|
|
386
|
+
console.log(chalk3.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
|
|
387
|
+
}
|
|
78
388
|
console.log(
|
|
79
|
-
|
|
389
|
+
chalk3.blue(`
|
|
80
390
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
81
391
|
\u2551 \u2551
|
|
82
|
-
\u2551 ${
|
|
392
|
+
\u2551 ${chalk3.bold("\u{1F680} Servcraft Project Generator")} \u2551
|
|
83
393
|
\u2551 \u2551
|
|
84
394
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
85
395
|
`)
|
|
@@ -174,7 +484,7 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
|
|
|
174
484
|
orm: db === "mongodb" ? "mongoose" : db === "none" ? "none" : "prisma"
|
|
175
485
|
};
|
|
176
486
|
}
|
|
177
|
-
const projectDir =
|
|
487
|
+
const projectDir = path4.resolve(process.cwd(), options.name);
|
|
178
488
|
const spinner = ora("Creating project...").start();
|
|
179
489
|
try {
|
|
180
490
|
try {
|
|
@@ -188,21 +498,21 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
|
|
|
188
498
|
spinner.text = "Generating project files...";
|
|
189
499
|
const packageJson = generatePackageJson(options);
|
|
190
500
|
await writeFile(
|
|
191
|
-
|
|
501
|
+
path4.join(projectDir, "package.json"),
|
|
192
502
|
JSON.stringify(packageJson, null, 2)
|
|
193
503
|
);
|
|
194
504
|
if (options.language === "typescript") {
|
|
195
|
-
await writeFile(
|
|
196
|
-
await writeFile(
|
|
505
|
+
await writeFile(path4.join(projectDir, "tsconfig.json"), generateTsConfig(options));
|
|
506
|
+
await writeFile(path4.join(projectDir, "tsup.config.ts"), generateTsupConfig(options));
|
|
197
507
|
} else {
|
|
198
|
-
await writeFile(
|
|
508
|
+
await writeFile(path4.join(projectDir, "jsconfig.json"), generateJsConfig(options));
|
|
199
509
|
}
|
|
200
|
-
await writeFile(
|
|
201
|
-
await writeFile(
|
|
202
|
-
await writeFile(
|
|
203
|
-
await writeFile(
|
|
510
|
+
await writeFile(path4.join(projectDir, ".env.example"), generateEnvExample(options));
|
|
511
|
+
await writeFile(path4.join(projectDir, ".env"), generateEnvExample(options));
|
|
512
|
+
await writeFile(path4.join(projectDir, ".gitignore"), generateGitignore());
|
|
513
|
+
await writeFile(path4.join(projectDir, "Dockerfile"), generateDockerfile(options));
|
|
204
514
|
await writeFile(
|
|
205
|
-
|
|
515
|
+
path4.join(projectDir, "docker-compose.yml"),
|
|
206
516
|
generateDockerCompose(options)
|
|
207
517
|
);
|
|
208
518
|
const ext = options.language === "typescript" ? "ts" : options.moduleSystem === "esm" ? "js" : "cjs";
|
|
@@ -223,59 +533,63 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
|
|
|
223
533
|
dirs.push("src/database/models");
|
|
224
534
|
}
|
|
225
535
|
for (const dir of dirs) {
|
|
226
|
-
await ensureDir(
|
|
536
|
+
await ensureDir(path4.join(projectDir, dir));
|
|
227
537
|
}
|
|
228
|
-
await writeFile(
|
|
538
|
+
await writeFile(path4.join(projectDir, `src/index.${ext}`), generateEntryFile(options));
|
|
229
539
|
await writeFile(
|
|
230
|
-
|
|
540
|
+
path4.join(projectDir, `src/core/server.${ext}`),
|
|
231
541
|
generateServerFile(options)
|
|
232
542
|
);
|
|
233
543
|
await writeFile(
|
|
234
|
-
|
|
544
|
+
path4.join(projectDir, `src/core/logger.${ext}`),
|
|
235
545
|
generateLoggerFile(options)
|
|
236
546
|
);
|
|
237
547
|
await writeFile(
|
|
238
|
-
|
|
548
|
+
path4.join(projectDir, `src/config/index.${ext}`),
|
|
239
549
|
generateConfigFile(options)
|
|
240
550
|
);
|
|
241
551
|
await writeFile(
|
|
242
|
-
|
|
552
|
+
path4.join(projectDir, `src/middleware/index.${ext}`),
|
|
243
553
|
generateMiddlewareFile(options)
|
|
244
554
|
);
|
|
245
555
|
await writeFile(
|
|
246
|
-
|
|
556
|
+
path4.join(projectDir, `src/utils/index.${ext}`),
|
|
247
557
|
generateUtilsFile(options)
|
|
248
558
|
);
|
|
249
559
|
await writeFile(
|
|
250
|
-
|
|
560
|
+
path4.join(projectDir, `src/types/index.${ext}`),
|
|
251
561
|
generateTypesFile(options)
|
|
252
562
|
);
|
|
253
563
|
if (options.orm === "prisma") {
|
|
254
564
|
await writeFile(
|
|
255
|
-
|
|
565
|
+
path4.join(projectDir, "prisma/schema.prisma"),
|
|
256
566
|
generatePrismaSchema(options)
|
|
257
567
|
);
|
|
258
568
|
} else if (options.orm === "mongoose") {
|
|
259
569
|
await writeFile(
|
|
260
|
-
|
|
570
|
+
path4.join(projectDir, `src/database/connection.${ext}`),
|
|
261
571
|
generateMongooseConnection(options)
|
|
262
572
|
);
|
|
263
573
|
await writeFile(
|
|
264
|
-
|
|
574
|
+
path4.join(projectDir, `src/database/models/user.model.${ext}`),
|
|
265
575
|
generateMongooseUserModel(options)
|
|
266
576
|
);
|
|
267
577
|
}
|
|
268
578
|
spinner.succeed("Project files generated!");
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
579
|
+
if (!cmdOptions?.dryRun) {
|
|
580
|
+
const installSpinner = ora("Installing dependencies...").start();
|
|
581
|
+
try {
|
|
582
|
+
execSync("npm install", { cwd: projectDir, stdio: "pipe" });
|
|
583
|
+
installSpinner.succeed("Dependencies installed!");
|
|
584
|
+
} catch {
|
|
585
|
+
installSpinner.warn("Failed to install dependencies automatically");
|
|
586
|
+
warn(' Run "npm install" manually in the project directory');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (!cmdOptions?.dryRun) {
|
|
590
|
+
console.log("\n" + chalk3.green("\u2728 Project created successfully!"));
|
|
276
591
|
}
|
|
277
|
-
console.log("\n" +
|
|
278
|
-
console.log("\n" + chalk2.bold("\u{1F4C1} Project structure:"));
|
|
592
|
+
console.log("\n" + chalk3.bold("\u{1F4C1} Project structure:"));
|
|
279
593
|
console.log(`
|
|
280
594
|
${options.name}/
|
|
281
595
|
\u251C\u2500\u2500 src/
|
|
@@ -290,19 +604,22 @@ var initCommand = new Command("init").alias("new").description("Initialize a new
|
|
|
290
604
|
\u251C\u2500\u2500 docker-compose.yml
|
|
291
605
|
\u2514\u2500\u2500 package.json
|
|
292
606
|
`);
|
|
293
|
-
console.log(
|
|
607
|
+
console.log(chalk3.bold("\u{1F680} Get started:"));
|
|
294
608
|
console.log(`
|
|
295
|
-
${
|
|
296
|
-
${options.database !== "none" ?
|
|
297
|
-
${
|
|
609
|
+
${chalk3.cyan(`cd ${options.name}`)}
|
|
610
|
+
${options.database !== "none" ? chalk3.cyan("npm run db:push # Setup database") : ""}
|
|
611
|
+
${chalk3.cyan("npm run dev # Start development server")}
|
|
298
612
|
`);
|
|
299
|
-
console.log(
|
|
613
|
+
console.log(chalk3.bold("\u{1F4DA} Available commands:"));
|
|
300
614
|
console.log(`
|
|
301
|
-
${
|
|
302
|
-
${
|
|
303
|
-
${
|
|
304
|
-
${
|
|
615
|
+
${chalk3.yellow("servcraft generate module <name>")} Generate a new module
|
|
616
|
+
${chalk3.yellow("servcraft generate controller <name>")} Generate a controller
|
|
617
|
+
${chalk3.yellow("servcraft generate service <name>")} Generate a service
|
|
618
|
+
${chalk3.yellow("servcraft add auth")} Add authentication module
|
|
305
619
|
`);
|
|
620
|
+
if (cmdOptions?.dryRun) {
|
|
621
|
+
dryRun.printSummary();
|
|
622
|
+
}
|
|
306
623
|
} catch (err) {
|
|
307
624
|
spinner.fail("Failed to create project");
|
|
308
625
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -1093,12 +1410,15 @@ declare module 'fastify' {
|
|
|
1093
1410
|
}
|
|
1094
1411
|
|
|
1095
1412
|
// src/cli/commands/generate.ts
|
|
1413
|
+
init_esm_shims();
|
|
1096
1414
|
import { Command as Command2 } from "commander";
|
|
1097
|
-
import
|
|
1415
|
+
import path5 from "path";
|
|
1098
1416
|
import ora2 from "ora";
|
|
1099
1417
|
import inquirer2 from "inquirer";
|
|
1418
|
+
import chalk5 from "chalk";
|
|
1100
1419
|
|
|
1101
1420
|
// src/cli/utils/field-parser.ts
|
|
1421
|
+
init_esm_shims();
|
|
1102
1422
|
var tsTypeMap = {
|
|
1103
1423
|
string: "string",
|
|
1104
1424
|
number: "number",
|
|
@@ -1240,7 +1560,11 @@ function parseFields(fieldsStr) {
|
|
|
1240
1560
|
return fieldsStr.split(/\s+/).filter(Boolean).map(parseField);
|
|
1241
1561
|
}
|
|
1242
1562
|
|
|
1563
|
+
// src/cli/commands/generate.ts
|
|
1564
|
+
init_error_handler();
|
|
1565
|
+
|
|
1243
1566
|
// src/cli/templates/controller.ts
|
|
1567
|
+
init_esm_shims();
|
|
1244
1568
|
function controllerTemplate(name, pascalName, camelName) {
|
|
1245
1569
|
return `import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
1246
1570
|
import type { ${pascalName}Service } from './${name}.service.js';
|
|
@@ -1310,6 +1634,7 @@ export function create${pascalName}Controller(${camelName}Service: ${pascalName}
|
|
|
1310
1634
|
}
|
|
1311
1635
|
|
|
1312
1636
|
// src/cli/templates/service.ts
|
|
1637
|
+
init_esm_shims();
|
|
1313
1638
|
function serviceTemplate(name, pascalName, camelName) {
|
|
1314
1639
|
return `import type { PaginatedResult, PaginationParams } from '../../types/index.js';
|
|
1315
1640
|
import { NotFoundError, ConflictError } from '../../utils/errors.js';
|
|
@@ -1370,6 +1695,7 @@ export function create${pascalName}Service(repository?: ${pascalName}Repository)
|
|
|
1370
1695
|
}
|
|
1371
1696
|
|
|
1372
1697
|
// src/cli/templates/repository.ts
|
|
1698
|
+
init_esm_shims();
|
|
1373
1699
|
function repositoryTemplate(name, pascalName, camelName, pluralName) {
|
|
1374
1700
|
return `import { randomUUID } from 'crypto';
|
|
1375
1701
|
import type { PaginatedResult, PaginationParams } from '../../types/index.js';
|
|
@@ -1476,6 +1802,7 @@ export function create${pascalName}Repository(): ${pascalName}Repository {
|
|
|
1476
1802
|
}
|
|
1477
1803
|
|
|
1478
1804
|
// src/cli/templates/types.ts
|
|
1805
|
+
init_esm_shims();
|
|
1479
1806
|
function typesTemplate(name, pascalName) {
|
|
1480
1807
|
return `import type { BaseEntity } from '../../types/index.js';
|
|
1481
1808
|
|
|
@@ -1505,6 +1832,7 @@ export interface ${pascalName}Filters {
|
|
|
1505
1832
|
}
|
|
1506
1833
|
|
|
1507
1834
|
// src/cli/templates/schemas.ts
|
|
1835
|
+
init_esm_shims();
|
|
1508
1836
|
function schemasTemplate(name, pascalName, camelName) {
|
|
1509
1837
|
return `import { z } from 'zod';
|
|
1510
1838
|
|
|
@@ -1533,6 +1861,7 @@ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
|
|
|
1533
1861
|
}
|
|
1534
1862
|
|
|
1535
1863
|
// src/cli/templates/routes.ts
|
|
1864
|
+
init_esm_shims();
|
|
1536
1865
|
function routesTemplate(name, pascalName, camelName, pluralName) {
|
|
1537
1866
|
return `import type { FastifyInstance } from 'fastify';
|
|
1538
1867
|
import type { ${pascalName}Controller } from './${name}.controller.js';
|
|
@@ -1585,6 +1914,7 @@ export function register${pascalName}Routes(
|
|
|
1585
1914
|
}
|
|
1586
1915
|
|
|
1587
1916
|
// src/cli/templates/module-index.ts
|
|
1917
|
+
init_esm_shims();
|
|
1588
1918
|
function moduleIndexTemplate(name, pascalName, camelName) {
|
|
1589
1919
|
return `import type { FastifyInstance } from 'fastify';
|
|
1590
1920
|
import { logger } from '../../core/logger.js';
|
|
@@ -1620,6 +1950,7 @@ export * from './${name}.schemas.js';
|
|
|
1620
1950
|
}
|
|
1621
1951
|
|
|
1622
1952
|
// src/cli/templates/prisma-model.ts
|
|
1953
|
+
init_esm_shims();
|
|
1623
1954
|
function prismaModelTemplate(name, pascalName, tableName) {
|
|
1624
1955
|
return `
|
|
1625
1956
|
// Add this model to your prisma/schema.prisma file
|
|
@@ -1639,6 +1970,7 @@ model ${pascalName} {
|
|
|
1639
1970
|
}
|
|
1640
1971
|
|
|
1641
1972
|
// src/cli/templates/dynamic-types.ts
|
|
1973
|
+
init_esm_shims();
|
|
1642
1974
|
function dynamicTypesTemplate(name, pascalName, fields) {
|
|
1643
1975
|
const fieldLines = fields.map((field) => {
|
|
1644
1976
|
const tsType = tsTypeMap[field.type];
|
|
@@ -1683,6 +2015,7 @@ ${fields.filter((f) => ["string", "enum", "boolean"].includes(f.type)).map((f) =
|
|
|
1683
2015
|
}
|
|
1684
2016
|
|
|
1685
2017
|
// src/cli/templates/dynamic-schemas.ts
|
|
2018
|
+
init_esm_shims();
|
|
1686
2019
|
function dynamicSchemasTemplate(name, pascalName, camelName, fields, validator = "zod") {
|
|
1687
2020
|
switch (validator) {
|
|
1688
2021
|
case "joi":
|
|
@@ -1861,6 +2194,7 @@ function getJsType(field) {
|
|
|
1861
2194
|
}
|
|
1862
2195
|
|
|
1863
2196
|
// src/cli/templates/dynamic-prisma.ts
|
|
2197
|
+
init_esm_shims();
|
|
1864
2198
|
function dynamicPrismaTemplate(modelName, tableName, fields) {
|
|
1865
2199
|
const fieldLines = [];
|
|
1866
2200
|
for (const field of fields) {
|
|
@@ -1929,10 +2263,23 @@ ${indexLines.join("\n")}
|
|
|
1929
2263
|
}
|
|
1930
2264
|
|
|
1931
2265
|
// src/cli/commands/generate.ts
|
|
2266
|
+
function enableDryRunIfNeeded(options) {
|
|
2267
|
+
const dryRun = DryRunManager.getInstance();
|
|
2268
|
+
if (options.dryRun) {
|
|
2269
|
+
dryRun.enable();
|
|
2270
|
+
console.log(chalk5.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
function showDryRunSummary(options) {
|
|
2274
|
+
if (options.dryRun) {
|
|
2275
|
+
DryRunManager.getInstance().printSummary();
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
1932
2278
|
var generateCommand = new Command2("generate").alias("g").description("Generate resources (module, controller, service, etc.)");
|
|
1933
2279
|
generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
1934
2280
|
"Generate a complete module with controller, service, repository, types, schemas, and routes"
|
|
1935
|
-
).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").action(async (name, fieldsArgs, options) => {
|
|
2281
|
+
).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) => {
|
|
2282
|
+
enableDryRunIfNeeded(options);
|
|
1936
2283
|
let fields = [];
|
|
1937
2284
|
if (options.interactive) {
|
|
1938
2285
|
fields = await promptForFields();
|
|
@@ -1947,7 +2294,7 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
1947
2294
|
const pluralName = pluralize(kebabName);
|
|
1948
2295
|
const tableName = pluralize(kebabName.replace(/-/g, "_"));
|
|
1949
2296
|
const validatorType = options.validator || "zod";
|
|
1950
|
-
const moduleDir =
|
|
2297
|
+
const moduleDir = path5.join(getModulesDir(), kebabName);
|
|
1951
2298
|
if (await fileExists(moduleDir)) {
|
|
1952
2299
|
spinner.stop();
|
|
1953
2300
|
error(`Module "${kebabName}" already exists`);
|
|
@@ -1986,7 +2333,7 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
1986
2333
|
});
|
|
1987
2334
|
}
|
|
1988
2335
|
for (const file of files) {
|
|
1989
|
-
await writeFile(
|
|
2336
|
+
await writeFile(path5.join(moduleDir, file.name), file.content);
|
|
1990
2337
|
}
|
|
1991
2338
|
spinner.succeed(`Module "${pascalName}" generated successfully!`);
|
|
1992
2339
|
if (options.prisma || hasFields) {
|
|
@@ -2024,42 +2371,46 @@ generateCommand.command("module <name> [fields...]").alias("m").description(
|
|
|
2024
2371
|
info(` ${hasFields ? "3" : "4"}. Add the Prisma model to schema.prisma`);
|
|
2025
2372
|
info(` ${hasFields ? "4" : "5"}. Run: npm run db:migrate`);
|
|
2026
2373
|
}
|
|
2374
|
+
showDryRunSummary(options);
|
|
2027
2375
|
} catch (err) {
|
|
2028
2376
|
spinner.fail("Failed to generate module");
|
|
2029
2377
|
error(err instanceof Error ? err.message : String(err));
|
|
2030
2378
|
}
|
|
2031
2379
|
});
|
|
2032
|
-
generateCommand.command("controller <name>").alias("c").description("Generate a controller").option("-m, --module <module>", "Target module name").action(async (name, options) => {
|
|
2380
|
+
generateCommand.command("controller <name>").alias("c").description("Generate a controller").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
|
|
2381
|
+
enableDryRunIfNeeded(options);
|
|
2033
2382
|
const spinner = ora2("Generating controller...").start();
|
|
2034
2383
|
try {
|
|
2035
2384
|
const kebabName = toKebabCase(name);
|
|
2036
2385
|
const pascalName = toPascalCase(name);
|
|
2037
2386
|
const camelName = toCamelCase(name);
|
|
2038
2387
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2039
|
-
const moduleDir =
|
|
2040
|
-
const filePath =
|
|
2388
|
+
const moduleDir = path5.join(getModulesDir(), moduleName);
|
|
2389
|
+
const filePath = path5.join(moduleDir, `${kebabName}.controller.ts`);
|
|
2041
2390
|
if (await fileExists(filePath)) {
|
|
2042
2391
|
spinner.stop();
|
|
2043
|
-
|
|
2392
|
+
displayError(ErrorTypes.FILE_ALREADY_EXISTS(`${kebabName}.controller.ts`));
|
|
2044
2393
|
return;
|
|
2045
2394
|
}
|
|
2046
2395
|
await writeFile(filePath, controllerTemplate(kebabName, pascalName, camelName));
|
|
2047
2396
|
spinner.succeed(`Controller "${pascalName}Controller" generated!`);
|
|
2048
2397
|
success(` src/modules/${moduleName}/${kebabName}.controller.ts`);
|
|
2398
|
+
showDryRunSummary(options);
|
|
2049
2399
|
} catch (err) {
|
|
2050
2400
|
spinner.fail("Failed to generate controller");
|
|
2051
2401
|
error(err instanceof Error ? err.message : String(err));
|
|
2052
2402
|
}
|
|
2053
2403
|
});
|
|
2054
|
-
generateCommand.command("service <name>").alias("s").description("Generate a service").option("-m, --module <module>", "Target module name").action(async (name, options) => {
|
|
2404
|
+
generateCommand.command("service <name>").alias("s").description("Generate a service").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
|
|
2405
|
+
enableDryRunIfNeeded(options);
|
|
2055
2406
|
const spinner = ora2("Generating service...").start();
|
|
2056
2407
|
try {
|
|
2057
2408
|
const kebabName = toKebabCase(name);
|
|
2058
2409
|
const pascalName = toPascalCase(name);
|
|
2059
2410
|
const camelName = toCamelCase(name);
|
|
2060
2411
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2061
|
-
const moduleDir =
|
|
2062
|
-
const filePath =
|
|
2412
|
+
const moduleDir = path5.join(getModulesDir(), moduleName);
|
|
2413
|
+
const filePath = path5.join(moduleDir, `${kebabName}.service.ts`);
|
|
2063
2414
|
if (await fileExists(filePath)) {
|
|
2064
2415
|
spinner.stop();
|
|
2065
2416
|
error(`Service "${kebabName}" already exists`);
|
|
@@ -2068,12 +2419,14 @@ generateCommand.command("service <name>").alias("s").description("Generate a ser
|
|
|
2068
2419
|
await writeFile(filePath, serviceTemplate(kebabName, pascalName, camelName));
|
|
2069
2420
|
spinner.succeed(`Service "${pascalName}Service" generated!`);
|
|
2070
2421
|
success(` src/modules/${moduleName}/${kebabName}.service.ts`);
|
|
2422
|
+
showDryRunSummary(options);
|
|
2071
2423
|
} catch (err) {
|
|
2072
2424
|
spinner.fail("Failed to generate service");
|
|
2073
2425
|
error(err instanceof Error ? err.message : String(err));
|
|
2074
2426
|
}
|
|
2075
2427
|
});
|
|
2076
|
-
generateCommand.command("repository <name>").alias("r").description("Generate a repository").option("-m, --module <module>", "Target module name").action(async (name, options) => {
|
|
2428
|
+
generateCommand.command("repository <name>").alias("r").description("Generate a repository").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
|
|
2429
|
+
enableDryRunIfNeeded(options);
|
|
2077
2430
|
const spinner = ora2("Generating repository...").start();
|
|
2078
2431
|
try {
|
|
2079
2432
|
const kebabName = toKebabCase(name);
|
|
@@ -2081,8 +2434,8 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
|
|
|
2081
2434
|
const camelName = toCamelCase(name);
|
|
2082
2435
|
const pluralName = pluralize(kebabName);
|
|
2083
2436
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2084
|
-
const moduleDir =
|
|
2085
|
-
const filePath =
|
|
2437
|
+
const moduleDir = path5.join(getModulesDir(), moduleName);
|
|
2438
|
+
const filePath = path5.join(moduleDir, `${kebabName}.repository.ts`);
|
|
2086
2439
|
if (await fileExists(filePath)) {
|
|
2087
2440
|
spinner.stop();
|
|
2088
2441
|
error(`Repository "${kebabName}" already exists`);
|
|
@@ -2091,19 +2444,21 @@ generateCommand.command("repository <name>").alias("r").description("Generate a
|
|
|
2091
2444
|
await writeFile(filePath, repositoryTemplate(kebabName, pascalName, camelName, pluralName));
|
|
2092
2445
|
spinner.succeed(`Repository "${pascalName}Repository" generated!`);
|
|
2093
2446
|
success(` src/modules/${moduleName}/${kebabName}.repository.ts`);
|
|
2447
|
+
showDryRunSummary(options);
|
|
2094
2448
|
} catch (err) {
|
|
2095
2449
|
spinner.fail("Failed to generate repository");
|
|
2096
2450
|
error(err instanceof Error ? err.message : String(err));
|
|
2097
2451
|
}
|
|
2098
2452
|
});
|
|
2099
|
-
generateCommand.command("types <name>").alias("t").description("Generate types/interfaces").option("-m, --module <module>", "Target module name").action(async (name, options) => {
|
|
2453
|
+
generateCommand.command("types <name>").alias("t").description("Generate types/interfaces").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
|
|
2454
|
+
enableDryRunIfNeeded(options);
|
|
2100
2455
|
const spinner = ora2("Generating types...").start();
|
|
2101
2456
|
try {
|
|
2102
2457
|
const kebabName = toKebabCase(name);
|
|
2103
2458
|
const pascalName = toPascalCase(name);
|
|
2104
2459
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2105
|
-
const moduleDir =
|
|
2106
|
-
const filePath =
|
|
2460
|
+
const moduleDir = path5.join(getModulesDir(), moduleName);
|
|
2461
|
+
const filePath = path5.join(moduleDir, `${kebabName}.types.ts`);
|
|
2107
2462
|
if (await fileExists(filePath)) {
|
|
2108
2463
|
spinner.stop();
|
|
2109
2464
|
error(`Types file "${kebabName}.types.ts" already exists`);
|
|
@@ -2112,20 +2467,22 @@ generateCommand.command("types <name>").alias("t").description("Generate types/i
|
|
|
2112
2467
|
await writeFile(filePath, typesTemplate(kebabName, pascalName));
|
|
2113
2468
|
spinner.succeed(`Types for "${pascalName}" generated!`);
|
|
2114
2469
|
success(` src/modules/${moduleName}/${kebabName}.types.ts`);
|
|
2470
|
+
showDryRunSummary(options);
|
|
2115
2471
|
} catch (err) {
|
|
2116
2472
|
spinner.fail("Failed to generate types");
|
|
2117
2473
|
error(err instanceof Error ? err.message : String(err));
|
|
2118
2474
|
}
|
|
2119
2475
|
});
|
|
2120
|
-
generateCommand.command("schema <name>").alias("v").description("Generate validation schemas").option("-m, --module <module>", "Target module name").action(async (name, options) => {
|
|
2476
|
+
generateCommand.command("schema <name>").alias("v").description("Generate validation schemas").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
|
|
2477
|
+
enableDryRunIfNeeded(options);
|
|
2121
2478
|
const spinner = ora2("Generating schemas...").start();
|
|
2122
2479
|
try {
|
|
2123
2480
|
const kebabName = toKebabCase(name);
|
|
2124
2481
|
const pascalName = toPascalCase(name);
|
|
2125
2482
|
const camelName = toCamelCase(name);
|
|
2126
2483
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2127
|
-
const moduleDir =
|
|
2128
|
-
const filePath =
|
|
2484
|
+
const moduleDir = path5.join(getModulesDir(), moduleName);
|
|
2485
|
+
const filePath = path5.join(moduleDir, `${kebabName}.schemas.ts`);
|
|
2129
2486
|
if (await fileExists(filePath)) {
|
|
2130
2487
|
spinner.stop();
|
|
2131
2488
|
error(`Schemas file "${kebabName}.schemas.ts" already exists`);
|
|
@@ -2134,12 +2491,14 @@ generateCommand.command("schema <name>").alias("v").description("Generate valida
|
|
|
2134
2491
|
await writeFile(filePath, schemasTemplate(kebabName, pascalName, camelName));
|
|
2135
2492
|
spinner.succeed(`Schemas for "${pascalName}" generated!`);
|
|
2136
2493
|
success(` src/modules/${moduleName}/${kebabName}.schemas.ts`);
|
|
2494
|
+
showDryRunSummary(options);
|
|
2137
2495
|
} catch (err) {
|
|
2138
2496
|
spinner.fail("Failed to generate schemas");
|
|
2139
2497
|
error(err instanceof Error ? err.message : String(err));
|
|
2140
2498
|
}
|
|
2141
2499
|
});
|
|
2142
|
-
generateCommand.command("routes <name>").description("Generate routes").option("-m, --module <module>", "Target module name").action(async (name, options) => {
|
|
2500
|
+
generateCommand.command("routes <name>").description("Generate routes").option("-m, --module <module>", "Target module name").option("--dry-run", "Preview changes without writing files").action(async (name, options) => {
|
|
2501
|
+
enableDryRunIfNeeded(options);
|
|
2143
2502
|
const spinner = ora2("Generating routes...").start();
|
|
2144
2503
|
try {
|
|
2145
2504
|
const kebabName = toKebabCase(name);
|
|
@@ -2147,8 +2506,8 @@ generateCommand.command("routes <name>").description("Generate routes").option("
|
|
|
2147
2506
|
const camelName = toCamelCase(name);
|
|
2148
2507
|
const pluralName = pluralize(kebabName);
|
|
2149
2508
|
const moduleName = options.module ? toKebabCase(options.module) : kebabName;
|
|
2150
|
-
const moduleDir =
|
|
2151
|
-
const filePath =
|
|
2509
|
+
const moduleDir = path5.join(getModulesDir(), moduleName);
|
|
2510
|
+
const filePath = path5.join(moduleDir, `${kebabName}.routes.ts`);
|
|
2152
2511
|
if (await fileExists(filePath)) {
|
|
2153
2512
|
spinner.stop();
|
|
2154
2513
|
error(`Routes file "${kebabName}.routes.ts" already exists`);
|
|
@@ -2157,6 +2516,7 @@ generateCommand.command("routes <name>").description("Generate routes").option("
|
|
|
2157
2516
|
await writeFile(filePath, routesTemplate(kebabName, pascalName, camelName, pluralName));
|
|
2158
2517
|
spinner.succeed(`Routes for "${pascalName}" generated!`);
|
|
2159
2518
|
success(` src/modules/${moduleName}/${kebabName}.routes.ts`);
|
|
2519
|
+
showDryRunSummary(options);
|
|
2160
2520
|
} catch (err) {
|
|
2161
2521
|
spinner.fail("Failed to generate routes");
|
|
2162
2522
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -2234,22 +2594,24 @@ async function promptForFields() {
|
|
|
2234
2594
|
}
|
|
2235
2595
|
|
|
2236
2596
|
// src/cli/commands/add-module.ts
|
|
2597
|
+
init_esm_shims();
|
|
2237
2598
|
import { Command as Command3 } from "commander";
|
|
2238
|
-
import
|
|
2599
|
+
import path8 from "path";
|
|
2239
2600
|
import ora3 from "ora";
|
|
2240
|
-
import
|
|
2601
|
+
import chalk7 from "chalk";
|
|
2241
2602
|
import * as fs5 from "fs/promises";
|
|
2242
2603
|
|
|
2243
2604
|
// src/cli/utils/env-manager.ts
|
|
2605
|
+
init_esm_shims();
|
|
2244
2606
|
import * as fs3 from "fs/promises";
|
|
2245
|
-
import * as
|
|
2607
|
+
import * as path6 from "path";
|
|
2246
2608
|
import { existsSync } from "fs";
|
|
2247
2609
|
var EnvManager = class {
|
|
2248
2610
|
envPath;
|
|
2249
2611
|
envExamplePath;
|
|
2250
2612
|
constructor(projectRoot) {
|
|
2251
|
-
this.envPath =
|
|
2252
|
-
this.envExamplePath =
|
|
2613
|
+
this.envPath = path6.join(projectRoot, ".env");
|
|
2614
|
+
this.envExamplePath = path6.join(projectRoot, ".env.example");
|
|
2253
2615
|
}
|
|
2254
2616
|
/**
|
|
2255
2617
|
* Add environment variables to .env file
|
|
@@ -2899,16 +3261,17 @@ var EnvManager = class {
|
|
|
2899
3261
|
};
|
|
2900
3262
|
|
|
2901
3263
|
// src/cli/utils/template-manager.ts
|
|
3264
|
+
init_esm_shims();
|
|
2902
3265
|
import * as fs4 from "fs/promises";
|
|
2903
|
-
import * as
|
|
3266
|
+
import * as path7 from "path";
|
|
2904
3267
|
import { createHash } from "crypto";
|
|
2905
3268
|
import { existsSync as existsSync2 } from "fs";
|
|
2906
3269
|
var TemplateManager = class {
|
|
2907
3270
|
templatesDir;
|
|
2908
3271
|
manifestsDir;
|
|
2909
3272
|
constructor(projectRoot) {
|
|
2910
|
-
this.templatesDir =
|
|
2911
|
-
this.manifestsDir =
|
|
3273
|
+
this.templatesDir = path7.join(projectRoot, ".servcraft", "templates");
|
|
3274
|
+
this.manifestsDir = path7.join(projectRoot, ".servcraft", "manifests");
|
|
2912
3275
|
}
|
|
2913
3276
|
/**
|
|
2914
3277
|
* Initialize template system
|
|
@@ -2922,10 +3285,10 @@ var TemplateManager = class {
|
|
|
2922
3285
|
*/
|
|
2923
3286
|
async saveTemplate(moduleName, files) {
|
|
2924
3287
|
await this.initialize();
|
|
2925
|
-
const moduleTemplateDir =
|
|
3288
|
+
const moduleTemplateDir = path7.join(this.templatesDir, moduleName);
|
|
2926
3289
|
await fs4.mkdir(moduleTemplateDir, { recursive: true });
|
|
2927
3290
|
for (const [fileName, content] of Object.entries(files)) {
|
|
2928
|
-
const filePath =
|
|
3291
|
+
const filePath = path7.join(moduleTemplateDir, fileName);
|
|
2929
3292
|
await fs4.writeFile(filePath, content, "utf-8");
|
|
2930
3293
|
}
|
|
2931
3294
|
}
|
|
@@ -2934,7 +3297,7 @@ var TemplateManager = class {
|
|
|
2934
3297
|
*/
|
|
2935
3298
|
async getTemplate(moduleName, fileName) {
|
|
2936
3299
|
try {
|
|
2937
|
-
const filePath =
|
|
3300
|
+
const filePath = path7.join(this.templatesDir, moduleName, fileName);
|
|
2938
3301
|
return await fs4.readFile(filePath, "utf-8");
|
|
2939
3302
|
} catch {
|
|
2940
3303
|
return null;
|
|
@@ -2959,7 +3322,7 @@ var TemplateManager = class {
|
|
|
2959
3322
|
installedAt: /* @__PURE__ */ new Date(),
|
|
2960
3323
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2961
3324
|
};
|
|
2962
|
-
const manifestPath =
|
|
3325
|
+
const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
|
|
2963
3326
|
await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
2964
3327
|
}
|
|
2965
3328
|
/**
|
|
@@ -2967,7 +3330,7 @@ var TemplateManager = class {
|
|
|
2967
3330
|
*/
|
|
2968
3331
|
async getManifest(moduleName) {
|
|
2969
3332
|
try {
|
|
2970
|
-
const manifestPath =
|
|
3333
|
+
const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
|
|
2971
3334
|
const content = await fs4.readFile(manifestPath, "utf-8");
|
|
2972
3335
|
return JSON.parse(content);
|
|
2973
3336
|
} catch {
|
|
@@ -2996,7 +3359,7 @@ var TemplateManager = class {
|
|
|
2996
3359
|
}
|
|
2997
3360
|
const results = [];
|
|
2998
3361
|
for (const [fileName, fileInfo] of Object.entries(manifest.files)) {
|
|
2999
|
-
const filePath =
|
|
3362
|
+
const filePath = path7.join(moduleDir, fileName);
|
|
3000
3363
|
if (!existsSync2(filePath)) {
|
|
3001
3364
|
results.push({
|
|
3002
3365
|
fileName,
|
|
@@ -3022,7 +3385,7 @@ var TemplateManager = class {
|
|
|
3022
3385
|
*/
|
|
3023
3386
|
async createBackup(moduleName, moduleDir) {
|
|
3024
3387
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
3025
|
-
const backupDir =
|
|
3388
|
+
const backupDir = path7.join(path7.dirname(moduleDir), `${moduleName}.backup-${timestamp}`);
|
|
3026
3389
|
await this.copyDirectory(moduleDir, backupDir);
|
|
3027
3390
|
return backupDir;
|
|
3028
3391
|
}
|
|
@@ -3033,8 +3396,8 @@ var TemplateManager = class {
|
|
|
3033
3396
|
await fs4.mkdir(dest, { recursive: true });
|
|
3034
3397
|
const entries = await fs4.readdir(src, { withFileTypes: true });
|
|
3035
3398
|
for (const entry of entries) {
|
|
3036
|
-
const srcPath =
|
|
3037
|
-
const destPath =
|
|
3399
|
+
const srcPath = path7.join(src, entry.name);
|
|
3400
|
+
const destPath = path7.join(dest, entry.name);
|
|
3038
3401
|
if (entry.isDirectory()) {
|
|
3039
3402
|
await this.copyDirectory(srcPath, destPath);
|
|
3040
3403
|
} else {
|
|
@@ -3135,23 +3498,24 @@ var TemplateManager = class {
|
|
|
3135
3498
|
}
|
|
3136
3499
|
manifest.files = fileHashes;
|
|
3137
3500
|
manifest.updatedAt = /* @__PURE__ */ new Date();
|
|
3138
|
-
const manifestPath =
|
|
3501
|
+
const manifestPath = path7.join(this.manifestsDir, `${moduleName}.json`);
|
|
3139
3502
|
await fs4.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
3140
3503
|
}
|
|
3141
3504
|
};
|
|
3142
3505
|
|
|
3143
3506
|
// src/cli/utils/interactive-prompt.ts
|
|
3507
|
+
init_esm_shims();
|
|
3144
3508
|
import inquirer3 from "inquirer";
|
|
3145
|
-
import
|
|
3509
|
+
import chalk6 from "chalk";
|
|
3146
3510
|
var InteractivePrompt = class {
|
|
3147
3511
|
/**
|
|
3148
3512
|
* Ask what to do when module already exists
|
|
3149
3513
|
*/
|
|
3150
3514
|
static async askModuleExists(moduleName, hasModifications) {
|
|
3151
|
-
console.log(
|
|
3515
|
+
console.log(chalk6.yellow(`
|
|
3152
3516
|
\u26A0\uFE0F Module "${moduleName}" already exists`));
|
|
3153
3517
|
if (hasModifications) {
|
|
3154
|
-
console.log(
|
|
3518
|
+
console.log(chalk6.yellow(" Some files have been modified by you.\n"));
|
|
3155
3519
|
}
|
|
3156
3520
|
const { action } = await inquirer3.prompt([
|
|
3157
3521
|
{
|
|
@@ -3189,12 +3553,12 @@ var InteractivePrompt = class {
|
|
|
3189
3553
|
* Ask what to do with a specific file
|
|
3190
3554
|
*/
|
|
3191
3555
|
static async askFileAction(fileName, isModified, yourLines, newLines) {
|
|
3192
|
-
console.log(
|
|
3556
|
+
console.log(chalk6.cyan(`
|
|
3193
3557
|
\u{1F4C1} ${fileName}`));
|
|
3194
3558
|
console.log(
|
|
3195
|
-
|
|
3559
|
+
chalk6.gray(` Your version: ${yourLines} lines${isModified ? " (modified)" : ""}`)
|
|
3196
3560
|
);
|
|
3197
|
-
console.log(
|
|
3561
|
+
console.log(chalk6.gray(` New version: ${newLines} lines
|
|
3198
3562
|
`));
|
|
3199
3563
|
const { action } = await inquirer3.prompt([
|
|
3200
3564
|
{
|
|
@@ -3246,7 +3610,7 @@ var InteractivePrompt = class {
|
|
|
3246
3610
|
* Display diff and ask to continue
|
|
3247
3611
|
*/
|
|
3248
3612
|
static async showDiffAndAsk(diff) {
|
|
3249
|
-
console.log(
|
|
3613
|
+
console.log(chalk6.cyan("\n\u{1F4CA} Differences:\n"));
|
|
3250
3614
|
console.log(diff);
|
|
3251
3615
|
return await this.confirm("\nDo you want to proceed with this change?", true);
|
|
3252
3616
|
}
|
|
@@ -3254,41 +3618,41 @@ var InteractivePrompt = class {
|
|
|
3254
3618
|
* Display merge conflicts
|
|
3255
3619
|
*/
|
|
3256
3620
|
static displayConflicts(conflicts) {
|
|
3257
|
-
console.log(
|
|
3621
|
+
console.log(chalk6.red("\n\u26A0\uFE0F Merge Conflicts Detected:\n"));
|
|
3258
3622
|
conflicts.forEach((conflict, i) => {
|
|
3259
|
-
console.log(
|
|
3623
|
+
console.log(chalk6.yellow(` ${i + 1}. ${conflict}`));
|
|
3260
3624
|
});
|
|
3261
|
-
console.log(
|
|
3262
|
-
console.log(
|
|
3263
|
-
console.log(
|
|
3264
|
-
console.log(
|
|
3265
|
-
console.log(
|
|
3266
|
-
console.log(
|
|
3625
|
+
console.log(chalk6.gray("\n Conflict markers have been added to the file:"));
|
|
3626
|
+
console.log(chalk6.gray(" <<<<<<< YOUR VERSION"));
|
|
3627
|
+
console.log(chalk6.gray(" ... your code ..."));
|
|
3628
|
+
console.log(chalk6.gray(" ======="));
|
|
3629
|
+
console.log(chalk6.gray(" ... new code ..."));
|
|
3630
|
+
console.log(chalk6.gray(" >>>>>>> NEW VERSION\n"));
|
|
3267
3631
|
}
|
|
3268
3632
|
/**
|
|
3269
3633
|
* Show backup location
|
|
3270
3634
|
*/
|
|
3271
3635
|
static showBackupCreated(backupPath) {
|
|
3272
|
-
console.log(
|
|
3273
|
-
\u2713 Backup created: ${
|
|
3636
|
+
console.log(chalk6.green(`
|
|
3637
|
+
\u2713 Backup created: ${chalk6.cyan(backupPath)}`));
|
|
3274
3638
|
}
|
|
3275
3639
|
/**
|
|
3276
3640
|
* Show merge summary
|
|
3277
3641
|
*/
|
|
3278
3642
|
static showMergeSummary(stats) {
|
|
3279
|
-
console.log(
|
|
3643
|
+
console.log(chalk6.bold("\n\u{1F4CA} Merge Summary:\n"));
|
|
3280
3644
|
if (stats.merged > 0) {
|
|
3281
|
-
console.log(
|
|
3645
|
+
console.log(chalk6.green(` \u2713 Merged: ${stats.merged} file(s)`));
|
|
3282
3646
|
}
|
|
3283
3647
|
if (stats.kept > 0) {
|
|
3284
|
-
console.log(
|
|
3648
|
+
console.log(chalk6.blue(` \u2192 Kept: ${stats.kept} file(s)`));
|
|
3285
3649
|
}
|
|
3286
3650
|
if (stats.overwritten > 0) {
|
|
3287
|
-
console.log(
|
|
3651
|
+
console.log(chalk6.yellow(` \u26A0 Overwritten: ${stats.overwritten} file(s)`));
|
|
3288
3652
|
}
|
|
3289
3653
|
if (stats.conflicts > 0) {
|
|
3290
|
-
console.log(
|
|
3291
|
-
console.log(
|
|
3654
|
+
console.log(chalk6.red(` \u26A0 Conflicts: ${stats.conflicts} file(s)`));
|
|
3655
|
+
console.log(chalk6.gray("\n Please resolve conflicts manually before committing.\n"));
|
|
3292
3656
|
}
|
|
3293
3657
|
}
|
|
3294
3658
|
/**
|
|
@@ -3326,6 +3690,7 @@ var InteractivePrompt = class {
|
|
|
3326
3690
|
};
|
|
3327
3691
|
|
|
3328
3692
|
// src/cli/commands/add-module.ts
|
|
3693
|
+
init_error_handler();
|
|
3329
3694
|
var AVAILABLE_MODULES = {
|
|
3330
3695
|
auth: {
|
|
3331
3696
|
name: "Authentication",
|
|
@@ -3460,31 +3825,40 @@ var AVAILABLE_MODULES = {
|
|
|
3460
3825
|
var addModuleCommand = new Command3("add").description("Add a pre-built module to your project").argument(
|
|
3461
3826
|
"[module]",
|
|
3462
3827
|
"Module to add (auth, users, email, audit, upload, cache, notifications, settings)"
|
|
3463
|
-
).option("-l, --list", "List available modules").option("-f, --force", "Force overwrite existing module").option("-u, --update", "Update existing module (smart merge)").option("--skip-existing", "Skip if module already exists").action(
|
|
3828
|
+
).option("-l, --list", "List available modules").option("-f, --force", "Force overwrite existing module").option("-u, --update", "Update existing module (smart merge)").option("--skip-existing", "Skip if module already exists").option("--dry-run", "Preview changes without writing files").action(
|
|
3464
3829
|
async (moduleName, options) => {
|
|
3465
3830
|
if (options?.list || !moduleName) {
|
|
3466
|
-
console.log(
|
|
3831
|
+
console.log(chalk7.bold("\n\u{1F4E6} Available Modules:\n"));
|
|
3467
3832
|
for (const [key, mod] of Object.entries(AVAILABLE_MODULES)) {
|
|
3468
|
-
console.log(` ${
|
|
3469
|
-
console.log(` ${" ".repeat(15)} ${
|
|
3833
|
+
console.log(` ${chalk7.cyan(key.padEnd(15))} ${mod.name}`);
|
|
3834
|
+
console.log(` ${" ".repeat(15)} ${chalk7.gray(mod.description)}
|
|
3470
3835
|
`);
|
|
3471
3836
|
}
|
|
3472
|
-
console.log(
|
|
3473
|
-
console.log(` ${
|
|
3474
|
-
console.log(` ${
|
|
3475
|
-
console.log(` ${
|
|
3837
|
+
console.log(chalk7.bold("Usage:"));
|
|
3838
|
+
console.log(` ${chalk7.yellow("servcraft add auth")} Add authentication module`);
|
|
3839
|
+
console.log(` ${chalk7.yellow("servcraft add users")} Add user management module`);
|
|
3840
|
+
console.log(` ${chalk7.yellow("servcraft add email")} Add email service module
|
|
3476
3841
|
`);
|
|
3477
3842
|
return;
|
|
3478
3843
|
}
|
|
3844
|
+
const dryRun = DryRunManager.getInstance();
|
|
3845
|
+
if (options?.dryRun) {
|
|
3846
|
+
dryRun.enable();
|
|
3847
|
+
console.log(chalk7.yellow("\n\u26A0 DRY RUN MODE - No files will be written\n"));
|
|
3848
|
+
}
|
|
3479
3849
|
const module = AVAILABLE_MODULES[moduleName];
|
|
3480
3850
|
if (!module) {
|
|
3481
|
-
|
|
3482
|
-
|
|
3851
|
+
displayError(ErrorTypes.MODULE_NOT_FOUND(moduleName));
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
const projectError = validateProject();
|
|
3855
|
+
if (projectError) {
|
|
3856
|
+
displayError(projectError);
|
|
3483
3857
|
return;
|
|
3484
3858
|
}
|
|
3485
3859
|
const spinner = ora3(`Adding ${module.name} module...`).start();
|
|
3486
3860
|
try {
|
|
3487
|
-
const moduleDir =
|
|
3861
|
+
const moduleDir = path8.join(getModulesDir(), moduleName);
|
|
3488
3862
|
const templateManager = new TemplateManager(process.cwd());
|
|
3489
3863
|
const moduleExists = await fileExists(moduleDir);
|
|
3490
3864
|
if (moduleExists) {
|
|
@@ -3552,16 +3926,16 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
|
|
|
3552
3926
|
info("\n\u{1F4DD} Created new .env file");
|
|
3553
3927
|
}
|
|
3554
3928
|
if (result.added.length > 0) {
|
|
3555
|
-
console.log(
|
|
3929
|
+
console.log(chalk7.bold("\n\u2705 Added to .env:"));
|
|
3556
3930
|
result.added.forEach((key) => success(` ${key}`));
|
|
3557
3931
|
}
|
|
3558
3932
|
if (result.skipped.length > 0) {
|
|
3559
|
-
console.log(
|
|
3933
|
+
console.log(chalk7.bold("\n\u23ED\uFE0F Already in .env (skipped):"));
|
|
3560
3934
|
result.skipped.forEach((key) => info(` ${key}`));
|
|
3561
3935
|
}
|
|
3562
3936
|
const requiredVars = envSections.flatMap((section) => section.variables).filter((v) => v.required && !v.value).map((v) => v.key);
|
|
3563
3937
|
if (requiredVars.length > 0) {
|
|
3564
|
-
console.log(
|
|
3938
|
+
console.log(chalk7.bold("\n\u26A0\uFE0F Required configuration:"));
|
|
3565
3939
|
requiredVars.forEach((key) => warn(` ${key} - Please configure this variable`));
|
|
3566
3940
|
}
|
|
3567
3941
|
} catch (err) {
|
|
@@ -3569,10 +3943,15 @@ var addModuleCommand = new Command3("add").description("Add a pre-built module t
|
|
|
3569
3943
|
error(err instanceof Error ? err.message : String(err));
|
|
3570
3944
|
}
|
|
3571
3945
|
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3946
|
+
if (!options?.dryRun) {
|
|
3947
|
+
console.log("\n\u{1F4CC} Next steps:");
|
|
3948
|
+
info(" 1. Configure environment variables in .env (if needed)");
|
|
3949
|
+
info(" 2. Register the module in your main app file");
|
|
3950
|
+
info(" 3. Run database migrations if needed");
|
|
3951
|
+
}
|
|
3952
|
+
if (options?.dryRun) {
|
|
3953
|
+
dryRun.printSummary();
|
|
3954
|
+
}
|
|
3576
3955
|
} catch (err) {
|
|
3577
3956
|
spinner.fail("Failed to add module");
|
|
3578
3957
|
error(err instanceof Error ? err.message : String(err));
|
|
@@ -3623,7 +4002,7 @@ export * from './auth.schemas.js';
|
|
|
3623
4002
|
`
|
|
3624
4003
|
};
|
|
3625
4004
|
for (const [name, content] of Object.entries(files)) {
|
|
3626
|
-
await writeFile(
|
|
4005
|
+
await writeFile(path8.join(dir, name), content);
|
|
3627
4006
|
}
|
|
3628
4007
|
}
|
|
3629
4008
|
async function generateUsersModule(dir) {
|
|
@@ -3663,7 +4042,7 @@ export * from './user.schemas.js';
|
|
|
3663
4042
|
`
|
|
3664
4043
|
};
|
|
3665
4044
|
for (const [name, content] of Object.entries(files)) {
|
|
3666
|
-
await writeFile(
|
|
4045
|
+
await writeFile(path8.join(dir, name), content);
|
|
3667
4046
|
}
|
|
3668
4047
|
}
|
|
3669
4048
|
async function generateEmailModule(dir) {
|
|
@@ -3720,7 +4099,7 @@ export { EmailService, emailService } from './email.service.js';
|
|
|
3720
4099
|
`
|
|
3721
4100
|
};
|
|
3722
4101
|
for (const [name, content] of Object.entries(files)) {
|
|
3723
|
-
await writeFile(
|
|
4102
|
+
await writeFile(path8.join(dir, name), content);
|
|
3724
4103
|
}
|
|
3725
4104
|
}
|
|
3726
4105
|
async function generateAuditModule(dir) {
|
|
@@ -3764,7 +4143,7 @@ export { AuditService, auditService } from './audit.service.js';
|
|
|
3764
4143
|
`
|
|
3765
4144
|
};
|
|
3766
4145
|
for (const [name, content] of Object.entries(files)) {
|
|
3767
|
-
await writeFile(
|
|
4146
|
+
await writeFile(path8.join(dir, name), content);
|
|
3768
4147
|
}
|
|
3769
4148
|
}
|
|
3770
4149
|
async function generateUploadModule(dir) {
|
|
@@ -3790,7 +4169,7 @@ export interface UploadOptions {
|
|
|
3790
4169
|
`
|
|
3791
4170
|
};
|
|
3792
4171
|
for (const [name, content] of Object.entries(files)) {
|
|
3793
|
-
await writeFile(
|
|
4172
|
+
await writeFile(path8.join(dir, name), content);
|
|
3794
4173
|
}
|
|
3795
4174
|
}
|
|
3796
4175
|
async function generateCacheModule(dir) {
|
|
@@ -3836,7 +4215,7 @@ export { CacheService, cacheService } from './cache.service.js';
|
|
|
3836
4215
|
`
|
|
3837
4216
|
};
|
|
3838
4217
|
for (const [name, content] of Object.entries(files)) {
|
|
3839
|
-
await writeFile(
|
|
4218
|
+
await writeFile(path8.join(dir, name), content);
|
|
3840
4219
|
}
|
|
3841
4220
|
}
|
|
3842
4221
|
async function generateGenericModule(dir, name) {
|
|
@@ -3850,18 +4229,18 @@ export interface ${name.charAt(0).toUpperCase() + name.slice(1)}Data {
|
|
|
3850
4229
|
`
|
|
3851
4230
|
};
|
|
3852
4231
|
for (const [fileName, content] of Object.entries(files)) {
|
|
3853
|
-
await writeFile(
|
|
4232
|
+
await writeFile(path8.join(dir, fileName), content);
|
|
3854
4233
|
}
|
|
3855
4234
|
}
|
|
3856
4235
|
async function findServercraftModules() {
|
|
3857
|
-
const scriptDir =
|
|
4236
|
+
const scriptDir = path8.dirname(new URL(import.meta.url).pathname);
|
|
3858
4237
|
const possiblePaths = [
|
|
3859
4238
|
// Local node_modules (when servcraft is a dependency)
|
|
3860
|
-
|
|
4239
|
+
path8.join(process.cwd(), "node_modules", "servcraft", "src", "modules"),
|
|
3861
4240
|
// From dist/cli/index.js -> src/modules (npx or global install)
|
|
3862
|
-
|
|
4241
|
+
path8.resolve(scriptDir, "..", "..", "src", "modules"),
|
|
3863
4242
|
// From src/cli/commands/add-module.ts -> src/modules (development)
|
|
3864
|
-
|
|
4243
|
+
path8.resolve(scriptDir, "..", "..", "modules")
|
|
3865
4244
|
];
|
|
3866
4245
|
for (const p of possiblePaths) {
|
|
3867
4246
|
try {
|
|
@@ -3885,7 +4264,7 @@ async function generateModuleFiles(moduleName, moduleDir) {
|
|
|
3885
4264
|
const sourceDirName = moduleNameMap[moduleName] || moduleName;
|
|
3886
4265
|
const servercraftModulesDir = await findServercraftModules();
|
|
3887
4266
|
if (servercraftModulesDir) {
|
|
3888
|
-
const sourceModuleDir =
|
|
4267
|
+
const sourceModuleDir = path8.join(servercraftModulesDir, sourceDirName);
|
|
3889
4268
|
if (await fileExists(sourceModuleDir)) {
|
|
3890
4269
|
await copyModuleFromSource(sourceModuleDir, moduleDir);
|
|
3891
4270
|
return;
|
|
@@ -3917,8 +4296,8 @@ async function generateModuleFiles(moduleName, moduleDir) {
|
|
|
3917
4296
|
async function copyModuleFromSource(sourceDir, targetDir) {
|
|
3918
4297
|
const entries = await fs5.readdir(sourceDir, { withFileTypes: true });
|
|
3919
4298
|
for (const entry of entries) {
|
|
3920
|
-
const sourcePath =
|
|
3921
|
-
const targetPath =
|
|
4299
|
+
const sourcePath = path8.join(sourceDir, entry.name);
|
|
4300
|
+
const targetPath = path8.join(targetDir, entry.name);
|
|
3922
4301
|
if (entry.isDirectory()) {
|
|
3923
4302
|
await fs5.mkdir(targetPath, { recursive: true });
|
|
3924
4303
|
await copyModuleFromSource(sourcePath, targetPath);
|
|
@@ -3931,7 +4310,7 @@ async function getModuleFiles(moduleName, moduleDir) {
|
|
|
3931
4310
|
const files = {};
|
|
3932
4311
|
const entries = await fs5.readdir(moduleDir);
|
|
3933
4312
|
for (const entry of entries) {
|
|
3934
|
-
const filePath =
|
|
4313
|
+
const filePath = path8.join(moduleDir, entry);
|
|
3935
4314
|
const stat2 = await fs5.stat(filePath);
|
|
3936
4315
|
if (stat2.isFile() && entry.endsWith(".ts")) {
|
|
3937
4316
|
const content = await fs5.readFile(filePath, "utf-8");
|
|
@@ -3942,14 +4321,14 @@ async function getModuleFiles(moduleName, moduleDir) {
|
|
|
3942
4321
|
}
|
|
3943
4322
|
async function showDiffForModule(templateManager, moduleName, moduleDir) {
|
|
3944
4323
|
const modifiedFiles = await templateManager.getModifiedFiles(moduleName, moduleDir);
|
|
3945
|
-
console.log(
|
|
4324
|
+
console.log(chalk7.cyan(`
|
|
3946
4325
|
\u{1F4CA} Changes in module "${moduleName}":
|
|
3947
4326
|
`));
|
|
3948
4327
|
for (const file of modifiedFiles) {
|
|
3949
4328
|
if (file.isModified) {
|
|
3950
|
-
console.log(
|
|
4329
|
+
console.log(chalk7.yellow(`
|
|
3951
4330
|
\u{1F4C4} ${file.fileName}:`));
|
|
3952
|
-
const currentPath =
|
|
4331
|
+
const currentPath = path8.join(moduleDir, file.fileName);
|
|
3953
4332
|
const currentContent = await fs5.readFile(currentPath, "utf-8");
|
|
3954
4333
|
const originalContent = await templateManager.getTemplate(moduleName, file.fileName);
|
|
3955
4334
|
if (originalContent) {
|
|
@@ -3962,11 +4341,11 @@ async function showDiffForModule(templateManager, moduleName, moduleDir) {
|
|
|
3962
4341
|
async function performSmartMerge(templateManager, moduleName, moduleDir, _displayName) {
|
|
3963
4342
|
const spinner = ora3("Analyzing files for merge...").start();
|
|
3964
4343
|
const newFiles = {};
|
|
3965
|
-
const templateDir =
|
|
4344
|
+
const templateDir = path8.join(templateManager["templatesDir"], moduleName);
|
|
3966
4345
|
try {
|
|
3967
4346
|
const entries = await fs5.readdir(templateDir);
|
|
3968
4347
|
for (const entry of entries) {
|
|
3969
|
-
const content = await fs5.readFile(
|
|
4348
|
+
const content = await fs5.readFile(path8.join(templateDir, entry), "utf-8");
|
|
3970
4349
|
newFiles[entry] = content;
|
|
3971
4350
|
}
|
|
3972
4351
|
} catch {
|
|
@@ -3984,7 +4363,7 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
3984
4363
|
};
|
|
3985
4364
|
for (const fileInfo of modifiedFiles) {
|
|
3986
4365
|
const fileName = fileInfo.fileName;
|
|
3987
|
-
const filePath =
|
|
4366
|
+
const filePath = path8.join(moduleDir, fileName);
|
|
3988
4367
|
const newContent = newFiles[fileName];
|
|
3989
4368
|
if (!newContent) {
|
|
3990
4369
|
continue;
|
|
@@ -4053,10 +4432,11 @@ async function performSmartMerge(templateManager, moduleName, moduleDir, _displa
|
|
|
4053
4432
|
}
|
|
4054
4433
|
|
|
4055
4434
|
// src/cli/commands/db.ts
|
|
4435
|
+
init_esm_shims();
|
|
4056
4436
|
import { Command as Command4 } from "commander";
|
|
4057
4437
|
import { execSync as execSync2, spawn } from "child_process";
|
|
4058
4438
|
import ora4 from "ora";
|
|
4059
|
-
import
|
|
4439
|
+
import chalk8 from "chalk";
|
|
4060
4440
|
var dbCommand = new Command4("db").description("Database management commands");
|
|
4061
4441
|
dbCommand.command("migrate").description("Run database migrations").option("-n, --name <name>", "Migration name").action(async (options) => {
|
|
4062
4442
|
const spinner = ora4("Running migrations...").start();
|
|
@@ -4113,7 +4493,7 @@ dbCommand.command("seed").description("Run database seed").action(async () => {
|
|
|
4113
4493
|
});
|
|
4114
4494
|
dbCommand.command("reset").description("Reset database (drop all data and re-run migrations)").option("-f, --force", "Skip confirmation").action(async (options) => {
|
|
4115
4495
|
if (!options.force) {
|
|
4116
|
-
console.log(
|
|
4496
|
+
console.log(chalk8.yellow("\n\u26A0\uFE0F WARNING: This will delete all data in your database!\n"));
|
|
4117
4497
|
const readline = await import("readline");
|
|
4118
4498
|
const rl = readline.createInterface({
|
|
4119
4499
|
input: process.stdin,
|
|
@@ -4146,21 +4526,25 @@ dbCommand.command("status").description("Show migration status").action(async ()
|
|
|
4146
4526
|
});
|
|
4147
4527
|
|
|
4148
4528
|
// src/cli/commands/docs.ts
|
|
4529
|
+
init_esm_shims();
|
|
4149
4530
|
import { Command as Command5 } from "commander";
|
|
4150
|
-
import
|
|
4531
|
+
import path10 from "path";
|
|
4151
4532
|
import fs7 from "fs/promises";
|
|
4152
4533
|
import ora6 from "ora";
|
|
4153
|
-
import
|
|
4534
|
+
import chalk9 from "chalk";
|
|
4154
4535
|
|
|
4155
4536
|
// src/cli/utils/docs-generator.ts
|
|
4537
|
+
init_esm_shims();
|
|
4156
4538
|
import fs6 from "fs/promises";
|
|
4157
|
-
import
|
|
4539
|
+
import path9 from "path";
|
|
4158
4540
|
import ora5 from "ora";
|
|
4159
4541
|
|
|
4160
4542
|
// src/core/server.ts
|
|
4543
|
+
init_esm_shims();
|
|
4161
4544
|
import Fastify from "fastify";
|
|
4162
4545
|
|
|
4163
4546
|
// src/core/logger.ts
|
|
4547
|
+
init_esm_shims();
|
|
4164
4548
|
import pino from "pino";
|
|
4165
4549
|
var defaultConfig = {
|
|
4166
4550
|
level: process.env.LOG_LEVEL || "info",
|
|
@@ -4293,7 +4677,14 @@ function createServer(config2 = {}) {
|
|
|
4293
4677
|
return new Server(config2);
|
|
4294
4678
|
}
|
|
4295
4679
|
|
|
4680
|
+
// src/middleware/index.ts
|
|
4681
|
+
init_esm_shims();
|
|
4682
|
+
|
|
4683
|
+
// src/middleware/error-handler.ts
|
|
4684
|
+
init_esm_shims();
|
|
4685
|
+
|
|
4296
4686
|
// src/utils/errors.ts
|
|
4687
|
+
init_esm_shims();
|
|
4297
4688
|
var AppError = class _AppError extends Error {
|
|
4298
4689
|
statusCode;
|
|
4299
4690
|
isOperational;
|
|
@@ -4347,7 +4738,11 @@ function isAppError(error2) {
|
|
|
4347
4738
|
return error2 instanceof AppError;
|
|
4348
4739
|
}
|
|
4349
4740
|
|
|
4741
|
+
// src/config/index.ts
|
|
4742
|
+
init_esm_shims();
|
|
4743
|
+
|
|
4350
4744
|
// src/config/env.ts
|
|
4745
|
+
init_esm_shims();
|
|
4351
4746
|
import { z } from "zod";
|
|
4352
4747
|
import dotenv from "dotenv";
|
|
4353
4748
|
dotenv.config();
|
|
@@ -4493,6 +4888,7 @@ function registerErrorHandler(app) {
|
|
|
4493
4888
|
}
|
|
4494
4889
|
|
|
4495
4890
|
// src/middleware/security.ts
|
|
4891
|
+
init_esm_shims();
|
|
4496
4892
|
import helmet from "@fastify/helmet";
|
|
4497
4893
|
import cors from "@fastify/cors";
|
|
4498
4894
|
import rateLimit from "@fastify/rate-limit";
|
|
@@ -4552,7 +4948,11 @@ async function registerSecurity(app, options = {}) {
|
|
|
4552
4948
|
}
|
|
4553
4949
|
}
|
|
4554
4950
|
|
|
4951
|
+
// src/modules/swagger/index.ts
|
|
4952
|
+
init_esm_shims();
|
|
4953
|
+
|
|
4555
4954
|
// src/modules/swagger/swagger.service.ts
|
|
4955
|
+
init_esm_shims();
|
|
4556
4956
|
import swagger from "@fastify/swagger";
|
|
4557
4957
|
import swaggerUi from "@fastify/swagger-ui";
|
|
4558
4958
|
var defaultConfig3 = {
|
|
@@ -4612,11 +5012,16 @@ async function registerSwagger(app, customConfig) {
|
|
|
4612
5012
|
logger.info("Swagger documentation registered at /docs");
|
|
4613
5013
|
}
|
|
4614
5014
|
|
|
5015
|
+
// src/modules/swagger/schema-builder.ts
|
|
5016
|
+
init_esm_shims();
|
|
5017
|
+
|
|
4615
5018
|
// src/modules/auth/index.ts
|
|
5019
|
+
init_esm_shims();
|
|
4616
5020
|
import jwt from "@fastify/jwt";
|
|
4617
5021
|
import cookie from "@fastify/cookie";
|
|
4618
5022
|
|
|
4619
5023
|
// src/modules/auth/auth.service.ts
|
|
5024
|
+
init_esm_shims();
|
|
4620
5025
|
import bcrypt from "bcryptjs";
|
|
4621
5026
|
import { Redis } from "ioredis";
|
|
4622
5027
|
var AuthService = class {
|
|
@@ -4815,7 +5220,11 @@ function createAuthService(app) {
|
|
|
4815
5220
|
return new AuthService(app);
|
|
4816
5221
|
}
|
|
4817
5222
|
|
|
5223
|
+
// src/modules/auth/auth.controller.ts
|
|
5224
|
+
init_esm_shims();
|
|
5225
|
+
|
|
4818
5226
|
// src/modules/auth/schemas.ts
|
|
5227
|
+
init_esm_shims();
|
|
4819
5228
|
import { z as z2 } from "zod";
|
|
4820
5229
|
var loginSchema = z2.object({
|
|
4821
5230
|
email: z2.string().email("Invalid email address"),
|
|
@@ -4842,6 +5251,7 @@ var changePasswordSchema = z2.object({
|
|
|
4842
5251
|
});
|
|
4843
5252
|
|
|
4844
5253
|
// src/utils/response.ts
|
|
5254
|
+
init_esm_shims();
|
|
4845
5255
|
function success2(reply, data, statusCode = 200) {
|
|
4846
5256
|
const response = {
|
|
4847
5257
|
success: true,
|
|
@@ -4857,6 +5267,7 @@ function noContent(reply) {
|
|
|
4857
5267
|
}
|
|
4858
5268
|
|
|
4859
5269
|
// src/modules/validation/validator.ts
|
|
5270
|
+
init_esm_shims();
|
|
4860
5271
|
import { z as z3 } from "zod";
|
|
4861
5272
|
function validateBody(schema, data) {
|
|
4862
5273
|
const result = schema.safeParse(data);
|
|
@@ -4875,11 +5286,11 @@ function validateQuery(schema, data) {
|
|
|
4875
5286
|
function formatZodErrors(error2) {
|
|
4876
5287
|
const errors = {};
|
|
4877
5288
|
for (const issue of error2.issues) {
|
|
4878
|
-
const
|
|
4879
|
-
if (!errors[
|
|
4880
|
-
errors[
|
|
5289
|
+
const path12 = issue.path.join(".") || "root";
|
|
5290
|
+
if (!errors[path12]) {
|
|
5291
|
+
errors[path12] = [];
|
|
4881
5292
|
}
|
|
4882
|
-
errors[
|
|
5293
|
+
errors[path12].push(issue.message);
|
|
4883
5294
|
}
|
|
4884
5295
|
return errors;
|
|
4885
5296
|
}
|
|
@@ -5027,7 +5438,11 @@ function createAuthController(authService, userService) {
|
|
|
5027
5438
|
return new AuthController(authService, userService);
|
|
5028
5439
|
}
|
|
5029
5440
|
|
|
5441
|
+
// src/modules/auth/auth.routes.ts
|
|
5442
|
+
init_esm_shims();
|
|
5443
|
+
|
|
5030
5444
|
// src/modules/auth/auth.middleware.ts
|
|
5445
|
+
init_esm_shims();
|
|
5031
5446
|
function createAuthMiddleware(authService) {
|
|
5032
5447
|
return async function authenticate(request, _reply) {
|
|
5033
5448
|
const authHeader = request.headers.authorization;
|
|
@@ -5070,7 +5485,14 @@ function registerAuthRoutes(app, controller, authService) {
|
|
|
5070
5485
|
);
|
|
5071
5486
|
}
|
|
5072
5487
|
|
|
5488
|
+
// src/modules/user/user.service.ts
|
|
5489
|
+
init_esm_shims();
|
|
5490
|
+
|
|
5491
|
+
// src/modules/user/user.repository.ts
|
|
5492
|
+
init_esm_shims();
|
|
5493
|
+
|
|
5073
5494
|
// src/database/prisma.ts
|
|
5495
|
+
init_esm_shims();
|
|
5074
5496
|
import { PrismaClient } from "@prisma/client";
|
|
5075
5497
|
var prismaClientSingleton = () => {
|
|
5076
5498
|
return new PrismaClient({
|
|
@@ -5084,6 +5506,7 @@ if (!isProduction()) {
|
|
|
5084
5506
|
}
|
|
5085
5507
|
|
|
5086
5508
|
// src/utils/pagination.ts
|
|
5509
|
+
init_esm_shims();
|
|
5087
5510
|
var DEFAULT_PAGE = 1;
|
|
5088
5511
|
var DEFAULT_LIMIT = 20;
|
|
5089
5512
|
var MAX_LIMIT = 100;
|
|
@@ -5348,6 +5771,7 @@ function createUserRepository() {
|
|
|
5348
5771
|
}
|
|
5349
5772
|
|
|
5350
5773
|
// src/modules/user/types.ts
|
|
5774
|
+
init_esm_shims();
|
|
5351
5775
|
var DEFAULT_ROLE_PERMISSIONS = {
|
|
5352
5776
|
user: ["profile:read", "profile:update"],
|
|
5353
5777
|
moderator: [
|
|
@@ -5478,6 +5902,9 @@ function createUserService(repository) {
|
|
|
5478
5902
|
return new UserService(repository || createUserRepository());
|
|
5479
5903
|
}
|
|
5480
5904
|
|
|
5905
|
+
// src/modules/auth/types.ts
|
|
5906
|
+
init_esm_shims();
|
|
5907
|
+
|
|
5481
5908
|
// src/modules/auth/index.ts
|
|
5482
5909
|
async function registerAuthModule(app) {
|
|
5483
5910
|
await app.register(jwt, {
|
|
@@ -5497,7 +5924,14 @@ async function registerAuthModule(app) {
|
|
|
5497
5924
|
logger.info("Auth module registered");
|
|
5498
5925
|
}
|
|
5499
5926
|
|
|
5927
|
+
// src/modules/user/index.ts
|
|
5928
|
+
init_esm_shims();
|
|
5929
|
+
|
|
5930
|
+
// src/modules/user/user.controller.ts
|
|
5931
|
+
init_esm_shims();
|
|
5932
|
+
|
|
5500
5933
|
// src/modules/user/schemas.ts
|
|
5934
|
+
init_esm_shims();
|
|
5501
5935
|
import { z as z4 } from "zod";
|
|
5502
5936
|
var userStatusEnum = z4.enum(["active", "inactive", "suspended", "banned"]);
|
|
5503
5937
|
var userRoleEnum = z4.enum(["user", "admin", "moderator", "super_admin"]);
|
|
@@ -5624,6 +6058,7 @@ function createUserController(userService) {
|
|
|
5624
6058
|
}
|
|
5625
6059
|
|
|
5626
6060
|
// src/modules/user/user.routes.ts
|
|
6061
|
+
init_esm_shims();
|
|
5627
6062
|
var idParamsSchema = {
|
|
5628
6063
|
type: "object",
|
|
5629
6064
|
properties: {
|
|
@@ -5712,8 +6147,8 @@ async function generateDocs(outputPath = "openapi.json", silent = false) {
|
|
|
5712
6147
|
await registerUserModule(app, authService);
|
|
5713
6148
|
await app.ready();
|
|
5714
6149
|
const spec = app.swagger();
|
|
5715
|
-
const absoluteOutput =
|
|
5716
|
-
await fs6.mkdir(
|
|
6150
|
+
const absoluteOutput = path9.resolve(outputPath);
|
|
6151
|
+
await fs6.mkdir(path9.dirname(absoluteOutput), { recursive: true });
|
|
5717
6152
|
await fs6.writeFile(absoluteOutput, JSON.stringify(spec, null, 2), "utf8");
|
|
5718
6153
|
spinner?.succeed(`OpenAPI spec generated at ${absoluteOutput}`);
|
|
5719
6154
|
await app.close();
|
|
@@ -5758,7 +6193,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
|
|
|
5758
6193
|
const spinner = ora6("Exporting documentation...").start();
|
|
5759
6194
|
try {
|
|
5760
6195
|
const projectRoot = getProjectRoot();
|
|
5761
|
-
const specPath =
|
|
6196
|
+
const specPath = path10.join(projectRoot, "openapi.json");
|
|
5762
6197
|
try {
|
|
5763
6198
|
await fs7.access(specPath);
|
|
5764
6199
|
} catch {
|
|
@@ -5785,7 +6220,7 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
|
|
|
5785
6220
|
default:
|
|
5786
6221
|
throw new Error(`Unknown format: ${options.format}`);
|
|
5787
6222
|
}
|
|
5788
|
-
const outPath =
|
|
6223
|
+
const outPath = path10.join(projectRoot, options.output || defaultName);
|
|
5789
6224
|
await fs7.writeFile(outPath, output);
|
|
5790
6225
|
spinner.succeed(`Exported to: ${options.output || defaultName}`);
|
|
5791
6226
|
if (options.format === "postman") {
|
|
@@ -5798,8 +6233,8 @@ docsCommand.command("export").description("Export documentation to Postman, Inso
|
|
|
5798
6233
|
});
|
|
5799
6234
|
docsCommand.command("status").description("Show documentation status").action(async () => {
|
|
5800
6235
|
const projectRoot = getProjectRoot();
|
|
5801
|
-
console.log(
|
|
5802
|
-
const specPath =
|
|
6236
|
+
console.log(chalk9.bold("\n\u{1F4CA} Documentation Status\n"));
|
|
6237
|
+
const specPath = path10.join(projectRoot, "openapi.json");
|
|
5803
6238
|
try {
|
|
5804
6239
|
const stat2 = await fs7.stat(specPath);
|
|
5805
6240
|
success(
|
|
@@ -5914,13 +6349,325 @@ function formatDate(date) {
|
|
|
5914
6349
|
});
|
|
5915
6350
|
}
|
|
5916
6351
|
|
|
6352
|
+
// src/cli/commands/list.ts
|
|
6353
|
+
init_esm_shims();
|
|
6354
|
+
import { Command as Command6 } from "commander";
|
|
6355
|
+
import chalk10 from "chalk";
|
|
6356
|
+
import fs8 from "fs/promises";
|
|
6357
|
+
var AVAILABLE_MODULES2 = {
|
|
6358
|
+
// Core
|
|
6359
|
+
auth: {
|
|
6360
|
+
name: "Authentication",
|
|
6361
|
+
description: "JWT authentication with access/refresh tokens",
|
|
6362
|
+
category: "Core"
|
|
6363
|
+
},
|
|
6364
|
+
users: {
|
|
6365
|
+
name: "User Management",
|
|
6366
|
+
description: "User CRUD with RBAC (roles & permissions)",
|
|
6367
|
+
category: "Core"
|
|
6368
|
+
},
|
|
6369
|
+
email: {
|
|
6370
|
+
name: "Email Service",
|
|
6371
|
+
description: "SMTP email with templates (Handlebars)",
|
|
6372
|
+
category: "Core"
|
|
6373
|
+
},
|
|
6374
|
+
// Security
|
|
6375
|
+
mfa: {
|
|
6376
|
+
name: "MFA/TOTP",
|
|
6377
|
+
description: "Two-factor authentication with QR codes",
|
|
6378
|
+
category: "Security"
|
|
6379
|
+
},
|
|
6380
|
+
oauth: {
|
|
6381
|
+
name: "OAuth",
|
|
6382
|
+
description: "Social login (Google, GitHub, Facebook, Twitter, Apple)",
|
|
6383
|
+
category: "Security"
|
|
6384
|
+
},
|
|
6385
|
+
"rate-limit": {
|
|
6386
|
+
name: "Rate Limiting",
|
|
6387
|
+
description: "Advanced rate limiting with multiple algorithms",
|
|
6388
|
+
category: "Security"
|
|
6389
|
+
},
|
|
6390
|
+
// Data & Storage
|
|
6391
|
+
cache: {
|
|
6392
|
+
name: "Redis Cache",
|
|
6393
|
+
description: "Redis caching with TTL & invalidation",
|
|
6394
|
+
category: "Data & Storage"
|
|
6395
|
+
},
|
|
6396
|
+
upload: {
|
|
6397
|
+
name: "File Upload",
|
|
6398
|
+
description: "File upload with local/S3/Cloudinary storage",
|
|
6399
|
+
category: "Data & Storage"
|
|
6400
|
+
},
|
|
6401
|
+
search: {
|
|
6402
|
+
name: "Search",
|
|
6403
|
+
description: "Full-text search with Elasticsearch/Meilisearch",
|
|
6404
|
+
category: "Data & Storage"
|
|
6405
|
+
},
|
|
6406
|
+
// Communication
|
|
6407
|
+
notification: {
|
|
6408
|
+
name: "Notifications",
|
|
6409
|
+
description: "Email, SMS, Push notifications",
|
|
6410
|
+
category: "Communication"
|
|
6411
|
+
},
|
|
6412
|
+
webhook: {
|
|
6413
|
+
name: "Webhooks",
|
|
6414
|
+
description: "Outgoing webhooks with HMAC signatures & retry",
|
|
6415
|
+
category: "Communication"
|
|
6416
|
+
},
|
|
6417
|
+
websocket: {
|
|
6418
|
+
name: "WebSockets",
|
|
6419
|
+
description: "Real-time communication with Socket.io",
|
|
6420
|
+
category: "Communication"
|
|
6421
|
+
},
|
|
6422
|
+
// Background Processing
|
|
6423
|
+
queue: {
|
|
6424
|
+
name: "Queue/Jobs",
|
|
6425
|
+
description: "Background jobs with Bull/BullMQ & cron scheduling",
|
|
6426
|
+
category: "Background Processing"
|
|
6427
|
+
},
|
|
6428
|
+
"media-processing": {
|
|
6429
|
+
name: "Media Processing",
|
|
6430
|
+
description: "Image/video processing with FFmpeg",
|
|
6431
|
+
category: "Background Processing"
|
|
6432
|
+
},
|
|
6433
|
+
// Monitoring & Analytics
|
|
6434
|
+
audit: {
|
|
6435
|
+
name: "Audit Logs",
|
|
6436
|
+
description: "Activity logging and audit trail",
|
|
6437
|
+
category: "Monitoring & Analytics"
|
|
6438
|
+
},
|
|
6439
|
+
analytics: {
|
|
6440
|
+
name: "Analytics/Metrics",
|
|
6441
|
+
description: "Prometheus metrics & event tracking",
|
|
6442
|
+
category: "Monitoring & Analytics"
|
|
6443
|
+
},
|
|
6444
|
+
// Internationalization
|
|
6445
|
+
i18n: {
|
|
6446
|
+
name: "i18n/Localization",
|
|
6447
|
+
description: "Multi-language support with 7+ locales",
|
|
6448
|
+
category: "Internationalization"
|
|
6449
|
+
},
|
|
6450
|
+
// API Management
|
|
6451
|
+
"feature-flag": {
|
|
6452
|
+
name: "Feature Flags",
|
|
6453
|
+
description: "A/B testing & progressive rollout",
|
|
6454
|
+
category: "API Management"
|
|
6455
|
+
},
|
|
6456
|
+
"api-versioning": {
|
|
6457
|
+
name: "API Versioning",
|
|
6458
|
+
description: "Multiple API versions support",
|
|
6459
|
+
category: "API Management"
|
|
6460
|
+
},
|
|
6461
|
+
// Payments
|
|
6462
|
+
payment: {
|
|
6463
|
+
name: "Payments",
|
|
6464
|
+
description: "Payment processing (Stripe, PayPal, Mobile Money)",
|
|
6465
|
+
category: "Payments"
|
|
6466
|
+
}
|
|
6467
|
+
};
|
|
6468
|
+
async function getInstalledModules() {
|
|
6469
|
+
try {
|
|
6470
|
+
const modulesDir = getModulesDir();
|
|
6471
|
+
const entries = await fs8.readdir(modulesDir, { withFileTypes: true });
|
|
6472
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
6473
|
+
} catch {
|
|
6474
|
+
return [];
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
function isServercraftProject() {
|
|
6478
|
+
try {
|
|
6479
|
+
getProjectRoot();
|
|
6480
|
+
return true;
|
|
6481
|
+
} catch {
|
|
6482
|
+
return false;
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
var listCommand = new Command6("list").alias("ls").description("List available and installed modules").option("-a, --available", "Show only available modules").option("-i, --installed", "Show only installed modules").option("-c, --category <category>", "Filter by category").option("--json", "Output as JSON").action(
|
|
6486
|
+
async (options) => {
|
|
6487
|
+
const installedModules = await getInstalledModules();
|
|
6488
|
+
const isProject = isServercraftProject();
|
|
6489
|
+
if (options.json) {
|
|
6490
|
+
const output = {
|
|
6491
|
+
available: Object.entries(AVAILABLE_MODULES2).map(([key, mod]) => ({
|
|
6492
|
+
id: key,
|
|
6493
|
+
...mod,
|
|
6494
|
+
installed: installedModules.includes(key)
|
|
6495
|
+
}))
|
|
6496
|
+
};
|
|
6497
|
+
if (isProject) {
|
|
6498
|
+
output.installed = installedModules;
|
|
6499
|
+
}
|
|
6500
|
+
console.log(JSON.stringify(output, null, 2));
|
|
6501
|
+
return;
|
|
6502
|
+
}
|
|
6503
|
+
const byCategory = {};
|
|
6504
|
+
for (const [key, mod] of Object.entries(AVAILABLE_MODULES2)) {
|
|
6505
|
+
if (options.category && mod.category.toLowerCase() !== options.category.toLowerCase()) {
|
|
6506
|
+
continue;
|
|
6507
|
+
}
|
|
6508
|
+
if (!byCategory[mod.category]) {
|
|
6509
|
+
byCategory[mod.category] = [];
|
|
6510
|
+
}
|
|
6511
|
+
byCategory[mod.category].push({
|
|
6512
|
+
id: key,
|
|
6513
|
+
name: mod.name,
|
|
6514
|
+
description: mod.description,
|
|
6515
|
+
installed: installedModules.includes(key)
|
|
6516
|
+
});
|
|
6517
|
+
}
|
|
6518
|
+
if (options.installed) {
|
|
6519
|
+
if (!isProject) {
|
|
6520
|
+
console.log(chalk10.yellow("\n\u26A0 Not in a Servcraft project directory\n"));
|
|
6521
|
+
return;
|
|
6522
|
+
}
|
|
6523
|
+
console.log(chalk10.bold("\n\u{1F4E6} Installed Modules:\n"));
|
|
6524
|
+
if (installedModules.length === 0) {
|
|
6525
|
+
console.log(chalk10.gray(" No modules installed yet.\n"));
|
|
6526
|
+
console.log(` Run ${chalk10.cyan("servcraft add <module>")} to add a module.
|
|
6527
|
+
`);
|
|
6528
|
+
return;
|
|
6529
|
+
}
|
|
6530
|
+
for (const modId of installedModules) {
|
|
6531
|
+
const mod = AVAILABLE_MODULES2[modId];
|
|
6532
|
+
if (mod) {
|
|
6533
|
+
console.log(` ${chalk10.green("\u2713")} ${chalk10.cyan(modId.padEnd(18))} ${mod.name}`);
|
|
6534
|
+
} else {
|
|
6535
|
+
console.log(
|
|
6536
|
+
` ${chalk10.green("\u2713")} ${chalk10.cyan(modId.padEnd(18))} ${chalk10.gray("(custom module)")}`
|
|
6537
|
+
);
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
console.log(`
|
|
6541
|
+
Total: ${chalk10.bold(installedModules.length)} module(s) installed
|
|
6542
|
+
`);
|
|
6543
|
+
return;
|
|
6544
|
+
}
|
|
6545
|
+
console.log(chalk10.bold("\n\u{1F4E6} Available Modules\n"));
|
|
6546
|
+
if (isProject) {
|
|
6547
|
+
console.log(
|
|
6548
|
+
chalk10.gray(` ${chalk10.green("\u2713")} = installed ${chalk10.dim("\u25CB")} = not installed
|
|
6549
|
+
`)
|
|
6550
|
+
);
|
|
6551
|
+
}
|
|
6552
|
+
for (const [category, modules] of Object.entries(byCategory)) {
|
|
6553
|
+
console.log(chalk10.bold.blue(` ${category}`));
|
|
6554
|
+
console.log(chalk10.gray(" " + "\u2500".repeat(40)));
|
|
6555
|
+
for (const mod of modules) {
|
|
6556
|
+
const status = isProject ? mod.installed ? chalk10.green("\u2713") : chalk10.dim("\u25CB") : " ";
|
|
6557
|
+
const nameColor = mod.installed ? chalk10.green : chalk10.cyan;
|
|
6558
|
+
console.log(` ${status} ${nameColor(mod.id.padEnd(18))} ${mod.name}`);
|
|
6559
|
+
console.log(` ${chalk10.gray(mod.description)}`);
|
|
6560
|
+
}
|
|
6561
|
+
console.log();
|
|
6562
|
+
}
|
|
6563
|
+
const totalAvailable = Object.keys(AVAILABLE_MODULES2).length;
|
|
6564
|
+
const totalInstalled = installedModules.filter((m) => AVAILABLE_MODULES2[m]).length;
|
|
6565
|
+
console.log(chalk10.gray("\u2500".repeat(50)));
|
|
6566
|
+
console.log(
|
|
6567
|
+
` ${chalk10.bold(totalAvailable)} modules available` + (isProject ? ` | ${chalk10.green.bold(totalInstalled)} installed` : "")
|
|
6568
|
+
);
|
|
6569
|
+
console.log();
|
|
6570
|
+
console.log(chalk10.bold(" Usage:"));
|
|
6571
|
+
console.log(` ${chalk10.yellow("servcraft add <module>")} Add a module`);
|
|
6572
|
+
console.log(` ${chalk10.yellow("servcraft list --installed")} Show installed only`);
|
|
6573
|
+
console.log(` ${chalk10.yellow("servcraft list --category Security")} Filter by category`);
|
|
6574
|
+
console.log();
|
|
6575
|
+
}
|
|
6576
|
+
);
|
|
6577
|
+
|
|
6578
|
+
// src/cli/commands/remove.ts
|
|
6579
|
+
init_esm_shims();
|
|
6580
|
+
import { Command as Command7 } from "commander";
|
|
6581
|
+
import path11 from "path";
|
|
6582
|
+
import ora7 from "ora";
|
|
6583
|
+
import chalk11 from "chalk";
|
|
6584
|
+
import fs9 from "fs/promises";
|
|
6585
|
+
import inquirer4 from "inquirer";
|
|
6586
|
+
init_error_handler();
|
|
6587
|
+
var removeCommand = new Command7("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) => {
|
|
6588
|
+
const projectError = validateProject();
|
|
6589
|
+
if (projectError) {
|
|
6590
|
+
displayError(projectError);
|
|
6591
|
+
return;
|
|
6592
|
+
}
|
|
6593
|
+
console.log(chalk11.bold.cyan("\n\u{1F5D1}\uFE0F ServCraft Module Removal\n"));
|
|
6594
|
+
const moduleDir = path11.join(getModulesDir(), moduleName);
|
|
6595
|
+
try {
|
|
6596
|
+
const exists = await fs9.access(moduleDir).then(() => true).catch(() => false);
|
|
6597
|
+
if (!exists) {
|
|
6598
|
+
displayError(
|
|
6599
|
+
new (init_error_handler(), __toCommonJS(error_handler_exports)).ServCraftError(
|
|
6600
|
+
`Module "${moduleName}" is not installed`,
|
|
6601
|
+
[
|
|
6602
|
+
`Run ${chalk11.cyan("servcraft list --installed")} to see installed modules`,
|
|
6603
|
+
`Check the spelling of the module name`
|
|
6604
|
+
]
|
|
6605
|
+
)
|
|
6606
|
+
);
|
|
6607
|
+
return;
|
|
6608
|
+
}
|
|
6609
|
+
const files = await fs9.readdir(moduleDir);
|
|
6610
|
+
const fileCount = files.length;
|
|
6611
|
+
if (!options?.yes) {
|
|
6612
|
+
console.log(chalk11.yellow(`\u26A0 This will remove the "${moduleName}" module:`));
|
|
6613
|
+
console.log(chalk11.gray(` Directory: ${moduleDir}`));
|
|
6614
|
+
console.log(chalk11.gray(` Files: ${fileCount} file(s)`));
|
|
6615
|
+
console.log();
|
|
6616
|
+
const { confirm } = await inquirer4.prompt([
|
|
6617
|
+
{
|
|
6618
|
+
type: "confirm",
|
|
6619
|
+
name: "confirm",
|
|
6620
|
+
message: "Are you sure you want to remove this module?",
|
|
6621
|
+
default: false
|
|
6622
|
+
}
|
|
6623
|
+
]);
|
|
6624
|
+
if (!confirm) {
|
|
6625
|
+
console.log(chalk11.yellow("\n\u2716 Removal cancelled\n"));
|
|
6626
|
+
return;
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
const spinner = ora7("Removing module...").start();
|
|
6630
|
+
await fs9.rm(moduleDir, { recursive: true, force: true });
|
|
6631
|
+
spinner.succeed(`Module "${moduleName}" removed successfully!`);
|
|
6632
|
+
console.log("\n" + chalk11.bold("\u2713 Removed:"));
|
|
6633
|
+
success(` src/modules/${moduleName}/ (${fileCount} files)`);
|
|
6634
|
+
if (!options?.keepEnv) {
|
|
6635
|
+
console.log("\n" + chalk11.bold("\u{1F4CC} Manual cleanup needed:"));
|
|
6636
|
+
info(" 1. Remove environment variables related to this module from .env");
|
|
6637
|
+
info(" 2. Remove module imports from your main app file");
|
|
6638
|
+
info(" 3. Remove related database migrations if any");
|
|
6639
|
+
info(" 4. Update your routes if they reference this module");
|
|
6640
|
+
} else {
|
|
6641
|
+
console.log("\n" + chalk11.bold("\u{1F4CC} Manual cleanup needed:"));
|
|
6642
|
+
info(" 1. Environment variables were kept (--keep-env flag)");
|
|
6643
|
+
info(" 2. Remove module imports from your main app file");
|
|
6644
|
+
info(" 3. Update your routes if they reference this module");
|
|
6645
|
+
}
|
|
6646
|
+
console.log();
|
|
6647
|
+
} catch (err) {
|
|
6648
|
+
error(err instanceof Error ? err.message : String(err));
|
|
6649
|
+
console.log();
|
|
6650
|
+
}
|
|
6651
|
+
});
|
|
6652
|
+
|
|
6653
|
+
// src/cli/commands/doctor.ts
|
|
6654
|
+
init_esm_shims();
|
|
6655
|
+
import { Command as Command8 } from "commander";
|
|
6656
|
+
import chalk12 from "chalk";
|
|
6657
|
+
var doctorCommand = new Command8("doctor").description("Diagnose project configuration and dependencies").action(async () => {
|
|
6658
|
+
console.log(chalk12.bold.cyan("\nServCraft Doctor - Coming soon!\n"));
|
|
6659
|
+
});
|
|
6660
|
+
|
|
5917
6661
|
// src/cli/index.ts
|
|
5918
|
-
var program = new
|
|
6662
|
+
var program = new Command9();
|
|
5919
6663
|
program.name("servcraft").description("Servcraft - A modular Node.js backend framework CLI").version("0.1.0");
|
|
5920
6664
|
program.addCommand(initCommand);
|
|
5921
6665
|
program.addCommand(generateCommand);
|
|
5922
6666
|
program.addCommand(addModuleCommand);
|
|
5923
6667
|
program.addCommand(dbCommand);
|
|
5924
6668
|
program.addCommand(docsCommand);
|
|
6669
|
+
program.addCommand(listCommand);
|
|
6670
|
+
program.addCommand(removeCommand);
|
|
6671
|
+
program.addCommand(doctorCommand);
|
|
5925
6672
|
program.parse();
|
|
5926
6673
|
//# sourceMappingURL=index.js.map
|