Private/SqlPackageHelpers.ps1

# SqlPackageHelpers.ps1
# Funciones helper para Invoke-SqlPackage

<#
.SYNOPSIS
    Lee y parsea el archivo sqlpackage.yaml del directorio actual.
 
.DESCRIPTION
    Usa ConvertFrom-Yaml (powershell-yaml) para parsear el archivo de configuración.
    Valida que existan las secciones mínimas requeridas (properties).
 
.PARAMETER Path
    Ruta al archivo sqlpackage.yaml. Por defecto: ./sqlpackage.yaml
 
.OUTPUTS
    Hashtable con la configuración parseada.
#>

function Read-SqlPackageConfig {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Path = ".\sqlpackage.yaml"
    )

    if (Get-Command -Name Ensure-YamlModule -ErrorAction SilentlyContinue) {
        Ensure-YamlModule
    }
    elseif (-not (Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue)) {
        try {
            Import-Module powershell-yaml -ErrorAction Stop | Out-Null
        }
        catch {
            throw "No se encontró el módulo 'powershell-yaml'. Instale con: Install-Module powershell-yaml -Scope CurrentUser -Force"
        }
    }

    if (-not (Test-Path $Path)) {
        throw "No se encontró '$Path'. Ejecute 'Invoke-SqlPackage -Init' para generarlo."
    }

    $raw = Get-Content $Path -Raw
    $config = ConvertFrom-Yaml $raw

    if (-not $config.properties) {
        throw "El archivo '$Path' no tiene la sección 'properties' requerida."
    }

    return $config
}

<#
.SYNOPSIS
    Construye el array de argumentos para sqlpackage.exe.
 
.DESCRIPTION
    Traduce la configuración YAML + credenciales .env en argumentos de línea de comando
    compatibles con sqlpackage.exe.
 
.PARAMETER Action
    La acción de SqlPackage (Publish, DeployReport, Script, Extract, Export, Import).
 
.PARAMETER Config
    Hashtable de configuración leída de sqlpackage.yaml.
 
.PARAMETER EnvVars
    Hashtable de variables de entorno leídas de .env.
 
.PARAMETER DacpacPath
    Ruta al archivo .dacpac (requerido para Publish, DeployReport, Script).
 
.PARAMETER OutputPath
    Ruta de salida para el archivo generado (requerido para DeployReport, Script, Extract, Export).
 
.PARAMETER SourcePath
    Ruta al archivo fuente .bacpac (requerido para Import).
 
.OUTPUTS
    String[] — Array de argumentos para sqlpackage.exe.
#>

function Build-SqlPackageArgs {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Publish', 'DeployReport', 'Script', 'Extract', 'Export', 'Import')]
        [string]$Action,

        [Parameter(Mandatory)]
        [hashtable]$Config,

        [Parameter(Mandatory)]
        [hashtable]$EnvVars,

        [Parameter()]
        [string]$DacpacPath,

        [Parameter()]
        [string]$OutputPath,

        [Parameter()]
        [string]$SourcePath
    )

    $sqlArgs = @("/Action:$Action")

    # Conexión al servidor (credenciales desde .env)
    $server   = $EnvVars['DB_SERVER']
    $database = $EnvVars['DB_NAME']
    $user     = $EnvVars['DB_USER']
    $password = $EnvVars['DB_PASSWORD']

    if (-not $server -or -not $database -or -not $user -or -not $password) {
        throw "Faltan variables en .env. Se requieren: DB_SERVER, DB_NAME, DB_USER, DB_PASSWORD"
    }

    # Acciones que usan Source (el .dacpac como fuente)
    $dacpacActions = @('Publish', 'DeployReport', 'Script')
    # Acciones que leen del servidor (Extract, Export)
    $serverSourceActions = @('Extract', 'Export')

    if ($Action -in $dacpacActions) {
        if (-not $DacpacPath) {
            throw "Se requiere -DacpacPath para la acción '$Action'"
        }
        $sqlArgs += "/SourceFile:$DacpacPath"
        $sqlArgs += "/TargetServerName:$server"
        $sqlArgs += "/TargetDatabaseName:$database"
        $sqlArgs += "/TargetUser:$user"
        $sqlArgs += "/TargetPassword:$password"
        $sqlArgs += "/TargetTrustServerCertificate:True"
        $sqlArgs += "/TargetEncryptConnection:True"
    }
    elseif ($Action -in $serverSourceActions) {
        $sqlArgs += "/SourceServerName:$server"
        $sqlArgs += "/SourceDatabaseName:$database"
        $sqlArgs += "/SourceUser:$user"
        $sqlArgs += "/SourcePassword:$password"
        $sqlArgs += "/SourceTrustServerCertificate:True"
        $sqlArgs += "/SourceEncryptConnection:True"
    }
    elseif ($Action -eq 'Import') {
        if (-not $SourcePath) {
            throw "Se requiere la ruta al archivo .bacpac en la configuración (import.sourcePath)"
        }
        $sqlArgs += "/SourceFile:$SourcePath"
        $sqlArgs += "/TargetServerName:$server"
        $sqlArgs += "/TargetDatabaseName:$database"
        $sqlArgs += "/TargetUser:$user"
        $sqlArgs += "/TargetPassword:$password"
        $sqlArgs += "/TargetTrustServerCertificate:True"
        $sqlArgs += "/TargetEncryptConnection:True"
    }

    # Output path
    if ($OutputPath) {
        if ($Action -in $serverSourceActions) {
            $sqlArgs += "/TargetFile:$OutputPath"
        }
        else {
            $sqlArgs += "/OutputPath:$OutputPath"
        }
    }

    # Propiedades /p: (solo para acciones que las soportan)
    $propsActions = @('Publish', 'DeployReport', 'Script')
    if ($Action -in $propsActions -and $Config.properties) {
        foreach ($key in $Config.properties.Keys) {
            $sqlArgs += "/p:$key=$($Config.properties[$key])"
        }
    }

    return $sqlArgs
}

