mirror of
https://github.com/ProjectSkyfire/SkyFire_548
synced 2026-08-18 06:32:13 -04:00
Add pending SQL promotion tool
Signed-off-by: AlterEgo <admin@projectskyfire.org>
This commit is contained in:
parent
b4fe165ef2
commit
40cdda196a
1 changed files with 296 additions and 0 deletions
296
tools/dev/promote_pending_updates.ps1
Normal file
296
tools/dev/promote_pending_updates.ps1
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Promotes pending SQL updates into the normal update stream.
|
||||
|
||||
.DESCRIPTION
|
||||
Moves SQL files from sql/pending_updates/<database> to sql/updates/<database>
|
||||
and renumbers them to the next available official update sequence.
|
||||
|
||||
The script runs in dry-run mode by default. Use -Apply to move files.
|
||||
|
||||
.EXAMPLE
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File tools/dev/promote_pending_updates.ps1
|
||||
|
||||
.EXAMPLE
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File tools/dev/promote_pending_updates.ps1 -Database world -Apply
|
||||
|
||||
.EXAMPLE
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File tools/dev/promote_pending_updates.ps1 -InputPath sql/pending_updates/world/2026-06-22_world_04_areatrigger_tavern_missing.sql -Apply
|
||||
#>
|
||||
|
||||
[CmdletBinding(SupportsShouldProcess = $true)]
|
||||
param(
|
||||
[string] $Root = '',
|
||||
|
||||
[ValidateSet('auth', 'characters', 'world')]
|
||||
[string[]] $Database,
|
||||
|
||||
[string[]] $InputPath,
|
||||
|
||||
[switch] $Apply
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ValidDatabases = @('auth', 'characters', 'world')
|
||||
$UpdateNamePattern = '^(?<date>\d{4}[-_]\d{2}[-_]\d{2})_(?<database>auth|characters|world)_(?<sequence>\d{2})(?<suffix>.*)\.sql$'
|
||||
|
||||
function Resolve-RepoRoot {
|
||||
param([string] $RequestedRoot)
|
||||
|
||||
if ($RequestedRoot) {
|
||||
return (Resolve-Path -LiteralPath $RequestedRoot).ProviderPath
|
||||
}
|
||||
|
||||
$gitRoot = & git rev-parse --show-toplevel 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $gitRoot) {
|
||||
return (Resolve-Path -LiteralPath $gitRoot.Trim()).ProviderPath
|
||||
}
|
||||
|
||||
return (Resolve-Path -LiteralPath '.').ProviderPath
|
||||
}
|
||||
|
||||
function Convert-ToRepoPath {
|
||||
param(
|
||||
[string] $Path,
|
||||
[string] $RootPath
|
||||
)
|
||||
|
||||
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
||||
$fullRoot = [System.IO.Path]::GetFullPath($RootPath)
|
||||
|
||||
if (-not $fullRoot.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
|
||||
$fullRoot += [System.IO.Path]::DirectorySeparatorChar
|
||||
}
|
||||
|
||||
if ($fullPath.StartsWith($fullRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return $fullPath.Substring($fullRoot.Length).Replace('\', '/')
|
||||
}
|
||||
|
||||
return $fullPath.Replace('\', '/')
|
||||
}
|
||||
|
||||
function Test-IsUnderPath {
|
||||
param(
|
||||
[string] $Path,
|
||||
[string] $ParentPath
|
||||
)
|
||||
|
||||
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
||||
$fullParent = [System.IO.Path]::GetFullPath($ParentPath)
|
||||
|
||||
if (-not $fullParent.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
|
||||
$fullParent += [System.IO.Path]::DirectorySeparatorChar
|
||||
}
|
||||
|
||||
return $fullPath.StartsWith($fullParent, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
}
|
||||
|
||||
function Get-SelectedDatabases {
|
||||
param([string[]] $RequestedDatabases)
|
||||
|
||||
if ($RequestedDatabases -and $RequestedDatabases.Count -gt 0) {
|
||||
return $RequestedDatabases
|
||||
}
|
||||
|
||||
return $ValidDatabases
|
||||
}
|
||||
|
||||
function Get-PendingSqlFiles {
|
||||
param(
|
||||
[string] $RootPath,
|
||||
[string[]] $RequestedDatabases,
|
||||
[string[]] $RequestedPaths
|
||||
)
|
||||
|
||||
$files = @()
|
||||
|
||||
if ($RequestedPaths -and $RequestedPaths.Count -gt 0) {
|
||||
foreach ($requestedPath in $RequestedPaths) {
|
||||
$candidate = $requestedPath
|
||||
if (-not [System.IO.Path]::IsPathRooted($candidate)) {
|
||||
$candidate = Join-Path $RootPath $candidate
|
||||
}
|
||||
|
||||
$resolved = Resolve-Path -LiteralPath $candidate
|
||||
foreach ($item in $resolved) {
|
||||
if (Test-Path -LiteralPath $item.ProviderPath -PathType Container) {
|
||||
$files += Get-ChildItem -LiteralPath $item.ProviderPath -Filter '*.sql' -File | Sort-Object FullName
|
||||
}
|
||||
else {
|
||||
$files += Get-Item -LiteralPath $item.ProviderPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($databaseName in (Get-SelectedDatabases -RequestedDatabases $RequestedDatabases)) {
|
||||
$pendingDir = Join-Path $RootPath "sql\pending_updates\$databaseName"
|
||||
if (Test-Path -LiteralPath $pendingDir -PathType Container) {
|
||||
$files += Get-ChildItem -LiteralPath $pendingDir -Filter '*.sql' -File | Sort-Object FullName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $files | Sort-Object FullName -Unique
|
||||
}
|
||||
|
||||
function Get-PendingUpdateInfo {
|
||||
param(
|
||||
[System.IO.FileInfo] $File,
|
||||
[string] $RootPath
|
||||
)
|
||||
|
||||
if ($File.Extension -ne '.sql') {
|
||||
throw "Pending update '$($File.FullName)' is not a .sql file."
|
||||
}
|
||||
|
||||
$pendingRoot = Join-Path $RootPath 'sql\pending_updates'
|
||||
if (-not (Test-IsUnderPath -Path $File.FullName -ParentPath $pendingRoot)) {
|
||||
throw "Refusing '$($File.FullName)' because it is not under sql/pending_updates."
|
||||
}
|
||||
|
||||
$relativePath = Convert-ToRepoPath -Path $File.FullName -RootPath $RootPath
|
||||
$relativeParts = $relativePath.Split('/')
|
||||
if ($relativeParts.Count -ne 4 -or $relativeParts[0] -ne 'sql' -or $relativeParts[1] -ne 'pending_updates') {
|
||||
throw "Refusing '$relativePath' because pending updates must be directly under sql/pending_updates/<database>."
|
||||
}
|
||||
|
||||
$databaseName = $relativeParts[2]
|
||||
if ($ValidDatabases -notcontains $databaseName) {
|
||||
throw "Refusing '$relativePath' because '$databaseName' is not a known database folder."
|
||||
}
|
||||
|
||||
$match = [regex]::Match($File.Name, $UpdateNamePattern)
|
||||
if (-not $match.Success) {
|
||||
throw "Pending update '$relativePath' must be named like YYYY-MM-DD_${databaseName}_00_description.sql."
|
||||
}
|
||||
|
||||
$fileDatabaseName = $match.Groups['database'].Value
|
||||
if ($fileDatabaseName -ne $databaseName) {
|
||||
throw "Pending update '$relativePath' is in the '$databaseName' folder but its filename says '$fileDatabaseName'."
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Source = $File
|
||||
Database = $databaseName
|
||||
Date = $match.Groups['date'].Value
|
||||
Suffix = $match.Groups['suffix'].Value
|
||||
}
|
||||
}
|
||||
|
||||
function Get-MaxOfficialSequence {
|
||||
param(
|
||||
[string] $RootPath,
|
||||
[string] $DatabaseName,
|
||||
[string] $DateText
|
||||
)
|
||||
|
||||
$updatesDir = Join-Path $RootPath "sql\updates\$DatabaseName"
|
||||
if (-not (Test-Path -LiteralPath $updatesDir -PathType Container)) {
|
||||
return -1
|
||||
}
|
||||
|
||||
$maxSequence = -1
|
||||
foreach ($file in (Get-ChildItem -LiteralPath $updatesDir -Filter '*.sql' -File)) {
|
||||
$match = [regex]::Match($file.Name, $UpdateNamePattern)
|
||||
if (-not $match.Success) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($match.Groups['database'].Value -ne $DatabaseName -or $match.Groups['date'].Value -ne $DateText) {
|
||||
continue
|
||||
}
|
||||
|
||||
$sequence = [int] $match.Groups['sequence'].Value
|
||||
if ($sequence -gt $maxSequence) {
|
||||
$maxSequence = $sequence
|
||||
}
|
||||
}
|
||||
|
||||
return $maxSequence
|
||||
}
|
||||
|
||||
function New-PromotionPlan {
|
||||
param(
|
||||
[string] $RootPath,
|
||||
[System.IO.FileInfo[]] $Files
|
||||
)
|
||||
|
||||
$infos = @()
|
||||
foreach ($file in $Files) {
|
||||
$infos += Get-PendingUpdateInfo -File $file -RootPath $RootPath
|
||||
}
|
||||
|
||||
$actions = @()
|
||||
$groups = $infos | Group-Object Database, Date
|
||||
|
||||
foreach ($group in $groups) {
|
||||
$first = $group.Group | Select-Object -First 1
|
||||
$nextSequence = (Get-MaxOfficialSequence -RootPath $RootPath -DatabaseName $first.Database -DateText $first.Date) + 1
|
||||
$updatesDir = Join-Path $RootPath "sql\updates\$($first.Database)"
|
||||
|
||||
foreach ($info in ($group.Group | Sort-Object { $_.Source.Name })) {
|
||||
$targetName = '{0}_{1}_{2:D2}{3}.sql' -f $info.Date, $info.Database, $nextSequence, $info.Suffix
|
||||
$targetPath = Join-Path $updatesDir $targetName
|
||||
|
||||
if (Test-Path -LiteralPath $targetPath) {
|
||||
throw "Target update already exists: $(Convert-ToRepoPath -Path $targetPath -RootPath $RootPath)"
|
||||
}
|
||||
|
||||
$actions += [PSCustomObject]@{
|
||||
Source = $info.Source.FullName
|
||||
Target = $targetPath
|
||||
Database = $info.Database
|
||||
}
|
||||
|
||||
$nextSequence++
|
||||
}
|
||||
}
|
||||
|
||||
return $actions | Sort-Object Database, Source
|
||||
}
|
||||
|
||||
function Invoke-PromotionPlan {
|
||||
param(
|
||||
[string] $RootPath,
|
||||
[object[]] $Actions,
|
||||
[switch] $ApplyChanges
|
||||
)
|
||||
|
||||
foreach ($action in $Actions) {
|
||||
$sourceRelative = Convert-ToRepoPath -Path $action.Source -RootPath $RootPath
|
||||
$targetRelative = Convert-ToRepoPath -Path $action.Target -RootPath $RootPath
|
||||
|
||||
if (-not $ApplyChanges) {
|
||||
Write-Host "DRY-RUN: $sourceRelative -> $targetRelative"
|
||||
continue
|
||||
}
|
||||
|
||||
$targetDirectory = Split-Path -Parent $action.Target
|
||||
if (-not (Test-Path -LiteralPath $targetDirectory -PathType Container)) {
|
||||
New-Item -ItemType Directory -Force -Path $targetDirectory | Out-Null
|
||||
}
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($targetRelative, "Promote pending SQL update from $sourceRelative")) {
|
||||
Move-Item -LiteralPath $action.Source -Destination $action.Target
|
||||
Write-Host "PROMOTED: $sourceRelative -> $targetRelative"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$repoRoot = Resolve-RepoRoot -RequestedRoot $Root
|
||||
$pendingFiles = @(Get-PendingSqlFiles -RootPath $repoRoot -RequestedDatabases $Database -RequestedPaths $InputPath)
|
||||
|
||||
if ($pendingFiles.Count -eq 0) {
|
||||
Write-Host 'No pending SQL updates found.'
|
||||
exit 0
|
||||
}
|
||||
|
||||
$plan = @(New-PromotionPlan -RootPath $repoRoot -Files $pendingFiles)
|
||||
Invoke-PromotionPlan -RootPath $repoRoot -Actions $plan -ApplyChanges:$Apply
|
||||
|
||||
if (-not $Apply) {
|
||||
Write-Host ''
|
||||
Write-Host 'Dry-run only. Re-run with -Apply to move these files.'
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue