diff --git a/dev/verify_migrations.ps1 b/dev/verify_migrations.ps1 index ad0d34cef1..ce1754e684 100644 --- a/dev/verify_migrations.ps1 +++ b/dev/verify_migrations.ps1 @@ -5,12 +5,19 @@ Validates that new database migration files follow naming conventions and chronological order. .DESCRIPTION - This script validates migration files in util/Migrator/DbScripts/ to ensure: + This script validates migration files to ensure: + + For SQL migrations in util/Migrator/DbScripts/: 1. New migrations follow the naming format: YYYY-MM-DD_NN_Description.sql 2. New migrations are chronologically ordered (filename sorts after existing migrations) 3. Dates use leading zeros (e.g., 2025-01-05, not 2025-1-5) 4. A 2-digit sequence number is included (e.g., _00, _01) + For Entity Framework migrations in util/MySqlMigrations, util/PostgresMigrations, util/SqliteMigrations: + 1. New migrations follow the naming format: YYYYMMDDHHMMSS_Description.cs + 2. Each migration has both .cs and .Designer.cs files + 3. New migrations are chronologically ordered (timestamp sorts after existing migrations) + .PARAMETER BaseRef The base git reference to compare against (e.g., 'main', 'HEAD~1') @@ -58,75 +65,288 @@ $currentMigrations = git ls-tree -r --name-only $CurrentRef -- "$migrationPath/" # Find added migrations $addedMigrations = $currentMigrations | Where-Object { $_ -notin $baseMigrations } +$sqlValidationFailed = $false + if ($addedMigrations.Count -eq 0) { - Write-Host "No new migration files added." - exit 0 + Write-Host "No new SQL migration files added." + Write-Host "" +} +else { + Write-Host "New SQL migration files detected:" + $addedMigrations | ForEach-Object { Write-Host " $_" } + Write-Host "" + + # Get the last migration from base reference + if ($baseMigrations.Count -eq 0) { + Write-Host "No previous SQL migrations found (initial commit?). Skipping chronological validation." + Write-Host "" + } + else { + $lastBaseMigration = Split-Path -Leaf ($baseMigrations | Select-Object -Last 1) + Write-Host "Last SQL migration in base reference: $lastBaseMigration" + Write-Host "" + + # Required format regex: YYYY-MM-DD_NN_Description.sql + $formatRegex = '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}_.+\.sql$' + + foreach ($migration in $addedMigrations) { + $migrationName = Split-Path -Leaf $migration + + # Validate NEW migration filename format + if ($migrationName -notmatch $formatRegex) { + Write-Host "ERROR: Migration '$migrationName' does not match required format" + Write-Host "Required format: YYYY-MM-DD_NN_Description.sql" + Write-Host " - YYYY: 4-digit year" + Write-Host " - MM: 2-digit month with leading zero (01-12)" + Write-Host " - DD: 2-digit day with leading zero (01-31)" + Write-Host " - NN: 2-digit sequence number (00, 01, 02, etc.)" + Write-Host "Example: 2025-01-15_00_MyMigration.sql" + $sqlValidationFailed = $true + continue + } + + # Compare migration name with last base migration (using ordinal string comparison) + if ([string]::CompareOrdinal($migrationName, $lastBaseMigration) -lt 0) { + Write-Host "ERROR: New migration '$migrationName' is not chronologically after '$lastBaseMigration'" + $sqlValidationFailed = $true + } + else { + Write-Host "OK: '$migrationName' is chronologically after '$lastBaseMigration'" + } + } + + Write-Host "" + } + + if ($sqlValidationFailed) { + Write-Host "FAILED: One or more SQL migrations are incorrectly named or not in chronological order" + Write-Host "" + Write-Host "All new SQL migration files must:" + Write-Host " 1. Follow the naming format: YYYY-MM-DD_NN_Description.sql" + Write-Host " 2. Use leading zeros in dates (e.g., 2025-01-05, not 2025-1-5)" + Write-Host " 3. Include a 2-digit sequence number (e.g., _00, _01)" + Write-Host " 4. Have a filename that sorts after the last migration in base" + Write-Host "" + Write-Host "To fix this issue:" + Write-Host " 1. Locate your migration file(s) in util/Migrator/DbScripts/" + Write-Host " 2. Rename to follow format: YYYY-MM-DD_NN_Description.sql" + Write-Host " 3. Ensure the date is after $lastBaseMigration" + Write-Host "" + Write-Host "Example: 2025-01-15_00_AddNewFeature.sql" + } + else { + Write-Host "SUCCESS: All new SQL migrations are correctly named and in chronological order" + } + + Write-Host "" } -Write-Host "New migration files detected:" -$addedMigrations | ForEach-Object { Write-Host " $_" } +# =========================================================================================== +# Validate Entity Framework Migrations +# =========================================================================================== + +Write-Host "===================================================================" +Write-Host "Validating Entity Framework Migrations" +Write-Host "===================================================================" Write-Host "" -# Get the last migration from base reference -if ($baseMigrations.Count -eq 0) { - Write-Host "No previous migrations found (initial commit?). Skipping validation." - exit 0 -} +$efMigrationPaths = @( + @{ Path = "util/MySqlMigrations/Migrations"; Name = "MySQL" }, + @{ Path = "util/PostgresMigrations/Migrations"; Name = "Postgres" }, + @{ Path = "util/SqliteMigrations/Migrations"; Name = "SQLite" } +) -$lastBaseMigration = Split-Path -Leaf ($baseMigrations | Select-Object -Last 1) -Write-Host "Last migration in base reference: $lastBaseMigration" -Write-Host "" +$efValidationFailed = $false -# Required format regex: YYYY-MM-DD_NN_Description.sql -$formatRegex = '^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}_.+\.sql$' +foreach ($migrationPathInfo in $efMigrationPaths) { + $efPath = $migrationPathInfo.Path + $dbName = $migrationPathInfo.Name -$validationFailed = $false + Write-Host "-------------------------------------------------------------------" + Write-Host "Checking $dbName EF migrations in $efPath" + Write-Host "-------------------------------------------------------------------" + Write-Host "" -foreach ($migration in $addedMigrations) { - $migrationName = Split-Path -Leaf $migration + # Get list of migrations from base reference + try { + $baseMigrations = git ls-tree -r --name-only $BaseRef -- "$efPath/" 2>$null | Where-Object { $_ -like "*.cs" -and $_ -notlike "*DatabaseContextModelSnapshot.cs" } | Sort-Object + if ($LASTEXITCODE -ne 0) { + Write-Host "Warning: Could not retrieve $dbName migrations from base reference '$BaseRef'" + $baseMigrations = @() + } + } + catch { + Write-Host "Warning: Could not retrieve $dbName migrations from base reference '$BaseRef'" + $baseMigrations = @() + } - # Validate NEW migration filename format - if ($migrationName -notmatch $formatRegex) { - Write-Host "ERROR: Migration '$migrationName' does not match required format" - Write-Host "Required format: YYYY-MM-DD_NN_Description.sql" - Write-Host " - YYYY: 4-digit year" - Write-Host " - MM: 2-digit month with leading zero (01-12)" - Write-Host " - DD: 2-digit day with leading zero (01-31)" - Write-Host " - NN: 2-digit sequence number (00, 01, 02, etc.)" - Write-Host "Example: 2025-01-15_00_MyMigration.sql" - $validationFailed = $true + # Get list of migrations from current reference + $currentMigrations = git ls-tree -r --name-only $CurrentRef -- "$efPath/" | Where-Object { $_ -like "*.cs" -and $_ -notlike "*DatabaseContextModelSnapshot.cs" } | Sort-Object + + # Find added migrations + $addedMigrations = $currentMigrations | Where-Object { $_ -notin $baseMigrations } + + if ($addedMigrations.Count -eq 0) { + Write-Host "No new $dbName EF migration files added." + Write-Host "" continue } - # Compare migration name with last base migration (using ordinal string comparison) - if ([string]::CompareOrdinal($migrationName, $lastBaseMigration) -lt 0) { - Write-Host "ERROR: New migration '$migrationName' is not chronologically after '$lastBaseMigration'" - $validationFailed = $true + Write-Host "New $dbName EF migration files detected:" + $addedMigrations | ForEach-Object { Write-Host " $_" } + Write-Host "" + + # Get the last migration from base reference + if ($baseMigrations.Count -eq 0) { + Write-Host "No previous $dbName migrations found. Skipping chronological validation." + Write-Host "" } else { - Write-Host "OK: '$migrationName' is chronologically after '$lastBaseMigration'" + $lastBaseMigration = Split-Path -Leaf ($baseMigrations | Select-Object -Last 1) + Write-Host "Last $dbName migration in base reference: $lastBaseMigration" + Write-Host "" } + + # Required format regex: YYYYMMDDHHMMSS_Description.cs or YYYYMMDDHHMMSS_Description.Designer.cs + $efFormatRegex = '^[0-9]{14}_.+\.cs$' + + # Group migrations by base name (without .Designer.cs suffix) + $migrationGroups = @{} + $unmatchedFiles = @() + + foreach ($migration in $addedMigrations) { + $migrationName = Split-Path -Leaf $migration + + # Extract base name (remove .Designer.cs or .cs) + if ($migrationName -match '^([0-9]{14}_.+?)(?:\.Designer)?\.cs$') { + $baseName = $matches[1] + if (-not $migrationGroups.ContainsKey($baseName)) { + $migrationGroups[$baseName] = @() + } + $migrationGroups[$baseName] += $migrationName + } + else { + # Track files that don't match the expected pattern + $unmatchedFiles += $migrationName + } + } + + # Flag any files that don't match the expected pattern + if ($unmatchedFiles.Count -gt 0) { + Write-Host "ERROR: The following migration files do not match the required format:" + foreach ($unmatchedFile in $unmatchedFiles) { + Write-Host " - $unmatchedFile" + } + Write-Host "" + Write-Host "Required format: YYYYMMDDHHMMSS_Description.cs or YYYYMMDDHHMMSS_Description.Designer.cs" + Write-Host " - YYYYMMDDHHMMSS: 14-digit timestamp (Year, Month, Day, Hour, Minute, Second)" + Write-Host " - Description: Descriptive name using PascalCase" + Write-Host "Example: 20250115120000_AddNewFeature.cs and 20250115120000_AddNewFeature.Designer.cs" + Write-Host "" + $efValidationFailed = $true + } + + foreach ($baseName in $migrationGroups.Keys | Sort-Object) { + $files = $migrationGroups[$baseName] + + # Validate format + $mainFile = "$baseName.cs" + $designerFile = "$baseName.Designer.cs" + + if ($mainFile -notmatch $efFormatRegex) { + Write-Host "ERROR: Migration '$mainFile' does not match required format" + Write-Host "Required format: YYYYMMDDHHMMSS_Description.cs" + Write-Host " - YYYYMMDDHHMMSS: 14-digit timestamp (Year, Month, Day, Hour, Minute, Second)" + Write-Host "Example: 20250115120000_AddNewFeature.cs" + $efValidationFailed = $true + continue + } + + # Check that both .cs and .Designer.cs files exist + $hasCsFile = $files -contains $mainFile + $hasDesignerFile = $files -contains $designerFile + + if (-not $hasCsFile) { + Write-Host "ERROR: Missing main migration file: $mainFile" + $efValidationFailed = $true + } + + if (-not $hasDesignerFile) { + Write-Host "ERROR: Missing designer file: $designerFile" + Write-Host "Each EF migration must have both a .cs and .Designer.cs file" + $efValidationFailed = $true + } + + if (-not $hasCsFile -or -not $hasDesignerFile) { + continue + } + + # Compare migration timestamp with last base migration (using ordinal string comparison) + if ($baseMigrations.Count -gt 0) { + if ([string]::CompareOrdinal($mainFile, $lastBaseMigration) -lt 0) { + Write-Host "ERROR: New migration '$mainFile' is not chronologically after '$lastBaseMigration'" + $efValidationFailed = $true + } + else { + Write-Host "OK: '$mainFile' is chronologically after '$lastBaseMigration'" + } + } + else { + Write-Host "OK: '$mainFile' (no previous migrations to compare)" + } + } + + Write-Host "" +} + +if ($efValidationFailed) { + Write-Host "FAILED: One or more EF migrations are incorrectly named or not in chronological order" + Write-Host "" + Write-Host "All new EF migration files must:" + Write-Host " 1. Follow the naming format: YYYYMMDDHHMMSS_Description.cs" + Write-Host " 2. Include both .cs and .Designer.cs files" + Write-Host " 3. Have a timestamp that sorts after the last migration in base" + Write-Host "" + Write-Host "To fix this issue:" + Write-Host " 1. Locate your migration file(s) in the respective Migrations directory" + Write-Host " 2. Ensure both .cs and .Designer.cs files exist" + Write-Host " 3. Rename to follow format: YYYYMMDDHHMMSS_Description.cs" + Write-Host " 4. Ensure the timestamp is after the last migration" + Write-Host "" + Write-Host "Example: 20250115120000_AddNewFeature.cs and 20250115120000_AddNewFeature.Designer.cs" +} +else { + Write-Host "SUCCESS: All new EF migrations are correctly named and in chronological order" } Write-Host "" +Write-Host "===================================================================" +Write-Host "Validation Summary" +Write-Host "===================================================================" + +if ($sqlValidationFailed -or $efValidationFailed) { + if ($sqlValidationFailed) { + Write-Host "❌ SQL migrations validation FAILED" + } + else { + Write-Host "✓ SQL migrations validation PASSED" + } + + if ($efValidationFailed) { + Write-Host "❌ EF migrations validation FAILED" + } + else { + Write-Host "✓ EF migrations validation PASSED" + } -if ($validationFailed) { - Write-Host "FAILED: One or more migrations are incorrectly named or not in chronological order" Write-Host "" - Write-Host "All new migration files must:" - Write-Host " 1. Follow the naming format: YYYY-MM-DD_NN_Description.sql" - Write-Host " 2. Use leading zeros in dates (e.g., 2025-01-05, not 2025-1-5)" - Write-Host " 3. Include a 2-digit sequence number (e.g., _00, _01)" - Write-Host " 4. Have a filename that sorts after the last migration in base" - Write-Host "" - Write-Host "To fix this issue:" - Write-Host " 1. Locate your migration file(s) in util/Migrator/DbScripts/" - Write-Host " 2. Rename to follow format: YYYY-MM-DD_NN_Description.sql" - Write-Host " 3. Ensure the date is after $lastBaseMigration" - Write-Host "" - Write-Host "Example: 2025-01-15_00_AddNewFeature.sql" + Write-Host "OVERALL RESULT: FAILED" exit 1 } - -Write-Host "SUCCESS: All new migrations are correctly named and in chronological order" -exit 0 +else { + Write-Host "✓ SQL migrations validation PASSED" + Write-Host "✓ EF migrations validation PASSED" + Write-Host "" + Write-Host "OVERALL RESULT: SUCCESS" + exit 0 +}