mirror of
https://github.com/MAKS-IT-COM/uscheduler.git
synced 2026-04-01 00:32:10 +02:00
369 lines
11 KiB
PowerShell
369 lines
11 KiB
PowerShell
#requires -Version 7.0
|
|
#requires -PSEdition Core
|
|
|
|
if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
|
|
$loggingModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) "Logging.psm1"
|
|
if (Test-Path $loggingModulePath -PathType Leaf) {
|
|
Import-Module $loggingModulePath -Force
|
|
}
|
|
}
|
|
|
|
function Import-PluginDependency {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ModuleName,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RequiredCommand
|
|
)
|
|
|
|
if (Get-Command $RequiredCommand -ErrorAction SilentlyContinue) {
|
|
return
|
|
}
|
|
|
|
$moduleRoot = Split-Path $PSScriptRoot -Parent
|
|
$modulePath = Join-Path $moduleRoot "$ModuleName.psm1"
|
|
if (Test-Path $modulePath -PathType Leaf) {
|
|
# Import into the global session so the calling plugin can see the exported commands.
|
|
# Importing only into this module's scope would make the dependency invisible to the plugin.
|
|
Import-Module $modulePath -Force -Global -ErrorAction Stop
|
|
}
|
|
|
|
if (-not (Get-Command $RequiredCommand -ErrorAction SilentlyContinue)) {
|
|
throw "Required command '$RequiredCommand' is still unavailable after importing module '$ModuleName'."
|
|
}
|
|
}
|
|
|
|
function Get-ConfiguredPlugins {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$Settings
|
|
)
|
|
|
|
if (-not $Settings.PSObject.Properties['Plugins'] -or $null -eq $Settings.Plugins) {
|
|
return @()
|
|
}
|
|
|
|
# JSON can deserialize a single plugin as one object or multiple plugins as an array.
|
|
# Always return an array so the engine can loop without special-case logic.
|
|
if ($Settings.Plugins -is [System.Collections.IEnumerable] -and -not ($Settings.Plugins -is [string])) {
|
|
return @($Settings.Plugins)
|
|
}
|
|
|
|
return @($Settings.Plugins)
|
|
}
|
|
|
|
function Get-PluginStage {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin
|
|
)
|
|
|
|
if (-not $Plugin.PSObject.Properties['Stage'] -or [string]::IsNullOrWhiteSpace([string]$Plugin.Stage)) {
|
|
return "Release"
|
|
}
|
|
|
|
return [string]$Plugin.Stage
|
|
}
|
|
|
|
function Get-PluginBranches {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin
|
|
)
|
|
|
|
if (-not $Plugin.PSObject.Properties['branches'] -or $null -eq $Plugin.branches) {
|
|
return @()
|
|
}
|
|
|
|
# Strings are also IEnumerable in PowerShell, so exclude them or we would split into characters.
|
|
if ($Plugin.branches -is [System.Collections.IEnumerable] -and -not ($Plugin.branches -is [string])) {
|
|
return @($Plugin.branches | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace([string]$Plugin.branches)) {
|
|
return @()
|
|
}
|
|
|
|
return @([string]$Plugin.branches)
|
|
}
|
|
|
|
function Test-IsPublishPlugin {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin
|
|
)
|
|
|
|
if ($null -eq $Plugin -or [string]::IsNullOrWhiteSpace([string]$Plugin.Name)) {
|
|
return $false
|
|
}
|
|
|
|
return @('GitHub', 'NuGet') -contains ([string]$Plugin.Name)
|
|
}
|
|
|
|
function Get-PluginSettingValue {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object[]]$Plugins,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PropertyName
|
|
)
|
|
|
|
foreach ($plugin in $Plugins) {
|
|
if ($null -eq $plugin -or [string]::IsNullOrWhiteSpace($plugin.Name)) {
|
|
continue
|
|
}
|
|
|
|
if (-not $plugin.PSObject.Properties[$PropertyName]) {
|
|
continue
|
|
}
|
|
|
|
$value = $plugin.$PropertyName
|
|
if ($null -eq $value) {
|
|
continue
|
|
}
|
|
|
|
if ($value -is [string] -and [string]::IsNullOrWhiteSpace($value)) {
|
|
continue
|
|
}
|
|
|
|
return $value
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Get-PluginPathListSetting {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object[]]$Plugins,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PropertyName,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BasePath
|
|
)
|
|
|
|
$rawPaths = @()
|
|
$value = Get-PluginSettingValue -Plugins $Plugins -PropertyName $PropertyName
|
|
|
|
if ($null -eq $value) {
|
|
return @()
|
|
}
|
|
|
|
# Same rule as above: treat a string as one path, not a char-by-char sequence.
|
|
if ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) {
|
|
$rawPaths += $value
|
|
}
|
|
else {
|
|
$rawPaths += $value
|
|
}
|
|
|
|
$resolvedPaths = @()
|
|
foreach ($path in $rawPaths) {
|
|
if ([string]::IsNullOrWhiteSpace([string]$path)) {
|
|
continue
|
|
}
|
|
|
|
$resolvedPaths += [System.IO.Path]::GetFullPath((Join-Path $BasePath ([string]$path)))
|
|
}
|
|
|
|
# Wrap again to stop PowerShell from unrolling a single-item array into a bare string.
|
|
return @($resolvedPaths)
|
|
}
|
|
|
|
function Get-PluginPathSetting {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object[]]$Plugins,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PropertyName,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BasePath
|
|
)
|
|
|
|
$value = Get-PluginSettingValue -Plugins $Plugins -PropertyName $PropertyName
|
|
if ($null -eq $value -or [string]::IsNullOrWhiteSpace([string]$value)) {
|
|
return $null
|
|
}
|
|
|
|
return [System.IO.Path]::GetFullPath((Join-Path $BasePath ([string]$value)))
|
|
}
|
|
|
|
function Get-ArchiveNamePattern {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object[]]$Plugins,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CurrentBranch
|
|
)
|
|
|
|
foreach ($plugin in $Plugins) {
|
|
if ($null -eq $plugin -or [string]::IsNullOrWhiteSpace($plugin.Name)) {
|
|
continue
|
|
}
|
|
|
|
if (-not $plugin.Enabled) {
|
|
continue
|
|
}
|
|
|
|
$allowedBranches = Get-PluginBranches -Plugin $plugin
|
|
if ($allowedBranches.Count -gt 0 -and -not ($allowedBranches -contains $CurrentBranch)) {
|
|
continue
|
|
}
|
|
|
|
if ($plugin.PSObject.Properties['zipNamePattern'] -and -not [string]::IsNullOrWhiteSpace([string]$plugin.zipNamePattern)) {
|
|
return [string]$plugin.zipNamePattern
|
|
}
|
|
}
|
|
|
|
return "release-{version}.zip"
|
|
}
|
|
|
|
function Resolve-PluginModulePath {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PluginsDirectory
|
|
)
|
|
|
|
$pluginFileName = "{0}.psm1" -f $Plugin.Name
|
|
$candidatePaths = @(
|
|
(Join-Path $PluginsDirectory $pluginFileName),
|
|
(Join-Path (Join-Path (Split-Path $PluginsDirectory -Parent) "CustomPlugins") $pluginFileName)
|
|
)
|
|
|
|
foreach ($candidatePath in $candidatePaths) {
|
|
if (Test-Path $candidatePath -PathType Leaf) {
|
|
return $candidatePath
|
|
}
|
|
}
|
|
|
|
return $candidatePaths[0]
|
|
}
|
|
|
|
function Test-PluginRunnable {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$SharedSettings,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PluginsDirectory,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[bool]$WriteLogs = $true
|
|
)
|
|
|
|
if ($null -eq $Plugin -or [string]::IsNullOrWhiteSpace($Plugin.Name)) {
|
|
if ($WriteLogs) {
|
|
Write-Log -Level "WARN" -Message "Skipping plugin entry with no Name."
|
|
}
|
|
return $false
|
|
}
|
|
|
|
if (-not $Plugin.Enabled) {
|
|
if ($WriteLogs) {
|
|
Write-Log -Level "WARN" -Message "Skipping plugin '$($Plugin.Name)' (disabled)."
|
|
}
|
|
return $false
|
|
}
|
|
|
|
if (Test-IsPublishPlugin -Plugin $Plugin) {
|
|
$allowedBranches = Get-PluginBranches -Plugin $Plugin
|
|
if ($allowedBranches.Count -eq 0) {
|
|
if ($WriteLogs) {
|
|
Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.Name)' because no publish branches are configured."
|
|
}
|
|
return $false
|
|
}
|
|
|
|
if (-not ($allowedBranches -contains $SharedSettings.CurrentBranch)) {
|
|
if ($WriteLogs) {
|
|
Write-Log -Level "INFO" -Message "Skipping plugin '$($Plugin.Name)' on branch '$($SharedSettings.CurrentBranch)'."
|
|
}
|
|
return $false
|
|
}
|
|
}
|
|
|
|
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
|
|
if (-not (Test-Path $pluginModulePath -PathType Leaf)) {
|
|
if ($WriteLogs) {
|
|
Write-Log -Level "ERROR" -Message "Plugin module not found: $pluginModulePath"
|
|
}
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function New-PluginInvocationSettings {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$SharedSettings
|
|
)
|
|
|
|
$properties = @{}
|
|
foreach ($property in $Plugin.PSObject.Properties) {
|
|
$properties[$property.Name] = $property.Value
|
|
}
|
|
|
|
# Plugins receive their own config plus a shared Context object that carries runtime artifacts.
|
|
$properties['Context'] = $SharedSettings
|
|
return [pscustomobject]$properties
|
|
}
|
|
|
|
function Invoke-ConfiguredPlugin {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Plugin,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$SharedSettings,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PluginsDirectory,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[bool]$ContinueOnError = $true
|
|
)
|
|
|
|
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -PluginsDirectory $PluginsDirectory -WriteLogs:$true)) {
|
|
return
|
|
}
|
|
|
|
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -PluginsDirectory $PluginsDirectory
|
|
Write-Log -Level "STEP" -Message "Running plugin '$($Plugin.Name)'..."
|
|
|
|
try {
|
|
$moduleInfo = Import-Module $pluginModulePath -Force -PassThru -ErrorAction Stop
|
|
# Resolve Invoke-Plugin from the imported module explicitly so we call the plugin we just loaded,
|
|
# not some command with the same name from another module already in session.
|
|
$invokeCommand = Get-Command -Name "Invoke-Plugin" -Module $moduleInfo.Name -ErrorAction Stop
|
|
$pluginSettings = New-PluginInvocationSettings -Plugin $Plugin -SharedSettings $SharedSettings
|
|
|
|
& $invokeCommand -Settings $pluginSettings
|
|
Write-Log -Level "OK" -Message " Plugin '$($Plugin.Name)' completed."
|
|
}
|
|
catch {
|
|
Write-Log -Level "ERROR" -Message " Plugin '$($Plugin.Name)' failed: $($_.Exception.Message)"
|
|
if (-not $ContinueOnError) {
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
Export-ModuleMember -Function Import-PluginDependency, Get-ConfiguredPlugins, Get-PluginStage, Get-PluginBranches, Test-IsPublishPlugin, Get-PluginSettingValue, Get-PluginPathListSetting, Get-PluginPathSetting, Get-ArchiveNamePattern, Resolve-PluginModulePath, Test-PluginRunnable, New-PluginInvocationSettings, Invoke-ConfiguredPlugin
|