diff --git a/package.json b/package.json index 88a55be..c3a1883 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "prepublishOnly": "npm run build" }, "bin": { - "zen-db": "./dist/cli/database.js" + "zen-db": "./dist/core/database/cli.js" }, "dependencies": { "@headlessui/react": "^2.0.0", diff --git a/src/core/database/cli.js b/src/core/database/cli.js new file mode 100644 index 0000000..80f8615 --- /dev/null +++ b/src/core/database/cli.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node + +/** + * Zen Database CLI + * Command-line tool for database management + */ + +// Load environment variables from the project's .env file +import dotenv from 'dotenv'; +import { resolve } from 'node:path'; + +// Load .env from the current working directory (user's project) +dotenv.config({ path: resolve(process.cwd(), '.env') }); +dotenv.config({ path: resolve(process.cwd(), '.env.local') }); + +// The CLI always runs locally, so default to development to use ZEN_DATABASE_URL_DEV if set +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +import { testConnection, closePool } from './index.js'; +import readline from 'readline'; +import { step, done, warn, fail } from '../../shared/lib/logger.js'; + +function printHelp() { + console.log(` +Zen Database CLI + +Usage: + npx zen-db + +Commands: + init Initialize database (create all required tables) + test Test database connection + drop Drop all tables (DANGER!) + help Show this help message + +Example: + npx zen-db init + `); +} + +/** + * Prompt the user for a confirmation answer. + * @param {string} question + * @returns {Promise} The trimmed, lowercased answer + */ +function askConfirmation(question) { + return new Promise((resolve) => { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + rl.question(question, (answer) => { + rl.close(); + resolve(answer.trim().toLowerCase()); + }); + }); +} + +async function runCLI() { + const command = process.argv[2]; + + if (!command) { + printHelp(); + process.exit(0); + } + + try { + switch (command) { + case 'init': { + step('Initializing database...'); + + const { initFeatures } = await import('../../features/init.js'); + const featuresResult = await initFeatures(); + + done(`DB ready — ${featuresResult.created.length} tables created, ${featuresResult.skipped.length} skipped`); + break; + } + + case 'test': + step('Testing database connection...'); + const isConnected = await testConnection(); + if (isConnected) { + done('Database connection successful'); + } else { + fail('Database connection failed'); + process.exit(1); + } + break; + + case 'drop': { + warn('This will delete all tables!'); + process.stdout.write(' Type "yes" to confirm or Ctrl+C to cancel...\n'); + const answer = await askConfirmation('Confirm (yes/no): '); + if (answer === 'yes') { + const { dropFeatures } = await import('../../features/init.js'); + await dropFeatures(); + done('Tables dropped successfully'); + } else { + warn('Operation cancelled'); + } + break; + } + + case 'help': + printHelp(); + break; + + default: + fail(`Unknown command: ${command}`); + process.stdout.write(' Run "npx zen-db help" for usage information.\n'); + process.exit(1); + } + + await closePool(); + process.exit(0); + + } catch (error) { + fail(`Error: ${error.message}`); + await closePool(); + process.exit(1); + } +} + +// Run CLI if called directly +import { fileURLToPath } from 'url'; +import { realpathSync } from 'node:fs'; +const __filename = realpathSync(fileURLToPath(import.meta.url)); +const isMainModule = process.argv[1] && realpathSync(process.argv[1]) === __filename; + +if (isMainModule) { + runCLI(); +} + +export { runCLI }; diff --git a/tsup.config.js b/tsup.config.js index 5e44e06..bd38868 100644 --- a/tsup.config.js +++ b/tsup.config.js @@ -17,7 +17,7 @@ export default defineConfig([ 'src/core/api/route-handler.js', 'src/core/cron/index.js', 'src/core/database/index.js', - 'src/cli/database.js', + 'src/core/database/cli.js', 'src/core/email/index.js', 'src/core/email/templates/index.js', 'src/core/storage/index.js',