zuro-cli 0.0.2-beta.12 → 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 CHANGED
@@ -690,7 +690,8 @@ var BLOCK_SIGNATURES = {
690
690
  "error-handler": "lib/errors.ts",
691
691
  logger: "lib/logger.ts",
692
692
  auth: "lib/auth.ts",
693
- mailer: "lib/mailer.ts"
693
+ mailer: "lib/mailer.ts",
694
+ docs: "lib/openapi.ts"
694
695
  };
695
696
  var resolveDependencies = async (moduleDependencies, cwd) => {
696
697
  if (!moduleDependencies || moduleDependencies.length === 0) {
@@ -841,6 +842,25 @@ function getModuleDocsPath(moduleName) {
841
842
  function escapeRegex(value) {
842
843
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
843
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
+ }
844
864
  async function hasEnvVariable(projectRoot, key) {
845
865
  const envPath = import_path6.default.join(projectRoot, ".env");
846
866
  if (!await import_fs_extra6.default.pathExists(envPath)) {
@@ -878,6 +898,12 @@ async function ensureSchemaExport(projectRoot, srcDir, schemaFileName) {
878
898
  `;
879
899
  await import_fs_extra6.default.writeFile(schemaIndexPath, next);
880
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
+ }
881
907
  async function injectErrorHandler(projectRoot, srcDir) {
882
908
  const appPath = import_path6.default.join(projectRoot, srcDir, "app.ts");
883
909
  if (!import_fs_extra6.default.existsSync(appPath)) {
@@ -940,25 +966,6 @@ async function injectAuthRoutes(projectRoot, srcDir) {
940
966
  const routeIndexUserImport = `import userRoutes from "./user.routes";`;
941
967
  const appUserImport = `import userRoutes from "./routes/user.routes";`;
942
968
  let appModified = false;
943
- const appendImport = (source, line) => {
944
- if (source.includes(line)) {
945
- return { source, inserted: true };
946
- }
947
- const importRegex = /^import .+ from .+;?\s*$/gm;
948
- let lastImportIndex = 0;
949
- let match;
950
- while ((match = importRegex.exec(source)) !== null) {
951
- lastImportIndex = match.index + match[0].length;
952
- }
953
- if (lastImportIndex <= 0) {
954
- return { source, inserted: false };
955
- }
956
- return {
957
- source: source.slice(0, lastImportIndex) + `
958
- ${line}` + source.slice(lastImportIndex),
959
- inserted: true
960
- };
961
- };
962
969
  for (const importLine of [authHandlerImport, authImport]) {
963
970
  const next = appendImport(appContent, importLine);
964
971
  if (!next.inserted) {
@@ -1044,7 +1051,177 @@ app.use("/api/users", userRoutes);
1044
1051
  }
1045
1052
  return true;
1046
1053
  }
1047
- var add = async (moduleName) => {
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 = {}) => {
1048
1225
  const projectRoot = process.cwd();
1049
1226
  const projectConfig = await readZuroConfig(projectRoot);
1050
1227
  if (!projectConfig) {
@@ -1069,6 +1246,7 @@ var add = async (moduleName) => {
1069
1246
  let customSmtpVars;
1070
1247
  let usedDefaultSmtp = false;
1071
1248
  let mailerProvider = "smtp";
1249
+ let shouldInstallDocsForAuth = false;
1072
1250
  if (resolvedModuleName === "database") {
1073
1251
  const variantResponse = await (0, import_prompts2.default)({
1074
1252
  type: "select",
@@ -1226,6 +1404,26 @@ var add = async (moduleName) => {
1226
1404
  }
1227
1405
  }
1228
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
+ }
1229
1427
  const pm = resolvePackageManager(projectRoot);
1230
1428
  const spinner = (0, import_ora2.default)(`Checking registry for ${resolvedModuleName}...`).start();
1231
1429
  let currentStep = "package manager preflight";
@@ -1308,6 +1506,16 @@ var add = async (moduleName) => {
1308
1506
  } else {
1309
1507
  spinner.warn("Could not configure routes automatically");
1310
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
+ }
1311
1519
  }
1312
1520
  if (resolvedModuleName === "error-handler") {
1313
1521
  spinner.start("Configuring error handler in app.ts...");
@@ -1318,6 +1526,25 @@ var add = async (moduleName) => {
1318
1526
  spinner.warn("Could not find app.ts - error handler needs manual setup");
1319
1527
  }
1320
1528
  }
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
+ }
1321
1548
  let envConfigKey = resolvedModuleName;
1322
1549
  if (resolvedModuleName === "mailer" && mailerProvider === "resend") {
1323
1550
  envConfigKey = "mailer-resend";
@@ -1382,6 +1609,14 @@ var add = async (moduleName) => {
1382
1609
  console.log(import_chalk4.default.yellow("\u2139 Review SMTP configuration in .env to ensure values are correct."));
1383
1610
  }
1384
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
+ }
1385
1620
  } catch (error) {
1386
1621
  spinner.fail(import_chalk4.default.red(`Failed during ${currentStep}.`));
1387
1622
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1396,6 +1631,6 @@ ${import_chalk4.default.bold("Retry:")}`);
1396
1631
  var program = new import_commander.Command();
1397
1632
  program.name("zuro-cli").description("Zuro CLI tool").version("0.0.1");
1398
1633
  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);
1634
+ program.command("add <module>").description("Add a module to your project").action((module2, options) => add(module2, options));
1400
1635
  program.parse(process.argv);
1401
1636
  //# sourceMappingURL=index.js.map