<#
.SYNOPSIS
    Busca el archivo .dacpac correspondiente al proyecto SQL actual.
 
.DESCRIPTION
    Localiza el archivo .sqlproj en el directorio actual, extrae el nombre del proyecto
    y construye la ruta esperada del .dacpac en bin/Debug/.
 
.OUTPUTS
    String — Ruta al archivo .dacpac.
#>

function Find-DacpacPath {
    [CmdletBinding()]
    param()

    $sqlproj = Get-ChildItem -Path "." -Filter "*.sqlproj" -File | Select-Object -First 1
    if (-not $sqlproj) {
        throw "No se encontró un archivo .sqlproj en el directorio actual."
    }

    $projectName = [System.IO.Path]::GetFileNameWithoutExtension($sqlproj.Name)
    $dacpacPath = ".\bin\Debug\$projectName.dacpac"

    return $dacpacPath
}

<#
.SYNOPSIS
    Parsea un DeployReport XML y muestra un resumen visual de las operaciones.
 
.DESCRIPTION
    Lee el archivo XML generado por SqlPackage /Action:DeployReport y muestra
    cada operación detectada con formato y colores.
 
.PARAMETER ReportPath
    Ruta al archivo XML del DeployReport.
 
.OUTPUTS
    PSCustomObject[] — Array de operaciones encontradas, o $null si no hay cambios.
#>

function Show-DeployReport {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ReportPath
    )

    if (-not (Test-Path $ReportPath)) {
        throw "No se encontró el reporte: $ReportPath"
    }

    [xml]$report = Get-Content $ReportPath
    $operations = $report.DeploymentReport.Operations.Operation

    if ($null -eq $operations) {
        Write-Host " No hay cambios pendientes. La base de datos está sincronizada." -ForegroundColor Green
        return $null
    }

    Write-Host ""
    Write-Host " Cambios detectados:" -ForegroundColor Cyan
    $results = @()

    $operations | ForEach-Object {
        $op = $_.Name
        $items = $_.Item
        if ($items -is [array]) {
            $items | ForEach-Object {
                Write-Host " $op → $($_.Value)" -ForegroundColor Yellow
                $results += [PSCustomObject]@{ Operation = $op; Object = $_.Value }
            }
        }
        else {
            Write-Host " $op → $($items.Value)" -ForegroundColor Yellow
            $results += [PSCustomObject]@{ Operation = $op; Object = $items.Value }
        }
    }

    Write-Host ""
    return $results
}

<#
.SYNOPSIS
    Copia las plantillas de configuración al directorio actual.
 
.DESCRIPTION
    Copia sqlpackage.yaml y .env.example desde los templates del módulo
    al directorio de trabajo actual. No sobrescribe archivos existentes.
 
.PARAMETER Force
    Sobrescribir archivos existentes.
#>

function New-SqlPackageConfig {
    [CmdletBinding()]
    param(
        [switch]$Force
    )

    $templateDir = Join-Path $PSScriptRoot "..\Resources\Invoke-SqlPackage\templates"

    # sqlpackage.yaml
    $yamlTarget = Join-Path (Get-Location) "sqlpackage.yaml"
    $yamlSource = Join-Path $templateDir "sqlpackage.yaml"

    if ((Test-Path $yamlTarget) -and -not $Force) {
        Write-Host " sqlpackage.yaml ya existe (use -Force para sobrescribir)" -ForegroundColor Yellow
    }
    else {
        Copy-Item $yamlSource $yamlTarget -Force
        Write-Host " sqlpackage.yaml creado" -ForegroundColor Green
    }

    # .env
    $envTarget = Join-Path (Get-Location) ".env"
    $envExampleSource = Join-Path $templateDir ".env.example"

    if ((Test-Path $envTarget) -and -not $Force) {
        Write-Host " .env ya existe (use -Force para sobrescribir)" -ForegroundColor Yellow
    }
    else {
        Copy-Item $envExampleSource $envTarget -Force
        Write-Host " .env creado (configure las credenciales)" -ForegroundColor Green
    }
}