import { mkdir, writeFile } from 'node:fs/promises' import { existsSync } from 'node:fs' import { join, resolve, basename } from 'node:path' import chalk from 'chalk' import { createInterface } from 'node:readline' function prompt(rl: ReturnType, question: string, defaultValue?: string): Promise { const suffix = defaultValue ? ` (${defaultValue})` : '' return new Promise((resolve) => { rl.question(`${question}${suffix}: `, (answer) => { resolve(answer.trim() || defaultValue || '') }) }) } function promptChoice(rl: ReturnType, question: string, choices: string[]): Promise { return new Promise((resolve) => { console.log(`\n${question}`) choices.forEach((c, i) => console.log(` ${chalk.cyan(String(i + 1))}. ${c}`)) rl.question(`Choice (1-${choices.length}): `, (answer) => { const idx = parseInt(answer.trim(), 10) - 1 resolve(choices[idx] ?? choices[0]) }) }) } export async function init(targetDir?: string) { const rl = createInterface({ input: process.stdin, output: process.stdout }) try { console.log(chalk.bold('\nšŸ— metabase-iac init\n')) console.log('This will scaffold a new project to manage your Metabase instance as code.\n') // Project name / directory const dirName = targetDir || await prompt(rl, 'Project directory name', 'my-metabase') const projectDir = resolve(dirName) const projectName = basename(projectDir) if (existsSync(projectDir) && existsSync(join(projectDir, 'package.json'))) { console.log(chalk.yellow(`\n${projectDir} already has a package.json. Aborting.`)) return } // Metabase URL const metabaseUrl = await prompt(rl, 'Metabase URL', 'https://metabase.example.com') // Auth method const authMethod = await promptChoice(rl, 'How do you want to authenticate?', [ 'Username + Password', 'Session Token', 'API Key', ]) let envLines = `METABASE_URL=${metabaseUrl}\n` let envExampleLines = `METABASE_URL=${metabaseUrl}\n` if (authMethod === 'Username + Password') { const username = await prompt(rl, 'Metabase admin email') envLines += `METABASE_USERNAME=${username}\nMETABASE_PASSWORD=\n` envExampleLines += `METABASE_USERNAME=${username}\nMETABASE_PASSWORD=your-password\n` } else if (authMethod === 'Session Token') { envLines += `METABASE_SESSION_TOKEN=\n` envExampleLines += `METABASE_SESSION_TOKEN=your-session-token\n` } else { envLines += `METABASE_API_KEY=\n` envExampleLines += `METABASE_API_KEY=mb_xxxx\n` } // Package manager const pkgManager = await promptChoice(rl, 'Package manager?', ['pnpm', 'npm', 'yarn']) // Pull now? const pullNow = await prompt(rl, 'Pull resources from Metabase after setup? (y/n)', 'y') console.log(chalk.bold('\nCreating project...\n')) // Create directories await mkdir(join(projectDir, 'collections'), { recursive: true }) await mkdir(join(projectDir, 'questions'), { recursive: true }) await mkdir(join(projectDir, 'dashboards'), { recursive: true }) // package.json const packageJson = { name: projectName, version: '0.1.0', private: true, description: `Metabase IaC resources for ${metabaseUrl}`, type: 'module', scripts: { pull: 'metabase-iac pull', diff: 'metabase-iac diff', plan: 'metabase-iac plan', apply: 'metabase-iac apply', databases: 'metabase-iac databases', collections: 'metabase-iac collections', }, dependencies: { 'metabase-iac': '^0.1.0', }, devDependencies: { typescript: '^5.9.0', }, } await writeFile(join(projectDir, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n') console.log(` ${chalk.green('+')} package.json`) // tsconfig.json const tsconfig = { compilerOptions: { target: 'ES2022', module: 'ES2022', moduleResolution: 'bundler', esModuleInterop: true, strict: true, skipLibCheck: true, noEmit: true, }, include: ['collections/**/*', 'questions/**/*', 'dashboards/**/*'], } await writeFile(join(projectDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2) + '\n') console.log(` ${chalk.green('+')} tsconfig.json`) // .env await writeFile(join(projectDir, '.env'), envLines) console.log(` ${chalk.green('+')} .env`) // .env.example await writeFile(join(projectDir, '.env.example'), envExampleLines) console.log(` ${chalk.green('+')} .env.example`) // .gitignore await writeFile( join(projectDir, '.gitignore'), 'node_modules/\n.env\n.metabase-state.json\n', ) console.log(` ${chalk.green('+')} .gitignore`) // README.md const readme = `# ${projectName} Metabase IaC resources for [${metabaseUrl}](${metabaseUrl}). Managed with [metabase-iac](https://www.npmjs.com/package/metabase-iac). ## Setup \`\`\`bash ${pkgManager} install cp .env.example .env # Edit .env with your credentials \`\`\` ## Commands \`\`\`bash ${pkgManager} run pull # Import from Metabase ${pkgManager} run plan # Preview changes (dry run) ${pkgManager} run apply # Push changes to Metabase ${pkgManager} run databases # List databases ${pkgManager} run collections # List collections \`\`\` ## Structure \`\`\` collections/ # Metabase collections questions/ # Saved questions (SQL, MongoDB, structured) dashboards/ # Dashboards with card layouts \`\`\` ` await writeFile(join(projectDir, 'README.md'), readme) console.log(` ${chalk.green('+')} README.md`) // Git init console.log(`\n${chalk.bold('Initializing git repository...')}`) const { execSync } = await import('node:child_process') execSync('git init', { cwd: projectDir, stdio: 'inherit' }) console.log(` ${chalk.green('+')} .git`) // Install dependencies console.log(`\n${chalk.bold('Installing dependencies...')}\n`) execSync(`${pkgManager} install`, { cwd: projectDir, stdio: 'inherit' }) console.log(chalk.green('\nProject created!\n')) // Pull if (pullNow.toLowerCase() === 'y') { console.log(chalk.bold('Pulling resources from Metabase...\n')) try { execSync('npx metabase-iac pull', { cwd: projectDir, stdio: 'inherit' }) } catch { console.log(chalk.yellow('\nPull failed — check your .env credentials and try again with:')) console.log(chalk.cyan(` cd ${dirName} && ${pkgManager} run pull\n`)) } } else { console.log(chalk.bold('Next steps:\n')) console.log(` ${chalk.cyan('cd')} ${dirName}`) console.log(` ${chalk.cyan('# Edit .env with your credentials')}`) console.log(` ${chalk.cyan(`${pkgManager} run pull`)}\n`) } } finally { rl.close() } }