zuro-cli 0.0.2-beta.12 → 0.0.2-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-VMOTWTER.mjs +37 -0
- package/dist/chunk-VMOTWTER.mjs.map +1 -0
- package/dist/chunk-W36ZIR4Y.mjs +216 -0
- package/dist/chunk-W36ZIR4Y.mjs.map +1 -0
- package/dist/chunk-YBAO5SKK.mjs +87 -0
- package/dist/chunk-YBAO5SKK.mjs.map +1 -0
- package/dist/database.handler-D7EVXRJX.mjs +28 -0
- package/dist/database.handler-D7EVXRJX.mjs.map +1 -0
- package/dist/docs.handler-JL3ZIVJQ.mjs +12 -0
- package/dist/docs.handler-JL3ZIVJQ.mjs.map +1 -0
- package/dist/index.js +928 -429
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +582 -417
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,6 +30,343 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
33
|
+
// src/utils/code-inject.ts
|
|
34
|
+
function escapeRegex(value) {
|
|
35
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36
|
+
}
|
|
37
|
+
function appendImport(source, line) {
|
|
38
|
+
if (source.includes(line)) {
|
|
39
|
+
return { source, inserted: true };
|
|
40
|
+
}
|
|
41
|
+
const importRegex = /^import .+ from .+;?\s*$/gm;
|
|
42
|
+
let lastImportIndex = 0;
|
|
43
|
+
let match;
|
|
44
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
45
|
+
lastImportIndex = match.index + match[0].length;
|
|
46
|
+
}
|
|
47
|
+
if (lastImportIndex <= 0) {
|
|
48
|
+
return { source, inserted: false };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
source: source.slice(0, lastImportIndex) + `
|
|
52
|
+
${line}` + source.slice(lastImportIndex),
|
|
53
|
+
inserted: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
var init_code_inject = __esm({
|
|
57
|
+
"src/utils/code-inject.ts"() {
|
|
58
|
+
"use strict";
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// src/handlers/database.handler.ts
|
|
63
|
+
var database_handler_exports = {};
|
|
64
|
+
__export(database_handler_exports, {
|
|
65
|
+
DEFAULT_DATABASE_URLS: () => DEFAULT_DATABASE_URLS,
|
|
66
|
+
backupDatabaseFiles: () => backupDatabaseFiles,
|
|
67
|
+
databaseLabel: () => databaseLabel,
|
|
68
|
+
detectInstalledDatabaseDialect: () => detectInstalledDatabaseDialect,
|
|
69
|
+
ensureSchemaExport: () => ensureSchemaExport,
|
|
70
|
+
getDatabaseSetupHint: () => getDatabaseSetupHint,
|
|
71
|
+
isDatabaseModule: () => isDatabaseModule,
|
|
72
|
+
parseDatabaseDialect: () => parseDatabaseDialect,
|
|
73
|
+
printDatabaseHints: () => printDatabaseHints,
|
|
74
|
+
promptDatabaseConfig: () => promptDatabaseConfig,
|
|
75
|
+
validateDatabaseUrl: () => validateDatabaseUrl
|
|
76
|
+
});
|
|
77
|
+
function parseDatabaseDialect(value) {
|
|
78
|
+
const normalized = value?.trim().toLowerCase();
|
|
79
|
+
if (!normalized) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
if (normalized === "pg" || normalized === "postgres" || normalized === "postgresql" || normalized === "database-pg") {
|
|
83
|
+
return "database-pg";
|
|
84
|
+
}
|
|
85
|
+
if (normalized === "mysql" || normalized === "database-mysql") {
|
|
86
|
+
return "database-mysql";
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function isDatabaseModule(moduleName) {
|
|
91
|
+
return moduleName === "database-pg" || moduleName === "database-mysql";
|
|
92
|
+
}
|
|
93
|
+
function validateDatabaseUrl(rawUrl, moduleName) {
|
|
94
|
+
const dbUrl = rawUrl.trim();
|
|
95
|
+
if (!dbUrl) {
|
|
96
|
+
throw new Error("Database URL cannot be empty.");
|
|
97
|
+
}
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = new URL(dbUrl);
|
|
101
|
+
} catch {
|
|
102
|
+
throw new Error(`Invalid database URL: '${dbUrl}'.`);
|
|
103
|
+
}
|
|
104
|
+
const protocol = parsed.protocol.toLowerCase();
|
|
105
|
+
if (moduleName === "database-pg" && protocol !== "postgresql:" && protocol !== "postgres:") {
|
|
106
|
+
throw new Error("PostgreSQL URL must start with postgres:// or postgresql://");
|
|
107
|
+
}
|
|
108
|
+
if (moduleName === "database-mysql" && protocol !== "mysql:") {
|
|
109
|
+
throw new Error("MySQL URL must start with mysql://");
|
|
110
|
+
}
|
|
111
|
+
return dbUrl;
|
|
112
|
+
}
|
|
113
|
+
async function detectInstalledDatabaseDialect(projectRoot, srcDir) {
|
|
114
|
+
const dbIndexPath = import_path7.default.join(projectRoot, srcDir, "db", "index.ts");
|
|
115
|
+
if (!import_fs_extra6.default.existsSync(dbIndexPath)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const content = await import_fs_extra6.default.readFile(dbIndexPath, "utf-8");
|
|
119
|
+
if (content.includes("drizzle-orm/node-postgres") || content.includes(`from "pg"`)) {
|
|
120
|
+
return "database-pg";
|
|
121
|
+
}
|
|
122
|
+
if (content.includes("drizzle-orm/mysql2") || content.includes(`from "mysql2`)) {
|
|
123
|
+
return "database-mysql";
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
async function backupDatabaseFiles(projectRoot, srcDir) {
|
|
128
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
129
|
+
const backupRoot = import_path7.default.join(projectRoot, ".zuro", "backups", `database-${timestamp}`);
|
|
130
|
+
const candidates = [
|
|
131
|
+
import_path7.default.join(projectRoot, srcDir, "db", "index.ts"),
|
|
132
|
+
import_path7.default.join(projectRoot, "drizzle.config.ts")
|
|
133
|
+
];
|
|
134
|
+
let copied = false;
|
|
135
|
+
for (const filePath of candidates) {
|
|
136
|
+
if (!import_fs_extra6.default.existsSync(filePath)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const relativePath = import_path7.default.relative(projectRoot, filePath);
|
|
140
|
+
const backupPath = import_path7.default.join(backupRoot, relativePath);
|
|
141
|
+
await import_fs_extra6.default.ensureDir(import_path7.default.dirname(backupPath));
|
|
142
|
+
await import_fs_extra6.default.copyFile(filePath, backupPath);
|
|
143
|
+
copied = true;
|
|
144
|
+
}
|
|
145
|
+
return copied ? backupRoot : null;
|
|
146
|
+
}
|
|
147
|
+
function databaseLabel(moduleName) {
|
|
148
|
+
return moduleName === "database-pg" ? "PostgreSQL" : "MySQL";
|
|
149
|
+
}
|
|
150
|
+
function getDatabaseSetupHint(moduleName, dbUrl) {
|
|
151
|
+
try {
|
|
152
|
+
const parsed = new URL(dbUrl);
|
|
153
|
+
const dbName = parsed.pathname.replace(/^\/+/, "") || "mydb";
|
|
154
|
+
if (moduleName === "database-pg") {
|
|
155
|
+
return `createdb ${dbName}`;
|
|
156
|
+
}
|
|
157
|
+
return `mysql -e "CREATE DATABASE IF NOT EXISTS ${dbName};"`;
|
|
158
|
+
} catch {
|
|
159
|
+
return moduleName === "database-pg" ? "createdb <database_name>" : `mysql -e "CREATE DATABASE IF NOT EXISTS <database_name>;"`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function ensureSchemaExport(projectRoot, srcDir, schemaFileName) {
|
|
163
|
+
const schemaIndexPath = import_path7.default.join(projectRoot, srcDir, "db", "schema", "index.ts");
|
|
164
|
+
if (!await import_fs_extra6.default.pathExists(schemaIndexPath)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const exportLine = `export * from "./${schemaFileName}";`;
|
|
168
|
+
const content = await import_fs_extra6.default.readFile(schemaIndexPath, "utf-8");
|
|
169
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
170
|
+
const exportPattern = new RegExp(
|
|
171
|
+
`^\\s*export\\s*\\*\\s*from\\s*["']\\./${escapeRegex(schemaFileName)}["'];?\\s*$`,
|
|
172
|
+
"m"
|
|
173
|
+
);
|
|
174
|
+
if (exportPattern.test(normalized)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
let next = normalized.replace(/^\s*export\s*\{\s*\};?\s*$/m, "").trimEnd();
|
|
178
|
+
if (next.length > 0) {
|
|
179
|
+
next += "\n\n";
|
|
180
|
+
}
|
|
181
|
+
next += `${exportLine}
|
|
182
|
+
`;
|
|
183
|
+
await import_fs_extra6.default.writeFile(schemaIndexPath, next);
|
|
184
|
+
}
|
|
185
|
+
async function promptDatabaseConfig(initialModuleName, projectRoot, srcDir) {
|
|
186
|
+
let resolvedModuleName;
|
|
187
|
+
if (initialModuleName === "database") {
|
|
188
|
+
const variantResponse = await (0, import_prompts2.default)({
|
|
189
|
+
type: "select",
|
|
190
|
+
name: "variant",
|
|
191
|
+
message: "Which database dialect?",
|
|
192
|
+
choices: [
|
|
193
|
+
{ title: "PostgreSQL", value: "database-pg" },
|
|
194
|
+
{ title: "MySQL", value: "database-mysql" }
|
|
195
|
+
]
|
|
196
|
+
});
|
|
197
|
+
if (!variantResponse.variant) {
|
|
198
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
resolvedModuleName = variantResponse.variant;
|
|
202
|
+
} else {
|
|
203
|
+
resolvedModuleName = initialModuleName;
|
|
204
|
+
}
|
|
205
|
+
let databaseBackupPath = null;
|
|
206
|
+
const installedDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);
|
|
207
|
+
if (installedDialect && installedDialect !== resolvedModuleName) {
|
|
208
|
+
console.log(
|
|
209
|
+
import_chalk4.default.yellow(
|
|
210
|
+
`
|
|
211
|
+
\u26A0 Existing database setup detected: ${databaseLabel(installedDialect)}.`
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
console.log(
|
|
215
|
+
import_chalk4.default.yellow(
|
|
216
|
+
` Switching to ${databaseLabel(resolvedModuleName)} will overwrite db files and drizzle config.
|
|
217
|
+
`
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
const switchResponse = await (0, import_prompts2.default)({
|
|
221
|
+
type: "confirm",
|
|
222
|
+
name: "proceed",
|
|
223
|
+
message: "Continue and switch database dialect?",
|
|
224
|
+
initial: false
|
|
225
|
+
});
|
|
226
|
+
if (!switchResponse.proceed) {
|
|
227
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
databaseBackupPath = await backupDatabaseFiles(projectRoot, srcDir);
|
|
231
|
+
}
|
|
232
|
+
const defaultUrl = DEFAULT_DATABASE_URLS[resolvedModuleName];
|
|
233
|
+
console.log(import_chalk4.default.dim(` Tip: Leave blank to use ${defaultUrl}
|
|
234
|
+
`));
|
|
235
|
+
const response = await (0, import_prompts2.default)({
|
|
236
|
+
type: "text",
|
|
237
|
+
name: "dbUrl",
|
|
238
|
+
message: "Database URL",
|
|
239
|
+
initial: ""
|
|
240
|
+
});
|
|
241
|
+
if (response.dbUrl === void 0) {
|
|
242
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const enteredUrl = response.dbUrl?.trim() || "";
|
|
246
|
+
const usedDefaultDbUrl = enteredUrl.length === 0;
|
|
247
|
+
const customDbUrl = validateDatabaseUrl(enteredUrl || defaultUrl, resolvedModuleName);
|
|
248
|
+
return { resolvedModuleName, customDbUrl, usedDefaultDbUrl, databaseBackupPath };
|
|
249
|
+
}
|
|
250
|
+
function printDatabaseHints(moduleName, customDbUrl, usedDefaultDbUrl, databaseBackupPath) {
|
|
251
|
+
if (databaseBackupPath) {
|
|
252
|
+
console.log(import_chalk4.default.blue(`\u2139 Backup created at: ${databaseBackupPath}
|
|
253
|
+
`));
|
|
254
|
+
}
|
|
255
|
+
if (usedDefaultDbUrl) {
|
|
256
|
+
console.log(import_chalk4.default.yellow("\u2139 Review DATABASE_URL in .env if your local DB config differs."));
|
|
257
|
+
}
|
|
258
|
+
const setupHint = getDatabaseSetupHint(
|
|
259
|
+
moduleName,
|
|
260
|
+
customDbUrl || DEFAULT_DATABASE_URLS[moduleName]
|
|
261
|
+
);
|
|
262
|
+
console.log(import_chalk4.default.yellow(`\u2139 Ensure DB exists: ${setupHint}`));
|
|
263
|
+
console.log(import_chalk4.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
264
|
+
}
|
|
265
|
+
var import_path7, import_fs_extra6, import_prompts2, import_chalk4, DEFAULT_DATABASE_URLS;
|
|
266
|
+
var init_database_handler = __esm({
|
|
267
|
+
"src/handlers/database.handler.ts"() {
|
|
268
|
+
"use strict";
|
|
269
|
+
import_path7 = __toESM(require("path"));
|
|
270
|
+
import_fs_extra6 = __toESM(require("fs-extra"));
|
|
271
|
+
import_prompts2 = __toESM(require("prompts"));
|
|
272
|
+
import_chalk4 = __toESM(require("chalk"));
|
|
273
|
+
init_code_inject();
|
|
274
|
+
DEFAULT_DATABASE_URLS = {
|
|
275
|
+
"database-pg": "postgresql://postgres@localhost:5432/mydb",
|
|
276
|
+
"database-mysql": "mysql://root@localhost:3306/mydb"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// src/handlers/docs.handler.ts
|
|
282
|
+
var docs_handler_exports = {};
|
|
283
|
+
__export(docs_handler_exports, {
|
|
284
|
+
injectDocsRoutes: () => injectDocsRoutes,
|
|
285
|
+
isDocsModuleInstalled: () => isDocsModuleInstalled,
|
|
286
|
+
printDocsHints: () => printDocsHints
|
|
287
|
+
});
|
|
288
|
+
async function isDocsModuleInstalled(projectRoot, srcDir) {
|
|
289
|
+
return await import_fs_extra7.default.pathExists(import_path8.default.join(projectRoot, srcDir, "lib", "openapi.ts"));
|
|
290
|
+
}
|
|
291
|
+
async function injectDocsRoutes(projectRoot, srcDir) {
|
|
292
|
+
const routeIndexPath = import_path8.default.join(projectRoot, srcDir, "routes", "index.ts");
|
|
293
|
+
const routeImport = `import docsRoutes from "./docs.routes";`;
|
|
294
|
+
const routeMountPattern = /rootRouter\.use\(\s*["']\/docs["']\s*,\s*docsRoutes\s*\)/;
|
|
295
|
+
if (await import_fs_extra7.default.pathExists(routeIndexPath)) {
|
|
296
|
+
let routeContent = await import_fs_extra7.default.readFile(routeIndexPath, "utf-8");
|
|
297
|
+
let routeModified = false;
|
|
298
|
+
const importResult = appendImport(routeContent, routeImport);
|
|
299
|
+
if (!importResult.inserted) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (importResult.source !== routeContent) {
|
|
303
|
+
routeContent = importResult.source;
|
|
304
|
+
routeModified = true;
|
|
305
|
+
}
|
|
306
|
+
if (!routeMountPattern.test(routeContent)) {
|
|
307
|
+
const routeSetup = `
|
|
308
|
+
// API docs
|
|
309
|
+
rootRouter.use("/docs", docsRoutes);
|
|
310
|
+
`;
|
|
311
|
+
const exportMatch = routeContent.match(/export default rootRouter;?\s*$/m);
|
|
312
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
routeContent = routeContent.slice(0, exportMatch.index) + routeSetup + "\n" + routeContent.slice(exportMatch.index);
|
|
316
|
+
routeModified = true;
|
|
317
|
+
}
|
|
318
|
+
if (routeModified) {
|
|
319
|
+
await import_fs_extra7.default.writeFile(routeIndexPath, routeContent);
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
const appPath = import_path8.default.join(projectRoot, srcDir, "app.ts");
|
|
324
|
+
if (!await import_fs_extra7.default.pathExists(appPath)) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
let appContent = await import_fs_extra7.default.readFile(appPath, "utf-8");
|
|
328
|
+
let appModified = false;
|
|
329
|
+
const appImportResult = appendImport(appContent, `import docsRoutes from "./routes/docs.routes";`);
|
|
330
|
+
if (!appImportResult.inserted) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
if (appImportResult.source !== appContent) {
|
|
334
|
+
appContent = appImportResult.source;
|
|
335
|
+
appModified = true;
|
|
336
|
+
}
|
|
337
|
+
const hasMount = /app\.use\(\s*["']\/api\/docs["']\s*,\s*docsRoutes\s*\)/.test(appContent);
|
|
338
|
+
if (!hasMount) {
|
|
339
|
+
const setup = `
|
|
340
|
+
// API docs
|
|
341
|
+
app.use("/api/docs", docsRoutes);
|
|
342
|
+
`;
|
|
343
|
+
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
344
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
appContent = appContent.slice(0, exportMatch.index) + setup + "\n" + appContent.slice(exportMatch.index);
|
|
348
|
+
appModified = true;
|
|
349
|
+
}
|
|
350
|
+
if (appModified) {
|
|
351
|
+
await import_fs_extra7.default.writeFile(appPath, appContent);
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
function printDocsHints() {
|
|
356
|
+
const chalk8 = require("chalk");
|
|
357
|
+
console.log(chalk8.yellow("\u2139 API docs available at: /api/docs"));
|
|
358
|
+
console.log(chalk8.yellow("\u2139 OpenAPI spec available at: /api/docs/openapi.json"));
|
|
359
|
+
}
|
|
360
|
+
var import_path8, import_fs_extra7;
|
|
361
|
+
var init_docs_handler = __esm({
|
|
362
|
+
"src/handlers/docs.handler.ts"() {
|
|
363
|
+
"use strict";
|
|
364
|
+
import_path8 = __toESM(require("path"));
|
|
365
|
+
import_fs_extra7 = __toESM(require("fs-extra"));
|
|
366
|
+
init_code_inject();
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
26
370
|
// src/index.ts
|
|
27
371
|
var import_commander = require("commander");
|
|
28
372
|
|
|
@@ -32,6 +376,7 @@ var import_chalk2 = __toESM(require("chalk"));
|
|
|
32
376
|
var import_fs_extra4 = __toESM(require("fs-extra"));
|
|
33
377
|
var import_path4 = __toESM(require("path"));
|
|
34
378
|
var import_prompts = __toESM(require("prompts"));
|
|
379
|
+
var import_child_process = require("child_process");
|
|
35
380
|
|
|
36
381
|
// src/utils/registry.ts
|
|
37
382
|
var import_node_crypto = require("crypto");
|
|
@@ -383,6 +728,16 @@ var ENV_CONFIGS = {
|
|
|
383
728
|
{ name: "RESEND_API_KEY", schema: "z.string().min(1)" },
|
|
384
729
|
{ name: "MAIL_FROM", schema: "z.string().min(1)" }
|
|
385
730
|
]
|
|
731
|
+
},
|
|
732
|
+
"rate-limiter": {
|
|
733
|
+
envVars: {
|
|
734
|
+
RATE_LIMIT_WINDOW_MS: "900000",
|
|
735
|
+
RATE_LIMIT_MAX: "100"
|
|
736
|
+
},
|
|
737
|
+
schemaFields: [
|
|
738
|
+
{ name: "RATE_LIMIT_WINDOW_MS", schema: "z.coerce.number().default(900000)" },
|
|
739
|
+
{ name: "RATE_LIMIT_MAX", schema: "z.coerce.number().default(100)" }
|
|
740
|
+
]
|
|
386
741
|
}
|
|
387
742
|
};
|
|
388
743
|
|
|
@@ -496,6 +851,62 @@ bun.lockb
|
|
|
496
851
|
await import_fs_extra4.default.writeFile(prettierIgnorePath, ignoreContent);
|
|
497
852
|
}
|
|
498
853
|
}
|
|
854
|
+
async function setupGitignore(targetDir) {
|
|
855
|
+
const gitignorePath = import_path4.default.join(targetDir, ".gitignore");
|
|
856
|
+
if (await import_fs_extra4.default.pathExists(gitignorePath)) {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const gitignoreContent = `# dependencies
|
|
860
|
+
node_modules
|
|
861
|
+
|
|
862
|
+
# build output
|
|
863
|
+
dist
|
|
864
|
+
build
|
|
865
|
+
|
|
866
|
+
# environment variables
|
|
867
|
+
.env
|
|
868
|
+
.env.*
|
|
869
|
+
!.env.example
|
|
870
|
+
|
|
871
|
+
# logs
|
|
872
|
+
*.log
|
|
873
|
+
npm-debug.log*
|
|
874
|
+
pnpm-debug.log*
|
|
875
|
+
|
|
876
|
+
# coverage
|
|
877
|
+
coverage
|
|
878
|
+
|
|
879
|
+
# OS files
|
|
880
|
+
.DS_Store
|
|
881
|
+
Thumbs.db
|
|
882
|
+
|
|
883
|
+
# IDE
|
|
884
|
+
.vscode
|
|
885
|
+
.idea
|
|
886
|
+
*.swp
|
|
887
|
+
*.swo
|
|
888
|
+
`;
|
|
889
|
+
await import_fs_extra4.default.writeFile(gitignorePath, gitignoreContent);
|
|
890
|
+
}
|
|
891
|
+
function tryGitInit(targetDir) {
|
|
892
|
+
try {
|
|
893
|
+
(0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", {
|
|
894
|
+
cwd: targetDir,
|
|
895
|
+
stdio: "ignore"
|
|
896
|
+
});
|
|
897
|
+
return;
|
|
898
|
+
} catch {
|
|
899
|
+
}
|
|
900
|
+
try {
|
|
901
|
+
(0, import_child_process.execSync)("git init", { cwd: targetDir, stdio: "ignore" });
|
|
902
|
+
(0, import_child_process.execSync)("git add -A", { cwd: targetDir, stdio: "ignore" });
|
|
903
|
+
(0, import_child_process.execSync)('git commit -m "Initial commit from zuro-cli"', {
|
|
904
|
+
cwd: targetDir,
|
|
905
|
+
stdio: "ignore"
|
|
906
|
+
});
|
|
907
|
+
} catch {
|
|
908
|
+
}
|
|
909
|
+
}
|
|
499
910
|
async function init() {
|
|
500
911
|
const cwd = process.cwd();
|
|
501
912
|
const isExistingProject = await import_fs_extra4.default.pathExists(import_path4.default.join(cwd, "package.json"));
|
|
@@ -647,8 +1058,16 @@ async function init() {
|
|
|
647
1058
|
currentStep = "prettier setup";
|
|
648
1059
|
await setupPrettier(targetDir);
|
|
649
1060
|
}
|
|
1061
|
+
currentStep = "gitignore setup";
|
|
1062
|
+
spinner.text = "Setting up .gitignore...";
|
|
1063
|
+
await setupGitignore(targetDir);
|
|
650
1064
|
currentStep = "config write";
|
|
651
1065
|
await writeZuroConfig(targetDir, zuroConfig);
|
|
1066
|
+
if (!isExistingProject) {
|
|
1067
|
+
currentStep = "git init";
|
|
1068
|
+
spinner.text = "Initializing git repository...";
|
|
1069
|
+
tryGitInit(targetDir);
|
|
1070
|
+
}
|
|
652
1071
|
spinner.succeed(import_chalk2.default.green("Project initialized successfully!"));
|
|
653
1072
|
console.log(`
|
|
654
1073
|
${import_chalk2.default.bold("Next steps:")}`);
|
|
@@ -672,10 +1091,9 @@ ${import_chalk2.default.bold("Retry:")}`);
|
|
|
672
1091
|
}
|
|
673
1092
|
|
|
674
1093
|
// src/commands/add.ts
|
|
675
|
-
var import_prompts2 = __toESM(require("prompts"));
|
|
676
1094
|
var import_ora2 = __toESM(require("ora"));
|
|
677
|
-
var
|
|
678
|
-
var
|
|
1095
|
+
var import_path12 = __toESM(require("path"));
|
|
1096
|
+
var import_fs_extra11 = __toESM(require("fs-extra"));
|
|
679
1097
|
var import_node_crypto2 = require("crypto");
|
|
680
1098
|
|
|
681
1099
|
// src/utils/dependency.ts
|
|
@@ -690,7 +1108,9 @@ var BLOCK_SIGNATURES = {
|
|
|
690
1108
|
"error-handler": "lib/errors.ts",
|
|
691
1109
|
logger: "lib/logger.ts",
|
|
692
1110
|
auth: "lib/auth.ts",
|
|
693
|
-
mailer: "lib/mailer.ts"
|
|
1111
|
+
mailer: "lib/mailer.ts",
|
|
1112
|
+
docs: "lib/openapi.ts",
|
|
1113
|
+
"rate-limiter": "middleware/rate-limiter.ts"
|
|
694
1114
|
};
|
|
695
1115
|
var resolveDependencies = async (moduleDependencies, cwd) => {
|
|
696
1116
|
if (!moduleDependencies || moduleDependencies.length === 0) {
|
|
@@ -721,12 +1141,8 @@ var resolveDependencies = async (moduleDependencies, cwd) => {
|
|
|
721
1141
|
}
|
|
722
1142
|
};
|
|
723
1143
|
|
|
724
|
-
// src/
|
|
725
|
-
var
|
|
726
|
-
var DEFAULT_DATABASE_URLS = {
|
|
727
|
-
"database-pg": "postgresql://postgres@localhost:5432/mydb",
|
|
728
|
-
"database-mysql": "mysql://root@localhost:3306/mydb"
|
|
729
|
-
};
|
|
1144
|
+
// src/utils/paths.ts
|
|
1145
|
+
var import_path6 = __toESM(require("path"));
|
|
730
1146
|
function resolveSafeTargetPath2(projectRoot, srcDir, file) {
|
|
731
1147
|
const targetPath = import_path6.default.resolve(projectRoot, srcDir, file.target);
|
|
732
1148
|
const normalizedRoot = import_path6.default.resolve(projectRoot);
|
|
@@ -735,155 +1151,380 @@ function resolveSafeTargetPath2(projectRoot, srcDir, file) {
|
|
|
735
1151
|
}
|
|
736
1152
|
return targetPath;
|
|
737
1153
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1154
|
+
|
|
1155
|
+
// src/commands/add.ts
|
|
1156
|
+
init_code_inject();
|
|
1157
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
1158
|
+
init_database_handler();
|
|
1159
|
+
|
|
1160
|
+
// src/handlers/auth.handler.ts
|
|
1161
|
+
var import_path9 = __toESM(require("path"));
|
|
1162
|
+
var import_fs_extra8 = __toESM(require("fs-extra"));
|
|
1163
|
+
var import_prompts3 = __toESM(require("prompts"));
|
|
1164
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1165
|
+
init_code_inject();
|
|
1166
|
+
async function isAuthModuleInstalled(projectRoot, srcDir) {
|
|
1167
|
+
return await import_fs_extra8.default.pathExists(import_path9.default.join(projectRoot, srcDir, "lib", "auth.ts"));
|
|
749
1168
|
}
|
|
750
|
-
function
|
|
751
|
-
const
|
|
752
|
-
if (!
|
|
753
|
-
return
|
|
1169
|
+
async function injectAuthRoutes(projectRoot, srcDir) {
|
|
1170
|
+
const appPath = import_path9.default.join(projectRoot, srcDir, "app.ts");
|
|
1171
|
+
if (!await import_fs_extra8.default.pathExists(appPath)) {
|
|
1172
|
+
return false;
|
|
754
1173
|
}
|
|
755
|
-
|
|
756
|
-
|
|
1174
|
+
let appContent = await import_fs_extra8.default.readFile(appPath, "utf-8");
|
|
1175
|
+
const authHandlerImport = `import { toNodeHandler } from "better-auth/node";`;
|
|
1176
|
+
const authImport = `import { auth } from "./lib/auth";`;
|
|
1177
|
+
const routeIndexUserImport = `import userRoutes from "./user.routes";`;
|
|
1178
|
+
const appUserImport = `import userRoutes from "./routes/user.routes";`;
|
|
1179
|
+
let appModified = false;
|
|
1180
|
+
for (const importLine of [authHandlerImport, authImport]) {
|
|
1181
|
+
const next = appendImport(appContent, importLine);
|
|
1182
|
+
if (!next.inserted) {
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
if (next.source !== appContent) {
|
|
1186
|
+
appContent = next.source;
|
|
1187
|
+
appModified = true;
|
|
1188
|
+
}
|
|
757
1189
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1190
|
+
const hasAuthMount = /toNodeHandler\(\s*auth\s*\)/.test(appContent) && /\/api\/auth/.test(appContent);
|
|
1191
|
+
if (!hasAuthMount) {
|
|
1192
|
+
const authMountLine = "app.all(/^\\/api\\/auth(?:\\/.*)?$/, toNodeHandler(auth));\n";
|
|
1193
|
+
const jsonIndex = appContent.search(/^\s*app\.use\(\s*express\.json\(\)\s*\);\s*$/m);
|
|
1194
|
+
let insertionIndex = jsonIndex;
|
|
1195
|
+
if (insertionIndex < 0) {
|
|
1196
|
+
const healthIndex = appContent.search(/^\s*app\.get\(\s*["']\/health["']\s*,/m);
|
|
1197
|
+
insertionIndex = healthIndex;
|
|
1198
|
+
}
|
|
1199
|
+
if (insertionIndex < 0) {
|
|
1200
|
+
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
1201
|
+
insertionIndex = exportMatch?.index ?? -1;
|
|
1202
|
+
}
|
|
1203
|
+
if (insertionIndex < 0) {
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
appContent = appContent.slice(0, insertionIndex) + authMountLine + appContent.slice(insertionIndex);
|
|
1207
|
+
appModified = true;
|
|
776
1208
|
}
|
|
777
|
-
const
|
|
778
|
-
if (
|
|
779
|
-
|
|
1209
|
+
const routeIndexPath = import_path9.default.join(projectRoot, srcDir, "routes", "index.ts");
|
|
1210
|
+
if (await import_fs_extra8.default.pathExists(routeIndexPath)) {
|
|
1211
|
+
let routeContent = await import_fs_extra8.default.readFile(routeIndexPath, "utf-8");
|
|
1212
|
+
let routeModified = false;
|
|
1213
|
+
const userImportResult = appendImport(routeContent, routeIndexUserImport);
|
|
1214
|
+
if (!userImportResult.inserted) {
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
if (userImportResult.source !== routeContent) {
|
|
1218
|
+
routeContent = userImportResult.source;
|
|
1219
|
+
routeModified = true;
|
|
1220
|
+
}
|
|
1221
|
+
const hasUserRoute = /rootRouter\.use\(\s*["']\/users["']\s*,\s*userRoutes\s*\)/.test(routeContent);
|
|
1222
|
+
if (!hasUserRoute) {
|
|
1223
|
+
const routeSetup = `
|
|
1224
|
+
// User routes
|
|
1225
|
+
rootRouter.use("/users", userRoutes);
|
|
1226
|
+
`;
|
|
1227
|
+
const exportMatch = routeContent.match(/export default rootRouter;?\s*$/m);
|
|
1228
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
routeContent = routeContent.slice(0, exportMatch.index) + routeSetup + "\n" + routeContent.slice(exportMatch.index);
|
|
1232
|
+
routeModified = true;
|
|
1233
|
+
}
|
|
1234
|
+
if (routeModified) {
|
|
1235
|
+
await import_fs_extra8.default.writeFile(routeIndexPath, routeContent);
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
const hasUserRoute = /app\.use\(\s*["']\/api\/users["']\s*,\s*userRoutes\s*\)/.test(appContent);
|
|
1239
|
+
if (!hasUserRoute) {
|
|
1240
|
+
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
1241
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
const routeSetup = `
|
|
1245
|
+
// User routes
|
|
1246
|
+
app.use("/api/users", userRoutes);
|
|
1247
|
+
`;
|
|
1248
|
+
appContent = appContent.slice(0, exportMatch.index) + routeSetup + "\n" + appContent.slice(exportMatch.index);
|
|
1249
|
+
appModified = true;
|
|
1250
|
+
}
|
|
1251
|
+
const userImportResult = appendImport(appContent, appUserImport);
|
|
1252
|
+
if (!userImportResult.inserted) {
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
if (userImportResult.source !== appContent) {
|
|
1256
|
+
appContent = userImportResult.source;
|
|
1257
|
+
appModified = true;
|
|
1258
|
+
}
|
|
780
1259
|
}
|
|
781
|
-
if (
|
|
782
|
-
|
|
1260
|
+
if (appModified) {
|
|
1261
|
+
await import_fs_extra8.default.writeFile(appPath, appContent);
|
|
783
1262
|
}
|
|
784
|
-
return
|
|
1263
|
+
return true;
|
|
785
1264
|
}
|
|
786
|
-
async function
|
|
787
|
-
const
|
|
788
|
-
if (!
|
|
789
|
-
return
|
|
1265
|
+
async function injectAuthDocs(projectRoot, srcDir) {
|
|
1266
|
+
const openApiPath = import_path9.default.join(projectRoot, srcDir, "lib", "openapi.ts");
|
|
1267
|
+
if (!await import_fs_extra8.default.pathExists(openApiPath)) {
|
|
1268
|
+
return false;
|
|
790
1269
|
}
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
1270
|
+
const authMarker = "// ZURO_AUTH_DOCS";
|
|
1271
|
+
let content = await import_fs_extra8.default.readFile(openApiPath, "utf-8");
|
|
1272
|
+
if (content.includes(authMarker)) {
|
|
1273
|
+
return true;
|
|
794
1274
|
}
|
|
795
|
-
|
|
796
|
-
|
|
1275
|
+
const moduleDocsEndMarker = "// ZURO_DOCS_MODULES_END";
|
|
1276
|
+
if (!content.includes(moduleDocsEndMarker)) {
|
|
1277
|
+
return false;
|
|
797
1278
|
}
|
|
798
|
-
|
|
1279
|
+
const authBlock = `
|
|
1280
|
+
const authSignUpSchema = z.object({
|
|
1281
|
+
email: z.string().email().openapi({ example: "dev@company.com" }),
|
|
1282
|
+
password: z.string().min(8).openapi({ example: "strong-password" }),
|
|
1283
|
+
name: z.string().min(1).optional().openapi({ example: "Dev User" }),
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
const authSignInSchema = z.object({
|
|
1287
|
+
email: z.string().email().openapi({ example: "dev@company.com" }),
|
|
1288
|
+
password: z.string().min(8).openapi({ example: "strong-password" }),
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
const authUserSchema = z.object({
|
|
1292
|
+
id: z.string().openapi({ example: "user_123" }),
|
|
1293
|
+
email: z.string().email().openapi({ example: "dev@company.com" }),
|
|
1294
|
+
name: z.string().nullable().openapi({ example: "Dev User" }),
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
${authMarker}
|
|
1298
|
+
registry.registerPath({
|
|
1299
|
+
method: "post",
|
|
1300
|
+
path: "/api/auth/sign-up/email",
|
|
1301
|
+
tags: ["Auth"],
|
|
1302
|
+
summary: "Register using email and password",
|
|
1303
|
+
request: {
|
|
1304
|
+
body: {
|
|
1305
|
+
content: {
|
|
1306
|
+
"application/json": {
|
|
1307
|
+
schema: authSignUpSchema,
|
|
1308
|
+
},
|
|
1309
|
+
},
|
|
1310
|
+
},
|
|
1311
|
+
},
|
|
1312
|
+
responses: {
|
|
1313
|
+
200: { description: "Registration successful" },
|
|
1314
|
+
},
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
registry.registerPath({
|
|
1318
|
+
method: "post",
|
|
1319
|
+
path: "/api/auth/sign-in/email",
|
|
1320
|
+
tags: ["Auth"],
|
|
1321
|
+
summary: "Sign in using email and password",
|
|
1322
|
+
request: {
|
|
1323
|
+
body: {
|
|
1324
|
+
content: {
|
|
1325
|
+
"application/json": {
|
|
1326
|
+
schema: authSignInSchema,
|
|
1327
|
+
},
|
|
1328
|
+
},
|
|
1329
|
+
},
|
|
1330
|
+
},
|
|
1331
|
+
responses: {
|
|
1332
|
+
200: { description: "Sign in successful" },
|
|
1333
|
+
401: { description: "Invalid credentials" },
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
registry.registerPath({
|
|
1338
|
+
method: "post",
|
|
1339
|
+
path: "/api/auth/sign-out",
|
|
1340
|
+
tags: ["Auth"],
|
|
1341
|
+
summary: "Sign out current user",
|
|
1342
|
+
responses: {
|
|
1343
|
+
200: { description: "Sign out successful" },
|
|
1344
|
+
},
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
registry.registerPath({
|
|
1348
|
+
method: "get",
|
|
1349
|
+
path: "/api/users/me",
|
|
1350
|
+
tags: ["Auth"],
|
|
1351
|
+
summary: "Get current authenticated user",
|
|
1352
|
+
security: [{ bearerAuth: [] }],
|
|
1353
|
+
responses: {
|
|
1354
|
+
200: {
|
|
1355
|
+
description: "Current user",
|
|
1356
|
+
content: {
|
|
1357
|
+
"application/json": {
|
|
1358
|
+
schema: z.object({ user: authUserSchema }),
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
401: { description: "Not authenticated" },
|
|
1363
|
+
},
|
|
1364
|
+
});
|
|
1365
|
+
`;
|
|
1366
|
+
content = content.replace(moduleDocsEndMarker, `${authBlock}
|
|
1367
|
+
${moduleDocsEndMarker}`);
|
|
1368
|
+
await import_fs_extra8.default.writeFile(openApiPath, content);
|
|
1369
|
+
return true;
|
|
799
1370
|
}
|
|
800
|
-
async function
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1371
|
+
async function promptAuthConfig(projectRoot, srcDir, options) {
|
|
1372
|
+
const { isDocsModuleInstalled: isDocsModuleInstalled2 } = await Promise.resolve().then(() => (init_docs_handler(), docs_handler_exports));
|
|
1373
|
+
const docsInstalled = await isDocsModuleInstalled2(projectRoot, srcDir);
|
|
1374
|
+
let shouldInstallDocsForAuth = false;
|
|
1375
|
+
if (!docsInstalled) {
|
|
1376
|
+
if (options.yes) {
|
|
1377
|
+
shouldInstallDocsForAuth = true;
|
|
1378
|
+
} else {
|
|
1379
|
+
const docsResponse = await (0, import_prompts3.default)({
|
|
1380
|
+
type: "confirm",
|
|
1381
|
+
name: "installDocs",
|
|
1382
|
+
message: "Install API docs module (Scalar + OpenAPI) too?",
|
|
1383
|
+
initial: true
|
|
1384
|
+
});
|
|
1385
|
+
if (docsResponse.installDocs === void 0) {
|
|
1386
|
+
console.log(import_chalk5.default.yellow("Operation cancelled."));
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
shouldInstallDocsForAuth = docsResponse.installDocs;
|
|
811
1390
|
}
|
|
812
|
-
const relativePath = import_path6.default.relative(projectRoot, filePath);
|
|
813
|
-
const backupPath = import_path6.default.join(backupRoot, relativePath);
|
|
814
|
-
await import_fs_extra6.default.ensureDir(import_path6.default.dirname(backupPath));
|
|
815
|
-
await import_fs_extra6.default.copyFile(filePath, backupPath);
|
|
816
|
-
copied = true;
|
|
817
1391
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
return moduleName === "database-pg" ? "PostgreSQL" : "MySQL";
|
|
1392
|
+
const { detectInstalledDatabaseDialect: detectInstalledDatabaseDialect2 } = await Promise.resolve().then(() => (init_database_handler(), database_handler_exports));
|
|
1393
|
+
const authDatabaseDialect = await detectInstalledDatabaseDialect2(projectRoot, srcDir);
|
|
1394
|
+
return { shouldInstallDocsForAuth, authDatabaseDialect };
|
|
822
1395
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
return `createdb ${dbName}`;
|
|
829
|
-
}
|
|
830
|
-
return `mysql -e "CREATE DATABASE IF NOT EXISTS ${dbName};"`;
|
|
831
|
-
} catch {
|
|
832
|
-
return moduleName === "database-pg" ? "createdb <database_name>" : `mysql -e "CREATE DATABASE IF NOT EXISTS <database_name>;"`;
|
|
1396
|
+
function printAuthHints(generatedAuthSecret) {
|
|
1397
|
+
if (generatedAuthSecret) {
|
|
1398
|
+
console.log(import_chalk5.default.yellow("\u2139 BETTER_AUTH_SECRET was generated automatically."));
|
|
1399
|
+
} else {
|
|
1400
|
+
console.log(import_chalk5.default.yellow("\u2139 Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env."));
|
|
833
1401
|
}
|
|
1402
|
+
console.log(import_chalk5.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
834
1403
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1404
|
+
|
|
1405
|
+
// src/handlers/mailer.handler.ts
|
|
1406
|
+
var import_prompts4 = __toESM(require("prompts"));
|
|
1407
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
1408
|
+
async function promptMailerConfig() {
|
|
1409
|
+
const providerResponse = await (0, import_prompts4.default)({
|
|
1410
|
+
type: "select",
|
|
1411
|
+
name: "provider",
|
|
1412
|
+
message: "Which email provider?",
|
|
1413
|
+
choices: [
|
|
1414
|
+
{ title: "SMTP (Nodemailer)", description: "Gmail, Mailtrap, any SMTP server", value: "smtp" },
|
|
1415
|
+
{ title: "Resend", description: "API-based, easiest setup", value: "resend" }
|
|
1416
|
+
]
|
|
1417
|
+
});
|
|
1418
|
+
if (providerResponse.provider === void 0) {
|
|
1419
|
+
console.log(import_chalk6.default.yellow("Operation cancelled."));
|
|
1420
|
+
return null;
|
|
838
1421
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1422
|
+
const mailerProvider = providerResponse.provider;
|
|
1423
|
+
let customSmtpVars;
|
|
1424
|
+
let usedDefaultSmtp = false;
|
|
1425
|
+
console.log(import_chalk6.default.dim(" Tip: Leave fields blank to use placeholder values and configure later\n"));
|
|
1426
|
+
if (mailerProvider === "smtp") {
|
|
1427
|
+
const smtpResponse = await (0, import_prompts4.default)([
|
|
1428
|
+
{
|
|
1429
|
+
type: "text",
|
|
1430
|
+
name: "host",
|
|
1431
|
+
message: "SMTP Host",
|
|
1432
|
+
initial: ""
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
type: "text",
|
|
1436
|
+
name: "port",
|
|
1437
|
+
message: "SMTP Port",
|
|
1438
|
+
initial: "587"
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
type: "text",
|
|
1442
|
+
name: "user",
|
|
1443
|
+
message: "SMTP User",
|
|
1444
|
+
initial: ""
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
type: "password",
|
|
1448
|
+
name: "pass",
|
|
1449
|
+
message: "SMTP Password"
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
type: "text",
|
|
1453
|
+
name: "from",
|
|
1454
|
+
message: "Mail From address",
|
|
1455
|
+
initial: ""
|
|
1456
|
+
}
|
|
1457
|
+
]);
|
|
1458
|
+
if (smtpResponse.host === void 0) {
|
|
1459
|
+
console.log(import_chalk6.default.yellow("Operation cancelled."));
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
const host = smtpResponse.host?.trim() || "";
|
|
1463
|
+
const user = smtpResponse.user?.trim() || "";
|
|
1464
|
+
const pass = smtpResponse.pass?.trim() || "";
|
|
1465
|
+
const from = smtpResponse.from?.trim() || "";
|
|
1466
|
+
const port = smtpResponse.port?.trim() || "587";
|
|
1467
|
+
usedDefaultSmtp = !host && !user;
|
|
1468
|
+
if (!usedDefaultSmtp) {
|
|
1469
|
+
customSmtpVars = {
|
|
1470
|
+
SMTP_HOST: host || "smtp.example.com",
|
|
1471
|
+
SMTP_PORT: port,
|
|
1472
|
+
SMTP_USER: user || "your-email@example.com",
|
|
1473
|
+
SMTP_PASS: pass || "your-password",
|
|
1474
|
+
MAIL_FROM: from || "noreply@example.com"
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
} else {
|
|
1478
|
+
const resendResponse = await (0, import_prompts4.default)([
|
|
1479
|
+
{
|
|
1480
|
+
type: "text",
|
|
1481
|
+
name: "apiKey",
|
|
1482
|
+
message: "Resend API Key",
|
|
1483
|
+
initial: ""
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
type: "text",
|
|
1487
|
+
name: "from",
|
|
1488
|
+
message: "Mail From address",
|
|
1489
|
+
initial: ""
|
|
1490
|
+
}
|
|
1491
|
+
]);
|
|
1492
|
+
if (resendResponse.apiKey === void 0) {
|
|
1493
|
+
console.log(import_chalk6.default.yellow("Operation cancelled."));
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
const apiKey = resendResponse.apiKey?.trim() || "";
|
|
1497
|
+
const from = resendResponse.from?.trim() || "";
|
|
1498
|
+
usedDefaultSmtp = !apiKey;
|
|
1499
|
+
if (!usedDefaultSmtp) {
|
|
1500
|
+
customSmtpVars = {
|
|
1501
|
+
RESEND_API_KEY: apiKey || "re_your_api_key",
|
|
1502
|
+
MAIL_FROM: from || "onboarding@resend.dev"
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
848
1505
|
}
|
|
849
|
-
|
|
850
|
-
const pattern = new RegExp(`^${escapeRegex(key)}=`, "m");
|
|
851
|
-
return pattern.test(content);
|
|
852
|
-
}
|
|
853
|
-
async function isLikelyEmptyDirectory(cwd) {
|
|
854
|
-
const entries = await import_fs_extra6.default.readdir(cwd);
|
|
855
|
-
const ignored = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
|
|
856
|
-
return entries.filter((entry) => !ignored.has(entry.toLowerCase())).length === 0;
|
|
1506
|
+
return { mailerProvider, customSmtpVars, usedDefaultSmtp };
|
|
857
1507
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const exportLine = `export * from "./${schemaFileName}";`;
|
|
864
|
-
const content = await import_fs_extra6.default.readFile(schemaIndexPath, "utf-8");
|
|
865
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
866
|
-
const exportPattern = new RegExp(
|
|
867
|
-
`^\\s*export\\s*\\*\\s*from\\s*["']\\./${escapeRegex(schemaFileName)}["'];?\\s*$`,
|
|
868
|
-
"m"
|
|
869
|
-
);
|
|
870
|
-
if (exportPattern.test(normalized)) {
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
let next = normalized.replace(/^\s*export\s*\{\s*\};?\s*$/m, "").trimEnd();
|
|
874
|
-
if (next.length > 0) {
|
|
875
|
-
next += "\n\n";
|
|
1508
|
+
function printMailerHints(usedDefaultSmtp) {
|
|
1509
|
+
if (usedDefaultSmtp) {
|
|
1510
|
+
console.log(import_chalk6.default.yellow("\u2139 Placeholder SMTP values added to .env \u2014 update them before sending emails."));
|
|
1511
|
+
} else {
|
|
1512
|
+
console.log(import_chalk6.default.yellow("\u2139 Review SMTP configuration in .env to ensure values are correct."));
|
|
876
1513
|
}
|
|
877
|
-
next += `${exportLine}
|
|
878
|
-
`;
|
|
879
|
-
await import_fs_extra6.default.writeFile(schemaIndexPath, next);
|
|
880
1514
|
}
|
|
1515
|
+
|
|
1516
|
+
// src/commands/add.ts
|
|
1517
|
+
init_docs_handler();
|
|
1518
|
+
|
|
1519
|
+
// src/handlers/error-handler.handler.ts
|
|
1520
|
+
var import_path10 = __toESM(require("path"));
|
|
1521
|
+
var import_fs_extra9 = __toESM(require("fs-extra"));
|
|
881
1522
|
async function injectErrorHandler(projectRoot, srcDir) {
|
|
882
|
-
const appPath =
|
|
883
|
-
if (!
|
|
1523
|
+
const appPath = import_path10.default.join(projectRoot, srcDir, "app.ts");
|
|
1524
|
+
if (!import_fs_extra9.default.existsSync(appPath)) {
|
|
884
1525
|
return false;
|
|
885
1526
|
}
|
|
886
|
-
let content = await
|
|
1527
|
+
let content = await import_fs_extra9.default.readFile(appPath, "utf-8");
|
|
887
1528
|
const errorImport = `import { errorHandler, notFoundHandler } from "./middleware/error-handler";`;
|
|
888
1529
|
const hasErrorImport = content.includes(errorImport);
|
|
889
1530
|
const hasNotFoundUse = /app\.use\(\s*notFoundHandler\s*\)/.test(content);
|
|
@@ -925,126 +1566,98 @@ ${setupLines.join("\n")}
|
|
|
925
1566
|
}
|
|
926
1567
|
}
|
|
927
1568
|
if (modified) {
|
|
928
|
-
await
|
|
1569
|
+
await import_fs_extra9.default.writeFile(appPath, content);
|
|
929
1570
|
}
|
|
930
1571
|
return importInserted && setupInserted;
|
|
931
1572
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1573
|
+
|
|
1574
|
+
// src/handlers/rate-limiter.handler.ts
|
|
1575
|
+
var import_path11 = __toESM(require("path"));
|
|
1576
|
+
var import_fs_extra10 = __toESM(require("fs-extra"));
|
|
1577
|
+
async function injectRateLimiter(projectRoot, srcDir) {
|
|
1578
|
+
const appPath = import_path11.default.join(projectRoot, srcDir, "app.ts");
|
|
1579
|
+
if (!import_fs_extra10.default.existsSync(appPath)) {
|
|
935
1580
|
return false;
|
|
936
1581
|
}
|
|
937
|
-
let
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
}
|
|
1582
|
+
let content = await import_fs_extra10.default.readFile(appPath, "utf-8");
|
|
1583
|
+
const rateLimiterImport = `import { rateLimiter } from "./middleware/rate-limiter";`;
|
|
1584
|
+
const hasImport = content.includes(rateLimiterImport);
|
|
1585
|
+
const hasUse = /app\.use\(\s*rateLimiter\s*\)/.test(content);
|
|
1586
|
+
if (hasImport && hasUse) {
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
let modified = false;
|
|
1590
|
+
if (!hasImport) {
|
|
947
1591
|
const importRegex = /^import .+ from .+;?\s*$/gm;
|
|
948
1592
|
let lastImportIndex = 0;
|
|
949
1593
|
let match;
|
|
950
|
-
while ((match = importRegex.exec(
|
|
1594
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
951
1595
|
lastImportIndex = match.index + match[0].length;
|
|
952
1596
|
}
|
|
953
|
-
if (lastImportIndex
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
source: source.slice(0, lastImportIndex) + `
|
|
958
|
-
${line}` + source.slice(lastImportIndex),
|
|
959
|
-
inserted: true
|
|
960
|
-
};
|
|
961
|
-
};
|
|
962
|
-
for (const importLine of [authHandlerImport, authImport]) {
|
|
963
|
-
const next = appendImport(appContent, importLine);
|
|
964
|
-
if (!next.inserted) {
|
|
965
|
-
return false;
|
|
966
|
-
}
|
|
967
|
-
if (next.source !== appContent) {
|
|
968
|
-
appContent = next.source;
|
|
969
|
-
appModified = true;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
const hasAuthMount = /toNodeHandler\(\s*auth\s*\)/.test(appContent) && /\/api\/auth/.test(appContent);
|
|
973
|
-
if (!hasAuthMount) {
|
|
974
|
-
const authMountLine = "app.all(/^\\/api\\/auth(?:\\/.*)?$/, toNodeHandler(auth));\n";
|
|
975
|
-
const jsonIndex = appContent.search(/^\s*app\.use\(\s*express\.json\(\)\s*\);\s*$/m);
|
|
976
|
-
let insertionIndex = jsonIndex;
|
|
977
|
-
if (insertionIndex < 0) {
|
|
978
|
-
const healthIndex = appContent.search(/^\s*app\.get\(\s*["']\/health["']\s*,/m);
|
|
979
|
-
insertionIndex = healthIndex;
|
|
980
|
-
}
|
|
981
|
-
if (insertionIndex < 0) {
|
|
982
|
-
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
983
|
-
insertionIndex = exportMatch?.index ?? -1;
|
|
984
|
-
}
|
|
985
|
-
if (insertionIndex < 0) {
|
|
986
|
-
return false;
|
|
1597
|
+
if (lastImportIndex > 0) {
|
|
1598
|
+
content = content.slice(0, lastImportIndex) + `
|
|
1599
|
+
${rateLimiterImport}` + content.slice(lastImportIndex);
|
|
1600
|
+
modified = true;
|
|
987
1601
|
}
|
|
988
|
-
appContent = appContent.slice(0, insertionIndex) + authMountLine + appContent.slice(insertionIndex);
|
|
989
|
-
appModified = true;
|
|
990
1602
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
// User routes
|
|
1007
|
-
rootRouter.use("/users", userRoutes);
|
|
1008
|
-
`;
|
|
1009
|
-
const exportMatch = routeContent.match(/export default rootRouter;?\s*$/m);
|
|
1010
|
-
if (!exportMatch || exportMatch.index === void 0) {
|
|
1011
|
-
return false;
|
|
1012
|
-
}
|
|
1013
|
-
routeContent = routeContent.slice(0, exportMatch.index) + routeSetup + "\n" + routeContent.slice(exportMatch.index);
|
|
1014
|
-
routeModified = true;
|
|
1015
|
-
}
|
|
1016
|
-
if (routeModified) {
|
|
1017
|
-
await import_fs_extra6.default.writeFile(routeIndexPath, routeContent);
|
|
1018
|
-
}
|
|
1019
|
-
} else {
|
|
1020
|
-
const hasUserRoute = /app\.use\(\s*["']\/api\/users["']\s*,\s*userRoutes\s*\)/.test(appContent);
|
|
1021
|
-
if (!hasUserRoute) {
|
|
1022
|
-
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
1023
|
-
if (!exportMatch || exportMatch.index === void 0) {
|
|
1024
|
-
return false;
|
|
1603
|
+
if (!hasUse) {
|
|
1604
|
+
const jsonMiddleware = /^(\s*app\.use\(\s*express\.json\(\)\s*\);?\s*)$/m;
|
|
1605
|
+
const jsonMatch = content.match(jsonMiddleware);
|
|
1606
|
+
if (jsonMatch && jsonMatch.index !== void 0) {
|
|
1607
|
+
const insertAt = jsonMatch.index + jsonMatch[0].length;
|
|
1608
|
+
content = content.slice(0, insertAt) + `
|
|
1609
|
+
app.use(rateLimiter);` + content.slice(insertAt);
|
|
1610
|
+
modified = true;
|
|
1611
|
+
} else {
|
|
1612
|
+
const routeMount = /^\s*app\.use\(\s*["']\/api["']/m;
|
|
1613
|
+
const routeMatch = content.match(routeMount);
|
|
1614
|
+
if (routeMatch && routeMatch.index !== void 0) {
|
|
1615
|
+
content = content.slice(0, routeMatch.index) + `app.use(rateLimiter);
|
|
1616
|
+
` + content.slice(routeMatch.index);
|
|
1617
|
+
modified = true;
|
|
1025
1618
|
}
|
|
1026
|
-
const routeSetup = `
|
|
1027
|
-
// User routes
|
|
1028
|
-
app.use("/api/users", userRoutes);
|
|
1029
|
-
`;
|
|
1030
|
-
appContent = appContent.slice(0, exportMatch.index) + routeSetup + "\n" + appContent.slice(exportMatch.index);
|
|
1031
|
-
appModified = true;
|
|
1032
|
-
}
|
|
1033
|
-
const userImportResult = appendImport(appContent, appUserImport);
|
|
1034
|
-
if (!userImportResult.inserted) {
|
|
1035
|
-
return false;
|
|
1036
|
-
}
|
|
1037
|
-
if (userImportResult.source !== appContent) {
|
|
1038
|
-
appContent = userImportResult.source;
|
|
1039
|
-
appModified = true;
|
|
1040
1619
|
}
|
|
1041
1620
|
}
|
|
1042
|
-
if (
|
|
1043
|
-
await
|
|
1621
|
+
if (modified) {
|
|
1622
|
+
await import_fs_extra10.default.writeFile(appPath, content);
|
|
1044
1623
|
}
|
|
1045
1624
|
return true;
|
|
1046
1625
|
}
|
|
1047
|
-
|
|
1626
|
+
|
|
1627
|
+
// src/commands/add.ts
|
|
1628
|
+
function resolvePackageManager(projectRoot) {
|
|
1629
|
+
if (import_fs_extra11.default.existsSync(import_path12.default.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
1630
|
+
return "pnpm";
|
|
1631
|
+
}
|
|
1632
|
+
if (import_fs_extra11.default.existsSync(import_path12.default.join(projectRoot, "bun.lockb")) || import_fs_extra11.default.existsSync(import_path12.default.join(projectRoot, "bun.lock"))) {
|
|
1633
|
+
return "bun";
|
|
1634
|
+
}
|
|
1635
|
+
if (import_fs_extra11.default.existsSync(import_path12.default.join(projectRoot, "yarn.lock"))) {
|
|
1636
|
+
return "yarn";
|
|
1637
|
+
}
|
|
1638
|
+
return "npm";
|
|
1639
|
+
}
|
|
1640
|
+
function getModuleDocsPath(moduleName) {
|
|
1641
|
+
if (isDatabaseModule(moduleName)) {
|
|
1642
|
+
return "database";
|
|
1643
|
+
}
|
|
1644
|
+
return moduleName;
|
|
1645
|
+
}
|
|
1646
|
+
async function hasEnvVariable(projectRoot, key) {
|
|
1647
|
+
const envPath = import_path12.default.join(projectRoot, ".env");
|
|
1648
|
+
if (!await import_fs_extra11.default.pathExists(envPath)) {
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
const content = await import_fs_extra11.default.readFile(envPath, "utf-8");
|
|
1652
|
+
const pattern = new RegExp(`^${escapeRegex(key)}=`, "m");
|
|
1653
|
+
return pattern.test(content);
|
|
1654
|
+
}
|
|
1655
|
+
async function isLikelyEmptyDirectory(cwd) {
|
|
1656
|
+
const entries = await import_fs_extra11.default.readdir(cwd);
|
|
1657
|
+
const ignored = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
|
|
1658
|
+
return entries.filter((entry) => !ignored.has(entry.toLowerCase())).length === 0;
|
|
1659
|
+
}
|
|
1660
|
+
var add = async (moduleName, options = {}) => {
|
|
1048
1661
|
const projectRoot = process.cwd();
|
|
1049
1662
|
const projectConfig = await readZuroConfig(projectRoot);
|
|
1050
1663
|
if (!projectConfig) {
|
|
@@ -1069,162 +1682,27 @@ var add = async (moduleName) => {
|
|
|
1069
1682
|
let customSmtpVars;
|
|
1070
1683
|
let usedDefaultSmtp = false;
|
|
1071
1684
|
let mailerProvider = "smtp";
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
]
|
|
1081
|
-
});
|
|
1082
|
-
if (!variantResponse.variant) {
|
|
1083
|
-
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
resolvedModuleName = variantResponse.variant;
|
|
1087
|
-
}
|
|
1088
|
-
if (isDatabaseModule(resolvedModuleName)) {
|
|
1089
|
-
const installedDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);
|
|
1090
|
-
if (installedDialect && installedDialect !== resolvedModuleName) {
|
|
1091
|
-
console.log(
|
|
1092
|
-
import_chalk4.default.yellow(
|
|
1093
|
-
`
|
|
1094
|
-
\u26A0 Existing database setup detected: ${databaseLabel(installedDialect)}.`
|
|
1095
|
-
)
|
|
1096
|
-
);
|
|
1097
|
-
console.log(
|
|
1098
|
-
import_chalk4.default.yellow(
|
|
1099
|
-
` Switching to ${databaseLabel(resolvedModuleName)} will overwrite db files and drizzle config.
|
|
1100
|
-
`
|
|
1101
|
-
)
|
|
1102
|
-
);
|
|
1103
|
-
const switchResponse = await (0, import_prompts2.default)({
|
|
1104
|
-
type: "confirm",
|
|
1105
|
-
name: "proceed",
|
|
1106
|
-
message: "Continue and switch database dialect?",
|
|
1107
|
-
initial: false
|
|
1108
|
-
});
|
|
1109
|
-
if (!switchResponse.proceed) {
|
|
1110
|
-
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
databaseBackupPath = await backupDatabaseFiles(projectRoot, srcDir);
|
|
1114
|
-
}
|
|
1115
|
-
const defaultUrl = DEFAULT_DATABASE_URLS[resolvedModuleName];
|
|
1116
|
-
console.log(import_chalk4.default.dim(` Tip: Leave blank to use ${defaultUrl}
|
|
1117
|
-
`));
|
|
1118
|
-
const response = await (0, import_prompts2.default)({
|
|
1119
|
-
type: "text",
|
|
1120
|
-
name: "dbUrl",
|
|
1121
|
-
message: "Database URL",
|
|
1122
|
-
initial: ""
|
|
1123
|
-
});
|
|
1124
|
-
if (response.dbUrl === void 0) {
|
|
1125
|
-
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1126
|
-
return;
|
|
1127
|
-
}
|
|
1128
|
-
const enteredUrl = response.dbUrl?.trim() || "";
|
|
1129
|
-
usedDefaultDbUrl = enteredUrl.length === 0;
|
|
1130
|
-
customDbUrl = validateDatabaseUrl(enteredUrl || defaultUrl, resolvedModuleName);
|
|
1685
|
+
let shouldInstallDocsForAuth = false;
|
|
1686
|
+
if (resolvedModuleName === "database" || isDatabaseModule(resolvedModuleName)) {
|
|
1687
|
+
const result = await promptDatabaseConfig(resolvedModuleName, projectRoot, srcDir);
|
|
1688
|
+
if (!result) return;
|
|
1689
|
+
resolvedModuleName = result.resolvedModuleName;
|
|
1690
|
+
customDbUrl = result.customDbUrl;
|
|
1691
|
+
usedDefaultDbUrl = result.usedDefaultDbUrl;
|
|
1692
|
+
databaseBackupPath = result.databaseBackupPath;
|
|
1131
1693
|
}
|
|
1132
1694
|
if (resolvedModuleName === "mailer") {
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
mailerProvider = providerResponse.provider;
|
|
1147
|
-
console.log(import_chalk4.default.dim(" Tip: Leave fields blank to use placeholder values and configure later\n"));
|
|
1148
|
-
if (mailerProvider === "smtp") {
|
|
1149
|
-
const smtpResponse = await (0, import_prompts2.default)([
|
|
1150
|
-
{
|
|
1151
|
-
type: "text",
|
|
1152
|
-
name: "host",
|
|
1153
|
-
message: "SMTP Host",
|
|
1154
|
-
initial: ""
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
type: "text",
|
|
1158
|
-
name: "port",
|
|
1159
|
-
message: "SMTP Port",
|
|
1160
|
-
initial: "587"
|
|
1161
|
-
},
|
|
1162
|
-
{
|
|
1163
|
-
type: "text",
|
|
1164
|
-
name: "user",
|
|
1165
|
-
message: "SMTP User",
|
|
1166
|
-
initial: ""
|
|
1167
|
-
},
|
|
1168
|
-
{
|
|
1169
|
-
type: "password",
|
|
1170
|
-
name: "pass",
|
|
1171
|
-
message: "SMTP Password"
|
|
1172
|
-
},
|
|
1173
|
-
{
|
|
1174
|
-
type: "text",
|
|
1175
|
-
name: "from",
|
|
1176
|
-
message: "Mail From address",
|
|
1177
|
-
initial: ""
|
|
1178
|
-
}
|
|
1179
|
-
]);
|
|
1180
|
-
if (smtpResponse.host === void 0) {
|
|
1181
|
-
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
const host = smtpResponse.host?.trim() || "";
|
|
1185
|
-
const user = smtpResponse.user?.trim() || "";
|
|
1186
|
-
const pass = smtpResponse.pass?.trim() || "";
|
|
1187
|
-
const from = smtpResponse.from?.trim() || "";
|
|
1188
|
-
const port = smtpResponse.port?.trim() || "587";
|
|
1189
|
-
usedDefaultSmtp = !host && !user;
|
|
1190
|
-
if (!usedDefaultSmtp) {
|
|
1191
|
-
customSmtpVars = {
|
|
1192
|
-
SMTP_HOST: host || "smtp.example.com",
|
|
1193
|
-
SMTP_PORT: port,
|
|
1194
|
-
SMTP_USER: user || "your-email@example.com",
|
|
1195
|
-
SMTP_PASS: pass || "your-password",
|
|
1196
|
-
MAIL_FROM: from || "noreply@example.com"
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
} else {
|
|
1200
|
-
const resendResponse = await (0, import_prompts2.default)([
|
|
1201
|
-
{
|
|
1202
|
-
type: "text",
|
|
1203
|
-
name: "apiKey",
|
|
1204
|
-
message: "Resend API Key",
|
|
1205
|
-
initial: ""
|
|
1206
|
-
},
|
|
1207
|
-
{
|
|
1208
|
-
type: "text",
|
|
1209
|
-
name: "from",
|
|
1210
|
-
message: "Mail From address",
|
|
1211
|
-
initial: ""
|
|
1212
|
-
}
|
|
1213
|
-
]);
|
|
1214
|
-
if (resendResponse.apiKey === void 0) {
|
|
1215
|
-
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1216
|
-
return;
|
|
1217
|
-
}
|
|
1218
|
-
const apiKey = resendResponse.apiKey?.trim() || "";
|
|
1219
|
-
const from = resendResponse.from?.trim() || "";
|
|
1220
|
-
usedDefaultSmtp = !apiKey;
|
|
1221
|
-
if (!usedDefaultSmtp) {
|
|
1222
|
-
customSmtpVars = {
|
|
1223
|
-
RESEND_API_KEY: apiKey || "re_your_api_key",
|
|
1224
|
-
MAIL_FROM: from || "onboarding@resend.dev"
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1695
|
+
const result = await promptMailerConfig();
|
|
1696
|
+
if (!result) return;
|
|
1697
|
+
mailerProvider = result.mailerProvider;
|
|
1698
|
+
customSmtpVars = result.customSmtpVars;
|
|
1699
|
+
usedDefaultSmtp = result.usedDefaultSmtp;
|
|
1700
|
+
}
|
|
1701
|
+
if (resolvedModuleName === "auth") {
|
|
1702
|
+
const result = await promptAuthConfig(projectRoot, srcDir, options);
|
|
1703
|
+
if (!result) return;
|
|
1704
|
+
shouldInstallDocsForAuth = result.shouldInstallDocsForAuth;
|
|
1705
|
+
authDatabaseDialect = result.authDatabaseDialect;
|
|
1228
1706
|
}
|
|
1229
1707
|
const pm = resolvePackageManager(projectRoot);
|
|
1230
1708
|
const spinner = (0, import_ora2.default)(`Checking registry for ${resolvedModuleName}...`).start();
|
|
@@ -1240,7 +1718,7 @@ var add = async (moduleName) => {
|
|
|
1240
1718
|
spinner.fail(`Module '${resolvedModuleName}' not found.`);
|
|
1241
1719
|
return;
|
|
1242
1720
|
}
|
|
1243
|
-
spinner.succeed(`Found module: ${
|
|
1721
|
+
spinner.succeed(`Found module: ${import_chalk7.default.cyan(resolvedModuleName)}`);
|
|
1244
1722
|
const moduleDeps = module2.moduleDependencies || [];
|
|
1245
1723
|
currentStep = "module dependency resolution";
|
|
1246
1724
|
await resolveDependencies(moduleDeps, projectRoot);
|
|
@@ -1262,9 +1740,6 @@ var add = async (moduleName) => {
|
|
|
1262
1740
|
spinner.succeed("Dependencies installed");
|
|
1263
1741
|
currentStep = "module scaffolding";
|
|
1264
1742
|
spinner.start("Scaffolding files...");
|
|
1265
|
-
if (resolvedModuleName === "auth") {
|
|
1266
|
-
authDatabaseDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);
|
|
1267
|
-
}
|
|
1268
1743
|
for (const file of module2.files) {
|
|
1269
1744
|
let fetchPath = file.path;
|
|
1270
1745
|
let expectedSha256 = file.sha256;
|
|
@@ -1292,10 +1767,10 @@ var add = async (moduleName) => {
|
|
|
1292
1767
|
);
|
|
1293
1768
|
}
|
|
1294
1769
|
const targetPath = resolveSafeTargetPath2(projectRoot, srcDir, file);
|
|
1295
|
-
await
|
|
1296
|
-
await
|
|
1770
|
+
await import_fs_extra11.default.ensureDir(import_path12.default.dirname(targetPath));
|
|
1771
|
+
await import_fs_extra11.default.writeFile(targetPath, content);
|
|
1297
1772
|
}
|
|
1298
|
-
const schemaExports = module2.files.map((file) => file.target.replace(/\\/g, "/")).filter((target) => /^db\/schema\/[^/]+\.ts$/.test(target)).map((target) =>
|
|
1773
|
+
const schemaExports = module2.files.map((file) => file.target.replace(/\\/g, "/")).filter((target) => /^db\/schema\/[^/]+\.ts$/.test(target)).map((target) => import_path12.default.posix.basename(target, ".ts")).filter((name) => name !== "index");
|
|
1299
1774
|
for (const schemaFileName of schemaExports) {
|
|
1300
1775
|
await ensureSchemaExport(projectRoot, srcDir, schemaFileName);
|
|
1301
1776
|
}
|
|
@@ -1308,6 +1783,16 @@ var add = async (moduleName) => {
|
|
|
1308
1783
|
} else {
|
|
1309
1784
|
spinner.warn("Could not configure routes automatically");
|
|
1310
1785
|
}
|
|
1786
|
+
const docsInstalled = await isDocsModuleInstalled(projectRoot, srcDir);
|
|
1787
|
+
if (docsInstalled) {
|
|
1788
|
+
spinner.start("Adding auth endpoints to API docs...");
|
|
1789
|
+
const authDocsInjected = await injectAuthDocs(projectRoot, srcDir);
|
|
1790
|
+
if (authDocsInjected) {
|
|
1791
|
+
spinner.succeed("Auth endpoints added to API docs");
|
|
1792
|
+
} else {
|
|
1793
|
+
spinner.warn("Could not update API docs automatically");
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1311
1796
|
}
|
|
1312
1797
|
if (resolvedModuleName === "error-handler") {
|
|
1313
1798
|
spinner.start("Configuring error handler in app.ts...");
|
|
@@ -1318,6 +1803,34 @@ var add = async (moduleName) => {
|
|
|
1318
1803
|
spinner.warn("Could not find app.ts - error handler needs manual setup");
|
|
1319
1804
|
}
|
|
1320
1805
|
}
|
|
1806
|
+
if (resolvedModuleName === "rate-limiter") {
|
|
1807
|
+
spinner.start("Configuring rate limiter in app.ts...");
|
|
1808
|
+
const injected = await injectRateLimiter(projectRoot, srcDir);
|
|
1809
|
+
if (injected) {
|
|
1810
|
+
spinner.succeed("Rate limiter configured in app.ts");
|
|
1811
|
+
} else {
|
|
1812
|
+
spinner.warn("Could not find app.ts - rate limiter needs manual setup");
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (resolvedModuleName === "docs") {
|
|
1816
|
+
spinner.start("Configuring docs routes...");
|
|
1817
|
+
const injected = await injectDocsRoutes(projectRoot, srcDir);
|
|
1818
|
+
if (injected) {
|
|
1819
|
+
spinner.succeed("Docs routes configured");
|
|
1820
|
+
} else {
|
|
1821
|
+
spinner.warn("Could not configure docs routes automatically");
|
|
1822
|
+
}
|
|
1823
|
+
const authInstalled = await isAuthModuleInstalled(projectRoot, srcDir);
|
|
1824
|
+
if (authInstalled) {
|
|
1825
|
+
spinner.start("Adding auth endpoints to API docs...");
|
|
1826
|
+
const authDocsInjected = await injectAuthDocs(projectRoot, srcDir);
|
|
1827
|
+
if (authDocsInjected) {
|
|
1828
|
+
spinner.succeed("Auth endpoints added to API docs");
|
|
1829
|
+
} else {
|
|
1830
|
+
spinner.warn("Could not update API docs automatically");
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1321
1834
|
let envConfigKey = resolvedModuleName;
|
|
1322
1835
|
if (resolvedModuleName === "mailer" && mailerProvider === "resend") {
|
|
1323
1836
|
envConfigKey = "mailer-resend";
|
|
@@ -1346,49 +1859,35 @@ var add = async (moduleName) => {
|
|
|
1346
1859
|
await updateEnvSchema(projectRoot, srcDir, envConfig.schemaFields);
|
|
1347
1860
|
spinner.succeed("Environment configured");
|
|
1348
1861
|
}
|
|
1349
|
-
console.log(
|
|
1862
|
+
console.log(import_chalk7.default.green(`
|
|
1350
1863
|
\u2714 ${resolvedModuleName} added successfully!
|
|
1351
1864
|
`));
|
|
1352
|
-
if (databaseBackupPath) {
|
|
1353
|
-
console.log(import_chalk4.default.blue(`\u2139 Backup created at: ${databaseBackupPath}
|
|
1354
|
-
`));
|
|
1355
|
-
}
|
|
1356
1865
|
const docsPath = getModuleDocsPath(resolvedModuleName);
|
|
1357
1866
|
const docsUrl = `https://zuro-cli.devbybriyan.com/docs/${docsPath}`;
|
|
1358
|
-
console.log(
|
|
1867
|
+
console.log(import_chalk7.default.blue(`\u2139 Docs: ${docsUrl}`));
|
|
1359
1868
|
if (isDatabaseModule(resolvedModuleName)) {
|
|
1360
|
-
|
|
1361
|
-
console.log(import_chalk4.default.yellow("\u2139 Review DATABASE_URL in .env if your local DB config differs."));
|
|
1362
|
-
}
|
|
1363
|
-
const setupHint = getDatabaseSetupHint(
|
|
1364
|
-
resolvedModuleName,
|
|
1365
|
-
customDbUrl || DEFAULT_DATABASE_URLS[resolvedModuleName]
|
|
1366
|
-
);
|
|
1367
|
-
console.log(import_chalk4.default.yellow(`\u2139 Ensure DB exists: ${setupHint}`));
|
|
1368
|
-
console.log(import_chalk4.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
1869
|
+
printDatabaseHints(resolvedModuleName, customDbUrl, usedDefaultDbUrl, databaseBackupPath);
|
|
1369
1870
|
}
|
|
1370
1871
|
if (resolvedModuleName === "auth") {
|
|
1371
|
-
|
|
1372
|
-
console.log(import_chalk4.default.yellow("\u2139 BETTER_AUTH_SECRET was generated automatically."));
|
|
1373
|
-
} else {
|
|
1374
|
-
console.log(import_chalk4.default.yellow("\u2139 Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env."));
|
|
1375
|
-
}
|
|
1376
|
-
console.log(import_chalk4.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
1872
|
+
printAuthHints(generatedAuthSecret);
|
|
1377
1873
|
}
|
|
1378
1874
|
if (resolvedModuleName === "mailer") {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1875
|
+
printMailerHints(usedDefaultSmtp);
|
|
1876
|
+
}
|
|
1877
|
+
if (resolvedModuleName === "docs") {
|
|
1878
|
+
printDocsHints();
|
|
1879
|
+
}
|
|
1880
|
+
if (resolvedModuleName === "auth" && shouldInstallDocsForAuth) {
|
|
1881
|
+
console.log(import_chalk7.default.blue("\n\u2139 Installing API docs module..."));
|
|
1882
|
+
await add("docs", { yes: true });
|
|
1384
1883
|
}
|
|
1385
1884
|
} catch (error) {
|
|
1386
|
-
spinner.fail(
|
|
1885
|
+
spinner.fail(import_chalk7.default.red(`Failed during ${currentStep}.`));
|
|
1387
1886
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1388
|
-
console.error(
|
|
1887
|
+
console.error(import_chalk7.default.red(errorMessage));
|
|
1389
1888
|
console.log(`
|
|
1390
|
-
${
|
|
1391
|
-
console.log(
|
|
1889
|
+
${import_chalk7.default.bold("Retry:")}`);
|
|
1890
|
+
console.log(import_chalk7.default.cyan(` npx zuro-cli add ${resolvedModuleName}`));
|
|
1392
1891
|
}
|
|
1393
1892
|
};
|
|
1394
1893
|
|
|
@@ -1396,6 +1895,6 @@ ${import_chalk4.default.bold("Retry:")}`);
|
|
|
1396
1895
|
var program = new import_commander.Command();
|
|
1397
1896
|
program.name("zuro-cli").description("Zuro CLI tool").version("0.0.1");
|
|
1398
1897
|
program.command("init").description("Initialize a new Zuro project").action(init);
|
|
1399
|
-
program.command("add <module>").description("Add a module to your project").action(add);
|
|
1898
|
+
program.command("add <module>").description("Add a module to your project").action((module2, options) => add(module2, options));
|
|
1400
1899
|
program.parse(process.argv);
|
|
1401
1900
|
//# sourceMappingURL=index.js.map
|