#!/usr/bin/env node /** * cli:efcore-apply — Apply EF Core migrations to the database, GATED by the 3-tier * migration policy (migration-policy.ts). * * 🟢 local dev DB → applies pending migrations autonomously * 🟡 down-migration (local) → needs "confirm": true * 🔴 remote/unknown DB → refused ({ blocked: true }); a human deploys * * Usage: * npx --prefer-offline tsx skills/efcore/cli/apply/index.ts --spec '{ * "cwd": "D:/path/to/worktree" * }' * # revert (down-migration), requires confirm: * ... --spec '{"cwd":"...","targetMigration":"","confirm":true}' */ import { parseArgs } from 'node:util'; import { existsSync, readFileSync } from 'node:fs'; import { validate } from './validate.js'; import { execute } from './execute.js'; async function main(): Promise { const { values } = parseArgs({ options: { spec: { type: 'string' }, help: { type: 'boolean', short: 'h' }, }, }); if (values.help || !values.spec) { console.log(`Usage: npx --prefer-offline tsx skills/efcore/cli/apply/index.ts --spec '{"cwd":"..."}'`); process.exit(values.help ? 0 : 1); } let raw: unknown; try { raw = existsSync(values.spec!) ? JSON.parse(readFileSync(values.spec!, 'utf-8')) : JSON.parse(values.spec!); } catch (err) { console.log(JSON.stringify({ success: false, errors: [`Invalid JSON: ${(err as Error).message}`], warnings: [] }, null, 2)); process.exit(1); } const validation = validate(raw); if (!validation.valid || !validation.data) { console.log(JSON.stringify({ success: false, errors: validation.blockers, warnings: validation.warnings }, null, 2)); process.exit(1); } const result = await execute(validation.data); console.log(JSON.stringify(result, null, 2)); process.exit(result.success ? 0 : 1); } void main();