Private/GitHubWorkflowCommand.Helpers.ps1
|
function ConvertTo-GitHubCommandData { [CmdletBinding()] [OutputType([string])] param( [AllowNull()] [object] $Value ) if ($null -eq $Value) { return '' } return ([string]$Value). Replace('%', '%25'). Replace("`r", '%0D'). Replace("`n", '%0A') } function ConvertTo-GitHubCommandProperty { [CmdletBinding()] param( [AllowNull()] [object] $Value ) return (ConvertTo-GitHubCommandData -Value $Value). Replace(':', '%3A'). Replace(',', '%2C') } function Write-GitHubWorkflowCommandInternal { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Command, [AllowNull()] [object] $Message, [System.Collections.IDictionary] $Properties ) $propertySegments = @() if ($Properties) { foreach ($key in $Properties.Keys) { $value = $Properties[$key] if ($null -eq $value -or [string]::IsNullOrWhiteSpace([string]$value)) { continue } $propertySegments += ('{0}={1}' -f $key, (ConvertTo-GitHubCommandProperty -Value $value)) } } $propertyText = '' if ($propertySegments.Count -gt 0) { $propertyText = ' ' + ($propertySegments -join ',') } Write-Output ('::{0}{1}::{2}' -f $Command.ToLowerInvariant(), $propertyText, (ConvertTo-GitHubCommandData -Value $Message)) } function Get-GitHubEnvironmentFilePath { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [string] $VariableName ) $filePath = [System.Environment]::GetEnvironmentVariable($VariableName) if ([string]::IsNullOrWhiteSpace($filePath)) { throw "The $VariableName environment variable is not available in the current session." } return $filePath } function ConvertTo-GitHubFileCommandEntry { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [string] $Name, [AllowNull()] [object] $Value ) $stringValue = if ($null -eq $Value) { '' } else { [string]$Value } if ($stringValue -match "`r|`n") { $delimiter = [guid]::NewGuid().Guid $lines = $stringValue -split "`r?`n" while ($lines -contains $delimiter) { $delimiter = [guid]::NewGuid().Guid } return @( "$Name<<$delimiter", $stringValue, $delimiter ) -join [System.Environment]::NewLine } return "$Name=$stringValue" } function Add-GitHubEnvironmentFileEntryInternal { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $VariableName, [Parameter(Mandatory)] [string] $Name, [AllowNull()] [object] $Value ) $filePath = Get-GitHubEnvironmentFilePath -VariableName $VariableName $entry = ConvertTo-GitHubFileCommandEntry -Name $Name -Value $Value $entry | Out-File -FilePath $filePath -Append -Encoding utf8 } function Write-GitHubEnvironmentFileContentInternal { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $VariableName, [AllowEmptyCollection()] [string[]] $Content = @(), [switch] $Append ) $filePath = Get-GitHubEnvironmentFilePath -VariableName $VariableName $text = ($Content -join [System.Environment]::NewLine) if ($Append) { $text | Out-File -FilePath $filePath -Append -Encoding utf8 return } $text | Out-File -FilePath $filePath -Encoding utf8 } function Remove-GitHubEnvironmentFileContentInternal { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [string] $VariableName ) $filePath = Get-GitHubEnvironmentFilePath -VariableName $VariableName if ((Test-Path -Path $filePath) -and $PSCmdlet.ShouldProcess($filePath, 'Remove GitHub Actions environment file')) { Remove-Item -Path $filePath -Force } } function New-GitHubAnnotationPropertyMap { [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [string] $Title, [string] $File, [Nullable[int]] $Line, [Nullable[int]] $EndLine, [Nullable[int]] $Column, [Nullable[int]] $EndColumn ) $target = if ([string]::IsNullOrWhiteSpace($Title)) { 'GitHub annotation' } else { $Title } if (-not $PSCmdlet.ShouldProcess($target, 'Create GitHub annotation property map')) { return } $properties = [ordered]@{} if (-not [string]::IsNullOrWhiteSpace($Title)) { $properties.title = $Title } if (-not [string]::IsNullOrWhiteSpace($File)) { $properties.file = $File } if ($Line.HasValue) { $properties.line = $Line.Value } if ($EndLine.HasValue) { $properties.endLine = $EndLine.Value } if ($Column.HasValue) { $properties.col = $Column.Value } if ($EndColumn.HasValue) { $properties.endColumn = $EndColumn.Value } return $properties } function ConvertTo-GitHubCacheSegmentInternal { [CmdletBinding()] [OutputType([string])] param( [AllowNull()] [object] $Value ) if ($null -eq $Value) { return $null } $text = [string]$Value if ([string]::IsNullOrWhiteSpace($text)) { return $null } if ($text -match "`r|`n") { throw 'GitHub cache key segments cannot contain newline characters.' } return $text.Trim() } function New-GitHubCacheKeyTextInternal { [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [OutputType([string])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Segment, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Separator ) $normalizedSegments = foreach ($currentSegment in $Segment) { $normalizedSegment = ConvertTo-GitHubCacheSegmentInternal -Value $currentSegment if ($null -ne $normalizedSegment) { $normalizedSegment } } if (@($normalizedSegments).Count -eq 0) { throw 'At least one GitHub cache key segment must be provided.' } $key = @($normalizedSegments) -join $Separator if ($key.Length -gt 512) { throw 'GitHub cache keys cannot exceed 512 characters.' } return $key } function Resolve-GitHubCacheHashInputInternal { [CmdletBinding()] [OutputType([System.IO.FileInfo[]])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string[]] $Path ) $files = [System.Collections.Generic.List[System.IO.FileInfo]]::new() $seenPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($currentPath in $Path) { $candidateFiles = @() if (Test-Path -LiteralPath $currentPath -PathType Leaf) { $candidateFiles = @(Get-Item -LiteralPath $currentPath) } elseif (Test-Path -LiteralPath $currentPath -PathType Container) { $candidateFiles = @(Get-ChildItem -LiteralPath $currentPath -File -Recurse) } else { $candidateFiles = @(Get-ChildItem -Path $currentPath -File -Recurse -ErrorAction SilentlyContinue) } if ($candidateFiles.Count -eq 0) { throw "The cache hash path '$currentPath' did not match any files." } foreach ($candidateFile in ($candidateFiles | Sort-Object FullName -Unique)) { if ($seenPaths.Add($candidateFile.FullName)) { $files.Add($candidateFile) } } } return [System.IO.FileInfo[]]($files.ToArray() | Sort-Object FullName) } function Get-GitHubCacheHashIdentityInternal { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [System.IO.FileInfo] $File ) $workspacePath = [System.Environment]::GetEnvironmentVariable('GITHUB_WORKSPACE', 'Process') if (-not [string]::IsNullOrWhiteSpace($workspacePath)) { $workspaceRoot = [System.IO.Path]::GetFullPath($workspacePath) $relativePath = [System.IO.Path]::GetRelativePath($workspaceRoot, $File.FullName) if (-not $relativePath.StartsWith('..')) { return $relativePath.Replace('\\', '/') } } return $File.FullName.Replace('\\', '/') } function Get-GitHubCacheHashInternal { [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [System.IO.FileInfo[]] $File ) $entries = foreach ($currentFile in ($File | Sort-Object FullName)) { $fileHash = (Get-FileHash -LiteralPath $currentFile.FullName -Algorithm SHA256).Hash.ToLowerInvariant() '{0}:{1}' -f (Get-GitHubCacheHashIdentityInternal -File $currentFile), $fileHash } $contentBytes = [System.Text.Encoding]::UTF8.GetBytes(($entries -join "`n")) $sha256 = [System.Security.Cryptography.SHA256]::Create() try { return [System.Convert]::ToHexString($sha256.ComputeHash($contentBytes)).ToLowerInvariant() } finally { $sha256.Dispose() } } |