UNPKG

@gftdcojp/gftd-cli

Version:

GFTD Schema Management CLI - Drizzle-like schema management for Kafka and ksqlDB

590 lines (589 loc) 23.8 kB
#!/usr/bin/env node "use strict"; /** * GFTD CLI - Main CLI Tool * Drizzle-like schema management for Kafka and ksqlDB with organization support */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.main = main; const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const fs = __importStar(require("fs")); const config_manager_1 = require("./core/config-manager"); const schema_manager_1 = require("./core/schema-manager"); const dev_proxy_manager_1 = require("./core/dev-proxy-manager"); const database_manager_1 = require("./core/database-manager"); const ora_1 = __importDefault(require("ora")); const program = new commander_1.Command(); // バージョン情報 const packageJson = require('../package.json'); program .name('gftd') .description('GFTD Schema Management CLI - Drizzle-like schema management for Kafka and ksqlDB') .version(packageJson.version); // init コマンド program .command('init') .description('Initialize a new GFTD project') .requiredOption('--org-id <orgId>', 'Organization ID') .option('--project <project>', 'Project name (optional)') .option('--interactive', 'Interactive mode', false) .action(async (options) => { try { console.log(chalk_1.default.blue('🚀 Initializing GFTD project...')); const initOptions = { orgId: options.orgId, project: options.project, interactive: options.interactive }; await config_manager_1.ConfigManager.init(initOptions); // .env.example を作成 const envExample = config_manager_1.ConfigManager.generateEnvExample(); fs.writeFileSync('.env.example', envExample); console.log('📄 Created .env.example file'); } catch (error) { console.error(chalk_1.default.red('❌ Initialization failed:'), error.message); process.exit(1); } }); // migrate コマンド program .command('migrate') .description('Run schema migrations') .option('--config <path>', 'Configuration file path') .option('--dry-run', 'Show what would be executed without running', false) .option('--force', 'Force migration even if some fail', false) .action(async (options) => { try { const spinner = (0, ora_1.default)('Loading configuration...').start(); const config = config_manager_1.ConfigManager.loadConfig(options.config); const schemaManager = new schema_manager_1.SchemaManager(config); spinner.text = 'Running migrations...'; const migrateOptions = { dryRun: options.dryRun, force: options.force }; await schemaManager.runMigrations(migrateOptions); spinner.succeed(chalk_1.default.green('✅ Migrations completed')); } catch (error) { console.error(chalk_1.default.red('❌ Migration failed:'), error.message); process.exit(1); } }); // status コマンド program .command('status') .description('Check current schema status') .option('--config <path>', 'Configuration file path') .action(async (options) => { try { const config = config_manager_1.ConfigManager.loadConfig(options.config); const schemaManager = new schema_manager_1.SchemaManager(config); await schemaManager.status(); } catch (error) { console.error(chalk_1.default.red('❌ Status check failed:'), error.message); process.exit(1); } }); // info コマンド program .command('info') .description('Show project information') .option('--config <path>', 'Configuration file path') .action(async (options) => { try { const config = config_manager_1.ConfigManager.loadConfig(options.config); config_manager_1.ConfigManager.showInfo(config); } catch (error) { console.error(chalk_1.default.red('❌ Info command failed:'), error.message); process.exit(1); } }); // validate コマンド program .command('validate') .description('Validate schema definition') .option('--config <path>', 'Configuration file path') .option('--schema <path>', 'Schema file path') .action(async (options) => { try { console.log(chalk_1.default.blue('🔍 Validating schema...')); const config = config_manager_1.ConfigManager.loadConfig(options.config); const schemaManager = new schema_manager_1.SchemaManager(config); const schema = schemaManager.loadSchema(options.schema); const validation = schemaManager.validateSchema(schema); if (validation.isValid) { console.log(chalk_1.default.green('✅ Schema validation passed')); if (validation.warnings.length > 0) { console.log(chalk_1.default.yellow('\n⚠️ Warnings:')); validation.warnings.forEach(warning => { console.log(chalk_1.default.yellow(` - ${warning}`)); }); } // 統計情報を表示 const stats = { topics: Object.keys(schema.topics).length, streams: Object.keys(schema.streams).length, tables: Object.keys(schema.tables).length }; console.log('\n📊 Schema Statistics:'); console.log(` Topics: ${stats.topics}`); console.log(` Streams: ${stats.streams}`); console.log(` Tables: ${stats.tables}`); } else { console.log(chalk_1.default.red('❌ Schema validation failed')); validation.errors.forEach(error => { console.log(chalk_1.default.red(` - ${error}`)); }); process.exit(1); } } catch (error) { console.error(chalk_1.default.red('❌ Validation failed:'), error.message); process.exit(1); } }); // generate コマンド (SQL生成のみ) program .command('generate') .description('Generate SQL migrations without executing') .option('--config <path>', 'Configuration file path') .option('--output <path>', 'Output directory for SQL files') .action(async (options) => { try { console.log(chalk_1.default.blue('📝 Generating SQL migrations...')); const config = config_manager_1.ConfigManager.loadConfig(options.config); const schemaManager = new schema_manager_1.SchemaManager(config); const schema = schemaManager.loadSchema(); const migrations = schemaManager.generateMigrations(schema); const outputDir = options.output || './migrations'; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } migrations.forEach(migration => { const filename = `${migration.id}.sql`; const filepath = `${outputDir}/${filename}`; const content = `-- ${migration.name}\n-- Generated at: ${new Date().toISOString()}\n-- Organization: ${migration.orgId}\n\n${migration.sql}\n`; fs.writeFileSync(filepath, content); console.log(` 📄 Generated: ${filename}`); }); console.log(chalk_1.default.green(`✅ Generated ${migrations.length} migration files in ${outputDir}`)); } catch (error) { console.error(chalk_1.default.red('❌ Generation failed:'), error.message); process.exit(1); } }); // dev コマンド program .command('dev') .description('Start local development server with proxy') .option('--port <port>', 'Project port (optional)') .option('--name <name>', 'Project name (optional)') .option('--subdomain <subdomain>', 'Custom subdomain (optional)') .option('--command <command>', 'Custom dev command (default: pnpm dev)') .option('--path <path>', 'Project path (default: current directory)') .option('--proxy', 'Start proxy server only', false) .option('--list', 'List all projects', false) .option('--stop <id>', 'Stop project by ID') .option('--stop-all', 'Stop all projects', false) .option('--remove <id>', 'Remove project by ID') .option('--dns-status', 'Show DNS management status', false) .option('--manage-dns', 'Enable DNS management for this project', true) .option('--skip-dns', 'Skip DNS management for this project', false) .action(async (options) => { try { const devManager = new dev_proxy_manager_1.DevProxyManager(); // DNS 状態表示 if (options.dnsStatus) { await devManager.showDnsStatus(); return; } // プロジェクト一覧表示 if (options.list) { devManager.listProjects(); return; } // プロジェクト削除 if (options.remove) { await devManager.removeProject(options.remove); return; } // 全プロジェクト停止 if (options.stopAll) { await devManager.stopAll(); return; } // 特定プロジェクト停止 if (options.stop) { await devManager.stopProject(options.stop); return; } // プロキシサーバーのみ起動 if (options.proxy) { await devManager.startProxy(); return; } // 新しいプロジェクトを追加・起動 const devOptions = { port: options.port ? parseInt(options.port) : undefined, name: options.name, subdomain: options.subdomain, command: options.command, path: options.path, manageDns: options.manageDns && !options.skipDns }; console.log(chalk_1.default.blue('🚀 Starting development environment...')); // プロジェクトを追加 const project = await devManager.addProject(devOptions); // プロキシサーバーを起動(まだ起動していない場合) await devManager.startProxy(); // プロジェクトを開始 await devManager.startProject(project.id); console.log(chalk_1.default.green('✅ Development environment ready!')); console.log(chalk_1.default.blue(` 🌐 Access your project at: http://${project.subdomain}.gftd.ai.dev`)); console.log(chalk_1.default.gray(` 📋 Use "gftd dev --list" to see all projects`)); console.log(chalk_1.default.gray(` 🛑 Use "gftd dev --stop ${project.id}" to stop this project`)); console.log(chalk_1.default.gray(` 🗑️ Use "gftd dev --remove ${project.id}" to remove this project`)); } catch (error) { console.error(chalk_1.default.red('❌ Dev command failed:'), error.message); process.exit(1); } }); // データベース初期化コマンド (supabase init相当) program .command('db:init') .description('Initialize database project with Supabase-like structure') .option('--type <type>', 'Database type (postgresql, mysql, sqlite)', 'postgresql') .option('--database <name>', 'Database name', 'gftd_dev') .option('--force', 'Force overwrite existing configuration', false) .option('--template <template>', 'Use template for initialization') .action(async (options) => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.init(options); } catch (error) { console.error(chalk_1.default.red('Failed to initialize database project:'), error); process.exit(1); } }); // マイグレーション生成コマンド (drizzle generate相当) program .command('migration:generate') .description('Generate new migration from schema changes') .argument('[name]', 'Migration name') .option('--dry', 'Show what would be generated without creating files', false) .option('--force', 'Force generation even if no changes detected', false) .action(async (name, options) => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.generateMigration(name, options); } catch (error) { console.error(chalk_1.default.red('Failed to generate migration:'), error); process.exit(1); } }); // マイグレーション実行コマンド (supabase migration up相当) program .command('migration:up') .description('Apply pending migrations') .option('--dry', 'Show what would be applied without executing', false) .option('--target <version>', 'Migrate to specific version') .action(async (options) => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.runMigrationsUp(options); } catch (error) { console.error(chalk_1.default.red('Failed to apply migrations:'), error); process.exit(1); } }); // マイグレーション巻き戻しコマンド (supabase migration down相当) program .command('migration:down') .description('Rollback migrations') .option('--steps <number>', 'Number of migrations to rollback', '1') .option('--dry', 'Show what would be rolled back without executing', false) .action(async (options) => { try { const migrationOptions = { ...options, steps: parseInt(options.steps, 10) }; const dbManager = new database_manager_1.DatabaseManager(); await dbManager.runMigrationsDown(migrationOptions); } catch (error) { console.error(chalk_1.default.red('Failed to rollback migrations:'), error); process.exit(1); } }); // マイグレーション一覧コマンド (supabase migration list相当) program .command('migration:list') .description('List migration status') .action(async () => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.listMigrations(); } catch (error) { console.error(chalk_1.default.red('Failed to list migrations:'), error); process.exit(1); } }); // 型生成コマンド (supabase gen types相当) program .command('generate:types') .description('Generate TypeScript types from database schema') .option('--output <path>', 'Output file path', 'types/database.ts') .option('--watch', 'Watch for schema changes and regenerate', false) .option('--declarations', 'Generate declaration files only', false) .option('--schema <schema>', 'Specific schema to generate types for') .action(async (options) => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.generateTypes(options); } catch (error) { console.error(chalk_1.default.red('Failed to generate types:'), error); process.exit(1); } }); // スキーマプルコマンド (supabase db pull相当) program .command('db:pull') .description('Pull schema from remote database') .option('--force', 'Force overwrite existing schema files', false) .action(async (options) => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.pullSchema(options); } catch (error) { console.error(chalk_1.default.red('Failed to pull schema:'), error); process.exit(1); } }); // スキーマプッシュコマンド (supabase db push相当) program .command('db:push') .description('Push schema changes to database') .option('--drop', 'Drop all tables before applying schema', false) .option('--seed', 'Run seeders after applying schema', false) .option('--force', 'Force push even with warnings', false) .action(async (options) => { try { const dbManager = new database_manager_1.DatabaseManager(); await dbManager.pushSchema(options); } catch (error) { console.error(chalk_1.default.red('Failed to push schema:'), error); process.exit(1); } }); // DNS管理コマンド program .command('dns') .description('DNS management commands') .option('--status', 'Show DNS management status', false) .option('--init', 'Initialize DNS management', false) .option('--cleanup', 'Remove all GFTD DNS entries', false) .option('--restore', 'Restore DNS from backup', false) .option('--add <hostname>', 'Add DNS entry manually') .option('--remove <hostname>', 'Remove DNS entry manually') .option('--ip <ip>', 'IP address for manual DNS entry (default: 127.0.0.1)', '127.0.0.1') .option('--domain <domain>', 'Custom domain (default: gftd.ai.dev)', 'gftd.ai.dev') .action(async (options) => { try { const devManager = new dev_proxy_manager_1.DevProxyManager(); // DNS状態表示 if (options.status) { await devManager.showDnsStatus(); return; } // DNS初期化 if (options.init) { await devManager.initializeDns(); return; } // DNS クリーンアップ if (options.cleanup) { console.log(chalk_1.default.yellow('⚠️ This will remove all GFTD managed DNS entries')); console.log(chalk_1.default.yellow('Are you sure you want to continue? (y/N)')); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('', async (answer) => { if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { await devManager.cleanupDns(); } else { console.log(chalk_1.default.gray('DNS cleanup cancelled')); } rl.close(); }); return; } // DNS バックアップ復元 if (options.restore) { console.log(chalk_1.default.yellow('⚠️ This will restore DNS from backup and overwrite current hosts file')); console.log(chalk_1.default.yellow('Are you sure you want to continue? (y/N)')); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('', async (answer) => { if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { await devManager.restoreDnsBackup(); } else { console.log(chalk_1.default.gray('DNS restore cancelled')); } rl.close(); }); return; } // DNS エントリを手動追加 if (options.add) { const hostname = options.add; const ip = options.ip; console.log(chalk_1.default.blue(`🔧 Adding DNS entry: ${hostname} -> ${ip}`)); const { DnsManager } = require('./core/dns-manager'); const dnsManager = new DnsManager(options.domain); await dnsManager.addEntry(hostname, ip, `# Manually added by GFTD CLI`); return; } // DNS エントリを手動削除 if (options.remove) { const hostname = options.remove; console.log(chalk_1.default.blue(`🔧 Removing DNS entry: ${hostname}`)); const { DnsManager } = require('./core/dns-manager'); const dnsManager = new DnsManager(options.domain); await dnsManager.removeEntry(hostname); return; } // デフォルトはDNS状態表示 await devManager.showDnsStatus(); } catch (error) { console.error(chalk_1.default.red('❌ DNS command failed:'), error.message); process.exit(1); } }); // version情報 program .command('version') .description('Show version information') .action(() => { console.log(chalk_1.default.blue(`GFTD CLI v${packageJson.version}`)); console.log(''); console.log(chalk_1.default.gray('Database Features:')); console.log(chalk_1.default.gray(' ✓ Supabase-like CLI commands')); console.log(chalk_1.default.gray(' ✓ Drizzle-style migrations')); console.log(chalk_1.default.gray(' ✓ TypeScript-first schema')); console.log(chalk_1.default.gray(' ✓ Multi-database support')); console.log(''); console.log(chalk_1.default.gray('System Information:')); console.log(chalk_1.default.gray(` Node.js: ${process.version}`)); console.log(chalk_1.default.gray(` Platform: ${process.platform} ${process.arch}`)); }); // エラーハンドリング program.on('command:*', () => { console.error(chalk_1.default.red('❌ Invalid command: %s'), program.args.join(' ')); console.log(chalk_1.default.yellow('See --help for a list of available commands.')); process.exit(1); }); // ヘルプメッセージのカスタマイズ program.configureHelp({ sortSubcommands: true, subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage() }); // バナー表示 function showBanner() { console.log(chalk_1.default.cyan(` _____ ______ _______ _____ / ____| ____|__ __| __ \\ | | __| |__ | | | | | | | | |_ | __| | | | | | | | |__| | | | | | |__| | \\_____|_| |_| |_____/ Schema Management CLI v${packageJson.version} Drizzle-like management for Kafka & ksqlDB `)); } // メイン実行 async function main() { // 引数がない場合はヘルプを表示 if (process.argv.length <= 2) { showBanner(); program.help(); } // プログラムのパース await program.parseAsync(); } // 未処理のエラー処理 process.on('uncaughtException', (error) => { console.error(chalk_1.default.red('❌ Uncaught Exception:'), error.message); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { console.error(chalk_1.default.red('❌ Unhandled Rejection at:'), promise, 'reason:', reason); process.exit(1); }); // CLIが直接実行された場合のみmainを実行 if (require.main === module) { main().catch((error) => { console.error(chalk_1.default.red('❌ CLI Error:'), error.message); process.exit(1); }); } //# sourceMappingURL=cli.js.map