zuro-cli 0.0.2-beta.4 → 0.0.2-beta.6

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.mjs CHANGED
@@ -179,18 +179,23 @@ async function ensurePackageManagerAvailable(pm) {
179
179
  );
180
180
  }
181
181
  }
182
- async function initPackageJson(cwd, force = false, packageName = "zuro-app", srcDir = "src") {
182
+ async function initPackageJson(cwd, force = false, packageName = "zuro-app", srcDir = "src", options = {}) {
183
183
  const pkgPath = path.join(cwd, "package.json");
184
184
  if (force || !await fs.pathExists(pkgPath)) {
185
+ const scripts = {
186
+ "dev": `tsx watch ${srcDir}/server.ts`,
187
+ "build": "tsc",
188
+ "start": "node dist/server.js"
189
+ };
190
+ if (options.enablePrettier) {
191
+ scripts["format"] = "prettier --write .";
192
+ scripts["format:check"] = "prettier --check .";
193
+ }
185
194
  await fs.writeJson(pkgPath, {
186
195
  name: normalizePackageName(packageName),
187
196
  version: "0.0.1",
188
197
  private: true,
189
- scripts: {
190
- "dev": `tsx watch ${srcDir}/server.ts`,
191
- "build": "tsc",
192
- "start": "node dist/server.js"
193
- }
198
+ scripts
194
199
  }, { spaces: 2 });
195
200
  }
196
201
  }
@@ -379,6 +384,13 @@ function showNonZuroProjectMessage() {
379
384
  console.log("- a fresh/empty directory, or");
380
385
  console.log("- an existing project already managed by Zuro CLI.");
381
386
  }
387
+ function showInitFirstMessage() {
388
+ console.log(chalk.yellow("No Zuro project found in this directory."));
389
+ console.log("");
390
+ console.log(chalk.yellow("Run init first, then add modules."));
391
+ console.log("");
392
+ console.log(chalk.cyan("npx zuro-cli init"));
393
+ }
382
394
 
383
395
  // src/commands/init.ts
384
396
  function resolveSafeTargetPath(projectRoot, srcDir, file) {
@@ -408,6 +420,33 @@ async function ensureSafeTargetDirectory(targetDir, cwd, projectName) {
408
420
  });
409
421
  return response.proceed === true;
410
422
  }
423
+ async function setupPrettier(targetDir) {
424
+ const prettierConfigPath = path4.join(targetDir, ".prettierrc");
425
+ const prettierIgnorePath = path4.join(targetDir, ".prettierignore");
426
+ if (!await fs4.pathExists(prettierConfigPath)) {
427
+ const prettierConfig = {
428
+ semi: true,
429
+ singleQuote: false,
430
+ trailingComma: "es5",
431
+ printWidth: 100,
432
+ tabWidth: 2
433
+ };
434
+ await fs4.writeJson(prettierConfigPath, prettierConfig, { spaces: 2 });
435
+ }
436
+ if (!await fs4.pathExists(prettierIgnorePath)) {
437
+ const ignoreContent = `node_modules
438
+ dist
439
+ build
440
+ coverage
441
+ .next
442
+ pnpm-lock.yaml
443
+ package-lock.json
444
+ bun.lock
445
+ bun.lockb
446
+ `;
447
+ await fs4.writeFile(prettierIgnorePath, ignoreContent);
448
+ }
449
+ }
411
450
  async function init() {
412
451
  const cwd = process.cwd();
413
452
  const isExistingProject = await fs4.pathExists(path4.join(cwd, "package.json"));
@@ -420,6 +459,7 @@ async function init() {
420
459
  let pm = "npm";
421
460
  let srcDir = "src";
422
461
  let projectName = path4.basename(cwd);
462
+ let enablePrettier = false;
423
463
  if (isExistingProject) {
424
464
  console.log(chalk2.blue("\u2139 Existing project detected."));
425
465
  projectName = path4.basename(cwd);
@@ -457,6 +497,12 @@ async function init() {
457
497
  { title: "bun", value: "bun" }
458
498
  ],
459
499
  initial: 0
500
+ },
501
+ {
502
+ type: "confirm",
503
+ name: "prettier",
504
+ message: "Setup Prettier?",
505
+ initial: true
460
506
  }
461
507
  ]);
