zuro-cli 0.0.2-beta.15 → 0.0.2-beta.17

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
@@ -825,6 +825,20 @@ var ENV_CONFIGS = {
825
825
  { name: "BETTER_AUTH_URL", schema: "z.string().url()" }
826
826
  ]
827
827
  },
828
+ "auth-jwt": {
829
+ envVars: {
830
+ JWT_ACCESS_SECRET: "your-jwt-access-secret-at-least-32-characters",
831
+ JWT_REFRESH_SECRET: "your-jwt-refresh-secret-at-least-32-characters",
832
+ JWT_ACCESS_EXPIRES_IN: "15m",
833
+ JWT_REFRESH_EXPIRES_IN: "7d"
834
+ },
835
+ schemaFields: [
836
+ { name: "JWT_ACCESS_SECRET", schema: "z.string().min(32)" },
837
+ { name: "JWT_REFRESH_SECRET", schema: "z.string().min(32)" },
838
+ { name: "JWT_ACCESS_EXPIRES_IN", schema: "z.string().min(2)" },
839
+ { name: "JWT_REFRESH_EXPIRES_IN", schema: "z.string().min(2)" }
840
+ ]
841
+ },
828
842
  mailer: {
829
843
  envVars: {
830
844
  SMTP_HOST: "smtp.example.com",
@@ -1309,57 +1323,93 @@ init_code_inject();
1309
1323
  async function isAuthModuleInstalled(projectRoot, srcDir) {
1310
1324
  return await import_fs_extra8.default.pathExists(import_path9.default.join(projectRoot, srcDir, "lib", "auth.ts"));
1311
1325
  }
1312
- async function injectAuthRoutes(projectRoot, srcDir) {
1326
+ async function detectInstalledAuthProvider(projectRoot, srcDir) {
1327
+ const authPath = import_path9.default.join(projectRoot, srcDir, "lib", "auth.ts");
1328
+ if (!await import_fs_extra8.default.pathExists(authPath)) {
1329
+ return null;
1330
+ }
1331
+ const authContent = await import_fs_extra8.default.readFile(authPath, "utf-8");
1332
+ if (authContent.includes("better-auth")) {
1333
+ return "better-auth";
1334
+ }
1335
+ if (authContent.includes("jsonwebtoken") || authContent.includes("JWT_ACCESS_SECRET")) {
1336
+ return "jwt";
1337
+ }
1338
+ return null;
1339
+ }
1340
+ async function injectAuthRoutes(projectRoot, srcDir, provider) {
1313
1341
  const appPath = import_path9.default.join(projectRoot, srcDir, "app.ts");
1314
1342
  if (!await import_fs_extra8.default.pathExists(appPath)) {
1315
1343
  return false;
1316
1344
  }
1317
1345
  let appContent = await import_fs_extra8.default.readFile(appPath, "utf-8");
1318
- const authHandlerImport = `import { toNodeHandler } from "better-auth/node";`;
1319
- const authImport = `import { auth } from "./lib/auth";`;
1320
1346
  const routeIndexUserImport = `import userRoutes from "./user.routes";`;
1347
+ const routeIndexAuthImport = `import authRoutes from "./auth.routes";`;
1321
1348
  const appUserImport = `import userRoutes from "./routes/user.routes";`;
1349
+ const appAuthImport = `import authRoutes from "./routes/auth.routes";`;
1322
1350
  let appModified = false;
1323
- for (const importLine of [authHandlerImport, authImport]) {
1324
- const next = appendImport(appContent, importLine);
1325
- if (!next.inserted) {
1326
- return false;
1351
+ if (provider === "better-auth") {
1352
+ const authHandlerImport = `import { toNodeHandler } from "better-auth/node";`;
1353
+ const authImport = `import { auth } from "./lib/auth";`;
1354
+ for (const importLine of [authHandlerImport, authImport]) {
1355
+ const next = appendImport(appContent, importLine);
1356
+ if (!next.inserted) {
1357
+ return false;
1358
+ }
1359
+ if (next.source !== appContent) {
1360
+ appContent = next.source;
1361
+ appModified = true;
1362
+ }
1327
1363
  }
1328
- if (next.source !== appContent) {
1329
- appContent = next.source;
1364
+ const hasAuthMount = /toNodeHandler\(\s*auth\s*\)/.test(appContent) && /\/api\/auth/.test(appContent);
1365
+ if (!hasAuthMount) {
1366
+ const authMountLine = "app.all(/^\\/api\\/auth(?:\\/.*)?$/, toNodeHandler(auth));\n";
1367
+ const jsonIndex = appContent.search(/^\s*app\.use\(\s*express\.json\(\)\s*\);\s*$/m);
1368
+ let insertionIndex = jsonIndex;
1369
+ if (insertionIndex < 0) {
1370
+ const healthIndex = appContent.search(/^\s*app\.get\(\s*["']\/health["']\s*,/m);
1371
+ insertionIndex = healthIndex;
1372
+ }
1373
+ if (insertionIndex < 0) {
1374
+ const exportMatch = appContent.match(/export default app;?\s*$/m);
1375
+ insertionIndex = exportMatch?.index ?? -1;
1376
+ }
1377
+ if (insertionIndex < 0) {
1378
+ return false;
1379
+ }
1380
+ appContent = appContent.slice(0, insertionIndex) + authMountLine + appContent.slice(insertionIndex);
1330
1381
  appModified = true;
1331
1382
  }
1332
1383
  }
1333
- const hasAuthMount = /toNodeHandler\(\s*auth\s*\)/.test(appContent) && /\/api\/auth/.test(appContent);
1334
- if (!hasAuthMount) {
1335
- const authMountLine = "app.all(/^\\/api\\/auth(?:\\/.*)?$/, toNodeHandler(auth));\n";
1336
- const jsonIndex = appContent.search(/^\s*app\.use\(\s*express\.json\(\)\s*\);\s*$/m);
1337
- let insertionIndex = jsonIndex;
1338
- if (insertionIndex < 0) {
1339
- const healthIndex = appContent.search(/^\s*app\.get\(\s*["']\/health["']\s*,/m);
1340
- insertionIndex = healthIndex;
1341
- }
1342
- if (insertionIndex < 0) {
1343
- const exportMatch = appContent.match(/export default app;?\s*$/m);
1344
- insertionIndex = exportMatch?.index ?? -1;
1345
- }
1346
- if (insertionIndex < 0) {
1347
- return false;
1348
- }
1349
- appContent = appContent.slice(0, insertionIndex) + authMountLine + appContent.slice(insertionIndex);
1350
- appModified = true;
1351
- }
1352
1384
  const routeIndexPath = import_path9.default.join(projectRoot, srcDir, "routes", "index.ts");
1353
1385
  if (await import_fs_extra8.default.pathExists(routeIndexPath)) {
1354
1386
  let routeContent = await import_fs_extra8.default.readFile(routeIndexPath, "utf-8");
1355
1387
  let routeModified = false;
1356
- const userImportResult = appendImport(routeContent, routeIndexUserImport);
1357
- if (!userImportResult.inserted) {
1358
- return false;
1388
+ const routeImports = provider === "jwt" ? [routeIndexAuthImport, routeIndexUserImport] : [routeIndexUserImport];
1389
+ for (const importLine of routeImports) {
1390
+ const next = appendImport(routeContent, importLine);
1391
+ if (!next.inserted) {
1392
+ return false;
1393
+ }
1394
+ if (next.source !== routeContent) {
1395
+ routeContent = next.source;
1396
+ routeModified = true;
1397
+ }
1359
1398
  }
1360
- if (userImportResult.source !== routeContent) {
1361
- routeContent = userImportResult.source;
1362
- routeModified = true;
1399
+ if (provider === "jwt") {
1400
+ const hasAuthRoute = /rootRouter\.use\(\s*["']\/auth["']\s*,\s*authRoutes\s*\)/.test(routeContent);
1401
+ if (!hasAuthRoute) {
1402
+ const routeSetup = `
1403
+ // Auth routes
1404
+ rootRouter.use("/auth", authRoutes);
1405
+ `;
1406
+ const exportMatch = routeContent.match(/export default rootRouter;?\s*$/m);
1407
+ if (!exportMatch || exportMatch.index === void 0) {
1408
+ return false;
1409
+ }
1410
+ routeContent = routeContent.slice(0, exportMatch.index) + routeSetup + "\n" + routeContent.slice(exportMatch.index);
1411
+ routeModified = true;
1412
+ }
1363
1413
  }
1364
1414
  const hasUserRoute = /rootRouter\.use\(\s*["']\/users["']\s*,\s*userRoutes\s*\)/.test(routeContent);
1365
1415
  if (!hasUserRoute) {
@@ -1378,6 +1428,29 @@ rootRouter.use("/users", userRoutes);
1378
1428
  await import_fs_extra8.default.writeFile(routeIndexPath, routeContent);
1379
1429
  }
1380
1430
  } else {
1431
+ if (provider === "jwt") {
1432
+ const authImportResult = appendImport(appContent, appAuthImport);
1433
+ if (!authImportResult.inserted) {
1434
+ return false;
1435
+ }
1436
+ if (authImportResult.source !== appContent) {
1437
+ appContent = authImportResult.source;
1438
+ appModified = true;
1439
+ }
1440
+ const hasAuthRoute = /app\.use\(\s*["']\/api\/auth["']\s*,\s*authRoutes\s*\)/.test(appContent);
1441
+ if (!hasAuthRoute) {
1442
+ const exportMatch = appContent.match(/export default app;?\s*$/m);
1443
+ if (!exportMatch || exportMatch.index === void 0) {
1444
+ return false;
1445
+ }
1446
+ const routeSetup = `
1447
+ // Auth routes
1448
+ app.use("/api/auth", authRoutes);
1449
+ `;
1450
+ appContent = appContent.slice(0, exportMatch.index) + routeSetup + "\n" + appContent.slice(exportMatch.index);
1451
+ appModified = true;
1452
+ }
1453
+ }
1381
1454
  const hasUserRoute = /app\.use\(\s*["']\/api\/users["']\s*,\s*userRoutes\s*\)/.test(appContent);
1382
1455
  if (!hasUserRoute) {
1383
1456
  const exportMatch = appContent.match(/export default app;?\s*$/m);
@@ -1405,21 +1478,23 @@ app.use("/api/users", userRoutes);
1405
1478
  }
1406
1479
  return true;
1407
1480
  }
1408
- async function injectAuthDocs(projectRoot, srcDir) {
1481
+ async function injectAuthDocs(projectRoot, srcDir, provider) {
1409
1482
  const openApiPath = import_path9.default.join(projectRoot, srcDir, "lib", "openapi.ts");
1410
1483
  if (!await import_fs_extra8.default.pathExists(openApiPath)) {
1411
1484
  return false;
1412
1485
  }
1413
- const authMarker = "// ZURO_AUTH_DOCS";
1486
+ const betterAuthMarker = "// ZURO_AUTH_DOCS_BETTER_AUTH";
1487
+ const jwtMarker = "// ZURO_AUTH_DOCS_JWT";
1488
+ const legacyMarker = "// ZURO_AUTH_DOCS";
1414
1489
  let content = await import_fs_extra8.default.readFile(openApiPath, "utf-8");
1415
- if (content.includes(authMarker)) {
1490
+ if (content.includes(betterAuthMarker) || content.includes(jwtMarker) || content.includes(legacyMarker)) {
1416
1491
  return true;
1417
1492
  }
1418
1493
  const moduleDocsEndMarker = "// ZURO_DOCS_MODULES_END";
1419
1494
  if (!content.includes(moduleDocsEndMarker)) {
1420
1495
  return false;
1421
1496
  }
1422
- const authBlock = `
1497
+ const authBlock = provider === "jwt" ? `
1423
1498
  const authSignUpSchema = z.object({
1424
1499
  email: z.string().email().openapi({ example: "dev@company.com" }),
1425
1500
  password: z.string().min(8).openapi({ example: "strong-password" }),
@@ -1431,13 +1506,169 @@ const authSignInSchema = z.object({
1431
1506
  password: z.string().min(8).openapi({ example: "strong-password" }),
1432
1507
  });
1433
1508
 
1509
+ const authRefreshSchema = z.object({
1510
+ refreshToken: z.string().openapi({ example: "eyJhbGciOi..." }),
1511
+ });
1512
+
1513
+ const authTokenSchema = z.object({
1514
+ accessToken: z.string().openapi({ example: "eyJhbGciOi..." }),
1515
+ refreshToken: z.string().openapi({ example: "eyJhbGciOi..." }),
1516
+ tokenType: z.literal("Bearer").openapi({ example: "Bearer" }),
1517
+ });
1518
+
1434
1519
  const authUserSchema = z.object({
1435
1520
  id: z.string().openapi({ example: "user_123" }),
1436
1521
  email: z.string().email().openapi({ example: "dev@company.com" }),
1437
1522
  name: z.string().nullable().openapi({ example: "Dev User" }),
1438
1523
  });
1439
1524
 
1440
- ${authMarker}
1525
+ ${jwtMarker}
1526
+ registry.registerPath({
1527
+ method: "post",
1528
+ path: "/api/auth/sign-up/email",
1529
+ tags: ["Auth"],
1530
+ summary: "Register user and issue JWT tokens",
1531
+ request: {
1532
+ body: {
1533
+ content: {
1534
+ "application/json": {
1535
+ schema: authSignUpSchema,
1536
+ },
1537
+ },
1538
+ },
1539
+ },
1540
+ responses: {
1541
+ 200: {
1542
+ description: "Registration successful",
1543
+ content: {
1544
+ "application/json": {
1545
+ schema: z.object({ user: authUserSchema, ...authTokenSchema.shape }),
1546
+ },
1547
+ },
1548
+ },
1549
+ 409: { description: "Email already registered" },
1550
+ },
1551
+ });
1552
+
1553
+ registry.registerPath({
1554
+ method: "post",
1555
+ path: "/api/auth/sign-in/email",
1556
+ tags: ["Auth"],
1557
+ summary: "Sign in and issue JWT tokens",
1558
+ request: {
1559
+ body: {
1560
+ content: {
1561
+ "application/json": {
1562
+ schema: authSignInSchema,
1563
+ },
1564
+ },
1565
+ },
1566
+ },
1567
+ responses: {
1568
+ 200: {
1569
+ description: "Sign in successful",
1570
+ content: {
1571
+ "application/json": {
1572
+ schema: authTokenSchema,
1573
+ },
1574
+ },
1575
+ },
1576
+ 401: { description: "Invalid credentials" },
1577
+ },
1578
+ });
1579
+
1580
+ registry.registerPath({
1581
+ method: "post",
1582
+ path: "/api/auth/refresh",
1583
+ tags: ["Auth"],
1584
+ summary: "Exchange refresh token for new JWT tokens",
1585
+ request: {
1586
+ body: {
1587
+ content: {
1588
+ "application/json": {
1589
+ schema: authRefreshSchema,
1590
+ },
1591
+ },
1592
+ },
1593
+ },
1594
+ responses: {
1595
+ 200: {
1596
+ description: "Tokens refreshed",
1597
+ content: {
1598
+ "application/json": {
1599
+ schema: authTokenSchema,
1600
+ },
1601
+ },
1602
+ },
1603
+ 401: { description: "Invalid refresh token" },
1604
+ },
1605
+ });
1606
+
1607
+ registry.registerPath({
1608
+ method: "post",
1609
+ path: "/api/auth/sign-out",
1610
+ tags: ["Auth"],
1611
+ summary: "Client-side sign out for JWT auth",
1612
+ responses: {
1613
+ 200: { description: "Sign out successful" },
1614
+ },
1615
+ });
1616
+
1617
+ registry.registerPath({
1618
+ method: "get",
1619
+ path: "/api/auth/get-session",
1620
+ tags: ["Auth"],
1621
+ summary: "Get current JWT session user",
1622
+ security: [{ bearerAuth: [] }],
1623
+ responses: {
1624
+ 200: {
1625
+ description: "Current session",
1626
+ content: {
1627
+ "application/json": {
1628
+ schema: z.object({ user: authUserSchema.nullable() }),
1629
+ },
1630
+ },
1631
+ },
1632
+ },
1633
+ });
1634
+
1635
+ registry.registerPath({
1636
+ method: "get",
1637
+ path: "/api/users/me",
1638
+ tags: ["Auth"],
1639
+ summary: "Get current authenticated user",
1640
+ security: [{ bearerAuth: [] }],
1641
+ responses: {
1642
+ 200: {
1643
+ description: "Current user",
1644
+ content: {
1645
+ "application/json": {
1646
+ schema: z.object({ user: authUserSchema }),
1647
+ },
1648
+ },
1649
+ },
1650
+ 401: { description: "Not authenticated" },
1651
+ },
1652
+ });
1653
+ ` : `
1654
+ const authSignUpSchema = z.object({
1655
+ email: z.string().email().openapi({ example: "dev@company.com" }),
1656
+ password: z.string().min(8).openapi({ example: "strong-password" }),
1657
+ name: z.string().min(1).optional().openapi({ example: "Dev User" }),
1658
+ });
1659
+
1660
+ const authSignInSchema = z.object({
1661
+ email: z.string().email().openapi({ example: "dev@company.com" }),
1662
+ password: z.string().min(8).openapi({ example: "strong-password" }),
1663
+ });
1664
+
1665
+ const authUserSchema = z.object({
1666
+ id: z.string().openapi({ example: "user_123" }),
1667
+ email: z.string().email().openapi({ example: "dev@company.com" }),
1668
+ name: z.string().nullable().openapi({ example: "Dev User" }),
1669
+ });
1670
+
1671
+ ${betterAuthMarker}
1441
1672
  registry.registerPath({
1442
1673
  method: "post",
1443
1674
  path: "/api/auth/sign-up/email",
@@ -1514,7 +1745,27 @@ ${moduleDocsEndMarker}`);
1514
1745
  async function promptAuthConfig(projectRoot, srcDir, options) {
1515
1746
  const { isDocsModuleInstalled: isDocsModuleInstalled2 } = await Promise.resolve().then(() => (init_docs_handler(), docs_handler_exports));
1516
1747
  const docsInstalled = await isDocsModuleInstalled2(projectRoot, srcDir);
1748
+ let authProvider = "better-auth";
1517
1749
  let shouldInstallDocsForAuth = false;
1750
+ if (options.authProvider) {
1751
+ authProvider = options.authProvider;
1752
+ } else if (!options.yes) {
1753
+ const providerResponse = await (0, import_prompts3.default)({
1754
+ type: "select",
1755
+ name: "provider",
1756
+ message: "Choose auth provider:",
1757
+ choices: [
1758
+ { title: "Better Auth (session + plugin ecosystem)", value: "better-auth" },
1759
+ { title: "JWT (access/refresh tokens)", value: "jwt" }
1760
+ ],
1761
+ initial: 0
1762
+ });
1763
+ if (!providerResponse.provider) {
1764
+ console.log(import_chalk5.default.yellow("Operation cancelled."));
1765
+ return null;
1766
+ }
1767
+ authProvider = providerResponse.provider;
1768
+ }
1518
1769
  if (!docsInstalled) {
1519
1770
  if (options.yes) {
1520
1771
  shouldInstallDocsForAuth = true;
@@ -1534,13 +1785,19 @@ async function promptAuthConfig(projectRoot, srcDir, options) {
1534
1785
  }
1535
1786
  const { detectInstalledDatabaseDialect: detectInstalledDatabaseDialect2 } = await Promise.resolve().then(() => (init_database_handler(), database_handler_exports));
1536
1787
  const authDatabaseDialect = await detectInstalledDatabaseDialect2(projectRoot, srcDir);
1537
- return { shouldInstallDocsForAuth, authDatabaseDialect };
1788
+ return { authProvider, shouldInstallDocsForAuth, authDatabaseDialect };
1538
1789
  }
1539
- function printAuthHints(generatedAuthSecret) {
1540
- if (generatedAuthSecret) {
1541
- console.log(import_chalk5.default.yellow("\u2139 BETTER_AUTH_SECRET was generated automatically."));
1790
+ function printAuthHints(generatedAuthSecret, provider) {
1791
+ if (provider === "better-auth") {
1792
+ if (generatedAuthSecret) {
1793
+ console.log(import_chalk5.default.yellow("\u2139 BETTER_AUTH_SECRET was generated automatically."));
1794
+ } else {
1795
+ console.log(import_chalk5.default.yellow("\u2139 Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env."));
1796
+ }
1797
+ } else if (generatedAuthSecret) {
1798
+ console.log(import_chalk5.default.yellow("\u2139 JWT_ACCESS_SECRET and JWT_REFRESH_SECRET were generated automatically."));
1542
1799
  } else {
1543
- console.log(import_chalk5.default.yellow("\u2139 Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env."));
1800
+ console.log(import_chalk5.default.yellow("\u2139 Review JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, and token TTLs in .env."));
1544
1801
  }
1545
1802
  console.log(import_chalk5.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
1546
1803
  }
@@ -1793,27 +2050,15 @@ var UPLOAD_PRESETS = {
1793
2050
  mimeTypes: ["video/mp4", "video/quicktime", "video/webm"],
1794
2051
  maxFileSize: 100 * 1024 * 1024,
1795
2052
  maxFiles: 1
1796
- },
1797
- mixed: {
1798
- mimeTypes: [
1799
- "image/jpeg",
1800
- "image/png",
1801
- "image/webp",
1802
- "application/pdf",
1803
- "text/plain",
1804
- "video/mp4"
1805
- ],
1806
- maxFileSize: 25 * 1024 * 1024,
1807
- maxFiles: 5
1808
2053
  }
1809
2054
  };
1810
2055
  function getUploadEnvSchemaFields(provider) {
1811
2056
  const shared = [
1812
2057
  { name: "UPLOAD_PROVIDER", schema: `z.enum(["s3", "r2", "cloudinary"])` },
1813
2058
  { name: "UPLOAD_MODE", schema: `z.enum(["proxy", "direct", "large"])` },
1814
- { name: "UPLOAD_AUTH_MODE", schema: `z.enum(["required", "optional", "none"])` },
2059
+ { name: "UPLOAD_AUTH_MODE", schema: `z.enum(["required", "none"])` },
1815
2060
  { name: "UPLOAD_FILE_ACCESS", schema: `z.enum(["private", "public"])` },
1816
- { name: "UPLOAD_FILE_PRESET", schema: `z.enum(["image", "document", "video", "mixed"])` },
2061
+ { name: "UPLOAD_FILE_PRESET", schema: `z.enum(["image", "document", "video"])` },
1817
2062
  { name: "UPLOAD_KEY_PREFIX", schema: "z.string().min(1)" },
1818
2063
  { name: "UPLOAD_ALLOWED_MIME", schema: "z.string().min(1)" },
1819
2064
  { name: "UPLOAD_MAX_FILE_SIZE", schema: "z.coerce.number().positive()" },
@@ -1848,103 +2093,24 @@ async function isAuthInstalled(projectRoot, srcDir) {
1848
2093
  function hasDrizzleDatabase(config) {
1849
2094
  return config?.database?.orm === "drizzle";
1850
2095
  }
1851
- async function promptCredentials(provider) {
1852
- console.log(import_chalk7.default.dim(" Tip: Leave fields blank to use placeholders and configure later.\n"));
2096
+ function getProviderEnvDefaults(provider) {
1853
2097
  if (provider === "cloudinary") {
1854
- const response2 = await (0, import_prompts5.default)([
1855
- {
1856
- type: "text",
1857
- name: "cloudName",
1858
- message: "Cloudinary cloud name",
1859
- initial: ""
1860
- },
1861
- {
1862
- type: "text",
1863
- name: "apiKey",
1864
- message: "Cloudinary API key",
1865
- initial: ""
1866
- },
1867
- {
1868
- type: "password",
1869
- name: "apiSecret",
1870
- message: "Cloudinary API secret"
1871
- },
1872
- {
1873
- type: "text",
1874
- name: "folder",
1875
- message: "Cloudinary folder",
1876
- initial: "uploads"
1877
- },
1878
- {
1879
- type: "text",
1880
- name: "uploadPreset",
1881
- message: "Cloudinary upload preset (optional)",
1882
- initial: ""
1883
- }
1884
- ]);
1885
- if (response2.cloudName === void 0) {
1886
- console.log(import_chalk7.default.yellow("Operation cancelled."));
1887
- return null;
1888
- }
1889
- const values2 = {
1890
- CLOUDINARY_CLOUD_NAME: response2.cloudName?.trim() || "your-cloud-name",
1891
- CLOUDINARY_API_KEY: response2.apiKey?.trim() || "your-api-key",
1892
- CLOUDINARY_API_SECRET: response2.apiSecret?.trim() || "your-api-secret",
1893
- CLOUDINARY_FOLDER: response2.folder?.trim() || "uploads",
1894
- CLOUDINARY_UPLOAD_PRESET: response2.uploadPreset?.trim() || ""
2098
+ return {
2099
+ CLOUDINARY_CLOUD_NAME: "your-cloud-name",
2100
+ CLOUDINARY_API_KEY: "your-api-key",
2101
+ CLOUDINARY_API_SECRET: "your-api-secret",
2102
+ CLOUDINARY_FOLDER: "uploads",
2103
+ CLOUDINARY_UPLOAD_PRESET: ""
1895
2104
  };
1896
- return values2;
1897
- }
1898
- const response = await (0, import_prompts5.default)([
1899
- {
1900
- type: "text",
1901
- name: "bucket",
1902
- message: `${provider.toUpperCase()} bucket name`,
1903
- initial: ""
1904
- },
1905
- {
1906
- type: "text",
1907
- name: "region",
1908
- message: `${provider.toUpperCase()} region`,
1909
- initial: provider === "r2" ? "auto" : "us-east-1"
1910
- },
1911
- {
1912
- type: "text",
1913
- name: "endpoint",
1914
- message: provider === "r2" ? "R2 S3 endpoint" : "Custom S3 endpoint (optional)",
1915
- initial: provider === "r2" ? "https://<account-id>.r2.cloudflarestorage.com" : ""
1916
- },
1917
- {
1918
- type: "text",
1919
- name: "accessKeyId",
1920
- message: "Access key ID",
1921
- initial: ""
1922
- },
1923
- {
1924
- type: "password",
1925
- name: "secretAccessKey",
1926
- message: "Secret access key"
1927
- },
1928
- {
1929
- type: "text",
1930
- name: "publicBaseUrl",
1931
- message: "Public base URL (optional)",
1932
- initial: ""
1933
- }
1934
- ]);
1935
- if (response.bucket === void 0) {
1936
- console.log(import_chalk7.default.yellow("Operation cancelled."));
1937
- return null;
1938
2105
  }
1939
- const values = {
1940
- UPLOAD_BUCKET: response.bucket?.trim() || `your-${provider}-bucket`,
1941
- UPLOAD_REGION: response.region?.trim() || (provider === "r2" ? "auto" : "us-east-1"),
1942
- UPLOAD_ENDPOINT: response.endpoint?.trim() || (provider === "r2" ? "https://<account-id>.r2.cloudflarestorage.com" : ""),
1943
- UPLOAD_ACCESS_KEY_ID: response.accessKeyId?.trim() || "your-access-key-id",
1944
- UPLOAD_SECRET_ACCESS_KEY: response.secretAccessKey?.trim() || "your-secret-access-key",
1945
- UPLOAD_PUBLIC_BASE_URL: response.publicBaseUrl?.trim() || ""
2106
+ return {
2107
+ UPLOAD_BUCKET: `your-${provider}-bucket`,
2108
+ UPLOAD_REGION: provider === "r2" ? "auto" : "us-east-1",
2109
+ UPLOAD_ENDPOINT: provider === "r2" ? "https://<account-id>.r2.cloudflarestorage.com" : "",
2110
+ UPLOAD_ACCESS_KEY_ID: "your-access-key-id",
2111
+ UPLOAD_SECRET_ACCESS_KEY: "your-secret-access-key",
2112
+ UPLOAD_PUBLIC_BASE_URL: ""
1946
2113
  };
1947
- return values;
1948
2114
  }
1949
2115
  function buildSharedEnvVars(provider, mode, authMode, access, preset, maxFileSize, maxFiles) {
1950
2116
  return {
@@ -1995,10 +2161,9 @@ async function promptUploadsConfig(projectRoot, srcDir) {
1995
2161
  message: "Who can upload?",
1996
2162
  choices: [
1997
2163
  { title: "Authenticated only", value: "required" },
1998
- { title: "Optional auth", value: "optional" },
1999
2164
  { title: "Public", value: "none" }
2000
2165
  ],
2001
- initial: authInstalled ? 0 : 2
2166
+ initial: authInstalled ? 0 : 1
2002
2167
  },
2003
2168
  {
2004
2169
  type: "select",
@@ -2017,16 +2182,9 @@ async function promptUploadsConfig(projectRoot, srcDir) {
2017
2182
  choices: [
2018
2183
  { title: "Image", value: "image" },
2019
2184
  { title: "Document", value: "document" },
2020
- { title: "Video", value: "video" },
2021
- { title: "Mixed", value: "mixed" }
2185
+ { title: "Video", value: "video" }
2022
2186
  ],
2023
2187
  initial: 0
2024
- },
2025
- {
2026
- type: "confirm",
2027
- name: "useDefaults",
2028
- message: "Use recommended upload limits for this preset?",
2029
- initial: true
2030
2188
  }
2031
2189
  ]);
2032
2190
  if (initial.provider === void 0) {
@@ -2043,7 +2201,7 @@ async function promptUploadsConfig(projectRoot, srcDir) {
2043
2201
  console.log(import_chalk7.default.yellow("Use S3 or R2 for large uploads, or pick Proxy/Direct for Cloudinary.\n"));
2044
2202
  return null;
2045
2203
  }
2046
- if (provider === "cloudinary" && (preset === "document" || preset === "mixed")) {
2204
+ if (provider === "cloudinary" && preset === "document") {
2047
2205
  const warning = await (0, import_prompts5.default)({
2048
2206
  type: "confirm",
2049
2207
  name: "continue",
@@ -2056,50 +2214,19 @@ async function promptUploadsConfig(projectRoot, srcDir) {
2056
2214
  }
2057
2215
  }
2058
2216
  const presetDefaults = UPLOAD_PRESETS[preset];
2059
- let maxFileSize = presetDefaults.maxFileSize;
2060
- let maxFiles = presetDefaults.maxFiles;
2061
- if (!initial.useDefaults) {
2062
- const custom = await (0, import_prompts5.default)([
2063
- {
2064
- type: "number",
2065
- name: "maxFileSizeMb",
2066
- message: "Max file size (MB)",
2067
- initial: Math.max(1, Math.round(presetDefaults.maxFileSize / (1024 * 1024))),
2068
- min: 1
2069
- },
2070
- {
2071
- type: "number",
2072
- name: "maxFiles",
2073
- message: "Max files per request",
2074
- initial: presetDefaults.maxFiles,
2075
- min: 1,
2076
- max: 20
2077
- }
2078
- ]);
2079
- if (custom.maxFileSizeMb === void 0) {
2080
- console.log(import_chalk7.default.yellow("Operation cancelled."));
2081
- return null;
2082
- }
2083
- maxFileSize = Number(custom.maxFileSizeMb) * 1024 * 1024;
2084
- maxFiles = Number(custom.maxFiles);
2085
- }
2086
2217
  let useDatabaseMetadata = false;
2087
2218
  let shouldInstallDatabase = false;
2088
2219
  const metadataPrompt = await (0, import_prompts5.default)({
2089
- type: "select",
2220
+ type: "confirm",
2090
2221
  name: "metadata",
2091
- message: "Upload metadata storage?",
2092
- choices: [
2093
- { title: drizzleInstalled ? "Database" : "Install database + track uploads", value: "db" },
2094
- { title: "No metadata", value: "none" }
2095
- ],
2096
- initial: 0
2222
+ message: drizzleInstalled ? "Store upload metadata in your database?" : "Install database and store upload metadata?",
2223
+ initial: true
2097
2224
  });
2098
2225
  if (metadataPrompt.metadata === void 0) {
2099
2226
  console.log(import_chalk7.default.yellow("Operation cancelled."));
2100
2227
  return null;
2101
2228
  }
2102
- if (metadataPrompt.metadata === "db") {
2229
+ if (metadataPrompt.metadata === true) {
2103
2230
  useDatabaseMetadata = true;
2104
2231
  if (!projectConfig?.database) {
2105
2232
  shouldInstallDatabase = true;
@@ -2135,10 +2262,6 @@ async function promptUploadsConfig(projectRoot, srcDir) {
2135
2262
  }
2136
2263
  shouldInstallAuth = true;
2137
2264
  }
2138
- const providerEnv = await promptCredentials(provider);
2139
- if (!providerEnv) {
2140
- return null;
2141
- }
2142
2265
  return {
2143
2266
  provider,
2144
2267
  mode,
@@ -2149,8 +2272,8 @@ async function promptUploadsConfig(projectRoot, srcDir) {
2149
2272
  shouldInstallAuth,
2150
2273
  shouldInstallDatabase,
2151
2274
  envVars: {
2152
- ...buildSharedEnvVars(provider, mode, authMode, access, preset, maxFileSize, maxFiles),
2153
- ...providerEnv
2275
+ ...buildSharedEnvVars(provider, mode, authMode, access, preset, presetDefaults.maxFileSize, presetDefaults.maxFiles),
2276
+ ...getProviderEnvDefaults(provider)
2154
2277
  }
2155
2278
  };
2156
2279
  }
@@ -2386,6 +2509,7 @@ ${moduleDocsEndMarker}`);
2386
2509
  function printUploadHints(result) {
2387
2510
  console.log(import_chalk7.default.yellow("\u2139 Upload routes are mounted at: /api/uploads"));
2388
2511
  console.log(import_chalk7.default.yellow(`\u2139 Provider: ${result.provider} \xB7 Mode: ${result.mode} \xB7 Access: ${result.access}`));
2512
+ console.log(import_chalk7.default.yellow("\u2139 Fill the generated upload env vars in .env before testing uploads."));
2389
2513
  if (result.mode === "proxy") {
2390
2514
  console.log(import_chalk7.default.yellow("\u2139 Reuse uploadSingle()/uploadArray() from src/lib/uploads/proxy.ts in your own form + file routes."));
2391
2515
  }
@@ -2456,6 +2580,8 @@ var add = async (moduleName, options = {}) => {
2456
2580
  let selectedDatabaseDialect = null;
2457
2581
  let generatedAuthSecret = false;
2458
2582
  let authDatabaseDialect = null;
2583
+ let authProvider = options.authProvider || "better-auth";
2584
+ let uploadAuthProvider = null;
2459
2585
  let customSmtpVars;
2460
2586
  let usedDefaultSmtp = false;
2461
2587
  let mailerProvider = "smtp";
@@ -2482,6 +2608,7 @@ var add = async (moduleName, options = {}) => {
2482
2608
  if (resolvedModuleName === "auth") {
2483
2609
  const result = await promptAuthConfig(projectRoot, srcDir, options);
2484
2610
  if (!result) return;
2611
+ authProvider = result.authProvider;
2485
2612
  shouldInstallDocsForAuth = result.shouldInstallDocsForAuth;
2486
2613
  authDatabaseDialect = result.authDatabaseDialect;
2487
2614
  }
@@ -2508,6 +2635,11 @@ var add = async (moduleName, options = {}) => {
2508
2635
  currentStep = "module dependency resolution";
2509
2636
  await resolveDependencies(moduleDeps, projectRoot);
2510
2637
  if (resolvedModuleName === "uploads" && uploadConfig) {
2638
+ const errorHandlerInstalled = import_fs_extra12.default.existsSync(import_path13.default.join(projectRoot, srcDir, "lib", "errors.ts"));
2639
+ if (!errorHandlerInstalled) {
2640
+ console.log(import_chalk8.default.blue("\n\u2139 Uploads needs the error-handler module. Installing error-handler..."));
2641
+ await add("error-handler");
2642
+ }
2511
2643
  if (uploadConfig.shouldInstallDatabase) {
2512
2644
  console.log(import_chalk8.default.blue("\n\u2139 Upload metadata needs a Drizzle database. Installing database module..."));
2513
2645
  await add("database");
@@ -2517,6 +2649,7 @@ var add = async (moduleName, options = {}) => {
2517
2649
  await add("auth", { yes: true });
2518
2650
  }
2519
2651
  uploadDatabaseDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);
2652
+ uploadAuthProvider = await detectInstalledAuthProvider(projectRoot, srcDir);
2520
2653
  if (uploadConfig.useDatabaseMetadata) {
2521
2654
  if (uploadDatabaseDialect === "database-prisma-pg" || uploadDatabaseDialect === "database-prisma-mysql") {
2522
2655
  spinner.fail("Uploads metadata currently supports Drizzle-based database setup only.");
@@ -2551,6 +2684,15 @@ var add = async (moduleName, options = {}) => {
2551
2684
  devDeps = ["@types/nodemailer"];
2552
2685
  }
2553
2686
  }
2687
+ if (resolvedModuleName === "auth") {
2688
+ if (authProvider === "jwt") {
2689
+ runtimeDeps = ["jsonwebtoken", "bcryptjs"];
2690
+ devDeps = ["@types/jsonwebtoken", "@types/bcryptjs"];
2691
+ } else {
2692
+ runtimeDeps = ["better-auth"];
2693
+ devDeps = [];
2694
+ }
2695
+ }
2554
2696
  if (resolvedModuleName === "uploads" && uploadConfig) {
2555
2697
  runtimeDeps = ["multer"];
2556
2698
  devDeps = ["@types/multer"];
@@ -2569,11 +2711,46 @@ var add = async (moduleName, options = {}) => {
2569
2711
  let fetchPath = file.path;
2570
2712
  let expectedSha256 = file.sha256;
2571
2713
  let expectedSize = file.size;
2714
+ if (resolvedModuleName === "auth" && authProvider === "better-auth" && (file.target === "routes/auth.routes.ts" || file.target === "controllers/auth.controller.ts")) {
2715
+ continue;
2716
+ }
2572
2717
  if (resolvedModuleName === "auth" && file.target === "db/schema/auth.ts" && authDatabaseDialect === "database-mysql") {
2573
2718
  fetchPath = "express/db/schema/auth.mysql.ts";
2574
2719
  expectedSha256 = void 0;
2575
2720
  expectedSize = void 0;
2576
2721
  }
2722
+ if (resolvedModuleName === "auth" && authProvider === "jwt") {
2723
+ if (file.target === "lib/auth.ts") {
2724
+ fetchPath = "express/lib/auth.jwt.ts";
2725
+ expectedSha256 = void 0;
2726
+ expectedSize = void 0;
2727
+ }
2728
+ if (file.target === "controllers/user.controller.ts") {
2729
+ fetchPath = "express/controllers/user.controller.jwt.ts";
2730
+ expectedSha256 = void 0;
2731
+ expectedSize = void 0;
2732
+ }
2733
+ if (file.target === "routes/user.routes.ts") {
2734
+ fetchPath = "express/routes/user.routes.jwt.ts";
2735
+ expectedSha256 = void 0;
2736
+ expectedSize = void 0;
2737
+ }
2738
+ if (file.target === "controllers/auth.controller.ts") {
2739
+ fetchPath = "express/controllers/auth.controller.jwt.ts";
2740
+ expectedSha256 = void 0;
2741
+ expectedSize = void 0;
2742
+ }
2743
+ if (file.target === "routes/auth.routes.ts") {
2744
+ fetchPath = "express/routes/auth.routes.jwt.ts";
2745
+ expectedSha256 = void 0;
2746
+ expectedSize = void 0;
2747
+ }
2748
+ if (file.target === "db/schema/auth.ts") {
2749
+ fetchPath = authDatabaseDialect === "database-mysql" ? "express/db/schema/auth.mysql.jwt.ts" : "express/db/schema/auth.jwt.ts";
2750
+ expectedSha256 = void 0;
2751
+ expectedSize = void 0;
2752
+ }
2753
+ }
2577
2754
  if (resolvedModuleName === "mailer" && file.target === "lib/mailer.ts" && mailerProvider === "resend") {
2578
2755
  fetchPath = "express/lib/mailer.resend.ts";
2579
2756
  expectedSha256 = void 0;
@@ -2591,7 +2768,12 @@ var add = async (moduleName, options = {}) => {
2591
2768
  expectedSize = void 0;
2592
2769
  }
2593
2770
  if (file.target === "middleware/upload-auth.ts") {
2594
- fetchPath = `express/middleware/upload-auth.${uploadConfig.authMode}.ts`;
2771
+ if (uploadConfig.authMode === "none") {
2772
+ fetchPath = "express/middleware/upload-auth.none.ts";
2773
+ } else {
2774
+ const providerSuffix = uploadAuthProvider === "jwt" ? "jwt" : "better-auth";
2775
+ fetchPath = `express/middleware/upload-auth.required.${providerSuffix}.ts`;
2776
+ }
2595
2777
  expectedSha256 = void 0;
2596
2778
  expectedSize = void 0;
2597
2779
  }
@@ -2627,7 +2809,7 @@ var add = async (moduleName, options = {}) => {
2627
2809
  spinner.succeed("Files generated");
2628
2810
  if (resolvedModuleName === "auth") {
2629
2811
  spinner.start("Configuring routes...");
2630
- const injected = await injectAuthRoutes(projectRoot, srcDir);
2812
+ const injected = await injectAuthRoutes(projectRoot, srcDir, authProvider);
2631
2813
  if (injected) {
2632
2814
  spinner.succeed("Routes configured");
2633
2815
  } else {
@@ -2636,7 +2818,7 @@ var add = async (moduleName, options = {}) => {
2636
2818
  const docsInstalled = await isDocsModuleInstalled(projectRoot, srcDir);
2637
2819
  if (docsInstalled) {
2638
2820
  spinner.start("Adding auth endpoints to API docs...");
2639
- const authDocsInjected = await injectAuthDocs(projectRoot, srcDir);
2821
+ const authDocsInjected = await injectAuthDocs(projectRoot, srcDir, authProvider);
2640
2822
  if (authDocsInjected) {
2641
2823
  spinner.succeed("Auth endpoints added to API docs");
2642
2824
  } else {
@@ -2672,8 +2854,9 @@ var add = async (moduleName, options = {}) => {
2672
2854
  }
2673
2855
  const authInstalled = await isAuthModuleInstalled(projectRoot, srcDir);
2674
2856
  if (authInstalled) {
2857
+ const installedAuthProvider = await detectInstalledAuthProvider(projectRoot, srcDir) || "better-auth";
2675
2858
  spinner.start("Adding auth endpoints to API docs...");
2676
- const authDocsInjected = await injectAuthDocs(projectRoot, srcDir);
2859
+ const authDocsInjected = await injectAuthDocs(projectRoot, srcDir, installedAuthProvider);
2677
2860
  if (authDocsInjected) {
2678
2861
  spinner.succeed("Auth endpoints added to API docs");
2679
2862
  } else {
@@ -2717,6 +2900,9 @@ var add = async (moduleName, options = {}) => {
2717
2900
  if (resolvedModuleName === "mailer" && mailerProvider === "resend") {
2718
2901
  envConfigKey = "mailer-resend";
2719
2902
  }
2903
+ if (resolvedModuleName === "auth" && authProvider === "jwt") {
2904
+ envConfigKey = "auth-jwt";
2905
+ }
2720
2906
  const envConfig = ENV_CONFIGS[envConfigKey];
2721
2907
  if (envConfig) {
2722
2908
  currentStep = "environment configuration";
@@ -2729,10 +2915,23 @@ var add = async (moduleName, options = {}) => {
2729
2915
  Object.assign(envVars, customSmtpVars);
2730
2916
  }
2731
2917
  if (resolvedModuleName === "auth") {
2732
- const hasExistingSecret = await hasEnvVariable(projectRoot, "BETTER_AUTH_SECRET");
2733
- if (!hasExistingSecret) {
2734
- envVars.BETTER_AUTH_SECRET = (0, import_node_crypto2.randomBytes)(32).toString("hex");
2735
- generatedAuthSecret = true;
2918
+ if (authProvider === "better-auth") {
2919
+ const hasExistingSecret = await hasEnvVariable(projectRoot, "BETTER_AUTH_SECRET");
2920
+ if (!hasExistingSecret) {
2921
+ envVars.BETTER_AUTH_SECRET = (0, import_node_crypto2.randomBytes)(32).toString("hex");
2922
+ generatedAuthSecret = true;
2923
+ }
2924
+ } else {
2925
+ const hasAccessSecret = await hasEnvVariable(projectRoot, "JWT_ACCESS_SECRET");
2926
+ if (!hasAccessSecret) {
2927
+ envVars.JWT_ACCESS_SECRET = (0, import_node_crypto2.randomBytes)(32).toString("hex");
2928
+ generatedAuthSecret = true;
2929
+ }
2930
+ const hasRefreshSecret = await hasEnvVariable(projectRoot, "JWT_REFRESH_SECRET");
2931
+ if (!hasRefreshSecret) {
2932
+ envVars.JWT_REFRESH_SECRET = (0, import_node_crypto2.randomBytes)(32).toString("hex");
2933
+ generatedAuthSecret = true;
2934
+ }
2736
2935
  }
2737
2936
  }
2738
2937
  await updateEnvFile(projectRoot, envVars, true, {
@@ -2773,7 +2972,7 @@ var add = async (moduleName, options = {}) => {
2773
2972
  printDatabaseHints(resolvedModuleName, customDbUrl, usedDefaultDbUrl, databaseBackupPath);
2774
2973
  }
2775
2974
  if (resolvedModuleName === "auth") {
2776
- printAuthHints(generatedAuthSecret);
2975
+ printAuthHints(generatedAuthSecret, authProvider);
2777
2976
  }
2778
2977
  if (resolvedModuleName === "mailer") {
2779
2978
  printMailerHints(usedDefaultSmtp);
@@ -2802,6 +3001,15 @@ ${import_chalk8.default.bold("Retry:")}`);
2802
3001
  var program = new import_commander.Command();
2803
3002
  program.name("zuro-cli").description("Zuro CLI tool").version("0.0.1");
2804
3003
  program.command("init").description("Initialize a new Zuro project").action(init);
2805
- program.command("add <module>").description("Add a module to your project").action((module2, options) => add(module2, options));
3004
+ program.command("add <module>").description("Add a module to your project").option(
3005
+ "--auth-provider <provider>",
3006
+ "Auth provider for auth module (better-auth|jwt)",
3007
+ (value) => {
3008
+ if (value === "better-auth" || value === "jwt") {
3009
+ return value;
3010
+ }
3011
+ throw new import_commander.InvalidArgumentError("auth-provider must be 'better-auth' or 'jwt'");
3012
+ }
3013
+ ).option("-y, --yes", "Skip prompts and use defaults").action((module2, options) => add(module2, options));
2806
3014
  program.parse(process.argv);
2807
3015
  //# sourceMappingURL=index.js.map