maksit-hamode/utils/plugins/Platform/ReleasePublishGuard.psm1
2026-07-28 22:28:19 +02:00

174 lines
6.4 KiB
PowerShell

#requires -Version 7.0
#requires -PSEdition Core
<#
.SYNOPSIS
Central gate before remote-mutation plugins (see each plugin's Get-PluginMetadata).
.DESCRIPTION
Place this plugin immediately before any publish plugins in scriptSettings.json. It sets
shared context skipPublishPlugins to false when all configured requirements pass, or true
when they do not (whenRequirementsNotMet: skip). Publish plugins no longer use per-plugin
branch lists; put allowed branches here instead.
Typical checks: allowed branches, optional clean working tree, exact semver tag on HEAD,
tag version vs DotNetReleaseVersion, optional push tag to remote.
The engine preflight no longer reads git tags; this plugin sets context.tag from the
git tag on HEAD when required. Shared context version always remains from DotNetReleaseVersion.
#>
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
$srcDir = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$pluginSupportModulePath = Join-Path $srcDir "modules/Engine/PluginSupport.psm1"
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
}
}
function Get-ExactTagOnHeadSilentlyInternal {
$raw = & git describe --tags --exact-match HEAD 2>&1
if ($LASTEXITCODE -ne 0) {
return $null
}
$s = ($raw | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace($s)) {
return $null
}
return $s
}
function Invoke-NotMetInternal {
param(
[Parameter(Mandatory = $true)]
$Shared,
[Parameter(Mandatory = $true)]
[string]$When,
[Parameter(Mandatory = $true)]
[string]$Reason
)
if (Get-Command Set-EngineState -ErrorAction SilentlyContinue) {
Set-EngineState -Context $Shared -Name 'skipPublishPlugins' -Value $true
}
else {
$Shared | Add-Member -NotePropertyName skipPublishPlugins -NotePropertyValue $true -Force
}
if ($When -eq 'fail') {
throw "ReleasePublishGuard: $Reason"
}
Write-Log -Level "WARN" -Message " Publish suppressed: $Reason"
}
function Invoke-Plugin {
param(
[Parameter(Mandatory = $true)]
$Settings
)
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "PluginSupport" -RequiredCommand "Get-PluginBranches"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineState"
Import-PluginDependency -ModuleName "GitTools" -RequiredCommand "Get-GitStatusShort"
Import-PluginDependency -ModuleName "GitTools" -RequiredCommand "Test-RemoteTagExists"
Import-PluginDependency -ModuleName "GitTools" -RequiredCommand "Push-TagToRemote"
$pluginSettings = $Settings
$shared = $Settings.context
$when = 'skip'
if ($null -ne $pluginSettings.whenRequirementsNotMet) {
$when = [string]$pluginSettings.whenRequirementsNotMet
}
if ($when -notin @('skip', 'fail')) {
throw "ReleasePublishGuard: whenRequirementsNotMet must be 'skip' or 'fail'."
}
Set-EngineState -Context $shared -Name 'skipPublishPlugins' -Value $false
Write-Log -Level "STEP" -Message "Release publish guard..."
$allowed = @(Get-PluginBranches -Plugin $pluginSettings)
if ($allowed.Count -gt 0 -and $allowed -notcontains '*' -and $allowed -notcontains $shared.currentBranch) {
Invoke-NotMetInternal -Shared $shared -When $when -Reason "branch '$($shared.currentBranch)' is not in the guard branches list."
return
}
$requireClean = $false
if ($null -ne $pluginSettings.requireCleanWorkingTree) {
$requireClean = [bool]$pluginSettings.requireCleanWorkingTree
}
if ($requireClean) {
$dirtyRaw = Get-GitStatusShort
if (-not [string]::IsNullOrWhiteSpace([string]$dirtyRaw)) {
Invoke-NotMetInternal -Shared $shared -When $when -Reason "working tree is not clean (requireCleanWorkingTree is true)."
return
}
}
$requireTag = $true
if ($null -ne $pluginSettings.requireExactTagOnHead) {
$requireTag = [bool]$pluginSettings.requireExactTagOnHead
}
$tag = $null
if ($requireTag) {
$tag = Get-ExactTagOnHeadSilentlyInternal
if ([string]::IsNullOrWhiteSpace($tag)) {
Invoke-NotMetInternal -Shared $shared -When $when -Reason "no exact semver tag on HEAD (git describe --tags --exact-match)."
return
}
if ($tag -notmatch '^v(\d+\.\d+\.\d+)$') {
Invoke-NotMetInternal -Shared $shared -When $when -Reason "tag '$tag' must match vX.Y.Z."
return
}
$tagVersion = $Matches[1]
$mustMatch = $true
if ($null -ne $pluginSettings.tagVersionMustMatchReleaseVersion) {
$mustMatch = [bool]$pluginSettings.tagVersionMustMatchReleaseVersion
}
elseif ($null -ne $pluginSettings.tagVersionMustMatchNpmRelease) {
$mustMatch = [bool]$pluginSettings.tagVersionMustMatchNpmRelease
}
elseif ($null -ne $pluginSettings.tagVersionMustMatchDotNetRelease) {
$mustMatch = [bool]$pluginSettings.tagVersionMustMatchDotNetRelease
}
if ($mustMatch -and $tagVersion -ne [string]$shared.version) {
Invoke-NotMetInternal -Shared $shared -When $when -Reason "tag version $tagVersion does not match release version $($shared.version)."
return
}
Set-EngineState -Context $shared -Name 'tag' -Value $tag
}
$ensureRemote = $true
if ($null -ne $pluginSettings.ensureTagOnRemote) {
$ensureRemote = [bool]$pluginSettings.ensureTagOnRemote
}
if ($ensureRemote -and $requireTag -and -not [string]::IsNullOrWhiteSpace($tag)) {
$remote = 'origin'
if (-not [string]::IsNullOrWhiteSpace([string]$pluginSettings.remoteName)) {
$remote = [string]$pluginSettings.remoteName
}
Write-Log -Level "STEP" -Message "Verifying tag on remote '$remote'..."
if (-not (Test-RemoteTagExists -Tag $tag -Remote $remote)) {
Write-Log -Level "WARN" -Message " Tag $tag not on remote. Pushing..."
Push-TagToRemote -Tag $tag -Remote $remote
}
else {
Write-Log -Level "OK" -Message " Tag exists on remote."
}
}
Write-Log -Level "OK" -Message " Publish guard passed; publish plugins will run."
}
Export-ModuleMember -Function Invoke-Plugin