UNPKG

@proofkit/better-auth

Version:

FileMaker adapter for Better Auth

163 lines (142 loc) 5.95 kB
#!/usr/bin/env node --no-warnings import { Command } from "@commander-js/extra-typings"; import type { Database, FFetchOptions } from "@proofkit/fmodata"; import { FMServerConnection } from "@proofkit/fmodata"; import { logger } from "better-auth"; import { getSchema } from "better-auth/db"; import chalk from "chalk"; import fs from "fs-extra"; import prompts from "prompts"; import { getConfig } from "../better-auth-cli/utils/get-config"; import { executeMigration, planMigration, prettyPrintMigrationPlan } from "../migrate"; import "dotenv/config"; async function main() { const program = new Command(); program .command("migrate", { isDefault: true }) .option("--cwd <path>", "Path to the current working directory", process.cwd()) .option("--config <path>", "Path to the config file") .option("-u, --username <username>", "Full Access Username") .option("-p, --password <password>", "Full Access Password") .option("-y, --yes", "Skip confirmation", false) .action(async (options) => { const cwd = options.cwd; if (!fs.existsSync(cwd)) { logger.error(`The directory "${cwd}" does not exist.`); process.exit(1); } const config = await getConfig({ cwd, configPath: options.config, }); if (!config) { logger.error( "No configuration file found. Add a `auth.ts` file to your project or pass the path to the configuration file using the `--config` flag.", ); return; } // Resolve adapter directly (getAdapter removed in Better Auth 1.5) const databaseFactory = config.database; if (!databaseFactory || typeof databaseFactory !== "function") { logger.error("No database adapter found in auth config."); process.exit(1); } let adapter: { id?: string; database?: unknown }; try { adapter = (databaseFactory as (opts: unknown) => { id?: string; database?: unknown })(config); } catch (e) { logger.error(e instanceof Error ? e.message : String(e)); process.exit(1); } if (adapter?.id !== "filemaker") { logger.error("This generator is only compatible with the FileMaker adapter."); return; } const betterAuthSchema = getSchema(config); // Extract Database from the adapter factory or resolved adapter. // config.database is the FileMakerAdapter factory function (has .database set on it). // adapter is the resolved adapter after getAdapter() calls the factory (also has .database). // Try both: adapter first (post-call), then config.database (pre-call / factory function). const configDb = (adapter as unknown as { database?: Database }).database ?? (config.database as unknown as { database?: Database } | undefined)?.database; if (!configDb || typeof configDb !== "object" || !("schema" in configDb)) { logger.error( "Could not extract Database instance from adapter. Ensure your auth.ts uses FileMakerAdapter with an fmodata Database.", ); process.exit(1); } let db: Database = configDb; // Extract database name and server URL for display. // Try the public getter first (_getDatabaseName), fall back to the private field (databaseName). const dbObj = configDb as unknown as { _getDatabaseName?: string; databaseName?: string; context?: { _getBaseUrl?: () => string; _fetchClientOptions?: unknown }; }; const dbName: string = dbObj._getDatabaseName ?? dbObj.databaseName ?? ""; const baseUrl: string | undefined = dbObj.context?._getBaseUrl?.(); const serverUrl = baseUrl ? new URL(baseUrl).origin : undefined; // If CLI credential overrides are provided, construct a new connection if (options.username && options.password) { if (!dbName) { logger.error("Could not determine database filename from adapter config."); process.exit(1); } if (!baseUrl) { logger.error( "Could not determine server URL from adapter config. Ensure your auth.ts uses FMServerConnection.", ); process.exit(1); } const fetchClientOptions = dbObj.context?._fetchClientOptions as FFetchOptions | undefined; const connection = new FMServerConnection({ serverUrl: serverUrl as string, auth: { username: options.username, password: options.password, }, fetchClientOptions, }); db = connection.database(dbName); } let migrationPlan: Awaited<ReturnType<typeof planMigration>>; try { migrationPlan = await planMigration(db, betterAuthSchema); } catch (err) { logger.error(`Failed to read database schema: ${err instanceof Error ? err.message : err}`); process.exit(1); } if (migrationPlan.length === 0) { logger.info("No changes to apply. Database is up to date."); return; } if (!options.yes) { prettyPrintMigrationPlan(migrationPlan, { serverUrl, fileName: dbName }); if (migrationPlan.length > 0) { console.log(chalk.gray("💡 Tip: You can use the --yes flag to skip this confirmation.")); } const { confirm } = await prompts({ type: "confirm", name: "confirm", message: "Apply the above changes to your database?", }); if (!confirm) { logger.error("Schema changes not applied."); return; } } try { await executeMigration(db, migrationPlan); logger.info("Migration applied successfully."); } catch { process.exit(1); } }); await program.parseAsync(process.argv); process.exit(0); } main().catch((err) => { logger.error(err.message ?? err); process.exit(1); });