#!/usr/bin/env node import { Command } from 'commander' import chalk from 'chalk' import { createClient } from './auth.js' import { apply } from './commands/apply.js' import { pull } from './commands/pull.js' import { init } from './commands/init.js' import { diff } from './commands/diff.js' import { saveState } from './state.js' const program = new Command() program .name('metabase-iac') .description('Infrastructure as Code for Metabase') .version('0.1.0') program .command('init [directory]') .description('Scaffold a new metabase-iac project') .action(async (directory) => { try { await init(directory) } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program .command('pull') .description('Import all collections, questions, dashboards from Metabase into local files') .option('-o, --output ', 'Output directory', '.') .option('--import-base ', 'Import path for types in generated files', 'metabase-iac/types') .action(async (opts) => { try { const client = await createClient() console.log(chalk.bold('Pulling from Metabase...\n')) const state = await pull(client, opts.output, opts.importBase) await saveState(state) console.log(chalk.green(`\nPull complete! ${state.length} resource(s) saved to ${opts.output}/`)) } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program .command('diff') .description('Compare local resources against the current Metabase state') .option('-d, --dir ', 'Resources directory', '.') .action(async (opts) => { try { const client = await createClient() await diff(client, opts.dir) } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program .command('plan') .description('Show what changes would be applied (dry run)') .option('-d, --dir ', 'Resources directory', '.') .action(async (opts) => { try { const client = await createClient() await apply(client, true, opts.dir) } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program .command('apply') .description('Apply resource definitions to Metabase') .option('-d, --dir ', 'Resources directory', '.') .action(async (opts) => { try { const client = await createClient() await apply(client, false, opts.dir) } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program .command('databases') .description('List available databases in Metabase') .action(async () => { try { const client = await createClient() const dbs = await client.getDatabases() console.log(chalk.bold('Databases:\n')) for (const db of dbs) { console.log(` ${chalk.cyan(db.id.toString().padStart(3))} ${db.name} (${db.engine})`) } } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program .command('collections') .description('List available collections in Metabase') .action(async () => { try { const client = await createClient() const collections = await client.getCollections() console.log(chalk.bold('Collections:\n')) for (const c of collections) { if (c.archived) continue const parent = c.parent_id ? ` (parent: ${c.parent_id})` : '' console.log(` ${chalk.cyan(c.id.toString().padStart(3))} ${c.name}${chalk.gray(parent)}`) } } catch (err: any) { console.error(chalk.red(`Error: ${err.message}`)) process.exit(1) } }) program.parse()