462
508
  if (response.pm === void 0) {
@@ -464,6 +510,7 @@ async function init() {
464
510
  return;
465
511
  }
466
512
  pm = response.pm;
513
+ enablePrettier = response.prettier === true;
467
514
  srcDir = "src";
468
515
  if (!response.path || response.path.trim() === "") {
469
516
  projectName = path4.basename(cwd);
@@ -502,7 +549,7 @@ async function init() {
502
549
  spinner.text = "Initializing project...";
503
550
  const hasPackageJson = await fs4.pathExists(path4.join(targetDir, "package.json"));
504
551
  if (!hasPackageJson) {
505
- await initPackageJson(targetDir, true, projectName, srcDir);
552
+ await initPackageJson(targetDir, true, projectName, srcDir, { enablePrettier });
506
553
  }
507
554
  currentStep = "dependency installation";
508
555
  spinner.text = `Installing dependencies using ${pm}...`;
@@ -519,6 +566,9 @@ async function init() {
519
566
  }
520
567
  await installDependencies(pm, runtimeDeps, targetDir);
521
568
  await installDependencies(pm, devDeps, targetDir, { dev: true });
569
+ if (enablePrettier) {
570
+ await installDependencies(pm, ["prettier"], targetDir, { dev: true });
571
+ }
522
572
  currentStep = "module file generation";
523
573
  spinner.text = "Fetching core module files...";
524
574
  for (const file of coreModule.files) {
@@ -544,6 +594,10 @@ async function init() {
544
594
  }
545
595
  currentStep = "environment file setup";
546
596
  await createInitialEnv(targetDir);
597
+ if (enablePrettier) {
598
+ currentStep = "prettier setup";
599
+ await setupPrettier(targetDir);
600
+ }
547
601
  currentStep = "config write";
548
602
  await writeZuroConfig(targetDir, zuroConfig);
549
603
  spinner.succeed(chalk2.green("Project initialized successfully!"));
@@ -728,6 +782,12 @@ function getDatabaseSetupHint(moduleName, dbUrl) {
728
782
  return moduleName === "database-pg" ? "createdb <database_name>" : `mysql -e "CREATE DATABASE IF NOT EXISTS <database_name>;"`;
729
783
  }
730
784
  }
785
+ function getModuleDocsPath(moduleName) {
786
+ if (isDatabaseModule(moduleName)) {
787
+ return "database";
788
+ }
789
+ return moduleName;
790
+ }
731
791
  function escapeRegex(value) {
732
792
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
733
793
  }
@@ -740,6 +800,34 @@ async function hasEnvVariable(projectRoot, key) {
740
800
  const pattern = new RegExp(`^${escapeRegex(key)}=`, "m");
741
801
  return pattern.test(content);
742
802
  }
803
+ async function isLikelyEmptyDirectory(cwd) {
804
+ const entries = await fs6.readdir(cwd);
805
+ const ignored = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
806
+ return entries.filter((entry) => !ignored.has(entry.toLowerCase())).length === 0;
807
+ }
808
+ async function ensureSchemaExport(projectRoot, srcDir, schemaFileName) {
809
+ const schemaIndexPath = path6.join(projectRoot, srcDir, "db", "schema", "index.ts");
810
+ if (!await fs6.pathExists(schemaIndexPath)) {
811
+ return;
812
+ }
813
+ const exportLine = `export * from "./${schemaFileName}";`;
814
+ const content = await fs6.readFile(schemaIndexPath, "utf-8");
815
+ const normalized = content.replace(/\r\n/g, "\n");
816
+ const exportPattern = new RegExp(
817
+ `^\\s*export\\s*\\*\\s*from\\s*["']\\./${escapeRegex(schemaFileName)}["'];?\\s*$`,
818
+ "m"
819
+ );
820
+ if (exportPattern.test(normalized)) {
821
+ return;
822
+ }
823
+ let next = normalized.replace(/^\s*export\s*\{\s*\};?\s*$/m, "").trimEnd();
824
+ if (next.length > 0) {
825
+ next += "\n\n";
826
+ }
827
+ next += `${exportLine}
828
+ `;
829
+ await fs6.writeFile(schemaIndexPath, next);
830
+ }
743
831
  async function injectErrorHandler(projectRoot, srcDir) {
744
832
  const appPath = path6.join(projectRoot, srcDir, "app.ts");
745
833
  if (!fs6.existsSync(appPath)) {
@@ -855,6 +943,10 @@ var add = async (moduleName) => {
855
943
  const projectRoot = process.cwd();
856
944
  const projectConfig = await readZuroConfig(projectRoot);
857
945
  if (!projectConfig) {
946
+ if (await isLikelyEmptyDirectory(projectRoot)) {
947
+ showInitFirstMessage();
948
+ return;
949
+ }
858
950
  showNonZuroProjectMessage();
859
951
  return;
860
952
  }
@@ -982,6 +1074,10 @@ var add = async (moduleName) => {
982
1074
  await fs6.ensureDir(path6.dirname(targetPath));
983
1075
  await fs6.writeFile(targetPath, content);
984
1076
  }
1077
+ const schemaExports = module.files.map((file) => file.target.replace(/\\/g, "/")).filter((target) => /^db\/schema\/[^/]+\.ts$/.test(target)).map((target) => path6.posix.basename(target, ".ts")).filter((name) => name !== "index");
1078
+ for (const schemaFileName of schemaExports) {
1079
+ await ensureSchemaExport(projectRoot, srcDir, schemaFileName);
1080
+ }
985
1081
  spinner.succeed("Files generated");
986
1082
  if (resolvedModuleName === "auth") {
987
1083
  spinner.start("Configuring routes in app.ts...");
@@ -1029,71 +1125,27 @@ var add = async (moduleName) => {
1029
1125
  console.log(chalk4.blue(`\u2139 Backup created at: ${databaseBackupPath}
1030
1126
  `));
1031
1127
  }
1032
- if (resolvedModuleName === "auth") {
1033
- console.log(chalk4.bold("\u{1F4CB} Next Steps:\n"));
1034
- if (generatedAuthSecret) {
1035
- console.log(chalk4.yellow("1. BETTER_AUTH_SECRET generated automatically."));
1036
- } else {
1037
- console.log(chalk4.yellow("1. Review your auth env values in .env."));
1038
- }
1039
- console.log(chalk4.dim(" Make sure BETTER_AUTH_URL matches your API origin (for example http://localhost:3000).\n"));
1040
- console.log(chalk4.yellow("2. Run database migrations:"));
1041
- console.log(chalk4.cyan(" npx drizzle-kit generate"));
1042
- console.log(chalk4.cyan(" npx drizzle-kit migrate\n"));
1043
- console.log(chalk4.yellow("3. Available endpoints:"));
1044
- console.log(chalk4.dim(" POST /auth/sign-up/email - Register"));
1045
- console.log(chalk4.dim(" POST /auth/sign-in/email - Login"));
1046
- console.log(chalk4.dim(" POST /auth/sign-out - Logout"));
1047
- console.log(chalk4.dim(" GET /api/users/me - Current user\n"));
1048
- } else if (resolvedModuleName === "error-handler") {
1049
- console.log(chalk4.bold("\u{1F4CB} Usage:\n"));
1050
- console.log(chalk4.yellow("Throw errors in your controllers:"));
1051
- console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1052
- console.log(chalk4.white(` import { UnauthorizedError, NotFoundError } from "./lib/errors";`));
1053
- console.log("");
1054
- console.log(chalk4.white(` throw new UnauthorizedError("Invalid credentials");`));
1055
- console.log(chalk4.white(` throw new NotFoundError("User not found");`));
1056
- console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
1057
- console.log(chalk4.yellow("Available error classes:"));
1058
- console.log(chalk4.dim(" BadRequestError (400)"));
1059
- console.log(chalk4.dim(" UnauthorizedError (401)"));
1060
- console.log(chalk4.dim(" ForbiddenError (403)"));
1061
- console.log(chalk4.dim(" NotFoundError (404)"));
1062
- console.log(chalk4.dim(" ConflictError (409)"));
1063
- console.log(chalk4.dim(" ValidationError (422)"));
1064
- console.log(chalk4.dim(" InternalServerError (500)\n"));
1065
- console.log(chalk4.yellow("Wrap async handlers:"));
1066
- console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1067
- console.log(chalk4.white(` import { asyncHandler } from "./middleware/error-handler";`));
1068
- console.log("");
1069
- console.log(chalk4.white(` router.get("/users", asyncHandler(async (req, res) => {`));
1070
- console.log(chalk4.white(" // errors auto-caught"));
1071
- console.log(chalk4.white(" }));"));
1072
- console.log(chalk4.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
1073
- } else if (isDatabaseModule(resolvedModuleName)) {
1074
- console.log(chalk4.bold("\u{1F4CB} Next Steps:\n"));
1075
- let stepNum = 1;
1128
+ const docsPath = getModuleDocsPath(resolvedModuleName);
1129
+ const docsUrl = `https://zuro-cli.devbybriyan.com/docs/${docsPath}`;
1130
+ console.log(chalk4.blue(`\u2139 Docs: ${docsUrl}`));
1131
+ if (isDatabaseModule(resolvedModuleName)) {
1076
1132
  if (usedDefaultDbUrl) {
1077
- console.log(chalk4.yellow(`${stepNum}. Update DATABASE_URL in .env:`));
1078
- console.log(
1079
- chalk4.dim(" We added a local default. Update it if your DB host/user/password differ.\n")
1080
- );
1081
- stepNum++;
1133
+ console.log(chalk4.yellow("\u2139 Review DATABASE_URL in .env if your local DB config differs."));
1082
1134
  }
1083
- console.log(chalk4.yellow(`${stepNum}. Create schemas in ${srcDir}/db/schema/:`));
1084
- console.log(chalk4.dim(" Add table files and export from index.ts\n"));
1085
- stepNum++;
1086
1135
  const setupHint = getDatabaseSetupHint(
1087
1136
  resolvedModuleName,
1088
1137
  customDbUrl || DEFAULT_DATABASE_URLS[resolvedModuleName]
1089
1138
  );
1090
- console.log(chalk4.yellow(`${stepNum}. Ensure the database exists:`));
1091
- console.log(chalk4.cyan(` ${setupHint}
1092
- `));
1093
- stepNum++;
1094
- console.log(chalk4.yellow(`${stepNum}. Run migrations:`));
1095
- console.log(chalk4.cyan(" npx drizzle-kit generate"));
1096
- console.log(chalk4.cyan(" npx drizzle-kit migrate\n"));
1139
+ console.log(chalk4.yellow(`\u2139 Ensure DB exists: ${setupHint}`));
1140
+ console.log(chalk4.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
1141
+ }
1142
+ if (resolvedModuleName === "auth") {
1143
+ if (generatedAuthSecret) {
1144
+ console.log(chalk4.yellow("\u2139 BETTER_AUTH_SECRET was generated automatically."));
1145
+ } else {
1146
+ console.log(chalk4.yellow("\u2139 Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env."));
1147
+ }
1148
+ console.log(chalk4.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
1097
1149
  }
1098
1150
  } catch (error) {
1099
1151
  spinner.fail(chalk4.red(`Failed during ${currentStep}.`));