#!/usr/bin/env bun /** * validate-schema.ts — Apply canonical.sql to an in-memory SQLite database * and extract structural metadata for CI comparison. * * Usage: bun tools/validate-schema.ts * Output: JSON to stdout with tables, columns, indexes, and schema version. */ import { Database } from "bun:sqlite"; import { readFileSync } from "fs"; import { join } from "path"; // ── Load canonical DDL ──────────────────────────────────────────────────────── const schemaPath = join(import.meta.dir, "..", "schema", "canonical.sql"); let ddl: string; try { ddl = readFileSync(schemaPath, "utf-8"); } catch (err) { console.error(`Failed to read ${schemaPath}: ${err}`); process.exit(1); } // ── Apply to in-memory DB ───────────────────────────────────────────────────── const db = new Database(":memory:"); try { db.exec(ddl); } catch (err) { console.error(`Schema application failed: ${err}`); process.exit(1); } // ── Extract metadata ────────────────────────────────────────────────────────── interface ColumnInfo { cid: number; name: string; type: string; notnull: number; dflt_value: string | null; pk: number; } interface TableMeta { name: string; type: string; // 'table' | 'virtual' columns: ColumnInfo[]; indexes: string[]; } // Get all tables and virtual tables from sqlite_master const masterRows = db .prepare( `SELECT name, type FROM sqlite_master WHERE type IN ('table', 'virtual table') AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_content' AND name NOT LIKE '%_data' AND name NOT LIKE '%_idx' AND name NOT LIKE '%_config' AND name NOT LIKE '%_docsize' ORDER BY name`, ) .all() as Array<{ name: string; type: string }>; const tables: TableMeta[] = []; for (const { name, type } of masterRows) { // Get column info (not available for FTS5 virtual tables) let columns: ColumnInfo[] = []; try { columns = db .prepare(`PRAGMA table_info('${name}')`) .all() as ColumnInfo[]; } catch { // FTS5 tables don't support table_info } // Get indexes for this table const indexRows = db .prepare( `SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ? ORDER BY name`, ) .all(name) as Array<{ name: string }>; tables.push({ name, type: type === "table" ? "table" : "virtual", columns, indexes: indexRows.map((r) => r.name), }); } // ── Output ──────────────────────────────────────────────────────────────────── const output = { schemaFile: "schema/canonical.sql", version: 1, tableCount: tables.length, tables, }; console.log(JSON.stringify(output, null, 2)); db.close();