import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { passkey } from "@better-auth/passkey"; import { getSchema } from "better-auth/db"; import { admin, emailOTP, haveIBeenPwned, lastLoginMethod, multiSession, openAPI, twoFactor, } from "better-auth/plugins"; const outFile = fileURLToPath( new URL("../migrations/0001_better_auth_account_center.sql", import.meta.url), ); const schema = getSchema({ emailAndPassword: { enabled: true, }, plugins: [ openAPI(), haveIBeenPwned(), emailOTP({ sendVerificationOTP: async () => {}, }), twoFactor(), multiSession({ maximumSessions: 10 }), lastLoginMethod({ storeInDatabase: true }), admin(), passkey({ rpID: "localhost", rpName: "cfw-auth", origin: "http://localhost:8788", }), ], }); function quoteIdent(value) { return `"${value.replaceAll('"', '""')}"`; } function sqliteType(field) { if (field.type === "boolean" || field.type === "number") { return "integer"; } if (field.type === "date") { return "date"; } return "text"; } function columnSql(name, field) { const parts = [quoteIdent(name), sqliteType(field)]; if (field.required !== false) { parts.push("NOT NULL"); } if (field.unique) { parts.push("UNIQUE"); } if (field.references) { const onDelete = field.references.onDelete ?? "cascade"; parts.push( `REFERENCES ${quoteIdent(field.references.model)}(${quoteIdent(field.references.field)}) ON DELETE ${onDelete}`, ); } return parts.join(" "); } function sortTables(entries) { return entries.sort((left, right) => { const leftOrder = Number.isFinite(left[1].order) ? left[1].order : Number.MAX_SAFE_INTEGER; const rightOrder = Number.isFinite(right[1].order) ? right[1].order : Number.MAX_SAFE_INTEGER; if (leftOrder !== rightOrder) { return leftOrder - rightOrder; } return left[0].localeCompare(right[0]); }); } function buildSql() { const chunks = [ "-- Generated from Better Auth schema. Do not edit by hand.", "-- Run `pnpm db:generate` after changing auth plugins that affect persistence.", "PRAGMA foreign_keys = ON;", ]; const indexes = []; for (const [tableName, table] of sortTables(Object.entries(schema))) { const columns = [`${quoteIdent("id")} text NOT NULL PRIMARY KEY`]; for (const [fieldName, field] of Object.entries(table.fields)) { columns.push(columnSql(fieldName, field)); if (field.index) { const suffix = field.unique ? "uidx" : "idx"; const indexName = `${tableName}_${fieldName}_${suffix}`; const indexSql = field.unique ? "CREATE UNIQUE INDEX" : "CREATE INDEX"; indexes.push( `${indexSql} IF NOT EXISTS ${quoteIdent(indexName)} ON ${quoteIdent(tableName)} (${quoteIdent(fieldName)});`, ); } } chunks.push( `CREATE TABLE IF NOT EXISTS ${quoteIdent(tableName)} (\n ${columns.join(",\n ")}\n);`, ); } chunks.push(...indexes); return `${chunks.join("\n\n")}\n`; } const sql = buildSql(); const checkOnly = process.argv.includes("--check"); if (checkOnly) { const current = await readFile(outFile, "utf8"); if (current !== sql) { throw new Error(`${outFile} is out of date. Run pnpm db:generate.`); } } else { await mkdir(dirname(outFile), { recursive: true }); await writeFile(outFile, sql, "utf8"); console.log(`Wrote ${outFile}`); }