Modules/businessdev.ALbuild.Feeds/Public/Select-BcArtifactForDependencies.ps1
|
function Select-BcArtifactForDependencies { <# .SYNOPSIS Picks the newest Business Central artifact whose build every project's dependencies still resolve against - stepping back through minor versions when an ISV has not yet shipped packages for the latest BC (dependency-aware selection with downgrade-on-conflict). .DESCRIPTION For 'Select = Latest' pipelines only. Enumerates the latest artifact and up to -MaxStepsBack earlier minor versions (crossing a major boundary to the previous major's latest minor when a minor reaches x.0), and - newest first - dry-runs Resolve-BcDependencies (-SkipDownload) for every project against that build. The first version where *all* projects resolve is chosen. If it is older than the latest, 'Downgraded' is set and 'ConflictReason' explains why the latest was skipped (the caller emits the pipeline warning). Throws if nothing within the window resolves. Because it uses an explicit target build and a synthetic Microsoft baseline, no container is needed - the choice is made before the (single) container is created. A specific -Version pins the choice: enumeration/downgrade only runs when no explicit version is given. .PARAMETER ProjectFolder The AL project folder(s) to resolve. Every one must resolve for a version to be chosen. .PARAMETER Type Artifact type (Sandbox or OnPrem). Default 'Sandbox'. .PARAMETER Country Artifact country/localisation. .PARAMETER Version Explicit version/prefix. When given, that version is returned as-is (no downgrade search). .PARAMETER MaxStepsBack Maximum number of minor versions to step back from the latest. Default 5. .PARAMETER Feeds Feed provider objects for the resolver (as Resolve-BcDependencies -Feeds). When omitted the resolver auto-loads the project/registered feeds. .PARAMETER InstalledApps Baseline apps pinned in every dry-run - the already-staged external dependencies (url/local/universal) so the resolver treats them as satisfied instead of fetching from a feed. .OUTPUTS PSCustomObject: ArtifactUrl, Version, LatestVersion, Downgraded, StepsBack, ConflictReason. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Selects and returns an artifact URL; it does not change persistent system state.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Selects a build for the full set of a project''s dependencies; the plural is intentional.')] [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string[]] $ProjectFolder, [string] $Type = 'Sandbox', [string] $Country = '', [string] $Version = '', [int] $MaxStepsBack = 5, [object[]] $Feeds, [object[]] $InstalledApps = @() ) $findArgs = @{ Type = $Type } if ($Country) { $findArgs['Country'] = $Country } # An explicit version pins the choice - no enumeration/downgrade. if ($Version) { $url = Find-BcArtifactUrl @findArgs -Version $Version -Select Latest if (-not $url) { throw "No '$Type' artifact found for version '$Version' (country '$Country')." } return [PSCustomObject]@{ ArtifactUrl = $url; Version = ($url.Split('/')[4]); LatestVersion = ($url.Split('/')[4]); Downgraded = $false; StepsBack = 0; ConflictReason = '' } } # 1. Build the descending candidate list: latest, then up to -MaxStepsBack earlier minors, # crossing to the previous major's latest minor when a minor reaches x.0. $latestUrl = Find-BcArtifactUrl @findArgs -Select Latest if (-not $latestUrl) { throw "No '$Type' artifact found for country '$Country'." } $latestVersion = [version]($latestUrl.Split('/')[4]) $candidates = [System.Collections.Generic.List[object]]::new() $candidates.Add([PSCustomObject]@{ Version = $latestVersion; Url = $latestUrl }) $curMajor = $latestVersion.Major $curMinor = $latestVersion.Minor for ($step = 1; $step -le $MaxStepsBack; $step++) { if ($curMinor -gt 0) { $curMinor-- } else { $prevUrl = Find-BcArtifactUrl @findArgs -Version "$($curMajor - 1)" -Select Latest if (-not $prevUrl) { break } $prevVer = [version]($prevUrl.Split('/')[4]) $curMajor = $prevVer.Major $curMinor = $prevVer.Minor } $url = Find-BcArtifactUrl @findArgs -Version "$curMajor.$curMinor" -Select Latest if (-not $url) { continue } $candidates.Add([PSCustomObject]@{ Version = [version]($url.Split('/')[4]); Url = $url }) } # 2. Dry-run each candidate newest-first; the first where every project resolves wins. $firstConflict = '' $tempLock = Join-Path ([System.IO.Path]::GetTempPath()) ("albuild-dryrun-" + [guid]::NewGuid().ToString('N') + '.lock.json') try { for ($i = 0; $i -lt $candidates.Count; $i++) { $cand = $candidates[$i] $ver = "$($cand.Version)" $ok = $true $why = '' foreach ($proj in $ProjectFolder) { $resolveArgs = @{ ProjectFolder = $proj TargetApplication = $ver TargetPlatform = $ver InstalledApps = $InstalledApps Select = 'Latest' SkipDownload = $true LockFilePath = $tempLock } if ($Feeds -and $Feeds.Count -gt 0) { $resolveArgs['Feeds'] = $Feeds } try { Resolve-BcDependencies @resolveArgs -ErrorAction Stop | Out-Null } catch { $ok = $false; $why = "$(Split-Path -Path $proj -Leaf): $($_.Exception.Message)"; break } } if ($ok) { return [PSCustomObject]@{ ArtifactUrl = $cand.Url Version = $ver LatestVersion = "$latestVersion" Downgraded = ($cand.Version -ne $latestVersion) StepsBack = $i ConflictReason = $firstConflict } } if ($i -eq 0) { $firstConflict = $why } Write-ALbuildLog -Level Warning "BC ${ver}: dependencies did not resolve ($why). Trying an earlier version..." } } finally { Remove-Item -LiteralPath $tempLock -Force -ErrorAction SilentlyContinue } throw "No Business Central version within $MaxStepsBack minor version(s) of $latestVersion resolves every project's dependencies. Latest conflict: $firstConflict" } |