Private/Compile/Write-ResolvedGovernance.ps1
|
function Write-ResolvedGovernance { <# .SYNOPSIS Serialises the resolved desired-state model to the given build output path (a generated artifact, never committed). #> [CmdletBinding()] param( [Parameter(Mandatory)][object]$Resolved, [Parameter(Mandatory)][string]$OutputPath ) $outputDir = Split-Path -Parent $OutputPath if ($outputDir -and -not (Test-Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force | Out-Null } $header = @( "# resolved.yaml" "# GENERATED by build.ps1 build — do not edit by hand, do not commit." '' ) -join "`n" $yaml = ConvertTo-Yaml $Resolved Set-Content -Path $OutputPath -Value ($header + $yaml) -Encoding utf8 return $OutputPath } function Test-ResolvedGovernance { <# .SYNOPSIS Validates the resolved model against Azure DevOps limits and the governance rules (unique shorts, resolvable owner refs, area-path constraints). Returns an array of issue strings; empty = valid. #> [CmdletBinding()] param([Parameter(Mandatory)][object]$Resolved) $issues = [System.Collections.Generic.List[string]]::new() $forbidden = '[\\/:*?"<>|#$&+]' $forbiddenInTeams = '[,"/\\[\]:!|<>+=;?*@~''&$#]' # ADO team name forbidden chars # Project declaration: process is free-form (custom templates allowed), but # visibility and sourceControl must be values az devops project create accepts. if ($Resolved.project) { if ($Resolved.project.visibility -notin @('private', 'public')) { $issues.Add("Project visibility must be 'private' or 'public', got '$($Resolved.project.visibility)'") } if ($Resolved.project.sourceControl -notin @('git', 'tfvc')) { $issues.Add("Project sourceControl must be 'git' or 'tfvc', got '$($Resolved.project.sourceControl)'") } } # Unique code chains (the global ownership keys, e.g. PTL-FND). Leaf shorts may # repeat across products; the product-qualified code must be unique. $codes = @($Resolved.areaPaths | Where-Object { $_.code } | ForEach-Object { $_.code }) $dupes = $codes | Group-Object | Where-Object Count -gt 1 foreach ($d in $dupes) { $issues.Add("Duplicate code '$($d.Name)' used $($d.Count) times") } foreach ($area in $Resolved.areaPaths) { $segments = $area.path.TrimStart('\') -split '\\' if ($segments.Count -ge 14) { $issues.Add("Area path exceeds 14 levels: $($area.path)") } foreach ($segment in $segments) { if ($segment.Length -gt 255) { $issues.Add("Area path segment > 255 chars: $segment") } if ($segment -match $forbidden) { $issues.Add("Area path segment contains forbidden characters: $segment") } } } foreach ($team in $Resolved.teams) { if ($team.name -match $forbiddenInTeams) { $issues.Add("Team name contains ADO-forbidden characters: '$($team.name)'") } } return $issues.ToArray() } |