public/Invoke-WoodpeckerPipelineCompiler.ps1
|
function Invoke-WoodpeckerPipelineCompiler { <# .SYNOPSIS Compiles .woodpecker-src templates into Woodpecker pipeline YAML. .DESCRIPTION Reads _vars.yml, resolves include() and yamlFile() primary expressions, expands Jax mustache placeholders, applies Woodpecker defaults, and writes deterministic UTF-8/LF YAML output. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $RepoRoot, [string] $SourceDir, [string] $OutDir, [string] $JaxRoot, [switch] $DryRun, [string] $SourceDirectoryLabel = '.woodpecker-src', [string] $GeneratorPath = 'WoodpeckerTools/Invoke-WoodpeckerPipelineCompiler', [string] $ReferencePath = 'https://github.com/serrnovik/woodpecker-tools', [string] $TechSpecPath ) Initialize-WpcDependencies -JaxRoot $JaxRoot $resolvedRepoRoot = [IO.Path]::GetFullPath($RepoRoot) if ([string]::IsNullOrWhiteSpace($SourceDir)) { $SourceDir = Join-Path $resolvedRepoRoot '.woodpecker-src' } if ([string]::IsNullOrWhiteSpace($OutDir)) { $OutDir = Join-Path $resolvedRepoRoot '.woodpecker' } $SourceDir = [IO.Path]::GetFullPath($SourceDir) $OutDir = [IO.Path]::GetFullPath($OutDir) if (-not (Test-Path -LiteralPath $SourceDir -PathType Container)) { throw "Source directory not found: '$SourceDir'" } if (-not (Test-Path -LiteralPath $OutDir) -and -not $DryRun) { New-Item -ItemType Directory -Path $OutDir -Force | Out-Null } $varsFile = Join-Path $SourceDir '_vars.yml' if (-not (Test-Path -LiteralPath $varsFile -PathType Leaf)) { throw "_vars.yml not found in '$SourceDir'" } $globalVars = Read-JaxYaml -Path $varsFile $sourceFiles = Get-WpcPipelineSourceFiles -SourceDir $SourceDir $anyDiff = $false $expectedOutFileNames = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::OrdinalIgnoreCase ) foreach ($srcFile in $sourceFiles) { $relativeSourcePath = Get-WpcRelativeSourcePath ` -SourceDir $SourceDir ` -SourceFilePath $srcFile.FullName $outFileName = Convert-WpcRelativeSourcePathToOutputFileName ` -RelativeSourcePath $relativeSourcePath $outFile = Join-Path $OutDir $outFileName $null = $expectedOutFileNames.Add($outFileName) Write-Host "Compiling: $relativeSourcePath → $outFileName" -ForegroundColor Cyan $template = Read-JaxYaml -Path $srcFile.FullName $preprocessed = Invoke-WpcPreProcess ` -node $template ` -baseVars $globalVars ` -repoRoot $resolvedRepoRoot ` -sourceDir $SourceDir $expanded = Expand-Placeholders ` -ht $preprocessed ` -override $globalVars ` -ignoreMissingPlaceholders ` -warningsAsVerbose $expanded = Restore-WpcArrayShapes ` -templateNode $preprocessed ` -expandedNode $expanded $expanded = Add-WpcDefaultCloneSettings -Pipeline $expanded $ciVars = $globalVars['vars']['ci'] if ($ciVars -and $ciVars.Contains('k8s_backend_options')) { $ciImageVariants = @( $ciVars['main_build_ci_image'], $ciVars['ci_builder_release_ci_image'], $ciVars['web_build_ci_image'], $ciVars['docker_build_ci_image'] ) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $expanded = Add-WpcDefaultKubernetesBackendOptions ` -Pipeline $expanded ` -Images $ciImageVariants ` -BackendOptions $ciVars['k8s_backend_options'] ` -StepEnvironment $ciVars['builder_environment'] } $serialized = ConvertTo-Yaml -Data $expanded $serialized = Repair-WpcSerializedYaml ` -YamlText $serialized ` -TemplateNode $preprocessed $yamlDocument = if ($serialized.TrimStart().StartsWith('---')) { $serialized } else { "---`n$serialized" } $generatedHeader = Get-WpcGeneratedFileHeader ` -SourceFileName $relativeSourcePath ` -SourceDirectoryLabel $SourceDirectoryLabel ` -GeneratorPath $GeneratorPath ` -ReferencePath $ReferencePath ` -TechSpecPath $TechSpecPath $yamlOut = "$generatedHeader`n$yamlDocument" if ($DryRun) { if (Test-Path -LiteralPath $outFile -PathType Leaf) { $existing = (Get-Content -LiteralPath $outFile -Raw) -replace "`r`n", "`n" $normalized = $yamlOut -replace "`r`n", "`n" if ($existing.TrimEnd() -ne $normalized.TrimEnd()) { Write-Host " DIFF: $outFileName would change" -ForegroundColor Yellow $anyDiff = $true } else { Write-Host ' OK (no change)' -ForegroundColor Green } } else { Write-Host " NEW: $outFileName would be created" -ForegroundColor Yellow $anyDiff = $true } } else { $lfText = ($yamlOut -replace "`r`n", "`n").TrimEnd("`n") + "`n" [IO.File]::WriteAllText( $outFile, $lfText, [Text.UTF8Encoding]::new($false) ) Write-Host " Written: $outFile" -ForegroundColor Green } } if (Test-Path -LiteralPath $OutDir -PathType Container) { $staleCompiledFiles = Get-ChildItem -LiteralPath $OutDir -Filter '*.yaml' -File | Where-Object { -not $expectedOutFileNames.Contains($_.Name) } foreach ($staleFile in $staleCompiledFiles) { if ($DryRun) { Write-Host " REMOVE: $($staleFile.Name) would be deleted" -ForegroundColor Yellow $anyDiff = $true continue } Remove-Item -LiteralPath $staleFile.FullName -Force Write-Host " Removed stale output: $($staleFile.FullName)" -ForegroundColor DarkYellow } } if ($DryRun -and $anyDiff) { Write-Host '' Write-Host 'Compiled pipelines are out of sync.' -ForegroundColor Red } elseif (-not $DryRun) { Write-Host '' Write-Host 'All pipelines compiled successfully.' -ForegroundColor Green } return [pscustomobject]@{ InSync = -not $anyDiff Changed = $anyDiff SourceCount = @($sourceFiles).Count SourceDir = $SourceDir OutDir = $OutDir } } |