maksit-core/utils/Release-Package/CorePlugins/GitHub.psm1
2026-02-28 22:17:04 +01:00

233 lines
8.4 KiB
PowerShell

#requires -Version 7.0
#requires -PSEdition Core
<#
.SYNOPSIS
GitHub release plugin.
.DESCRIPTION
This plugin validates GitHub CLI access, resolves the target
repository, and creates the configured GitHub release using the
shared release artifacts and extracted release notes.
#>
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
$pluginSupportModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "PluginSupport.psm1"
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
}
}
function Get-GitHubRepositoryInternal {
param(
[Parameter(Mandatory = $false)]
[string]$ConfiguredRepository
)
$repoSource = $ConfiguredRepository
if ([string]::IsNullOrWhiteSpace($repoSource)) {
$repoSource = git config --get remote.origin.url
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($repoSource)) {
throw "Could not determine git remote origin URL."
}
}
$repoSource = $repoSource.Trim()
if ($repoSource -match "(?i)github\.com[:/](?<owner>[^/]+)/(?<repo>[^/.]+)(\.git)?$") {
return "$($matches['owner'])/$($matches['repo'])"
}
if ($repoSource -match "^(?<owner>[^/]+)/(?<repo>[^/]+)$") {
return "$($matches['owner'])/$($matches['repo'])"
}
throw "Could not parse GitHub repo from source: $repoSource. Configure Plugins[].repository with 'owner/repo' or a GitHub URL."
}
function Get-ReleaseNotesInternal {
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseNotesFile,
[Parameter(Mandatory = $true)]
[string]$Version
)
Write-Log -Level "INFO" -Message "Verifying release notes source..."
if (-not (Test-Path $ReleaseNotesFile -PathType Leaf)) {
throw "Release notes source file not found at: $ReleaseNotesFile"
}
$releaseNotesContent = Get-Content $ReleaseNotesFile -Raw
if ($releaseNotesContent -notmatch '##\s+v(\d+\.\d+\.\d+)') {
throw "No version entry found in the configured release notes source."
}
$releaseNotesVersion = $Matches[1]
if ($releaseNotesVersion -ne $Version) {
throw "Project version ($Version) does not match the latest release notes version ($releaseNotesVersion)."
}
Write-Log -Level "OK" -Message " Release notes version matches: v$releaseNotesVersion"
Write-Log -Level "STEP" -Message "Extracting release notes..."
$pattern = "(?ms)^##\s+v$([regex]::Escape($Version))\b.*?(?=^##\s+v\d+\.\d+\.\d+|\Z)"
$match = [regex]::Match($releaseNotesContent, $pattern)
if (-not $match.Success) {
throw "Release notes entry for version $Version not found."
}
Write-Log -Level "OK" -Message " Release notes extracted."
return $match.Value.Trim()
}
function Invoke-Plugin {
param(
[Parameter(Mandatory = $true)]
$Settings
)
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
$pluginSettings = $Settings
$sharedSettings = $Settings.Context
$githubTokenEnvVar = $pluginSettings.githubToken
$configuredRepository = $pluginSettings.repository
$releaseNotesFileSetting = $pluginSettings.releaseNotesFile
$releaseTitlePatternSetting = $pluginSettings.releaseTitlePattern
$scriptDir = $sharedSettings.ScriptDir
$version = $sharedSettings.Version
$tag = $sharedSettings.Tag
$releaseDir = $sharedSettings.ReleaseDir
$releaseAssetPaths = @()
Assert-Command gh
if ([string]::IsNullOrWhiteSpace($githubTokenEnvVar)) {
throw "GitHub plugin requires 'githubToken' in scriptsettings.json."
}
$githubToken = [System.Environment]::GetEnvironmentVariable($githubTokenEnvVar)
if ([string]::IsNullOrWhiteSpace($githubToken)) {
throw "GitHub token is not set. Set '$githubTokenEnvVar' and rerun."
}
if ([string]::IsNullOrWhiteSpace($releaseNotesFileSetting)) {
throw "GitHub plugin requires 'releaseNotesFile' in scriptsettings.json."
}
$releaseNotesFile = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $releaseNotesFileSetting))
$releaseNotes = Get-ReleaseNotesInternal -ReleaseNotesFile $releaseNotesFile -Version $version
if ($sharedSettings.PSObject.Properties['ReleaseAssetPaths'] -and $sharedSettings.ReleaseAssetPaths) {
$releaseAssetPaths = @($sharedSettings.ReleaseAssetPaths)
}
elseif ($sharedSettings.PSObject.Properties['PackageFile'] -and $sharedSettings.PackageFile) {
$releaseAssetPaths = @($sharedSettings.PackageFile.FullName)
if ($sharedSettings.PSObject.Properties['SymbolsPackageFile'] -and $sharedSettings.SymbolsPackageFile) {
$releaseAssetPaths += $sharedSettings.SymbolsPackageFile.FullName
}
}
if ($releaseAssetPaths.Count -eq 0) {
throw "GitHub release requires at least one prepared release asset."
}
$repo = Get-GitHubRepositoryInternal -ConfiguredRepository $configuredRepository
$releaseTitlePattern = if ([string]::IsNullOrWhiteSpace($releaseTitlePatternSetting)) {
"Release {version}"
}
else {
$releaseTitlePatternSetting
}
$releaseName = $releaseTitlePattern -replace '\{version\}', $version
Write-Log -Level "INFO" -Message " GitHub repository: $repo"
Write-Log -Level "INFO" -Message " GitHub tag: $tag"
Write-Log -Level "INFO" -Message " GitHub title: $releaseName"
$previousGhToken = $env:GH_TOKEN
$env:GH_TOKEN = $githubToken
try {
$ghVersion = & gh --version 2>&1
if ($ghVersion) {
Write-Log -Level "INFO" -Message " gh version: $($ghVersion[0])"
}
Write-Log -Level "INFO" -Message " Auth env var: $githubTokenEnvVar (set)"
$authArgs = @("api", "repos/$repo", "--jq", ".full_name")
$authOutput = & gh @authArgs 2>&1
$authExitCode = $LASTEXITCODE
if ($authExitCode -ne 0 -or [string]::IsNullOrWhiteSpace(($authOutput | Out-String))) {
Write-Log -Level "WARN" -Message " gh auth check failed (exit code: $authExitCode)."
if ($authOutput) {
$authOutput | ForEach-Object { Write-Log -Level "WARN" -Message " $_" }
}
$authStatus = & gh auth status --hostname github.com 2>&1
if ($authStatus) {
$authStatus | ForEach-Object { Write-Log -Level "WARN" -Message " $_" }
}
throw "GitHub CLI authentication failed for repository '$repo'. Ensure '$githubTokenEnvVar' is valid and has access to this repository."
}
Write-Log -Level "OK" -Message " GitHub token validated for repository: $($authOutput | Select-Object -First 1)"
Write-Log -Level "STEP" -Message "Creating GitHub release..."
$releaseViewArgs = @("release", "view", $tag, "--repo", $repo)
& gh @releaseViewArgs 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Log -Level "WARN" -Message " Release $tag already exists. Deleting..."
$releaseDeleteArgs = @("release", "delete", $tag, "--repo", $repo, "--yes")
& gh @releaseDeleteArgs
if ($LASTEXITCODE -ne 0) {
throw "Failed to delete existing release $tag."
}
}
$notesFilePath = Join-Path $releaseDir ("release-notes-{0}.md" -f $version)
try {
[System.IO.File]::WriteAllText($notesFilePath, $releaseNotes, [System.Text.UTF8Encoding]::new($false))
$createReleaseArgs = @("release", "create", $tag) + $releaseAssetPaths + @(
"--repo", $repo,
"--title", $releaseName,
"--notes-file", $notesFilePath
)
& gh @createReleaseArgs
if ($LASTEXITCODE -ne 0) {
throw "Failed to create GitHub release for tag $tag."
}
}
finally {
if (Test-Path $notesFilePath) {
Remove-Item $notesFilePath -Force
}
}
Write-Log -Level "OK" -Message " GitHub release created successfully."
$sharedSettings | Add-Member -NotePropertyName PublishCompleted -NotePropertyValue $true -Force
}
finally {
if ($null -ne $previousGhToken) {
$env:GH_TOKEN = $previousGhToken
}
else {
Remove-Item Env:GH_TOKEN -ErrorAction SilentlyContinue
}
}
}
Export-ModuleMember -Function Invoke-Plugin