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.js +118 -66
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +118 -66
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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(
|
|
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(
|
|
1091
|
-
console.log(chalk4.
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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}.`));
|