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
JavaScript
;
/**
* 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