zuro-cli 0.0.2-beta.11 → 0.0.2-beta.13
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/index.js +417 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +417 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -357,6 +357,32 @@ var ENV_CONFIGS = {
|
|
|
357
357
|
{ name: "BETTER_AUTH_SECRET", schema: "z.string().min(32)" },
|
|
358
358
|
{ name: "BETTER_AUTH_URL", schema: "z.string().url()" }
|
|
359
359
|
]
|
|
360
|
+
},
|
|
361
|
+
mailer: {
|
|
362
|
+
envVars: {
|
|
363
|
+
SMTP_HOST: "smtp.example.com",
|
|
364
|
+
SMTP_PORT: "587",
|
|
365
|
+
SMTP_USER: "your-email@example.com",
|
|
366
|
+
SMTP_PASS: "your-password",
|
|
367
|
+
MAIL_FROM: "noreply@example.com"
|
|
368
|
+
},
|
|
369
|
+
schemaFields: [
|
|
370
|
+
{ name: "SMTP_HOST", schema: "z.string().min(1)" },
|
|
371
|
+
{ name: "SMTP_PORT", schema: "z.coerce.number().default(587)" },
|
|
372
|
+
{ name: "SMTP_USER", schema: "z.string().min(1)" },
|
|
373
|
+
{ name: "SMTP_PASS", schema: "z.string().min(1)" },
|
|
374
|
+
{ name: "MAIL_FROM", schema: "z.string().email()" }
|
|
375
|
+
]
|
|
376
|
+
},
|
|
377
|
+
"mailer-resend": {
|
|
378
|
+
envVars: {
|
|
379
|
+
RESEND_API_KEY: "re_your_api_key",
|
|
380
|
+
MAIL_FROM: "onboarding@resend.dev"
|
|
381
|
+
},
|
|
382
|
+
schemaFields: [
|
|
383
|
+
{ name: "RESEND_API_KEY", schema: "z.string().min(1)" },
|
|
384
|
+
{ name: "MAIL_FROM", schema: "z.string().min(1)" }
|
|
385
|
+
]
|
|
360
386
|
}
|
|
361
387
|
};
|
|
362
388
|
|
|
@@ -663,7 +689,9 @@ var BLOCK_SIGNATURES = {
|
|
|
663
689
|
validator: "middleware/validate.ts",
|
|
664
690
|
"error-handler": "lib/errors.ts",
|
|
665
691
|
logger: "lib/logger.ts",
|
|
666
|
-
auth: "lib/auth.ts"
|
|
692
|
+
auth: "lib/auth.ts",
|
|
693
|
+
mailer: "lib/mailer.ts",
|
|
694
|
+
docs: "lib/openapi.ts"
|
|
667
695
|
};
|
|
668
696
|
var resolveDependencies = async (moduleDependencies, cwd) => {
|
|
669
697
|
if (!moduleDependencies || moduleDependencies.length === 0) {
|
|
@@ -814,6 +842,25 @@ function getModuleDocsPath(moduleName) {
|
|
|
814
842
|
function escapeRegex(value) {
|
|
815
843
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
816
844
|
}
|
|
845
|
+
function appendImport(source, line) {
|
|
846
|
+
if (source.includes(line)) {
|
|
847
|
+
return { source, inserted: true };
|
|
848
|
+
}
|
|
849
|
+
const importRegex = /^import .+ from .+;?\s*$/gm;
|
|
850
|
+
let lastImportIndex = 0;
|
|
851
|
+
let match;
|
|
852
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
853
|
+
lastImportIndex = match.index + match[0].length;
|
|
854
|
+
}
|
|
855
|
+
if (lastImportIndex <= 0) {
|
|
856
|
+
return { source, inserted: false };
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
source: source.slice(0, lastImportIndex) + `
|
|
860
|
+
${line}` + source.slice(lastImportIndex),
|
|
861
|
+
inserted: true
|
|
862
|
+
};
|
|
863
|
+
}
|
|
817
864
|
async function hasEnvVariable(projectRoot, key) {
|
|
818
865
|
const envPath = import_path6.default.join(projectRoot, ".env");
|
|
819
866
|
if (!await import_fs_extra6.default.pathExists(envPath)) {
|
|
@@ -851,6 +898,12 @@ async function ensureSchemaExport(projectRoot, srcDir, schemaFileName) {
|
|
|
851
898
|
`;
|
|
852
899
|
await import_fs_extra6.default.writeFile(schemaIndexPath, next);
|
|
853
900
|
}
|
|
901
|
+
async function isDocsModuleInstalled(projectRoot, srcDir) {
|
|
902
|
+
return await import_fs_extra6.default.pathExists(import_path6.default.join(projectRoot, srcDir, "lib", "openapi.ts"));
|
|
903
|
+
}
|
|
904
|
+
async function isAuthModuleInstalled(projectRoot, srcDir) {
|
|
905
|
+
return await import_fs_extra6.default.pathExists(import_path6.default.join(projectRoot, srcDir, "lib", "auth.ts"));
|
|
906
|
+
}
|
|
854
907
|
async function injectErrorHandler(projectRoot, srcDir) {
|
|
855
908
|
const appPath = import_path6.default.join(projectRoot, srcDir, "app.ts");
|
|
856
909
|
if (!import_fs_extra6.default.existsSync(appPath)) {
|
|
@@ -913,25 +966,6 @@ async function injectAuthRoutes(projectRoot, srcDir) {
|
|
|
913
966
|
const routeIndexUserImport = `import userRoutes from "./user.routes";`;
|
|
914
967
|
const appUserImport = `import userRoutes from "./routes/user.routes";`;
|
|
915
968
|
let appModified = false;
|
|
916
|
-
const appendImport = (source, line) => {
|
|
917
|
-
if (source.includes(line)) {
|
|
918
|
-
return { source, inserted: true };
|
|
919
|
-
}
|
|
920
|
-
const importRegex = /^import .+ from .+;?\s*$/gm;
|
|
921
|
-
let lastImportIndex = 0;
|
|
922
|
-
let match;
|
|
923
|
-
while ((match = importRegex.exec(source)) !== null) {
|
|
924
|
-
lastImportIndex = match.index + match[0].length;
|
|
925
|
-
}
|
|
926
|
-
if (lastImportIndex <= 0) {
|
|
927
|
-
return { source, inserted: false };
|
|
928
|
-
}
|
|
929
|
-
return {
|
|
930
|
-
source: source.slice(0, lastImportIndex) + `
|
|
931
|
-
${line}` + source.slice(lastImportIndex),
|
|
932
|
-
inserted: true
|
|
933
|
-
};
|
|
934
|
-
};
|
|
935
969
|
for (const importLine of [authHandlerImport, authImport]) {
|
|
936
970
|
const next = appendImport(appContent, importLine);
|
|
937
971
|
if (!next.inserted) {
|
|
@@ -1017,7 +1051,177 @@ app.use("/api/users", userRoutes);
|
|
|
1017
1051
|
}
|
|
1018
1052
|
return true;
|
|
1019
1053
|
}
|
|
1020
|
-
|
|
1054
|
+
async function injectDocsRoutes(projectRoot, srcDir) {
|
|
1055
|
+
const routeIndexPath = import_path6.default.join(projectRoot, srcDir, "routes", "index.ts");
|
|
1056
|
+
const routeImport = `import docsRoutes from "./docs.routes";`;
|
|
1057
|
+
const routeMountPattern = /rootRouter\.use\(\s*["']\/docs["']\s*,\s*docsRoutes\s*\)/;
|
|
1058
|
+
if (await import_fs_extra6.default.pathExists(routeIndexPath)) {
|
|
1059
|
+
let routeContent = await import_fs_extra6.default.readFile(routeIndexPath, "utf-8");
|
|
1060
|
+
let routeModified = false;
|
|
1061
|
+
const importResult = appendImport(routeContent, routeImport);
|
|
1062
|
+
if (!importResult.inserted) {
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
if (importResult.source !== routeContent) {
|
|
1066
|
+
routeContent = importResult.source;
|
|
1067
|
+
routeModified = true;
|
|
1068
|
+
}
|
|
1069
|
+
if (!routeMountPattern.test(routeContent)) {
|
|
1070
|
+
const routeSetup = `
|
|
1071
|
+
// API docs
|
|
1072
|
+
rootRouter.use("/docs", docsRoutes);
|
|
1073
|
+
`;
|
|
1074
|
+
const exportMatch = routeContent.match(/export default rootRouter;?\s*$/m);
|
|
1075
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
routeContent = routeContent.slice(0, exportMatch.index) + routeSetup + "\n" + routeContent.slice(exportMatch.index);
|
|
1079
|
+
routeModified = true;
|
|
1080
|
+
}
|
|
1081
|
+
if (routeModified) {
|
|
1082
|
+
await import_fs_extra6.default.writeFile(routeIndexPath, routeContent);
|
|
1083
|
+
}
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
const appPath = import_path6.default.join(projectRoot, srcDir, "app.ts");
|
|
1087
|
+
if (!await import_fs_extra6.default.pathExists(appPath)) {
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
let appContent = await import_fs_extra6.default.readFile(appPath, "utf-8");
|
|
1091
|
+
let appModified = false;
|
|
1092
|
+
const appImportResult = appendImport(appContent, `import docsRoutes from "./routes/docs.routes";`);
|
|
1093
|
+
if (!appImportResult.inserted) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
if (appImportResult.source !== appContent) {
|
|
1097
|
+
appContent = appImportResult.source;
|
|
1098
|
+
appModified = true;
|
|
1099
|
+
}
|
|
1100
|
+
const hasMount = /app\.use\(\s*["']\/api\/docs["']\s*,\s*docsRoutes\s*\)/.test(appContent);
|
|
1101
|
+
if (!hasMount) {
|
|
1102
|
+
const setup = `
|
|
1103
|
+
// API docs
|
|
1104
|
+
app.use("/api/docs", docsRoutes);
|
|
1105
|
+
`;
|
|
1106
|
+
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
1107
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
appContent = appContent.slice(0, exportMatch.index) + setup + "\n" + appContent.slice(exportMatch.index);
|
|
1111
|
+
appModified = true;
|
|
1112
|
+
}
|
|
1113
|
+
if (appModified) {
|
|
1114
|
+
await import_fs_extra6.default.writeFile(appPath, appContent);
|
|
1115
|
+
}
|
|
1116
|
+
return true;
|
|
1117
|
+
}
|
|
1118
|
+
async function injectAuthDocs(projectRoot, srcDir) {
|
|
1119
|
+
const openApiPath = import_path6.default.join(projectRoot, srcDir, "lib", "openapi.ts");
|
|
1120
|
+
if (!await import_fs_extra6.default.pathExists(openApiPath)) {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
const authMarker = "// ZURO_AUTH_DOCS";
|
|
1124
|
+
let content = await import_fs_extra6.default.readFile(openApiPath, "utf-8");
|
|
1125
|
+
if (content.includes(authMarker)) {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
const moduleDocsEndMarker = "// ZURO_DOCS_MODULES_END";
|
|
1129
|
+
if (!content.includes(moduleDocsEndMarker)) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
const authBlock = `
|
|
1133
|
+
const authSignUpSchema = z.object({
|
|
1134
|
+
email: z.string().email().openapi({ example: "dev@company.com" }),
|
|
1135
|
+
password: z.string().min(8).openapi({ example: "strong-password" }),
|
|
1136
|
+
name: z.string().min(1).optional().openapi({ example: "Dev User" }),
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
const authSignInSchema = z.object({
|
|
1140
|
+
email: z.string().email().openapi({ example: "dev@company.com" }),
|
|
1141
|
+
password: z.string().min(8).openapi({ example: "strong-password" }),
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
const authUserSchema = z.object({
|
|
1145
|
+
id: z.string().openapi({ example: "user_123" }),
|
|
1146
|
+
email: z.string().email().openapi({ example: "dev@company.com" }),
|
|
1147
|
+
name: z.string().nullable().openapi({ example: "Dev User" }),
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
${authMarker}
|
|
1151
|
+
registry.registerPath({
|
|
1152
|
+
method: "post",
|
|
1153
|
+
path: "/api/auth/sign-up/email",
|
|
1154
|
+
tags: ["Auth"],
|
|
1155
|
+
summary: "Register using email and password",
|
|
1156
|
+
request: {
|
|
1157
|
+
body: {
|
|
1158
|
+
content: {
|
|
1159
|
+
"application/json": {
|
|
1160
|
+
schema: authSignUpSchema,
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
},
|
|
1165
|
+
responses: {
|
|
1166
|
+
200: { description: "Registration successful" },
|
|
1167
|
+
},
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
registry.registerPath({
|
|
1171
|
+
method: "post",
|
|
1172
|
+
path: "/api/auth/sign-in/email",
|
|
1173
|
+
tags: ["Auth"],
|
|
1174
|
+
summary: "Sign in using email and password",
|
|
1175
|
+
request: {
|
|
1176
|
+
body: {
|
|
1177
|
+
content: {
|
|
1178
|
+
"application/json": {
|
|
1179
|
+
schema: authSignInSchema,
|
|
1180
|
+
},
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
responses: {
|
|
1185
|
+
200: { description: "Sign in successful" },
|
|
1186
|
+
401: { description: "Invalid credentials" },
|
|
1187
|
+
},
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
registry.registerPath({
|
|
1191
|
+
method: "post",
|
|
1192
|
+
path: "/api/auth/sign-out",
|
|
1193
|
+
tags: ["Auth"],
|
|
1194
|
+
summary: "Sign out current user",
|
|
1195
|
+
responses: {
|
|
1196
|
+
200: { description: "Sign out successful" },
|
|
1197
|
+
},
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
registry.registerPath({
|
|
1201
|
+
method: "get",
|
|
1202
|
+
path: "/api/users/me",
|
|
1203
|
+
tags: ["Auth"],
|
|
1204
|
+
summary: "Get current authenticated user",
|
|
1205
|
+
security: [{ bearerAuth: [] }],
|
|
1206
|
+
responses: {
|
|
1207
|
+
200: {
|
|
1208
|
+
description: "Current user",
|
|
1209
|
+
content: {
|
|
1210
|
+
"application/json": {
|
|
1211
|
+
schema: z.object({ user: authUserSchema }),
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
401: { description: "Not authenticated" },
|
|
1216
|
+
},
|
|
1217
|
+
});
|
|
1218
|
+
`;
|
|
1219
|
+
content = content.replace(moduleDocsEndMarker, `${authBlock}
|
|
1220
|
+
${moduleDocsEndMarker}`);
|
|
1221
|
+
await import_fs_extra6.default.writeFile(openApiPath, content);
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
1224
|
+
var add = async (moduleName, options = {}) => {
|
|
1021
1225
|
const projectRoot = process.cwd();
|
|
1022
1226
|
const projectConfig = await readZuroConfig(projectRoot);
|
|
1023
1227
|
if (!projectConfig) {
|
|
@@ -1039,6 +1243,10 @@ var add = async (moduleName) => {
|
|
|
1039
1243
|
let databaseBackupPath = null;
|
|
1040
1244
|
let generatedAuthSecret = false;
|
|
1041
1245
|
let authDatabaseDialect = null;
|
|
1246
|
+
let customSmtpVars;
|
|
1247
|
+
let usedDefaultSmtp = false;
|
|
1248
|
+
let mailerProvider = "smtp";
|
|
1249
|
+
let shouldInstallDocsForAuth = false;
|
|
1042
1250
|
if (resolvedModuleName === "database") {
|
|
1043
1251
|
const variantResponse = await (0, import_prompts2.default)({
|
|
1044
1252
|
type: "select",
|
|
@@ -1099,6 +1307,123 @@ var add = async (moduleName) => {
|
|
|
1099
1307
|
usedDefaultDbUrl = enteredUrl.length === 0;
|
|
1100
1308
|
customDbUrl = validateDatabaseUrl(enteredUrl || defaultUrl, resolvedModuleName);
|
|
1101
1309
|
}
|
|
1310
|
+
if (resolvedModuleName === "mailer") {
|
|
1311
|
+
const providerResponse = await (0, import_prompts2.default)({
|
|
1312
|
+
type: "select",
|
|
1313
|
+
name: "provider",
|
|
1314
|
+
message: "Which email provider?",
|
|
1315
|
+
choices: [
|
|
1316
|
+
{ title: "SMTP (Nodemailer)", description: "Gmail, Mailtrap, any SMTP server", value: "smtp" },
|
|
1317
|
+
{ title: "Resend", description: "API-based, easiest setup", value: "resend" }
|
|
1318
|
+
]
|
|
1319
|
+
});
|
|
1320
|
+
if (providerResponse.provider === void 0) {
|
|
1321
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
mailerProvider = providerResponse.provider;
|
|
1325
|
+
console.log(import_chalk4.default.dim(" Tip: Leave fields blank to use placeholder values and configure later\n"));
|
|
1326
|
+
if (mailerProvider === "smtp") {
|
|
1327
|
+
const smtpResponse = await (0, import_prompts2.default)([
|
|
1328
|
+
{
|
|
1329
|
+
type: "text",
|
|
1330
|
+
name: "host",
|
|
1331
|
+
message: "SMTP Host",
|
|
1332
|
+
initial: ""
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
type: "text",
|
|
1336
|
+
name: "port",
|
|
1337
|
+
message: "SMTP Port",
|
|
1338
|
+
initial: "587"
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
type: "text",
|
|
1342
|
+
name: "user",
|
|
1343
|
+
message: "SMTP User",
|
|
1344
|
+
initial: ""
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
type: "password",
|
|
1348
|
+
name: "pass",
|
|
1349
|
+
message: "SMTP Password"
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
type: "text",
|
|
1353
|
+
name: "from",
|
|
1354
|
+
message: "Mail From address",
|
|
1355
|
+
initial: ""
|
|
1356
|
+
}
|
|
1357
|
+
]);
|
|
1358
|
+
if (smtpResponse.host === void 0) {
|
|
1359
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const host = smtpResponse.host?.trim() || "";
|
|
1363
|
+
const user = smtpResponse.user?.trim() || "";
|
|
1364
|
+
const pass = smtpResponse.pass?.trim() || "";
|
|
1365
|
+
const from = smtpResponse.from?.trim() || "";
|
|
1366
|
+
const port = smtpResponse.port?.trim() || "587";
|
|
1367
|
+
usedDefaultSmtp = !host && !user;
|
|
1368
|
+
if (!usedDefaultSmtp) {
|
|
1369
|
+
customSmtpVars = {
|
|
1370
|
+
SMTP_HOST: host || "smtp.example.com",
|
|
1371
|
+
SMTP_PORT: port,
|
|
1372
|
+
SMTP_USER: user || "your-email@example.com",
|
|
1373
|
+
SMTP_PASS: pass || "your-password",
|
|
1374
|
+
MAIL_FROM: from || "noreply@example.com"
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
} else {
|
|
1378
|
+
const resendResponse = await (0, import_prompts2.default)([
|
|
1379
|
+
{
|
|
1380
|
+
type: "text",
|
|
1381
|
+
name: "apiKey",
|
|
1382
|
+
message: "Resend API Key",
|
|
1383
|
+
initial: ""
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
type: "text",
|
|
1387
|
+
name: "from",
|
|
1388
|
+
message: "Mail From address",
|
|
1389
|
+
initial: ""
|
|
1390
|
+
}
|
|
1391
|
+
]);
|
|
1392
|
+
if (resendResponse.apiKey === void 0) {
|
|
1393
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const apiKey = resendResponse.apiKey?.trim() || "";
|
|
1397
|
+
const from = resendResponse.from?.trim() || "";
|
|
1398
|
+
usedDefaultSmtp = !apiKey;
|
|
1399
|
+
if (!usedDefaultSmtp) {
|
|
1400
|
+
customSmtpVars = {
|
|
1401
|
+
RESEND_API_KEY: apiKey || "re_your_api_key",
|
|
1402
|
+
MAIL_FROM: from || "onboarding@resend.dev"
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (resolvedModuleName === "auth") {
|
|
1408
|
+
const docsInstalled = await isDocsModuleInstalled(projectRoot, srcDir);
|
|
1409
|
+
if (!docsInstalled) {
|
|
1410
|
+
if (options.yes) {
|
|
1411
|
+
shouldInstallDocsForAuth = true;
|
|
1412
|
+
} else {
|
|
1413
|
+
const docsResponse = await (0, import_prompts2.default)({
|
|
1414
|
+
type: "confirm",
|
|
1415
|
+
name: "installDocs",
|
|
1416
|
+
message: "Install API docs module (Scalar + OpenAPI) too?",
|
|
1417
|
+
initial: true
|
|
1418
|
+
});
|
|
1419
|
+
if (docsResponse.installDocs === void 0) {
|
|
1420
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
shouldInstallDocsForAuth = docsResponse.installDocs;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1102
1427
|
const pm = resolvePackageManager(projectRoot);
|
|
1103
1428
|
const spinner = (0, import_ora2.default)(`Checking registry for ${resolvedModuleName}...`).start();
|
|
1104
1429
|
let currentStep = "package manager preflight";
|
|
@@ -1119,8 +1444,19 @@ var add = async (moduleName) => {
|
|
|
1119
1444
|
await resolveDependencies(moduleDeps, projectRoot);
|
|
1120
1445
|
currentStep = "dependency installation";
|
|
1121
1446
|
spinner.start("Installing dependencies...");
|
|
1122
|
-
|
|
1123
|
-
|
|
1447
|
+
let runtimeDeps = module2.dependencies || [];
|
|
1448
|
+
let devDeps = module2.devDependencies || [];
|
|
1449
|
+
if (resolvedModuleName === "mailer") {
|
|
1450
|
+
if (mailerProvider === "resend") {
|
|
1451
|
+
runtimeDeps = ["resend"];
|
|
1452
|
+
devDeps = [];
|
|
1453
|
+
} else {
|
|
1454
|
+
runtimeDeps = ["nodemailer"];
|
|
1455
|
+
devDeps = ["@types/nodemailer"];
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
await installDependencies(pm, runtimeDeps, projectRoot);
|
|
1459
|
+
await installDependencies(pm, devDeps, projectRoot, { dev: true });
|
|
1124
1460
|
spinner.succeed("Dependencies installed");
|
|
1125
1461
|
currentStep = "module scaffolding";
|
|
1126
1462
|
spinner.start("Scaffolding files...");
|
|
@@ -1136,6 +1472,11 @@ var add = async (moduleName) => {
|
|
|
1136
1472
|
expectedSha256 = void 0;
|
|
1137
1473
|
expectedSize = void 0;
|
|
1138
1474
|
}
|
|
1475
|
+
if (resolvedModuleName === "mailer" && file.target === "lib/mailer.ts" && mailerProvider === "resend") {
|
|
1476
|
+
fetchPath = "express/lib/mailer.resend.ts";
|
|
1477
|
+
expectedSha256 = void 0;
|
|
1478
|
+
expectedSize = void 0;
|
|
1479
|
+
}
|
|
1139
1480
|
let content = await fetchFile(fetchPath, {
|
|
1140
1481
|
baseUrl: registryContext.fileBaseUrl,
|
|
1141
1482
|
expectedSha256,
|
|
@@ -1165,6 +1506,16 @@ var add = async (moduleName) => {
|
|
|
1165
1506
|
} else {
|
|
1166
1507
|
spinner.warn("Could not configure routes automatically");
|
|
1167
1508
|
}
|
|
1509
|
+
const docsInstalled = await isDocsModuleInstalled(projectRoot, srcDir);
|
|
1510
|
+
if (docsInstalled) {
|
|
1511
|
+
spinner.start("Adding auth endpoints to API docs...");
|
|
1512
|
+
const authDocsInjected = await injectAuthDocs(projectRoot, srcDir);
|
|
1513
|
+
if (authDocsInjected) {
|
|
1514
|
+
spinner.succeed("Auth endpoints added to API docs");
|
|
1515
|
+
} else {
|
|
1516
|
+
spinner.warn("Could not update API docs automatically");
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1168
1519
|
}
|
|
1169
1520
|
if (resolvedModuleName === "error-handler") {
|
|
1170
1521
|
spinner.start("Configuring error handler in app.ts...");
|
|
@@ -1175,7 +1526,30 @@ var add = async (moduleName) => {
|
|
|
1175
1526
|
spinner.warn("Could not find app.ts - error handler needs manual setup");
|
|
1176
1527
|
}
|
|
1177
1528
|
}
|
|
1178
|
-
|
|
1529
|
+
if (resolvedModuleName === "docs") {
|
|
1530
|
+
spinner.start("Configuring docs routes...");
|
|
1531
|
+
const injected = await injectDocsRoutes(projectRoot, srcDir);
|
|
1532
|
+
if (injected) {
|
|
1533
|
+
spinner.succeed("Docs routes configured");
|
|
1534
|
+
} else {
|
|
1535
|
+
spinner.warn("Could not configure docs routes automatically");
|
|
1536
|
+
}
|
|
1537
|
+
const authInstalled = await isAuthModuleInstalled(projectRoot, srcDir);
|
|
1538
|
+
if (authInstalled) {
|
|
1539
|
+
spinner.start("Adding auth endpoints to API docs...");
|
|
1540
|
+
const authDocsInjected = await injectAuthDocs(projectRoot, srcDir);
|
|
1541
|
+
if (authDocsInjected) {
|
|
1542
|
+
spinner.succeed("Auth endpoints added to API docs");
|
|
1543
|
+
} else {
|
|
1544
|
+
spinner.warn("Could not update API docs automatically");
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
let envConfigKey = resolvedModuleName;
|
|
1549
|
+
if (resolvedModuleName === "mailer" && mailerProvider === "resend") {
|
|
1550
|
+
envConfigKey = "mailer-resend";
|
|
1551
|
+
}
|
|
1552
|
+
const envConfig = ENV_CONFIGS[envConfigKey];
|
|
1179
1553
|
if (envConfig) {
|
|
1180
1554
|
currentStep = "environment configuration";
|
|
1181
1555
|
spinner.start("Updating environment configuration...");
|
|
@@ -1183,6 +1557,9 @@ var add = async (moduleName) => {
|
|
|
1183
1557
|
if (customDbUrl && isDatabaseModule(resolvedModuleName)) {
|
|
1184
1558
|
envVars.DATABASE_URL = customDbUrl;
|
|
1185
1559
|
}
|
|
1560
|
+
if (resolvedModuleName === "mailer" && customSmtpVars) {
|
|
1561
|
+
Object.assign(envVars, customSmtpVars);
|
|
1562
|
+
}
|
|
1186
1563
|
if (resolvedModuleName === "auth") {
|
|
1187
1564
|
const hasExistingSecret = await hasEnvVariable(projectRoot, "BETTER_AUTH_SECRET");
|
|
1188
1565
|
if (!hasExistingSecret) {
|
|
@@ -1225,6 +1602,21 @@ var add = async (moduleName) => {
|
|
|
1225
1602
|
}
|
|
1226
1603
|
console.log(import_chalk4.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
1227
1604
|
}
|
|
1605
|
+
if (resolvedModuleName === "mailer") {
|
|
1606
|
+
if (usedDefaultSmtp) {
|
|
1607
|
+
console.log(import_chalk4.default.yellow("\u2139 Placeholder SMTP values added to .env \u2014 update them before sending emails."));
|
|
1608
|
+
} else {
|
|
1609
|
+
console.log(import_chalk4.default.yellow("\u2139 Review SMTP configuration in .env to ensure values are correct."));
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
if (resolvedModuleName === "docs") {
|
|
1613
|
+
console.log(import_chalk4.default.yellow("\u2139 API docs available at: /api/docs"));
|
|
1614
|
+
console.log(import_chalk4.default.yellow("\u2139 OpenAPI spec available at: /api/docs/openapi.json"));
|
|
1615
|
+
}
|
|
1616
|
+
if (resolvedModuleName === "auth" && shouldInstallDocsForAuth) {
|
|
1617
|
+
console.log(import_chalk4.default.blue("\n\u2139 Installing API docs module..."));
|
|
1618
|
+
await add("docs", { yes: true });
|
|
1619
|
+
}
|
|
1228
1620
|
} catch (error) {
|
|
1229
1621
|
spinner.fail(import_chalk4.default.red(`Failed during ${currentStep}.`));
|
|
1230
1622
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1239,6 +1631,6 @@ ${import_chalk4.default.bold("Retry:")}`);
|
|
|
1239
1631
|
var program = new import_commander.Command();
|
|
1240
1632
|
program.name("zuro-cli").description("Zuro CLI tool").version("0.0.1");
|
|
1241
1633
|
program.command("init").description("Initialize a new Zuro project").action(init);
|
|
1242
|
-
program.command("add <module>").description("Add a module to your project").action(add);
|
|
1634
|
+
program.command("add <module>").description("Add a module to your project").action((module2, options) => add(module2, options));
|
|
1243
1635
|
program.parse(process.argv);
|
|
1244
1636
|
//# sourceMappingURL=index.js.map
|