UNPKG

supa-seed

Version:

A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support

658 lines 28.2 kB
"use strict"; /** * Configuration Migration System * Migrates existing configs from hardcoded framework assumptions to schema-first format * Ensures backward compatibility while encouraging adoption of new architecture */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigMigrator = void 0; const schema_driven_adapter_1 = require("./schema-driven-adapter"); const dynamic_column_mapper_1 = require("./dynamic-column-mapper"); const logger_1 = require("../core/utils/logger"); const fs = __importStar(require("fs")); class ConfigMigrator { constructor(client) { this.client = client; this.adapter = new schema_driven_adapter_1.SchemaDrivenAdapter(client); this.columnMapper = new dynamic_column_mapper_1.DynamicColumnMapper(); } /** * Migrate a legacy configuration to the new schema-first format */ async migrateConfig(legacyConfig, options = {}) { logger_1.Logger.info('🔄 Starting configuration migration to schema-first format...'); const migrationOptions = { inputPath: options.inputPath, outputPath: options.outputPath, createBackup: options.createBackup ?? true, enableSchemaAnalysis: options.enableSchemaAnalysis ?? true, preserveLegacyBehavior: options.preserveLegacyBehavior ?? false, enableModernFeatures: options.enableModernFeatures ?? true, validateAfterMigration: options.validateAfterMigration ?? true, dryRun: options.dryRun ?? false, customMappings: options.customMappings, frameworkHint: options.frameworkHint }; const warnings = []; const errors = []; const changes = []; let backupPath = ''; try { // Create backup if requested if (migrationOptions.createBackup && migrationOptions.inputPath) { backupPath = await this.createBackup(migrationOptions.inputPath); logger_1.Logger.info(`📋 Configuration backup created: ${backupPath}`); } // Analyze current schema if enabled let schemaAnalysis = null; if (migrationOptions.enableSchemaAnalysis) { try { schemaAnalysis = await this.analyzeCurrentSchema(legacyConfig); logger_1.Logger.debug('Schema analysis completed'); } catch (error) { warnings.push(`Schema analysis failed: ${error.message}`); logger_1.Logger.warn('Schema analysis failed, proceeding with basic migration'); } } // Build modern configuration const modernConfig = await this.buildModernConfig(legacyConfig, schemaAnalysis, migrationOptions, changes, warnings); // Validate migrated configuration if (migrationOptions.validateAfterMigration) { const validationResult = await this.validateModernConfig(modernConfig); if (!validationResult.valid) { errors.push(...validationResult.errors); warnings.push(...validationResult.warnings); } } // Save configuration if not dry run if (!migrationOptions.dryRun && migrationOptions.outputPath) { await this.saveModernConfig(modernConfig, migrationOptions.outputPath); logger_1.Logger.success(`✅ Migrated configuration saved: ${migrationOptions.outputPath}`); } // Generate recommendations const recommendedActions = this.generateRecommendations(legacyConfig, modernConfig, schemaAnalysis, warnings); const result = { success: errors.length === 0, modernConfig, warnings, errors, changes, backupPath, recommendedActions }; this.logMigrationResult(result); return result; } catch (error) { logger_1.Logger.error('Configuration migration failed:', error); return { success: false, modernConfig: await this.createFallbackConfig(legacyConfig), warnings, errors: [...errors, error.message], changes, backupPath, recommendedActions: ['Fix migration errors before proceeding'] }; } } /** * Migrate configuration file from disk */ async migrateConfigFile(inputPath, outputPath, options = {}) { try { // Read legacy configuration const legacyConfig = await this.readLegacyConfig(inputPath); // Set up migration options const migrationOptions = { inputPath, outputPath: outputPath || inputPath.replace(/\.json$/, '.v2.json'), createBackup: options.createBackup ?? true, enableSchemaAnalysis: options.enableSchemaAnalysis ?? true, preserveLegacyBehavior: options.preserveLegacyBehavior ?? false, enableModernFeatures: options.enableModernFeatures ?? true, validateAfterMigration: options.validateAfterMigration ?? true, dryRun: options.dryRun ?? false, customMappings: options.customMappings, frameworkHint: options.frameworkHint }; return await this.migrateConfig(legacyConfig, migrationOptions); } catch (error) { logger_1.Logger.error(`Failed to migrate config file ${inputPath}:`, error); throw error; } } /** * Check if a configuration needs migration */ async checkMigrationNeeded(configPath) { try { const config = await this.readConfigFile(configPath); const needsMigration = !this.isModernConfig(config); const reasons = []; if (!config.version || config.version !== '2.1.0') { reasons.push('Missing or outdated version number'); } if (!config.schema?.columnMapping) { reasons.push('Missing dynamic column mapping configuration'); } if (!config.schema?.constraints) { reasons.push('Missing constraint handling configuration'); } if (!config.execution) { reasons.push('Missing execution configuration'); } return { needsMigration, currentVersion: config.version || 'legacy', targetVersion: '2.1.0', reasons }; } catch (error) { return { needsMigration: true, currentVersion: 'unknown', targetVersion: '2.1.0', reasons: [`Failed to read config: ${error.message}`] }; } } /** * Create a backup of the original configuration */ async createBackup(originalPath) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = originalPath.replace(/\.json$/, `.backup.${timestamp}.json`); await fs.promises.copyFile(originalPath, backupPath); return backupPath; } /** * Analyze current schema to inform migration */ async analyzeCurrentSchema(legacyConfig) { try { // Update adapter configuration this.adapter.updateConfig({ enableSchemaCache: false // Force fresh analysis }); const schemaInfo = await this.adapter.getSchemaInfo(); const columnMappings = await this.columnMapper.createMappings({ tables: [], relationships: [], patterns: [], constraints: { userCreationConstraints: [], dataIntegrityRules: [], businessLogicConstraints: [] }, framework: schemaInfo.framework, recommendations: [] }); return { framework: schemaInfo.framework, tables: schemaInfo.tables, mappings: this.columnMapper.exportMappings(columnMappings) }; } catch (error) { logger_1.Logger.warn(`Schema analysis failed: ${error.message}`); return null; } } /** * Build modern configuration from legacy configuration */ async buildModernConfig(legacyConfig, schemaAnalysis, options, changes, warnings) { const modernConfig = { version: '2.1.0', // Migrate connection settings (mostly unchanged) supabaseUrl: legacyConfig.supabaseUrl, supabaseServiceKey: legacyConfig.supabaseServiceKey, environment: legacyConfig.environment, // Migrate and enhance seeding settings seeding: { userCount: legacyConfig.userCount, setupsPerUser: legacyConfig.setupsPerUser, imagesPerSetup: legacyConfig.imagesPerSetup, enableRealImages: legacyConfig.enableRealImages, seed: legacyConfig.seed, emailDomain: legacyConfig.emailDomain, domain: legacyConfig.domain || 'general', testUserPassword: legacyConfig.testUserPassword || 'password123', // New schema-driven settings enableSchemaIntrospection: options.enableModernFeatures, enableConstraintValidation: options.enableModernFeatures, enableAutoFixes: options.enableModernFeatures, enableProgressiveEnhancement: options.enableModernFeatures, enableGracefulDegradation: true }, // Build new schema configuration schema: { autoDetectFramework: !options.frameworkHint, frameworkOverride: options.frameworkHint || legacyConfig.schema?.framework, primaryUserTable: legacyConfig.schema?.primaryUserTable, columnMapping: { enableDynamicMapping: options.enableModernFeatures, enableFuzzyMatching: options.enableModernFeatures, enablePatternMatching: options.enableModernFeatures, minimumConfidence: 0.3, customMappings: this.migrateLegacyMappings(legacyConfig, options.customMappings) }, constraints: { enableValidation: options.enableModernFeatures, enableAutoFixes: options.enableModernFeatures, skipOptionalConstraints: false, createDependenciesOnDemand: options.enableModernFeatures }, relationships: { enableDiscovery: options.enableModernFeatures, respectForeignKeys: true, handleCircularDependencies: options.enableModernFeatures, enableParallelSeeding: options.enableModernFeatures } }, // Build execution configuration execution: { enableRollback: options.enableModernFeatures, maxRetries: 3, timeoutMs: 30000, continueOnError: false, enableCaching: true, cacheTimeout: 30 }, // Set compatibility options compatibility: { enableLegacyMode: options.preserveLegacyBehavior, legacyFallbacks: options.preserveLegacyBehavior ? ['simple_profiles', 'accounts_only', 'auth_only'] : [], maintainOldBehavior: options.preserveLegacyBehavior }, // Migration metadata migration: { migratedFrom: legacyConfig.schema?.framework || 'unknown', migrationDate: new Date().toISOString(), originalConfigHash: this.generateConfigHash(legacyConfig), migrationWarnings: warnings.slice() // Copy current warnings } }; // Track changes this.trackConfigurationChanges(legacyConfig, modernConfig, changes); // Apply schema analysis results if available if (schemaAnalysis) { this.applySchemaAnalysisToConfig(modernConfig, schemaAnalysis, changes); } return modernConfig; } /** * Migrate legacy column mappings to new format */ migrateLegacyMappings(legacyConfig, customMappings) { const mappings = {}; // Start with custom mappings if provided if (customMappings) { Object.assign(mappings, customMappings); } // Migrate user table mappings if (legacyConfig.schema?.userTable) { const userTable = legacyConfig.schema.userTable; const tableName = userTable.name; if (!mappings[tableName]) { mappings[tableName] = {}; } if (userTable.emailField) mappings[tableName].email = userTable.emailField; if (userTable.nameField) mappings[tableName].name = userTable.nameField; if (userTable.pictureField) mappings[tableName].avatar = userTable.pictureField; if (userTable.bioField) mappings[tableName].bio = userTable.bioField; if (userTable.usernameField) mappings[tableName].username = userTable.usernameField; } // Migrate setup table mappings if (legacyConfig.schema?.setupTable) { const setupTable = legacyConfig.schema.setupTable; const tableName = setupTable.name; if (!mappings[tableName]) { mappings[tableName] = {}; } if (setupTable.userField) mappings[tableName].author = setupTable.userField; if (setupTable.titleField) mappings[tableName].title = setupTable.titleField; if (setupTable.descriptionField) mappings[tableName].content = setupTable.descriptionField; if (setupTable.categoryField) mappings[tableName].category = setupTable.categoryField; if (setupTable.publicField) mappings[tableName].published = setupTable.publicField; } // Migrate base template mappings if (legacyConfig.schema?.baseTemplateTable) { const templateTable = legacyConfig.schema.baseTemplateTable; const tableName = templateTable.name; if (!mappings[tableName]) { mappings[tableName] = {}; } if (templateTable.typeField) mappings[tableName].type = templateTable.typeField; if (templateTable.makeField) mappings[tableName].make = templateTable.makeField; if (templateTable.modelField) mappings[tableName].model = templateTable.modelField; if (templateTable.yearField) mappings[tableName].year = templateTable.yearField; if (templateTable.descriptionField) mappings[tableName].description = templateTable.descriptionField; } return mappings; } /** * Track configuration changes for reporting */ trackConfigurationChanges(legacyConfig, modernConfig, changes) { // Track version addition changes.push({ type: 'added', path: 'version', newValue: '2.1.0', reason: 'Added version tracking for future migrations' }); // Track structure changes changes.push({ type: 'restructured', path: 'seeding', oldValue: 'top-level properties', newValue: 'nested seeding object', reason: 'Organized seeding configuration into dedicated section' }); changes.push({ type: 'added', path: 'schema', newValue: modernConfig.schema, reason: 'Added comprehensive schema-first configuration' }); changes.push({ type: 'added', path: 'execution', newValue: modernConfig.execution, reason: 'Added execution behavior configuration' }); // Track specific feature additions if (modernConfig.seeding.enableSchemaIntrospection) { changes.push({ type: 'added', path: 'seeding.enableSchemaIntrospection', newValue: true, reason: 'Enabled dynamic schema discovery' }); } if (modernConfig.schema.columnMapping.enableDynamicMapping) { changes.push({ type: 'added', path: 'schema.columnMapping.enableDynamicMapping', newValue: true, reason: 'Enabled intelligent column mapping' }); } } /** * Apply schema analysis results to configuration */ applySchemaAnalysisToConfig(modernConfig, schemaAnalysis, changes) { // Update framework detection based on analysis if (schemaAnalysis.framework && !modernConfig.schema.frameworkOverride) { modernConfig.schema.frameworkOverride = schemaAnalysis.framework.type; changes.push({ type: 'added', path: 'schema.frameworkOverride', newValue: schemaAnalysis.framework.type, reason: `Framework auto-detected as ${schemaAnalysis.framework.type}` }); } // Apply discovered column mappings if (schemaAnalysis.mappings) { Object.assign(modernConfig.schema.columnMapping.customMappings, schemaAnalysis.mappings); changes.push({ type: 'modified', path: 'schema.columnMapping.customMappings', newValue: 'discovered mappings applied', reason: 'Applied column mappings discovered from schema analysis' }); } // Update primary user table if detected if (schemaAnalysis.tables && schemaAnalysis.tables.length > 0) { const userTable = schemaAnalysis.tables.find((t) => t.role === 'user'); if (userTable && !modernConfig.schema.primaryUserTable) { modernConfig.schema.primaryUserTable = userTable.name; changes.push({ type: 'added', path: 'schema.primaryUserTable', newValue: userTable.name, reason: `Primary user table detected as ${userTable.name}` }); } } } /** * Validate the migrated modern configuration */ async validateModernConfig(modernConfig) { const errors = []; const warnings = []; // Validate required fields if (!modernConfig.version) { errors.push('Missing version field'); } if (!modernConfig.supabaseUrl || !modernConfig.supabaseServiceKey) { errors.push('Missing required connection settings'); } // Validate seeding configuration if (modernConfig.seeding.userCount <= 0) { errors.push('userCount must be greater than 0'); } // Validate schema configuration if (modernConfig.schema.columnMapping.minimumConfidence < 0 || modernConfig.schema.columnMapping.minimumConfidence > 1) { warnings.push('minimumConfidence should be between 0 and 1'); } // Validate execution configuration if (modernConfig.execution.maxRetries < 0) { warnings.push('maxRetries should be non-negative'); } if (modernConfig.execution.timeoutMs < 1000) { warnings.push('timeoutMs should be at least 1000ms'); } return { valid: errors.length === 0, errors, warnings }; } /** * Generate recommendations based on migration */ generateRecommendations(legacyConfig, modernConfig, schemaAnalysis, warnings) { const recommendations = []; // Schema analysis recommendations if (!schemaAnalysis) { recommendations.push('Run schema analysis to optimize configuration: npx supa-seed detect'); } else { if (schemaAnalysis.framework.confidence < 0.7) { recommendations.push('Low framework detection confidence. Consider manually specifying framework in config.'); } } // Feature recommendations if (!modernConfig.seeding.enableSchemaIntrospection) { recommendations.push('Enable schema introspection for better compatibility with schema changes.'); } if (!modernConfig.schema.constraints.enableValidation) { recommendations.push('Enable constraint validation to prevent seeding errors.'); } if (!modernConfig.execution.enableRollback) { recommendations.push('Enable rollback for safer seeding operations.'); } // Configuration recommendations if (Object.keys(modernConfig.schema.columnMapping.customMappings).length === 0) { recommendations.push('Add custom column mappings for better seeding accuracy.'); } // Compatibility recommendations if (modernConfig.compatibility.enableLegacyMode) { recommendations.push('Consider disabling legacy mode once migration is tested and working correctly.'); } // Warning-based recommendations if (warnings.length > 0) { recommendations.push(`Address ${warnings.length} migration warnings for optimal performance.`); } return recommendations; } /** * Utility methods */ async readLegacyConfig(path) { const content = await fs.promises.readFile(path, 'utf8'); return JSON.parse(content); } async readConfigFile(path) { const content = await fs.promises.readFile(path, 'utf8'); return JSON.parse(content); } isModernConfig(config) { return config.version === '2.1.0' && config.schema?.columnMapping && config.execution; } generateConfigHash(config) { const configString = JSON.stringify(config, Object.keys(config).sort()); return btoa(configString).slice(0, 16); } async saveModernConfig(config, outputPath) { const content = JSON.stringify(config, null, 2); await fs.promises.writeFile(outputPath, content, 'utf8'); } async createFallbackConfig(legacyConfig) { return { version: '2.1.0', supabaseUrl: legacyConfig.supabaseUrl, supabaseServiceKey: legacyConfig.supabaseServiceKey, environment: legacyConfig.environment, seeding: { userCount: legacyConfig.userCount, setupsPerUser: legacyConfig.setupsPerUser, imagesPerSetup: legacyConfig.imagesPerSetup, enableRealImages: legacyConfig.enableRealImages, seed: legacyConfig.seed, emailDomain: legacyConfig.emailDomain, domain: legacyConfig.domain || 'general', testUserPassword: legacyConfig.testUserPassword || 'password123', enableSchemaIntrospection: false, enableConstraintValidation: false, enableAutoFixes: false, enableProgressiveEnhancement: false, enableGracefulDegradation: true }, schema: { autoDetectFramework: false, columnMapping: { enableDynamicMapping: false, enableFuzzyMatching: false, enablePatternMatching: false, minimumConfidence: 0.3, customMappings: {} }, constraints: { enableValidation: false, enableAutoFixes: false, skipOptionalConstraints: true, createDependenciesOnDemand: false }, relationships: { enableDiscovery: false, respectForeignKeys: false, handleCircularDependencies: false, enableParallelSeeding: false } }, execution: { enableRollback: false, maxRetries: 1, timeoutMs: 30000, continueOnError: true, enableCaching: false, cacheTimeout: 0 }, compatibility: { enableLegacyMode: true, legacyFallbacks: ['simple_profiles', 'accounts_only', 'auth_only'], maintainOldBehavior: true }, migration: { migratedFrom: 'fallback', migrationDate: new Date().toISOString(), originalConfigHash: 'fallback', migrationWarnings: ['Migration failed, using fallback configuration'] } }; } logMigrationResult(result) { if (result.success) { logger_1.Logger.success('✅ Configuration migration completed successfully'); logger_1.Logger.info(` Changes: ${result.changes.length}`); logger_1.Logger.info(` Warnings: ${result.warnings.length}`); logger_1.Logger.info(` Recommendations: ${result.recommendedActions.length}`); } else { logger_1.Logger.error('❌ Configuration migration failed'); logger_1.Logger.error(` Errors: ${result.errors.length}`); logger_1.Logger.error(` Warnings: ${result.warnings.length}`); } if (result.warnings.length > 0) { logger_1.Logger.warn('Migration warnings:'); result.warnings.forEach(warning => logger_1.Logger.warn(` • ${warning}`)); } if (result.recommendedActions.length > 0) { logger_1.Logger.info('Recommended actions:'); result.recommendedActions.forEach(action => logger_1.Logger.info(` • ${action}`)); } } } exports.ConfigMigrator = ConfigMigrator; //# sourceMappingURL=config-migrator.js.map