zuro-cli 0.0.2-beta.1 → 0.0.2-beta.11
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/README.md +26 -22
- package/dist/index.js +565 -162
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +565 -162
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -189,18 +189,36 @@ async function fetchFile(filePath, options) {
|
|
|
189
189
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
190
190
|
var import_path = __toESM(require("path"));
|
|
191
191
|
var import_execa = require("execa");
|
|
192
|
-
|
|
192
|
+
function normalizePackageName(name) {
|
|
193
|
+
const normalized = name.trim().toLowerCase().replace(/[^a-z0-9._-]/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
|
|
194
|
+
return normalized || "zuro-app";
|
|
195
|
+
}
|
|
196
|
+
async function ensurePackageManagerAvailable(pm) {
|
|
197
|
+
try {
|
|
198
|
+
await (0, import_execa.execa)(pm, ["--version"], { stdio: "ignore" });
|
|
199
|
+
} catch {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Package manager '${pm}' is not installed or not available in PATH. Install it or choose npm.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function initPackageJson(cwd, force = false, packageName = "zuro-app", srcDir = "src", options = {}) {
|
|
193
206
|
const pkgPath = import_path.default.join(cwd, "package.json");
|
|
194
207
|
if (force || !await import_fs_extra.default.pathExists(pkgPath)) {
|
|
208
|
+
const scripts = {
|
|
209
|
+
"dev": `tsx watch ${srcDir}/server.ts`,
|
|
210
|
+
"build": "tsc",
|
|
211
|
+
"start": "node dist/server.js"
|
|
212
|
+
};
|
|
213
|
+
if (options.enablePrettier) {
|
|
214
|
+
scripts["format"] = "prettier --write .";
|
|
215
|
+
scripts["format:check"] = "prettier --check .";
|
|
216
|
+
}
|
|
195
217
|
await import_fs_extra.default.writeJson(pkgPath, {
|
|
196
|
-
name:
|
|
218
|
+
name: normalizePackageName(packageName),
|
|
197
219
|
version: "0.0.1",
|
|
198
220
|
private: true,
|
|
199
|
-
scripts
|
|
200
|
-
"dev": "tsx watch src/server.ts",
|
|
201
|
-
"build": "tsc",
|
|
202
|
-
"start": "node dist/server.js"
|
|
203
|
-
}
|
|
221
|
+
scripts
|
|
204
222
|
}, { spaces: 2 });
|
|
205
223
|
}
|
|
206
224
|
}
|
|
@@ -237,8 +255,9 @@ async function installDependencies(pm, deps, cwd, options = {}) {
|
|
|
237
255
|
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
238
256
|
var import_path2 = __toESM(require("path"));
|
|
239
257
|
var import_os = __toESM(require("os"));
|
|
240
|
-
var updateEnvFile = async (cwd, variables, createIfMissing = true) => {
|
|
258
|
+
var updateEnvFile = async (cwd, variables, createIfMissing = true, options = {}) => {
|
|
241
259
|
const envPath = import_path2.default.join(cwd, ".env");
|
|
260
|
+
const overwriteExisting = options.overwriteExisting ?? false;
|
|
242
261
|
let content = "";
|
|
243
262
|
if (import_fs_extra2.default.existsSync(envPath)) {
|
|
244
263
|
content = await import_fs_extra2.default.readFile(envPath, "utf-8");
|
|
@@ -247,14 +266,25 @@ var updateEnvFile = async (cwd, variables, createIfMissing = true) => {
|
|
|
247
266
|
}
|
|
248
267
|
let modified = false;
|
|
249
268
|
for (const [key, value] of Object.entries(variables)) {
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
269
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
270
|
+
const regex = new RegExp(`^${escapedKey}=.*$`, "m");
|
|
271
|
+
if (regex.test(content)) {
|
|
272
|
+
if (!overwriteExisting) {
|
|
273
|
+
continue;
|
|
254
274
|
}
|
|
255
|
-
|
|
256
|
-
|
|
275
|
+
const nextLine = `${key}=${value}`;
|
|
276
|
+
const updated = content.replace(regex, nextLine);
|
|
277
|
+
if (updated !== content) {
|
|
278
|
+
content = updated;
|
|
279
|
+
modified = true;
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
257
282
|
}
|
|
283
|
+
if (content && !content.endsWith("\n")) {
|
|
284
|
+
content += import_os.default.EOL;
|
|
285
|
+
}
|
|
286
|
+
content += `${key}=${value}${import_os.default.EOL}`;
|
|
287
|
+
modified = true;
|
|
258
288
|
}
|
|
259
289
|
if (modified || !import_fs_extra2.default.existsSync(envPath)) {
|
|
260
290
|
await import_fs_extra2.default.writeFile(envPath, content);
|
|
@@ -377,6 +407,13 @@ function showNonZuroProjectMessage() {
|
|
|
377
407
|
console.log("- a fresh/empty directory, or");
|
|
378
408
|
console.log("- an existing project already managed by Zuro CLI.");
|
|
379
409
|
}
|
|
410
|
+
function showInitFirstMessage() {
|
|
411
|
+
console.log(import_chalk.default.yellow("No Zuro project found in this directory."));
|
|
412
|
+
console.log("");
|
|
413
|
+
console.log(import_chalk.default.yellow("Run init first, then add modules."));
|
|
414
|
+
console.log("");
|
|
415
|
+
console.log(import_chalk.default.cyan("npx zuro-cli init"));
|
|
416
|
+
}
|
|
380
417
|
|
|
381
418
|
// src/commands/init.ts
|
|
382
419
|
function resolveSafeTargetPath(projectRoot, srcDir, file) {
|
|
@@ -391,6 +428,48 @@ function resolveSafeTargetPath(projectRoot, srcDir, file) {
|
|
|
391
428
|
targetPath
|
|
392
429
|
};
|
|
393
430
|
}
|
|
431
|
+
async function ensureSafeTargetDirectory(targetDir, cwd, projectName) {
|
|
432
|
+
await import_fs_extra4.default.ensureDir(targetDir);
|
|
433
|
+
const entries = await import_fs_extra4.default.readdir(targetDir);
|
|
434
|
+
if (entries.length === 0) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
const isCurrentFolder = targetDir === cwd;
|
|
438
|
+
const response = await (0, import_prompts.default)({
|
|
439
|
+
type: "confirm",
|
|
440
|
+
name: "proceed",
|
|
441
|
+
message: isCurrentFolder ? `Current folder '${projectName}' is not empty. Continue anyway?` : `Target folder '${projectName}' already exists and is not empty. Continue anyway?`,
|
|
442
|
+
initial: false
|
|
443
|
+
});
|
|
444
|
+
return response.proceed === true;
|
|
445
|
+
}
|
|
446
|
+
async function setupPrettier(targetDir) {
|
|
447
|
+
const prettierConfigPath = import_path4.default.join(targetDir, ".prettierrc");
|
|
448
|
+
const prettierIgnorePath = import_path4.default.join(targetDir, ".prettierignore");
|
|
449
|
+
if (!await import_fs_extra4.default.pathExists(prettierConfigPath)) {
|
|
450
|
+
const prettierConfig = {
|
|
451
|
+
semi: true,
|
|
452
|
+
singleQuote: false,
|
|
453
|
+
trailingComma: "es5",
|
|
454
|
+
printWidth: 100,
|
|
455
|
+
tabWidth: 2
|
|
456
|
+
};
|
|
457
|
+
await import_fs_extra4.default.writeJson(prettierConfigPath, prettierConfig, { spaces: 2 });
|
|
458
|
+
}
|
|
459
|
+
if (!await import_fs_extra4.default.pathExists(prettierIgnorePath)) {
|
|
460
|
+
const ignoreContent = `node_modules
|
|
461
|
+
dist
|
|
462
|
+
build
|
|
463
|
+
coverage
|
|
464
|
+
.next
|
|
465
|
+
pnpm-lock.yaml
|
|
466
|
+
package-lock.json
|
|
467
|
+
bun.lock
|
|
468
|
+
bun.lockb
|
|
469
|
+
`;
|
|
470
|
+
await import_fs_extra4.default.writeFile(prettierIgnorePath, ignoreContent);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
394
473
|
async function init() {
|
|
395
474
|
const cwd = process.cwd();
|
|
396
475
|
const isExistingProject = await import_fs_extra4.default.pathExists(import_path4.default.join(cwd, "package.json"));
|
|
@@ -403,6 +482,7 @@ async function init() {
|
|
|
403
482
|
let pm = "npm";
|
|
404
483
|
let srcDir = "src";
|
|
405
484
|
let projectName = import_path4.default.basename(cwd);
|
|
485
|
+
let enablePrettier = false;
|
|
406
486
|
if (isExistingProject) {
|
|
407
487
|
console.log(import_chalk2.default.blue("\u2139 Existing project detected."));
|
|
408
488
|
projectName = import_path4.default.basename(cwd);
|
|
@@ -440,6 +520,12 @@ async function init() {
|
|
|
440
520
|
{ title: "bun", value: "bun" }
|
|
441
521
|
],
|
|
442
522
|
initial: 0
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
type: "confirm",
|
|
526
|
+
name: "prettier",
|
|
527
|
+
message: "Setup Prettier?",
|
|
528
|
+
initial: true
|
|
443
529
|
}
|
|
444
530
|
]);
|
|
445
531
|
if (response.pm === void 0) {
|
|
@@ -447,6 +533,7 @@ async function init() {
|
|
|
447
533
|
return;
|
|
448
534
|
}
|
|
449
535
|
pm = response.pm;
|
|
536
|
+
enablePrettier = response.prettier === true;
|
|
450
537
|
srcDir = "src";
|
|
451
538
|
if (!response.path || response.path.trim() === "") {
|
|
452
539
|
projectName = import_path4.default.basename(cwd);
|
|
@@ -455,7 +542,11 @@ async function init() {
|
|
|
455
542
|
} else {
|
|
456
543
|
projectName = response.path.trim();
|
|
457
544
|
targetDir = import_path4.default.resolve(cwd, projectName);
|
|
458
|
-
|
|
545
|
+
}
|
|
546
|
+
const isSafeTarget = await ensureSafeTargetDirectory(targetDir, cwd, projectName);
|
|
547
|
+
if (!isSafeTarget) {
|
|
548
|
+
console.log(import_chalk2.default.red("Operation cancelled."));
|
|
549
|
+
return;
|
|
459
550
|
}
|
|
460
551
|
}
|
|
461
552
|
const existingConfig = targetDir === cwd ? existingZuroConfig : await readZuroConfig(targetDir);
|
|
@@ -464,20 +555,26 @@ async function init() {
|
|
|
464
555
|
pm,
|
|
465
556
|
srcDir: srcDir || existingConfig?.srcDir || "src"
|
|
466
557
|
};
|
|
467
|
-
await writeZuroConfig(targetDir, zuroConfig);
|
|
468
558
|
const spinner = (0, import_ora.default)("Connecting to Zuro Registry...").start();
|
|
559
|
+
let currentStep = "package manager preflight";
|
|
469
560
|
try {
|
|
561
|
+
spinner.text = `Checking ${pm} availability...`;
|
|
562
|
+
await ensurePackageManagerAvailable(pm);
|
|
563
|
+
currentStep = "registry fetch";
|
|
564
|
+
spinner.text = "Connecting to Zuro Registry...";
|
|
470
565
|
const registryContext = await fetchRegistry();
|
|
471
566
|
const coreModule = registryContext.manifest.modules.core;
|
|
472
567
|
if (!coreModule) {
|
|
473
568
|
spinner.fail("Core module not found in registry.");
|
|
474
569
|
return;
|
|
475
570
|
}
|
|
571
|
+
currentStep = "project initialization";
|
|
476
572
|
spinner.text = "Initializing project...";
|
|
477
573
|
const hasPackageJson = await import_fs_extra4.default.pathExists(import_path4.default.join(targetDir, "package.json"));
|
|
478
574
|
if (!hasPackageJson) {
|
|
479
|
-
await initPackageJson(targetDir, true);
|
|
575
|
+
await initPackageJson(targetDir, true, projectName, srcDir, { enablePrettier });
|
|
480
576
|
}
|
|
577
|
+
currentStep = "dependency installation";
|
|
481
578
|
spinner.text = `Installing dependencies using ${pm}...`;
|
|
482
579
|
let runtimeDeps = [];
|
|
483
580
|
let devDeps = [];
|
|
@@ -492,6 +589,10 @@ async function init() {
|
|
|
492
589
|
}
|
|
493
590
|
await installDependencies(pm, runtimeDeps, targetDir);
|
|
494
591
|
await installDependencies(pm, devDeps, targetDir, { dev: true });
|
|
592
|
+
if (enablePrettier) {
|
|
593
|
+
await installDependencies(pm, ["prettier"], targetDir, { dev: true });
|
|
594
|
+
}
|
|
595
|
+
currentStep = "module file generation";
|
|
495
596
|
spinner.text = "Fetching core module files...";
|
|
496
597
|
for (const file of coreModule.files) {
|
|
497
598
|
const { relativeTargetPath, targetPath } = resolveSafeTargetPath(targetDir, srcDir, file);
|
|
@@ -514,7 +615,14 @@ async function init() {
|
|
|
514
615
|
await import_fs_extra4.default.ensureDir(import_path4.default.dirname(targetPath));
|
|
515
616
|
await import_fs_extra4.default.writeFile(targetPath, content);
|
|
516
617
|
}
|
|
618
|
+
currentStep = "environment file setup";
|
|
517
619
|
await createInitialEnv(targetDir);
|
|
620
|
+
if (enablePrettier) {
|
|
621
|
+
currentStep = "prettier setup";
|
|
622
|
+
await setupPrettier(targetDir);
|
|
623
|
+
}
|
|
624
|
+
currentStep = "config write";
|
|
625
|
+
await writeZuroConfig(targetDir, zuroConfig);
|
|
518
626
|
spinner.succeed(import_chalk2.default.green("Project initialized successfully!"));
|
|
519
627
|
console.log(`
|
|
520
628
|
${import_chalk2.default.bold("Next steps:")}`);
|
|
@@ -525,8 +633,15 @@ ${import_chalk2.default.bold("Next steps:")}`);
|
|
|
525
633
|
console.log(`
|
|
526
634
|
${import_chalk2.default.dim("Add modules: zuro-cli add database, zuro-cli add auth")}`);
|
|
527
635
|
} catch (error) {
|
|
528
|
-
spinner.fail(import_chalk2.default.red(
|
|
529
|
-
|
|
636
|
+
spinner.fail(import_chalk2.default.red(`Failed during ${currentStep}.`));
|
|
637
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
638
|
+
console.error(import_chalk2.default.red(errorMessage));
|
|
639
|
+
console.log(`
|
|
640
|
+
${import_chalk2.default.bold("Retry:")}`);
|
|
641
|
+
if (targetDir !== cwd) {
|
|
642
|
+
console.log(import_chalk2.default.cyan(` cd ${projectName}`));
|
|
643
|
+
}
|
|
644
|
+
console.log(import_chalk2.default.cyan(" npx zuro-cli init"));
|
|
530
645
|
}
|
|
531
646
|
}
|
|
532
647
|
|
|
@@ -535,6 +650,7 @@ var import_prompts2 = __toESM(require("prompts"));
|
|
|
535
650
|
var import_ora2 = __toESM(require("ora"));
|
|
536
651
|
var import_path6 = __toESM(require("path"));
|
|
537
652
|
var import_fs_extra6 = __toESM(require("fs-extra"));
|
|
653
|
+
var import_node_crypto2 = require("crypto");
|
|
538
654
|
|
|
539
655
|
// src/utils/dependency.ts
|
|
540
656
|
var import_fs_extra5 = __toESM(require("fs-extra"));
|
|
@@ -580,6 +696,10 @@ var resolveDependencies = async (moduleDependencies, cwd) => {
|
|
|
580
696
|
|
|
581
697
|
// src/commands/add.ts
|
|
582
698
|
var import_chalk4 = __toESM(require("chalk"));
|
|
699
|
+
var DEFAULT_DATABASE_URLS = {
|
|
700
|
+
"database-pg": "postgresql://postgres@localhost:5432/mydb",
|
|
701
|
+
"database-mysql": "mysql://root@localhost:3306/mydb"
|
|
702
|
+
};
|
|
583
703
|
function resolveSafeTargetPath2(projectRoot, srcDir, file) {
|
|
584
704
|
const targetPath = import_path6.default.resolve(projectRoot, srcDir, file.target);
|
|
585
705
|
const normalizedRoot = import_path6.default.resolve(projectRoot);
|
|
@@ -588,86 +708,338 @@ function resolveSafeTargetPath2(projectRoot, srcDir, file) {
|
|
|
588
708
|
}
|
|
589
709
|
return targetPath;
|
|
590
710
|
}
|
|
711
|
+
function resolvePackageManager(projectRoot) {
|
|
712
|
+
if (import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
713
|
+
return "pnpm";
|
|
714
|
+
}
|
|
715
|
+
if (import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "bun.lockb")) || import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "bun.lock"))) {
|
|
716
|
+
return "bun";
|
|
717
|
+
}
|
|
718
|
+
if (import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "yarn.lock"))) {
|
|
719
|
+
return "yarn";
|
|
720
|
+
}
|
|
721
|
+
return "npm";
|
|
722
|
+
}
|
|
723
|
+
function parseDatabaseDialect(value) {
|
|
724
|
+
const normalized = value?.trim().toLowerCase();
|
|
725
|
+
if (!normalized) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
if (normalized === "pg" || normalized === "postgres" || normalized === "postgresql" || normalized === "database-pg") {
|
|
729
|
+
return "database-pg";
|
|
730
|
+
}
|
|
731
|
+
if (normalized === "mysql" || normalized === "database-mysql") {
|
|
732
|
+
return "database-mysql";
|
|
733
|
+
}
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
function isDatabaseModule(moduleName) {
|
|
737
|
+
return moduleName === "database-pg" || moduleName === "database-mysql";
|
|
738
|
+
}
|
|
739
|
+
function validateDatabaseUrl(rawUrl, moduleName) {
|
|
740
|
+
const dbUrl = rawUrl.trim();
|
|
741
|
+
if (!dbUrl) {
|
|
742
|
+
throw new Error("Database URL cannot be empty.");
|
|
743
|
+
}
|
|
744
|
+
let parsed;
|
|
745
|
+
try {
|
|
746
|
+
parsed = new URL(dbUrl);
|
|
747
|
+
} catch {
|
|
748
|
+
throw new Error(`Invalid database URL: '${dbUrl}'.`);
|
|
749
|
+
}
|
|
750
|
+
const protocol = parsed.protocol.toLowerCase();
|
|
751
|
+
if (moduleName === "database-pg" && protocol !== "postgresql:" && protocol !== "postgres:") {
|
|
752
|
+
throw new Error("PostgreSQL URL must start with postgres:// or postgresql://");
|
|
753
|
+
}
|
|
754
|
+
if (moduleName === "database-mysql" && protocol !== "mysql:") {
|
|
755
|
+
throw new Error("MySQL URL must start with mysql://");
|
|
756
|
+
}
|
|
757
|
+
return dbUrl;
|
|
758
|
+
}
|
|
759
|
+
async function detectInstalledDatabaseDialect(projectRoot, srcDir) {
|
|
760
|
+
const dbIndexPath = import_path6.default.join(projectRoot, srcDir, "db", "index.ts");
|
|
761
|
+
if (!import_fs_extra6.default.existsSync(dbIndexPath)) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
const content = await import_fs_extra6.default.readFile(dbIndexPath, "utf-8");
|
|
765
|
+
if (content.includes("drizzle-orm/node-postgres") || content.includes(`from "pg"`)) {
|
|
766
|
+
return "database-pg";
|
|
767
|
+
}
|
|
768
|
+
if (content.includes("drizzle-orm/mysql2") || content.includes(`from "mysql2`)) {
|
|
769
|
+
return "database-mysql";
|
|
770
|
+
}
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
async function backupDatabaseFiles(projectRoot, srcDir) {
|
|
774
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
775
|
+
const backupRoot = import_path6.default.join(projectRoot, ".zuro", "backups", `database-${timestamp}`);
|
|
776
|
+
const candidates = [
|
|
777
|
+
import_path6.default.join(projectRoot, srcDir, "db", "index.ts"),
|
|
778
|
+
import_path6.default.join(projectRoot, "drizzle.config.ts")
|
|
779
|
+
];
|
|
780
|
+
let copied = false;
|
|
781
|
+
for (const filePath of candidates) {
|
|
782
|
+
if (!import_fs_extra6.default.existsSync(filePath)) {
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
const relativePath = import_path6.default.relative(projectRoot, filePath);
|
|
786
|
+
const backupPath = import_path6.default.join(backupRoot, relativePath);
|
|
787
|
+
await import_fs_extra6.default.ensureDir(import_path6.default.dirname(backupPath));
|
|
788
|
+
await import_fs_extra6.default.copyFile(filePath, backupPath);
|
|
789
|
+
copied = true;
|
|
790
|
+
}
|
|
791
|
+
return copied ? backupRoot : null;
|
|
792
|
+
}
|
|
793
|
+
function databaseLabel(moduleName) {
|
|
794
|
+
return moduleName === "database-pg" ? "PostgreSQL" : "MySQL";
|
|
795
|
+
}
|
|
796
|
+
function getDatabaseSetupHint(moduleName, dbUrl) {
|
|
797
|
+
try {
|
|
798
|
+
const parsed = new URL(dbUrl);
|
|
799
|
+
const dbName = parsed.pathname.replace(/^\/+/, "") || "mydb";
|
|
800
|
+
if (moduleName === "database-pg") {
|
|
801
|
+
return `createdb ${dbName}`;
|
|
802
|
+
}
|
|
803
|
+
return `mysql -e "CREATE DATABASE IF NOT EXISTS ${dbName};"`;
|
|
804
|
+
} catch {
|
|
805
|
+
return moduleName === "database-pg" ? "createdb <database_name>" : `mysql -e "CREATE DATABASE IF NOT EXISTS <database_name>;"`;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function getModuleDocsPath(moduleName) {
|
|
809
|
+
if (isDatabaseModule(moduleName)) {
|
|
810
|
+
return "database";
|
|
811
|
+
}
|
|
812
|
+
return moduleName;
|
|
813
|
+
}
|
|
814
|
+
function escapeRegex(value) {
|
|
815
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
816
|
+
}
|
|
817
|
+
async function hasEnvVariable(projectRoot, key) {
|
|
818
|
+
const envPath = import_path6.default.join(projectRoot, ".env");
|
|
819
|
+
if (!await import_fs_extra6.default.pathExists(envPath)) {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
const content = await import_fs_extra6.default.readFile(envPath, "utf-8");
|
|
823
|
+
const pattern = new RegExp(`^${escapeRegex(key)}=`, "m");
|
|
824
|
+
return pattern.test(content);
|
|
825
|
+
}
|
|
826
|
+
async function isLikelyEmptyDirectory(cwd) {
|
|
827
|
+
const entries = await import_fs_extra6.default.readdir(cwd);
|
|
828
|
+
const ignored = /* @__PURE__ */ new Set([".ds_store", "thumbs.db"]);
|
|
829
|
+
return entries.filter((entry) => !ignored.has(entry.toLowerCase())).length === 0;
|
|
830
|
+
}
|
|
831
|
+
async function ensureSchemaExport(projectRoot, srcDir, schemaFileName) {
|
|
832
|
+
const schemaIndexPath = import_path6.default.join(projectRoot, srcDir, "db", "schema", "index.ts");
|
|
833
|
+
if (!await import_fs_extra6.default.pathExists(schemaIndexPath)) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
const exportLine = `export * from "./${schemaFileName}";`;
|
|
837
|
+
const content = await import_fs_extra6.default.readFile(schemaIndexPath, "utf-8");
|
|
838
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
839
|
+
const exportPattern = new RegExp(
|
|
840
|
+
`^\\s*export\\s*\\*\\s*from\\s*["']\\./${escapeRegex(schemaFileName)}["'];?\\s*$`,
|
|
841
|
+
"m"
|
|
842
|
+
);
|
|
843
|
+
if (exportPattern.test(normalized)) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
let next = normalized.replace(/^\s*export\s*\{\s*\};?\s*$/m, "").trimEnd();
|
|
847
|
+
if (next.length > 0) {
|
|
848
|
+
next += "\n\n";
|
|
849
|
+
}
|
|
850
|
+
next += `${exportLine}
|
|
851
|
+
`;
|
|
852
|
+
await import_fs_extra6.default.writeFile(schemaIndexPath, next);
|
|
853
|
+
}
|
|
591
854
|
async function injectErrorHandler(projectRoot, srcDir) {
|
|
592
855
|
const appPath = import_path6.default.join(projectRoot, srcDir, "app.ts");
|
|
593
856
|
if (!import_fs_extra6.default.existsSync(appPath)) {
|
|
594
857
|
return false;
|
|
595
858
|
}
|
|
596
859
|
let content = await import_fs_extra6.default.readFile(appPath, "utf-8");
|
|
597
|
-
if (content.includes("errorHandler")) {
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
860
|
const errorImport = `import { errorHandler, notFoundHandler } from "./middleware/error-handler";`;
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
861
|
+
const hasErrorImport = content.includes(errorImport);
|
|
862
|
+
const hasNotFoundUse = /app\.use\(\s*notFoundHandler\s*\)/.test(content);
|
|
863
|
+
const hasErrorUse = /app\.use\(\s*errorHandler\s*\)/.test(content);
|
|
864
|
+
let modified = false;
|
|
865
|
+
let importInserted = hasErrorImport;
|
|
866
|
+
if (!hasErrorImport) {
|
|
867
|
+
const importRegex = /^import .+ from .+;?\s*$/gm;
|
|
868
|
+
let lastImportIndex = 0;
|
|
869
|
+
let match;
|
|
870
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
871
|
+
lastImportIndex = match.index + match[0].length;
|
|
872
|
+
}
|
|
873
|
+
if (lastImportIndex > 0) {
|
|
874
|
+
content = content.slice(0, lastImportIndex) + `
|
|
609
875
|
${errorImport}` + content.slice(lastImportIndex);
|
|
876
|
+
modified = true;
|
|
877
|
+
importInserted = true;
|
|
878
|
+
}
|
|
610
879
|
}
|
|
611
|
-
|
|
880
|
+
let setupInserted = hasNotFoundUse && hasErrorUse;
|
|
881
|
+
if (!setupInserted) {
|
|
882
|
+
const setupLines = [];
|
|
883
|
+
if (!hasNotFoundUse) {
|
|
884
|
+
setupLines.push("app.use(notFoundHandler);");
|
|
885
|
+
}
|
|
886
|
+
if (!hasErrorUse) {
|
|
887
|
+
setupLines.push("app.use(errorHandler);");
|
|
888
|
+
}
|
|
889
|
+
const errorSetup = `
|
|
612
890
|
// Error handling (must be last)
|
|
613
|
-
|
|
614
|
-
app.use(errorHandler);
|
|
891
|
+
${setupLines.join("\n")}
|
|
615
892
|
`;
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
893
|
+
const exportMatch = content.match(/export default app;?\s*$/m);
|
|
894
|
+
if (exportMatch && exportMatch.index !== void 0) {
|
|
895
|
+
content = content.slice(0, exportMatch.index) + errorSetup + "\n" + content.slice(exportMatch.index);
|
|
896
|
+
modified = true;
|
|
897
|
+
setupInserted = true;
|
|
898
|
+
}
|
|
619
899
|
}
|
|
620
|
-
|
|
621
|
-
|
|
900
|
+
if (modified) {
|
|
901
|
+
await import_fs_extra6.default.writeFile(appPath, content);
|
|
902
|
+
}
|
|
903
|
+
return importInserted && setupInserted;
|
|
622
904
|
}
|
|
623
905
|
async function injectAuthRoutes(projectRoot, srcDir) {
|
|
624
906
|
const appPath = import_path6.default.join(projectRoot, srcDir, "app.ts");
|
|
625
|
-
if (!import_fs_extra6.default.
|
|
907
|
+
if (!await import_fs_extra6.default.pathExists(appPath)) {
|
|
626
908
|
return false;
|
|
627
909
|
}
|
|
628
|
-
let
|
|
629
|
-
|
|
630
|
-
|
|
910
|
+
let appContent = await import_fs_extra6.default.readFile(appPath, "utf-8");
|
|
911
|
+
const authHandlerImport = `import { toNodeHandler } from "better-auth/node";`;
|
|
912
|
+
const authImport = `import { auth } from "./lib/auth";`;
|
|
913
|
+
const routeIndexUserImport = `import userRoutes from "./user.routes";`;
|
|
914
|
+
const appUserImport = `import userRoutes from "./routes/user.routes";`;
|
|
915
|
+
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
|
+
for (const importLine of [authHandlerImport, authImport]) {
|
|
936
|
+
const next = appendImport(appContent, importLine);
|
|
937
|
+
if (!next.inserted) {
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
if (next.source !== appContent) {
|
|
941
|
+
appContent = next.source;
|
|
942
|
+
appModified = true;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const hasAuthMount = /toNodeHandler\(\s*auth\s*\)/.test(appContent) && /\/api\/auth/.test(appContent);
|
|
946
|
+
if (!hasAuthMount) {
|
|
947
|
+
const authMountLine = "app.all(/^\\/api\\/auth(?:\\/.*)?$/, toNodeHandler(auth));\n";
|
|
948
|
+
const jsonIndex = appContent.search(/^\s*app\.use\(\s*express\.json\(\)\s*\);\s*$/m);
|
|
949
|
+
let insertionIndex = jsonIndex;
|
|
950
|
+
if (insertionIndex < 0) {
|
|
951
|
+
const healthIndex = appContent.search(/^\s*app\.get\(\s*["']\/health["']\s*,/m);
|
|
952
|
+
insertionIndex = healthIndex;
|
|
953
|
+
}
|
|
954
|
+
if (insertionIndex < 0) {
|
|
955
|
+
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
956
|
+
insertionIndex = exportMatch?.index ?? -1;
|
|
957
|
+
}
|
|
958
|
+
if (insertionIndex < 0) {
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
appContent = appContent.slice(0, insertionIndex) + authMountLine + appContent.slice(insertionIndex);
|
|
962
|
+
appModified = true;
|
|
631
963
|
}
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
964
|
+
const routeIndexPath = import_path6.default.join(projectRoot, srcDir, "routes", "index.ts");
|
|
965
|
+
if (await import_fs_extra6.default.pathExists(routeIndexPath)) {
|
|
966
|
+
let routeContent = await import_fs_extra6.default.readFile(routeIndexPath, "utf-8");
|
|
967
|
+
let routeModified = false;
|
|
968
|
+
const userImportResult = appendImport(routeContent, routeIndexUserImport);
|
|
969
|
+
if (!userImportResult.inserted) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
if (userImportResult.source !== routeContent) {
|
|
973
|
+
routeContent = userImportResult.source;
|
|
974
|
+
routeModified = true;
|
|
975
|
+
}
|
|
976
|
+
const hasUserRoute = /rootRouter\.use\(\s*["']\/users["']\s*,\s*userRoutes\s*\)/.test(routeContent);
|
|
977
|
+
if (!hasUserRoute) {
|
|
978
|
+
const routeSetup = `
|
|
979
|
+
// User routes
|
|
980
|
+
rootRouter.use("/users", userRoutes);
|
|
981
|
+
`;
|
|
982
|
+
const exportMatch = routeContent.match(/export default rootRouter;?\s*$/m);
|
|
983
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
routeContent = routeContent.slice(0, exportMatch.index) + routeSetup + "\n" + routeContent.slice(exportMatch.index);
|
|
987
|
+
routeModified = true;
|
|
988
|
+
}
|
|
989
|
+
if (routeModified) {
|
|
990
|
+
await import_fs_extra6.default.writeFile(routeIndexPath, routeContent);
|
|
991
|
+
}
|
|
992
|
+
} else {
|
|
993
|
+
const hasUserRoute = /app\.use\(\s*["']\/api\/users["']\s*,\s*userRoutes\s*\)/.test(appContent);
|
|
994
|
+
if (!hasUserRoute) {
|
|
995
|
+
const exportMatch = appContent.match(/export default app;?\s*$/m);
|
|
996
|
+
if (!exportMatch || exportMatch.index === void 0) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
const routeSetup = `
|
|
1000
|
+
// User routes
|
|
637
1001
|
app.use("/api/users", userRoutes);
|
|
638
1002
|
`;
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
await import_fs_extra6.default.writeFile(appPath, content);
|
|
1003
|
+
appContent = appContent.slice(0, exportMatch.index) + routeSetup + "\n" + appContent.slice(exportMatch.index);
|
|
1004
|
+
appModified = true;
|
|
1005
|
+
}
|
|
1006
|
+
const userImportResult = appendImport(appContent, appUserImport);
|
|
1007
|
+
if (!userImportResult.inserted) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
if (userImportResult.source !== appContent) {
|
|
1011
|
+
appContent = userImportResult.source;
|
|
1012
|
+
appModified = true;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (appModified) {
|
|
1016
|
+
await import_fs_extra6.default.writeFile(appPath, appContent);
|
|
1017
|
+
}
|
|
655
1018
|
return true;
|
|
656
1019
|
}
|
|
657
1020
|
var add = async (moduleName) => {
|
|
658
1021
|
const projectRoot = process.cwd();
|
|
659
1022
|
const projectConfig = await readZuroConfig(projectRoot);
|
|
660
1023
|
if (!projectConfig) {
|
|
1024
|
+
if (await isLikelyEmptyDirectory(projectRoot)) {
|
|
1025
|
+
showInitFirstMessage();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
661
1028
|
showNonZuroProjectMessage();
|
|
662
1029
|
return;
|
|
663
1030
|
}
|
|
664
|
-
|
|
1031
|
+
const srcDir = projectConfig.srcDir || "src";
|
|
1032
|
+
let resolvedModuleName = moduleName;
|
|
1033
|
+
const parsedDialect = parseDatabaseDialect(moduleName);
|
|
1034
|
+
if (parsedDialect) {
|
|
1035
|
+
resolvedModuleName = parsedDialect;
|
|
1036
|
+
}
|
|
665
1037
|
let customDbUrl;
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (
|
|
1038
|
+
let usedDefaultDbUrl = false;
|
|
1039
|
+
let databaseBackupPath = null;
|
|
1040
|
+
let generatedAuthSecret = false;
|
|
1041
|
+
let authDatabaseDialect = null;
|
|
1042
|
+
if (resolvedModuleName === "database") {
|
|
671
1043
|
const variantResponse = await (0, import_prompts2.default)({
|
|
672
1044
|
type: "select",
|
|
673
1045
|
name: "variant",
|
|
@@ -678,22 +1050,39 @@ var add = async (moduleName) => {
|
|
|
678
1050
|
]
|
|
679
1051
|
});
|
|
680
1052
|
if (!variantResponse.variant) {
|
|
681
|
-
|
|
1053
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1054
|
+
return;
|
|
682
1055
|
}
|
|
683
|
-
|
|
684
|
-
const defaultUrl = DEFAULT_URLS[moduleName];
|
|
685
|
-
console.log(import_chalk4.default.dim(` Tip: Leave blank to use ${defaultUrl}
|
|
686
|
-
`));
|
|
687
|
-
const urlResponse = await (0, import_prompts2.default)({
|
|
688
|
-
type: "text",
|
|
689
|
-
name: "dbUrl",
|
|
690
|
-
message: "Database URL",
|
|
691
|
-
initial: ""
|
|
692
|
-
});
|
|
693
|
-
customDbUrl = urlResponse.dbUrl?.trim() || defaultUrl;
|
|
1056
|
+
resolvedModuleName = variantResponse.variant;
|
|
694
1057
|
}
|
|
695
|
-
if ((
|
|
696
|
-
const
|
|
1058
|
+
if (isDatabaseModule(resolvedModuleName)) {
|
|
1059
|
+
const installedDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);
|
|
1060
|
+
if (installedDialect && installedDialect !== resolvedModuleName) {
|
|
1061
|
+
console.log(
|
|
1062
|
+
import_chalk4.default.yellow(
|
|
1063
|
+
`
|
|
1064
|
+
\u26A0 Existing database setup detected: ${databaseLabel(installedDialect)}.`
|
|
1065
|
+
)
|
|
1066
|
+
);
|
|
1067
|
+
console.log(
|
|
1068
|
+
import_chalk4.default.yellow(
|
|
1069
|
+
` Switching to ${databaseLabel(resolvedModuleName)} will overwrite db files and drizzle config.
|
|
1070
|
+
`
|
|
1071
|
+
)
|
|
1072
|
+
);
|
|
1073
|
+
const switchResponse = await (0, import_prompts2.default)({
|
|
1074
|
+
type: "confirm",
|
|
1075
|
+
name: "proceed",
|
|
1076
|
+
message: "Continue and switch database dialect?",
|
|
1077
|
+
initial: false
|
|
1078
|
+
});
|
|
1079
|
+
if (!switchResponse.proceed) {
|
|
1080
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
databaseBackupPath = await backupDatabaseFiles(projectRoot, srcDir);
|
|
1084
|
+
}
|
|
1085
|
+
const defaultUrl = DEFAULT_DATABASE_URLS[resolvedModuleName];
|
|
697
1086
|
console.log(import_chalk4.default.dim(` Tip: Leave blank to use ${defaultUrl}
|
|
698
1087
|
`));
|
|
699
1088
|
const response = await (0, import_prompts2.default)({
|
|
@@ -702,53 +1091,82 @@ var add = async (moduleName) => {
|
|
|
702
1091
|
message: "Database URL",
|
|
703
1092
|
initial: ""
|
|
704
1093
|
});
|
|
705
|
-
|
|
1094
|
+
if (response.dbUrl === void 0) {
|
|
1095
|
+
console.log(import_chalk4.default.yellow("Operation cancelled."));
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const enteredUrl = response.dbUrl?.trim() || "";
|
|
1099
|
+
usedDefaultDbUrl = enteredUrl.length === 0;
|
|
1100
|
+
customDbUrl = validateDatabaseUrl(enteredUrl || defaultUrl, resolvedModuleName);
|
|
706
1101
|
}
|
|
707
|
-
const
|
|
1102
|
+
const pm = resolvePackageManager(projectRoot);
|
|
1103
|
+
const spinner = (0, import_ora2.default)(`Checking registry for ${resolvedModuleName}...`).start();
|
|
1104
|
+
let currentStep = "package manager preflight";
|
|
708
1105
|
try {
|
|
1106
|
+
spinner.text = `Checking ${pm} availability...`;
|
|
1107
|
+
await ensurePackageManagerAvailable(pm);
|
|
1108
|
+
currentStep = "registry fetch";
|
|
1109
|
+
spinner.text = `Checking registry for ${resolvedModuleName}...`;
|
|
709
1110
|
const registryContext = await fetchRegistry();
|
|
710
|
-
const module2 = registryContext.manifest.modules[
|
|
1111
|
+
const module2 = registryContext.manifest.modules[resolvedModuleName];
|
|
711
1112
|
if (!module2) {
|
|
712
|
-
spinner.fail(`Module '${
|
|
1113
|
+
spinner.fail(`Module '${resolvedModuleName}' not found.`);
|
|
713
1114
|
return;
|
|
714
1115
|
}
|
|
715
|
-
spinner.succeed(`Found module: ${import_chalk4.default.cyan(
|
|
1116
|
+
spinner.succeed(`Found module: ${import_chalk4.default.cyan(resolvedModuleName)}`);
|
|
716
1117
|
const moduleDeps = module2.moduleDependencies || [];
|
|
1118
|
+
currentStep = "module dependency resolution";
|
|
717
1119
|
await resolveDependencies(moduleDeps, projectRoot);
|
|
1120
|
+
currentStep = "dependency installation";
|
|
718
1121
|
spinner.start("Installing dependencies...");
|
|
719
|
-
let pm = "npm";
|
|
720
|
-
if (import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
721
|
-
pm = "pnpm";
|
|
722
|
-
} else if (import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "bun.lockb"))) {
|
|
723
|
-
pm = "bun";
|
|
724
|
-
} else if (import_fs_extra6.default.existsSync(import_path6.default.join(projectRoot, "yarn.lock"))) {
|
|
725
|
-
pm = "yarn";
|
|
726
|
-
}
|
|
727
1122
|
await installDependencies(pm, module2.dependencies || [], projectRoot);
|
|
728
1123
|
await installDependencies(pm, module2.devDependencies || [], projectRoot, { dev: true });
|
|
729
1124
|
spinner.succeed("Dependencies installed");
|
|
1125
|
+
currentStep = "module scaffolding";
|
|
730
1126
|
spinner.start("Scaffolding files...");
|
|
1127
|
+
if (resolvedModuleName === "auth") {
|
|
1128
|
+
authDatabaseDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);
|
|
1129
|
+
}
|
|
731
1130
|
for (const file of module2.files) {
|
|
732
|
-
|
|
1131
|
+
let fetchPath = file.path;
|
|
1132
|
+
let expectedSha256 = file.sha256;
|
|
1133
|
+
let expectedSize = file.size;
|
|
1134
|
+
if (resolvedModuleName === "auth" && file.target === "db/schema/auth.ts" && authDatabaseDialect === "database-mysql") {
|
|
1135
|
+
fetchPath = "express/db/schema/auth.mysql.ts";
|
|
1136
|
+
expectedSha256 = void 0;
|
|
1137
|
+
expectedSize = void 0;
|
|
1138
|
+
}
|
|
1139
|
+
let content = await fetchFile(fetchPath, {
|
|
733
1140
|
baseUrl: registryContext.fileBaseUrl,
|
|
734
|
-
expectedSha256
|
|
735
|
-
expectedSize
|
|
1141
|
+
expectedSha256,
|
|
1142
|
+
expectedSize
|
|
736
1143
|
});
|
|
1144
|
+
if (isDatabaseModule(resolvedModuleName) && file.target === "../drizzle.config.ts") {
|
|
1145
|
+
const normalizedSrcDir = srcDir.replace(/\\/g, "/");
|
|
1146
|
+
content = content.replace(
|
|
1147
|
+
/schema:\s*["'][^"']+["']/,
|
|
1148
|
+
`schema: "./${normalizedSrcDir}/db/schema/*"`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
737
1151
|
const targetPath = resolveSafeTargetPath2(projectRoot, srcDir, file);
|
|
738
1152
|
await import_fs_extra6.default.ensureDir(import_path6.default.dirname(targetPath));
|
|
739
1153
|
await import_fs_extra6.default.writeFile(targetPath, content);
|
|
740
1154
|
}
|
|
1155
|
+
const schemaExports = module2.files.map((file) => file.target.replace(/\\/g, "/")).filter((target) => /^db\/schema\/[^/]+\.ts$/.test(target)).map((target) => import_path6.default.posix.basename(target, ".ts")).filter((name) => name !== "index");
|
|
1156
|
+
for (const schemaFileName of schemaExports) {
|
|
1157
|
+
await ensureSchemaExport(projectRoot, srcDir, schemaFileName);
|
|
1158
|
+
}
|
|
741
1159
|
spinner.succeed("Files generated");
|
|
742
|
-
if (
|
|
743
|
-
spinner.start("Configuring routes
|
|
1160
|
+
if (resolvedModuleName === "auth") {
|
|
1161
|
+
spinner.start("Configuring routes...");
|
|
744
1162
|
const injected = await injectAuthRoutes(projectRoot, srcDir);
|
|
745
1163
|
if (injected) {
|
|
746
|
-
spinner.succeed("Routes configured
|
|
1164
|
+
spinner.succeed("Routes configured");
|
|
747
1165
|
} else {
|
|
748
|
-
spinner.warn("Could not
|
|
1166
|
+
spinner.warn("Could not configure routes automatically");
|
|
749
1167
|
}
|
|
750
1168
|
}
|
|
751
|
-
if (
|
|
1169
|
+
if (resolvedModuleName === "error-handler") {
|
|
752
1170
|
spinner.start("Configuring error handler in app.ts...");
|
|
753
1171
|
const injected = await injectErrorHandler(projectRoot, srcDir);
|
|
754
1172
|
if (injected) {
|
|
@@ -757,78 +1175,63 @@ var add = async (moduleName) => {
|
|
|
757
1175
|
spinner.warn("Could not find app.ts - error handler needs manual setup");
|
|
758
1176
|
}
|
|
759
1177
|
}
|
|
760
|
-
const envConfig = ENV_CONFIGS[
|
|
1178
|
+
const envConfig = ENV_CONFIGS[resolvedModuleName];
|
|
761
1179
|
if (envConfig) {
|
|
1180
|
+
currentStep = "environment configuration";
|
|
762
1181
|
spinner.start("Updating environment configuration...");
|
|
763
1182
|
const envVars = { ...envConfig.envVars };
|
|
764
|
-
if (customDbUrl && (
|
|
1183
|
+
if (customDbUrl && isDatabaseModule(resolvedModuleName)) {
|
|
765
1184
|
envVars.DATABASE_URL = customDbUrl;
|
|
766
1185
|
}
|
|
767
|
-
|
|
1186
|
+
if (resolvedModuleName === "auth") {
|
|
1187
|
+
const hasExistingSecret = await hasEnvVariable(projectRoot, "BETTER_AUTH_SECRET");
|
|
1188
|
+
if (!hasExistingSecret) {
|
|
1189
|
+
envVars.BETTER_AUTH_SECRET = (0, import_node_crypto2.randomBytes)(32).toString("hex");
|
|
1190
|
+
generatedAuthSecret = true;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
await updateEnvFile(projectRoot, envVars, true, {
|
|
1194
|
+
overwriteExisting: isDatabaseModule(resolvedModuleName)
|
|
1195
|
+
});
|
|
768
1196
|
await updateEnvSchema(projectRoot, srcDir, envConfig.schemaFields);
|
|
769
1197
|
spinner.succeed("Environment configured");
|
|
770
1198
|
}
|
|
771
1199
|
console.log(import_chalk4.default.green(`
|
|
772
|
-
\u2714 ${
|
|
1200
|
+
\u2714 ${resolvedModuleName} added successfully!
|
|
773
1201
|
`));
|
|
774
|
-
if (
|
|
775
|
-
console.log(import_chalk4.default.
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1202
|
+
if (databaseBackupPath) {
|
|
1203
|
+
console.log(import_chalk4.default.blue(`\u2139 Backup created at: ${databaseBackupPath}
|
|
1204
|
+
`));
|
|
1205
|
+
}
|
|
1206
|
+
const docsPath = getModuleDocsPath(resolvedModuleName);
|
|
1207
|
+
const docsUrl = `https://zuro-cli.devbybriyan.com/docs/${docsPath}`;
|
|
1208
|
+
console.log(import_chalk4.default.blue(`\u2139 Docs: ${docsUrl}`));
|
|
1209
|
+
if (isDatabaseModule(resolvedModuleName)) {
|
|
1210
|
+
if (usedDefaultDbUrl) {
|
|
1211
|
+
console.log(import_chalk4.default.yellow("\u2139 Review DATABASE_URL in .env if your local DB config differs."));
|
|
1212
|
+
}
|
|
1213
|
+
const setupHint = getDatabaseSetupHint(
|
|
1214
|
+
resolvedModuleName,
|
|
1215
|
+
customDbUrl || DEFAULT_DATABASE_URLS[resolvedModuleName]
|
|
779
1216
|
);
|
|
780
|
-
console.log(import_chalk4.default.yellow(
|
|
781
|
-
console.log(import_chalk4.default.
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
} else if (moduleName === "error-handler") {
|
|
789
|
-
console.log(import_chalk4.default.bold("\u{1F4CB} Usage:\n"));
|
|
790
|
-
console.log(import_chalk4.default.yellow("Throw errors in your controllers:"));
|
|
791
|
-
console.log(import_chalk4.default.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"));
|
|
792
|
-
console.log(import_chalk4.default.white(` import { UnauthorizedError, NotFoundError } from "./lib/errors";`));
|
|
793
|
-
console.log("");
|
|
794
|
-
console.log(import_chalk4.default.white(` throw new UnauthorizedError("Invalid credentials");`));
|
|
795
|
-
console.log(import_chalk4.default.white(` throw new NotFoundError("User not found");`));
|
|
796
|
-
console.log(import_chalk4.default.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"));
|
|
797
|
-
console.log(import_chalk4.default.yellow("Available error classes:"));
|
|
798
|
-
console.log(import_chalk4.default.dim(" BadRequestError (400)"));
|
|
799
|
-
console.log(import_chalk4.default.dim(" UnauthorizedError (401)"));
|
|
800
|
-
console.log(import_chalk4.default.dim(" ForbiddenError (403)"));
|
|
801
|
-
console.log(import_chalk4.default.dim(" NotFoundError (404)"));
|
|
802
|
-
console.log(import_chalk4.default.dim(" ConflictError (409)"));
|
|
803
|
-
console.log(import_chalk4.default.dim(" ValidationError (422)"));
|
|
804
|
-
console.log(import_chalk4.default.dim(" InternalServerError (500)\n"));
|
|
805
|
-
console.log(import_chalk4.default.yellow("Wrap async handlers:"));
|
|
806
|
-
console.log(import_chalk4.default.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"));
|
|
807
|
-
console.log(import_chalk4.default.white(` import { asyncHandler } from "./middleware/error-handler";`));
|
|
808
|
-
console.log("");
|
|
809
|
-
console.log(import_chalk4.default.white(` router.get("/users", asyncHandler(async (req, res) => {`));
|
|
810
|
-
console.log(import_chalk4.default.white(" // errors auto-caught"));
|
|
811
|
-
console.log(import_chalk4.default.white(" }));"));
|
|
812
|
-
console.log(import_chalk4.default.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"));
|
|
813
|
-
} else if (moduleName.includes("database")) {
|
|
814
|
-
console.log(import_chalk4.default.bold("\u{1F4CB} Next Steps:\n"));
|
|
815
|
-
let stepNum = 1;
|
|
816
|
-
if (!customDbUrl) {
|
|
817
|
-
console.log(import_chalk4.default.yellow(`${stepNum}. Update DATABASE_URL in .env:`));
|
|
818
|
-
console.log(
|
|
819
|
-
import_chalk4.default.dim(" We added a placeholder. Update with your actual database credentials.\n")
|
|
820
|
-
);
|
|
821
|
-
stepNum++;
|
|
1217
|
+
console.log(import_chalk4.default.yellow(`\u2139 Ensure DB exists: ${setupHint}`));
|
|
1218
|
+
console.log(import_chalk4.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
1219
|
+
}
|
|
1220
|
+
if (resolvedModuleName === "auth") {
|
|
1221
|
+
if (generatedAuthSecret) {
|
|
1222
|
+
console.log(import_chalk4.default.yellow("\u2139 BETTER_AUTH_SECRET was generated automatically."));
|
|
1223
|
+
} else {
|
|
1224
|
+
console.log(import_chalk4.default.yellow("\u2139 Review BETTER_AUTH_SECRET and BETTER_AUTH_URL in .env."));
|
|
822
1225
|
}
|
|
823
|
-
console.log(import_chalk4.default.yellow(
|
|
824
|
-
console.log(import_chalk4.default.dim(" Add table files and export from index.ts\n"));
|
|
825
|
-
stepNum++;
|
|
826
|
-
console.log(import_chalk4.default.yellow(`${stepNum}. Run migrations:`));
|
|
827
|
-
console.log(import_chalk4.default.cyan(" npx drizzle-kit generate"));
|
|
828
|
-
console.log(import_chalk4.default.cyan(" npx drizzle-kit migrate\n"));
|
|
1226
|
+
console.log(import_chalk4.default.yellow("\u2139 Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate"));
|
|
829
1227
|
}
|
|
830
1228
|
} catch (error) {
|
|
831
|
-
spinner.fail(`Failed
|
|
1229
|
+
spinner.fail(import_chalk4.default.red(`Failed during ${currentStep}.`));
|
|
1230
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1231
|
+
console.error(import_chalk4.default.red(errorMessage));
|
|
1232
|
+
console.log(`
|
|
1233
|
+
${import_chalk4.default.bold("Retry:")}`);
|
|
1234
|
+
console.log(import_chalk4.default.cyan(` npx zuro-cli add ${resolvedModuleName}`));
|
|
832
1235
|
}
|
|
833
1236
|
};
|
|
834
1237
|
|