@dbsp/cli 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-T4ODDHZQ.js → chunk-EO3GHEFW.js} +12 -2
- package/dist/chunk-EO3GHEFW.js.map +1 -0
- package/dist/{chunk-VUNVYYOZ.js → chunk-G6NS4BAV.js} +3 -3
- package/dist/{chunk-VUNVYYOZ.js.map → chunk-G6NS4BAV.js.map} +1 -1
- package/dist/generators/schema-codegen.js +1 -1
- package/dist/index.js +342 -239
- package/dist/index.js.map +1 -1
- package/dist/repl/batch.d.ts +3 -2
- package/dist/utils/schema-loader.d.ts +5 -3
- package/dist/utils/schema-loader.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-T4ODDHZQ.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/commands/introspect.ts","../src/commands/migrate.ts","../src/migration-file.ts","../src/commands/push.ts","../src/ddl-executor.ts","../src/commands/repl.ts","../src/commands/verify.ts","../src/verifier.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ARCH-002 Block 3: CLI Scaffold\n *\n * dbsp CLI - Schema-first code generation for db-semantic-planner.\n */\n\nimport { Command, CommanderError } from 'commander';\nimport { generateCommand } from './commands/generate.js';\nimport { introspectCommand } from './commands/introspect.js';\nimport { migrateCommand } from './commands/migrate.js';\nimport { pushCommand } from './commands/push.js';\nimport { replCommand } from './commands/repl.js';\nimport { verifyCommand } from './commands/verify.js';\n\nconst program = new Command();\n\nprogram\n\t.name('dbsp')\n\t.description('Schema-first code generation for db-semantic-planner')\n\t.version('0.0.1');\n\n// Register commands\nprogram.addCommand(generateCommand);\nprogram.addCommand(introspectCommand);\nprogram.addCommand(migrateCommand);\nprogram.addCommand(pushCommand);\nprogram.addCommand(replCommand);\nprogram.addCommand(verifyCommand);\n\n// CC-15: Intercept Commander parse errors so --json commands receive a JSON\n// error object on stdout instead of a plain-text usage message.\nprogram.exitOverride();\n\ntry {\n\tprogram.parse();\n} catch (err) {\n\t// Commander throws CommanderError for --help, --version, and parse errors.\n\t// Exit 0 for informational outputs (help/version); only exit 1 for real errors.\n\tif (err instanceof CommanderError && err.exitCode === 0) {\n\t\tprocess.exit(0);\n\t}\n\tconst message = err instanceof Error ? err.message : 'Command parse error';\n\tif (process.argv.includes('--json')) {\n\t\tconsole.log(JSON.stringify({ status: 'error', error: message }, null, 2));\n\t} else {\n\t\tconsole.error(`❌ ${message}`);\n\t}\n\tprocess.exit(1);\n}\n","/**\n * ARCH-002 Block 3+4+5: Generate Command\n * CLI-DDL: Added DDL generation target\n * ARCH-005: Migrated to schema() API, removed legacy generators\n *\n * dbsp generate <target> - Generate code from schema.\n *\n * Targets:\n * - ddl: Generate SQL DDL (CREATE TABLE statements)\n *\n * Deprecated targets (removed in ARCH-005):\n * - manifest: Removed legacy manifest generator\n * - kysely: Removed legacy Kysely generator\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport const generateCommand = new Command('generate')\n\t.description('Generate code from schema')\n\t.argument('<target>', 'Target to generate: ddl')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option('-o, --out <dir>', 'Output directory (default: ./generated/<target>)')\n\t.option('--output <dir>', 'Output directory (alias for --out)')\n\t.option('--drop', 'Include DROP TABLE IF EXISTS statements (ddl only)')\n\t.option('--schema-name <name>', 'Database schema name (ddl only)')\n\t.option(\n\t\t'--dialect <name>',\n\t\t'Database dialect: postgresql | mysql | sqlite | mssql (default: postgresql)',\n\t)\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming: snake | camel | none (default: based on dialect)',\n\t)\n\t.action(\n\t\tasync (\n\t\t\ttarget: string,\n\t\t\toptions: {\n\t\t\t\tschema?: string;\n\t\t\t\tout?: string;\n\t\t\t\toutput?: string;\n\t\t\t\tdrop?: boolean;\n\t\t\t\tschemaName?: string;\n\t\t\t\tdialect?: string;\n\t\t\t\tcasing?: 'snake' | 'camel' | 'none';\n\t\t\t},\n\t\t) => {\n\t\t\ttry {\n\t\t\t\t// Reject the target BEFORE loading the schema: loading it executes the\n\t\t\t\t// user's schema module, and there is no reason to run their code for a\n\t\t\t\t// target we are going to refuse anyway.\n\t\t\t\tif (target === 'manifest' || target === 'kysely') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Target '${target}' has been removed. ` +\n\t\t\t\t\t\t\t`Use 'ddl' for SQL generation, or the 'introspect' command to generate a schema file.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target !== 'ddl') {\n\t\t\t\t\tthrow new Error(`Unknown target: ${target}. Available targets: ddl`);\n\t\t\t\t}\n\n\t\t\t\t// Load schema (ARCH-005: only schema() format supported)\n\t\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\t\t\t\tlet schemaPath: string;\n\n\t\t\t\tif (options.schema) {\n\t\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\t\tschemaPath = options.schema;\n\t\t\t\t} else {\n\t\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\t\tschema = result.schema;\n\t\t\t\t\tschemaPath = result.path;\n\t\t\t\t}\n\n\t\t\t\t// For DDL without --output, we output to stdout so use stderr for info\n\t\t\t\tconst outputPath = options.out ?? options.output;\n\t\t\t\tconst useStdout = target === 'ddl' && !outputPath;\n\t\t\t\tconst log = useStdout ? console.error : console.log;\n\n\t\t\t\tlog(`📄 Loaded schema from: ${schemaPath}`);\n\n\t\t\t\t// Validate dialect option\n\t\t\t\tconst dialect = options.dialect ?? 'postgresql';\n\t\t\t\tif (dialect !== 'postgresql') {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`⚠️ Warning: Only 'postgresql' dialect is currently supported. Using postgresql.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Generate based on target\n\t\t\t\tswitch (target) {\n\t\t\t\t\tcase 'ddl': {\n\t\t\t\t\t\t// Determine casing: explicit option > dialect default > 'snake'\n\t\t\t\t\t\tconst casing = options.casing ?? 'snake';\n\n\t\t\t\t\t\t// Import adapter from adapter-pgsql (compile-only, no DB connection needed)\n\t\t\t\t\t\tconst { createPgsqlCompileOnlyAdapter } = await import(\n\t\t\t\t\t\t\t'@dbsp/adapter-pgsql'\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst dbCasing =\n\t\t\t\t\t\t\tcasing === 'snake'\n\t\t\t\t\t\t\t\t? ('snake_case' as const)\n\t\t\t\t\t\t\t\t: ('preserve' as const);\n\t\t\t\t\t\tconst adapter = createPgsqlCompileOnlyAdapter({\n\t\t\t\t\t\t\tdbCasing,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// ARCH-005: Use schema.model directly (already ModelIR)\n\t\t\t\t\t\t\tconst ddlStatements = adapter.generateDDL(schema.model, {\n\t\t\t\t\t\t\t\t...(options.drop !== undefined && {\n\t\t\t\t\t\t\t\t\tincludeDropStatements: options.drop,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst ddlContent = ddlStatements.join('\\n\\n');\n\t\t\t\t\t\t\tconst outputPath = options.out ?? options.output;\n\n\t\t\t\t\t\t\tif (outputPath) {\n\t\t\t\t\t\t\t\t// Write to file if --output is specified\n\t\t\t\t\t\t\t\tconst outPath = outputPath.endsWith('.sql')\n\t\t\t\t\t\t\t\t\t? resolve(process.cwd(), outputPath)\n\t\t\t\t\t\t\t\t\t: resolve(process.cwd(), outputPath, 'schema.sql');\n\n\t\t\t\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\t\t\t\twriteFileSync(outPath, ddlContent, 'utf-8');\n\n\t\t\t\t\t\t\t\tconsole.log(`✅ Generated DDL: ${outPath}`);\n\t\t\t\t\t\t\t\tconsole.log(` Tables: ${schema.tableNames.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Statements: ${ddlStatements.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Casing: ${casing}`);\n\t\t\t\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Includes DROP statements`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.schemaName) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Output DDL to stdout (for piping)\n\t\t\t\t\t\t\t\t// Info messages (schema loaded) went to stderr\n\t\t\t\t\t\t\t\tconsole.log(ddlContent);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * CLI-DDL: Introspect Command\n *\n * dbsp introspect - Generate schema.ts from database introspection.\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport {\n\tgenerateSchemaFileWithDiagnostics,\n\ttype SchemaCodegenOptions,\n} from '../generators/schema-codegen.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\n\nfunction uniqueWarnings(warnings: readonly string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst unique: string[] = [];\n\tfor (const warning of warnings) {\n\t\tif (seen.has(warning)) continue;\n\t\tseen.add(warning);\n\t\tunique.push(warning);\n\t}\n\treturn unique;\n}\n\nfunction reportWarnings(warnings: readonly string[]): void {\n\tfor (const warning of warnings) {\n\t\tconsole.log(` ⚠️ ${warning}`);\n\t}\n}\n\nexport const introspectCommand = new Command('introspect')\n\t.description('Generate schema.ts from database introspection')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('-o, --out <file>', 'Output schema file', './dbsp.schema.ts')\n\t.option('--schema-name <name>', 'Database schema name', 'public')\n\t.option(\n\t\t'--exclude <patterns>',\n\t\t'Tables to exclude (comma-separated glob patterns)',\n\t\t'_migrations,_prisma*,pg_*',\n\t)\n\t.option('--include <patterns>', 'Tables to include (comma-separated)')\n\t.option('--no-db-type-comments', 'Omit original DB type comments')\n\t.option(\n\t\t'--db-casing <casing>',\n\t\t'Database column casing (snake_case → camelCase in generated code)',\n\t\t'snake_case',\n\t)\n\t.action(\n\t\tasync (options: {\n\t\t\tdb: string;\n\t\t\tout: string;\n\t\t\tschemaName: string;\n\t\t\texclude: string;\n\t\t\tinclude?: string;\n\t\t\tdbTypeComments: boolean;\n\t\t\tdbCasing: 'snake_case' | 'camelCase' | 'preserve';\n\t\t}) => {\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tconsole.log(`🔍 Introspecting database: ${redactedUrl}`);\n\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\tif (options.exclude) {\n\t\t\t\tconsole.log(` Excluding: ${options.exclude}`);\n\t\t\t}\n\t\t\tconsole.log('');\n\n\t\t\ttry {\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Import introspect from adapter-pgsql\n\t\t\t\t\tconst { introspect } = await import('@dbsp/adapter-pgsql');\n\n\t\t\t\t\t// Build introspection options from CLI flags\n\t\t\t\t\tconst excludePatterns = options.exclude\n\t\t\t\t\t\t? options.exclude.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst includePatterns = options.include\n\t\t\t\t\t\t? options.include.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\t// Introspect the database directly (returns IntrospectedModelIR)\n\t\t\t\t\tconst model = await introspect(pool, {\n\t\t\t\t\t\tschema: options.schemaName,\n\t\t\t\t\t\t...(excludePatterns ? { exclude: excludePatterns } : {}),\n\t\t\t\t\t\t...(includePatterns ? { include: includePatterns } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Report what we found\n\t\t\t\t\tconst tableCount = model.tables.size;\n\t\t\t\t\tconst relationCount = model.relations.size;\n\t\t\t\t\tconst hierarchyCount = model.hierarchies?.length ?? 0;\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`📊 Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Generate schema file — pass metadata from introspection\n\t\t\t\t\tconst codegenOptions: SchemaCodegenOptions = {\n\t\t\t\t\t\tsourceUrl: options.db,\n\t\t\t\t\t\tincludeDbTypeComments: options.dbTypeComments,\n\t\t\t\t\t\tintrospectedAt: model.introspectedAt,\n\t\t\t\t\t\tdbCasing: options.dbCasing,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst generated = generateSchemaFileWithDiagnostics(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tcodegenOptions,\n\t\t\t\t\t);\n\t\t\t\t\tconst warnings = uniqueWarnings(generated.warnings);\n\t\t\t\t\tif (warnings.length > 0) {\n\t\t\t\t\t\treportWarnings(warnings);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\t// Write output file\n\t\t\t\t\tconst outPath = resolve(process.cwd(), options.out);\n\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\twriteFileSync(outPath, generated.code, 'utf-8');\n\n\t\t\t\t\tconsole.log(`✅ Generated schema: ${outPath}`);\n\t\t\t\t\tconsole.log(` Tables: ${tableCount}`);\n\t\t\t\t\tconsole.log(` Relations: ${relationCount}`);\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Migrate Command — Migration Infrastructure\n *\n * dbsp migrate dev - Generate migration from schema diff\n * dbsp migrate apply - Apply pending migrations\n * dbsp migrate rollback - Roll back applied migrations\n * dbsp migrate status - Show migration status\n */\n\nimport {\n\tcompareSchemata,\n\tensureMigrationsTable,\n\tgenerateMigrationFile,\n\tgenerateMigrationSQL,\n\tgetAppliedMigrations,\n\tgetNextSchemaVersion,\n\tintrospect,\n\tisDestructiveDown,\n\tparseMigrationFile,\n\trecordMigration,\n\tremoveMigrationRecord,\n\twithMigrationLock,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport type { Pool } from 'pg';\nimport {\n\tDEFAULT_MIGRATIONS_DIR,\n\tgenerateMigrationFilename,\n\tscanMigrationFiles,\n\twriteMigrationFile,\n} from '../migration-file.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Typed error for migration failures.\n * Distinguishes user-facing errors (validation, logic) from unexpected errors.\n */\nexport class MigrationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'MigrationError';\n\t}\n}\n\n/**\n * Sanitize a pg error for user-facing output.\n *\n * PostgreSQL errors may carry schema/table/row details in their message text.\n * For pg errors (identifiable by SQLSTATE `.code`), we emit a sanitized message\n * keyed only by the SQLSTATE code. The raw message is available via DEBUG=dbsp.\n *\n * @param err - Any caught value\n * @returns An Error instance safe to display to the user\n */\nexport function sanitizePgError(err: unknown): Error {\n\tif (err instanceof Error) {\n\t\t// pg errors carry a SQLSTATE `.code` property\n\t\tconst code = (err as Error & { code?: string }).code;\n\t\tif (typeof code === 'string' && /^[0-9A-Z]{5}$/.test(code)) {\n\t\t\tif (process.env.DEBUG?.includes('dbsp')) {\n\t\t\t\tconsole.error(`[DEBUG] pg error detail: ${err.message}`);\n\t\t\t}\n\t\t\treturn new MigrationError(`Migration failed: database error ${code}`);\n\t\t}\n\t\treturn err;\n\t}\n\treturn new Error(String(err));\n}\n\n/**\n * DRY lifecycle wrapper: create pool → run fn → end pool on success or error.\n * Replaces the repeated try { ... } finally { pool.end() } pattern across commands.\n *\n * process.exit is called OUTSIDE the pool lifecycle so the lock-holding client\n * (from withMigrationLock) is always released before the process terminates.\n */\nexport async function withMigratePool<T>(\n\tdbUrl: string,\n\tfn: (pool: Pool) => Promise<T>,\n): Promise<T> {\n\tconst { pool } = await createDbConnection(dbUrl);\n\ttry {\n\t\treturn await fn(pool);\n\t} finally {\n\t\tlet endError: unknown;\n\t\ttry {\n\t\t\tawait pool.end();\n\t\t} catch (e) {\n\t\t\tendError = e;\n\t\t}\n\t\tif (endError !== undefined) {\n\t\t\t// Cleanup failure is non-fatal — log it but don't mask the original error\n\t\t\tconsole.error(\n\t\t\t\t`Warning: pool.end() failed: ${endError instanceof Error ? endError.message : String(endError)}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Top-level error handler for migrate commands.\n * Ensures process.exit is only called AFTER pool cleanup (via withMigratePool).\n */\nasync function runMigrateAction(fn: () => Promise<void>): Promise<void> {\n\ttry {\n\t\tawait fn();\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`❌ Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error('❌ Unknown error occurred');\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n\n/**\n * Returns true if a migration statement contains at least one executable SQL\n * line (non-empty, not a comment). Statements that consist entirely of blank\n * lines and `--` comment lines are skipped; statements that begin with a\n * comment header but also contain real SQL are kept (PostgreSQL ignores the\n * comment, so the header is harmless and preserves intent).\n */\nexport function hasExecutableSql(stmt: string): boolean {\n\treturn stmt.split('\\n').some((line) => {\n\t\tconst t = line.trim();\n\t\treturn t.length > 0 && !t.startsWith('--');\n\t});\n}\n\n// ============================================================================\n// Subcommands\n// ============================================================================\n\nconst devCommand = new Command('dev')\n\t.description('Generate a migration from schema changes')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('-n, --name <description>', 'Migration description', 'migration')\n\t.option('--allow-destructive', 'Include destructive changes (drops)')\n\t.action(\n\t\t(options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdir: string;\n\t\t\tname: string;\n\t\t\tallowDestructive?: boolean;\n\t\t}) =>\n\t\t\trunMigrateAction(async () => {\n\t\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\t\tconsole.log(`📝 Generating migration: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\tconsole.log('');\n\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t\tconst dbModel = await introspect(pool, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\tconst diff = compareSchemata(schemaModel, dbModel);\n\n\t\t\t\t\tif (diff.changes.length === 0) {\n\t\t\t\t\t\tconsole.log('✅ No changes detected — database matches schema.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for destructive changes\n\t\t\t\t\tif (diff.hasDestructive && !options.allowDestructive) {\n\t\t\t\t\t\tconst destructive = diff.changes.filter((c) => c.destructive);\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`${destructive.length} destructive change(s) detected:\\n` +\n\t\t\t\t\t\t\t\tdestructive.map((c) => ` - ${c.details}`).join('\\n') +\n\t\t\t\t\t\t\t\t'\\n\\nUse --allow-destructive to include these changes.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sqlOptions = {\n\t\t\t\t\t\tincludeDestructive: options.allowDestructive ?? false,\n\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t};\n\n\t\t\t\t\tconst statements = generateMigrationSQL(diff, sqlOptions);\n\n\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t'✅ No migration needed — all changes are non-actionable.',\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Generate migration file with UP + DOWN sections\n\t\t\t\t\tconst existingFiles = scanMigrationFiles(options.dir).map(\n\t\t\t\t\t\t(f) => f.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst filename = generateMigrationFilename(\n\t\t\t\t\t\texistingFiles,\n\t\t\t\t\t\toptions.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst content = generateMigrationFile(diff, {\n\t\t\t\t\t\t...sqlOptions,\n\t\t\t\t\t\tname: filename,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst file = writeMigrationFile(options.dir, filename, content);\n\n\t\t\t\t\tconsole.log(`✅ Migration created: ${file.path}`);\n\t\t\t\t\tconsole.log(` Statements: ${statements.length}`);\n\t\t\t\t\tconsole.log(` Checksum: ${file.checksum.slice(0, 12)}...`);\n\t\t\t\t});\n\t\t\t}),\n\t);\n\nconst applyCommand = new Command('apply')\n\t.description('Apply pending migrations')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--dry-run', 'Show pending migrations without applying')\n\t.action((options: { db: string; dir: string; dryRun?: boolean }) =>\n\t\trunMigrateAction(async () => {\n\t\t\tconsole.log('🔄 Applying migrations');\n\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\tconsole.log('');\n\n\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t// Ensure tracking table exists before acquiring lock\n\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\tawait withMigrationLock(pool, async (client) => {\n\t\t\t\t\t// Get applied migrations on the lock-holding client\n\t\t\t\t\tconst applied = await getAppliedMigrations(client as unknown as Pool);\n\t\t\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m.checksum]));\n\n\t\t\t\t\t// Scan migration files\n\t\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\n\t\t\t\t\tif (files.length === 0) {\n\t\t\t\t\t\tconsole.log(`No migration files found in ${options.dir}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate checksums for already-applied migrations\n\t\t\t\t\tfor (const file of files) {\n\t\t\t\t\t\tconst existingChecksum = appliedMap.get(file.name);\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\texistingChecksum !== undefined &&\n\t\t\t\t\t\t\texistingChecksum !== file.checksum\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t`Checksum mismatch for ${file.name}\\n` +\n\t\t\t\t\t\t\t\t\t` Expected: ${existingChecksum}\\n` +\n\t\t\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t\t\t'\\nMigration file has been tampered with after being applied.',\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Find pending migrations\n\t\t\t\t\tconst pending = files.filter((f) => !appliedMap.has(f.name));\n\n\t\t\t\t\tif (pending.length === 0) {\n\t\t\t\t\t\tconsole.log('✅ All migrations already applied.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (options.dryRun) {\n\t\t\t\t\t\tconsole.log(`${pending.length} pending migration(s):`);\n\t\t\t\t\t\tfor (const file of pending) {\n\t\t\t\t\t\t\tconsole.log(` - ${file.name}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply each pending migration atomically (DDL + record in one transaction)\n\t\t\t\t\tlet appliedCount = 0;\n\t\t\t\t\tfor (const file of pending) {\n\t\t\t\t\t\tconsole.log(` Applying: ${file.name}...`);\n\n\t\t\t\t\t\t// Parse UP section from file\n\t\t\t\t\t\tconst parsed = parseMigrationFile(file.content);\n\t\t\t\t\t\tconst statements = parsed.upStatements.filter(hasExecutableSql);\n\n\t\t\t\t\t\t// Determine version and destructive flag before the transaction\n\t\t\t\t\t\t// (read from the lock-held client so we see a consistent view)\n\t\t\t\t\t\tconst version = await getNextSchemaVersion(\n\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst destructive = parsed.destructive === true;\n\n\t\t\t\t\t\t// Atomic: DDL + record in ONE transaction on the lock-holding client\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\t\t\tfor (const stmt of statements) {\n\t\t\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tawait recordMigration(\n\t\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t\t\tfile.name,\n\t\t\t\t\t\t\t\tfile.checksum,\n\t\t\t\t\t\t\t\tversion,\n\t\t\t\t\t\t\t\tdestructive,\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t\t\t} catch (applyError) {\n\t\t\t\t\t\t\tlet rollbackError: unknown;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\trollbackError = e;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst primary = sanitizePgError(applyError);\n\t\t\t\t\t\t\tif (rollbackError !== undefined) {\n\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackError).message}`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthrow primary;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tappliedCount++;\n\t\t\t\t\t\tconsole.log(` ✅ Applied: ${file.name}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`\\n✅ ${appliedCount} migration(s) applied successfully.`,\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t});\n\t\t}),\n\t);\n\nconst rollbackCommand = new Command('rollback')\n\t.description('Roll back applied migrations')\n\t.argument('<count>', 'Number of migrations to roll back')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--force', 'Force rollback of destructive or empty DOWN migrations')\n\t.action(\n\t\t(\n\t\t\tcountArg: string,\n\t\t\toptions: { db: string; dir: string; force?: boolean },\n\t\t) => {\n\t\t\tconst count = Number.parseInt(countArg, 10);\n\t\t\tif (Number.isNaN(count) || count < 1) {\n\t\t\t\tconsole.error('❌ Count must be a positive integer');\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\treturn runMigrateAction(async () => {\n\t\t\t\tconsole.log(`⏪ Rolling back ${count} migration(s)`);\n\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\t\tconsole.log('');\n\n\t\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\t\tawait withMigrationLock(pool, async (client) => {\n\t\t\t\t\t\t// Get applied migrations on the lock-holding client\n\t\t\t\t\t\tconst applied = await getAppliedMigrations(\n\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst sortedDesc = [...applied].sort((a, b) =>\n\t\t\t\t\t\t\tb.name.localeCompare(a.name),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// SC-18: Validate count\n\t\t\t\t\t\tif (count > sortedDesc.length) {\n\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t`Cannot roll back ${count} migration(s) — only ${sortedDesc.length} applied`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst toRollback = sortedDesc.slice(0, count);\n\n\t\t\t\t\t\t// Load migration files from disk\n\t\t\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\t\t\t\t\t\tconst fileMap = new Map(files.map((f) => [f.name, f]));\n\n\t\t\t\t\t\tlet rolledBack = 0;\n\t\t\t\t\t\tfor (const record of toRollback) {\n\t\t\t\t\t\t\tconst file = fileMap.get(record.name);\n\t\t\t\t\t\t\tif (!file) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration file not found on disk: ${record.name}`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// SC-17: Verify checksum\n\t\t\t\t\t\t\tif (file.checksum !== record.checksum) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Checksum mismatch for ${record.name}\\n` +\n\t\t\t\t\t\t\t\t\t\t` Expected: ${record.checksum}\\n` +\n\t\t\t\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t\t\t\t'\\nMigration file has been modified since it was applied.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Parse DOWN section\n\t\t\t\t\t\t\tconst parsed = parseMigrationFile(file.content);\n\n\t\t\t\t\t\t\t// SC-10/ERR-01: No DOWN section\n\t\t\t\t\t\t\tif (!parsed.hasDown) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration ${record.name} has no DOWN section\\n` +\n\t\t\t\t\t\t\t\t\t\t' Cannot roll back a migration without a DOWN section.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// SC-11/ERR-04: Empty DOWN section\n\t\t\t\t\t\t\tconst downStmts = parsed.downStatements.filter(hasExecutableSql);\n\t\t\t\t\t\t\tif (downStmts.length === 0 && !options.force) {\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration ${record.name} has an empty DOWN section\\n` +\n\t\t\t\t\t\t\t\t\t\t' Use --force to roll back anyway.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// SC-19: Metadata-driven destructive rollback guard\n\t\t\t\t\t\t\tconst safeHeaderContradictedByDown =\n\t\t\t\t\t\t\t\tparsed.destructive === false && isDestructiveDown(downStmts);\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t(parsed.destructive !== false ||\n\t\t\t\t\t\t\t\t\tsafeHeaderContradictedByDown) &&\n\t\t\t\t\t\t\t\t!options.force\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tif (parsed.destructive === true) {\n\t\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t\t`Migration ${record.name} has destructive DOWN operations\\n` +\n\t\t\t\t\t\t\t\t\t\t\t' Use --force to proceed with destructive rollback.',\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (safeHeaderContradictedByDown) {\n\t\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t\t`Migration ${record.name} is marked non-destructive but its DOWN section contains an obvious destructive statement\\n` +\n\t\t\t\t\t\t\t\t\t\t\t' Re-generate the migration with accurate dbsp metadata or pass --force.',\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t\t\t`Migration ${record.name} is unmarked or legacy\\n` +\n\t\t\t\t\t\t\t\t\t\t' Re-generate the migration with dbsp metadata or pass --force.',\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Atomic: DOWN SQL + remove record in ONE transaction on the lock-holding client\n\t\t\t\t\t\t\tif (downStmts.length > 0 || options.force) {\n\t\t\t\t\t\t\t\tconsole.log(` Rolling back: ${record.name}...`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\t\t\t\tfor (const stmt of downStmts) {\n\t\t\t\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tawait removeMigrationRecord(\n\t\t\t\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\t\t\t\trecord.name,\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\t\t\t\tlet rollbackCleanupError: unknown;\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\trollbackCleanupError = e;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst primary = sanitizePgError(rollbackError);\n\t\t\t\t\t\t\t\tif (rollbackCleanupError !== undefined) {\n\t\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackCleanupError).message}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthrow primary;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\trolledBack++;\n\t\t\t\t\t\t\tconsole.log(` ✅ Rolled back: ${record.name}`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`\\n✅ ${rolledBack} migration(s) rolled back successfully.`,\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t},\n\t);\n\nconst statusCommand = new Command('status')\n\t.description('Show migration status')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--json', 'Output as JSON')\n\t.action((options: { db: string; dir: string; json?: boolean }) =>\n\t\trunMigrateAction(async () => {\n\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\tconst applied = await getAppliedMigrations(pool);\n\t\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m]));\n\n\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\n\t\t\t\tconst statuses: Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tstatus: 'pending' | 'applied' | 'checksum_mismatch' | 'missing_file';\n\t\t\t\t\tappliedAt?: Date;\n\t\t\t\t}> = files.map((file) => {\n\t\t\t\t\tconst record = appliedMap.get(file.name);\n\t\t\t\t\tif (record === undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'pending' as const,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tif (record.checksum !== file.checksum) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'checksum_mismatch' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\tstatus: 'applied' as const,\n\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Also show applied migrations that don't have files anymore\n\t\t\t\tfor (const record of applied) {\n\t\t\t\t\tif (!files.some((f) => f.name === record.name)) {\n\t\t\t\t\t\tstatuses.push({\n\t\t\t\t\t\t\tname: record.name,\n\t\t\t\t\t\t\tstatus: 'missing_file' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(JSON.stringify(statuses, null, 2));\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log('Migration Status');\n\t\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\tif (statuses.length === 0) {\n\t\t\t\t\t\tconsole.log('No migrations found.');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const s of statuses) {\n\t\t\t\t\t\t\tconst icon =\n\t\t\t\t\t\t\t\ts.status === 'applied'\n\t\t\t\t\t\t\t\t\t? '✅'\n\t\t\t\t\t\t\t\t\t: s.status === 'pending'\n\t\t\t\t\t\t\t\t\t\t? '⏳'\n\t\t\t\t\t\t\t\t\t\t: s.status === 'checksum_mismatch'\n\t\t\t\t\t\t\t\t\t\t\t? '⚠️'\n\t\t\t\t\t\t\t\t\t\t\t: '❓';\n\t\t\t\t\t\t\tconst detail =\n\t\t\t\t\t\t\t\t'appliedAt' in s && s.appliedAt\n\t\t\t\t\t\t\t\t\t? ` (applied: ${s.appliedAt.toISOString()})`\n\t\t\t\t\t\t\t\t\t: '';\n\t\t\t\t\t\t\tconsole.log(` ${icon} ${s.name} — ${s.status}${detail}`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst pendingCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'pending',\n\t\t\t\t\t\t).length;\n\t\t\t\t\t\tconst appliedCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'applied',\n\t\t\t\t\t\t).length;\n\n\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`Total: ${statuses.length} | Applied: ${appliedCount} | Pending: ${pendingCount}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}),\n\t);\n\n// ============================================================================\n// Main Command\n// ============================================================================\n\nexport const migrateCommand = new Command('migrate')\n\t.description('Database migration management')\n\t.addCommand(devCommand)\n\t.addCommand(applyCommand)\n\t.addCommand(rollbackCommand)\n\t.addCommand(statusCommand);\n","/**\n * Migration File — File I/O, naming conventions, checksums.\n *\n * Handles reading/writing migration SQL files and computing\n * SHA-256 checksums for tamper detection.\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MigrationFile {\n\t/** Filename (e.g., \"0001_create_users.sql\") */\n\treadonly name: string;\n\t/** Full path to the file */\n\treadonly path: string;\n\t/** SQL content */\n\treadonly content: string;\n\t/** SHA-256 checksum of content */\n\treadonly checksum: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default directory for migration files */\nexport const DEFAULT_MIGRATIONS_DIR = 'migrations';\n\n/** Migration file naming pattern: NNNN_description.sql */\nconst MIGRATION_FILENAME_PATTERN = /^\\d{4}_[\\w-]+\\.sql$/;\n\n// ============================================================================\n// Checksum\n// ============================================================================\n\n/**\n * Compute SHA-256 checksum of content.\n */\nexport function computeChecksum(content: string): string {\n\treturn createHash('sha256').update(content, 'utf-8').digest('hex');\n}\n\n// ============================================================================\n// File Naming\n// ============================================================================\n\n/**\n * Generate the next migration filename.\n *\n * @param existingFiles - List of existing migration filenames\n * @param description - Human-readable description (e.g., \"create_users\")\n * @returns Filename (e.g., \"0001_create_users.sql\")\n */\nexport function generateMigrationFilename(\n\texistingFiles: readonly string[],\n\tdescription: string,\n): string {\n\tconst maxNum = existingFiles.reduce((max, f) => {\n\t\tconst match = f.match(/^(\\d{4})/);\n\t\treturn match?.[1] ? Math.max(max, Number.parseInt(match[1], 10)) : max;\n\t}, 0);\n\n\tconst nextNum = String(maxNum + 1).padStart(4, '0');\n\tconst sanitized = description\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9_-]/g, '_')\n\t\t.replace(/_+/g, '_')\n\t\t.replace(/^_|_$/g, '');\n\n\treturn `${nextNum}_${sanitized || 'migration'}.sql`;\n}\n\n// ============================================================================\n// File I/O\n// ============================================================================\n\n/**\n * Ensure the migrations directory exists.\n */\nexport function ensureMigrationsDir(dir: string): string {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\tmkdirSync(fullPath, { recursive: true });\n\t}\n\treturn fullPath;\n}\n\n/**\n * Scan migrations directory and return all valid migration files,\n * sorted by name (lexicographic = chronological with zero-padded numbers).\n */\nexport function scanMigrationFiles(dir: string): readonly MigrationFile[] {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\treturn [];\n\t}\n\n\tconst entries = readdirSync(fullPath)\n\t\t.filter((f) => MIGRATION_FILENAME_PATTERN.test(f))\n\t\t.sort();\n\n\treturn entries.map((name) => {\n\t\tconst filePath = join(fullPath, name);\n\t\tconst content = readFileSync(filePath, 'utf-8');\n\t\treturn {\n\t\t\tname,\n\t\t\tpath: filePath,\n\t\t\tcontent,\n\t\t\tchecksum: computeChecksum(content),\n\t\t};\n\t});\n}\n\n/**\n * Write a migration file to the migrations directory.\n *\n * @returns The created MigrationFile\n */\nexport function writeMigrationFile(\n\tdir: string,\n\tfilename: string,\n\tcontent: string,\n): MigrationFile {\n\tconst fullDir = ensureMigrationsDir(dir);\n\tconst filePath = join(fullDir, filename);\n\twriteFileSync(filePath, content, 'utf-8');\n\n\treturn {\n\t\tname: filename,\n\t\tpath: filePath,\n\t\tcontent,\n\t\tchecksum: computeChecksum(content),\n\t};\n}\n","/**\n * Push Command — Schema Provisioning\n *\n * dbsp push - Push schema to database.\n * Additive by default: only creates missing objects.\n * With --drop: recreates from scratch (preserves _dbsp_migrations).\n */\n\nimport {\n\tcompareSchemata,\n\tgenerateDDL,\n\tgenerateMigrationSQL,\n\tintrospect,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { executeDdl } from '../ddl-executor.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n/** Table name reserved for migration history — never dropped by push. */\nconst MIGRATIONS_TABLE = '_dbsp_migrations';\n\nexport const pushCommand = new Command('push')\n\t.description('Push schema to database (additive by default)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--drop', 'Drop and recreate all objects (preserves migrations)')\n\t.option('--dry-run', 'Print SQL without executing')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdrop?: boolean;\n\t\t\tdryRun?: boolean;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`🚀 Pushing schema: ${schemaPath}${options.drop ? ' (with --drop)' : ''}`,\n\t\t\t\t);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tif (options.dryRun) {\n\t\t\t\t\tconsole.log(` Mode: DRY RUN (no changes will be applied)`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t// --drop mode: generate full DDL with drops, excluding _dbsp_migrations\n\t\t\t\t\t\tconst statements = generateDDL(schemaModel, {\n\t\t\t\t\t\t\tincludeDropStatements: true,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// SEC-7: Escape MIGRATIONS_TABLE before interpolating into RegExp\n\t\t\t\t\t\tconst escapedTable = MIGRATIONS_TABLE.replace(\n\t\t\t\t\t\t\t/[.*+?^${}()|[\\]\\\\]/g,\n\t\t\t\t\t\t\t'\\\\$&',\n\t\t\t\t\t\t);\n\t\t\t\t\t\t// CC-11: Token-based check — match DROP TABLE ... \"tableName\" (no greedy .*\n\t\t\t\t\t\t// across statement boundaries). The pattern anchors on the quoted table name\n\t\t\t\t\t\t// appearing anywhere in the statement, which is safe for single-statement\n\t\t\t\t\t\t// inputs (generateDDL returns one statement per array entry).\n\t\t\t\t\t\tconst migrationsPattern = new RegExp(\n\t\t\t\t\t\t\t`DROP\\\\s+TABLE(?:\\\\s+IF\\\\s+EXISTS)?(?:\\\\s+\"[^\"]*\"\\\\s*\\\\.)?\\\\s*\"${escapedTable}\"`,\n\t\t\t\t\t\t\t'i',\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst filtered = statements.filter(\n\t\t\t\t\t\t\t(stmt) => !migrationsPattern.test(stmt),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\toutputResult(filtered, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, filtered, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// CC-1: --drop --json must emit JSON to stdout on success\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconst droppedTables = statements\n\t\t\t\t\t\t\t\t.filter((s) => /DROP\\s+TABLE/i.test(s))\n\t\t\t\t\t\t\t\t.filter((s) => !migrationsPattern.test(s))\n\t\t\t\t\t\t\t\t.map((s) => {\n\t\t\t\t\t\t\t\t\t// M6: handle CASCADE between last quoted identifier and semicolon\n\t\t\t\t\t\t\t\t\t// e.g. DROP TABLE IF EXISTS \"public\".\"users\" CASCADE;\n\t\t\t\t\t\t\t\t\tconst m = s.match(/\"([^\"]+)\"\\s*(?:CASCADE\\s*)?;?\\s*$/i);\n\t\t\t\t\t\t\t\t\treturn m ? m[1] : s;\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.filter((t): t is string => t !== undefined);\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'dropped',\n\t\t\t\t\t\t\t\t\t\ttables: droppedTables,\n\t\t\t\t\t\t\t\t\t\ttablesDropped: droppedTables.length,\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statements executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Additive mode: introspect → diff → generate migration SQL (additive only)\n\t\t\t\t\t\tconst dbModel = await introspect(pool, {\n\t\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst diff = compareSchemata(schemaModel, dbModel);\n\n\t\t\t\t\t\t// Generate SQL for additive changes only (no destructive)\n\t\t\t\t\t\tconst statements = generateMigrationSQL(diff, {\n\t\t\t\t\t\t\tincludeDestructive: false,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Collect warnings for skipped non-additive changes\n\t\t\t\t\t\tconst skippedChanges = diff.changes.filter((c) => c.destructive);\n\n\t\t\t\t\t\tif (!options.json && skippedChanges.length > 0) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`⚠️ ${skippedChanges.length} non-additive change(s) skipped:`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tfor (const change of skippedChanges) {\n\t\t\t\t\t\t\t\tconsole.log(` - ${change.details}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tstatus: 'up-to-date',\n\t\t\t\t\t\t\t\t\t\t\tstatementsExecuted: 0,\n\t\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.log('✅ Database is up to date — nothing to push.');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// EH-14: return instead of process.exit(0) so pool.end() in finally runs\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toutputResult(statements, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, statements, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'applied',\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statement(s) executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// EH-14: return so finally runs pool.end() before the outer success path\n\t\t\t\t} finally {\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\n/**\n * Output SQL statements (dry-run or --json mode).\n */\nfunction outputResult(\n\tstatements: readonly string[],\n\toptions: { dryRun?: boolean; json?: boolean },\n): void {\n\tif (options.dryRun && !options.json) {\n\t\tconsole.log(`-- Dry run: ${statements.length} statement(s)\\n`);\n\t\tfor (const stmt of statements) {\n\t\t\tconsole.log(`${stmt};\\n`);\n\t\t}\n\t}\n}\n","/**\n * DDL Executor — Transaction-wrapped DDL execution.\n *\n * Shared by `dbsp push` and `dbsp migrate apply`.\n * Executes an array of SQL statements inside a single transaction.\n */\n\nimport type { Pool, PoolClient } from 'pg';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DdlExecutionResult {\n\t/** Number of statements executed */\n\tstatementsExecuted: number;\n\t/** Whether execution was a dry-run (no actual changes) */\n\tdryRun: boolean;\n}\n\n// ============================================================================\n// Executor\n// ============================================================================\n\n/**\n * Execute DDL statements in a transaction.\n *\n * @param pool - pg Pool instance\n * @param statements - SQL statements to execute\n * @param options - Execution options\n * @returns Execution result\n */\nexport async function executeDdl(\n\tpool: Pool,\n\tstatements: readonly string[],\n\toptions?: { dryRun?: boolean },\n): Promise<DdlExecutionResult> {\n\tif (statements.length === 0) {\n\t\treturn { statementsExecuted: 0, dryRun: options?.dryRun ?? false };\n\t}\n\n\tif (options?.dryRun) {\n\t\treturn { statementsExecuted: statements.length, dryRun: true };\n\t}\n\n\tlet client: PoolClient | undefined;\n\ttry {\n\t\tclient = await pool.connect();\n\t\tawait client.query('BEGIN');\n\n\t\tfor (const stmt of statements) {\n\t\t\tawait client.query(stmt);\n\t\t}\n\n\t\tawait client.query('COMMIT');\n\t\treturn { statementsExecuted: statements.length, dryRun: false };\n\t} catch (error) {\n\t\tif (client) {\n\t\t\tawait client.query('ROLLBACK');\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tif (client) {\n\t\t\tclient.release();\n\t\t}\n\t}\n}\n","/**\n * DX-030 Block 1: REPL Command\n *\n * dbsp repl [--schema <path>] - Launch interactive REPL.\n * CLI-022: Batch mode support with --eval and --input options.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { Command } from 'commander';\nimport { config } from '../config.js';\nimport { validateIdentifier } from '../utils/identifier-validation.js';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport interface ReplOptions {\n\tschema?: string;\n\t/** CLI-020: Database connection URL for execution mode */\n\tdb?: string;\n\t/** CLI-022: Single query to evaluate (batch mode) */\n\teval?: string;\n\t/** CLI-022: File containing queries to execute (batch mode, one per line) */\n\tinput?: string;\n\t/** CLI-022: Output format for batch mode */\n\tformat?: 'text' | 'json';\n\t/** DEMO-E2E: Assertion file for validating query output */\n\tassert?: string;\n\t/** CLI-IMPORT: SQL files to import before queries (injected as .import commands) */\n\timport?: string[];\n\t/** CLI-USE: PostgreSQL schema to use (injected as .use command) */\n\tuse?: string;\n\t/** CLI-MUT: Start REPL with parse mode enabled */\n\tparse?: boolean;\n\t/** CLI-MUT: Start REPL with exec mode enabled */\n\texec?: boolean;\n\t/** CLI-CONFIG: Custom config file path (default: ~/.dbsp/config.json) */\n\tconfig?: string;\n\t/** CLI-CASING: Column naming convention (describes DB column casing) */\n\tcasing?: 'snake' | 'camel' | 'none';\n}\n\nexport const replCommand = new Command('repl')\n\t.description('Launch interactive REPL for exploring schema and queries')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option(\n\t\t'-d, --db <url>',\n\t\t'PostgreSQL connection URL for execution mode (e.g., postgres://localhost/mydb)',\n\t)\n\t.option('-e, --eval <query>', 'Execute a single query and exit (batch mode)')\n\t.option(\n\t\t'-i, --input <file>',\n\t\t'Execute queries from file, one per line (batch mode)',\n\t)\n\t.option(\n\t\t'-f, --format <format>',\n\t\t'Output format for batch mode: text (default) or json',\n\t\t'text',\n\t)\n\t.option(\n\t\t'-a, --assert <file>',\n\t\t'Assertion file to validate query output (requires --input)',\n\t)\n\t.option(\n\t\t'--import <files...>',\n\t\t'SQL files to import before queries (equivalent to .import commands)',\n\t)\n\t.option(\n\t\t'--use <schema>',\n\t\t'PostgreSQL schema to use (equivalent to .use command)',\n\t)\n\t.option('--parse', 'Start REPL with parse mode enabled (.parse toggle)')\n\t.option('--exec', 'Start REPL with exec mode enabled (.exec toggle)')\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming convention: snake (DB uses snake_case), camel (DB uses camelCase), none (preserve as-is)',\n\t)\n\t.option(\n\t\t'-c, --config <path>',\n\t\t'Custom config file path (default: ~/.dbsp/config.json)',\n\t)\n\t.action(async (options: ReplOptions) => {\n\t\t// Set custom config path if provided\n\t\tif (options.config) {\n\t\t\tconfig.setConfigPath(options.config);\n\t\t}\n\t\t// Load config\n\t\tconfig.load();\n\t\ttry {\n\t\t\t// Load schema\n\t\t\tlet schemaPath: string;\n\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\n\t\t\tif (options.schema) {\n\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\tschemaPath = options.schema;\n\t\t\t} else {\n\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\tschema = result.schema;\n\t\t\t\tschemaPath = result.path;\n\t\t\t}\n\n\t\t\t// DEMO-E2E: Validate --assert requires --input\n\t\t\tif (options.assert && !options.input) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'--assert requires --input (assertion files validate query output from input files)',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// CLI-IMPORT: Validate that --import requires batch mode (SQL file execution)\n\t\t\t// Note: --use, --parse, --exec work in interactive mode (REPL state setup)\n\t\t\tif (options.import && !options.eval && !options.input) {\n\t\t\t\tthrow new Error('--import requires batch mode (--eval or --input)');\n\t\t\t}\n\n\t\t\t// SEC: Validate --use schema name at the entry point so both the batch path\n\t\t\t// (which injects `.use <schema>`) and the interactive path (which passes\n\t\t\t// initialSchemaName) are protected against SQL injection.\n\t\t\tif (options.use) {\n\t\t\t\tvalidateIdentifier(options.use, 'schema');\n\t\t\t}\n\n\t\t\t// CLI-CASING: Map --casing flag to dbCasing (intuitive: describes DB columns)\n\t\t\tconst dbCasing =\n\t\t\t\toptions.casing === 'snake'\n\t\t\t\t\t? ('snake_case' as const)\n\t\t\t\t\t: options.casing === 'camel'\n\t\t\t\t\t\t? ('camelCase' as const)\n\t\t\t\t\t\t: options.casing === 'none'\n\t\t\t\t\t\t\t? ('preserve' as const)\n\t\t\t\t\t\t\t: undefined;\n\n\t\t\t// CLI-022: Batch mode - execute queries without interactive UI\n\t\t\tif (options.eval || options.input) {\n\t\t\t\tconst { runBatchMode } = await import('../repl/batch.js');\n\t\t\t\tconst queries: string[] = [];\n\n\t\t\t\t// CLI-USE: Inject .use command first (schema scoping)\n\t\t\t\tif (options.use) {\n\t\t\t\t\tqueries.push(`.use ${options.use}`);\n\t\t\t\t}\n\n\t\t\t\t// CLI-IMPORT: Inject .import commands for SQL files\n\t\t\t\tif (options.import) {\n\t\t\t\t\tfor (const file of options.import) {\n\t\t\t\t\t\tqueries.push(`.import ${file}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.eval) {\n\t\t\t\t\tqueries.push(options.eval);\n\t\t\t\t}\n\n\t\t\t\tif (options.input) {\n\t\t\t\t\t// EH-2: Map ENOENT to a friendly error instead of raw stack trace\n\t\t\t\t\tlet content: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(options.input, 'utf-8');\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst isNotFound =\n\t\t\t\t\t\t\terr instanceof Error &&\n\t\t\t\t\t\t\t'code' in err &&\n\t\t\t\t\t\t\t(err as NodeJS.ErrnoException).code === 'ENOENT';\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tisNotFound\n\t\t\t\t\t\t\t\t? `Input file not found: ${options.input}`\n\t\t\t\t\t\t\t\t: `Failed to read input file: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tqueries.push(...content.split('\\n'));\n\t\t\t\t}\n\n\t\t\t\tawait runBatchMode({\n\t\t\t\t\tqueries,\n\t\t\t\t\tschema,\n\t\t\t\t\tschemaPath,\n\t\t\t\t\tformat: options.format ?? 'text',\n\t\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t\t...(options.assert && { assertFile: options.assert }),\n\t\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Dynamic import to avoid loading React/Ink for other commands\n\t\t\tconst { startRepl } = await import('../repl/index.js');\n\t\t\t// CLI-020: Pass database URL if provided\n\t\t\t// CLI-MUT: Pass initial REPL state options\n\t\t\tawait startRepl({\n\t\t\t\tschema,\n\t\t\t\tschemaPath,\n\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t...(options.use && { initialSchemaName: options.use }),\n\t\t\t\t...(options.parse && { initialParseMode: true }),\n\t\t\t\t...(options.exec && { initialExecMode: true }),\n\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconsole.error(`❌ ${message}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t});\n","/**\n * Verify Command — Schema Drift Detection\n *\n * dbsp verify - Compare schema vs real database using the comparison engine.\n * Detects drift in tables, columns, types, nullable, defaults, FKs, indexes.\n */\n\nimport { compareSchemata, introspect } from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\nimport { formatVerifyResult, verifyFromDiff } from '../verifier.js';\n\nexport const verifyCommand = new Command('verify')\n\t.description('Compare schema vs real database (drift detection)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(`🔍 Verifying schema: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Load schema from file → ModelIR\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Introspect database → ModelIR\n\t\t\t\t\tconst dbModel = await introspect(pool, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Compare using the comparison engine\n\t\t\t\t\tconst diff = compareSchemata(schemaModel, dbModel);\n\n\t\t\t\t\t// Convert to verify result\n\t\t\t\t\tconst schemaTables = Array.from(schemaModel.tables.keys());\n\t\t\t\t\tconst dbTables = Array.from(dbModel.tables.keys());\n\t\t\t\t\tconst result = verifyFromDiff(diff, schemaTables, dbTables);\n\n\t\t\t\t\t// Output\n\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t// Exclude the full diff meta from JSON output (too verbose)\n\t\t\t\t\t\tconst { diff: _diff, ...jsonResult } = result;\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...jsonResult,\n\t\t\t\t\t\t\t\t\tsummary: diff.summary,\n\t\t\t\t\t\t\t\t\thasDestructive: diff.hasDestructive,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(formatVerifyResult(result));\n\t\t\t\t\t}\n\n\t\t\t\t\t// EH-14: set exit code; let finally run pool.end() before process exits\n\t\t\t\t\tprocess.exitCode = result.valid ? 0 : 1;\n\t\t\t\t\treturn;\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Schema Verifier — Drift Detection via Comparison Engine\n *\n * Compares schema definition against real database using the\n * adapter's compareSchemata engine for full drift detection.\n *\n * Detects: tables, columns, types, nullable, defaults, FKs, indexes, PKs.\n */\n\nimport type { ChangeKind, SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DriftSeverity = 'error' | 'warning' | 'info';\n\nexport type DriftType =\n\t// Tables\n\t| 'missing_table_in_db'\n\t| 'missing_table_in_schema'\n\t// Columns\n\t| 'missing_column_in_db'\n\t| 'missing_column_in_schema'\n\t| 'type_mismatch'\n\t| 'nullable_mismatch'\n\t| 'default_mismatch'\n\t| 'unique_mismatch'\n\t// Constraints\n\t| 'primary_key_mismatch'\n\t| 'missing_fk_in_db'\n\t| 'missing_fk_in_schema'\n\t| 'fk_on_delete_mismatch'\n\t// Indexes\n\t| 'missing_index_in_db'\n\t| 'missing_index_in_schema'\n\t// CHECK constraints\n\t| 'missing_check_in_db'\n\t| 'missing_check_in_schema'\n\t// ENUM types\n\t| 'missing_enum_in_db'\n\t| 'missing_enum_in_schema'\n\t| 'enum_value_mismatch'\n\t// Column enhancements\n\t| 'collation_mismatch'\n\t| 'identity_mismatch'\n\t// Comments\n\t| 'comment_mismatch'\n\t// Extensions & Sequences\n\t| 'missing_extension'\n\t| 'missing_sequence'\n\t| 'sequence_mismatch'\n\t// Constraints\n\t| 'constraint_validation_mismatch'\n\t// Row-Level Security\n\t| 'rls_enabled_mismatch'\n\t| 'missing_policy_in_db'\n\t| 'missing_policy_in_schema';\n\nexport interface DriftIssue {\n\t/** Issue severity */\n\tseverity: DriftSeverity;\n\t/** Human-readable message */\n\tmessage: string;\n\t/** Table affected (if any) */\n\ttable?: string;\n\t/** Column affected (if any) */\n\tcolumn?: string;\n\t/** Issue type for programmatic handling */\n\ttype: DriftType;\n}\n\nexport interface VerifyResult {\n\t/** Whether the schema matches the database */\n\tvalid: boolean;\n\t/** List of drift issues found */\n\tissues: DriftIssue[];\n\t/** Tables in schema */\n\tschemaTables: string[];\n\t/** Tables in database */\n\tdbTables: string[];\n\t/** Structured diff (for programmatic consumers) */\n\tdiff: SchemaDiff;\n}\n\n// ============================================================================\n// Change → Drift Mapping\n// ============================================================================\n\nconst CHANGE_TO_DRIFT: Record<\n\tChangeKind,\n\t{ type: DriftType; severity: DriftSeverity }\n> = {\n\tcreate_table: { type: 'missing_table_in_db', severity: 'error' },\n\tdrop_table: { type: 'missing_table_in_schema', severity: 'warning' },\n\tadd_column: { type: 'missing_column_in_db', severity: 'error' },\n\tdrop_column: { type: 'missing_column_in_schema', severity: 'info' },\n\talter_column_type: { type: 'type_mismatch', severity: 'error' },\n\talter_column_nullable: { type: 'nullable_mismatch', severity: 'warning' },\n\talter_column_default: { type: 'default_mismatch', severity: 'warning' },\n\talter_column_unique: { type: 'unique_mismatch', severity: 'warning' },\n\tadd_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tdrop_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tadd_foreign_key: { type: 'missing_fk_in_db', severity: 'error' },\n\tdrop_foreign_key: { type: 'missing_fk_in_schema', severity: 'warning' },\n\talter_foreign_key: { type: 'fk_on_delete_mismatch', severity: 'warning' },\n\tcreate_index: { type: 'missing_index_in_db', severity: 'warning' },\n\tdrop_index: { type: 'missing_index_in_schema', severity: 'info' },\n\t// CHECK constraints\n\tadd_check_constraint: { type: 'missing_check_in_db', severity: 'warning' },\n\tdrop_check_constraint: { type: 'missing_check_in_schema', severity: 'info' },\n\t// ENUM types\n\tcreate_enum: { type: 'missing_enum_in_db', severity: 'error' },\n\talter_enum_add_value: { type: 'enum_value_mismatch', severity: 'warning' },\n\tdrop_enum: { type: 'missing_enum_in_schema', severity: 'warning' },\n\t// Column enhancements\n\talter_column_collation: { type: 'collation_mismatch', severity: 'warning' },\n\talter_column_identity: { type: 'identity_mismatch', severity: 'warning' },\n\t// Comments\n\tadd_comment: { type: 'comment_mismatch', severity: 'info' },\n\tdrop_comment: { type: 'comment_mismatch', severity: 'info' },\n\t// Extensions & Sequences\n\tcreate_extension: { type: 'missing_extension', severity: 'error' },\n\tdrop_extension: { type: 'missing_extension', severity: 'info' },\n\tcreate_sequence: { type: 'missing_sequence', severity: 'warning' },\n\talter_sequence: { type: 'sequence_mismatch', severity: 'warning' },\n\tdrop_sequence: { type: 'missing_sequence', severity: 'info' },\n\t// Constraints\n\tvalidate_constraint: {\n\t\ttype: 'constraint_validation_mismatch',\n\t\tseverity: 'warning',\n\t},\n\t// Row-Level Security\n\tenable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tdisable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tcreate_policy: { type: 'missing_policy_in_db', severity: 'warning' },\n\tdrop_policy: { type: 'missing_policy_in_schema', severity: 'warning' },\n};\n\nfunction changeToDriftIssue(change: SchemaChange): DriftIssue {\n\tconst mapping = CHANGE_TO_DRIFT[change.kind] ?? {\n\t\ttype: 'type_mismatch' as DriftType,\n\t\tseverity: 'warning' as DriftSeverity,\n\t};\n\treturn {\n\t\tseverity: mapping.severity,\n\t\ttype: mapping.type,\n\t\ttable: change.table,\n\t\t...(change.column !== undefined ? { column: change.column } : {}),\n\t\tmessage: change.details,\n\t};\n}\n\n// ============================================================================\n// Verification (from SchemaDiff)\n// ============================================================================\n\n/**\n * Convert a SchemaDiff into a VerifyResult.\n *\n * @param diff - Structured diff from compareSchemata()\n * @param schemaTables - Table names in schema (for backward compat)\n * @param dbTables - Table names in database (for backward compat)\n */\nexport function verifyFromDiff(\n\tdiff: SchemaDiff,\n\tschemaTables: string[],\n\tdbTables: string[],\n): VerifyResult {\n\tconst issues = diff.changes.map(changeToDriftIssue);\n\n\t// Sort by severity (error > warning > info)\n\tconst severityOrder: Record<DriftSeverity, number> = {\n\t\terror: 0,\n\t\twarning: 1,\n\t\tinfo: 2,\n\t};\n\tissues.sort(\n\t\t(a: DriftIssue, b: DriftIssue) =>\n\t\t\tseverityOrder[a.severity] - severityOrder[b.severity],\n\t);\n\n\tconst hasErrors = issues.some((i: DriftIssue) => i.severity === 'error');\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tissues,\n\t\tschemaTables,\n\t\tdbTables,\n\t\tdiff,\n\t};\n}\n\n// ============================================================================\n// Format\n// ============================================================================\n\n/**\n * Format verification result for CLI output.\n */\nexport function formatVerifyResult(result: VerifyResult): string {\n\tconst lines: string[] = [];\n\n\tif (result.valid) {\n\t\tlines.push('✅ Schema matches database');\n\t} else {\n\t\tlines.push('❌ Schema drift detected');\n\t}\n\n\tlines.push('');\n\tlines.push(`Tables in schema: ${result.schemaTables.length}`);\n\tlines.push(`Tables in database: ${result.dbTables.length}`);\n\tlines.push('');\n\n\tif (result.issues.length === 0) {\n\t\tlines.push('No issues found.');\n\t} else {\n\t\tconst errors = result.issues.filter((i) => i.severity === 'error');\n\t\tconst warnings = result.issues.filter((i) => i.severity === 'warning');\n\t\tconst infos = result.issues.filter((i) => i.severity === 'info');\n\n\t\tif (errors.length > 0) {\n\t\t\tlines.push(`❌ ${errors.length} error(s):`);\n\t\t\tfor (const issue of errors) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (warnings.length > 0) {\n\t\t\tlines.push(`⚠️ ${warnings.length} warning(s):`);\n\t\t\tfor (const issue of warnings) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (infos.length > 0) {\n\t\t\tlines.push(`ℹ️ ${infos.length} info:`);\n\t\t\tfor (const issue of infos) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\t}\n\n\treturn lines.join('\\n');\n}\n"],"mappings":"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","names":["Command","outputPath","mkdirSync","writeFileSync","dirname","resolve","Command","Command","introspect","resolve","mkdirSync","dirname","writeFileSync","Command","mkdirSync","writeFileSync","resolve","Command","compareSchemata","generateMigrationSQL","introspect","Command","Command","introspect","compareSchemata","generateMigrationSQL","readFileSync","Command","Command","readFileSync","compareSchemata","introspect","Command","Command","introspect","compareSchemata","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/commands/introspect.ts","../src/commands/migrate.ts","../src/migration-file.ts","../src/commands/push.ts","../src/ddl-executor.ts","../src/commands/repl.ts","../src/commands/verify.ts","../src/verifier.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ARCH-002 Block 3: CLI Scaffold\n *\n * dbsp CLI - Schema-first code generation for db-semantic-planner.\n */\n\nimport { Command, CommanderError } from 'commander';\nimport { generateCommand } from './commands/generate.js';\nimport { introspectCommand } from './commands/introspect.js';\nimport { migrateCommand } from './commands/migrate.js';\nimport { pushCommand } from './commands/push.js';\nimport { replCommand } from './commands/repl.js';\nimport { verifyCommand } from './commands/verify.js';\n\nconst program = new Command();\n\nprogram\n\t.name('dbsp')\n\t.description('Schema-first code generation for db-semantic-planner')\n\t.version('0.0.1');\n\n// Register commands\nprogram.addCommand(generateCommand);\nprogram.addCommand(introspectCommand);\nprogram.addCommand(migrateCommand);\nprogram.addCommand(pushCommand);\nprogram.addCommand(replCommand);\nprogram.addCommand(verifyCommand);\n\n// CC-15: Intercept Commander parse errors so --json commands receive a JSON\n// error object on stdout instead of a plain-text usage message.\nprogram.exitOverride();\n\ntry {\n\tprogram.parse();\n} catch (err) {\n\t// Commander throws CommanderError for --help, --version, and parse errors.\n\t// Exit 0 for informational outputs (help/version); only exit 1 for real errors.\n\tif (err instanceof CommanderError && err.exitCode === 0) {\n\t\tprocess.exit(0);\n\t}\n\tconst message = err instanceof Error ? err.message : 'Command parse error';\n\tif (process.argv.includes('--json')) {\n\t\tconsole.log(JSON.stringify({ status: 'error', error: message }, null, 2));\n\t} else {\n\t\tconsole.error(`❌ ${message}`);\n\t}\n\tprocess.exit(1);\n}\n","/**\n * ARCH-002 Block 3+4+5: Generate Command\n * CLI-DDL: Added DDL generation target\n * ARCH-005: Migrated to schema() API, removed legacy generators\n *\n * dbsp generate <target> - Generate code from schema.\n *\n * Targets:\n * - ddl: Generate SQL DDL (CREATE TABLE statements)\n *\n * Deprecated targets (removed in ARCH-005):\n * - manifest: Removed legacy manifest generator\n * - kysely: Removed legacy Kysely generator\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport type { DbCasing } from '@dbsp/types';\nimport { Command } from 'commander';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport type GenerateCasingOption = 'snake' | 'camel' | 'none';\n\nexport const generateCommand = new Command('generate')\n\t.description('Generate code from schema')\n\t.argument('<target>', 'Target to generate: ddl')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option('-o, --out <dir>', 'Output directory (default: ./generated/<target>)')\n\t.option('--output <dir>', 'Output directory (alias for --out)')\n\t.option('--drop', 'Include DROP TABLE IF EXISTS statements (ddl only)')\n\t.option('--schema-name <name>', 'Database schema name (ddl only)')\n\t.option(\n\t\t'--dialect <name>',\n\t\t'Database dialect: postgresql | mysql | sqlite | mssql (default: postgresql)',\n\t)\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming: snake | camel | none (default: based on dialect)',\n\t)\n\t.action(\n\t\tasync (\n\t\t\ttarget: string,\n\t\t\toptions: {\n\t\t\t\tschema?: string;\n\t\t\t\tout?: string;\n\t\t\t\toutput?: string;\n\t\t\t\tdrop?: boolean;\n\t\t\t\tschemaName?: string;\n\t\t\t\tdialect?: string;\n\t\t\t\tcasing?: GenerateCasingOption;\n\t\t\t},\n\t\t) => {\n\t\t\ttry {\n\t\t\t\t// Reject the target BEFORE loading the schema: loading it executes the\n\t\t\t\t// user's schema module, and there is no reason to run their code for a\n\t\t\t\t// target we are going to refuse anyway.\n\t\t\t\tif (target === 'manifest' || target === 'kysely') {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Target '${target}' has been removed. ` +\n\t\t\t\t\t\t\t`Use 'ddl' for SQL generation, or the 'introspect' command to generate a schema file.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (target !== 'ddl') {\n\t\t\t\t\tthrow new Error(`Unknown target: ${target}. Available targets: ddl`);\n\t\t\t\t}\n\n\t\t\t\t// Load schema (ARCH-005: only schema() format supported)\n\t\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\t\t\t\tlet schemaPath: string;\n\n\t\t\t\tif (options.schema) {\n\t\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\t\tschemaPath = options.schema;\n\t\t\t\t} else {\n\t\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\t\tschema = result.schema;\n\t\t\t\t\tschemaPath = result.path;\n\t\t\t\t}\n\n\t\t\t\t// For DDL without --output, we output to stdout so use stderr for info\n\t\t\t\tconst outputPath = options.out ?? options.output;\n\t\t\t\tconst useStdout = target === 'ddl' && !outputPath;\n\t\t\t\tconst log = useStdout ? console.error : console.log;\n\n\t\t\t\tlog(`📄 Loaded schema from: ${schemaPath}`);\n\n\t\t\t\t// Validate dialect option\n\t\t\t\tconst dialect = options.dialect ?? 'postgresql';\n\t\t\t\tif (!isDialectSupported(dialect)) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`⚠️ Warning: Only 'postgresql' dialect is currently supported. Using postgresql.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Generate based on target\n\t\t\t\tswitch (target) {\n\t\t\t\t\tcase 'ddl': {\n\t\t\t\t\t\t// Determine casing: explicit option > schema export > dialect default.\n\t\t\t\t\t\tconst dbCasing =\n\t\t\t\t\t\t\tmapCasingToDbCasing(options.casing) ??\n\t\t\t\t\t\t\tschema.dbCasing ??\n\t\t\t\t\t\t\t('snake_case' as const);\n\t\t\t\t\t\tconst casingLabel = formatGenerateCasingLabel(dbCasing);\n\n\t\t\t\t\t\t// Import adapter from adapter-pgsql (compile-only, no DB connection needed)\n\t\t\t\t\t\tconst { createPgsqlCompileOnlyAdapter } = await import(\n\t\t\t\t\t\t\t'@dbsp/adapter-pgsql'\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst adapter = createPgsqlCompileOnlyAdapter({\n\t\t\t\t\t\t\tdbCasing,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// ARCH-005: Use schema.model directly (already ModelIR)\n\t\t\t\t\t\t\tconst ddlStatements = adapter.generateDDL(schema.model, {\n\t\t\t\t\t\t\t\t...(options.drop !== undefined && {\n\t\t\t\t\t\t\t\t\tincludeDropStatements: options.drop,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst ddlContent = ddlStatements.join('\\n\\n');\n\t\t\t\t\t\t\tconst outputPath = options.out ?? options.output;\n\n\t\t\t\t\t\t\tif (outputPath) {\n\t\t\t\t\t\t\t\t// Write to file if --output is specified\n\t\t\t\t\t\t\t\tconst outPath = outputPath.endsWith('.sql')\n\t\t\t\t\t\t\t\t\t? resolve(process.cwd(), outputPath)\n\t\t\t\t\t\t\t\t\t: resolve(process.cwd(), outputPath, 'schema.sql');\n\n\t\t\t\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\t\t\t\twriteFileSync(outPath, ddlContent, 'utf-8');\n\n\t\t\t\t\t\t\t\tconsole.log(`✅ Generated DDL: ${outPath}`);\n\t\t\t\t\t\t\t\tconsole.log(` Tables: ${schema.tableNames.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Statements: ${ddlStatements.length}`);\n\t\t\t\t\t\t\t\tconsole.log(` Casing: ${casingLabel}`);\n\t\t\t\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Includes DROP statements`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (options.schemaName) {\n\t\t\t\t\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Output DDL to stdout (for piping)\n\t\t\t\t\t\t\t\t// Info messages (schema loaded) went to stderr\n\t\t\t\t\t\t\t\tconsole.log(ddlContent);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\nexport function isDialectSupported(dialect: string): dialect is 'postgresql' {\n\treturn dialect === 'postgresql';\n}\n\nexport function mapCasingToDbCasing(\n\tcasing: GenerateCasingOption | undefined,\n): DbCasing | undefined {\n\tswitch (casing) {\n\t\tcase 'snake':\n\t\t\treturn 'snake_case';\n\t\tcase 'camel':\n\t\t\treturn 'camelCase';\n\t\tcase 'none':\n\t\t\treturn 'preserve';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction formatGenerateCasingLabel(\n\tdbCasing: DbCasing,\n): 'snake' | 'camel' | 'none' {\n\tswitch (dbCasing) {\n\t\tcase 'snake_case':\n\t\t\treturn 'snake';\n\t\tcase 'camelCase':\n\t\t\treturn 'camel';\n\t\tcase 'preserve':\n\t\t\treturn 'none';\n\t}\n}\n","/**\n * CLI-DDL: Introspect Command\n *\n * dbsp introspect - Generate schema.ts from database introspection.\n */\n\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { Command } from 'commander';\nimport {\n\tgenerateSchemaFileWithDiagnostics,\n\ttype SchemaCodegenOptions,\n} from '../generators/schema-codegen.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\n\nfunction uniqueWarnings(warnings: readonly string[]): string[] {\n\tconst seen = new Set<string>();\n\tconst unique: string[] = [];\n\tfor (const warning of warnings) {\n\t\tif (seen.has(warning)) continue;\n\t\tseen.add(warning);\n\t\tunique.push(warning);\n\t}\n\treturn unique;\n}\n\nfunction reportWarnings(warnings: readonly string[]): void {\n\tfor (const warning of warnings) {\n\t\tconsole.log(` ⚠️ ${warning}`);\n\t}\n}\n\nexport const introspectCommand = new Command('introspect')\n\t.description('Generate schema.ts from database introspection')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('-o, --out <file>', 'Output schema file', './dbsp.schema.ts')\n\t.option('--schema-name <name>', 'Database schema name', 'public')\n\t.option(\n\t\t'--exclude <patterns>',\n\t\t'Tables to exclude (comma-separated glob patterns)',\n\t\t'_migrations,_prisma*,pg_*',\n\t)\n\t.option('--include <patterns>', 'Tables to include (comma-separated)')\n\t.option('--no-db-type-comments', 'Omit original DB type comments')\n\t.option(\n\t\t'--db-casing <casing>',\n\t\t'Database column casing (snake_case → camelCase in generated code)',\n\t\t'snake_case',\n\t)\n\t.action(\n\t\tasync (options: {\n\t\t\tdb: string;\n\t\t\tout: string;\n\t\t\tschemaName: string;\n\t\t\texclude: string;\n\t\t\tinclude?: string;\n\t\t\tdbTypeComments: boolean;\n\t\t\tdbCasing: 'snake_case' | 'camelCase' | 'preserve';\n\t\t}) => {\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tconsole.log(`🔍 Introspecting database: ${redactedUrl}`);\n\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\tif (options.exclude) {\n\t\t\t\tconsole.log(` Excluding: ${options.exclude}`);\n\t\t\t}\n\t\t\tconsole.log('');\n\n\t\t\ttry {\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Import introspect from adapter-pgsql\n\t\t\t\t\tconst { introspect } = await import('@dbsp/adapter-pgsql');\n\n\t\t\t\t\t// Build introspection options from CLI flags\n\t\t\t\t\tconst excludePatterns = options.exclude\n\t\t\t\t\t\t? options.exclude.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst includePatterns = options.include\n\t\t\t\t\t\t? options.include.split(',').map((s) => s.trim())\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\t// Introspect the database directly (returns IntrospectedModelIR)\n\t\t\t\t\tconst model = await introspect(pool, {\n\t\t\t\t\t\tschema: options.schemaName,\n\t\t\t\t\t\t...(excludePatterns ? { exclude: excludePatterns } : {}),\n\t\t\t\t\t\t...(includePatterns ? { include: includePatterns } : {}),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Report what we found\n\t\t\t\t\tconst tableCount = model.tables.size;\n\t\t\t\t\tconst relationCount = model.relations.size;\n\t\t\t\t\tconst hierarchyCount = model.hierarchies?.length ?? 0;\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`📊 Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`,\n\t\t\t\t\t);\n\n\t\t\t\t\t// Generate schema file — pass metadata from introspection\n\t\t\t\t\tconst codegenOptions: SchemaCodegenOptions = {\n\t\t\t\t\t\tsourceUrl: options.db,\n\t\t\t\t\t\tincludeDbTypeComments: options.dbTypeComments,\n\t\t\t\t\t\tintrospectedAt: model.introspectedAt,\n\t\t\t\t\t\tdbCasing: options.dbCasing,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst generated = generateSchemaFileWithDiagnostics(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tcodegenOptions,\n\t\t\t\t\t);\n\t\t\t\t\tconst warnings = uniqueWarnings(generated.warnings);\n\t\t\t\t\tif (warnings.length > 0) {\n\t\t\t\t\t\treportWarnings(warnings);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\t// Write output file\n\t\t\t\t\tconst outPath = resolve(process.cwd(), options.out);\n\t\t\t\t\tmkdirSync(dirname(outPath), { recursive: true });\n\t\t\t\t\twriteFileSync(outPath, generated.code, 'utf-8');\n\n\t\t\t\t\tconsole.log(`✅ Generated schema: ${outPath}`);\n\t\t\t\t\tconsole.log(` Tables: ${tableCount}`);\n\t\t\t\t\tconsole.log(` Relations: ${relationCount}`);\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconsole.error(`❌ ${message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Migrate Command — Migration Infrastructure\n *\n * dbsp migrate dev - Generate migration from schema diff\n * dbsp migrate apply - Apply pending migrations\n * dbsp migrate rollback - Roll back applied migrations\n * dbsp migrate status - Show migration status\n */\n\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n\tensureMigrationsTable,\n\tgenerateMigrationFile,\n\tgenerateMigrationSQL,\n\tgetAppliedMigrations,\n\tgetNextSchemaVersion,\n\tisDestructiveDown,\n\ttype MigrationRecord,\n\tparseMigrationFile,\n\trecordMigration,\n\tremoveMigrationRecord,\n\twithMigrationLock,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport type { Pool, PoolClient } from 'pg';\nimport {\n\tDEFAULT_MIGRATIONS_DIR,\n\tgenerateMigrationFilename,\n\ttype MigrationFile,\n\tscanMigrationFiles,\n\twriteMigrationFile,\n} from '../migration-file.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Typed error for migration failures.\n * Distinguishes user-facing errors (validation, logic) from unexpected errors.\n */\nexport class MigrationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'MigrationError';\n\t}\n}\n\n/**\n * Sanitize a pg error for user-facing output.\n *\n * PostgreSQL errors may carry schema/table/row details in their message text.\n * For pg errors (identifiable by SQLSTATE `.code`), we emit a sanitized message\n * keyed only by the SQLSTATE code. The raw message is available via DEBUG=dbsp.\n *\n * @param err - Any caught value\n * @returns An Error instance safe to display to the user\n */\nexport function sanitizePgError(err: unknown): Error {\n\tif (err instanceof Error) {\n\t\t// pg errors carry a SQLSTATE `.code` property\n\t\tconst code = (err as Error & { code?: string }).code;\n\t\tif (typeof code === 'string' && /^[0-9A-Z]{5}$/.test(code)) {\n\t\t\tif (process.env.DEBUG?.includes('dbsp')) {\n\t\t\t\tconsole.error(`[DEBUG] pg error detail: ${err.message}`);\n\t\t\t}\n\t\t\treturn new MigrationError(`Migration failed: database error ${code}`);\n\t\t}\n\t\treturn err;\n\t}\n\treturn new Error(String(err));\n}\n\n/**\n * DRY lifecycle wrapper: create pool → run fn → end pool on success or error.\n * Replaces the repeated try { ... } finally { pool.end() } pattern across commands.\n *\n * process.exit is called OUTSIDE the pool lifecycle so the lock-holding client\n * (from withMigrationLock) is always released before the process terminates.\n */\nexport async function withMigratePool<T>(\n\tdbUrl: string,\n\tfn: (pool: Pool) => Promise<T>,\n): Promise<T> {\n\tconst { pool } = await createDbConnection(dbUrl);\n\ttry {\n\t\treturn await fn(pool);\n\t} finally {\n\t\tlet endError: unknown;\n\t\ttry {\n\t\t\tawait pool.end();\n\t\t} catch (e) {\n\t\t\tendError = e;\n\t\t}\n\t\tif (endError !== undefined) {\n\t\t\t// Cleanup failure is non-fatal — log it but don't mask the original error\n\t\t\tconsole.error(\n\t\t\t\t`Warning: pool.end() failed: ${endError instanceof Error ? endError.message : String(endError)}`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Top-level error handler for migrate commands.\n * Ensures process.exit is only called AFTER pool cleanup (via withMigratePool).\n */\nasync function runMigrateAction(fn: () => Promise<void>): Promise<void> {\n\ttry {\n\t\tawait fn();\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(`❌ Error: ${error.message}`);\n\t\t} else {\n\t\t\tconsole.error('❌ Unknown error occurred');\n\t\t}\n\t\tprocess.exit(1);\n\t}\n}\n\n/**\n * Returns true if a migration statement contains at least one executable SQL\n * line (non-empty, not a comment). Statements that consist entirely of blank\n * lines and `--` comment lines are skipped; statements that begin with a\n * comment header but also contain real SQL are kept (PostgreSQL ignores the\n * comment, so the header is harmless and preserves intent).\n */\nexport function hasExecutableSql(stmt: string): boolean {\n\treturn stmt.split('\\n').some((line) => {\n\t\tconst t = line.trim();\n\t\treturn t.length > 0 && !t.startsWith('--');\n\t});\n}\n\nexport interface ApplyOptions {\n\tdb: string;\n\tdir: string;\n\tdryRun?: boolean;\n}\n\nexport interface RollbackOptions {\n\tcount: number;\n\tdb: string;\n\tdir: string;\n\tforce?: boolean;\n}\n\nexport interface MigrateDeps {\n\twithMigratePool?: <T>(\n\t\tdbUrl: string,\n\t\tfn: (pool: Pool) => Promise<T>,\n\t) => Promise<T>;\n\tensureMigrationsTable?: (pool: Pool) => Promise<void>;\n\twithMigrationLock?: <T>(\n\t\tpool: Pool,\n\t\tfn: (client: PoolClient) => Promise<T>,\n\t) => Promise<T>;\n\tgetAppliedMigrations?: (pool: Pool) => Promise<readonly MigrationRecord[]>;\n\tscanMigrationFiles?: (dir: string) => readonly MigrationFile[];\n\tparseMigrationFile?: typeof parseMigrationFile;\n\tgetNextSchemaVersion?: (pool: Pool) => Promise<number>;\n\trecordMigration?: (\n\t\tpool: Pool,\n\t\tname: string,\n\t\tchecksum: string,\n\t\tschemaVersion: number,\n\t\tdestructive: boolean,\n\t) => Promise<void>;\n\tremoveMigrationRecord?: (pool: Pool, name: string) => Promise<void>;\n\tisDestructiveDown?: (downStatements: readonly string[]) => boolean;\n}\n\nconst defaultMigrateDeps: Required<MigrateDeps> = {\n\twithMigratePool,\n\tensureMigrationsTable,\n\twithMigrationLock,\n\tgetAppliedMigrations,\n\tscanMigrationFiles,\n\tparseMigrationFile,\n\tgetNextSchemaVersion,\n\trecordMigration,\n\tremoveMigrationRecord,\n\tisDestructiveDown,\n};\n\nfunction resolveMigrateDeps(deps?: MigrateDeps): Required<MigrateDeps> {\n\treturn { ...defaultMigrateDeps, ...deps };\n}\n\nexport async function runApply(\n\toptions: ApplyOptions,\n\tdeps?: MigrateDeps,\n): Promise<void> {\n\tconst d = resolveMigrateDeps(deps);\n\n\tconsole.log('🔄 Applying migrations');\n\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\tconsole.log(` Directory: ${options.dir}`);\n\tconsole.log('');\n\n\tawait d.withMigratePool(options.db, async (pool) => {\n\t\t// Ensure tracking table exists before acquiring lock\n\t\tawait d.ensureMigrationsTable(pool);\n\n\t\tawait d.withMigrationLock(pool, async (client) => {\n\t\t\t// Get applied migrations on the lock-holding client\n\t\t\tconst applied = await d.getAppliedMigrations(client as unknown as Pool);\n\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m.checksum]));\n\n\t\t\t// Scan migration files\n\t\t\tconst files = d.scanMigrationFiles(options.dir);\n\n\t\t\tif (files.length === 0) {\n\t\t\t\tconsole.log(`No migration files found in ${options.dir}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Validate checksums for already-applied migrations\n\t\t\tfor (const file of files) {\n\t\t\t\tconst existingChecksum = appliedMap.get(file.name);\n\t\t\t\tif (\n\t\t\t\t\texistingChecksum !== undefined &&\n\t\t\t\t\texistingChecksum !== file.checksum\n\t\t\t\t) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Checksum mismatch for ${file.name}\\n` +\n\t\t\t\t\t\t\t` Expected: ${existingChecksum}\\n` +\n\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t'\\nMigration file has been tampered with after being applied.',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Find pending migrations\n\t\t\tconst pending = files.filter((f) => !appliedMap.has(f.name));\n\n\t\t\tif (pending.length === 0) {\n\t\t\t\tconsole.log('✅ All migrations already applied.');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (options.dryRun) {\n\t\t\t\tconsole.log(`${pending.length} pending migration(s):`);\n\t\t\t\tfor (const file of pending) {\n\t\t\t\t\tconsole.log(` - ${file.name}`);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Apply each pending migration atomically (DDL + record in one transaction)\n\t\t\tlet appliedCount = 0;\n\t\t\tfor (const file of pending) {\n\t\t\t\tconsole.log(` Applying: ${file.name}...`);\n\n\t\t\t\t// Parse UP section from file\n\t\t\t\tconst parsed = d.parseMigrationFile(file.content);\n\t\t\t\tconst statements = parsed.upStatements.filter(hasExecutableSql);\n\n\t\t\t\t// Determine version and destructive flag before the transaction\n\t\t\t\t// (read from the lock-held client so we see a consistent view)\n\t\t\t\tconst version = await d.getNextSchemaVersion(client as unknown as Pool);\n\t\t\t\tconst destructive = parsed.destructive === true;\n\n\t\t\t\t// Atomic: DDL + record in ONE transaction on the lock-holding client\n\t\t\t\ttry {\n\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\tfor (const stmt of statements) {\n\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait d.recordMigration(\n\t\t\t\t\t\tclient as unknown as Pool,\n\t\t\t\t\t\tfile.name,\n\t\t\t\t\t\tfile.checksum,\n\t\t\t\t\t\tversion,\n\t\t\t\t\t\tdestructive,\n\t\t\t\t\t);\n\n\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t} catch (applyError) {\n\t\t\t\t\tlet rollbackError: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\trollbackError = e;\n\t\t\t\t\t}\n\t\t\t\t\tconst primary = sanitizePgError(applyError);\n\t\t\t\t\tif (rollbackError !== undefined) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackError).message}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow primary;\n\t\t\t\t}\n\n\t\t\t\tappliedCount++;\n\t\t\t\tconsole.log(` ✅ Applied: ${file.name}`);\n\t\t\t}\n\n\t\t\tconsole.log(`\\n✅ ${appliedCount} migration(s) applied successfully.`);\n\t\t});\n\t});\n}\n\nexport async function runRollback(\n\toptions: RollbackOptions,\n\tdeps?: MigrateDeps,\n): Promise<void> {\n\tconst d = resolveMigrateDeps(deps);\n\n\tconsole.log(`⏪ Rolling back ${options.count} migration(s)`);\n\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\tconsole.log(` Directory: ${options.dir}`);\n\tconsole.log('');\n\n\tawait d.withMigratePool(options.db, async (pool) => {\n\t\tawait d.ensureMigrationsTable(pool);\n\n\t\tawait d.withMigrationLock(pool, async (client) => {\n\t\t\t// Get applied migrations on the lock-holding client\n\t\t\tconst applied = await d.getAppliedMigrations(client as unknown as Pool);\n\t\t\tconst sortedDesc = [...applied].sort((a, b) =>\n\t\t\t\tb.name.localeCompare(a.name),\n\t\t\t);\n\n\t\t\t// SC-18: Validate count\n\t\t\tif (options.count > sortedDesc.length) {\n\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t`Cannot roll back ${options.count} migration(s) — only ${sortedDesc.length} applied`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst toRollback = sortedDesc.slice(0, options.count);\n\n\t\t\t// Load migration files from disk\n\t\t\tconst files = d.scanMigrationFiles(options.dir);\n\t\t\tconst fileMap = new Map(files.map((f) => [f.name, f]));\n\n\t\t\tlet rolledBack = 0;\n\t\t\tfor (const record of toRollback) {\n\t\t\t\tconst file = fileMap.get(record.name);\n\t\t\t\tif (!file) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration file not found on disk: ${record.name}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// SC-17: Verify checksum\n\t\t\t\tif (file.checksum !== record.checksum) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Checksum mismatch for ${record.name}\\n` +\n\t\t\t\t\t\t\t` Expected: ${record.checksum}\\n` +\n\t\t\t\t\t\t\t` Got: ${file.checksum}\\n` +\n\t\t\t\t\t\t\t'\\nMigration file has been modified since it was applied.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Parse DOWN section\n\t\t\t\tconst parsed = d.parseMigrationFile(file.content);\n\n\t\t\t\t// SC-10/ERR-01: No DOWN section\n\t\t\t\tif (!parsed.hasDown) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration ${record.name} has no DOWN section\\n` +\n\t\t\t\t\t\t\t' Cannot roll back a migration without a DOWN section.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// SC-11/ERR-04: Empty DOWN section\n\t\t\t\tconst downStmts = parsed.downStatements.filter(hasExecutableSql);\n\t\t\t\tif (downStmts.length === 0 && !options.force) {\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration ${record.name} has an empty DOWN section\\n` +\n\t\t\t\t\t\t\t' Use --force to roll back anyway.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// SC-19: Metadata-driven destructive rollback guard\n\t\t\t\tconst safeHeaderContradictedByDown =\n\t\t\t\t\tparsed.destructive === false && d.isDestructiveDown(downStmts);\n\t\t\t\tif (\n\t\t\t\t\t(parsed.destructive !== false || safeHeaderContradictedByDown) &&\n\t\t\t\t\t!options.force\n\t\t\t\t) {\n\t\t\t\t\tif (parsed.destructive === true) {\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`Migration ${record.name} has destructive DOWN operations\\n` +\n\t\t\t\t\t\t\t\t' Use --force to proceed with destructive rollback.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (safeHeaderContradictedByDown) {\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`Migration ${record.name} is marked non-destructive but its DOWN section contains an obvious destructive statement\\n` +\n\t\t\t\t\t\t\t\t' Re-generate the migration with accurate dbsp metadata or pass --force.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t`Migration ${record.name} is unmarked or legacy\\n` +\n\t\t\t\t\t\t\t' Re-generate the migration with dbsp metadata or pass --force.',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Atomic: DOWN SQL + remove record in ONE transaction on the lock-holding client\n\t\t\t\tif (downStmts.length > 0 || options.force) {\n\t\t\t\t\tconsole.log(` Rolling back: ${record.name}...`);\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tawait client.query('BEGIN');\n\n\t\t\t\t\tfor (const stmt of downStmts) {\n\t\t\t\t\t\tawait client.query(stmt);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait d.removeMigrationRecord(client as unknown as Pool, record.name);\n\n\t\t\t\t\tawait client.query('COMMIT');\n\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\tlet rollbackCleanupError: unknown;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\trollbackCleanupError = e;\n\t\t\t\t\t}\n\t\t\t\t\tconst primary = sanitizePgError(rollbackError);\n\t\t\t\t\tif (rollbackCleanupError !== undefined) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t` Note: rollback also failed: ${sanitizePgError(rollbackCleanupError).message}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow primary;\n\t\t\t\t}\n\n\t\t\t\trolledBack++;\n\t\t\t\tconsole.log(` ✅ Rolled back: ${record.name}`);\n\t\t\t}\n\n\t\t\tconsole.log(`\\n✅ ${rolledBack} migration(s) rolled back successfully.`);\n\t\t});\n\t});\n}\n\nfunction runRollbackCommand(\n\tcountArg: string,\n\toptions: { db: string; dir: string; force?: boolean },\n): Promise<void> {\n\tconst count = Number.parseInt(countArg, 10);\n\tif (Number.isNaN(count) || count < 1) {\n\t\tconsole.error('❌ Count must be a positive integer');\n\t\tprocess.exit(1);\n\t}\n\n\treturn runMigrateAction(async () => {\n\t\tawait runRollback({ count, ...options });\n\t});\n}\n\n// ============================================================================\n// Subcommands\n// ============================================================================\n\nconst devCommand = new Command('dev')\n\t.description('Generate a migration from schema changes')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('-n, --name <description>', 'Migration description', 'migration')\n\t.option('--allow-destructive', 'Include destructive changes (drops)')\n\t.action(\n\t\t(options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdir: string;\n\t\t\tname: string;\n\t\t\tallowDestructive?: boolean;\n\t\t}) =>\n\t\t\trunMigrateAction(async () => {\n\t\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\t\tconsole.log(`📝 Generating migration: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\tconsole.log('');\n\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t...(loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}),\n\t\t\t\t\t\tonWarning: (message) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (diff.changes.length === 0) {\n\t\t\t\t\t\tconsole.log('✅ No changes detected — database matches schema.');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for destructive changes\n\t\t\t\t\tif (diff.hasDestructive && !options.allowDestructive) {\n\t\t\t\t\t\tconst destructive = diff.changes.filter((c) => c.destructive);\n\t\t\t\t\t\tthrow new MigrationError(\n\t\t\t\t\t\t\t`${destructive.length} destructive change(s) detected:\\n` +\n\t\t\t\t\t\t\t\tdestructive.map((c) => ` - ${c.details}`).join('\\n') +\n\t\t\t\t\t\t\t\t'\\n\\nUse --allow-destructive to include these changes.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sqlOptions = {\n\t\t\t\t\t\tincludeDestructive: options.allowDestructive ?? false,\n\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t};\n\n\t\t\t\t\tconst statements = generateMigrationSQL(diff, sqlOptions);\n\n\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t'✅ No migration needed — all changes are non-actionable.',\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Generate migration file with UP + DOWN sections\n\t\t\t\t\tconst existingFiles = scanMigrationFiles(options.dir).map(\n\t\t\t\t\t\t(f) => f.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst filename = generateMigrationFilename(\n\t\t\t\t\t\texistingFiles,\n\t\t\t\t\t\toptions.name,\n\t\t\t\t\t);\n\t\t\t\t\tconst content = generateMigrationFile(diff, {\n\t\t\t\t\t\t...sqlOptions,\n\t\t\t\t\t\tname: filename,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst file = writeMigrationFile(options.dir, filename, content);\n\n\t\t\t\t\tconsole.log(`✅ Migration created: ${file.path}`);\n\t\t\t\t\tconsole.log(` Statements: ${statements.length}`);\n\t\t\t\t\tconsole.log(` Checksum: ${file.checksum.slice(0, 12)}...`);\n\t\t\t\t});\n\t\t\t}),\n\t);\n\nconst applyCommand = new Command('apply')\n\t.description('Apply pending migrations')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--dry-run', 'Show pending migrations without applying')\n\t.action((options: ApplyOptions) =>\n\t\trunMigrateAction(async () => {\n\t\t\tawait runApply(options);\n\t\t}),\n\t);\n\nconst rollbackCommand = new Command('rollback')\n\t.description('Roll back applied migrations')\n\t.argument('<count>', 'Number of migrations to roll back')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--force', 'Force rollback of destructive or empty DOWN migrations')\n\t.action(runRollbackCommand);\n\nconst statusCommand = new Command('status')\n\t.description('Show migration status')\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--dir <path>', 'Migrations directory', DEFAULT_MIGRATIONS_DIR)\n\t.option('--json', 'Output as JSON')\n\t.action((options: { db: string; dir: string; json?: boolean }) =>\n\t\trunMigrateAction(async () => {\n\t\t\tawait withMigratePool(options.db, async (pool) => {\n\t\t\t\tawait ensureMigrationsTable(pool);\n\n\t\t\t\tconst applied = await getAppliedMigrations(pool);\n\t\t\t\tconst appliedMap = new Map(applied.map((m) => [m.name, m]));\n\n\t\t\t\tconst files = scanMigrationFiles(options.dir);\n\n\t\t\t\tconst statuses: Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tstatus: 'pending' | 'applied' | 'checksum_mismatch' | 'missing_file';\n\t\t\t\t\tappliedAt?: Date;\n\t\t\t\t}> = files.map((file) => {\n\t\t\t\t\tconst record = appliedMap.get(file.name);\n\t\t\t\t\tif (record === undefined) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'pending' as const,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tif (record.checksum !== file.checksum) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\t\tstatus: 'checksum_mismatch' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: file.name,\n\t\t\t\t\t\tstatus: 'applied' as const,\n\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// Also show applied migrations that don't have files anymore\n\t\t\t\tfor (const record of applied) {\n\t\t\t\t\tif (!files.some((f) => f.name === record.name)) {\n\t\t\t\t\t\tstatuses.push({\n\t\t\t\t\t\t\tname: record.name,\n\t\t\t\t\t\t\tstatus: 'missing_file' as const,\n\t\t\t\t\t\t\tappliedAt: record.appliedAt,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(JSON.stringify(statuses, null, 2));\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log('Migration Status');\n\t\t\t\t\tconsole.log(` Database: ${redactDbUrl(options.db)}`);\n\t\t\t\t\tconsole.log(` Directory: ${options.dir}`);\n\t\t\t\t\tconsole.log('');\n\n\t\t\t\t\tif (statuses.length === 0) {\n\t\t\t\t\t\tconsole.log('No migrations found.');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor (const s of statuses) {\n\t\t\t\t\t\t\tconst icon =\n\t\t\t\t\t\t\t\ts.status === 'applied'\n\t\t\t\t\t\t\t\t\t? '✅'\n\t\t\t\t\t\t\t\t\t: s.status === 'pending'\n\t\t\t\t\t\t\t\t\t\t? '⏳'\n\t\t\t\t\t\t\t\t\t\t: s.status === 'checksum_mismatch'\n\t\t\t\t\t\t\t\t\t\t\t? '⚠️'\n\t\t\t\t\t\t\t\t\t\t\t: '❓';\n\t\t\t\t\t\t\tconst detail =\n\t\t\t\t\t\t\t\t'appliedAt' in s && s.appliedAt\n\t\t\t\t\t\t\t\t\t? ` (applied: ${s.appliedAt.toISOString()})`\n\t\t\t\t\t\t\t\t\t: '';\n\t\t\t\t\t\t\tconsole.log(` ${icon} ${s.name} — ${s.status}${detail}`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst pendingCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'pending',\n\t\t\t\t\t\t).length;\n\t\t\t\t\t\tconst appliedCount = statuses.filter(\n\t\t\t\t\t\t\t(s) => s.status === 'applied',\n\t\t\t\t\t\t).length;\n\n\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`Total: ${statuses.length} | Applied: ${appliedCount} | Pending: ${pendingCount}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}),\n\t);\n\n// ============================================================================\n// Main Command\n// ============================================================================\n\nexport const migrateCommand = new Command('migrate')\n\t.description('Database migration management')\n\t.addCommand(devCommand)\n\t.addCommand(applyCommand)\n\t.addCommand(rollbackCommand)\n\t.addCommand(statusCommand);\n","/**\n * Migration File — File I/O, naming conventions, checksums.\n *\n * Handles reading/writing migration SQL files and computing\n * SHA-256 checksums for tamper detection.\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MigrationFile {\n\t/** Filename (e.g., \"0001_create_users.sql\") */\n\treadonly name: string;\n\t/** Full path to the file */\n\treadonly path: string;\n\t/** SQL content */\n\treadonly content: string;\n\t/** SHA-256 checksum of content */\n\treadonly checksum: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default directory for migration files */\nexport const DEFAULT_MIGRATIONS_DIR = 'migrations';\n\n/** Migration file naming pattern: NNNN_description.sql */\nconst MIGRATION_FILENAME_PATTERN = /^\\d{4}_[\\w-]+\\.sql$/;\n\n// ============================================================================\n// Checksum\n// ============================================================================\n\n/**\n * Compute SHA-256 checksum of content.\n */\nexport function computeChecksum(content: string): string {\n\treturn createHash('sha256').update(content, 'utf-8').digest('hex');\n}\n\n// ============================================================================\n// File Naming\n// ============================================================================\n\n/**\n * Generate the next migration filename.\n *\n * @param existingFiles - List of existing migration filenames\n * @param description - Human-readable description (e.g., \"create_users\")\n * @returns Filename (e.g., \"0001_create_users.sql\")\n */\nexport function generateMigrationFilename(\n\texistingFiles: readonly string[],\n\tdescription: string,\n): string {\n\tconst maxNum = existingFiles.reduce((max, f) => {\n\t\tconst match = f.match(/^(\\d{4})/);\n\t\treturn match?.[1] ? Math.max(max, Number.parseInt(match[1], 10)) : max;\n\t}, 0);\n\n\tconst nextNum = String(maxNum + 1).padStart(4, '0');\n\tconst sanitized = description\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9_-]/g, '_')\n\t\t.replace(/_+/g, '_')\n\t\t.replace(/^_|_$/g, '');\n\n\treturn `${nextNum}_${sanitized || 'migration'}.sql`;\n}\n\n// ============================================================================\n// File I/O\n// ============================================================================\n\n/**\n * Ensure the migrations directory exists.\n */\nexport function ensureMigrationsDir(dir: string): string {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\tmkdirSync(fullPath, { recursive: true });\n\t}\n\treturn fullPath;\n}\n\n/**\n * Scan migrations directory and return all valid migration files,\n * sorted by name (lexicographic = chronological with zero-padded numbers).\n */\nexport function scanMigrationFiles(dir: string): readonly MigrationFile[] {\n\tconst fullPath = resolve(dir);\n\tif (!existsSync(fullPath)) {\n\t\treturn [];\n\t}\n\n\tconst entries = readdirSync(fullPath)\n\t\t.filter((f) => MIGRATION_FILENAME_PATTERN.test(f))\n\t\t.sort();\n\n\treturn entries.map((name) => {\n\t\tconst filePath = join(fullPath, name);\n\t\tconst content = readFileSync(filePath, 'utf-8');\n\t\treturn {\n\t\t\tname,\n\t\t\tpath: filePath,\n\t\t\tcontent,\n\t\t\tchecksum: computeChecksum(content),\n\t\t};\n\t});\n}\n\n/**\n * Write a migration file to the migrations directory.\n *\n * @returns The created MigrationFile\n */\nexport function writeMigrationFile(\n\tdir: string,\n\tfilename: string,\n\tcontent: string,\n): MigrationFile {\n\tconst fullDir = ensureMigrationsDir(dir);\n\tconst filePath = join(fullDir, filename);\n\twriteFileSync(filePath, content, 'utf-8');\n\n\treturn {\n\t\tname: filename,\n\t\tpath: filePath,\n\t\tcontent,\n\t\tchecksum: computeChecksum(content),\n\t};\n}\n","/**\n * Push Command — Schema Provisioning\n *\n * dbsp push - Push schema to database.\n * Additive by default: only creates missing objects.\n * With --drop: recreates from scratch (preserves _dbsp_migrations).\n */\n\nimport type { SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n\tgenerateDDL,\n\tgenerateMigrationSQL,\n\tgetNamingPluginForDbCasing,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { executeDdl } from '../ddl-executor.js';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\n\n/** Table name reserved for migration history — never dropped by push. */\nconst MIGRATIONS_TABLE = '_dbsp_migrations';\n\nexport const pushCommand = new Command('push')\n\t.description('Push schema to database (additive by default)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--drop', 'Drop and recreate all objects (preserves migrations)')\n\t.option('--dry-run', 'Print SQL without executing')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tdrop?: boolean;\n\t\t\tdryRun?: boolean;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`🚀 Pushing schema: ${schemaPath}${options.drop ? ' (with --drop)' : ''}`,\n\t\t\t\t);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tif (options.dryRun) {\n\t\t\t\t\tconsole.log(` Mode: DRY RUN (no changes will be applied)`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (options.drop) {\n\t\t\t\t\t\t// --drop mode: generate full DDL with drops, excluding _dbsp_migrations\n\t\t\t\t\t\tconst statements = generateDDL(schemaModel, {\n\t\t\t\t\t\t\tincludeDropStatements: true,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t\t...(loaded.dbCasing !== undefined\n\t\t\t\t\t\t\t\t? { naming: getNamingPluginForDbCasing(loaded.dbCasing) }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst migrationsPattern =\n\t\t\t\t\t\t\tbuildMigrationsTableDropPattern(MIGRATIONS_TABLE);\n\t\t\t\t\t\tconst filtered = statements.filter(\n\t\t\t\t\t\t\t(stmt) => !migrationsPattern.test(stmt),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\toutputResult(filtered, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, filtered, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconst droppedTables = statements\n\t\t\t\t\t\t\t\t.filter((s) => /DROP\\s+TABLE/i.test(s))\n\t\t\t\t\t\t\t\t.filter((s) => !migrationsPattern.test(s))\n\t\t\t\t\t\t\t\t.map(extractDroppedTableName)\n\t\t\t\t\t\t\t\t.filter((t): t is string => t !== undefined);\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'dropped',\n\t\t\t\t\t\t\t\t\t\ttables: droppedTables,\n\t\t\t\t\t\t\t\t\t\ttablesDropped: droppedTables.length,\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statements executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Additive mode: live diff -> generate migration SQL (additive only)\n\t\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\t\tconst compareOptions = {\n\t\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t\t...(loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {}),\n\t\t\t\t\t\t\tonWarning: (message: string) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(\n\t\t\t\t\t\t\tadapter,\n\t\t\t\t\t\t\tschemaModel,\n\t\t\t\t\t\t\tcompareOptions,\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\t// Generate SQL for additive changes only (no destructive)\n\t\t\t\t\t\tconst statements = generateMigrationSQL(diff, {\n\t\t\t\t\t\t\tincludeDestructive: false,\n\t\t\t\t\t\t\t...(options.schemaName ? { schemaName: options.schemaName } : {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Collect warnings for skipped non-additive changes\n\t\t\t\t\t\tconst skippedChanges = diff.changes.filter((c) => c.destructive);\n\n\t\t\t\t\t\tif (!options.json && skippedChanges.length > 0) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`⚠️ ${skippedChanges.length} non-additive change(s) skipped:`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tfor (const change of skippedChanges) {\n\t\t\t\t\t\t\t\tconsole.log(` - ${change.details}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconsole.log('');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (statements.length === 0) {\n\t\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tstatus: 'up-to-date',\n\t\t\t\t\t\t\t\t\t\t\tstatementsExecuted: 0,\n\t\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tconsole.log('✅ Database is up to date — nothing to push.');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// EH-14: return instead of process.exit(0) so pool.end() in finally runs\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\toutputResult(statements, options);\n\n\t\t\t\t\t\tconst result = await executeDdl(pool, statements, {\n\t\t\t\t\t\t\t...(options.dryRun !== undefined\n\t\t\t\t\t\t\t\t? { dryRun: options.dryRun }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (!options.dryRun) {\n\t\t\t\t\t\t\tconst postApplyDiff = await comparePgsqlDatabaseSchema(\n\t\t\t\t\t\t\t\tadapter,\n\t\t\t\t\t\t\t\tschemaModel,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...compareOptions,\n\t\t\t\t\t\t\t\t\tpreviouslyAppliedDiff: diff,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tassertNoUnexpectedResidualDrift(postApplyDiff, skippedChanges);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstatus: options.dryRun ? 'dry-run' : 'applied',\n\t\t\t\t\t\t\t\t\t\tstatementsExecuted: result.statementsExecuted,\n\t\t\t\t\t\t\t\t\t\tskippedChanges: skippedChanges.length,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else if (!options.dryRun) {\n\t\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t\t`\\n✅ Push complete: ${result.statementsExecuted} statement(s) executed`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// EH-14: return so finally runs pool.end() before the outer success path\n\t\t\t\t} finally {\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n\n/**\n * Output SQL statements (dry-run or --json mode).\n */\n/**\n * Match a `DROP TABLE` statement that targets the migrations table, so `--drop`\n * can never destroy dbsp's own migration history.\n *\n * The table name is escaped before it reaches the pattern: an unescaped `.` or\n * `$` in it would otherwise match characters it should not, and a table whose\n * name merely resembles the migrations table would be spared — or worse, one\n * that should be spared would be dropped.\n *\n * The pattern matches the quoted name anywhere in the statement rather than\n * anchoring to the end. That is safe because `generateDDL` returns one statement\n * per array entry, so a match can never jump across a statement boundary.\n */\nexport function buildMigrationsTableDropPattern(tableName: string): RegExp {\n\tconst escaped = tableName.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\treturn new RegExp(\n\t\t`DROP\\\\s+TABLE(?:\\\\s+IF\\\\s+EXISTS)?(?:\\\\s+\"[^\"]*\"\\\\s*\\\\.)?\\\\s*\"${escaped}\"`,\n\t\t'i',\n\t);\n}\n\n/**\n * Read the table name out of a `DROP TABLE` statement for the JSON report.\n *\n * The name is the last quoted identifier, which skips the schema in\n * `\"public\".\"users\"`, and it may be followed by `CASCADE` before the semicolon.\n * Returns the statement itself when nothing matches, so the report never silently\n * loses a table.\n */\nexport function extractDroppedTableName(statement: string): string {\n\tconst match = statement.match(/\"([^\"]+)\"\\s*(?:CASCADE\\s*)?;?\\s*$/iu);\n\treturn match?.[1] ?? statement;\n}\n\nfunction outputResult(\n\tstatements: readonly string[],\n\toptions: { dryRun?: boolean; json?: boolean },\n): void {\n\tif (options.dryRun && !options.json) {\n\t\tconsole.log(`-- Dry run: ${statements.length} statement(s)\\n`);\n\t\tfor (const stmt of statements) {\n\t\t\tconsole.log(`${stmt};\\n`);\n\t\t}\n\t}\n}\n\nfunction assertNoUnexpectedResidualDrift(\n\tpostApplyDiff: SchemaDiff,\n\tskippedChanges: readonly SchemaChange[],\n): void {\n\tconst skippedKeys = new Set(skippedChanges.map(changeIdentity));\n\tconst unexpected = postApplyDiff.changes.filter(\n\t\t(change) =>\n\t\t\t!(change.destructive && skippedKeys.has(changeIdentity(change))),\n\t);\n\tif (unexpected.length === 0) return;\n\n\tthrow new Error(\n\t\t`Push did not converge after applying DDL; ${unexpected.length} outstanding change(s) remain:\\n` +\n\t\t\tunexpected.map(formatOutstandingChange).join('\\n'),\n\t);\n}\n\nfunction changeIdentity(change: SchemaChange): string {\n\treturn JSON.stringify([\n\t\tchange.kind,\n\t\tchange.table,\n\t\tchange.column ?? null,\n\t\tchange.details,\n\t\tchange.destructive,\n\t]);\n}\n\nfunction formatOutstandingChange(change: SchemaChange): string {\n\tconst target = change.column\n\t\t? `${change.table}.${change.column}`\n\t\t: change.table || 'schema';\n\treturn ` - ${change.details} (${change.kind}: ${target})`;\n}\n","/**\n * DDL Executor — Transaction-wrapped DDL execution.\n *\n * Shared by `dbsp push` and `dbsp migrate apply`.\n * Executes an array of SQL statements inside a single transaction.\n */\n\nimport type { Pool, PoolClient } from 'pg';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DdlExecutionResult {\n\t/** Number of statements executed */\n\tstatementsExecuted: number;\n\t/** Whether execution was a dry-run (no actual changes) */\n\tdryRun: boolean;\n}\n\n// ============================================================================\n// Executor\n// ============================================================================\n\n/**\n * Execute DDL statements in a transaction.\n *\n * @param pool - pg Pool instance\n * @param statements - SQL statements to execute\n * @param options - Execution options\n * @returns Execution result\n */\nexport async function executeDdl(\n\tpool: Pool,\n\tstatements: readonly string[],\n\toptions?: { dryRun?: boolean },\n): Promise<DdlExecutionResult> {\n\tif (statements.length === 0) {\n\t\treturn { statementsExecuted: 0, dryRun: options?.dryRun ?? false };\n\t}\n\n\tif (options?.dryRun) {\n\t\treturn { statementsExecuted: statements.length, dryRun: true };\n\t}\n\n\tlet client: PoolClient | undefined;\n\ttry {\n\t\tclient = await pool.connect();\n\t\tawait client.query('BEGIN');\n\n\t\tfor (const stmt of statements) {\n\t\t\tawait client.query(stmt);\n\t\t}\n\n\t\tawait client.query('COMMIT');\n\t\treturn { statementsExecuted: statements.length, dryRun: false };\n\t} catch (error) {\n\t\tif (client) {\n\t\t\tawait client.query('ROLLBACK');\n\t\t}\n\t\tthrow error;\n\t} finally {\n\t\tif (client) {\n\t\t\tclient.release();\n\t\t}\n\t}\n}\n","/**\n * DX-030 Block 1: REPL Command\n *\n * dbsp repl [--schema <path>] - Launch interactive REPL.\n * CLI-022: Batch mode support with --eval and --input options.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { DbCasing } from '@dbsp/types';\nimport { Command } from 'commander';\nimport { config } from '../config.js';\nimport { validateIdentifier } from '../utils/identifier-validation.js';\nimport { loadSchema, loadSchemaFromCwd } from '../utils/schema-loader.js';\n\nexport interface ReplOptions {\n\tschema?: string;\n\t/** CLI-020: Database connection URL for execution mode */\n\tdb?: string;\n\t/** CLI-022: Single query to evaluate (batch mode) */\n\teval?: string;\n\t/** CLI-022: File containing queries to execute (batch mode, one per line) */\n\tinput?: string;\n\t/** CLI-022: Output format for batch mode */\n\tformat?: 'text' | 'json';\n\t/** DEMO-E2E: Assertion file for validating query output */\n\tassert?: string;\n\t/** CLI-IMPORT: SQL files to import before queries (injected as .import commands) */\n\timport?: string[];\n\t/** CLI-USE: PostgreSQL schema to use (injected as .use command) */\n\tuse?: string;\n\t/** CLI-MUT: Start REPL with parse mode enabled */\n\tparse?: boolean;\n\t/** CLI-MUT: Start REPL with exec mode enabled */\n\texec?: boolean;\n\t/** CLI-CONFIG: Custom config file path (default: ~/.dbsp/config.json) */\n\tconfig?: string;\n\t/** CLI-CASING: Column naming convention (describes DB column casing) */\n\tcasing?: 'snake' | 'camel' | 'none';\n}\n\nexport const replCommand = new Command('repl')\n\t.description('Launch interactive REPL for exploring schema and queries')\n\t.option('-s, --schema <path>', 'Path to schema file (default: auto-detect)')\n\t.option(\n\t\t'-d, --db <url>',\n\t\t'PostgreSQL connection URL for execution mode (e.g., postgres://localhost/mydb)',\n\t)\n\t.option('-e, --eval <query>', 'Execute a single query and exit (batch mode)')\n\t.option(\n\t\t'-i, --input <file>',\n\t\t'Execute queries from file, one per line (batch mode)',\n\t)\n\t.option(\n\t\t'-f, --format <format>',\n\t\t'Output format for batch mode: text (default) or json',\n\t\t'text',\n\t)\n\t.option(\n\t\t'-a, --assert <file>',\n\t\t'Assertion file to validate query output (requires --input)',\n\t)\n\t.option(\n\t\t'--import <files...>',\n\t\t'SQL files to import before queries (equivalent to .import commands)',\n\t)\n\t.option(\n\t\t'--use <schema>',\n\t\t'PostgreSQL schema to use (equivalent to .use command)',\n\t)\n\t.option('--parse', 'Start REPL with parse mode enabled (.parse toggle)')\n\t.option('--exec', 'Start REPL with exec mode enabled (.exec toggle)')\n\t.option(\n\t\t'--casing <type>',\n\t\t'Column naming convention: snake (DB uses snake_case), camel (DB uses camelCase), none (preserve as-is)',\n\t)\n\t.option(\n\t\t'-c, --config <path>',\n\t\t'Custom config file path (default: ~/.dbsp/config.json)',\n\t)\n\t.action(async (options: ReplOptions) => {\n\t\t// Set custom config path if provided\n\t\tif (options.config) {\n\t\t\tconfig.setConfigPath(options.config);\n\t\t}\n\t\t// Load config\n\t\tconfig.load();\n\t\ttry {\n\t\t\t// Load schema\n\t\t\tlet schemaPath: string;\n\t\t\tlet schema: Awaited<ReturnType<typeof loadSchema>>;\n\n\t\t\tif (options.schema) {\n\t\t\t\tschema = await loadSchema(options.schema);\n\t\t\t\tschemaPath = options.schema;\n\t\t\t} else {\n\t\t\t\tconst result = await loadSchemaFromCwd();\n\t\t\t\tschema = result.schema;\n\t\t\t\tschemaPath = result.path;\n\t\t\t}\n\n\t\t\t// DEMO-E2E: Validate --assert requires --input\n\t\t\tif (options.assert && !options.input) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'--assert requires --input (assertion files validate query output from input files)',\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// CLI-IMPORT: Validate that --import requires batch mode (SQL file execution)\n\t\t\t// Note: --use, --parse, --exec work in interactive mode (REPL state setup)\n\t\t\tif (options.import && !options.eval && !options.input) {\n\t\t\t\tthrow new Error('--import requires batch mode (--eval or --input)');\n\t\t\t}\n\n\t\t\t// SEC: Validate --use schema name at the entry point so both the batch path\n\t\t\t// (which injects `.use <schema>`) and the interactive path (which passes\n\t\t\t// initialSchemaName) are protected against SQL injection.\n\t\t\tif (options.use) {\n\t\t\t\tvalidateIdentifier(options.use, 'schema');\n\t\t\t}\n\n\t\t\t// CLI-CASING: explicit flag wins; otherwise use the schema declaration.\n\t\t\tconst dbCasing = mapReplCasingOption(options.casing) ?? schema.dbCasing;\n\n\t\t\t// CLI-022: Batch mode - execute queries without interactive UI\n\t\t\tif (options.eval || options.input) {\n\t\t\t\tconst { runBatchMode } = await import('../repl/batch.js');\n\t\t\t\tconst queries: string[] = [];\n\n\t\t\t\t// CLI-USE: Inject .use command first (schema scoping)\n\t\t\t\tif (options.use) {\n\t\t\t\t\tqueries.push(`.use ${options.use}`);\n\t\t\t\t}\n\n\t\t\t\t// CLI-IMPORT: Inject .import commands for SQL files\n\t\t\t\tif (options.import) {\n\t\t\t\t\tfor (const file of options.import) {\n\t\t\t\t\t\tqueries.push(`.import ${file}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (options.eval) {\n\t\t\t\t\tqueries.push(options.eval);\n\t\t\t\t}\n\n\t\t\t\tif (options.input) {\n\t\t\t\t\t// EH-2: Map ENOENT to a friendly error instead of raw stack trace\n\t\t\t\t\tlet content: string;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcontent = readFileSync(options.input, 'utf-8');\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconst isNotFound =\n\t\t\t\t\t\t\terr instanceof Error &&\n\t\t\t\t\t\t\t'code' in err &&\n\t\t\t\t\t\t\t(err as NodeJS.ErrnoException).code === 'ENOENT';\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\tisNotFound\n\t\t\t\t\t\t\t\t? `Input file not found: ${options.input}`\n\t\t\t\t\t\t\t\t: `Failed to read input file: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tqueries.push(...content.split('\\n'));\n\t\t\t\t}\n\n\t\t\t\tawait runBatchMode({\n\t\t\t\t\tqueries,\n\t\t\t\t\tschema,\n\t\t\t\t\tschemaPath,\n\t\t\t\t\tformat: options.format ?? 'text',\n\t\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t\t...(options.assert && { assertFile: options.assert }),\n\t\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Dynamic import to avoid loading React/Ink for other commands\n\t\t\tconst { startRepl } = await import('../repl/index.js');\n\t\t\t// CLI-020: Pass database URL if provided\n\t\t\t// CLI-MUT: Pass initial REPL state options\n\t\t\tawait startRepl({\n\t\t\t\tschema,\n\t\t\t\tschemaPath,\n\t\t\t\t...(options.db && { databaseUrl: options.db }),\n\t\t\t\t...(options.use && { initialSchemaName: options.use }),\n\t\t\t\t...(options.parse && { initialParseMode: true }),\n\t\t\t\t...(options.exec && { initialExecMode: true }),\n\t\t\t\t...(dbCasing && { dbCasing }),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconsole.error(`❌ ${message}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t});\n\nfunction mapReplCasingOption(\n\tcasing: ReplOptions['casing'],\n): DbCasing | undefined {\n\tswitch (casing) {\n\t\tcase 'snake':\n\t\t\treturn 'snake_case';\n\t\tcase 'camel':\n\t\t\treturn 'camelCase';\n\t\tcase 'none':\n\t\t\treturn 'preserve';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n","/**\n * Verify Command — Schema Drift Detection\n *\n * dbsp verify - Compare schema vs real database using the comparison engine.\n * Detects drift in tables, columns, types, nullable, defaults, FKs, indexes.\n */\n\nimport {\n\tcomparePgsqlDatabaseSchema,\n\tcreatePgsqlAdapter,\n} from '@dbsp/adapter-pgsql';\nimport { Command } from 'commander';\nimport { createDbConnection, redactDbUrl } from '../utils/db-utils.js';\nimport { loadSchema } from '../utils/schema-loader.js';\nimport { formatVerifyResult, verifyFromDiff } from '../verifier.js';\n\nexport const verifyCommand = new Command('verify')\n\t.description('Compare schema vs real database (drift detection)')\n\t.option(\n\t\t'-s, --schema <path>',\n\t\t'Path to schema file (default: dbsp.schema.ts)',\n\t)\n\t.requiredOption('-d, --db <url>', 'Database connection URL (required)')\n\t.option('--schema-name <name>', 'Database schema name (default: public)')\n\t.option('--json', 'Output as JSON')\n\t.action(\n\t\tasync (options: {\n\t\t\tschema?: string;\n\t\t\tdb: string;\n\t\t\tschemaName?: string;\n\t\t\tjson?: boolean;\n\t\t}) => {\n\t\t\tconst schemaPath = options.schema ?? 'dbsp.schema.ts';\n\n\t\t\tconst redactedUrl = redactDbUrl(options.db);\n\n\t\t\tif (!options.json) {\n\t\t\t\tconsole.log(`🔍 Verifying schema: ${schemaPath}`);\n\t\t\t\tconsole.log(` Database: ${redactedUrl}`);\n\t\t\t\tif (options.schemaName) {\n\t\t\t\t\tconsole.log(` Schema: ${options.schemaName}`);\n\t\t\t\t}\n\t\t\t\tconsole.log('');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Load schema from file → ModelIR\n\t\t\t\tconst loaded = await loadSchema(schemaPath);\n\t\t\t\tconst schemaModel = loaded.model;\n\n\t\t\t\t// Connect to database\n\t\t\t\tconst { pool } = await createDbConnection(options.db);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst adapter = createPgsqlAdapter(pool);\n\t\t\t\t\t// Live diff: introspect database and canonicalise PostgreSQL CHECK\n\t\t\t\t\t// expressions before comparing.\n\t\t\t\t\tconst diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t\t...(loaded.dbCasing !== undefined\n\t\t\t\t\t\t\t? { dbCasing: loaded.dbCasing }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\tonWarning: (message) => console.warn(`⚠️ ${message}`),\n\t\t\t\t\t});\n\n\t\t\t\t\t// Convert to verify result\n\t\t\t\t\t// Keep the legacy reporting fields sourced from introspection so\n\t\t\t\t\t// --json consumers continue to see database table names as before.\n\t\t\t\t\tconst dbModel = await adapter.introspect({\n\t\t\t\t\t\t...(options.schemaName ? { schema: options.schemaName } : {}),\n\t\t\t\t\t});\n\t\t\t\t\tconst schemaTables = Array.from(schemaModel.tables.keys());\n\t\t\t\t\tconst dbTables = Array.from(dbModel.tables.keys());\n\t\t\t\t\tconst result = verifyFromDiff(diff, schemaTables, dbTables);\n\n\t\t\t\t\t// Output\n\t\t\t\t\tif (options.json) {\n\t\t\t\t\t\t// Exclude the full diff meta from JSON output (too verbose)\n\t\t\t\t\t\tconst { diff: _diff, ...jsonResult } = result;\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\tJSON.stringify(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t...jsonResult,\n\t\t\t\t\t\t\t\t\tsummary: diff.summary,\n\t\t\t\t\t\t\t\t\thasDestructive: diff.hasDestructive,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.log(formatVerifyResult(result));\n\t\t\t\t\t}\n\n\t\t\t\t\t// EH-14: set exit code; let finally run pool.end() before process exits\n\t\t\t\t\tprocess.exitCode = result.valid ? 0 : 1;\n\t\t\t\t\treturn;\n\t\t\t\t} finally {\n\t\t\t\t\t// Close database connection\n\t\t\t\t\tawait pool.end();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error occurred';\n\t\t\t\t// CC-2+EH-7: If --json, error goes to stdout as JSON; otherwise stderr\n\t\t\t\tif (options.json) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\tJSON.stringify({ status: 'error', error: message }, null, 2),\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(`❌ Error: ${message}`);\n\t\t\t\t}\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t},\n\t);\n","/**\n * Schema Verifier — Drift Detection via Comparison Engine\n *\n * Compares schema definition against real database using the\n * adapter's compareSchemata engine for full drift detection.\n *\n * Detects: tables, columns, types, nullable, defaults, FKs, indexes, PKs.\n */\n\nimport type { ChangeKind, SchemaChange, SchemaDiff } from '@dbsp/adapter-pgsql';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DriftSeverity = 'error' | 'warning' | 'info';\n\nexport type DriftType =\n\t// Tables\n\t| 'missing_table_in_db'\n\t| 'missing_table_in_schema'\n\t// Columns\n\t| 'missing_column_in_db'\n\t| 'missing_column_in_schema'\n\t| 'type_mismatch'\n\t| 'nullable_mismatch'\n\t| 'default_mismatch'\n\t| 'unique_mismatch'\n\t// Constraints\n\t| 'primary_key_mismatch'\n\t| 'missing_fk_in_db'\n\t| 'missing_fk_in_schema'\n\t| 'fk_on_delete_mismatch'\n\t// Indexes\n\t| 'missing_index_in_db'\n\t| 'missing_index_in_schema'\n\t// CHECK constraints\n\t| 'missing_check_in_db'\n\t| 'missing_check_in_schema'\n\t// ENUM types\n\t| 'missing_enum_in_db'\n\t| 'missing_enum_in_schema'\n\t| 'enum_value_mismatch'\n\t// Column enhancements\n\t| 'collation_mismatch'\n\t| 'identity_mismatch'\n\t// Comments\n\t| 'comment_mismatch'\n\t// Extensions & Sequences\n\t| 'missing_extension'\n\t| 'missing_sequence'\n\t| 'sequence_mismatch'\n\t// Constraints\n\t| 'constraint_validation_mismatch'\n\t// Row-Level Security\n\t| 'rls_enabled_mismatch'\n\t| 'missing_policy_in_db'\n\t| 'missing_policy_in_schema';\n\nexport interface DriftIssue {\n\t/** Issue severity */\n\tseverity: DriftSeverity;\n\t/** Human-readable message */\n\tmessage: string;\n\t/** Table affected (if any) */\n\ttable?: string;\n\t/** Column affected (if any) */\n\tcolumn?: string;\n\t/** Issue type for programmatic handling */\n\ttype: DriftType;\n}\n\nexport interface VerifyResult {\n\t/** Whether the schema matches the database */\n\tvalid: boolean;\n\t/** List of drift issues found */\n\tissues: DriftIssue[];\n\t/** Tables in schema */\n\tschemaTables: string[];\n\t/** Tables in database */\n\tdbTables: string[];\n\t/** Structured diff (for programmatic consumers) */\n\tdiff: SchemaDiff;\n}\n\n// ============================================================================\n// Change → Drift Mapping\n// ============================================================================\n\nconst CHANGE_TO_DRIFT: Record<\n\tChangeKind,\n\t{ type: DriftType; severity: DriftSeverity }\n> = {\n\tcreate_table: { type: 'missing_table_in_db', severity: 'error' },\n\tdrop_table: { type: 'missing_table_in_schema', severity: 'warning' },\n\tadd_column: { type: 'missing_column_in_db', severity: 'error' },\n\tdrop_column: { type: 'missing_column_in_schema', severity: 'info' },\n\talter_column_type: { type: 'type_mismatch', severity: 'error' },\n\talter_column_nullable: { type: 'nullable_mismatch', severity: 'warning' },\n\talter_column_default: { type: 'default_mismatch', severity: 'warning' },\n\talter_column_unique: { type: 'unique_mismatch', severity: 'warning' },\n\tadd_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tdrop_primary_key: { type: 'primary_key_mismatch', severity: 'error' },\n\tadd_foreign_key: { type: 'missing_fk_in_db', severity: 'error' },\n\tdrop_foreign_key: { type: 'missing_fk_in_schema', severity: 'warning' },\n\talter_foreign_key: { type: 'fk_on_delete_mismatch', severity: 'warning' },\n\tcreate_index: { type: 'missing_index_in_db', severity: 'warning' },\n\tdrop_index: { type: 'missing_index_in_schema', severity: 'info' },\n\t// CHECK constraints\n\tadd_check_constraint: { type: 'missing_check_in_db', severity: 'warning' },\n\tdrop_check_constraint: { type: 'missing_check_in_schema', severity: 'info' },\n\t// ENUM types\n\tcreate_enum: { type: 'missing_enum_in_db', severity: 'error' },\n\talter_enum_add_value: { type: 'enum_value_mismatch', severity: 'warning' },\n\tdrop_enum: { type: 'missing_enum_in_schema', severity: 'warning' },\n\t// Column enhancements\n\talter_column_collation: { type: 'collation_mismatch', severity: 'warning' },\n\talter_column_identity: { type: 'identity_mismatch', severity: 'warning' },\n\t// Comments\n\tadd_comment: { type: 'comment_mismatch', severity: 'info' },\n\tdrop_comment: { type: 'comment_mismatch', severity: 'info' },\n\t// Extensions & Sequences\n\tcreate_extension: { type: 'missing_extension', severity: 'error' },\n\tdrop_extension: { type: 'missing_extension', severity: 'info' },\n\tcreate_sequence: { type: 'missing_sequence', severity: 'warning' },\n\talter_sequence: { type: 'sequence_mismatch', severity: 'warning' },\n\tdrop_sequence: { type: 'missing_sequence', severity: 'info' },\n\t// Constraints\n\tvalidate_constraint: {\n\t\ttype: 'constraint_validation_mismatch',\n\t\tseverity: 'warning',\n\t},\n\t// Row-Level Security\n\tenable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tdisable_rls: { type: 'rls_enabled_mismatch', severity: 'warning' },\n\tcreate_policy: { type: 'missing_policy_in_db', severity: 'warning' },\n\tdrop_policy: { type: 'missing_policy_in_schema', severity: 'warning' },\n};\n\nfunction changeToDriftIssue(change: SchemaChange): DriftIssue {\n\tconst mapping = CHANGE_TO_DRIFT[change.kind] ?? {\n\t\ttype: 'type_mismatch' as DriftType,\n\t\tseverity: 'warning' as DriftSeverity,\n\t};\n\treturn {\n\t\tseverity: mapping.severity,\n\t\ttype: mapping.type,\n\t\ttable: change.table,\n\t\t...(change.column !== undefined ? { column: change.column } : {}),\n\t\tmessage: change.details,\n\t};\n}\n\n// ============================================================================\n// Verification (from SchemaDiff)\n// ============================================================================\n\n/**\n * Convert a SchemaDiff into a VerifyResult.\n *\n * @param diff - Structured diff from compareSchemata()\n * @param schemaTables - Table names in schema (for backward compat)\n * @param dbTables - Table names in database (for backward compat)\n */\nexport function verifyFromDiff(\n\tdiff: SchemaDiff,\n\tschemaTables: string[],\n\tdbTables: string[],\n): VerifyResult {\n\tconst issues = diff.changes.map(changeToDriftIssue);\n\n\t// Sort by severity (error > warning > info)\n\tconst severityOrder: Record<DriftSeverity, number> = {\n\t\terror: 0,\n\t\twarning: 1,\n\t\tinfo: 2,\n\t};\n\tissues.sort(\n\t\t(a: DriftIssue, b: DriftIssue) =>\n\t\t\tseverityOrder[a.severity] - severityOrder[b.severity],\n\t);\n\n\tconst hasErrors = issues.some((i: DriftIssue) => i.severity === 'error');\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tissues,\n\t\tschemaTables,\n\t\tdbTables,\n\t\tdiff,\n\t};\n}\n\n// ============================================================================\n// Format\n// ============================================================================\n\n/**\n * Format verification result for CLI output.\n */\nexport function formatVerifyResult(result: VerifyResult): string {\n\tconst lines: string[] = [];\n\n\tif (result.valid) {\n\t\tlines.push('✅ Schema matches database');\n\t} else {\n\t\tlines.push('❌ Schema drift detected');\n\t}\n\n\tlines.push('');\n\tlines.push(`Tables in schema: ${result.schemaTables.length}`);\n\tlines.push(`Tables in database: ${result.dbTables.length}`);\n\tlines.push('');\n\n\tif (result.issues.length === 0) {\n\t\tlines.push('No issues found.');\n\t} else {\n\t\tconst errors = result.issues.filter((i) => i.severity === 'error');\n\t\tconst warnings = result.issues.filter((i) => i.severity === 'warning');\n\t\tconst infos = result.issues.filter((i) => i.severity === 'info');\n\n\t\tif (errors.length > 0) {\n\t\t\tlines.push(`❌ ${errors.length} error(s):`);\n\t\t\tfor (const issue of errors) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (warnings.length > 0) {\n\t\t\tlines.push(`⚠️ ${warnings.length} warning(s):`);\n\t\t\tfor (const issue of warnings) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\n\t\tif (infos.length > 0) {\n\t\t\tlines.push(`ℹ️ ${infos.length} info:`);\n\t\t\tfor (const issue of infos) {\n\t\t\t\tlines.push(` ${issue.message}`);\n\t\t\t}\n\t\t\tlines.push('');\n\t\t}\n\t}\n\n\treturn lines.join('\\n');\n}\n"],"mappings":"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","names":["Command","outputPath","mkdirSync","writeFileSync","dirname","resolve","Command","Command","resolve","mkdirSync","dirname","writeFileSync","Command","mkdirSync","writeFileSync","resolve","Command","comparePgsqlDatabaseSchema","createPgsqlAdapter","generateMigrationSQL","Command","Command","createPgsqlAdapter","comparePgsqlDatabaseSchema","generateMigrationSQL","readFileSync","Command","Command","readFileSync","comparePgsqlDatabaseSchema","createPgsqlAdapter","Command","Command","createPgsqlAdapter","comparePgsqlDatabaseSchema","Command"]}
|
package/dist/repl/batch.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IntentSummary, ModelIR, AssertionSummary } from '@dbsp/core';
|
|
2
|
-
import { LoadedSchema } from '@dbsp/types';
|
|
2
|
+
import { LoadedSchema as LoadedSchema$1 } from '@dbsp/types';
|
|
3
|
+
import { LoadedSchema } from '../utils/schema-loader.js';
|
|
3
4
|
import pg from 'pg';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -276,7 +277,7 @@ declare function processDotCommand(input: string, schema: LoadedSchema, state: B
|
|
|
276
277
|
|
|
277
278
|
interface BatchModeOptions {
|
|
278
279
|
queries: string[];
|
|
279
|
-
schema: LoadedSchema;
|
|
280
|
+
schema: LoadedSchema$1;
|
|
280
281
|
schemaPath: string;
|
|
281
282
|
format: 'text' | 'json';
|
|
282
283
|
databaseUrl?: string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { LoadedSchema } from '@dbsp/types';
|
|
2
|
-
export { LoadedSchema } from '@dbsp/types';
|
|
1
|
+
import { LoadedSchema as LoadedSchema$1, DbCasing } from '@dbsp/types';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Schema Loader Utility
|
|
@@ -14,6 +13,9 @@ export { LoadedSchema } from '@dbsp/types';
|
|
|
14
13
|
* See ARCH-004 for analysis of this intentional duplication.
|
|
15
14
|
*/
|
|
16
15
|
|
|
16
|
+
interface LoadedSchema extends LoadedSchema$1 {
|
|
17
|
+
readonly dbCasing?: DbCasing;
|
|
18
|
+
}
|
|
17
19
|
/**
|
|
18
20
|
* Default schema file names to search for.
|
|
19
21
|
*/
|
|
@@ -44,4 +46,4 @@ declare function loadSchemaFromCwd(cwd?: string): Promise<{
|
|
|
44
46
|
path: string;
|
|
45
47
|
}>;
|
|
46
48
|
|
|
47
|
-
export { DEFAULT_SCHEMA_FILES, SchemaLoadError, findSchemaFile, loadSchema, loadSchemaFromCwd };
|
|
49
|
+
export { DEFAULT_SCHEMA_FILES, type LoadedSchema, SchemaLoadError, findSchemaFile, loadSchema, loadSchemaFromCwd };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbsp/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI tools for db-semantic-planner — REPL, schema generation, DDL provisioning",
|
|
5
5
|
"author": "Olivier Orabona <oorabona@users.noreply.github.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"commander": "^15.0.0",
|
|
61
61
|
"ink": "^7.1.0",
|
|
62
62
|
"react": "^19.2.7",
|
|
63
|
-
"@dbsp/
|
|
64
|
-
"@dbsp/
|
|
65
|
-
"@dbsp/
|
|
66
|
-
"@dbsp/types": "
|
|
63
|
+
"@dbsp/adapter-pgsql": "3.1.0",
|
|
64
|
+
"@dbsp/nql": "1.10.0",
|
|
65
|
+
"@dbsp/core": "3.1.0",
|
|
66
|
+
"@dbsp/types": "3.1.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"pg": "^8.16.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/schema-loader.ts"],"sourcesContent":["/**\n * Schema Loader Utility\n *\n * Loads dbsp.schema.ts files using tsx for TypeScript support.\n * Falls back to native import for .js files.\n *\n * NOTE: Similar code exists in packages/mcp-server/src/schema-loader.ts\n * The MCP version has additional security features (path traversal protection,\n * allowedRoots, error codes) that are required for network-exposed services.\n * This CLI version is simpler as it runs locally with user trust.\n * See ARCH-004 for analysis of this intentional duplication.\n */\n\nimport { existsSync, realpathSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { LoadedSchema } from '@dbsp/types';\nimport { isValidSchema } from '@dbsp/types';\n\nexport type { LoadedSchema } from '@dbsp/types';\n\n/**\n * Default schema file names to search for.\n */\nexport const DEFAULT_SCHEMA_FILES = [\n\t'dbsp.schema.ts',\n\t'dbsp.schema.js',\n\t'schema.ts',\n\t'schema.js',\n];\n\n/**\n * Error thrown when schema loading fails.\n */\nexport class SchemaLoadError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'SchemaLoadError';\n\t}\n}\n\n/**\n * Find the schema file in the given directory.\n */\nexport function findSchemaFile(cwd: string): string | null {\n\tfor (const file of DEFAULT_SCHEMA_FILES) {\n\t\tconst fullPath = resolve(cwd, file);\n\t\tif (existsSync(fullPath)) {\n\t\t\treturn fullPath;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Load a schema from a TypeScript or JavaScript file.\n *\n * ARCH-005: Only accepts schema() results.\n * For TypeScript files, this uses tsx loader via dynamic import.\n */\nexport async function loadSchema(schemaPath: string): Promise<LoadedSchema> {\n\tconst resolvedPath = resolve(schemaPath);\n\n\t// SEC-8: Prevent path traversal — schema must be under cwd.\n\t// This check runs before existsSync so traversal attempts are caught\n\t// regardless of whether the file exists.\n\tconst cwd = resolve(process.cwd());\n\tif (!resolvedPath.startsWith(cwd + sep)) {\n\t\tthrow new SchemaLoadError(\n\t\t\t`Schema file must be inside the current working directory: ${resolvedPath}`,\n\t\t);\n\t}\n\n\t// SEC-8b: Symlink bypass — a symlink inside cwd can point outside cwd.\n\t// Resolve symlinks and re-check containment. Use try/catch because\n\t// realpathSync throws ENOENT for non-existent paths; fall through to the\n\t// existsSync check below in that case (non-existent file still blocked).\n\ttry {\n\t\tconst realPath = realpathSync(resolvedPath);\n\t\tconst realCwd = realpathSync(cwd);\n\t\tif (!realPath.startsWith(realCwd + sep)) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Schema file must be inside the current working directory: ${resolvedPath}`,\n\t\t\t);\n\t\t}\n\t} catch (e) {\n\t\tif (e instanceof SchemaLoadError) {\n\t\t\tthrow e;\n\t\t}\n\t\t// Re-throw non-ENOENT errors (EACCES, ELOOP, EPERM, EIO, etc.) so real\n\t\t// filesystem problems are not silently swallowed. Only ENOENT is expected\n\t\t// here — the file simply doesn't exist yet; existsSync below will handle it.\n\t\tif ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\tif (!existsSync(resolvedPath)) {\n\t\tthrow new SchemaLoadError(`Schema file not found: ${resolvedPath}`);\n\t}\n\n\ttry {\n\t\t// For TypeScript files, we need tsx to be in the loader chain\n\t\t// This works when running via `tsx` or when tsx is registered\n\t\tconst fileUrl = pathToFileURL(resolvedPath).href;\n\t\tconst module = await import(fileUrl);\n\n\t\t// Look for schema export (named 'schema' or default)\n\t\tconst schema = module.schema ?? module.default;\n\n\t\tif (!schema) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Schema file must export 'schema' or default export: ${resolvedPath}`,\n\t\t\t);\n\t\t}\n\n\t\t// ARCH-005: Validate schema() format\n\t\tif (!isValidSchema(schema)) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Invalid schema format in ${resolvedPath}. ` +\n\t\t\t\t\t`Use schema() from @dbsp/core to create schemas.`,\n\t\t\t);\n\t\t}\n\n\t\treturn schema;\n\t} catch (error) {\n\t\tif (error instanceof SchemaLoadError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t// Provide helpful error for TypeScript files\n\t\tif (\n\t\t\tresolvedPath.endsWith('.ts') &&\n\t\t\tmessage.includes('Cannot find module')\n\t\t) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Failed to load TypeScript schema. Make sure 'tsx' is installed:\\n` +\n\t\t\t\t\t` pnpm add -D tsx\\n\\n` +\n\t\t\t\t\t`Then run dbsp via tsx:\\n` +\n\t\t\t\t\t` pnpm tsx node_modules/.bin/dbsp generate ddl\\n\\n` +\n\t\t\t\t\t`Original error: ${message}`,\n\t\t\t);\n\t\t}\n\n\t\tthrow new SchemaLoadError(`Failed to load schema: ${message}`);\n\t}\n}\n\n/**\n * Load schema from the current working directory.\n * Searches for default schema file names.\n */\nexport async function loadSchemaFromCwd(cwd: string = process.cwd()): Promise<{\n\tschema: LoadedSchema;\n\tpath: string;\n}> {\n\tconst schemaPath = findSchemaFile(cwd);\n\n\tif (!schemaPath) {\n\t\tthrow new SchemaLoadError(\n\t\t\t`No schema file found in ${cwd}.\\n` +\n\t\t\t\t`Expected one of: ${DEFAULT_SCHEMA_FILES.join(', ')}`,\n\t\t);\n\t}\n\n\tconst schema = await loadSchema(schemaPath);\n\treturn { schema, path: schemaPath };\n}\n"],"mappings":";AAaA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,WAAW;AAC7B,SAAS,qBAAqB;AAE9B,SAAS,qBAAqB;AAOvB,IAAM,uBAAuB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAKO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAC1C,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAKO,SAAS,eAAe,KAA4B;AAC1D,aAAW,QAAQ,sBAAsB;AACxC,UAAM,WAAW,QAAQ,KAAK,IAAI;AAClC,QAAI,WAAW,QAAQ,GAAG;AACzB,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO;AACR;AAQA,eAAsB,WAAW,YAA2C;AAC3E,QAAM,eAAe,QAAQ,UAAU;AAKvC,QAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC;AACjC,MAAI,CAAC,aAAa,WAAW,MAAM,GAAG,GAAG;AACxC,UAAM,IAAI;AAAA,MACT,6DAA6D,YAAY;AAAA,IAC1E;AAAA,EACD;AAMA,MAAI;AACH,UAAM,WAAW,aAAa,YAAY;AAC1C,UAAM,UAAU,aAAa,GAAG;AAChC,QAAI,CAAC,SAAS,WAAW,UAAU,GAAG,GAAG;AACxC,YAAM,IAAI;AAAA,QACT,6DAA6D,YAAY;AAAA,MAC1E;AAAA,IACD;AAAA,EACD,SAAS,GAAG;AACX,QAAI,aAAa,iBAAiB;AACjC,YAAM;AAAA,IACP;AAIA,QAAK,EAA4B,SAAS,UAAU;AACnD,YAAM;AAAA,IACP;AAAA,EACD;AAEA,MAAI,CAAC,WAAW,YAAY,GAAG;AAC9B,UAAM,IAAI,gBAAgB,0BAA0B,YAAY,EAAE;AAAA,EACnE;AAEA,MAAI;AAGH,UAAM,UAAU,cAAc,YAAY,EAAE;AAC5C,UAAM,SAAS,MAAM,OAAO;AAG5B,UAAM,SAAS,OAAO,UAAU,OAAO;AAEvC,QAAI,CAAC,QAAQ;AACZ,YAAM,IAAI;AAAA,QACT,uDAAuD,YAAY;AAAA,MACpE;AAAA,IACD;AAGA,QAAI,CAAC,cAAc,MAAM,GAAG;AAC3B,YAAM,IAAI;AAAA,QACT,4BAA4B,YAAY;AAAA,MAEzC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,SAAS,OAAO;AACf,QAAI,iBAAiB,iBAAiB;AACrC,YAAM;AAAA,IACP;AAEA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAGrE,QACC,aAAa,SAAS,KAAK,KAC3B,QAAQ,SAAS,oBAAoB,GACpC;AACD,YAAM,IAAI;AAAA,QACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAIoB,OAAO;AAAA,MAC5B;AAAA,IACD;AAEA,UAAM,IAAI,gBAAgB,0BAA0B,OAAO,EAAE;AAAA,EAC9D;AACD;AAMA,eAAsB,kBAAkB,MAAc,QAAQ,IAAI,GAG/D;AACF,QAAM,aAAa,eAAe,GAAG;AAErC,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI;AAAA,MACT,2BAA2B,GAAG;AAAA,mBACT,qBAAqB,KAAK,IAAI,CAAC;AAAA,IACrD;AAAA,EACD;AAEA,QAAM,SAAS,MAAM,WAAW,UAAU;AAC1C,SAAO,EAAE,QAAQ,MAAM,WAAW;AACnC;","names":[]}
|