Modules/businessdev.ALbuild.Containers/Public/Get-BcArtifactSymbolFolder.ps1

function Get-BcArtifactSymbolFolder {
    <#
    .SYNOPSIS
        Returns the symbol (package cache) folders a host AL compile needs from a downloaded BC artifact.
 
    .DESCRIPTION
        Collects every folder of a Get-BcArtifact result that provides compiled first-party symbols
        for a host (AL Tool) compile, in one place instead of each caller re-deriving the layout:
 
          * '<application>/Extensions' - first-party app symbols shipped with the country artifact.
          * '<application>/Applications.<COUNTRY>' - the compiled country apps INCLUDING the test toolkit
            (Tests-TestLibraries, Library Assert, Test Runner, ...). Localized artifacts only.
          * the platform folder holding 'System.app' - the AL system/runtime symbols.
 
        The W1 artifact ships no 'Applications.W1' folder - its compiled test toolkit lives scattered
        through the PLATFORM artifact instead ('Applications/TestFramework/**', 'Applications/BaseApp/Test',
        'Applications/System Application/Test', ...). Without it, compiling a test app against W1 fails
        with AL1022 ('Tests-TestLibraries ... could not be found'). So when the country artifact has no
        'Applications.*' folder, the platform's apps are staged once into '.albuild-toolkit-symbols'
        beside the platform artifact and that folder is returned too (keyed to the artifact version, so
        it is reused across builds):
          * W1 sandbox (an 'Extensions' folder IS present): only the test toolkit is missing - stage
            just those apps (paths matching 'TestFramework', a 'Test' segment, or a 'Test Library' name).
          * On-premises (NO 'Extensions' and NO 'Applications.<country>'): the platform 'Applications'
            tree is the only source of first-party symbols, so stage them ALL (the business apps
            Application / Base Application / Business Foundation under '<App>/Source', plus the toolkit),
            or the host compile fails with AL1022 for 'Microsoft Application' / 'Base Application'.
 
    .PARAMETER Artifact
        The artifact object returned by Get-BcArtifact (ApplicationPath / PlatformPath).
 
    .PARAMETER Force
        Re-stage the W1 toolkit symbols even when the staging folder already exists.
 
    .EXAMPLE
        $artifact = Get-BcArtifact -ArtifactUrl $url
        $symbols = Get-BcArtifactSymbolFolder -Artifact $artifact
        Invoke-BcCompiler -ProjectFolder .\app -PackageCachePath (@('.alpackages') + $symbols)
 
    .OUTPUTS
        System.String[] - existing folders, ready for the compiler's package cache path.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory)] [ValidateNotNull()] [object] $Artifact,
        [switch] $Force
    )

    $paths = [System.Collections.Generic.List[string]]::new()

    $extensions = Join-Path $Artifact.ApplicationPath 'Extensions'
    $hasExtensions = Test-Path -LiteralPath $extensions
    if ($hasExtensions) { $paths.Add($extensions) }

    # Compiled, country-specific first-party apps incl. the test toolkit (localized artifacts only).
    $countryApps = @(Get-ChildItem -LiteralPath $Artifact.ApplicationPath -Directory -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -match '^Applications\..+' })
    foreach ($folder in $countryApps) { $paths.Add($folder.FullName) }

    if ($Artifact.PlatformPath) {
        # The platform 'System.app' (AL system/runtime symbols) sits in a version-specific folder.
        $systemApp = Get-ChildItem -LiteralPath $Artifact.PlatformPath -Recurse -Filter 'System.app' -File -ErrorAction SilentlyContinue |
            Select-Object -First 1
        if ($systemApp) { $paths.Add($systemApp.Directory.FullName) }

        # No 'Applications.<country>' folder: the compiled first-party apps the compiler needs live in
        # the platform 'Applications' tree instead - stage them once beside the platform artifact.
        # * W1 sandbox: the country artifact's 'Extensions' folder already provides the business apps
        # (Application, Base Application, ...); only the platform's test toolkit is missing, so stage
        # just that (TestFramework / a 'Test' segment / a 'Test Library' name).
        # * On-premises: there is no 'Extensions' (nor 'Applications.<country>') folder at all, so the
        # platform 'Applications' tree is the ONLY source of first-party symbols - stage every app
        # (the business apps under '<App>/Source' plus the test toolkit), or the compile fails with
        # AL1022 for 'Microsoft Application' / 'Base Application'.
        if ($countryApps.Count -eq 0) {
            $platformApps = Join-Path $Artifact.PlatformPath 'Applications'
            if (Test-Path -LiteralPath $platformApps) {
                $staging = Join-Path $Artifact.PlatformPath '.albuild-toolkit-symbols'
                # The staging folder is cached across builds, but its required CONTENT differs by mode:
                # 'toolkit' (W1 sandbox - test toolkit only) vs 'full' (on-prem - business apps + toolkit).
                # A folder staged in one mode must be re-staged for the other, so a plain "folder is not
                # empty" check is not enough - an older build (or a pre-fix module) may have staged only
                # the toolkit for what is really an on-prem artifact. Record the mode in a marker file and
                # re-stage when it is missing or does not match.
                $mode = if ($hasExtensions) { 'toolkit' } else { 'full' }
                $markerFile = Join-Path $staging '.albuild-symbols-mode'
                $currentMode = if (Test-Path -LiteralPath $markerFile) { "$(Get-Content -LiteralPath $markerFile -Raw -ErrorAction SilentlyContinue)".Trim() } else { '' }
                # Wrapped in @(): an if that emits an empty array collapses to $null via the pipeline,
                # and .Count on $null then dies under Set-StrictMode.
                $staged = @(if (Test-Path -LiteralPath $staging) {
                        Get-ChildItem -LiteralPath $staging -Filter '*.app' -File -ErrorAction SilentlyContinue
                    })
                if ($Force -or $staged.Count -eq 0 -or $currentMode -ne $mode) {
                    New-Item -ItemType Directory -Force -Path $staging | Out-Null
                    $platformAppFiles = @(Get-ChildItem -LiteralPath $platformApps -Recurse -Filter '*.app' -File -ErrorAction SilentlyContinue)
                    if ($hasExtensions) {
                        $platformAppFiles = @($platformAppFiles | Where-Object { $_.FullName -match '(?i)[\\/]TestFramework[\\/]|[\\/]Test[\\/]|Test Library' })
                    }
                    $platformAppFiles | Copy-Item -Destination $staging -Force
                    Set-Content -LiteralPath $markerFile -Value $mode -Encoding UTF8 -NoNewline
                    $what = if ($hasExtensions) { 'test toolkit' } else { 'first-party' }
                    Write-ALbuildLog -Level Information "Staged the platform $what symbols ($($platformAppFiles.Count) app(s)) into '$staging'."
                }
                if (@(Get-ChildItem -LiteralPath $staging -Filter '*.app' -File -ErrorAction SilentlyContinue).Count -gt 0) {
                    $paths.Add($staging)
                }
            }
        }
    }

    return $paths.ToArray()
}