Modules/businessdev.ALbuild.Core/Private/Read-ALbuildProjectConfigFile.ps1

function Read-ALbuildProjectConfigFile {
    <#
    .SYNOPSIS
        Reads a single ALbuild project config file from a folder, as a settings hashtable.
 
    .DESCRIPTION
        Internal helper. Looks for the canonical 'albuild.json' in the folder; if absent, falls
        back to the deprecated V1 'pipeline.config' (emitting a one-time deprecation warning per
        folder and translating its 'alTestRunnerId' to 'TestRunnerCodeunitId'). Returns $null when
        neither file is present.
 
    .PARAMETER Folder
        The folder to read the config file from.
 
    .OUTPUTS
        System.Collections.Hashtable, or $null when no config file exists.
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory)] [string] $Folder
    )

    $canonicalPath = Join-Path -Path $Folder -ChildPath 'albuild.json'
    $legacyPath = Join-Path -Path $Folder -ChildPath 'pipeline.config'

    $path = $null
    $isLegacy = $false
    if (Test-Path -LiteralPath $canonicalPath) {
        $path = $canonicalPath
    }
    elseif (Test-Path -LiteralPath $legacyPath) {
        $path = $legacyPath
        $isLegacy = $true
    }
    else {
        return $null
    }

    try {
        $parsed = Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json
    }
    catch {
        Write-ALbuildLog -Level Warning "Ignoring invalid project config '$path': $($_.Exception.Message)."
        return $null
    }

    $settings = @{}
    foreach ($property in $parsed.PSObject.Properties) {
        $settings[$property.Name] = $property.Value
    }

    if ($isLegacy) {
        Write-ALbuildLog -Level Warning "'$path' uses the deprecated V1 'pipeline.config' name. Rename it to 'albuild.json'. It is still parsed for backward compatibility."
        # Translate the V1 key to the current schema.
        if ($settings.ContainsKey('alTestRunnerId') -and -not $settings.ContainsKey('TestRunnerCodeunitId')) {
            $settings['TestRunnerCodeunitId'] = $settings['alTestRunnerId']
        }
        $settings.Remove('alTestRunnerId')
    }

    # The runner id is strongly typed ([int]); tolerate a non-numeric value rather than failing.
    if ($settings.ContainsKey('TestRunnerCodeunitId')) {
        $parsedId = 0
        if ([int]::TryParse("$($settings['TestRunnerCodeunitId'])", [ref] $parsedId)) {
            $settings['TestRunnerCodeunitId'] = $parsedId
        }
        else {
            Write-ALbuildLog -Level Warning "Ignoring non-numeric TestRunnerCodeunitId in '$path'; using the default test runner."
            $settings.Remove('TestRunnerCodeunitId')
        }
    }

    return $settings
}