Modules/businessdev.ALbuild.Marketplace/Private/Resolve-BcMarketplaceAppSet.ps1
|
function Resolve-BcMarketplaceAppSet { <# .SYNOPSIS Resolves the set of .app files to submit to Marketplace from a path, excluding test apps. .DESCRIPTION Marketplace/AppSource submissions take a MAIN app plus its dependency (library) apps, and a TEST app must NEVER be published. Release artifacts, however, contain a folder of built .app files whose names carry the version - and often the test app too. This turns a path (a folder or a single .app) into { MainApp; LibraryApps; Excluded } so the caller need only point at the output folder: * reads each .app's manifest on the host (Expand-BcAppFile - no container needed); * EXCLUDES test apps (manifest Target='Test', or a dependency on a Microsoft test/assert library - the same signal AL uses); * picks the MAIN app as the one no other included app depends on (the top of the dependency graph); the remaining non-test apps are its libraries. .PARAMETER Path A folder containing built .app files, or a single .app file. .OUTPUTS PSCustomObject: MainApp / LibraryApps / Excluded, each element having File, Id, Name, Publisher. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Path ) if (-not (Test-Path -LiteralPath $Path)) { throw "App path '$Path' does not exist." } $item = Get-Item -LiteralPath $Path $files = @() if ($item.PSIsContainer) { $files = @(Get-ChildItem -LiteralPath $item.FullName -Filter '*.app' -File -Recurse -ErrorAction SilentlyContinue | Sort-Object FullName) } elseif ($item.Extension -ieq '.app') { $files = @($item) } else { throw "App path '$Path' is neither a folder nor a .app file." } if (@($files).Count -eq 0) { throw "No .app files were found under '$Path'." } $apps = foreach ($f in $files) { try { $info = Expand-BcAppFile -Path $f.FullName } catch { Write-ALbuildLog -Level Warning "Skipping '$($f.Name)': could not read its manifest ($($_.Exception.Message))."; continue } # Test app: manifest Target='Test', or a dependency on a Microsoft test/assert library. $target = '' try { $appNode = $info.Manifest.SelectSingleNode("//*[local-name()='App']"); if ($appNode) { $target = "$($appNode.GetAttribute('Target'))" } } catch { Write-Verbose "Could not read the App Target attribute from '$($f.Name)': $($_.Exception.Message)" } $isTest = ($target -ieq 'Test') if (-not $isTest) { foreach ($dep in @($info.Dependencies)) { if ("$($dep.Publisher)" -eq 'Microsoft' -and ("$($dep.Name)" -like '*Test*' -or "$($dep.Name)" -like '*Assert*')) { $isTest = $true; break } } } [PSCustomObject]@{ File = $f.FullName; Id = "$($info.Id)"; Name = "$($info.Name)"; Publisher = "$($info.Publisher)"; DependencyIds = @($info.Dependencies | ForEach-Object { "$($_.Id)".ToLowerInvariant() }); IsTest = $isTest } } $apps = @($apps) $excluded = @($apps | Where-Object { $_.IsTest }) foreach ($x in $excluded) { Write-ALbuildLog "Excluding test app '$($x.Name)' ($([System.IO.Path]::GetFileName($x.File))) - test apps are never published to Marketplace." } $candidates = @($apps | Where-Object { -not $_.IsTest }) if ($candidates.Count -eq 0) { throw "All .app files under '$Path' are test apps; there is nothing to publish to Marketplace." } # Main app = the one no OTHER included app depends on (top of the dependency graph); libraries = rest. $dependedOn = @{} foreach ($c in $candidates) { foreach ($d in $c.DependencyIds) { if ($d) { $dependedOn[$d] = $true } } } $roots = @($candidates | Where-Object { -not $dependedOn.ContainsKey($_.Id.ToLowerInvariant()) }) if ($roots.Count -eq 0) { $roots = $candidates } # cyclic/edge case: fall back to all if ($roots.Count -gt 1) { Write-ALbuildLog -Level Warning "Found $($roots.Count) independent apps with no dependant ($(($roots | ForEach-Object { $_.Name }) -join ', ')); submitting '$($roots[0].Name)' as the main app. Point the task at a single product's output, or set the product name explicitly, if that is wrong." } $main = $roots[0] $libraries = @($candidates | Where-Object { $_.File -ne $main.File }) Write-ALbuildLog "Marketplace app set: main '$($main.Name)' ($([System.IO.Path]::GetFileName($main.File)))$(if ($libraries.Count) { "; $($libraries.Count) library app(s): $(($libraries | ForEach-Object { $_.Name }) -join ', ')" })." [PSCustomObject]@{ MainApp = $main; LibraryApps = $libraries; Excluded = $excluded } } |