mgcheck 0.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/LICENSE +21 -0
- package/README.md +416 -0
- package/dist/activity-log-ETNHCZ7B.js +13 -0
- package/dist/activity-log-ETNHCZ7B.js.map +1 -0
- package/dist/applier-TLGJ6SZ2.js +9 -0
- package/dist/applier-TLGJ6SZ2.js.map +1 -0
- package/dist/chunk-3G3R2NM3.js +73 -0
- package/dist/chunk-3G3R2NM3.js.map +1 -0
- package/dist/chunk-4YTQB62X.js +261 -0
- package/dist/chunk-4YTQB62X.js.map +1 -0
- package/dist/chunk-7ENQ5WVM.js +97 -0
- package/dist/chunk-7ENQ5WVM.js.map +1 -0
- package/dist/chunk-7JBFSZBD.js +1212 -0
- package/dist/chunk-7JBFSZBD.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/installer-KGWDJ6OR.js +323 -0
- package/dist/installer-KGWDJ6OR.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +158 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/detector/index.ts","../src/detector/raw-sql.ts","../src/detector/prisma.ts","../src/detector/drizzle.ts","../src/analyzer/rules/types.ts","../src/analyzer/rules/create-index-not-concurrent.ts","../src/analyzer/rules/alter-column-type.ts","../src/analyzer/rules/add-column-not-null-no-default.ts","../src/analyzer/rules/add-constraint-not-valid.ts","../src/analyzer/rules/drop-column.ts","../src/analyzer/rules/drop-table.ts","../src/analyzer/rules/rename-column.ts","../src/analyzer/rules/rename-table.ts","../src/analyzer/rules/missing-lock-timeout.ts","../src/analyzer/rules/missing-down-migration.ts","../src/analyzer/rules/index.ts","../src/analyzer/parser.ts","../src/analyzer/index.ts","../src/shadow/docker-postgres.ts","../src/shadow/pglite.ts","../src/shadow/index.ts","../src/core/engine.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { MgcheckConfig, RuleSeverity } from './types.js';\n\n/** Default configuration values */\nconst DEFAULT_CONFIG: MgcheckConfig = {\n shadowDb: {\n provider: 'docker',\n dockerImage: 'postgres:16-alpine',\n },\n rules: {},\n output: {\n format: 'terminal',\n verbose: false,\n },\n confirmDestructive: false,\n};\n\n/** Config file names to search for, in priority order */\nconst CONFIG_FILES = [\n '.mgcheckrc.json',\n '.mgcheckrc.yml',\n '.mgcheckrc',\n 'mgcheck.config.json',\n];\n\n/**\n * Load configuration from file, env vars, and CLI overrides.\n * Priority: CLI flags > env vars > config file > defaults\n */\nexport function loadConfig(overrides: Partial<MgcheckConfig> = {}, configPath?: string): MgcheckConfig {\n // 1. Start with defaults\n let config: MgcheckConfig = structuredClone(DEFAULT_CONFIG);\n\n // 2. Load from config file\n const fileConfig = loadConfigFile(configPath);\n if (fileConfig) {\n config = mergeConfig(config, fileConfig);\n }\n\n // 3. Apply environment variables\n config = applyEnvVars(config);\n\n // 4. Apply CLI overrides (highest priority)\n config = mergeConfig(config, overrides);\n\n return config;\n}\n\n/** Search for and load a config file */\nfunction loadConfigFile(explicitPath?: string): Partial<MgcheckConfig> | null {\n if (explicitPath) {\n const fullPath = resolve(explicitPath);\n if (!existsSync(fullPath)) {\n throw new Error(`Config file not found: ${fullPath}`);\n }\n return parseConfigFile(fullPath);\n }\n\n // Auto-detect config file in current directory\n for (const name of CONFIG_FILES) {\n const fullPath = resolve(process.cwd(), name);\n if (existsSync(fullPath)) {\n return parseConfigFile(fullPath);\n }\n }\n\n return null;\n}\n\n/** Parse a JSON config file */\nfunction parseConfigFile(filePath: string): Partial<MgcheckConfig> {\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as Partial<MgcheckConfig>;\n } catch (err) {\n throw new Error(`Failed to parse config file ${filePath}: ${(err as Error).message}`);\n }\n}\n\n/** Apply environment variables to config */\nfunction applyEnvVars(config: MgcheckConfig): MgcheckConfig {\n const result = structuredClone(config);\n\n if (process.env.MGCHECK_DB_URL) {\n result.shadowDb.dbUrl = process.env.MGCHECK_DB_URL;\n }\n\n if (process.env.MGCHECK_DOCKER_IMAGE) {\n result.shadowDb.dockerImage = process.env.MGCHECK_DOCKER_IMAGE;\n }\n\n if (process.env.MGCHECK_PROVIDER) {\n const provider = process.env.MGCHECK_PROVIDER;\n if (provider === 'docker' || provider === 'pglite') {\n result.shadowDb.provider = provider;\n }\n }\n\n // v1: LLM config\n if (process.env.MGCHECK_LLM_PROVIDER || process.env.MGCHECK_LLM_API_KEY) {\n result.llm = {\n provider: process.env.MGCHECK_LLM_PROVIDER || 'openai',\n model: process.env.MGCHECK_LLM_MODEL || 'gpt-4o',\n apiKey: process.env.MGCHECK_LLM_API_KEY || '',\n maxFixAttempts: 1,\n };\n }\n\n return result;\n}\n\n/** Deep merge two config objects, b takes precedence */\nfunction mergeConfig(a: MgcheckConfig, b: Partial<MgcheckConfig>): MgcheckConfig {\n const result = structuredClone(a);\n\n if (b.shadowDb) {\n Object.assign(result.shadowDb, b.shadowDb);\n }\n\n if (b.rules) {\n Object.assign(result.rules, b.rules);\n }\n\n if (b.output) {\n Object.assign(result.output, b.output);\n }\n\n if (b.confirmDestructive !== undefined) {\n result.confirmDestructive = b.confirmDestructive;\n }\n\n if (b.llm) {\n result.llm = { ...result.llm, ...b.llm };\n }\n\n return result;\n}\n\n/**\n * Get the effective severity for a rule, considering config overrides.\n * Returns null if the rule is disabled ('off').\n */\nexport function getRuleSeverity(\n ruleId: string,\n defaultSeverity: RuleSeverity,\n config: MgcheckConfig\n): RuleSeverity | null {\n const override = config.rules[ruleId];\n if (override === 'off') return null;\n if (override === 'error' || override === 'warning') return override;\n return defaultSeverity;\n}\n","import { statSync, existsSync, readdirSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\nimport type { DetectedMigration } from '../core/types.js';\nimport { detectRawSql, detectRawSqlDirectory } from './raw-sql.js';\nimport { detectPrisma, isPrismaDirectory } from './prisma.js';\nimport { detectDrizzle, isDrizzleDirectory } from './drizzle.js';\n\n/**\n * Auto-detect the migration format from a given path.\n *\n * Detection logic:\n * File (.sql) → Raw SQL\n * Directory:\n * → Contains migration.sql or subdirs with migration.sql → Prisma\n * → Contains meta/_journal.json → Drizzle\n * → Contains *.sql files → Raw SQL directory\n * → Otherwise: error\n */\nexport function detectMigration(inputPath: string): DetectedMigration {\n const fullPath = resolve(inputPath);\n\n if (!existsSync(fullPath)) {\n throw new Error(`Path does not exist: ${fullPath}`);\n }\n\n const stat = statSync(fullPath);\n\n // Single file\n if (stat.isFile()) {\n if (!fullPath.endsWith('.sql')) {\n throw new Error(\n `Unsupported file type: ${fullPath}. Expected a .sql file.`\n );\n }\n return detectRawSql(fullPath);\n }\n\n // Directory\n if (stat.isDirectory()) {\n // Check Prisma first (more specific pattern)\n if (isPrismaDirectory(fullPath)) {\n return detectPrisma(fullPath);\n }\n\n // Check Drizzle (has meta/_journal.json)\n if (isDrizzleDirectory(fullPath)) {\n return detectDrizzle(fullPath);\n }\n\n // Check for raw SQL files\n const entries = readdirSync(fullPath);\n const sqlFiles = entries\n .filter((f) => f.endsWith('.sql'))\n .map((f) => join(fullPath, f));\n\n if (sqlFiles.length > 0) {\n return detectRawSqlDirectory(sqlFiles);\n }\n\n throw new Error(\n `No migration files found in ${fullPath}. Expected .sql files, a Prisma migration directory, or a Drizzle migration directory.`\n );\n }\n\n throw new Error(`Unsupported path type: ${fullPath}`);\n}\n\nexport { detectRawSql, detectRawSqlDirectory } from './raw-sql.js';\nexport { detectPrisma, isPrismaDirectory } from './prisma.js';\nexport { detectDrizzle, isDrizzleDirectory } from './drizzle.js';\n","import { readFileSync } from 'node:fs';\nimport { basename } from 'node:path';\nimport type { DetectedMigration, MigrationFile } from '../core/types.js';\n\n/**\n * Handle raw .sql files — single file or directory of SQL files.\n */\nexport function detectRawSql(filePath: string): DetectedMigration {\n const sql = readFileSync(filePath, 'utf-8');\n const file: MigrationFile = {\n path: filePath,\n sql,\n order: 0,\n };\n\n return {\n format: 'raw-sql',\n files: [file],\n hasDownMigration: false,\n basePath: filePath,\n };\n}\n\n/**\n * Handle a directory of raw SQL files.\n * Files are sorted by name (alphabetical/numeric).\n */\nexport function detectRawSqlDirectory(files: string[]): DetectedMigration {\n const sqlFiles = files\n .filter((f) => f.endsWith('.sql'))\n .sort()\n .map((filePath, index) => ({\n path: filePath,\n sql: readFileSync(filePath, 'utf-8'),\n order: index,\n }));\n\n // Check for down migration patterns\n const hasDown = sqlFiles.some(\n (f) =>\n basename(f.path).toLowerCase().includes('down') ||\n basename(f.path).toLowerCase().includes('rollback')\n );\n\n return {\n format: 'raw-sql',\n files: sqlFiles,\n hasDownMigration: hasDown,\n basePath: sqlFiles[0]?.path || '',\n };\n}\n","import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';\nimport { join, basename, dirname } from 'node:path';\nimport type { DetectedMigration, MigrationFile } from '../core/types.js';\n\n/**\n * Detect and parse a Prisma migration directory.\n * \n * Prisma structure:\n * prisma/migrations/\n * 20240301120000_add_user_status/\n * migration.sql\n * 20240302130000_add_billing/\n * migration.sql\n */\nexport function detectPrisma(dirPath: string): DetectedMigration {\n const migrationSqlPath = join(dirPath, 'migration.sql');\n\n // Case 1: Pointed directly at a specific migration directory\n if (existsSync(migrationSqlPath)) {\n const sql = readFileSync(migrationSqlPath, 'utf-8');\n const hasDown = existsSync(join(dirPath, 'down.sql'));\n\n return {\n format: 'prisma',\n files: [{ path: migrationSqlPath, sql, order: 0 }],\n hasDownMigration: hasDown,\n basePath: dirPath,\n };\n }\n\n // Case 2: Pointed at the migrations root (prisma/migrations/)\n const files: MigrationFile[] = [];\n const entries = readdirSync(dirPath)\n .filter((entry) => {\n const entryPath = join(dirPath, entry);\n return statSync(entryPath).isDirectory();\n })\n .sort(); // Timestamp-prefixed dirs sort chronologically\n\n let hasDown = false;\n\n for (let i = 0; i < entries.length; i++) {\n const entryDir = join(dirPath, entries[i]);\n const sqlFile = join(entryDir, 'migration.sql');\n\n if (existsSync(sqlFile)) {\n files.push({\n path: sqlFile,\n sql: readFileSync(sqlFile, 'utf-8'),\n order: i,\n });\n\n if (existsSync(join(entryDir, 'down.sql'))) {\n hasDown = true;\n }\n }\n }\n\n return {\n format: 'prisma',\n files,\n hasDownMigration: hasDown,\n basePath: dirPath,\n };\n}\n\n/**\n * Check if a directory looks like a Prisma migration directory.\n */\nexport function isPrismaDirectory(dirPath: string): boolean {\n // Direct migration directory: contains migration.sql\n if (existsSync(join(dirPath, 'migration.sql'))) {\n return true;\n }\n\n // Migrations root: subdirectories contain migration.sql\n try {\n const entries = readdirSync(dirPath);\n return entries.some((entry) => {\n const entryPath = join(dirPath, entry);\n return (\n statSync(entryPath).isDirectory() &&\n existsSync(join(entryPath, 'migration.sql'))\n );\n });\n } catch {\n return false;\n }\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { DetectedMigration, MigrationFile } from '../core/types.js';\n\ninterface DrizzleJournal {\n version: string;\n dialect: string;\n entries: Array<{\n idx: number;\n version: string;\n when: number;\n tag: string;\n breakpoints: boolean;\n }>;\n}\n\n/**\n * Detect and parse a Drizzle migration directory.\n *\n * Drizzle structure:\n * drizzle/\n * 0001_init.sql\n * 0002_add_billing.sql\n * meta/\n * _journal.json\n * 0001_snapshot.json\n * 0002_snapshot.json\n */\nexport function detectDrizzle(dirPath: string): DetectedMigration {\n const journalPath = join(dirPath, 'meta', '_journal.json');\n const files: MigrationFile[] = [];\n\n if (existsSync(journalPath)) {\n // Use journal for ordering\n const journal: DrizzleJournal = JSON.parse(\n readFileSync(journalPath, 'utf-8')\n );\n\n for (const entry of journal.entries) {\n const sqlFile = join(dirPath, `${entry.tag}.sql`);\n if (existsSync(sqlFile)) {\n files.push({\n path: sqlFile,\n sql: readFileSync(sqlFile, 'utf-8'),\n order: entry.idx,\n });\n }\n }\n } else {\n // Fallback: read SQL files sorted by name\n const sqlFiles = readdirSync(dirPath)\n .filter((f) => f.endsWith('.sql'))\n .sort();\n\n for (let i = 0; i < sqlFiles.length; i++) {\n const filePath = join(dirPath, sqlFiles[i]);\n files.push({\n path: filePath,\n sql: readFileSync(filePath, 'utf-8'),\n order: i,\n });\n }\n }\n\n return {\n format: 'drizzle',\n files,\n hasDownMigration: false, // Drizzle doesn't natively support down migrations\n basePath: dirPath,\n };\n}\n\n/**\n * Check if a directory looks like a Drizzle migration directory.\n */\nexport function isDrizzleDirectory(dirPath: string): boolean {\n return existsSync(join(dirPath, 'meta', '_journal.json'));\n}\n","import type { Rule, RuleViolation, ParsedStatement, RuleContext, RuleSeverity } from '../../core/types.js';\n\nexport type { Rule, RuleViolation, ParsedStatement, RuleContext };\n\n/**\n * Helper: Check if an AST node matches a specific statement type.\n * pgsql-parser wraps statements as { RawStmt: { stmt: { <Type>: {...} } } }\n */\nexport function getStmtType(ast: any): string | null {\n if (!ast) return null;\n\n const stmt = ast.RawStmt?.stmt || ast.stmt || ast;\n const keys = Object.keys(stmt);\n return keys.length > 0 ? keys[0] : null;\n}\n\n/**\n * Helper: Extract the inner statement node from a pgsql-parser AST node.\n */\nexport function getStmtNode(ast: any): any {\n if (!ast) return null;\n const stmt = ast.RawStmt?.stmt || ast.stmt || ast;\n const type = getStmtType(ast);\n return type ? stmt[type] : null;\n}\n\n/**\n * Helper: Check if raw SQL matches a pattern (case-insensitive).\n * Used as fallback when AST is not available.\n */\nexport function sqlMatches(raw: string, pattern: RegExp): boolean {\n return pattern.test(raw);\n}\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG001: CREATE INDEX without CONCURRENTLY\n *\n * CREATE INDEX acquires a SHARE lock, blocking writes for the entire\n * index build. On large tables this can block INSERT/UPDATE/DELETE\n * for minutes or hours. CONCURRENTLY builds the index without holding\n * a long-duration lock.\n */\nexport const createIndexNotConcurrent: Rule = {\n id: 'MG001',\n name: 'create-index-not-concurrent',\n severity: 'error',\n description: 'CREATE INDEX without CONCURRENTLY locks the table for writes during index build',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'IndexStmt') {\n const node = getStmtNode(stmt.ast);\n if (node && !node.concurrent) {\n isViolation = true;\n }\n }\n } else {\n // Regex fallback\n const pattern = /CREATE\\s+INDEX\\b(?!\\s+CONCURRENTLY)/i;\n const uniquePattern = /CREATE\\s+UNIQUE\\s+INDEX\\b(?!\\s+CONCURRENTLY)/i;\n if (\n (sqlMatches(stmt.raw, pattern) || sqlMatches(stmt.raw, uniquePattern)) &&\n !sqlMatches(stmt.raw, /CREATE\\s+(UNIQUE\\s+)?INDEX\\s+CONCURRENTLY/i)\n ) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG001',\n ruleName: 'create-index-not-concurrent',\n severity: 'error',\n message:\n 'CREATE INDEX without CONCURRENTLY acquires a SHARE lock on the table, ' +\n 'blocking all INSERT, UPDATE, and DELETE operations for the entire duration ' +\n 'of the index build. On a table with millions of rows, this can block writes ' +\n 'for minutes or hours.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'Use CREATE INDEX CONCURRENTLY instead.\\n' +\n 'Note: CONCURRENTLY cannot run inside a transaction block, so each index must ' +\n 'be created in its own migration or with explicit transaction control.',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG002: ALTER TABLE ... ALTER COLUMN ... TYPE\n *\n * Changing a column's data type almost always requires a full table rewrite,\n * acquiring an ACCESS EXCLUSIVE lock that blocks ALL access (reads + writes)\n * for the duration. On large tables this can cause minutes of complete\n * downtime.\n */\nexport const alterColumnType: Rule = {\n id: 'MG002',\n name: 'alter-column-type',\n severity: 'error',\n description: 'ALTER COLUMN TYPE causes a full table rewrite with ACCESS EXCLUSIVE lock',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'AlterTableStmt') {\n const node = getStmtNode(stmt.ast);\n if (node?.cmds) {\n for (const cmd of node.cmds) {\n const alterCmd = cmd.AlterTableCmd;\n // AT_AlterColumnType = 25 in Postgres internals\n // subtype field maps to the AT_* enum\n if (\n alterCmd &&\n (alterCmd.subtype === 'AT_AlterColumnType' ||\n alterCmd.subtype === 25)\n ) {\n isViolation = true;\n }\n }\n }\n }\n } else {\n // Regex fallback\n if (sqlMatches(stmt.raw, /ALTER\\s+TABLE\\b.*\\bALTER\\s+COLUMN\\b.*\\bTYPE\\b/i)) {\n isViolation = true;\n }\n // Also catch: ALTER TABLE ... ALTER ... SET DATA TYPE\n if (sqlMatches(stmt.raw, /ALTER\\s+TABLE\\b.*\\bSET\\s+DATA\\s+TYPE\\b/i)) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG002',\n ruleName: 'alter-column-type',\n severity: 'error',\n message:\n 'ALTER COLUMN TYPE triggers a full table rewrite, acquiring an ACCESS EXCLUSIVE lock ' +\n 'that blocks ALL operations (including SELECT) for the entire duration. On a table ' +\n 'with millions of rows, this can cause minutes of complete downtime.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'Use the expand/contract pattern instead:\\n' +\n '1. Add a new column with the desired type\\n' +\n '2. Backfill data from the old column to the new one (in batches)\\n' +\n '3. Update application code to use the new column\\n' +\n '4. Drop the old column in a subsequent migration',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG003: ADD COLUMN ... NOT NULL without DEFAULT\n *\n * Adding a NOT NULL column without a DEFAULT requires PostgreSQL to rewrite\n * the entire table (pre-PG11). Even on PG11+ where DEFAULT is metadata-only,\n * omitting the DEFAULT means every existing row fails the NOT NULL check\n * unless the table is empty.\n */\nexport const addColumnNotNullNoDefault: Rule = {\n id: 'MG003',\n name: 'add-column-not-null-no-default',\n severity: 'error',\n description: 'ADD COLUMN NOT NULL without DEFAULT causes table rewrite or failure on non-empty tables',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'AlterTableStmt') {\n const node = getStmtNode(stmt.ast);\n if (node?.cmds) {\n for (const cmd of node.cmds) {\n const alterCmd = cmd.AlterTableCmd;\n // AT_AddColumn\n if (\n alterCmd &&\n (alterCmd.subtype === 'AT_AddColumn' || alterCmd.subtype === 0)\n ) {\n const colDef = alterCmd.def?.ColumnDef || alterCmd.def;\n if (colDef) {\n const hasNotNull = colDef.constraints?.some(\n (c: any) =>\n c.Constraint?.contype === 'CONSTR_NOTNULL' ||\n c.Constraint?.contype === 1\n );\n const hasDefault = colDef.constraints?.some(\n (c: any) =>\n c.Constraint?.contype === 'CONSTR_DEFAULT' ||\n c.Constraint?.contype === 2\n );\n\n if (hasNotNull && !hasDefault) {\n isViolation = true;\n }\n }\n }\n }\n }\n }\n } else {\n // Regex fallback: match ADD COLUMN with NOT NULL but no DEFAULT\n if (\n sqlMatches(stmt.raw, /ADD\\s+COLUMN\\b.*\\bNOT\\s+NULL\\b/i) &&\n !sqlMatches(stmt.raw, /\\bDEFAULT\\b/i)\n ) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG003',\n ruleName: 'add-column-not-null-no-default',\n severity: 'error',\n message:\n 'Adding a NOT NULL column without a DEFAULT value will fail on any non-empty table ' +\n '(existing rows cannot satisfy the NOT NULL constraint). On PostgreSQL < 11, even ' +\n 'with a DEFAULT, this triggers a full table rewrite. On PG 11+, constant DEFAULT ' +\n 'values are metadata-only and safe.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'Safe approach (works on all PG versions):\\n' +\n '1. ADD COLUMN name type (nullable, no constraint)\\n' +\n '2. UPDATE table SET name = default_value WHERE name IS NULL (backfill in batches)\\n' +\n '3. ALTER COLUMN name SET NOT NULL\\n\\n' +\n 'Or on PG 11+:\\n' +\n ' ADD COLUMN name type NOT NULL DEFAULT \\'value\\'',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { sqlMatches } from './types.js';\n\n/**\n * MG004: ADD CONSTRAINT without NOT VALID\n *\n * Adding a CHECK or FOREIGN KEY constraint without NOT VALID requires\n * PostgreSQL to validate all existing rows immediately, holding an\n * ACCESS EXCLUSIVE lock for the duration. The safe pattern is to add\n * the constraint as NOT VALID first, then VALIDATE CONSTRAINT separately\n * (which only takes a SHARE UPDATE EXCLUSIVE lock).\n */\nexport const addConstraintNotValid: Rule = {\n id: 'MG004',\n name: 'add-constraint-not-valid',\n severity: 'warning',\n description: 'ADD CONSTRAINT without NOT VALID causes full table scan under heavy lock',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n // Check for ADD CONSTRAINT ... CHECK or FOREIGN KEY without NOT VALID\n const hasAddConstraint = sqlMatches(stmt.raw, /ADD\\s+CONSTRAINT\\b/i);\n const hasCheckOrFK =\n sqlMatches(stmt.raw, /\\bCHECK\\s*\\(/i) ||\n sqlMatches(stmt.raw, /\\bFOREIGN\\s+KEY\\b/i);\n const hasNotValid = sqlMatches(stmt.raw, /\\bNOT\\s+VALID\\b/i);\n\n if (hasAddConstraint && hasCheckOrFK && !hasNotValid) {\n violations.push({\n ruleId: 'MG004',\n ruleName: 'add-constraint-not-valid',\n severity: 'warning',\n message:\n 'Adding a CHECK or FOREIGN KEY constraint without NOT VALID causes PostgreSQL to ' +\n 'scan and validate every existing row while holding an ACCESS EXCLUSIVE lock. On ' +\n 'large tables, this blocks all access for the duration of the scan.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'Split into two steps:\\n' +\n '1. ALTER TABLE t ADD CONSTRAINT c CHECK (expr) NOT VALID;\\n' +\n ' (instant, only applies to new/updated rows)\\n' +\n '2. ALTER TABLE t VALIDATE CONSTRAINT c;\\n' +\n ' (validates existing rows with a weaker SHARE UPDATE EXCLUSIVE lock)',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG005: DROP COLUMN\n *\n * Dropping a column is a destructive, irreversible operation that\n * permanently removes data. This always requires explicit confirmation.\n */\nexport const dropColumn: Rule = {\n id: 'MG005',\n name: 'drop-column',\n severity: 'error',\n description: 'DROP COLUMN is destructive and irreversible — requires --confirm-destructive',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'AlterTableStmt') {\n const node = getStmtNode(stmt.ast);\n if (node?.cmds) {\n for (const cmd of node.cmds) {\n const alterCmd = cmd.AlterTableCmd;\n // AT_DropColumn\n if (\n alterCmd &&\n (alterCmd.subtype === 'AT_DropColumn' || alterCmd.subtype === 12)\n ) {\n isViolation = true;\n }\n }\n }\n }\n } else {\n if (sqlMatches(stmt.raw, /ALTER\\s+TABLE\\b.*\\bDROP\\s+COLUMN\\b/i)) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG005',\n ruleName: 'drop-column',\n severity: 'error',\n message:\n '⚠️ DESTRUCTIVE: DROP COLUMN permanently removes the column and all its data. ' +\n 'This operation is irreversible. Any application code still referencing this column ' +\n 'will break immediately.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'Before dropping a column in production:\\n' +\n '1. Remove all application code references to the column first\\n' +\n '2. Deploy the code change\\n' +\n '3. Only then drop the column in a separate migration\\n' +\n '4. Use --confirm-destructive to proceed with this migration',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG006: DROP TABLE\n *\n * Dropping a table permanently deletes the table and ALL its data.\n * This is the most destructive migration operation possible.\n */\nexport const dropTable: Rule = {\n id: 'MG006',\n name: 'drop-table',\n severity: 'error',\n description: 'DROP TABLE is destructive and irreversible — requires --confirm-destructive',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'DropStmt') {\n const node = getStmtNode(stmt.ast);\n if (node && (node.removeType === 'OBJECT_TABLE' || node.removeType === 38)) {\n isViolation = true;\n }\n }\n } else {\n if (sqlMatches(stmt.raw, /DROP\\s+TABLE\\b/i)) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG006',\n ruleName: 'drop-table',\n severity: 'error',\n message:\n '⚠️ DESTRUCTIVE: DROP TABLE permanently deletes the table and ALL its data. ' +\n 'This operation is irreversible. All foreign keys, indexes, triggers, and ' +\n 'policies associated with this table will also be removed.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'Before dropping a table in production:\\n' +\n '1. Ensure no application code references this table\\n' +\n '2. Consider renaming the table first (e.g., _deprecated_tablename)\\n' +\n '3. Wait a safe period to confirm nothing breaks\\n' +\n '4. Back up the data if it might be needed later\\n' +\n '5. Use --confirm-destructive to proceed',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG007: RENAME COLUMN\n *\n * Renaming a column is a breaking change for zero-downtime deployments.\n * Old code still referencing the original column name will fail\n * immediately after the rename.\n */\nexport const renameColumn: Rule = {\n id: 'MG007',\n name: 'rename-column',\n severity: 'warning',\n description: 'RENAME COLUMN is a breaking change for zero-downtime deployments',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'RenameStmt') {\n const node = getStmtNode(stmt.ast);\n // OBJECT_COLUMN rename\n if (\n node &&\n (node.renameType === 'OBJECT_COLUMN' || node.renameType === 3)\n ) {\n isViolation = true;\n }\n }\n } else {\n if (sqlMatches(stmt.raw, /ALTER\\s+TABLE\\b.*\\bRENAME\\s+COLUMN\\b/i)) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG007',\n ruleName: 'rename-column',\n severity: 'warning',\n message:\n 'Renaming a column is a breaking change for zero-downtime deployments. Any running ' +\n 'application code, ORM queries, or reports referencing the old column name will fail ' +\n 'immediately after the rename is applied.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'For zero-downtime rename, use the expand/contract pattern:\\n' +\n '1. Add a new column with the desired name\\n' +\n '2. Write to both old and new columns in application code\\n' +\n '3. Backfill existing data to the new column\\n' +\n '4. Switch reads to the new column\\n' +\n '5. Stop writing to the old column\\n' +\n '6. Drop the old column in a subsequent migration',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { getStmtType, getStmtNode, sqlMatches } from './types.js';\n\n/**\n * MG008: RENAME TABLE\n *\n * Renaming a table is a breaking change for zero-downtime deployments.\n * All application code, views, foreign keys, and queries referencing\n * the old table name will fail immediately.\n */\nexport const renameTable: Rule = {\n id: 'MG008',\n name: 'rename-table',\n severity: 'warning',\n description: 'RENAME TABLE is a breaking change for zero-downtime deployments',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n for (const stmt of statements) {\n let isViolation = false;\n\n if (stmt.ast) {\n const type = getStmtType(stmt.ast);\n if (type === 'RenameStmt') {\n const node = getStmtNode(stmt.ast);\n // OBJECT_TABLE rename\n if (\n node &&\n (node.renameType === 'OBJECT_TABLE' || node.renameType === 38)\n ) {\n isViolation = true;\n }\n }\n } else {\n if (sqlMatches(stmt.raw, /ALTER\\s+TABLE\\b.*\\bRENAME\\s+TO\\b/i)) {\n isViolation = true;\n }\n }\n\n if (isViolation) {\n violations.push({\n ruleId: 'MG008',\n ruleName: 'rename-table',\n severity: 'warning',\n message:\n 'Renaming a table is a breaking change for zero-downtime deployments. All ' +\n 'application code, views, foreign keys, and queries referencing the old table name ' +\n 'will fail immediately after the rename.',\n line: stmt.line,\n sql: stmt.raw,\n suggestion:\n 'For zero-downtime rename:\\n' +\n '1. Create a new table with the desired name\\n' +\n '2. Create a VIEW with the old name pointing to the new table\\n' +\n '3. Migrate application code to use the new table name\\n' +\n '4. Drop the view and old table in a subsequent migration',\n });\n }\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\nimport { sqlMatches } from './types.js';\n\n/**\n * MG009: Missing lock_timeout guard\n *\n * Risky DDL operations (CREATE INDEX, ALTER TABLE, etc.) should be preceded\n * by SET lock_timeout to prevent the migration from waiting indefinitely\n * for a lock. Without lock_timeout, a migration can trigger a \"lock queue\n * death spiral\" where the waiting lock request blocks all subsequent queries.\n */\nexport const missingLockTimeout: Rule = {\n id: 'MG009',\n name: 'missing-lock-timeout',\n severity: 'warning',\n description: 'No lock_timeout or statement_timeout set before risky operations',\n\n check(statements: ParsedStatement[], _context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n // Check if any statement sets lock_timeout or statement_timeout\n const hasLockTimeout = statements.some((s) =>\n sqlMatches(s.raw, /SET\\s+(LOCAL\\s+)?lock_timeout/i)\n );\n const hasStatementTimeout = statements.some((s) =>\n sqlMatches(s.raw, /SET\\s+(LOCAL\\s+)?statement_timeout/i)\n );\n\n if (hasLockTimeout || hasStatementTimeout) {\n return violations;\n }\n\n // Check if there are any risky operations that would benefit from a timeout\n const riskyPatterns = [\n /CREATE\\s+INDEX\\b/i,\n /ALTER\\s+TABLE\\b/i,\n /DROP\\s+INDEX\\b/i,\n ];\n\n const hasRiskyOp = statements.some((s) =>\n riskyPatterns.some((p) => sqlMatches(s.raw, p))\n );\n\n if (hasRiskyOp) {\n violations.push({\n ruleId: 'MG009',\n ruleName: 'missing-lock-timeout',\n severity: 'warning',\n message:\n 'This migration contains DDL operations that acquire locks but does not set a ' +\n 'lock_timeout. Without a lock_timeout, if another transaction holds a conflicting ' +\n 'lock, your migration will wait indefinitely — and all subsequent queries will ' +\n 'queue behind it, potentially causing a cascading outage (lock queue death spiral).',\n line: 1,\n sql: _context.fullSql.substring(0, 200) + '...',\n suggestion:\n 'Add a lock timeout at the beginning of your migration:\\n' +\n ' SET lock_timeout = \\'5s\\';\\n\\n' +\n 'This causes the migration to fail fast if it cannot acquire the lock within 5 seconds, ' +\n 'rather than waiting indefinitely and blocking other queries. You can retry the migration ' +\n 'during a quieter period.',\n });\n }\n\n return violations;\n },\n};\n","import type { Rule, ParsedStatement, RuleViolation, RuleContext } from './types.js';\n\n/**\n * MG010: Missing down/rollback migration\n *\n * Best practice is to include a rollback migration alongside every\n * \"up\" migration. This allows quick recovery if a migration causes\n * problems in production.\n */\nexport const missingDownMigration: Rule = {\n id: 'MG010',\n name: 'missing-down-migration',\n severity: 'warning',\n description: 'No rollback/down migration detected',\n\n check(_statements: ParsedStatement[], context: RuleContext): RuleViolation[] {\n const violations: RuleViolation[] = [];\n\n // Only check for frameworks that support down migrations\n // Drizzle doesn't natively support them, so skip\n if (context.format === 'drizzle') {\n return violations;\n }\n\n if (!context.hasDownMigration) {\n violations.push({\n ruleId: 'MG010',\n ruleName: 'missing-down-migration',\n severity: 'warning',\n message:\n 'No rollback/down migration was detected. Without a rollback migration, reverting ' +\n 'this change in production requires manual intervention. This is especially risky ' +\n 'for schema changes that are difficult to reverse (e.g., column type changes).',\n line: 1,\n sql: '(no down migration file found)',\n suggestion:\n 'Create a corresponding rollback migration:\\n' +\n '• For Prisma: Add a down.sql file in the same migration directory\\n' +\n '• For raw SQL: Create a corresponding *_down.sql or *_rollback.sql file\\n\\n' +\n 'The rollback should reverse the changes made by this migration. For example:\\n' +\n ' Up: ALTER TABLE users ADD COLUMN status TEXT;\\n' +\n ' Down: ALTER TABLE users DROP COLUMN status;',\n });\n }\n\n return violations;\n },\n};\n","import type { Rule } from './types.js';\nimport { createIndexNotConcurrent } from './create-index-not-concurrent.js';\nimport { alterColumnType } from './alter-column-type.js';\nimport { addColumnNotNullNoDefault } from './add-column-not-null-no-default.js';\nimport { addConstraintNotValid } from './add-constraint-not-valid.js';\nimport { dropColumn } from './drop-column.js';\nimport { dropTable } from './drop-table.js';\nimport { renameColumn } from './rename-column.js';\nimport { renameTable } from './rename-table.js';\nimport { missingLockTimeout } from './missing-lock-timeout.js';\nimport { missingDownMigration } from './missing-down-migration.js';\n\n/** All built-in rules, in order */\nexport const ALL_RULES: Rule[] = [\n createIndexNotConcurrent, // MG001\n alterColumnType, // MG002\n addColumnNotNullNoDefault, // MG003\n addConstraintNotValid, // MG004\n dropColumn, // MG005\n dropTable, // MG006\n renameColumn, // MG007\n renameTable, // MG008\n missingLockTimeout, // MG009\n missingDownMigration, // MG010\n];\n\n/** IDs of rules that flag destructive operations */\nexport const DESTRUCTIVE_RULE_IDS = new Set(['MG005', 'MG006']);\n\n/** Get a rule by its ID */\nexport function getRuleById(id: string): Rule | undefined {\n return ALL_RULES.find((r) => r.id === id);\n}\n\n/** Get all rule IDs */\nexport function getAllRuleIds(): string[] {\n return ALL_RULES.map((r) => r.id);\n}\n\n// Re-export all rules\nexport {\n createIndexNotConcurrent,\n alterColumnType,\n addColumnNotNullNoDefault,\n addConstraintNotValid,\n dropColumn,\n dropTable,\n renameColumn,\n renameTable,\n missingLockTimeout,\n missingDownMigration,\n};\n","import type { ParsedStatement } from '../core/types.js';\n\n/**\n * Parse raw SQL into individual statements with AST representation.\n * Uses pgsql-parser (WASM port of the real PostgreSQL C parser) for\n * production-grade AST parsing.\n *\n * Falls back to a regex-based splitter if AST parsing fails (e.g.\n * for non-standard SQL extensions) to still provide best-effort\n * rule checking.\n */\nexport async function parseSql(rawSql: string): Promise<ParsedStatement[]> {\n const statements: ParsedStatement[] = [];\n\n try {\n // Dynamic import to handle ESM/WASM loading\n const { parse } = await import('pgsql-parser');\n const result = await parse(rawSql);\n const stmtList = Array.isArray(result) ? result : (result?.stmts || []);\n\n if (stmtList.length > 0) {\n for (let i = 0; i < stmtList.length; i++) {\n const stmtObj = stmtList[i];\n const loc = stmtObj.stmt_location || 0;\n const len = stmtObj.stmt_len && stmtObj.stmt_len > 0\n ? stmtObj.stmt_len\n : rawSql.length - loc;\n const raw = rawSql.substring(loc, loc + len).trim();\n const line = rawSql.substring(0, loc).split('\\n').length;\n\n statements.push({\n raw,\n line,\n ast: stmtObj,\n });\n }\n return statements;\n }\n } catch {\n // Fallback: regex-based splitting with no AST\n // This allows rule checks that don't need deep AST access\n const rawStatements = splitStatements(rawSql);\n for (let i = 0; i < rawStatements.length; i++) {\n const raw = rawStatements[i].trim();\n if (!raw) continue;\n\n statements.push({\n raw,\n line: findLineNumber(rawSql, rawStatements[i]),\n ast: null,\n });\n }\n return statements;\n }\n\n // If stmtList was empty, try fallback splitting\n const fallbackStatements = splitStatements(rawSql);\n for (const rawStmt of fallbackStatements) {\n const raw = rawStmt.trim();\n if (!raw) continue;\n statements.push({\n raw,\n line: findLineNumber(rawSql, rawStmt),\n ast: null,\n });\n }\n\n return statements;\n}\n\n/**\n * Split SQL text into individual statements.\n * Respects string literals, dollar-quoted strings, and comments.\n */\nfunction splitStatements(sql: string): string[] {\n const statements: string[] = [];\n let current = '';\n let inSingleQuote = false;\n let inDoubleQuote = false;\n let inDollarQuote = false;\n let dollarTag = '';\n let inLineComment = false;\n let inBlockComment = false;\n\n for (let i = 0; i < sql.length; i++) {\n const char = sql[i];\n const next = sql[i + 1] || '';\n\n // Handle line comments\n if (!inSingleQuote && !inDoubleQuote && !inDollarQuote && !inBlockComment) {\n if (char === '-' && next === '-') {\n inLineComment = true;\n current += char;\n continue;\n }\n }\n\n if (inLineComment) {\n current += char;\n if (char === '\\n') {\n inLineComment = false;\n }\n continue;\n }\n\n // Handle block comments\n if (!inSingleQuote && !inDoubleQuote && !inDollarQuote) {\n if (char === '/' && next === '*') {\n inBlockComment = true;\n current += char;\n continue;\n }\n }\n\n if (inBlockComment) {\n current += char;\n if (char === '*' && next === '/') {\n current += next;\n i++;\n inBlockComment = false;\n }\n continue;\n }\n\n // Handle dollar-quoted strings ($$...$$)\n if (!inSingleQuote && !inDoubleQuote && char === '$') {\n if (inDollarQuote) {\n // Check if this is the closing tag\n const remaining = sql.substring(i);\n if (remaining.startsWith(dollarTag)) {\n current += dollarTag;\n i += dollarTag.length - 1;\n inDollarQuote = false;\n continue;\n }\n } else {\n // Check if this starts a dollar quote\n const match = sql.substring(i).match(/^(\\$[^$]*\\$)/);\n if (match) {\n dollarTag = match[1];\n current += dollarTag;\n i += dollarTag.length - 1;\n inDollarQuote = true;\n continue;\n }\n }\n }\n\n if (inDollarQuote) {\n current += char;\n continue;\n }\n\n // Handle single quotes\n if (char === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n current += char;\n continue;\n }\n\n if (inSingleQuote) {\n current += char;\n continue;\n }\n\n // Handle double quotes (identifiers)\n if (char === '\"' && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n current += char;\n continue;\n }\n\n if (inDoubleQuote) {\n current += char;\n continue;\n }\n\n // Statement terminator\n if (char === ';') {\n current += char;\n if (current.trim()) {\n statements.push(current);\n }\n current = '';\n continue;\n }\n\n current += char;\n }\n\n // Don't forget the last statement (may not end with ;)\n if (current.trim()) {\n statements.push(current);\n }\n\n return statements;\n}\n\n/**\n * Find the 1-based line number of a substring in the full SQL text.\n */\nfunction findLineNumber(fullSql: string, substring: string): number {\n const trimmed = substring.trim();\n const index = fullSql.indexOf(trimmed);\n if (index === -1) return 1;\n\n const before = fullSql.substring(0, index);\n return before.split('\\n').length;\n}\n","import type { DetectedMigration, MgcheckConfig, RuleViolation, RuleContext, AnalyzedStatementInfo } from '../core/types.js';\nimport { getRuleSeverity } from '../core/config.js';\nimport { parseSql } from './parser.js';\nimport { ALL_RULES, DESTRUCTIVE_RULE_IDS } from './rules/index.js';\n\nexport interface AnalysisResult {\n violations: RuleViolation[];\n hasDestructive: boolean;\n statements: AnalyzedStatementInfo[];\n}\n\n/**\n * Run all enabled rules against the migration SQL.\n *\n * 1. Concatenates all migration files into one SQL body\n * 2. Parses into AST statements\n * 3. Runs each enabled rule\n * 4. Applies config severity overrides\n * 5. Returns violations + destructive flag\n */\nexport async function analyze(\n migration: DetectedMigration,\n config: MgcheckConfig\n): Promise<AnalysisResult> {\n // Combine all migration files into one SQL body\n const fullSql = migration.files.map((f) => f.sql).join('\\n');\n\n // Parse into statements\n const statements = await parseSql(fullSql);\n\n // Build rule context\n const context: RuleContext = {\n hasDownMigration: migration.hasDownMigration,\n format: migration.format,\n fullSql,\n };\n\n // Run all rules\n const violations: RuleViolation[] = [];\n let hasDestructive = false;\n\n for (const rule of ALL_RULES) {\n // Check if rule is disabled or has severity override\n const effectiveSeverity = getRuleSeverity(rule.id, rule.severity, config);\n\n if (effectiveSeverity === null) {\n continue; // Rule is disabled\n }\n\n const ruleViolations = rule.check(statements, context);\n\n for (const violation of ruleViolations) {\n // Apply severity override\n violation.severity = effectiveSeverity;\n\n violations.push(violation);\n\n // Check if this is a destructive operation\n if (DESTRUCTIVE_RULE_IDS.has(violation.ruleId)) {\n hasDestructive = true;\n }\n }\n }\n\n return {\n violations,\n hasDestructive,\n statements: statements.map((s) => ({ line: s.line, raw: s.raw })),\n };\n}\n\nexport { parseSql } from './parser.js';\nexport { ALL_RULES, DESTRUCTIVE_RULE_IDS } from './rules/index.js';\n","import Dockerode from 'dockerode';\nimport pg from 'pg';\nimport type { ShadowDatabase, ConnectionConfig, ExecutionResult } from '../core/types.js';\n\nconst { Client } = pg;\n\n/**\n * Docker-based shadow database using official Postgres image.\n *\n * Lifecycle:\n * 1. Pull image (if not cached)\n * 2. Start container with random port\n * 3. Wait for Postgres to accept connections\n * 4. Execute migrations\n * 5. Force-remove container\n */\nexport class DockerPostgresShadow implements ShadowDatabase {\n private docker: Dockerode;\n private container: Dockerode.Container | null = null;\n private connectionConfig: ConnectionConfig | null = null;\n private externalDbUrl: string | null = null;\n private imageName: string;\n\n constructor(imageName: string = 'postgres:16-alpine', externalDbUrl?: string) {\n this.docker = new Dockerode();\n this.imageName = imageName;\n this.externalDbUrl = externalDbUrl || null;\n }\n\n async create(): Promise<ConnectionConfig> {\n // If using an external database URL, parse it and return\n if (this.externalDbUrl) {\n const url = new URL(this.externalDbUrl);\n this.connectionConfig = {\n host: url.hostname,\n port: parseInt(url.port || '5432', 10),\n database: url.pathname.slice(1) || 'postgres',\n user: url.username || 'postgres',\n password: url.password || 'postgres',\n };\n return this.connectionConfig;\n }\n\n // Pull image if needed\n try {\n await this.docker.getImage(this.imageName).inspect();\n } catch {\n console.error(`Pulling ${this.imageName}...`);\n await new Promise<void>((resolve, reject) => {\n this.docker.pull(this.imageName, (err: any, stream: any) => {\n if (err) return reject(err);\n this.docker.modem.followProgress(stream, (err2: any) => {\n if (err2) return reject(err2);\n resolve();\n });\n });\n });\n }\n\n const password = 'mgcheck_shadow_' + Math.random().toString(36).substring(7);\n const containerName = `mgcheck-shadow-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n\n // Create and start container\n this.container = await this.docker.createContainer({\n Image: this.imageName,\n name: containerName,\n Env: [\n `POSTGRES_PASSWORD=${password}`,\n 'POSTGRES_DB=mgcheck_shadow',\n 'POSTGRES_USER=mgcheck',\n ],\n HostConfig: {\n AutoRemove: true,\n PortBindings: {\n '5432/tcp': [{ HostPort: '0' }], // Random port\n },\n },\n ExposedPorts: {\n '5432/tcp': {},\n },\n });\n\n await this.container.start();\n\n // Get the assigned port\n const info = await this.container.inspect();\n const hostPort =\n info.NetworkSettings.Ports['5432/tcp']?.[0]?.HostPort;\n\n if (!hostPort) {\n throw new Error('Failed to get assigned port for shadow database container');\n }\n\n this.connectionConfig = {\n host: '127.0.0.1',\n port: parseInt(hostPort, 10),\n database: 'mgcheck_shadow',\n user: 'mgcheck',\n password,\n };\n\n // Wait for Postgres to be ready\n await this.waitForReady();\n\n return this.connectionConfig;\n }\n\n async seed(sql: string): Promise<void> {\n if (!this.connectionConfig) {\n throw new Error('Shadow database not created yet. Call create() first.');\n }\n\n const client = new Client(this.connectionConfig);\n try {\n await client.connect();\n await client.query(sql);\n } finally {\n await client.end();\n }\n }\n\n async execute(sql: string): Promise<ExecutionResult> {\n if (!this.connectionConfig) {\n throw new Error('Shadow database not created yet. Call create() first.');\n }\n\n const client = new Client(this.connectionConfig);\n const startTime = Date.now();\n let statementsExecuted = 0;\n\n try {\n await client.connect();\n\n // Execute the full migration SQL\n // pg client handles multi-statement SQL natively\n const result = await client.query(sql);\n statementsExecuted = Array.isArray(result) ? result.length : 1;\n\n return {\n success: true,\n executionTimeMs: Date.now() - startTime,\n statementsExecuted,\n };\n } catch (err) {\n return {\n success: false,\n error: (err as Error).message,\n executionTimeMs: Date.now() - startTime,\n statementsExecuted,\n };\n } finally {\n await client.end();\n }\n }\n\n async teardown(): Promise<void> {\n if (this.container) {\n try {\n await this.container.stop({ t: 2 });\n } catch {\n // Container may already be stopped (AutoRemove)\n }\n try {\n await this.container.remove({ force: true });\n } catch {\n // Container may already be removed (AutoRemove)\n }\n this.container = null;\n }\n this.connectionConfig = null;\n }\n\n /**\n * Wait for Postgres to accept connections with exponential backoff.\n * Timeout: 30 seconds.\n */\n private async waitForReady(timeoutMs: number = 30000): Promise<void> {\n if (!this.connectionConfig) throw new Error('No connection config');\n\n const startTime = Date.now();\n let delay = 200; // Start with 200ms\n\n while (Date.now() - startTime < timeoutMs) {\n const client = new Client(this.connectionConfig);\n try {\n await client.connect();\n await client.query('SELECT 1');\n await client.end();\n return; // Ready!\n } catch {\n await client.end().catch(() => {});\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * 1.5, 2000); // Cap at 2s\n }\n }\n\n throw new Error(\n `Shadow database failed to become ready within ${timeoutMs / 1000}s. ` +\n 'Ensure Docker is running and the Postgres image is accessible.'\n );\n }\n}\n","import type { ShadowDatabase, ConnectionConfig, ExecutionResult } from '../core/types.js';\n\n/**\n * PGlite-based shadow database — runs PostgreSQL entirely in-process\n * via WebAssembly. No Docker required.\n *\n * Advantages:\n * - Zero external dependencies\n * - Starts in ~200ms\n * - Perfect for CI environments without Docker\n *\n * Limitations:\n * - Cannot run CREATE INDEX CONCURRENTLY (single connection)\n * - Some extensions may not be available\n * - Not suitable for testing concurrency-dependent behavior\n */\nexport class PGliteShadow implements ShadowDatabase {\n private db: any = null;\n\n async create(): Promise<ConnectionConfig> {\n // Dynamic import for optional dependency\n const { PGlite } = await import('@electric-sql/pglite');\n this.db = new PGlite();\n\n // PGlite runs in-process, no real network connection\n return {\n host: 'pglite-in-process',\n port: 0,\n database: 'pglite',\n user: 'pglite',\n password: '',\n };\n }\n\n async seed(sql: string): Promise<void> {\n if (!this.db) {\n throw new Error('PGlite database not created yet. Call create() first.');\n }\n await this.db.exec(sql);\n }\n\n async execute(sql: string): Promise<ExecutionResult> {\n if (!this.db) {\n throw new Error('PGlite database not created yet. Call create() first.');\n }\n\n const startTime = Date.now();\n\n try {\n // PGlite's exec handles multi-statement SQL\n const results = await this.db.exec(sql);\n const statementsExecuted = Array.isArray(results) ? results.length : 1;\n\n return {\n success: true,\n executionTimeMs: Date.now() - startTime,\n statementsExecuted,\n };\n } catch (err) {\n const errorMessage = (err as Error).message;\n\n // Known PGlite limitation: CONCURRENTLY not supported\n if (errorMessage.includes('CONCURRENTLY')) {\n return {\n success: false,\n error:\n errorMessage +\n '\\n\\nNote: PGlite does not support CONCURRENTLY operations. ' +\n 'Use --provider docker for full Postgres fidelity.',\n executionTimeMs: Date.now() - startTime,\n statementsExecuted: 0,\n };\n }\n\n return {\n success: false,\n error: errorMessage,\n executionTimeMs: Date.now() - startTime,\n statementsExecuted: 0,\n };\n }\n }\n\n async teardown(): Promise<void> {\n if (this.db) {\n try {\n await this.db.close();\n } catch {\n // Ignore close errors\n }\n this.db = null;\n }\n }\n}\n","import type { ShadowDatabase, ShadowProvider, MgcheckConfig } from '../core/types.js';\nimport { DockerPostgresShadow } from './docker-postgres.js';\nimport { PGliteShadow } from './pglite.js';\n\n/**\n * Create a shadow database instance based on the config.\n */\nexport function createShadowDatabase(config: MgcheckConfig): ShadowDatabase {\n // If a direct DB URL is provided, use Docker provider with the URL\n if (config.shadowDb.dbUrl) {\n return new DockerPostgresShadow(config.shadowDb.dockerImage, config.shadowDb.dbUrl);\n }\n\n switch (config.shadowDb.provider) {\n case 'docker':\n return new DockerPostgresShadow(config.shadowDb.dockerImage);\n case 'pglite':\n return new PGliteShadow();\n default:\n throw new Error(`Unknown shadow DB provider: ${config.shadowDb.provider}`);\n }\n}\n\nexport { DockerPostgresShadow } from './docker-postgres.js';\nexport { PGliteShadow } from './pglite.js';\n","import { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { MgcheckConfig, MigrationReport, DetectedMigration, ExecutionResult } from './types.js';\nimport { detectMigration } from '../detector/index.js';\nimport { analyze } from '../analyzer/index.js';\nimport { createShadowDatabase } from '../shadow/index.js';\n\n/**\n * Main engine orchestrator.\n *\n * Pipeline:\n * 1. Detect migration format\n * 2. Run static analysis (rule engine)\n * 3. (If mode=run) Spin up shadow DB → seed → execute → teardown\n * 4. Build final report\n */\nexport async function runCheck(\n inputPath: string,\n config: MgcheckConfig,\n mode: 'run' | 'analyze' = 'run',\n onProgress?: (message: string) => void\n): Promise<MigrationReport> {\n const log = onProgress || (() => {});\n\n // 1. Detect migration format\n log('Detecting migration format...');\n const migration = detectMigration(inputPath);\n log(`Detected: ${migration.format} (${migration.files.length} file(s))`);\n\n // 2. Run static analysis\n log('Running rule analysis...');\n const analysis = await analyze(migration, config);\n log(`Parsed ${analysis.statements.length} SQL statement(s):`);\n for (const s of analysis.statements) {\n const singleLine = s.raw.replace(/\\s+/g, ' ').trim();\n const truncated = singleLine.length > 70 ? singleLine.substring(0, 67) + '...' : singleLine;\n log(` • [Line ${s.line}] ${truncated}`);\n }\n log(`Rule analysis completed: ${analysis.violations.length} violation(s) found`);\n\n // 3. Shadow DB execution (only in 'run' mode)\n let execution: ExecutionResult | null = null;\n\n if (mode === 'run') {\n const shadow = createShadowDatabase(config);\n\n try {\n log(`Starting shadow database (${config.shadowDb.provider})...`);\n await shadow.create();\n log('Shadow database ready');\n\n // Seed with base schema if configured\n if (config.shadowDb.seedFile) {\n const seedPath = resolve(config.shadowDb.seedFile);\n if (!existsSync(seedPath)) {\n throw new Error(`Seed file not found: ${seedPath}`);\n }\n const seedSql = readFileSync(seedPath, 'utf-8');\n log('Applying seed schema...');\n await shadow.seed(seedSql);\n log('Seed schema applied');\n }\n\n // Execute the target migration\n const fullSql = migration.files.map((f) => f.sql).join('\\n');\n log(`Executing migration against shadow database (${analysis.statements.length} statement(s))...`);\n execution = await shadow.execute(fullSql);\n log(execution.success ? 'Migration executed successfully' : `Migration execution failed: ${execution.error}`);\n } finally {\n log('Tearing down shadow database...');\n await shadow.teardown();\n log('Shadow database cleaned up');\n }\n }\n\n // 4. Build report\n const hasBlockingViolations = analysis.violations.some((v) => v.severity === 'error');\n const executionFailed = execution !== null && !execution.success;\n\n // Check destructive violations against --confirm-destructive\n const hasUnconfirmedDestructive =\n analysis.hasDestructive && !config.confirmDestructive;\n\n const passed =\n !executionFailed &&\n !hasBlockingViolations &&\n !hasUnconfirmedDestructive;\n\n const report: MigrationReport = {\n migration,\n execution,\n violations: analysis.violations,\n statements: analysis.statements,\n passed,\n hasDestructive: analysis.hasDestructive,\n timestamp: new Date().toISOString(),\n mode,\n };\n\n return report;\n}\n"],"mappings":"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","names":["statSync","existsSync","readdirSync","resolve","join","readFileSync","readFileSync","existsSync","hasDown","readFileSync","existsSync","readdirSync","join","resolve","existsSync","statSync","readdirSync","join","resolve","readFileSync","existsSync","resolve","resolve","existsSync","readFileSync"]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getExitCode,
|
|
4
|
+
renderReport
|
|
5
|
+
} from "./chunk-4YTQB62X.js";
|
|
6
|
+
import {
|
|
7
|
+
loadConfig,
|
|
8
|
+
runCheck
|
|
9
|
+
} from "./chunk-7JBFSZBD.js";
|
|
10
|
+
|
|
11
|
+
// src/cli.ts
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
var program = new Command();
|
|
14
|
+
program.name("mgcheck").description("\u{1F6E1}\uFE0F Migration Guardian \u2014 Catch unsafe database migrations before they hit production").version("0.1.0");
|
|
15
|
+
program.command("run").description("Analyze and execute a migration against a shadow database").argument("<path>", "Path to migration file or directory").option("--db-url <url>", "Use existing Postgres URL instead of spinning up Docker").option("--target-db-url <url>", "Target real PostgreSQL URL to apply verified migration to").option("--apply", "Automatically apply to target database if verification passes", false).option("--seed <path>", "Base schema SQL to apply before target migration").option("--docker-image <image>", "Postgres Docker image", "postgres:16-alpine").option("--provider <provider>", "Shadow DB provider: docker | pglite", "docker").option("--format <format>", "Output format: terminal | json | markdown", "terminal").option("--ci", "Machine-readable JSON output + exit codes only", false).option("--confirm-destructive", "Allow destructive operations (DROP, TRUNCATE)", false).option("--verbose", "Show detailed execution logs", false).option("--config <path>", "Path to config file").action(async (inputPath, options) => {
|
|
16
|
+
try {
|
|
17
|
+
const config = buildConfig(options);
|
|
18
|
+
const format = options.ci ? "json" : config.output.format;
|
|
19
|
+
const report = await runCheck(inputPath, config, "run", (msg) => {
|
|
20
|
+
if (config.output.verbose && !options.ci) {
|
|
21
|
+
console.error(` ${msg}`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
const output = renderReport(report, format);
|
|
25
|
+
console.log(output);
|
|
26
|
+
if (report.passed && !options.ci) {
|
|
27
|
+
const fullSql = report.migration.files.map((f) => f.sql).join("\n");
|
|
28
|
+
const { promptAndApplyIfConfirmed } = await import("./applier-TLGJ6SZ2.js");
|
|
29
|
+
await promptAndApplyIfConfirmed(fullSql, {
|
|
30
|
+
targetDbUrl: options.targetDbUrl,
|
|
31
|
+
autoApply: options.apply,
|
|
32
|
+
isCi: options.ci
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
process.exitCode = getExitCode(report);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (options.ci) {
|
|
38
|
+
console.log(JSON.stringify({ error: err.message }, null, 2));
|
|
39
|
+
} else {
|
|
40
|
+
console.error(`
|
|
41
|
+
\u274C Error: ${err.message}
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
process.exitCode = 3;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
program.command("analyze").description("Static analysis only \u2014 check migration SQL against safety rules (no shadow DB)").argument("<path>", "Path to migration file or directory").option("--format <format>", "Output format: terminal | json | markdown", "terminal").option("--ci", "Machine-readable JSON output + exit codes only", false).option("--confirm-destructive", "Allow destructive operations", false).option("--verbose", "Show detailed logs", false).option("--config <path>", "Path to config file").action(async (inputPath, options) => {
|
|
48
|
+
try {
|
|
49
|
+
const config = buildConfig(options);
|
|
50
|
+
const format = options.ci ? "json" : config.output.format;
|
|
51
|
+
const report = await runCheck(inputPath, config, "analyze", (msg) => {
|
|
52
|
+
if (config.output.verbose && !options.ci) {
|
|
53
|
+
console.error(` ${msg}`);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const output = renderReport(report, format);
|
|
57
|
+
console.log(output);
|
|
58
|
+
process.exitCode = getExitCode(report);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (options.ci) {
|
|
61
|
+
console.log(JSON.stringify({ error: err.message }, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
console.error(`
|
|
64
|
+
\u274C Error: ${err.message}
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
process.exitCode = 3;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
program.command("mcp").description("Start the MCP server for AI agent integration (stdio transport)").action(async () => {
|
|
71
|
+
await import("./mcp/server.js");
|
|
72
|
+
});
|
|
73
|
+
program.command("setup").alias("init").alias("install-mcp").description("Interactive wizard to connect Migration Guardian to Antigravity, Cursor, and Claude").option("-y, --yes", "Skip confirmation prompt and connect automatically", false).option("--client <client>", "Target client: antigravity | claude | cursor | all", "all").action(async (options) => {
|
|
74
|
+
const { runInteractiveWizard } = await import("./installer-KGWDJ6OR.js");
|
|
75
|
+
await runInteractiveWizard({ yes: options.yes, client: options.client });
|
|
76
|
+
});
|
|
77
|
+
function buildConfig(options) {
|
|
78
|
+
const overrides = {
|
|
79
|
+
shadowDb: {
|
|
80
|
+
provider: options.provider || "docker",
|
|
81
|
+
dockerImage: options.dockerImage || "postgres:16-alpine",
|
|
82
|
+
dbUrl: options.dbUrl,
|
|
83
|
+
seedFile: options.seed
|
|
84
|
+
},
|
|
85
|
+
output: {
|
|
86
|
+
format: options.format || "terminal",
|
|
87
|
+
verbose: options.verbose || false
|
|
88
|
+
},
|
|
89
|
+
confirmDestructive: options.confirmDestructive || false
|
|
90
|
+
};
|
|
91
|
+
return loadConfig(overrides, options.config);
|
|
92
|
+
}
|
|
93
|
+
if (process.argv.slice(2).length === 0) {
|
|
94
|
+
const { runInteractiveWizard } = await import("./installer-KGWDJ6OR.js");
|
|
95
|
+
await runInteractiveWizard();
|
|
96
|
+
} else {
|
|
97
|
+
program.parse();
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { loadConfig } from './core/config.js';\nimport { runCheck } from './core/engine.js';\nimport { renderReport, getExitCode } from './reporter/index.js';\nimport type { MgcheckConfig, OutputFormat, ShadowProvider } from './core/types.js';\n\nconst program = new Command();\n\nprogram\n .name('mgcheck')\n .description('🛡️ Migration Guardian — Catch unsafe database migrations before they hit production')\n .version('0.1.0');\n\n// ─── mgcheck run ────────────────────────────────────────────────────\n\nprogram\n .command('run')\n .description('Analyze and execute a migration against a shadow database')\n .argument('<path>', 'Path to migration file or directory')\n .option('--db-url <url>', 'Use existing Postgres URL instead of spinning up Docker')\n .option('--target-db-url <url>', 'Target real PostgreSQL URL to apply verified migration to')\n .option('--apply', 'Automatically apply to target database if verification passes', false)\n .option('--seed <path>', 'Base schema SQL to apply before target migration')\n .option('--docker-image <image>', 'Postgres Docker image', 'postgres:16-alpine')\n .option('--provider <provider>', 'Shadow DB provider: docker | pglite', 'docker')\n .option('--format <format>', 'Output format: terminal | json | markdown', 'terminal')\n .option('--ci', 'Machine-readable JSON output + exit codes only', false)\n .option('--confirm-destructive', 'Allow destructive operations (DROP, TRUNCATE)', false)\n .option('--verbose', 'Show detailed execution logs', false)\n .option('--config <path>', 'Path to config file')\n .action(async (inputPath: string, options: any) => {\n try {\n const config = buildConfig(options);\n const format = options.ci ? 'json' : (config.output.format as OutputFormat);\n\n const report = await runCheck(inputPath, config, 'run', (msg) => {\n if (config.output.verbose && !options.ci) {\n console.error(` ${msg}`);\n }\n });\n\n const output = renderReport(report, format);\n console.log(output);\n\n // If verification passed and not in CI mode, prompt to apply to real database\n if (report.passed && !options.ci) {\n const fullSql = report.migration.files.map((f) => f.sql).join('\\n');\n const { promptAndApplyIfConfirmed } = await import('./core/applier.js');\n await promptAndApplyIfConfirmed(fullSql, {\n targetDbUrl: options.targetDbUrl,\n autoApply: options.apply,\n isCi: options.ci,\n });\n }\n\n process.exitCode = getExitCode(report);\n } catch (err) {\n if (options.ci) {\n console.log(JSON.stringify({ error: (err as Error).message }, null, 2));\n } else {\n console.error(`\\n ❌ Error: ${(err as Error).message}\\n`);\n }\n process.exitCode = 3;\n }\n });\n\n// ─── mgcheck analyze ───────────────────────────────────────────────\n\nprogram\n .command('analyze')\n .description('Static analysis only — check migration SQL against safety rules (no shadow DB)')\n .argument('<path>', 'Path to migration file or directory')\n .option('--format <format>', 'Output format: terminal | json | markdown', 'terminal')\n .option('--ci', 'Machine-readable JSON output + exit codes only', false)\n .option('--confirm-destructive', 'Allow destructive operations', false)\n .option('--verbose', 'Show detailed logs', false)\n .option('--config <path>', 'Path to config file')\n .action(async (inputPath: string, options: any) => {\n try {\n const config = buildConfig(options);\n const format = options.ci ? 'json' : (config.output.format as OutputFormat);\n\n const report = await runCheck(inputPath, config, 'analyze', (msg) => {\n if (config.output.verbose && !options.ci) {\n console.error(` ${msg}`);\n }\n });\n\n const output = renderReport(report, format);\n console.log(output);\n\n process.exitCode = getExitCode(report);\n } catch (err) {\n if (options.ci) {\n console.log(JSON.stringify({ error: (err as Error).message }, null, 2));\n } else {\n console.error(`\\n ❌ Error: ${(err as Error).message}\\n`);\n }\n process.exitCode = 3;\n }\n });\n\n// ─── mgcheck mcp ───────────────────────────────────────────────────\n\nprogram\n .command('mcp')\n .description('Start the MCP server for AI agent integration (stdio transport)')\n .action(async () => {\n await import('./mcp/server.js');\n });\n\n// ─── mgcheck setup ─────────────────────────────────────────────────\n\nprogram\n .command('setup')\n .alias('init')\n .alias('install-mcp')\n .description('Interactive wizard to connect Migration Guardian to Antigravity, Cursor, and Claude')\n .option('-y, --yes', 'Skip confirmation prompt and connect automatically', false)\n .option('--client <client>', 'Target client: antigravity | claude | cursor | all', 'all')\n .action(async (options: any) => {\n const { runInteractiveWizard } = await import('./installer/index.js');\n await runInteractiveWizard({ yes: options.yes, client: options.client });\n });\n\n// ─── Helpers ────────────────────────────────────────────────────────\n\nfunction buildConfig(options: any): MgcheckConfig {\n const overrides: Partial<MgcheckConfig> = {\n shadowDb: {\n provider: (options.provider || 'docker') as ShadowProvider,\n dockerImage: options.dockerImage || 'postgres:16-alpine',\n dbUrl: options.dbUrl,\n seedFile: options.seed,\n },\n output: {\n format: (options.format || 'terminal') as OutputFormat,\n verbose: options.verbose || false,\n },\n confirmDestructive: options.confirmDestructive || false,\n };\n\n return loadConfig(overrides, options.config);\n}\n\n// ─── Entry Point ────────────────────────────────────────────────────\n\n// If no arguments are passed (e.g. npx mgcheck), launch interactive wizard\nif (process.argv.slice(2).length === 0) {\n const { runInteractiveWizard } = await import('./installer/index.js');\n await runInteractiveWizard();\n} else {\n program.parse();\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
type MigrationFormat = 'raw-sql' | 'prisma' | 'drizzle';
|
|
2
|
+
interface MigrationFile {
|
|
3
|
+
/** Absolute or relative path to the file */
|
|
4
|
+
path: string;
|
|
5
|
+
/** Raw SQL content */
|
|
6
|
+
sql: string;
|
|
7
|
+
/** Ordering index (0-based) */
|
|
8
|
+
order: number;
|
|
9
|
+
}
|
|
10
|
+
interface DetectedMigration {
|
|
11
|
+
/** Detected framework format */
|
|
12
|
+
format: MigrationFormat;
|
|
13
|
+
/** Ordered list of migration SQL files */
|
|
14
|
+
files: MigrationFile[];
|
|
15
|
+
/** Whether a down/rollback migration was found */
|
|
16
|
+
hasDownMigration: boolean;
|
|
17
|
+
/** Root path that was analyzed */
|
|
18
|
+
basePath: string;
|
|
19
|
+
}
|
|
20
|
+
type RuleSeverity = 'error' | 'warning';
|
|
21
|
+
interface RuleViolation {
|
|
22
|
+
/** Rule identifier, e.g. "MG001" */
|
|
23
|
+
ruleId: string;
|
|
24
|
+
/** Human-readable rule name */
|
|
25
|
+
ruleName: string;
|
|
26
|
+
/** Severity: error = blocking, warning = informational */
|
|
27
|
+
severity: RuleSeverity;
|
|
28
|
+
/** Plain-English explanation of why this is dangerous */
|
|
29
|
+
message: string;
|
|
30
|
+
/** Line number in the SQL file (1-based), if available */
|
|
31
|
+
line?: number;
|
|
32
|
+
/** Column number (1-based), if available */
|
|
33
|
+
column?: number;
|
|
34
|
+
/** The offending SQL statement */
|
|
35
|
+
sql: string;
|
|
36
|
+
/** Suggested safe rewrite pattern */
|
|
37
|
+
suggestion?: string;
|
|
38
|
+
}
|
|
39
|
+
interface Rule {
|
|
40
|
+
/** Unique rule ID, e.g. "MG001" */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Human-readable name, e.g. "create-index-not-concurrent" */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Default severity */
|
|
45
|
+
severity: RuleSeverity;
|
|
46
|
+
/** Short description */
|
|
47
|
+
description: string;
|
|
48
|
+
/** Check parsed SQL statements for violations */
|
|
49
|
+
check(statements: ParsedStatement[], context: RuleContext): RuleViolation[];
|
|
50
|
+
}
|
|
51
|
+
interface RuleContext {
|
|
52
|
+
/** Whether the migration has a down/rollback counterpart */
|
|
53
|
+
hasDownMigration: boolean;
|
|
54
|
+
/** Detected migration format */
|
|
55
|
+
format: MigrationFormat;
|
|
56
|
+
/** All raw SQL combined */
|
|
57
|
+
fullSql: string;
|
|
58
|
+
}
|
|
59
|
+
interface ParsedStatement {
|
|
60
|
+
/** The raw SQL text of this statement */
|
|
61
|
+
raw: string;
|
|
62
|
+
/** Approximate line number in the original file */
|
|
63
|
+
line: number;
|
|
64
|
+
/** The parsed AST node from pgsql-parser */
|
|
65
|
+
ast: any;
|
|
66
|
+
}
|
|
67
|
+
type ShadowProvider = 'docker' | 'pglite';
|
|
68
|
+
interface ConnectionConfig {
|
|
69
|
+
host: string;
|
|
70
|
+
port: number;
|
|
71
|
+
database: string;
|
|
72
|
+
user: string;
|
|
73
|
+
password: string;
|
|
74
|
+
}
|
|
75
|
+
interface ShadowDatabase {
|
|
76
|
+
/** Spin up the shadow database, return connection info */
|
|
77
|
+
create(): Promise<ConnectionConfig>;
|
|
78
|
+
/** Apply seed/base schema SQL */
|
|
79
|
+
seed(sql: string): Promise<void>;
|
|
80
|
+
/** Execute migration SQL and capture result */
|
|
81
|
+
execute(sql: string): Promise<ExecutionResult>;
|
|
82
|
+
/** Tear down and clean up all resources */
|
|
83
|
+
teardown(): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
interface ExecutionResult {
|
|
86
|
+
/** Whether all statements executed without error */
|
|
87
|
+
success: boolean;
|
|
88
|
+
/** The exact Postgres error message, if any */
|
|
89
|
+
error?: string;
|
|
90
|
+
/** Execution time in milliseconds */
|
|
91
|
+
executionTimeMs: number;
|
|
92
|
+
/** Number of statements successfully executed */
|
|
93
|
+
statementsExecuted: number;
|
|
94
|
+
}
|
|
95
|
+
type OutputFormat = 'terminal' | 'json' | 'markdown';
|
|
96
|
+
interface AnalyzedStatementInfo {
|
|
97
|
+
line: number;
|
|
98
|
+
raw: string;
|
|
99
|
+
}
|
|
100
|
+
interface MigrationReport {
|
|
101
|
+
/** Info about the detected migration */
|
|
102
|
+
migration: DetectedMigration;
|
|
103
|
+
/** Shadow DB execution result (null if --analyze only) */
|
|
104
|
+
execution: ExecutionResult | null;
|
|
105
|
+
/** Rule violations found */
|
|
106
|
+
violations: RuleViolation[];
|
|
107
|
+
/** Parsed statements inspected */
|
|
108
|
+
statements?: AnalyzedStatementInfo[];
|
|
109
|
+
/** Overall pass/fail: true only if execution succeeded AND no error-severity violations */
|
|
110
|
+
passed: boolean;
|
|
111
|
+
/** True if any destructive operation was detected */
|
|
112
|
+
hasDestructive: boolean;
|
|
113
|
+
/** ISO 8601 timestamp */
|
|
114
|
+
timestamp: string;
|
|
115
|
+
/** Execution mode */
|
|
116
|
+
mode: 'run' | 'analyze';
|
|
117
|
+
}
|
|
118
|
+
interface MgcheckConfig {
|
|
119
|
+
shadowDb: {
|
|
120
|
+
provider: ShadowProvider;
|
|
121
|
+
dockerImage: string;
|
|
122
|
+
dbUrl?: string;
|
|
123
|
+
seedFile?: string;
|
|
124
|
+
};
|
|
125
|
+
rules: Record<string, RuleSeverity | 'off'>;
|
|
126
|
+
output: {
|
|
127
|
+
format: OutputFormat;
|
|
128
|
+
verbose: boolean;
|
|
129
|
+
};
|
|
130
|
+
confirmDestructive: boolean;
|
|
131
|
+
llm?: {
|
|
132
|
+
provider: string;
|
|
133
|
+
model: string;
|
|
134
|
+
apiKey: string;
|
|
135
|
+
maxFixAttempts: number;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Main engine orchestrator.
|
|
141
|
+
*
|
|
142
|
+
* Pipeline:
|
|
143
|
+
* 1. Detect migration format
|
|
144
|
+
* 2. Run static analysis (rule engine)
|
|
145
|
+
* 3. (If mode=run) Spin up shadow DB → seed → execute → teardown
|
|
146
|
+
* 4. Build final report
|
|
147
|
+
*/
|
|
148
|
+
declare function runCheck(inputPath: string, config: MgcheckConfig, mode?: 'run' | 'analyze', onProgress?: (message: string) => void): Promise<MigrationReport>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Load configuration from file, env vars, and CLI overrides.
|
|
152
|
+
* Priority: CLI flags > env vars > config file > defaults
|
|
153
|
+
*/
|
|
154
|
+
declare function loadConfig(overrides?: Partial<MgcheckConfig>, configPath?: string): MgcheckConfig;
|
|
155
|
+
/**
|
|
156
|
+
* Get the effective severity for a rule, considering config overrides.
|
|
157
|
+
* Returns null if the rule is disabled ('off').
|
|
158
|
+
*/
|
|
159
|
+
declare function getRuleSeverity(ruleId: string, defaultSeverity: RuleSeverity, config: MgcheckConfig): RuleSeverity | null;
|
|
160
|
+
|
|
161
|
+
interface ApplyResult {
|
|
162
|
+
success: boolean;
|
|
163
|
+
error?: string;
|
|
164
|
+
executionTimeMs: number;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Executes verified SQL migration directly against a target real PostgreSQL database.
|
|
168
|
+
*/
|
|
169
|
+
declare function applyToRealDatabase(sql: string, dbUrl: string): Promise<ApplyResult>;
|
|
170
|
+
/**
|
|
171
|
+
* Interactive prompt: displays the verified SQL and asks the user
|
|
172
|
+
* whether they want to apply it to their real database.
|
|
173
|
+
*/
|
|
174
|
+
declare function promptAndApplyIfConfirmed(sql: string, options?: {
|
|
175
|
+
targetDbUrl?: string;
|
|
176
|
+
autoApply?: boolean;
|
|
177
|
+
isCi?: boolean;
|
|
178
|
+
}): Promise<boolean>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Auto-detect the migration format from a given path.
|
|
182
|
+
*
|
|
183
|
+
* Detection logic:
|
|
184
|
+
* File (.sql) → Raw SQL
|
|
185
|
+
* Directory:
|
|
186
|
+
* → Contains migration.sql or subdirs with migration.sql → Prisma
|
|
187
|
+
* → Contains meta/_journal.json → Drizzle
|
|
188
|
+
* → Contains *.sql files → Raw SQL directory
|
|
189
|
+
* → Otherwise: error
|
|
190
|
+
*/
|
|
191
|
+
declare function detectMigration(inputPath: string): DetectedMigration;
|
|
192
|
+
|
|
193
|
+
/** All built-in rules, in order */
|
|
194
|
+
declare const ALL_RULES: Rule[];
|
|
195
|
+
/** IDs of rules that flag destructive operations */
|
|
196
|
+
declare const DESTRUCTIVE_RULE_IDS: Set<string>;
|
|
197
|
+
|
|
198
|
+
interface AnalysisResult {
|
|
199
|
+
violations: RuleViolation[];
|
|
200
|
+
hasDestructive: boolean;
|
|
201
|
+
statements: AnalyzedStatementInfo[];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Run all enabled rules against the migration SQL.
|
|
205
|
+
*
|
|
206
|
+
* 1. Concatenates all migration files into one SQL body
|
|
207
|
+
* 2. Parses into AST statements
|
|
208
|
+
* 3. Runs each enabled rule
|
|
209
|
+
* 4. Applies config severity overrides
|
|
210
|
+
* 5. Returns violations + destructive flag
|
|
211
|
+
*/
|
|
212
|
+
declare function analyze(migration: DetectedMigration, config: MgcheckConfig): Promise<AnalysisResult>;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Create a shadow database instance based on the config.
|
|
216
|
+
*/
|
|
217
|
+
declare function createShadowDatabase(config: MgcheckConfig): ShadowDatabase;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Render a migration report in the specified format.
|
|
221
|
+
*/
|
|
222
|
+
declare function renderReport(report: MigrationReport, format: OutputFormat): string;
|
|
223
|
+
/**
|
|
224
|
+
* Get the exit code for a migration report.
|
|
225
|
+
*
|
|
226
|
+
* 0 = passed
|
|
227
|
+
* 1 = failed (execution error or blocking violation)
|
|
228
|
+
* 2 = warnings only
|
|
229
|
+
*/
|
|
230
|
+
declare function getExitCode(report: MigrationReport): number;
|
|
231
|
+
|
|
232
|
+
export { ALL_RULES, type AnalyzedStatementInfo, type ConnectionConfig, DESTRUCTIVE_RULE_IDS, type DetectedMigration, type ExecutionResult, type MgcheckConfig, type MigrationFile, type MigrationFormat, type MigrationReport, type OutputFormat, type ParsedStatement, type Rule, type RuleContext, type RuleSeverity, type RuleViolation, type ShadowDatabase, type ShadowProvider, analyze, applyToRealDatabase, createShadowDatabase, detectMigration, getExitCode, getRuleSeverity, loadConfig, promptAndApplyIfConfirmed, renderReport, runCheck };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getExitCode,
|
|
3
|
+
renderReport
|
|
4
|
+
} from "./chunk-4YTQB62X.js";
|
|
5
|
+
import {
|
|
6
|
+
ALL_RULES,
|
|
7
|
+
DESTRUCTIVE_RULE_IDS,
|
|
8
|
+
analyze,
|
|
9
|
+
createShadowDatabase,
|
|
10
|
+
detectMigration,
|
|
11
|
+
getRuleSeverity,
|
|
12
|
+
loadConfig,
|
|
13
|
+
runCheck
|
|
14
|
+
} from "./chunk-7JBFSZBD.js";
|
|
15
|
+
import {
|
|
16
|
+
applyToRealDatabase,
|
|
17
|
+
promptAndApplyIfConfirmed
|
|
18
|
+
} from "./chunk-7ENQ5WVM.js";
|
|
19
|
+
export {
|
|
20
|
+
ALL_RULES,
|
|
21
|
+
DESTRUCTIVE_RULE_IDS,
|
|
22
|
+
analyze,
|
|
23
|
+
applyToRealDatabase,
|
|
24
|
+
createShadowDatabase,
|
|
25
|
+
detectMigration,
|
|
26
|
+
getExitCode,
|
|
27
|
+
getRuleSeverity,
|
|
28
|
+
loadConfig,
|
|
29
|
+
promptAndApplyIfConfirmed,
|
|
30
|
+
renderReport,
|
|
31
|
+
runCheck
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|