Private/Rules/Read-AvmRuleSet.ps1
|
function Read-AvmRuleSet { <# .SYNOPSIS Load every AvmRule that applies to a given on-disk target root. .DESCRIPTION Walks (in this order) and merges by rule Id (later entries override earlier ones with the same Id): 1. Built-in rules at <ModuleRoot>/Resources/Rules/*.psd1 (sorted by filename, ordinal). These ship with the module. 2. Per-repo rules at <Path>/.avm/rules/*.psd1 (sorted by filename, ordinal). These let a repo override a built-in rule by re-declaring its Id, or introduce custom rules entirely. Highest precedence. Each .psd1 file is loaded with Import-PowerShellDataFile (safer than dot-sourcing arbitrary code) and must return a single hashtable matching the AvmRule schema (see Test-AvmRule). The loader hands each definition to New-AvmRule for normalisation + validation; on schema violation it rethrows as AvmConfigurationException with the offending file path prefixed. The Source property of every returned rule is the full path to the .psd1 file it came from so downstream diagnostics (and engine envelopes) can cite the source. Pinned-asset rule bundles ('assets.avm-rules-*' via Resolve-AvmPinnedAsset) are NOT loaded by this function in Slice C -- that integration is tracked as a future slice. .PARAMETER Path A directory (typically the module root) used to locate the per-repo rules dir at <Path>/.avm/rules/. Required. .OUTPUTS An object array of validated AvmRule pscustomobjects, sorted by Id (ordinal) so callers can rely on a stable processing order. #> [CmdletBinding()] [OutputType([object[]])] param( [Parameter(Mandatory)] [string] $Path ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' $rulesById = [ordered]@{} $moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $builtinDir = Join-Path (Join-Path $moduleRoot 'Resources') 'Rules' if (Test-Path -LiteralPath $builtinDir -PathType Container) { foreach ($file in Get-AvmRuleFilesOrdinal -Directory $builtinDir) { $rule = Read-AvmRuleFile -ConfigPath $file.FullName $rulesById[$rule.Id] = $rule } } $resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop $dir = $resolved.ProviderPath if ((Get-Item -LiteralPath $dir).PSIsContainer -eq $false) { $dir = Split-Path -Parent $dir } $repoDir = Join-Path (Join-Path $dir '.avm') 'rules' if (Test-Path -LiteralPath $repoDir -PathType Container) { foreach ($file in Get-AvmRuleFilesOrdinal -Directory $repoDir) { $rule = Read-AvmRuleFile -ConfigPath $file.FullName $rulesById[$rule.Id] = $rule } } if ($rulesById.Count -eq 0) { return @() } $ids = @($rulesById.Keys) [System.Array]::Sort($ids, [System.StringComparer]::Ordinal) $result = New-Object 'System.Collections.Generic.List[object]' foreach ($id in $ids) { $result.Add($rulesById[$id]) } return $result.ToArray() } function Read-AvmRuleFile { <# .SYNOPSIS Internal: parse and validate one rule .psd1 file. .DESCRIPTION Uses Import-PowerShellDataFile so arbitrary script in the .psd1 cannot execute. The file must declare a single top-level hashtable carrying the AvmRule fields. Schema violations bubble up as AvmConfigurationException with the file path prefixed. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [string] $ConfigPath ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' try { $loaded = Import-PowerShellDataFile -LiteralPath $ConfigPath -ErrorAction Stop } catch { throw [AvmConfigurationException]::new( "${ConfigPath}: unable to load rule definition: $($_.Exception.Message)", $_.Exception) } if ($loaded -isnot [System.Collections.IDictionary]) { throw [AvmConfigurationException]::new( "${ConfigPath}: rule .psd1 must declare a single top-level hashtable.") } try { return New-AvmRule -Definition ([hashtable]$loaded) -Source $ConfigPath } catch [System.Data.DataException] { throw [AvmConfigurationException]::new( "${ConfigPath}: $($_.Exception.Message)", $_.Exception) } } function Get-AvmRuleFilesOrdinal { <# .SYNOPSIS Internal: list rule .psd1 files in a directory in ordinal-stable order. #> [CmdletBinding()] [OutputType([object[]])] param( [Parameter(Mandatory)] [string] $Directory ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' $files = Get-ChildItem -LiteralPath $Directory -File -Filter '*.psd1' -ErrorAction SilentlyContinue if (-not $files) { return @() } $arr = @($files | ForEach-Object { $_.FullName }) [System.Array]::Sort($arr, [System.StringComparer]::Ordinal) return @($arr | ForEach-Object { Get-Item -LiteralPath $_ }) } |