@gftdcojp/gftd-cli
Version:
GFTD Schema Management CLI - Drizzle-like schema management for Kafka and ksqlDB
590 lines (589 loc) • 23.8 kB
JavaScript
;
/**
* 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