zuro-cli 0.0.2-beta.14 → 0.0.2-beta.16

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zuro-cli",
3
- "version": "0.0.2-beta.14",
4
- "description": "The backend builder for busy developers.",
3
+ "version": "0.0.2-beta.16",
4
+ "description": "Production-ready backend modules you own. No framework lock-in.",
5
5
  "bin": {
6
6
  "zuro-cli": "./dist/index.js"
7
7
  },
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/handlers/database.handler.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport chalk from \"chalk\";\nimport { escapeRegex } from \"../utils/code-inject\";\n\nexport type DatabaseModuleName = \"database-pg\" | \"database-mysql\";\n\nexport const DEFAULT_DATABASE_URLS: Record<DatabaseModuleName, string> = {\n \"database-pg\": \"postgresql://postgres@localhost:5432/mydb\",\n \"database-mysql\": \"mysql://root@localhost:3306/mydb\",\n};\n\nexport function parseDatabaseDialect(value?: string): DatabaseModuleName | null {\n const normalized = value?.trim().toLowerCase();\n if (!normalized) {\n return null;\n }\n\n if (normalized === \"pg\" || normalized === \"postgres\" || normalized === \"postgresql\" || normalized === \"database-pg\") {\n return \"database-pg\";\n }\n\n if (normalized === \"mysql\" || normalized === \"database-mysql\") {\n return \"database-mysql\";\n }\n\n return null;\n}\n\nexport function isDatabaseModule(moduleName: string): moduleName is DatabaseModuleName {\n return moduleName === \"database-pg\" || moduleName === \"database-mysql\";\n}\n\nexport function validateDatabaseUrl(rawUrl: string, moduleName: DatabaseModuleName) {\n const dbUrl = rawUrl.trim();\n if (!dbUrl) {\n throw new Error(\"Database URL cannot be empty.\");\n }\n\n let parsed: URL;\n try {\n parsed = new URL(dbUrl);\n } catch {\n throw new Error(`Invalid database URL: '${dbUrl}'.`);\n }\n\n const protocol = parsed.protocol.toLowerCase();\n if (moduleName === \"database-pg\" && protocol !== \"postgresql:\" && protocol !== \"postgres:\") {\n throw new Error(\"PostgreSQL URL must start with postgres:// or postgresql://\");\n }\n\n if (moduleName === \"database-mysql\" && protocol !== \"mysql:\") {\n throw new Error(\"MySQL URL must start with mysql://\");\n }\n\n return dbUrl;\n}\n\nexport async function detectInstalledDatabaseDialect(projectRoot: string, srcDir: string): Promise<DatabaseModuleName | null> {\n const dbIndexPath = path.join(projectRoot, srcDir, \"db\", \"index.ts\");\n if (!fs.existsSync(dbIndexPath)) {\n return null;\n }\n\n const content = await fs.readFile(dbIndexPath, \"utf-8\");\n if (content.includes(\"drizzle-orm/node-postgres\") || content.includes(`from \"pg\"`)) {\n return \"database-pg\";\n }\n\n if (content.includes(\"drizzle-orm/mysql2\") || content.includes(`from \"mysql2`)) {\n return \"database-mysql\";\n }\n\n return null;\n}\n\nexport async function backupDatabaseFiles(projectRoot: string, srcDir: string): Promise<string | null> {\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupRoot = path.join(projectRoot, \".zuro\", \"backups\", `database-${timestamp}`);\n const candidates = [\n path.join(projectRoot, srcDir, \"db\", \"index.ts\"),\n path.join(projectRoot, \"drizzle.config.ts\"),\n ];\n\n let copied = false;\n for (const filePath of candidates) {\n if (!fs.existsSync(filePath)) {\n continue;\n }\n\n const relativePath = path.relative(projectRoot, filePath);\n const backupPath = path.join(backupRoot, relativePath);\n await fs.ensureDir(path.dirname(backupPath));\n await fs.copyFile(filePath, backupPath);\n copied = true;\n }\n\n return copied ? backupRoot : null;\n}\n\nexport function databaseLabel(moduleName: DatabaseModuleName) {\n return moduleName === \"database-pg\" ? \"PostgreSQL\" : \"MySQL\";\n}\n\nexport function getDatabaseSetupHint(moduleName: DatabaseModuleName, dbUrl: string) {\n try {\n const parsed = new URL(dbUrl);\n const dbName = parsed.pathname.replace(/^\\/+/, \"\") || \"mydb\";\n\n if (moduleName === \"database-pg\") {\n return `createdb ${dbName}`;\n }\n\n return `mysql -e \"CREATE DATABASE IF NOT EXISTS ${dbName};\"`;\n } catch {\n return moduleName === \"database-pg\"\n ? \"createdb <database_name>\"\n : `mysql -e \"CREATE DATABASE IF NOT EXISTS <database_name>;\"`;\n }\n}\n\nexport async function ensureSchemaExport(projectRoot: string, srcDir: string, schemaFileName: string) {\n const schemaIndexPath = path.join(projectRoot, srcDir, \"db\", \"schema\", \"index.ts\");\n if (!await fs.pathExists(schemaIndexPath)) {\n return;\n }\n\n const exportLine = `export * from \"./${schemaFileName}\";`;\n const content = await fs.readFile(schemaIndexPath, \"utf-8\");\n const normalized = content.replace(/\\r\\n/g, \"\\n\");\n const exportPattern = new RegExp(\n `^\\\\s*export\\\\s*\\\\*\\\\s*from\\\\s*[\"']\\\\./${escapeRegex(schemaFileName)}[\"'];?\\\\s*$`,\n \"m\"\n );\n\n if (exportPattern.test(normalized)) {\n return;\n }\n\n let next = normalized\n .replace(/^\\s*export\\s*\\{\\s*\\};?\\s*$/m, \"\")\n .trimEnd();\n\n if (next.length > 0) {\n next += \"\\n\\n\";\n }\n\n next += `${exportLine}\\n`;\n await fs.writeFile(schemaIndexPath, next);\n}\n\nexport interface DatabasePromptResult {\n resolvedModuleName: DatabaseModuleName;\n customDbUrl: string | undefined;\n usedDefaultDbUrl: boolean;\n databaseBackupPath: string | null;\n}\n\n/**\n * Runs all database-specific interactive prompts and validation.\n * Returns null if the user cancels.\n */\nexport async function promptDatabaseConfig(\n initialModuleName: string,\n projectRoot: string,\n srcDir: string,\n): Promise<DatabasePromptResult | null> {\n let resolvedModuleName: DatabaseModuleName;\n\n if (initialModuleName === \"database\") {\n const variantResponse = await prompts({\n type: \"select\",\n name: \"variant\",\n message: \"Which database dialect?\",\n choices: [\n { title: \"PostgreSQL\", value: \"database-pg\" },\n { title: \"MySQL\", value: \"database-mysql\" },\n ],\n });\n\n if (!variantResponse.variant) {\n console.log(chalk.yellow(\"Operation cancelled.\"));\n return null;\n }\n\n resolvedModuleName = variantResponse.variant;\n } else {\n resolvedModuleName = initialModuleName as DatabaseModuleName;\n }\n\n // Check for dialect switch\n let databaseBackupPath: string | null = null;\n const installedDialect = await detectInstalledDatabaseDialect(projectRoot, srcDir);\n\n if (installedDialect && installedDialect !== resolvedModuleName) {\n console.log(\n chalk.yellow(\n `\\n⚠ Existing database setup detected: ${databaseLabel(installedDialect)}.`\n )\n );\n console.log(\n chalk.yellow(\n ` Switching to ${databaseLabel(resolvedModuleName)} will overwrite db files and drizzle config.\\n`\n )\n );\n\n const switchResponse = await prompts({\n type: \"confirm\",\n name: \"proceed\",\n message: \"Continue and switch database dialect?\",\n initial: false,\n });\n\n if (!switchResponse.proceed) {\n console.log(chalk.yellow(\"Operation cancelled.\"));\n return null;\n }\n\n databaseBackupPath = await backupDatabaseFiles(projectRoot, srcDir);\n }\n\n // Prompt for database URL\n const defaultUrl = DEFAULT_DATABASE_URLS[resolvedModuleName];\n console.log(chalk.dim(` Tip: Leave blank to use ${defaultUrl}\\n`));\n\n const response = await prompts({\n type: \"text\",\n name: \"dbUrl\",\n message: \"Database URL\",\n initial: \"\",\n });\n\n if (response.dbUrl === undefined) {\n console.log(chalk.yellow(\"Operation cancelled.\"));\n return null;\n }\n\n const enteredUrl = response.dbUrl?.trim() || \"\";\n const usedDefaultDbUrl = enteredUrl.length === 0;\n const customDbUrl = validateDatabaseUrl(enteredUrl || defaultUrl, resolvedModuleName);\n\n return { resolvedModuleName, customDbUrl, usedDefaultDbUrl, databaseBackupPath };\n}\n\n/**\n * Prints post-install hints for database modules.\n */\nexport function printDatabaseHints(\n moduleName: DatabaseModuleName,\n customDbUrl: string | undefined,\n usedDefaultDbUrl: boolean,\n databaseBackupPath: string | null,\n) {\n if (databaseBackupPath) {\n console.log(chalk.blue(`ℹ Backup created at: ${databaseBackupPath}\\n`));\n }\n\n if (usedDefaultDbUrl) {\n console.log(chalk.yellow(\"ℹ Review DATABASE_URL in .env if your local DB config differs.\"));\n }\n\n const setupHint = getDatabaseSetupHint(\n moduleName,\n customDbUrl || DEFAULT_DATABASE_URLS[moduleName]\n );\n console.log(chalk.yellow(`ℹ Ensure DB exists: ${setupHint}`));\n console.log(chalk.yellow(\"ℹ Run migrations: npx drizzle-kit generate && npx drizzle-kit migrate\"));\n}\n"],"mappings":"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","names":[]}