Modules/businessdev.ALbuild.Feeds/Classes/BcFeedModels.ps1
|
# Typed package model for the dependency resolver: BcPackageDependency and BcPackageManifest. # Replaces the loose hashtable/PSCustomObject "candidate" shape that used to be rebuilt in the feed # provider, the solver and the tests. # # File name matters: the module loader dot-sources Classes/*.ps1 in filename order, and PowerShell # resolves class type references (including in method signatures/bodies) at parse time. These model # types are referenced by BcNuGetFeedProvider, so this file must load first - 'BcFeedModels' sorts # before 'BcNuGetFeedProvider'. The two classes also live together here so BcPackageManifest's # parse-time reference to BcPackageDependency (its Dependencies property) resolves. # # The From() factories accept anything that exposes the right fields (hashtable, PSCustomObject or # an instance) so feed providers and in-memory test fixtures can keep producing plain records that # are normalised into these types at the boundary. class BcPackageDependency { [string] $Id [string] $Name [string] $Publisher [string] $MinVersion # The exact NuGet package id this dependency was read from (e.g. read out of a parent package's # nuspec). When known it is the authoritative way to locate the package on a feed; reconstructing # it from publisher/name via the feed's id scheme is only a fallback (and loses the friendly, # space-bearing publisher/name a nuspec id has already normalised away). [string] $PackageId BcPackageDependency() { } BcPackageDependency([string] $id, [string] $name, [string] $publisher, [string] $minVersion) { $this.Id = $id $this.Name = $name $this.Publisher = $publisher $this.MinVersion = $minVersion } # The empty GUID denotes the BC Platform/Application itself - provided by the target build. static [string] $EmptyGuid = '00000000-0000-0000-0000-000000000000' # True when this dependency is a Microsoft first-party app (or the Platform/Application). Such # apps ship with the Business Central build and are pinned to the target, never downloaded from # a third-party feed. Recognised by a Microsoft publisher, a 'Microsoft.'-prefixed package id, or # the empty GUID. [bool] IsMicrosoft() { if ($this.Id -eq [BcPackageDependency]::EmptyGuid) { return $true } if ($this.Publisher -and $this.Publisher -match '^(?i)microsoft\b') { return $true } if ($this.PackageId -and $this.PackageId -match '^(?i)microsoft\.') { return $true } return $false } # Reads a field by name from a hashtable, PSCustomObject or class instance, StrictMode-safe. static [object] Field([object] $source, [string] $name, [object] $default) { if ($null -eq $source) { return $default } if ($source -is [System.Collections.IDictionary]) { if ($source.Contains($name)) { return $source[$name] } return $default } $property = $source.PSObject.Properties[$name] if ($property) { return $property.Value } return $default } # Normalises any field-bearing record into a BcPackageDependency. A 'Version' field is accepted # as the minimum when 'MinVersion' is absent (app.json dependencies carry 'version'). static [BcPackageDependency] FromObject([object] $source) { if ($source -is [BcPackageDependency]) { return $source } $min = [BcPackageDependency]::Field($source, 'MinVersion', $null) if ($null -eq $min -or "$min" -eq '') { $min = [BcPackageDependency]::Field($source, 'Version', '0.0.0.0') } $dep = [BcPackageDependency]::new( "$([BcPackageDependency]::Field($source, 'Id', ''))", "$([BcPackageDependency]::Field($source, 'Name', ''))", "$([BcPackageDependency]::Field($source, 'Publisher', ''))", "$min" ) $dep.PackageId = "$([BcPackageDependency]::Field($source, 'PackageId', ''))" return $dep } } class BcPackageManifest { [string] $Id [string] $Name # friendly app name, for readable logs/output (not just the id) [string] $Version [BcPackageDependency[]] $Dependencies = @() [string] $Platform [string] $Application [string] $Kind [string] $PackageId [string] $ProviderName [object] $Provider [bool] $Pinned # The concrete package coordinates to download. For a direct app package these equal # PackageId/Version. For an "indirect" (metapackage) candidate - which carries no .app itself but # points at a per-app-version runtime sub-package - these are the runtime sub-package id and the # BC platform build chosen for the target (closest match). Version stays the *app* version (the # axis the solver dedupes/orders on); DownloadVersion is the BC build actually fetched. [string] $DownloadId [string] $DownloadVersion BcPackageManifest() { } # The package coordinates to download, defaulting to the app package when no indirection applies. [string] EffectiveDownloadId() { return $(if ($this.DownloadId) { $this.DownloadId } else { $this.PackageId }) } [string] EffectiveDownloadVersion() { return $(if ($this.DownloadVersion) { $this.DownloadVersion } else { $this.Version }) } # Normalises any field-bearing record (e.g. a feed provider's candidate hashtable or a test # fixture) into a BcPackageManifest, including its dependency list. static [BcPackageManifest] FromObject([object] $source) { if ($source -is [BcPackageManifest]) { return $source } $manifest = [BcPackageManifest]::new() $manifest.Id = "$([BcPackageDependency]::Field($source, 'Id', ''))" $manifest.Name = "$([BcPackageDependency]::Field($source, 'Name', ''))" $manifest.Version = "$([BcPackageDependency]::Field($source, 'Version', ''))" $manifest.Platform = "$([BcPackageDependency]::Field($source, 'Platform', ''))" $manifest.Application = "$([BcPackageDependency]::Field($source, 'Application', ''))" $manifest.Kind = "$([BcPackageDependency]::Field($source, 'Kind', ''))" $manifest.PackageId = "$([BcPackageDependency]::Field($source, 'PackageId', ''))" $manifest.ProviderName = "$([BcPackageDependency]::Field($source, 'ProviderName', ''))" $manifest.Provider = [BcPackageDependency]::Field($source, 'Provider', $null) $manifest.Pinned = [bool]([BcPackageDependency]::Field($source, 'Pinned', $false)) $manifest.DownloadId = "$([BcPackageDependency]::Field($source, 'DownloadId', ''))" $manifest.DownloadVersion = "$([BcPackageDependency]::Field($source, 'DownloadVersion', ''))" $deps = [BcPackageDependency]::Field($source, 'Dependencies', @()) $manifest.Dependencies = @($deps | ForEach-Object { [BcPackageDependency]::FromObject($_) }) return $manifest } # The pinned baseline entry for an app already present in the target build (cannot be upgraded). static [BcPackageManifest] NewPinned([string] $id, [string] $version) { $manifest = [BcPackageManifest]::new() $manifest.Id = $id $manifest.Version = $version $manifest.Kind = 'pinned' $manifest.PackageId = $id $manifest.ProviderName = 'target' $manifest.Pinned = $true return $manifest } } |