microsoft/hve-core
Publicmirrored fromhttps://github.com/microsoft/hve-coreAvailable
scripts/devcontainer/Write-DevcontainerChangeLog.ps1
198lines · modecode
| 1 | #!/usr/bin/env pwsh |
| 2 | # Copyright (c) Microsoft Corporation. |
| 3 | # SPDX-License-Identifier: MIT |
| 4 | #Requires -Version 7.0 |
| 5 | |
| 6 | <# |
| 7 | .SYNOPSIS |
| 8 | Classifies devcontainer file changes and generates a markdown summary. |
| 9 | |
| 10 | .DESCRIPTION |
| 11 | Analyzes git diff output to identify changed devcontainer infrastructure files, |
| 12 | classifies each by category and pre-build impact, and produces a markdown summary |
| 13 | table. In CI, writes to GITHUB_STEP_SUMMARY; locally, writes to stdout. |
| 14 | |
| 15 | .PARAMETER CommitSha |
| 16 | The commit SHA to diff against. Defaults to HEAD when not specified. |
| 17 | |
| 18 | .PARAMETER BranchName |
| 19 | The branch name for display in the summary header. |
| 20 | |
| 21 | .PARAMETER EventName |
| 22 | The GitHub event name that triggered the workflow. Defaults to 'local'. |
| 23 | |
| 24 | .PARAMETER BeforeSha |
| 25 | The before-push SHA for computing the diff range. |
| 26 | |
| 27 | .PARAMETER RepoRoot |
| 28 | Root directory of the repository. Defaults to git toplevel or script directory. |
| 29 | |
| 30 | .EXAMPLE |
| 31 | ./Write-DevcontainerChangeLog.ps1 |
| 32 | Generate a local devcontainer change summary for the current HEAD. |
| 33 | |
| 34 | .EXAMPLE |
| 35 | ./Write-DevcontainerChangeLog.ps1 -CommitSha "abc123" -BeforeSha "def456" -BranchName "main" -EventName "push" |
| 36 | Generate a change summary for a specific commit range in CI. |
| 37 | |
| 38 | .NOTES |
| 39 | Runs via: npm run devcontainer:changelog (when configured) |
| 40 | Replaces inline bash in .github/workflows/devcontainer-change-log.yml |
| 41 | #> |
| 42 | |
| 43 | [CmdletBinding()] |
| 44 | param( |
| 45 | [Parameter(Mandatory = $false)] |
| 46 | [string]$CommitSha, |
| 47 | |
| 48 | [Parameter(Mandatory = $false)] |
| 49 | [string]$BranchName, |
| 50 | |
| 51 | [Parameter(Mandatory = $false)] |
| 52 | [string]$EventName = 'local', |
| 53 | |
| 54 | [Parameter(Mandatory = $false)] |
| 55 | [string]$BeforeSha, |
| 56 | |
| 57 | [Parameter(Mandatory = $false)] |
| 58 | [string]$RepoRoot = (git rev-parse --show-toplevel 2>$null) |
| 59 | ) |
| 60 | |
| 61 | if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $RepoRoot = $PSScriptRoot } |
| 62 | |
| 63 | $ErrorActionPreference = 'Stop' |
| 64 | |
| 65 | Import-Module (Join-Path $PSScriptRoot '../lib/Modules/CIHelpers.psm1') -Force |
| 66 | |
| 67 | #region Functions |
| 68 | |
| 69 | function Get-DevcontainerFileClassification { |
| 70 | <# |
| 71 | .SYNOPSIS |
| 72 | Classifies a devcontainer file path by category and impact. |
| 73 | #> |
| 74 | [CmdletBinding()] |
| 75 | [OutputType([hashtable])] |
| 76 | param( |
| 77 | [Parameter(Mandatory)] |
| 78 | [string]$FilePath |
| 79 | ) |
| 80 | |
| 81 | switch -Wildcard ($FilePath) { |
| 82 | '.devcontainer/scripts/on-create.sh' { return @{ Category = 'Lifecycle Scripts'; Impact = 'High' } } |
| 83 | '.devcontainer/scripts/post-create.sh' { return @{ Category = 'Lifecycle Scripts'; Impact = 'Low' } } |
| 84 | { $_ -like '.devcontainer/Dockerfile*' -or $_ -like '.devcontainer/*.dockerfile' } { return @{ Category = 'Base Image'; Impact = 'High' } } |
| 85 | '.devcontainer/features/*' { return @{ Category = 'Features'; Impact = 'Medium' } } |
| 86 | '.devcontainer/devcontainer.json' { return @{ Category = 'Config'; Impact = 'High' } } |
| 87 | '.devcontainer/devcontainer-lock.json' { return @{ Category = 'Lockfile'; Impact = 'Medium' } } |
| 88 | '.github/workflows/copilot-setup-steps.yml' { return @{ Category = 'Setup Steps'; Impact = 'Medium' } } |
| 89 | '.devcontainer/*' { return @{ Category = 'Config'; Impact = 'Medium' } } |
| 90 | default { return @{ Category = 'Other'; Impact = 'Unknown' } } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | function New-DevcontainerChangeSummary { |
| 95 | <# |
| 96 | .SYNOPSIS |
| 97 | Builds a markdown change summary for devcontainer infrastructure files. |
| 98 | #> |
| 99 | [CmdletBinding()] |
| 100 | [OutputType([string])] |
| 101 | param( |
| 102 | [Parameter(Mandatory = $false)] |
| 103 | [string]$CommitSha, |
| 104 | |
| 105 | [Parameter(Mandatory = $false)] |
| 106 | [string]$BranchName, |
| 107 | |
| 108 | [Parameter(Mandatory = $false)] |
| 109 | [string]$EventName = 'local', |
| 110 | |
| 111 | [Parameter(Mandatory = $false)] |
| 112 | [string]$BeforeSha, |
| 113 | |
| 114 | [Parameter(Mandatory = $false)] |
| 115 | [string]$RepoRoot |
| 116 | ) |
| 117 | |
| 118 | if ([string]::IsNullOrWhiteSpace($CommitSha)) { |
| 119 | $CommitSha = git -C $RepoRoot rev-parse HEAD 2>$null |
| 120 | } |
| 121 | if ([string]::IsNullOrWhiteSpace($BranchName)) { |
| 122 | $BranchName = git -C $RepoRoot rev-parse --abbrev-ref HEAD 2>$null |
| 123 | } |
| 124 | |
| 125 | $sb = [System.Text.StringBuilder]::new() |
| 126 | [void]$sb.AppendLine('## Devcontainer Infrastructure Changes') |
| 127 | [void]$sb.AppendLine('') |
| 128 | [void]$sb.AppendLine('| Property | Value |') |
| 129 | [void]$sb.AppendLine('|----------|-------|') |
| 130 | [void]$sb.AppendLine("| Commit | ``$CommitSha`` |") |
| 131 | [void]$sb.AppendLine("| Branch | ``$BranchName`` |") |
| 132 | [void]$sb.AppendLine("| Trigger | ``$EventName`` |") |
| 133 | [void]$sb.AppendLine('') |
| 134 | |
| 135 | if ($EventName -eq 'workflow_dispatch') { |
| 136 | [void]$sb.AppendLine('_Triggered via workflow_dispatch. No push range available for automatic diff._') |
| 137 | return $sb.ToString() |
| 138 | } |
| 139 | |
| 140 | if ($BeforeSha -eq '0000000000000000000000000000000000000000') { |
| 141 | [void]$sb.AppendLine('_Initial push to branch -- no prior commit range available._') |
| 142 | return $sb.ToString() |
| 143 | } |
| 144 | |
| 145 | if ([string]::IsNullOrWhiteSpace($BeforeSha)) { |
| 146 | # Local run without a before SHA -- diff HEAD~1 as a reasonable default |
| 147 | $BeforeSha = git -C $RepoRoot rev-parse 'HEAD~1' 2>$null |
| 148 | if ($LASTEXITCODE -ne 0) { |
| 149 | [void]$sb.AppendLine('_No prior commit available for diff (initial commit?)._') |
| 150 | return $sb.ToString() |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | $changed = git -C $RepoRoot diff --name-only $BeforeSha $CommitSha -- '.devcontainer/' '.github/workflows/copilot-setup-steps.yml' 2>&1 |
| 155 | if ($LASTEXITCODE -ne 0) { |
| 156 | [void]$sb.AppendLine("_Could not compute diff: ``$BeforeSha`` may not be reachable (force push?)._") |
| 157 | return $sb.ToString() |
| 158 | } |
| 159 | |
| 160 | $files = ($changed -split "`n") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
| 161 | if (-not $files -or $files.Count -eq 0) { |
| 162 | [void]$sb.AppendLine('_No devcontainer infrastructure files changed in this push._') |
| 163 | return $sb.ToString() |
| 164 | } |
| 165 | |
| 166 | [void]$sb.AppendLine('| File | Category | Pre-build Impact |') |
| 167 | [void]$sb.AppendLine('|------|----------|-----------------|') |
| 168 | foreach ($file in $files) { |
| 169 | $classification = Get-DevcontainerFileClassification -FilePath $file |
| 170 | [void]$sb.AppendLine("| ``$file`` | $($classification.Category) | $($classification.Impact) |") |
| 171 | } |
| 172 | |
| 173 | return $sb.ToString() |
| 174 | } |
| 175 | |
| 176 | #endregion Functions |
| 177 | |
| 178 | #region Main Execution |
| 179 | |
| 180 | if ($MyInvocation.InvocationName -ne '.') { |
| 181 | try { |
| 182 | $markdown = New-DevcontainerChangeSummary -CommitSha $CommitSha -BranchName $BranchName -EventName $EventName -BeforeSha $BeforeSha -RepoRoot $RepoRoot |
| 183 | |
| 184 | if ($env:GITHUB_STEP_SUMMARY) { |
| 185 | $markdown | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding UTF8 |
| 186 | } |
| 187 | else { |
| 188 | Write-Output $markdown |
| 189 | } |
| 190 | exit 0 |
| 191 | } |
| 192 | catch { |
| 193 | Write-Error -ErrorAction Continue "Write-DevcontainerChangeLog failed: $($_.Exception.Message)" |
| 194 | exit 1 |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | #endregion Main Execution |
| 199 | |