Compare commits

..

No commits in common. "main" and "v1.3.0" have entirely different histories.
main ... v1.3.0

31 changed files with 982 additions and 842 deletions

View File

@ -7,14 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.3.1] - 2026-08-14
### Changed
- Updated **MaksIT.Results** (2.0.4), **Microsoft.Extensions.*** (10.0.11), and **System.Management.Automation** (7.6.5) package references.
- Updated test dependencies: **Microsoft.NET.Test.Sdk** (18.9.0), **Microsoft.Extensions.Logging.Console** (10.0.11).
- Synced **RepoUtils** (`utils/`): engine/plugin updates; removed obsolete `Update-RepoUtils` tooling (local-copy sync only).
## [1.3.0] - 2026-07-27
### Added

View File

@ -7,7 +7,7 @@
<RootNamespace>MaksIT.PodmanClientDotNet.PowerShell</RootNamespace>
<AssemblyName>MaksIT.PodmanClientDotNet.PowerShell</AssemblyName>
<Description>PowerShell module with cmdlets for PodmanClient.DotNet.</Description>
<Version>1.3.1</Version>
<Version>1.3.0</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@ -16,8 +16,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Management.Automation" Version="7.6.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.11" />
<PackageReference Include="System.Management.Automation" Version="7.6.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
</ItemGroup>
<ItemGroup>

View File

@ -12,7 +12,7 @@
<!-- NuGet package metadata -->
<PackageId>PodmanClient.DotNet</PackageId>
<Version>1.3.1</Version>
<Version>1.3.0</Version>
<Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company>
<Product>PodmanClient.DotNet</Product>
@ -49,11 +49,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MaksIT.Results" Version="2.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.11" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.11" />
<PackageReference Include="MaksIT.Results" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
</ItemGroup>
<ItemGroup>

View File

@ -14,8 +14,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>

View File

@ -0,0 +1,3 @@
@echo off
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0tools\Update-RepoUtils\Update-RepoUtils.ps1" %*
pause

View File

@ -5,14 +5,21 @@
.SYNOPSIS
Plugin-driven release engine entry script.
.NOTES
Per-plugin dryRun on mutatesRemote plugins validates without remote mutation.
There is no engine-wide dryRun switch set dryRun on each plugin as needed.
.PARAMETER DryRun
When set, plugins that declare mutatesRemote in Get-PluginMetadata validate only (no registry push, GitHub release, or cluster deploy).
.PARAMETER Mode
Optional override for HelmSelfDeploy values resolution (single or ha).
When omitted, HelmSelfDeploy.deployMode from scriptSettings.json is used (CI/CD default).
Manual installs: Invoke-ReleasePackage-Single.bat / Invoke-ReleasePackage-HA.bat.
#>
# Keep a plain param block so unbound launcher arguments remain in $args for
# optional Initialize-ReleaseExtension (advanced binding would reject them).
param()
[CmdletBinding()]
param(
[switch]$DryRun,
[ValidateSet('single', 'ha')]
[string]$Mode
)
$ErrorActionPreference = 'Stop'
Set-Location $PSScriptRoot
@ -22,43 +29,20 @@ $srcDir = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
. (Join-Path $srcDir 'modules/Engine/Import-EngineModules.ps1')
Import-EngineModules -Engine Release
$releaseExtension = $null
if (Get-Command Initialize-ReleaseExtension -ErrorAction SilentlyContinue) {
$releaseExtension = Initialize-ReleaseExtension -ScriptDir $PSScriptRoot -ArgumentList $args
$settings = Get-ScriptSettings -ScriptDir $PSScriptRoot
$resolveModeParams = @{ Settings = $settings }
if ($PSBoundParameters.ContainsKey('Mode')) {
$resolveModeParams['ModeOverride'] = $Mode
}
$settings = if ($null -ne $releaseExtension) {
$releaseExtension.Settings
}
else {
Get-ScriptSettings -ScriptDir $PSScriptRoot
}
$deployMode = Get-DeployModeFromSettings @resolveModeParams
$configuredPlugins = Get-ConfiguredPlugins -Settings $settings
$releaseBanner = if ($null -ne $releaseExtension) {
$releaseExtension.StepBanner
}
else {
'RELEASE ENGINE'
}
Write-Log -Level 'STEP' -Message '=================================================='
Write-Log -Level 'STEP' -Message $releaseBanner
Write-Log -Level 'STEP' -Message "RELEASE ENGINE (deploy mode: $deployMode)"
Write-Log -Level 'STEP' -Message '=================================================='
$plugins = $configuredPlugins
$newContextParams = @{
Plugins = $plugins
ScriptDir = $PSScriptRoot
SrcDir = $srcDir
Settings = $settings
}
if ($null -ne $releaseExtension) {
$newContextParams['ExtensionData'] = $releaseExtension.ContextData
}
$engineContext = New-EngineContext @newContextParams
$engineContext = New-EngineContext -Plugins $plugins -ScriptDir $PSScriptRoot -SrcDir $srcDir -Settings $settings -DryRun:$DryRun -DeployMode $deployMode
Write-Log -Level 'OK' -Message 'All pre-flight checks passed!'
$sharedPluginSettings = $engineContext
@ -75,14 +59,14 @@ else {
if ((Test-IsPublishPlugin -Plugin $plugin -EngineDirectory $PSScriptRoot) -and -not $releaseStageInitialized) {
if (Test-PluginRunnable -Plugin $plugin -SharedSettings $sharedPluginSettings -EngineDirectory $PSScriptRoot -WriteLogs:$false) {
Initialize-ReleaseStageContext -SharedSettings $sharedPluginSettings -ArtifactsDirectory $engineContext.artifactsDirectory
$remainingPlugins = @($plugins[$pluginIndex..($plugins.Count - 1)])
Initialize-ReleaseStageContext -RemainingPlugins $remainingPlugins -SharedSettings $sharedPluginSettings -ArtifactsDirectory $engineContext.artifactsDirectory -Version $engineContext.version
$releaseStageInitialized = $true
}
}
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $sharedPluginSettings -EngineDirectory $PSScriptRoot
# Exact $true only: polluted arrays (CLI stdout + $false) are truthy under -not.
if ($pluginSucceeded -ne $true) {
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $sharedPluginSettings -EngineDirectory $PSScriptRoot -ContinueOnError:$false
if (-not $pluginSucceeded) {
$releaseHadPluginFailures = $true
break
}
@ -104,6 +88,9 @@ elseif ($engineContext.PSObject.Properties.Name -contains 'skipPublishPlugins' -
elseif ($engineContext.isNonReleaseBranch) {
Write-Log -Level 'OK' -Message 'NON-RELEASE RUN COMPLETE'
}
elseif ($engineContext.dryRun) {
Write-Log -Level 'OK' -Message 'DRY RUN COMPLETE'
}
else {
Write-Log -Level 'OK' -Message 'RELEASE COMPLETE'
}

View File

@ -6,8 +6,6 @@
Plugin-driven test and coverage engine entry script.
#>
$ErrorActionPreference = 'Stop'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$srcDir = (Resolve-Path (Join-Path $scriptDir '..\..')).Path
@ -31,9 +29,8 @@ if ($configuredPlugins.Count -eq 0) {
$testHadPluginFailures = $false
foreach ($plugin in $configuredPlugins) {
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $engineContext -EngineDirectory $scriptDir
# Exact $true only: polluted arrays (CLI stdout + $false) are truthy under -not.
if ($pluginSucceeded -ne $true) {
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $engineContext -EngineDirectory $scriptDir -ContinueOnError:$false
if (-not $pluginSucceeded) {
$testHadPluginFailures = $true
break
}

View File

@ -3,14 +3,13 @@
<#
.SYNOPSIS
Generic engine helpers: path resolution and the shared context facts API.
Helpers to resolve engine semver and relative paths from plugin configuration.
.DESCRIPTION
Engine-owned state stays on the context object (version, tag, skipPublishPlugins, ).
Plugin-published values go under $context.facts[namespace][name] via Set/Get/Test-EngineFact.
During migration, -LegacyProperty on Set dual-writes flat properties; Get falls back to them.
Version plugins declare providesVersion = $true; New-EngineContext discovers the single enabled one.
Used by New-EngineContext and version plugins:
- DotNetReleaseVersion plugin -> projectFiles (.csproj <Version>)
- NpmReleaseVersion plugin -> packageJsonPath (package.json version)
- FileReleaseVersion plugin -> versionFilePath (repo-root VERSION file)
#>
if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
@ -20,16 +19,6 @@ if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
}
}
$script:EngineStateAllowlist = @(
'version'
'tag'
'skipPublishPlugins'
'releaseDir'
'artifactsDirectory'
'deployMode'
'orchestrator'
)
function Resolve-RelativePaths {
param(
[Parameter(Mandatory = $true)]
@ -63,270 +52,251 @@ function Resolve-RelativePaths {
return @($resolved)
}
function Initialize-EngineFactsBag {
function Get-CsprojPropertyValue {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context
[xml]$Csproj,
[Parameter(Mandatory = $true)]
[string]$PropertyName
)
if (-not ($Context.PSObject.Properties.Name -contains 'facts') -or $null -eq $Context.facts) {
$Context | Add-Member -NotePropertyName facts -NotePropertyValue ([ordered]@{}) -Force
}
}
# SDK-style .csproj files can have multiple PropertyGroup nodes.
# Use the first group that defines the requested property.
$propNode = $Csproj.Project.PropertyGroup |
Where-Object { $_.$PropertyName } |
Select-Object -First 1
function Assert-EngineFactNamespace {
param(
[Parameter(Mandatory = $true)]
[string]$Namespace
)
if ($Namespace -eq 'engine') {
throw "Namespace 'engine' is reserved; use Set-EngineState / Get-EngineState for engine-owned fields."
}
if ($Namespace -cnotmatch '^[a-z][a-z0-9]*$') {
throw "Invalid facts namespace '$Namespace'. Use lowercase alphanumeric starting with a letter (e.g. 'test', 'dotnet')."
}
}
function Assert-EngineFactName {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
if ($Name -cnotmatch '^[a-zA-Z][a-zA-Z0-9_]*$') {
throw "Invalid fact name '$Name'. Use letters, digits, underscore; must start with a letter."
}
}
function Test-EngineFactValuePresent {
param(
[AllowNull()]
$Value
)
if ($null -eq $Value) {
return $false
}
if ($Value -is [string] -and [string]::IsNullOrWhiteSpace($Value)) {
return $false
}
return $true
}
function Set-EngineFact {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Namespace,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
[AllowNull()]
$Value,
[ValidateSet('Error', 'Replace', 'Keep')]
[string]$Overwrite = 'Error',
[Parameter(Mandatory = $false)]
[string]$LegacyProperty
)
Assert-EngineFactNamespace -Namespace $Namespace
Assert-EngineFactName -Name $Name
Initialize-EngineFactsBag -Context $Context
if (-not $Context.facts.Contains($Namespace)) {
$Context.facts[$Namespace] = [ordered]@{}
}
$bag = $Context.facts[$Namespace]
$exists = $bag.Contains($Name)
if ($exists) {
if ($Overwrite -eq 'Keep') {
if (-not [string]::IsNullOrWhiteSpace($LegacyProperty) -and -not ($Context.PSObject.Properties.Name -contains $LegacyProperty)) {
$Context | Add-Member -NotePropertyName $LegacyProperty -NotePropertyValue $bag[$Name] -Force
}
return
}
if ($Overwrite -eq 'Error') {
throw "Fact ${Namespace}.${Name} already set (use -Overwrite Replace or Keep)."
}
}
$bag[$Name] = $Value
if (-not [string]::IsNullOrWhiteSpace($LegacyProperty)) {
$Context | Add-Member -NotePropertyName $LegacyProperty -NotePropertyValue $Value -Force
}
}
function Get-EngineFact {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Namespace,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $false)]
$Default,
[switch]$Required,
[Parameter(Mandatory = $false)]
[string[]]$LegacyProperty
)
Assert-EngineFactNamespace -Namespace $Namespace
Assert-EngineFactName -Name $Name
Initialize-EngineFactsBag -Context $Context
if ($Context.facts.Contains($Namespace) -and $Context.facts[$Namespace].Contains($Name)) {
$value = $Context.facts[$Namespace][$Name]
if (Test-EngineFactValuePresent -Value $value) {
return $value
}
}
foreach ($propertyName in @($LegacyProperty)) {
if ([string]::IsNullOrWhiteSpace($propertyName)) {
continue
}
if ($Context.PSObject.Properties.Name -contains $propertyName) {
$legacyValue = $Context.$propertyName
if (Test-EngineFactValuePresent -Value $legacyValue) {
return $legacyValue
}
}
}
if ($Required) {
throw "Required fact ${Namespace}.${Name} is missing."
}
if ($PSBoundParameters.ContainsKey('Default')) {
return $Default
if ($propNode) {
return $propNode.$PropertyName
}
return $null
}
function Test-EngineFact {
function Get-CsprojVersions {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Namespace,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $false)]
[string[]]$LegacyProperty
[string[]]$ProjectFiles
)
$value = Get-EngineFact -Context $Context -Namespace $Namespace -Name $Name -LegacyProperty $LegacyProperty
return (Test-EngineFactValuePresent -Value $value)
Write-Log -Level "INFO" -Message "Reading version(s) from SDK-style project files (projectFiles)..."
$projectVersions = @{}
foreach ($projectPath in $ProjectFiles) {
if (-not (Test-Path $projectPath -PathType Leaf)) {
Write-Error "Project file not found at: $projectPath"
exit 1
}
function Set-EngineState {
if ([System.IO.Path]::GetExtension($projectPath) -ne ".csproj") {
Write-Error "Configured project file is not a .csproj file: $projectPath"
exit 1
}
[xml]$csproj = Get-Content $projectPath
$version = Get-CsprojPropertyValue -Csproj $csproj -PropertyName "Version"
if (-not $version) {
Write-Error "Version not found in $projectPath"
exit 1
}
$projectVersions[$projectPath] = $version
Write-Log -Level "OK" -Message " $([System.IO.Path]::GetFileName($projectPath)): $version"
}
return $projectVersions
}
function Resolve-DotNetReleaseVersion {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[object[]]$Plugins,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
[AllowNull()]
$Value
[string]$ScriptDir
)
if ($script:EngineStateAllowlist -notcontains $Name) {
throw "Engine state key '$Name' is not allowlisted. Use Set-EngineFact for plugin outputs, or extend the engine allowlist for documented engine fields."
$releaseVersionPlugin = @($Plugins | Where-Object { $_.name -eq 'DotNetReleaseVersion' } | Select-Object -First 1)
if ($releaseVersionPlugin.Count -eq 0 -or $null -eq $releaseVersionPlugin[0]) {
Write-Error "Configure a DotNetReleaseVersion plugin in scriptSettings.json with projectFiles."
exit 1
}
$Context | Add-Member -NotePropertyName $Name -NotePropertyValue $Value -Force
$releaseVersionSettings = $releaseVersionPlugin[0]
$projectFiles = @(Resolve-RelativePaths -Value $releaseVersionSettings.projectFiles -BasePath $ScriptDir)
if ($projectFiles.Count -eq 0) {
Write-Error "Configure release version via DotNetReleaseVersion.projectFiles (first .csproj with <Version>)."
exit 1
}
function Add-EnginePublishCompletion {
$projectVersions = Get-CsprojVersions -ProjectFiles $projectFiles
$version = $projectVersions[$projectFiles[0]]
return [pscustomobject]@{
version = $version
source = 'DotNetReleaseVersion'
}
}
function Resolve-NpmReleaseVersion {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[object[]]$Plugins,
[Parameter(Mandatory = $true)]
[string]$Publisher
[string]$ScriptDir
)
if ([string]::IsNullOrWhiteSpace($Publisher)) {
throw "Publisher name is required."
$releaseVersionPlugin = @($Plugins | Where-Object { $_.name -eq 'NpmReleaseVersion' } | Select-Object -First 1)
if ($releaseVersionPlugin.Count -eq 0 -or $null -eq $releaseVersionPlugin[0]) {
Write-Error "Configure an NpmReleaseVersion plugin in scriptSettings.json with packageJsonPath."
exit 1
}
$completedBy = @(Get-EngineFact -Context $Context -Namespace 'publish' -Name 'completedBy' -Default @())
if ($completedBy -notcontains $Publisher) {
$completedBy += $Publisher
$releaseVersionSettings = $releaseVersionPlugin[0]
$packageJsonPaths = @(Resolve-RelativePaths -Value $releaseVersionSettings.packageJsonPath -BasePath $ScriptDir)
if ($packageJsonPaths.Count -eq 0) {
Write-Error "Configure release version via NpmReleaseVersion.packageJsonPath."
exit 1
}
Set-EngineFact -Context $Context -Namespace 'publish' -Name 'completedBy' -Value $completedBy -Overwrite Replace
Set-EngineFact -Context $Context -Namespace 'publish' -Name 'completed' -Value $true -Overwrite Replace -LegacyProperty 'publishCompleted'
$packageJsonPath = $packageJsonPaths[0]
if (-not (Test-Path $packageJsonPath -PathType Leaf)) {
Write-Error "NpmReleaseVersion: package.json not found at: $packageJsonPath"
exit 1
}
function Get-EngineState {
Write-Log -Level "INFO" -Message "Reading version from npm package.json (packageJsonPath)..."
$json = Get-Content -Path $packageJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
$version = [string]$json.version
if ([string]::IsNullOrWhiteSpace($version)) {
Write-Error "NpmReleaseVersion: 'version' is missing in '$packageJsonPath'."
exit 1
}
if ($version -notmatch '^\d+\.\d+\.\d+') {
Write-Error "NpmReleaseVersion: version '$version' in '$packageJsonPath' is not a valid semver."
exit 1
}
Write-Log -Level "OK" -Message " $([System.IO.Path]::GetFileName($packageJsonPath)): $version"
return [pscustomobject]@{
version = $version
source = 'NpmReleaseVersion'
}
}
function Get-VersionFileSemver {
param(
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $false)]
$Default,
[switch]$Required
[string]$VersionFilePath
)
if ($script:EngineStateAllowlist -notcontains $Name) {
throw "Engine state key '$Name' is not allowlisted."
if (-not (Test-Path $VersionFilePath -PathType Leaf)) {
Write-Error "FileReleaseVersion: VERSION file not found at: $VersionFilePath"
exit 1
}
if ($Context.PSObject.Properties.Name -contains $Name) {
$value = $Context.$Name
if (Test-EngineFactValuePresent -Value $value) {
return $value
$version = (Get-Content -Path $VersionFilePath -Raw -Encoding UTF8).Trim()
if ([string]::IsNullOrWhiteSpace($version)) {
Write-Error "FileReleaseVersion: VERSION file is empty at: $VersionFilePath"
exit 1
}
$version = $version -replace '^[vV]', ''
if ($version -notmatch '^\d+\.\d+\.\d+') {
Write-Error "FileReleaseVersion: version '$version' in '$VersionFilePath' is not a valid semver."
exit 1
}
return $version
}
function Resolve-FileReleaseVersion {
param(
[Parameter(Mandatory = $true)]
[object[]]$Plugins,
[Parameter(Mandatory = $true)]
[string]$ScriptDir
)
$releaseVersionPlugin = @($Plugins | Where-Object { $_.name -eq 'FileReleaseVersion' } | Select-Object -First 1)
if ($releaseVersionPlugin.Count -eq 0 -or $null -eq $releaseVersionPlugin[0]) {
Write-Error "Configure a FileReleaseVersion plugin in scriptSettings.json with versionFilePath."
exit 1
}
$releaseVersionSettings = $releaseVersionPlugin[0]
$versionFileSetting = if ($releaseVersionSettings.versionFilePath) {
$releaseVersionSettings.versionFilePath
}
else {
'..\\..\\..\\VERSION'
}
$versionFilePaths = @(Resolve-RelativePaths -Value $versionFileSetting -BasePath $ScriptDir)
if ($versionFilePaths.Count -eq 0) {
Write-Error "Configure release version via FileReleaseVersion.versionFilePath (repo-root VERSION file)."
exit 1
}
$versionFilePath = $versionFilePaths[0]
Write-Log -Level "INFO" -Message "Reading version from VERSION file (versionFilePath)..."
$version = Get-VersionFileSemver -VersionFilePath $versionFilePath
Write-Log -Level "OK" -Message " $([System.IO.Path]::GetFileName($versionFilePath)): $version"
return [pscustomobject]@{
version = $version
source = 'FileReleaseVersion'
}
}
if ($Required) {
throw "Required engine state '$Name' is missing."
function Resolve-ReleaseVersion {
param(
[Parameter(Mandatory = $true)]
[object[]]$Plugins,
[Parameter(Mandatory = $true)]
[string]$ScriptDir
)
$dotnetPlugin = @($Plugins | Where-Object { $_.name -eq 'DotNetReleaseVersion' -and $_.enabled -ne $false })
$npmPlugin = @($Plugins | Where-Object { $_.name -eq 'NpmReleaseVersion' -and $_.enabled -ne $false })
$filePlugin = @($Plugins | Where-Object { $_.name -eq 'FileReleaseVersion' -and $_.enabled -ne $false })
$enabledVersionPlugins = @()
if ($dotnetPlugin.Count -gt 0) { $enabledVersionPlugins += 'DotNetReleaseVersion' }
if ($npmPlugin.Count -gt 0) { $enabledVersionPlugins += 'NpmReleaseVersion' }
if ($filePlugin.Count -gt 0) { $enabledVersionPlugins += 'FileReleaseVersion' }
if ($enabledVersionPlugins.Count -gt 1) {
Write-Error "Configure only one release version plugin: $($enabledVersionPlugins -join ', ')."
exit 1
}
if ($PSBoundParameters.ContainsKey('Default')) {
return $Default
if ($dotnetPlugin.Count -gt 0) {
return Resolve-DotNetReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir
}
return $null
if ($npmPlugin.Count -gt 0) {
return Resolve-NpmReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir
}
Export-ModuleMember -Function `
Resolve-RelativePaths, `
Initialize-EngineFactsBag, `
Set-EngineFact, `
Get-EngineFact, `
Test-EngineFact, `
Set-EngineState, `
Add-EnginePublishCompletion, `
Get-EngineState
if ($filePlugin.Count -gt 0) {
return Resolve-FileReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir
}
Write-Error "Configure a DotNetReleaseVersion (projectFiles), NpmReleaseVersion (packageJsonPath), or FileReleaseVersion (versionFilePath) plugin in scriptSettings.json."
exit 1
}
Export-ModuleMember -Function Get-CsprojPropertyValue, Get-CsprojVersions, Get-VersionFileSemver, Resolve-RelativePaths, Resolve-DotNetReleaseVersion, Resolve-NpmReleaseVersion, Resolve-FileReleaseVersion, Resolve-ReleaseVersion

View File

@ -32,9 +32,4 @@ function Import-EngineModules {
Import-Module $modulePath -Force
}
$extensionImport = Join-Path $modulesDir 'Extensions' 'Import-ExtensionModules.ps1'
if (Test-Path -LiteralPath $extensionImport -PathType Leaf) {
. $extensionImport
}
}

View File

@ -22,7 +22,7 @@ function Test-IsEngineRuntimeModuleName {
[string]$ModuleName
)
# Host engine runtime under modules/ (and optional modules/Extensions/) — never dual-homed under plugins/.
# Engine runtime under modules/ only — never dual-homed under plugins/.
$engineNames = [System.Collections.Generic.HashSet[string]]::new(
[string[]]@(
'ChangelogSupport',
@ -34,7 +34,11 @@ function Test-IsEngineRuntimeModuleName {
'EngineContext',
'PluginSupport',
'ReleaseSupport',
'TestSupport'
'TestSupport',
'DeployConfig',
'EngineContextSupport',
'OrchestratorSupport',
'PluginPathSupport'
),
[System.StringComparer]::OrdinalIgnoreCase
)
@ -42,34 +46,6 @@ function Test-IsEngineRuntimeModuleName {
return $engineNames.Contains($ModuleName)
}
function Get-PluginDependencyGroupDirectories {
param(
[Parameter(Mandatory = $true)]
[string]$PluginsRoot
)
if (-not (Test-Path -LiteralPath $PluginsRoot -PathType Container)) {
return @()
}
# Prefer Shared (helpers), then stock host groups; any other plugins/{Group}/ is discovered.
$preferred = @('Shared', 'Platform', 'DotNet', 'Npm')
$dirs = [System.Collections.Generic.List[string]]::new()
foreach ($name in $preferred) {
$path = Join-Path $PluginsRoot $name
if (Test-Path -LiteralPath $path -PathType Container) {
$dirs.Add($path)
}
}
Get-ChildItem -LiteralPath $PluginsRoot -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notin $preferred } |
Sort-Object Name |
ForEach-Object { $dirs.Add($_.FullName) }
return @($dirs)
}
function Import-PluginDependency {
param(
[Parameter(Mandatory = $true)]
@ -90,16 +66,16 @@ function Import-PluginDependency {
$candidatePaths = [System.Collections.Generic.List[string]]::new()
if (Test-IsEngineRuntimeModuleName -ModuleName $ModuleName) {
# Engine runtime: modules/ only (no plugins/ fallback). Optional Extensions/ for layered hosts.
# Engine runtime: modules/ only (no plugins/ fallback).
$candidatePaths.Add((Join-Path $modulesDir "$ModuleName.psm1"))
$candidatePaths.Add((Join-Path $engineModuleDir "$ModuleName.psm1"))
$extensionsDir = Join-Path $modulesDir 'Extensions'
$candidatePaths.Add((Join-Path $extensionsDir "$ModuleName.psm1"))
}
else {
# Plugin helpers: plugins/{Group}/ only (no modules/ legacy shadow). Groups are discovered.
foreach ($groupDir in Get-PluginDependencyGroupDirectories -PluginsRoot $pluginsRoot) {
$candidatePaths.Add((Join-Path $groupDir "$ModuleName.psm1"))
# Plugin helpers: plugins/ only (no modules/ legacy shadow).
foreach ($group in @('Shared', 'Platform', 'DotNet', 'Npm', 'Helm', 'Docker', 'Podman')) {
$candidatePaths.Add((Join-Path (Join-Path $pluginsRoot $group) "$ModuleName.psm1"))
}
}
@ -368,8 +344,8 @@ function Get-RegistryCredentialsFromRuntime {
.DESCRIPTION
Looks up the environment variable named by SecretName. The value must be
Base64(UTF8('username:password')). Used by registry login and image-pull
secret creation never pass the password itself as a parameter.
Base64(UTF8('username:password')). Used by Docker/Podman/Helm registry login
and image-pull secret creation never pass the password itself as a parameter.
.PARAMETER SecretName
Logical secret name (environment variable name), not a password or token.
@ -695,7 +671,10 @@ function Invoke-ConfiguredPlugin {
[psobject]$SharedSettings,
[Parameter(Mandatory = $true)]
[string]$EngineDirectory
[string]$EngineDirectory,
[Parameter(Mandatory = $false)]
[bool]$ContinueOnError = $false
)
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -EngineDirectory $EngineDirectory -WriteLogs:$true)) {
@ -737,16 +716,12 @@ function Invoke-ConfiguredPlugin {
$pluginModulePath = Resolve-PluginModulePath -Plugin $Plugin -EngineDirectory $EngineDirectory
Write-Log -Level "STEP" -Message "Running plugin '$($Plugin.name)'..."
# Sink plugin success-stream output to the host so it cannot pollute this
# function's return value. Otherwise `return $false` after CLI stdout becomes
# @("helm-line…", $false), which is truthy under `if (-not $result)` and
# causes RELEASE COMPLETE / exit 0 after a failed plugin.
try {
$moduleInfo = Import-Module $pluginModulePath -Force -PassThru -ErrorAction Stop
$invokeCommand = Get-Command -Name "Invoke-Plugin" -Module $moduleInfo.Name -ErrorAction Stop
$pluginSettings = New-PluginInvocationSettings -Plugin $Plugin -SharedSettings $SharedSettings
& $invokeCommand -Settings $pluginSettings | ForEach-Object { Write-Host $_ }
& $invokeCommand -Settings $pluginSettings
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
return $true
}

View File

@ -24,101 +24,13 @@ if (-not (Get-Command Get-PluginStageLabel -ErrorAction SilentlyContinue) -or -n
}
}
if (-not (Get-Command Resolve-RelativePaths -ErrorAction SilentlyContinue) -or -not (Get-Command Set-EngineState -ErrorAction SilentlyContinue)) {
if (-not (Get-Command Resolve-ReleaseVersion -ErrorAction SilentlyContinue)) {
$engineContextModulePath = Join-Path $PSScriptRoot "EngineContext.psm1"
if (Test-Path $engineContextModulePath -PathType Leaf) {
Import-Module $engineContextModulePath -Force
}
}
function Get-EnabledVersionPlugins {
param(
[Parameter(Mandatory = $true)]
[object[]]$Plugins,
[Parameter(Mandatory = $true)]
[string]$ScriptDir
)
$versionPlugins = @()
foreach ($plugin in $Plugins) {
if ($null -eq $plugin -or [string]::IsNullOrWhiteSpace([string]$plugin.name)) {
continue
}
if (-not $plugin.enabled) {
continue
}
$metadata = Get-PluginMetadataObject -Plugin $plugin -EngineDirectory $ScriptDir
if ($null -eq $metadata) {
continue
}
if (($metadata.PSObject.Properties.Name -contains 'providesVersion') -and [bool]$metadata.providesVersion) {
$versionPlugins += $plugin
}
}
return @($versionPlugins)
}
function Invoke-VersionPlugin {
param(
[Parameter(Mandatory = $true)]
$Plugin,
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$EngineDirectory
)
$modulePath = Resolve-PluginModulePath -Plugin $Plugin -EngineDirectory $EngineDirectory
if (-not (Test-Path $modulePath -PathType Leaf)) {
throw "Version plugin '$($Plugin.name)' module not found at: $modulePath"
}
$moduleInfo = Import-Module $modulePath -Force -PassThru -ErrorAction Stop
$invokeCommand = Get-Command -Name "Invoke-Plugin" -Module $moduleInfo.Name -ErrorAction Stop
$pluginSettings = New-PluginInvocationSettings -Plugin $Plugin -SharedSettings $Context
& $invokeCommand -Settings $pluginSettings
}
function Resolve-EngineContextVersion {
param(
[Parameter(Mandatory = $true)]
[object[]]$Plugins,
[Parameter(Mandatory = $true)]
[psobject]$Context,
[Parameter(Mandatory = $true)]
[string]$ScriptDir
)
$versionPlugins = @(Get-EnabledVersionPlugins -Plugins $Plugins -ScriptDir $ScriptDir)
if ($versionPlugins.Count -eq 0) {
throw "Configure exactly one enabled release version plugin (declares providesVersion in Get-PluginMetadata), e.g. DotNetReleaseVersion (projectFiles), NpmReleaseVersion (packageJsonPath), or FileReleaseVersion (versionFilePath)."
}
if ($versionPlugins.Count -gt 1) {
$names = ($versionPlugins | ForEach-Object { [string]$_.name }) -join ', '
throw "Configure only one enabled release version plugin. Found: $names."
}
$versionPlugin = $versionPlugins[0]
Invoke-VersionPlugin -Plugin $versionPlugin -Context $Context -EngineDirectory $ScriptDir
$version = Get-EngineState -Context $Context -Name 'version'
if ($null -eq $version -or [string]::IsNullOrWhiteSpace([string]$version)) {
throw "Version plugin '$($versionPlugin.name)' did not set a version on the engine context."
}
return [string]$versionPlugin.name
}
function Assert-WorkingTreeClean {
$gitStatus = Get-GitStatusShort
if (-not [string]::IsNullOrWhiteSpace([string]$gitStatus)) {
@ -136,22 +48,23 @@ function Assert-WorkingTreeClean {
function Initialize-ReleaseStageContext {
param(
[Parameter(Mandatory = $true)]
[object[]]$RemainingPlugins,
[Parameter(Mandatory = $true)]
[psobject]$SharedSettings,
[Parameter(Mandatory = $true)]
[string]$ArtifactsDirectory
[string]$ArtifactsDirectory,
[Parameter(Mandatory = $true)]
[string]$Version
)
if (-not $SharedSettings.PSObject.Properties['releaseDir'] -or [string]::IsNullOrWhiteSpace([string]$SharedSettings.releaseDir)) {
if (Get-Command Set-EngineState -ErrorAction SilentlyContinue) {
Set-EngineState -Context $SharedSettings -Name 'releaseDir' -Value $ArtifactsDirectory
}
else {
$SharedSettings | Add-Member -NotePropertyName releaseDir -NotePropertyValue $ArtifactsDirectory -Force
}
}
}
function New-EngineContext {
param(
@ -167,10 +80,15 @@ function New-EngineContext {
[Parameter(Mandatory = $false)]
[psobject]$Settings,
[Parameter(Mandatory = $false)]
[psobject]$ExtensionData
[switch]$DryRun,
[ValidateSet('single', 'ha')]
[string]$DeployMode = 'ha'
)
$resolvedVersion = Resolve-ReleaseVersion -Plugins $Plugins -ScriptDir $ScriptDir
$version = $resolvedVersion.version
$versionSource = $resolvedVersion.source
$releaseRelative = '..\..\..\releases'
$artifactsDirectory = [System.IO.Path]::GetFullPath((Join-Path $ScriptDir $releaseRelative))
@ -198,58 +116,48 @@ function New-EngineContext {
$releaseBranches = @('main')
}
$isNonReleaseBranch = -not ($releaseBranches -contains $currentBranch)
$isReleaseBranch = $releaseBranches -contains $currentBranch
$isNonReleaseBranch = -not $isReleaseBranch
Assert-WorkingTreeClean
$context = [pscustomobject]@{
$tag = "v$version"
Write-Log -Level "INFO" -Message " Release tag default from ${versionSource}: $tag (ReleasePublishGuard may replace from git when publish is allowed)."
$dryRun = $false
if ($DryRun) {
$dryRun = $true
}
else {
$dryRun = Get-EngineDryRun -Settings $Settings
}
Write-Log -Level "INFO" -Message " Dry run (remote mutations only): $dryRun"
$orchestrator = Get-MaksitOrchestrator
if ($orchestrator) {
Write-Log -Level "INFO" -Message " Orchestrator: $orchestrator (plugin profile filtering active)"
}
else {
Write-Log -Level "INFO" -Message " Orchestrator: not set (dev mode — all plugins eligible; engine probe still selects docker vs podman)"
}
return [pscustomobject]@{
scriptDir = $ScriptDir
srcDir = $SrcDir
utilsDir = $SrcDir
currentBranch = $currentBranch
version = $version
tag = $tag
artifactsDirectory = $artifactsDirectory
isReleaseBranch = $isReleaseBranch
isNonReleaseBranch = $isNonReleaseBranch
releaseBranches = $releaseBranches
publishCompleted = $false
skipPublishPlugins = $false
facts = [ordered]@{}
dryRun = $dryRun
deployMode = $DeployMode
orchestrator = $orchestrator
}
$versionSource = Resolve-EngineContextVersion -Plugins $Plugins -Context $context -ScriptDir $ScriptDir
$version = [string](Get-EngineState -Context $context -Name 'version' -Required)
$tag = "v$version"
Set-EngineState -Context $context -Name 'tag' -Value $tag
Write-Log -Level "INFO" -Message " Release tag default from ${versionSource}: $tag (ReleasePublishGuard may replace from git when publish is allowed)."
$dryRunPlugins = @(
$Plugins |
Where-Object {
$_.enabled -and
($_.PSObject.Properties.Name -contains 'dryRun') -and
$null -ne $_.dryRun -and
[bool]$_.dryRun -and
(Test-PluginMutatesRemote -Plugin $_ -EngineDirectory $ScriptDir)
} |
ForEach-Object { [string]$_.name }
)
if ($dryRunPlugins.Count -gt 0) {
Write-Log -Level "INFO" -Message " Plugin dryRun (validate only): $($dryRunPlugins -join ', ')"
}
$expandContext = Get-Command Expand-ExtensionEngineContext -ErrorAction SilentlyContinue
if ($expandContext) {
$expandParams = @{
Context = $context
ScriptDir = $ScriptDir
Settings = $Settings
}
if ($PSBoundParameters.ContainsKey('ExtensionData')) {
$expandParams['ExtensionData'] = $ExtensionData
}
return & $expandContext @expandParams
}
return $context
}
function Get-PreferredReleaseBranch {

View File

@ -10,13 +10,6 @@ if (-not (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
}
}
if (-not (Get-Command Initialize-EngineFactsBag -ErrorAction SilentlyContinue)) {
$engineContextModulePath = Join-Path $PSScriptRoot "EngineContext.psm1"
if (Test-Path $engineContextModulePath -PathType Leaf) {
Import-Module $engineContextModulePath -Force
}
}
function New-EngineContext {
param(
[Parameter(Mandatory = $true)]
@ -28,32 +21,22 @@ function New-EngineContext {
[Parameter(Mandatory = $false)]
[psobject]$Settings,
[Parameter(Mandatory = $false)]
[psobject]$ExtensionData
[ValidateSet('single', 'ha')]
[string]$DeployMode = 'ha'
)
$context = [pscustomobject]@{
$badgesDir = $null
if ($Settings -and $Settings.PSObject.Properties['paths'] -and $Settings.paths.badgesDir) {
$badgesDir = [System.IO.Path]::GetFullPath((Join-Path $ScriptDir ([string]$Settings.paths.badgesDir)))
}
return [pscustomobject]@{
scriptDir = $ScriptDir
srcDir = $SrcDir
utilsDir = $SrcDir
facts = [ordered]@{}
badgesDir = $badgesDir
deployMode = $DeployMode
}
$expandContext = Get-Command Expand-ExtensionEngineContext -ErrorAction SilentlyContinue
if ($expandContext) {
$expandParams = @{
Context = $context
ScriptDir = $ScriptDir
Settings = $Settings
}
if ($PSBoundParameters.ContainsKey('ExtensionData')) {
$expandParams['ExtensionData'] = $ExtensionData
}
return & $expandContext @expandParams
}
return $context
}
Export-ModuleMember -Function New-EngineContext

View File

@ -1,16 +1,6 @@
#requires -Version 7.0
#requires -PSEdition Core
<#
Runs native CLIs (dotnet, git, helm, ) and keeps $LASTEXITCODE intact.
By default throws on non-zero exit so callers cannot forget to check.
Pass -ThrowOnError:$false when you need the exit code / output yourself
(e.g. TestRunner Success objects, logging full container build output first).
Test hooks: Set-ExternalCommandTestHandler / Set-ExternalCommandAvailability
let Pester stub CLIs without touching PATH.
#>
$script:ExternalCommandTestHandler = $null
$script:ExternalCommandAvailability = @{}
@ -50,10 +40,7 @@ function Invoke-ExternalCommand {
[string]$InputObject,
[switch]$MergeErrorOutput,
# Default true: fail fast. Soft callers (tests, nested loggers) pass $false.
[bool]$ThrowOnError = $true
[switch]$MergeErrorOutput
)
$previousLocation = $null
@ -64,7 +51,6 @@ function Invoke-ExternalCommand {
try {
$effectiveWorkingDirectory = (Get-Location).Path
$output = @()
if ($null -ne $script:ExternalCommandTestHandler) {
$handlerResult = & $script:ExternalCommandTestHandler `
@ -76,46 +62,31 @@ function Invoke-ExternalCommand {
$global:LASTEXITCODE = [int]$handlerResult.ExitCode
if ($null -eq $handlerResult.Output) {
$output = @()
return @()
}
elseif ($handlerResult.Output -is [System.Collections.IEnumerable] -and -not ($handlerResult.Output -is [string])) {
$output = @($handlerResult.Output)
if ($handlerResult.Output -is [System.Collections.IEnumerable] -and -not ($handlerResult.Output -is [string])) {
return @($handlerResult.Output)
}
else {
$output = @([string]$handlerResult.Output)
return @([string]$handlerResult.Output)
}
}
else {
if ($script:ExternalCommandAvailability.ContainsKey($Name) -and -not $script:ExternalCommandAvailability[$Name]) {
throw "External command '$Name' is marked unavailable."
}
if (-not [string]::IsNullOrWhiteSpace($InputObject)) {
$raw = $InputObject | & $Name @ArgumentList 2>&1
$output = $InputObject | & $Name @ArgumentList 2>&1
}
elseif ($MergeErrorOutput) {
$raw = & $Name @ArgumentList 2>&1
$output = & $Name @ArgumentList 2>&1
}
else {
$raw = & $Name @ArgumentList
$output = & $Name @ArgumentList
}
$output = @($raw)
}
$exitCode = [int]$global:LASTEXITCODE
if ($ThrowOnError -and $exitCode -ne 0) {
$preview = ($output | ForEach-Object {
if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.ToString() } else { [string]$_ }
} | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 8) -join ' '
if ([string]::IsNullOrWhiteSpace($preview)) {
throw "External command '$Name' failed with exit code $exitCode."
}
throw "External command '$Name' failed with exit code $exitCode. $preview"
}
return $output
return @($output)
}
finally {
if ($null -ne $previousLocation) {

View File

@ -51,33 +51,7 @@ function Invoke-GitInternal {
[string]$ErrorMessage = "Git command failed"
)
if (-not (Get-Command Invoke-ExternalCommand -ErrorAction SilentlyContinue)) {
$srcDir = Split-Path $PSScriptRoot -Parent
$externalModule = Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1'
if (Test-Path -LiteralPath $externalModule -PathType Leaf) {
Import-Module $externalModule -Global
}
elseif (Test-Path -LiteralPath (Join-Path $srcDir 'modules' 'ExternalCommandSupport.psm1') -PathType Leaf) {
Import-Module (Join-Path $srcDir 'modules' 'ExternalCommandSupport.psm1') -Global
}
}
if ($CaptureOutput) {
if (Get-Command Invoke-ExternalCommand -ErrorAction SilentlyContinue) {
$output = Invoke-ExternalCommand -Name git -ArgumentList $Arguments -MergeErrorOutput -ThrowOnError:$false
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Error "$ErrorMessage (exit code: $exitCode)"
exit 1
}
if ($null -eq $output) {
return ""
}
return ($output -join "`n").Trim()
}
$output = & git @Arguments 2>&1
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
@ -92,13 +66,7 @@ function Invoke-GitInternal {
return ($output -join "`n").Trim()
}
if (Get-Command Invoke-ExternalCommand -ErrorAction SilentlyContinue) {
Invoke-ExternalCommand -Name git -ArgumentList $Arguments -MergeErrorOutput -ThrowOnError:$false | Out-Null
}
else {
& git @Arguments
}
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Error "$ErrorMessage (exit code: $exitCode)"

View File

@ -7,11 +7,25 @@ function Get-ScriptSettings {
[Parameter(Mandatory = $true)]
[string]$ScriptDir,
[ValidateSet('single', 'ha')]
[string]$Mode,
[Parameter(Mandatory = $false)]
[string]$SettingsFileName = 'scriptSettings.json'
)
$settingsPath = Join-Path $ScriptDir $SettingsFileName
$settingsPath = if ($PSBoundParameters.ContainsKey('Mode')) {
$modePath = Join-Path $ScriptDir "scriptSettings.$Mode.json"
if (Test-Path -LiteralPath $modePath -PathType Leaf) {
$modePath
}
else {
Join-Path $ScriptDir $SettingsFileName
}
}
else {
Join-Path $ScriptDir $SettingsFileName
}
if (-not (Test-Path -LiteralPath $settingsPath -PathType Leaf)) {
throw "Settings file not found: $settingsPath"
@ -20,6 +34,92 @@ function Get-ScriptSettings {
return Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json
}
function Get-HelmSelfDeployPluginFromSettings {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[psobject]$Settings
)
if (-not ($Settings.PSObject.Properties.Name -contains 'plugins') -or -not $Settings.plugins) {
return $null
}
foreach ($plugin in @($Settings.plugins)) {
if ($plugin.name -eq 'HelmSelfDeploy') {
return $plugin
}
}
return $null
}
function Get-DeployModeFromSettings {
<#
.SYNOPSIS
Resolves cluster deploy profile from HelmSelfDeploy plugin settings or an explicit CLI override.
.DESCRIPTION
CI/CD pipelines omit -Mode and read deployMode on the HelmSelfDeploy plugin.
Manual installs use Invoke-ReleasePackage-Single.bat / Invoke-ReleasePackage-HA.bat.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[psobject]$Settings,
[ValidateSet('single', 'ha')]
[string]$ModeOverride
)
if ($PSBoundParameters.ContainsKey('ModeOverride')) {
return $ModeOverride
}
$helmSelfDeploy = Get-HelmSelfDeployPluginFromSettings -Settings $Settings
if ($null -ne $helmSelfDeploy -and $helmSelfDeploy.PSObject.Properties.Name -contains 'deployMode') {
$mode = [string]$helmSelfDeploy.deployMode
if (-not [string]::IsNullOrWhiteSpace($mode)) {
$mode = $mode.Trim().ToLowerInvariant()
if ($mode -in @('single', 'ha')) {
return $mode
}
throw "HelmSelfDeploy.deployMode must be 'single' or 'ha' (got '$mode')."
}
}
return 'ha'
}
function Resolve-DeployValuesFilePath {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$DeploySettingsDir,
[Parameter(Mandatory = $true)]
[string]$ValuesFile,
[Parameter(Mandatory = $true)]
[ValidateSet('single', 'ha')]
[string]$DeployMode
)
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($ValuesFile)
$extension = [System.IO.Path]::GetExtension($ValuesFile)
if ([string]::IsNullOrEmpty($extension)) {
$extension = '.yaml'
}
$valuesPath = Join-Path $DeploySettingsDir "$baseName.$DeployMode$extension"
if (Test-Path -LiteralPath $valuesPath -PathType Leaf) {
return $valuesPath
}
throw "Deploy values file not found: '$valuesPath'. HelmSelfDeploy expects values.single.yaml or values.ha.yaml beside scriptSettings.json."
}
function Assert-Command {
[CmdletBinding()]
param(
@ -32,4 +132,4 @@ function Assert-Command {
}
}
Export-ModuleMember -Function Get-ScriptSettings, Assert-Command
Export-ModuleMember -Function Get-ScriptSettings, Get-DeployModeFromSettings, Resolve-DeployValuesFilePath, Assert-Command

View File

@ -19,8 +19,10 @@ function Import-ExternalCommandSupportInternal {
return
}
$srcDir = Split-Path $PSScriptRoot -Parent
$candidates = @(
(Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1')
(Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1'),
(Join-Path $srcDir 'plugins' 'Shared' 'ExternalCommandSupport.psm1')
)
foreach ($modulePath in $candidates) {
if (Test-Path -LiteralPath $modulePath -PathType Leaf) {
@ -173,10 +175,10 @@ function Invoke-TestsWithCoverage {
Import-ExternalCommandSupportInternal
if ($Silent) {
$null = Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs -MergeErrorOutput -ThrowOnError:$false
$null = Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs -MergeErrorOutput
}
else {
Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs -ThrowOnError:$false | Out-Default
Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs | Out-Default
}
$testExitCode = $LASTEXITCODE
@ -354,10 +356,10 @@ function Invoke-NpmJestTestsWithCoverage {
$npmArgs = @('run', $TestScript, '--', '--coverage', '--coverageReporters=json-summary', '--coverageReporters=text')
Import-ExternalCommandSupportInternal
if ($Silent) {
$null = Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs -MergeErrorOutput -ThrowOnError:$false
$null = Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs -MergeErrorOutput
}
else {
Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs -ThrowOnError:$false | Out-Default
Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs | Out-Default
}
if ($LASTEXITCODE -ne 0) {

View File

@ -26,7 +26,6 @@ function Invoke-Plugin {
)
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineFact"
$pluginSettings = $Settings
$sharedSettings = $Settings.context
@ -34,18 +33,13 @@ function Invoke-Plugin {
$version = $sharedSettings.version
$archiveInputs = @()
$fromFact = Get-EngineFact -Context $sharedSettings -Namespace 'release' -Name 'archiveInputs' -LegacyProperty @('releaseArchiveInputs')
if ($null -ne $fromFact) {
$archiveInputs = @($fromFact)
}
else {
$packageFile = Get-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'packageFile' -LegacyProperty @('packageFile')
if ($null -ne $packageFile) {
$archiveInputs = @($packageFile.FullName)
$symbolsPackageFile = Get-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'symbolsPackageFile' -LegacyProperty @('symbolsPackageFile')
if ($null -ne $symbolsPackageFile) {
$archiveInputs += $symbolsPackageFile.FullName
if ($sharedSettings.PSObject.Properties['releaseArchiveInputs'] -and $sharedSettings.releaseArchiveInputs) {
$archiveInputs = @($sharedSettings.releaseArchiveInputs)
}
elseif ($sharedSettings.PSObject.Properties['packageFile'] -and $sharedSettings.packageFile) {
$archiveInputs = @($sharedSettings.packageFile.FullName)
if ($sharedSettings.PSObject.Properties['symbolsPackageFile'] -and $sharedSettings.symbolsPackageFile) {
$archiveInputs += $sharedSettings.symbolsPackageFile.FullName
}
}
@ -85,18 +79,16 @@ function Invoke-Plugin {
Write-Log -Level "OK" -Message " Release archive ready: $zipPath"
$releaseAssetPaths = @($zipPath)
$packageFile = Get-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'packageFile' -LegacyProperty @('packageFile')
if ($null -ne $packageFile) {
$releaseAssetPaths += $packageFile.FullName
if ($sharedSettings.PSObject.Properties['packageFile'] -and $sharedSettings.packageFile) {
$releaseAssetPaths += $sharedSettings.packageFile.FullName
}
$symbolsPackageFile = Get-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'symbolsPackageFile' -LegacyProperty @('symbolsPackageFile')
if ($null -ne $symbolsPackageFile) {
$releaseAssetPaths += $symbolsPackageFile.FullName
if ($sharedSettings.PSObject.Properties['symbolsPackageFile'] -and $sharedSettings.symbolsPackageFile) {
$releaseAssetPaths += $sharedSettings.symbolsPackageFile.FullName
}
Set-EngineState -Context $sharedSettings -Name 'releaseDir' -Value $artifactsDirectory
Set-EngineFact -Context $sharedSettings -Namespace 'release' -Name 'archivePath' -Value $zipPath -Overwrite Replace -LegacyProperty 'releaseArchivePath'
Set-EngineFact -Context $sharedSettings -Namespace 'release' -Name 'assetPaths' -Value $releaseAssetPaths -Overwrite Replace -LegacyProperty 'releaseAssetPaths'
$sharedSettings | Add-Member -NotePropertyName releaseDir -NotePropertyValue $artifactsDirectory -Force
$sharedSettings | Add-Member -NotePropertyName releaseArchivePath -NotePropertyValue $zipPath -Force
$sharedSettings | Add-Member -NotePropertyName releaseAssetPaths -NotePropertyValue $releaseAssetPaths -Force
}
Export-ModuleMember -Function Invoke-Plugin

View File

@ -76,8 +76,7 @@ function Invoke-Plugin {
}
Write-Log -Level "OK" -Message " NuGet push completed."
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Add-EnginePublishCompletion"
Add-EnginePublishCompletion -Context $sharedSettings -Publisher 'DotNetNuGet'
$sharedSettings | Add-Member -NotePropertyName publishCompleted -NotePropertyValue $true -Force
}
function Get-PluginMetadata {

View File

@ -30,8 +30,7 @@ function Invoke-Plugin {
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineFact"
Import-PluginDependency -ModuleName "DotNetArtifactSupport" -RequiredCommand "Resolve-DotNetPackageArtifacts"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Resolve-RelativePaths"
$sharedSettings = $Settings.context
$scriptDir = $sharedSettings.scriptDir
@ -49,12 +48,12 @@ function Invoke-Plugin {
if ($Settings.PSObject.Properties['artifactsDir'] -and -not [string]::IsNullOrWhiteSpace([string]$Settings.artifactsDir)) {
$artifactsDirectory = [System.IO.Path]::GetFullPath((Join-Path $scriptDir ([string]$Settings.artifactsDir)))
Set-EngineState -Context $sharedSettings -Name 'artifactsDirectory' -Value $artifactsDirectory
Set-EngineState -Context $sharedSettings -Name 'releaseDir' -Value $artifactsDirectory
}
else {
$artifactsDirectory = $sharedSettings.artifactsDirectory
}
$packageProjectPath = $null
$releaseArchiveInputs = @()
Assert-Command dotnet
@ -80,19 +79,50 @@ function Invoke-Plugin {
throw "dotnet pack failed for $packageProjectPath."
}
$resolved = Resolve-DotNetPackageArtifacts -ArtifactsDirectory $outputDir -Version $version
# dotnet pack can leave older packages in the artifacts directory.
# Pick the newest file matching the current version rather than assuming a clean folder.
$packageFile = $null
$newestNupkgWrite = [datetime]::MinValue
$nupkgCandidates = Get-ChildItem -Path $outputDir -Filter "*.nupkg"
foreach ($candidate in $nupkgCandidates) {
if (($candidate.Name -like "*$version*.nupkg") -and ($candidate.Name -notlike "*.symbols.nupkg") -and ($candidate.Name -notlike "*.snupkg")) {
if ($candidate.LastWriteTime -gt $newestNupkgWrite) {
$newestNupkgWrite = $candidate.LastWriteTime
$packageFile = $candidate
}
}
}
Write-Log -Level "OK" -Message " Package ready: $($resolved.PackageFile.FullName)"
if ($resolved.SymbolsPackageFile) {
Write-Log -Level "OK" -Message " Symbols package ready: $($resolved.SymbolsPackageFile.FullName)"
if (-not $packageFile) {
throw "Could not locate generated NuGet package for version $version in: $outputDir"
}
Write-Log -Level "OK" -Message " Package ready: $($packageFile.FullName)"
$releaseArchiveInputs = @($packageFile.FullName)
$symbolsPackageFile = $null
$newestSnupkgWrite = [datetime]::MinValue
$snupkgCandidates = Get-ChildItem -Path $outputDir -Filter "*.snupkg"
foreach ($candidate in $snupkgCandidates) {
if ($candidate.Name -like "*$version*.snupkg") {
if ($candidate.LastWriteTime -gt $newestSnupkgWrite) {
$newestSnupkgWrite = $candidate.LastWriteTime
$symbolsPackageFile = $candidate
}
}
}
if ($symbolsPackageFile) {
Write-Log -Level "OK" -Message " Symbols package ready: $($symbolsPackageFile.FullName)"
$releaseArchiveInputs += $symbolsPackageFile.FullName
}
else {
Write-Log -Level "WARN" -Message " Symbols package (.snupkg) not found for version $version."
}
Set-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'packageFile' -Value $resolved.PackageFile -Overwrite Replace -LegacyProperty 'packageFile'
Set-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'symbolsPackageFile' -Value $resolved.SymbolsPackageFile -Overwrite Replace -LegacyProperty 'symbolsPackageFile'
Set-EngineFact -Context $sharedSettings -Namespace 'release' -Name 'archiveInputs' -Value $resolved.ReleaseArchiveInputs -Overwrite Replace -LegacyProperty 'releaseArchiveInputs'
$sharedSettings | Add-Member -NotePropertyName packageFile -NotePropertyValue $packageFile -Force
$sharedSettings | Add-Member -NotePropertyName symbolsPackageFile -NotePropertyValue $symbolsPackageFile -Force
$sharedSettings | Add-Member -NotePropertyName releaseArchiveInputs -NotePropertyValue $releaseArchiveInputs -Force
}
Export-ModuleMember -Function Invoke-Plugin

View File

@ -27,21 +27,18 @@ function Invoke-Plugin {
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineFact"
$sharedSettings = $Settings.context
$projectFiles = Get-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'projectFiles' -LegacyProperty @('projectFiles')
$projectFiles = $sharedSettings.projectFiles
$artifactsDirectory = $sharedSettings.artifactsDirectory
$publishProjectPath = $null
Assert-Command dotnet
if ($null -eq $projectFiles -or @($projectFiles).Count -eq 0) {
if (-not $sharedSettings.PSObject.Properties['projectFiles'] -or $projectFiles.Count -eq 0) {
throw "DotNetPublish plugin requires project files in the shared context."
}
$projectFiles = @($projectFiles)
if (!(Test-Path $artifactsDirectory)) {
New-Item -ItemType Directory -Path $artifactsDirectory | Out-Null
}
@ -67,9 +64,9 @@ function Invoke-Plugin {
Write-Log -Level "OK" -Message " Published artifact ready: $publishDir"
Set-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'packageFile' -Value $null -Overwrite Replace -LegacyProperty 'packageFile'
Set-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'symbolsPackageFile' -Value $null -Overwrite Replace -LegacyProperty 'symbolsPackageFile'
Set-EngineFact -Context $sharedSettings -Namespace 'release' -Name 'archiveInputs' -Value @($publishDir) -Overwrite Replace -LegacyProperty 'releaseArchiveInputs'
$sharedSettings | Add-Member -NotePropertyName packageFile -NotePropertyValue $null -Force
$sharedSettings | Add-Member -NotePropertyName symbolsPackageFile -NotePropertyValue $null -Force
$sharedSettings | Add-Member -NotePropertyName releaseArchiveInputs -NotePropertyValue @($publishDir) -Force
}
Export-ModuleMember -Function Invoke-Plugin

View File

@ -3,13 +3,11 @@
<#
.SYNOPSIS
Loads release version from an SDK-style .csproj into shared context.
Loads release version into shared context.
.DESCRIPTION
Dedicated version-loading plugin. Reads <Version> from the first configured
projectFiles entry and writes it (plus the resolved projectFiles) to the
shared runtime context. Declares providesVersion = $true so the engine can
discover it as the single release version source.
Dedicated version-loading plugin. It reads .csproj version via
EngineContext helpers and writes Version into the shared runtime context.
#>
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
@ -20,56 +18,6 @@ if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
}
}
function Get-CsprojPropertyValueInternal {
param(
[Parameter(Mandatory = $true)]
[xml]$Csproj,
[Parameter(Mandatory = $true)]
[string]$PropertyName
)
# SDK-style .csproj files can have multiple PropertyGroup nodes.
# Use the first group that defines the requested property.
$propNode = $Csproj.Project.PropertyGroup |
Where-Object { $_.$PropertyName } |
Select-Object -First 1
if ($propNode) {
return $propNode.$PropertyName
}
return $null
}
function Get-CsprojVersionInternal {
param(
[Parameter(Mandatory = $true)]
[string]$ProjectPath
)
if (-not (Test-Path $ProjectPath -PathType Leaf)) {
throw "DotNetReleaseVersion: project file not found at '$ProjectPath'."
}
if ([System.IO.Path]::GetExtension($ProjectPath) -ne ".csproj") {
throw "DotNetReleaseVersion: configured project file is not a .csproj file: '$ProjectPath'."
}
[xml]$csproj = Get-Content $ProjectPath
$version = Get-CsprojPropertyValueInternal -Csproj $csproj -PropertyName "Version"
if ([string]::IsNullOrWhiteSpace([string]$version)) {
throw "DotNetReleaseVersion: <Version> not found in '$ProjectPath'."
}
return [string]$version
}
function Get-PluginMetadata {
return [pscustomobject]@{ providesVersion = $true }
}
function Invoke-Plugin {
param(
[Parameter(Mandatory = $true)]
@ -77,20 +25,17 @@ function Invoke-Plugin {
)
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineState"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Resolve-DotNetReleaseVersion"
$shared = $Settings.context
$resolved = Resolve-DotNetReleaseVersion -Plugins @($Settings) -ScriptDir $shared.scriptDir
$projectFiles = @(Resolve-RelativePaths -Value $Settings.projectFiles -BasePath $shared.scriptDir)
if ($projectFiles.Count -eq 0) {
throw "DotNetReleaseVersion plugin requires 'projectFiles' (first .csproj with <Version>) in scriptSettings.json."
$shared | Add-Member -NotePropertyName version -NotePropertyValue $resolved.version -Force
$shared | Add-Member -NotePropertyName projectFiles -NotePropertyValue $projectFiles -Force
Write-Log -Level "OK" -Message " Release version loaded by DotNetReleaseVersion plugin: $($shared.version)"
}
Write-Log -Level "INFO" -Message "Reading version from SDK-style project file (projectFiles)..."
$version = Get-CsprojVersionInternal -ProjectPath $projectFiles[0]
Export-ModuleMember -Function Invoke-Plugin
Set-EngineState -Context $shared -Name 'version' -Value $version
Set-EngineFact -Context $shared -Namespace 'dotnet' -Name 'projectFiles' -Value $projectFiles -Overwrite Replace -LegacyProperty 'projectFiles'
Write-Log -Level "OK" -Message " Release version loaded by DotNetReleaseVersion plugin: $version"
}
Export-ModuleMember -Function Invoke-Plugin, Get-PluginMetadata

View File

@ -58,12 +58,20 @@ function Invoke-Plugin {
throw "Tests failed. $($testResult.Error)"
}
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineFact"
Import-PluginDependency -ModuleName "TestRunner" -RequiredCommand "Publish-CoverageMetricsToSharedContext"
Set-EngineFact -Context $sharedSettings -Namespace 'npm' -Name 'workspaceRoot' -Value $workspaceRoot -Overwrite Replace -LegacyProperty 'npmWorkspaceRoot'
Publish-CoverageMetricsToSharedContext -SharedSettings $sharedSettings -TestResult $testResult
$sharedSettings | Add-Member -NotePropertyName npmWorkspaceRoot -NotePropertyValue $workspaceRoot -Force
$sharedSettings | Add-Member -NotePropertyName testResult -NotePropertyValue $testResult -Force
$sharedSettings | Add-Member -NotePropertyName qualityLineCoverage -NotePropertyValue $testResult.LineRate -Force
$sharedSettings | Add-Member -NotePropertyName coverageLineRate -NotePropertyValue $testResult.LineRate -Force
$sharedSettings | Add-Member -NotePropertyName coverageBranchRate -NotePropertyValue $testResult.BranchRate -Force
$sharedSettings | Add-Member -NotePropertyName coverageMethodRate -NotePropertyValue $testResult.MethodRate -Force
$sharedSettings | Add-Member -NotePropertyName coverageTotalMethods -NotePropertyValue $testResult.TotalMethods -Force
$sharedSettings | Add-Member -NotePropertyName coverageCoveredMethods -NotePropertyValue $testResult.CoveredMethods -Force
if (($testResult.PSObject.Properties.Name -contains 'ResultsDirectory') -and $testResult.ResultsDirectory) {
$sharedSettings | Add-Member -NotePropertyName testResultsDirectory -NotePropertyValue $testResult.ResultsDirectory -Force
}
if (($testResult.PSObject.Properties.Name -contains 'CoverageSummaryFile') -and $testResult.CoverageSummaryFile) {
Set-EngineFact -Context $sharedSettings -Namespace 'test' -Name 'coverageSummaryFile' -Value $testResult.CoverageSummaryFile -Overwrite Replace -LegacyProperty 'coverageSummaryFile'
$sharedSettings | Add-Member -NotePropertyName coverageSummaryFile -NotePropertyValue $testResult.CoverageSummaryFile -Force
}
Write-Log -Level "OK" -Message " All tests passed!"

View File

@ -27,7 +27,7 @@ function Invoke-Plugin {
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineFact"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Resolve-RelativePaths"
$pluginSettings = $Settings
$shared = $Settings.context
@ -49,7 +49,6 @@ function Invoke-Plugin {
$artifactsDirectory = $null
if ($pluginSettings.PSObject.Properties['artifactsDir'] -and -not [string]::IsNullOrWhiteSpace([string]$pluginSettings.artifactsDir)) {
$artifactsDirectory = [System.IO.Path]::GetFullPath((Join-Path $shared.scriptDir ([string]$pluginSettings.artifactsDir)))
Set-EngineState -Context $shared -Name 'artifactsDirectory' -Value $artifactsDirectory
}
elseif ($shared.PSObject.Properties['artifactsDirectory'] -and -not [string]::IsNullOrWhiteSpace([string]$shared.artifactsDirectory)) {
$artifactsDirectory = [string]$shared.artifactsDirectory
@ -126,11 +125,10 @@ function Invoke-Plugin {
Pop-Location
}
Set-EngineState -Context $shared -Name 'releaseDir' -Value $artifactsDirectory
Set-EngineFact -Context $shared -Namespace 'release' -Name 'assetPaths' -Value $releaseAssetPaths -Overwrite Replace -LegacyProperty 'releaseAssetPaths'
$shared | Add-Member -NotePropertyName releaseDir -NotePropertyValue $artifactsDirectory -Force
$shared | Add-Member -NotePropertyName releaseAssetPaths -NotePropertyValue $releaseAssetPaths -Force
if ($releaseAssetPaths.Count -gt 0) {
$packageItem = Get-Item -LiteralPath $releaseAssetPaths[0]
Set-EngineFact -Context $shared -Namespace 'npm' -Name 'packageFile' -Value $packageItem -Overwrite Replace -LegacyProperty 'packageFile'
$shared | Add-Member -NotePropertyName packageFile -NotePropertyValue (Get-Item -LiteralPath $releaseAssetPaths[0]) -Force
}
Write-Log -Level "OK" -Message " npm pack completed ($($releaseAssetPaths.Count) tarball(s))."

View File

@ -127,8 +127,7 @@ registry=$registry
}
Write-Log -Level "OK" -Message " npm publish completed."
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Add-EnginePublishCompletion"
Add-EnginePublishCompletion -Context $shared -Publisher 'NpmPublish'
$shared | Add-Member -NotePropertyName publishCompleted -NotePropertyValue $true -Force
}
finally {
if (Test-Path $tempNpmRcPath -PathType Leaf) {

View File

@ -57,10 +57,6 @@ function Set-PackageJsonVersionInternal {
($json | ConvertTo-Json -Depth 100) + [Environment]::NewLine | Set-Content -Path $PackageJsonPath -Encoding UTF8 -NoNewline
}
function Get-PluginMetadata {
return [pscustomobject]@{ providesVersion = $true }
}
function Invoke-Plugin {
param(
[Parameter(Mandatory = $true)]
@ -68,7 +64,7 @@ function Invoke-Plugin {
)
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineState"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Resolve-RelativePaths"
$pluginSettings = $Settings
$shared = $Settings.context
@ -94,11 +90,10 @@ function Invoke-Plugin {
Write-Log -Level "OK" -Message " Synchronized workspace package versions to $version."
}
$npmWorkspaceRoot = Split-Path -Parent $packageJsonPath
Set-EngineState -Context $shared -Name 'version' -Value $version
Set-EngineFact -Context $shared -Namespace 'npm' -Name 'workspaceRoot' -Value $npmWorkspaceRoot -Overwrite Replace -LegacyProperty 'npmWorkspaceRoot'
Set-EngineFact -Context $shared -Namespace 'npm' -Name 'packageJsonPath' -Value $packageJsonPath -Overwrite Replace -LegacyProperty 'npmPackageJsonPath'
$shared | Add-Member -NotePropertyName version -NotePropertyValue $version -Force
$shared | Add-Member -NotePropertyName npmWorkspaceRoot -NotePropertyValue (Split-Path -Parent $packageJsonPath) -Force
$shared | Add-Member -NotePropertyName npmPackageJsonPath -NotePropertyValue $packageJsonPath -Force
Write-Log -Level "OK" -Message " Release version loaded by NpmReleaseVersion plugin: $version"
}
Export-ModuleMember -Function Invoke-Plugin, Get-PluginMetadata
Export-ModuleMember -Function Invoke-Plugin

View File

@ -6,10 +6,8 @@
Loads release version from a repo-root VERSION file into shared context.
.DESCRIPTION
Reads a single-line semver from the configured versionFilePath (default
repo-root VERSION). Useful for repositories without .csproj or package.json
version metadata. Declares providesVersion = $true so the engine can
discover it as the single release version source.
Reads a single-line semver from the configured versionFilePath (default repo-root VERSION).
Used by container-only repos without .csproj or package.json.
#>
if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
@ -20,33 +18,6 @@ if (-not (Get-Command Import-PluginDependency -ErrorAction SilentlyContinue)) {
}
}
function Get-VersionFileSemverInternal {
param(
[Parameter(Mandatory = $true)]
[string]$VersionFilePath
)
if (-not (Test-Path $VersionFilePath -PathType Leaf)) {
throw "FileReleaseVersion: VERSION file not found at '$VersionFilePath'."
}
$version = (Get-Content -Path $VersionFilePath -Raw -Encoding UTF8).Trim()
if ([string]::IsNullOrWhiteSpace($version)) {
throw "FileReleaseVersion: VERSION file is empty at '$VersionFilePath'."
}
$version = $version -replace '^[vV]', ''
if ($version -notmatch '^\d+\.\d+\.\d+') {
throw "FileReleaseVersion: version '$version' in '$VersionFilePath' is not a valid semver."
}
return $version
}
function Get-PluginMetadata {
return [pscustomobject]@{ providesVersion = $true }
}
function Invoke-Plugin {
param(
[Parameter(Mandatory = $true)]
@ -54,28 +25,17 @@ function Invoke-Plugin {
)
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Set-EngineState"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Resolve-FileReleaseVersion"
$shared = $Settings.context
$versionFileSetting = if ($Settings.versionFilePath) {
$Settings.versionFilePath
$resolved = Resolve-FileReleaseVersion -Plugins @($Settings) -ScriptDir $shared.scriptDir
$versionFilePaths = @(Resolve-RelativePaths -Value $Settings.versionFilePath -BasePath $shared.scriptDir)
$shared | Add-Member -NotePropertyName version -NotePropertyValue $resolved.version -Force
if ($versionFilePaths.Count -gt 0) {
$shared | Add-Member -NotePropertyName versionFilePath -NotePropertyValue $versionFilePaths[0] -Force
}
else {
'..\..\..\VERSION'
Write-Log -Level "OK" -Message " Release version loaded by FileReleaseVersion plugin: $($shared.version)"
}
$versionFilePaths = @(Resolve-RelativePaths -Value $versionFileSetting -BasePath $shared.scriptDir)
if ($versionFilePaths.Count -eq 0) {
throw "FileReleaseVersion plugin requires 'versionFilePath' (repo-root VERSION file) in scriptSettings.json."
}
$versionFilePath = $versionFilePaths[0]
Write-Log -Level "INFO" -Message "Reading version from VERSION file (versionFilePath)..."
$version = Get-VersionFileSemverInternal -VersionFilePath $versionFilePath
Set-EngineState -Context $shared -Name 'version' -Value $version
Set-EngineFact -Context $shared -Namespace 'release' -Name 'versionFilePath' -Value $versionFilePath -Overwrite Replace -LegacyProperty 'versionFilePath'
Write-Log -Level "OK" -Message " Release version loaded by FileReleaseVersion plugin: $version"
}
Export-ModuleMember -Function Invoke-Plugin, Get-PluginMetadata
Export-ModuleMember -Function Invoke-Plugin

View File

@ -6,7 +6,7 @@
Pester test plugin for the RepoUtils test engine.
.DESCRIPTION
Runs the RepoUtils Pester suite and publishes normalized coverage metrics on the
Runs the community Pester suite and publishes normalized coverage metrics on the
shared engine context for QualityGate.
#>

View File

@ -10,9 +10,9 @@
shared engine context (same object passed to every plugin as .context).
Line coverage for threshold checks is resolved in order (first present wins):
- facts test.coverageLineRate (via Get-EngineFact)
- legacy qualityLineCoverage / coverageLineRate flat properties
- testResult.LineRate (object from a test plugin)
- qualityLineCoverage (generic; any plugin may set this)
- coverageLineRate (conventional flat metric)
- testResult.LineRate (object from a test plugin; property name is conventional)
Configure coverageThreshold > 0 to require one of those inputs. With coverageThreshold 0
and scanVulnerabilities false, the plugin is a no-op.
@ -64,20 +64,6 @@ function Get-LineCoveragePercentFromSharedContext {
$Shared
)
if (Get-Command Get-EngineFact -ErrorAction SilentlyContinue) {
$fromFact = Get-EngineFact -Context $Shared -Namespace 'test' -Name 'coverageLineRate' -LegacyProperty @('qualityLineCoverage', 'coverageLineRate')
if ($null -ne $fromFact -and -not [string]::IsNullOrWhiteSpace([string]$fromFact)) {
return [double]$fromFact
}
$testResult = Get-EngineFact -Context $Shared -Namespace 'test' -Name 'testResult' -LegacyProperty @('testResult')
if ($null -ne $testResult -and ($testResult.PSObject.Properties.Name -contains 'LineRate')) {
return [double]$testResult.LineRate
}
return $null
}
foreach ($prop in @('qualityLineCoverage', 'coverageLineRate')) {
if ($Shared.PSObject.Properties.Name -contains $prop) {
$raw = $Shared.$prop
@ -106,7 +92,7 @@ function Invoke-Plugin {
Import-PluginDependency -ModuleName "Logging" -RequiredCommand "Write-Log"
Import-PluginDependency -ModuleName "ScriptConfig" -RequiredCommand "Assert-Command"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Get-EngineFact"
Import-PluginDependency -ModuleName "EngineContext" -RequiredCommand "Resolve-RelativePaths"
$pluginSettings = $Settings
$sharedSettings = $Settings.context
@ -121,9 +107,11 @@ function Invoke-Plugin {
if ($pluginSettings.PSObject.Properties['projectFiles'] -and $null -ne $pluginSettings.projectFiles) {
$projectFiles = @(Resolve-RelativePaths -Value $pluginSettings.projectFiles -BasePath $scriptDir)
}
elseif ($sharedSettings.PSObject.Properties['projectFiles'] -and $null -ne $sharedSettings.projectFiles) {
$projectFiles = @($sharedSettings.projectFiles)
}
else {
$fromContext = Get-EngineFact -Context $sharedSettings -Namespace 'dotnet' -Name 'projectFiles' -LegacyProperty @('projectFiles')
$projectFiles = if ($null -ne $fromContext) { @($fromContext) } else { @() }
$projectFiles = @()
}
$coverageThreshold = 0
@ -141,7 +129,7 @@ function Invoke-Plugin {
if ($needCoverageCheck) {
$lineRate = Get-LineCoveragePercentFromSharedContext -Shared $sharedSettings
if ($null -eq $lineRate) {
throw "coverageThreshold is $coverageThreshold but shared context has no line coverage. Set test.coverageLineRate (or legacy qualityLineCoverage / coverageLineRate / testResult.LineRate) from an earlier plugin."
throw "coverageThreshold is $coverageThreshold but shared context has no line coverage. Set one of: qualityLineCoverage, coverageLineRate, or testResult.LineRate (from an earlier plugin)."
}
Write-Log -Level "STEP" -Message "Checking line coverage threshold against shared context..."

View File

@ -50,15 +50,10 @@ function Invoke-NotMetInternal {
[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 "ERROR" -Message "ReleasePublishGuard: $Reason"
exit 1
}
Write-Log -Level "WARN" -Message " Publish suppressed: $Reason"
@ -72,7 +67,6 @@ function Invoke-Plugin {
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"
@ -88,10 +82,15 @@ function Invoke-Plugin {
throw "ReleasePublishGuard: whenRequirementsNotMet must be 'skip' or 'fail'."
}
Set-EngineState -Context $shared -Name 'skipPublishPlugins' -Value $false
$shared | Add-Member -NotePropertyName skipPublishPlugins -NotePropertyValue $false -Force
Write-Log -Level "STEP" -Message "Release publish guard..."
if ($shared.PSObject.Properties.Name -contains 'dryRun' -and [bool]$shared.dryRun) {
Write-Log -Level "INFO" -Message " Dry run: publish guard relaxed; publish plugins will validate only."
return
}
$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."
@ -144,7 +143,7 @@ function Invoke-Plugin {
return
}
Set-EngineState -Context $shared -Name 'tag' -Value $tag
$shared | Add-Member -NotePropertyName tag -NotePropertyValue $tag -Force
}
$ensureRemote = $true

View File

@ -0,0 +1,363 @@
#requires -Version 7.0
#requires -PSEdition Core
<#
.SYNOPSIS
Refreshes a local maksit-repoutils copy from GitHub.
.DESCRIPTION
This script clones the configured repository into a temporary directory,
refreshes the parent directory of this script, preserves existing
scriptSettings.json files in subfolders, and copies the cloned source
contents into that parent directory.
All configuration is stored in scriptSettings.json.
.EXAMPLE
pwsh -File .\Update-RepoUtils.ps1
.NOTES
CONFIGURATION (scriptSettings.json):
- dryRun: If true, logs the planned update without modifying files
- repository.url: Git repository to clone
- repository.sourceSubdirectory: Folder copied into the target directory
- repository.preserveFileName: Existing file name to preserve in subfolders
- repository.cloneDepth: Depth used for git clone
- repository.skippedRelativeDirectories: Relative directories to exclude from phase-two refresh
#>
[CmdletBinding()]
param(
[switch]$ContinueAfterSelfUpdate,
[string]$TargetDirectoryOverride,
[string]$ClonedSourceDirectoryOverride,
[string]$TemporaryRootOverride
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$srcDir = Split-Path (Split-Path $scriptDir -Parent) -Parent
$modulesDir = Join-Path $srcDir 'modules'
# Refresh the src directory that contains modules, engines, plugins, and tools.
$targetDirectory = if ([string]::IsNullOrWhiteSpace($TargetDirectoryOverride)) {
$srcDir
}
else {
[System.IO.Path]::GetFullPath($TargetDirectoryOverride)
}
$currentScriptPath = [System.IO.Path]::GetFullPath($MyInvocation.MyCommand.Path)
$selfUpdateDirectory = [System.IO.Path]::Combine('tools', 'Update-RepoUtils')
function ConvertTo-NormalizedRelativePath {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$normalizedPath = $Path.Replace('/', [System.IO.Path]::DirectorySeparatorChar).Replace('\', [System.IO.Path]::DirectorySeparatorChar)
return $normalizedPath.TrimStart('.', [System.IO.Path]::DirectorySeparatorChar).TrimEnd([System.IO.Path]::DirectorySeparatorChar)
}
function Test-IsInRelativeDirectory {
param(
[Parameter(Mandatory = $true)]
[string]$RelativePath,
[Parameter(Mandatory = $true)]
[string[]]$Directories
)
$normalizedRelativePath = ConvertTo-NormalizedRelativePath -Path $RelativePath
foreach ($directory in $Directories) {
$normalizedDirectory = ConvertTo-NormalizedRelativePath -Path $directory
if ([string]::IsNullOrWhiteSpace($normalizedDirectory)) {
continue
}
if (
$normalizedRelativePath.Equals($normalizedDirectory, [System.StringComparison]::OrdinalIgnoreCase) -or
$normalizedRelativePath.StartsWith($normalizedDirectory + [System.IO.Path]::DirectorySeparatorChar, [System.StringComparison]::OrdinalIgnoreCase)
) {
return $true
}
}
return $false
}
#region Import Modules
$scriptConfigModulePath = Join-Path $modulesDir "ScriptConfig.psm1"
if (-not (Test-Path $scriptConfigModulePath)) {
Write-Error "ScriptConfig module not found at: $scriptConfigModulePath"
exit 1
}
$loggingModulePath = Join-Path $modulesDir "Logging.psm1"
if (-not (Test-Path $loggingModulePath)) {
Write-Error "Logging module not found at: $loggingModulePath"
exit 1
}
Import-Module $scriptConfigModulePath -Force
Import-Module $loggingModulePath -Force
#endregion
#region Load Settings
$settings = Get-ScriptSettings -ScriptDir $scriptDir
#endregion
#region Configuration
$repositoryUrl = $settings.repository.url
$dryRun = if ($null -ne $settings.dryRun) { [bool]$settings.dryRun } else { $false }
$sourceSubdirectory = if ($settings.repository.sourceSubdirectory) { $settings.repository.sourceSubdirectory } else { 'src' }
$preserveFileName = if ($settings.repository.preserveFileName) { $settings.repository.preserveFileName } else { 'scriptSettings.json' }
$cloneDepth = if ($settings.repository.cloneDepth) { [int]$settings.repository.cloneDepth } else { 1 }
[string[]]$skippedRelativeDirectories = if ($settings.repository.skippedRelativeDirectories) {
@(
$settings.repository.skippedRelativeDirectories |
ForEach-Object {
ConvertTo-NormalizedRelativePath -Path ([string]$_)
}
)
}
else {
@(
[System.IO.Path]::Combine('engines', 'release', 'custom'),
[System.IO.Path]::Combine('engines', 'test', 'custom')
)
}
#endregion
#region Validate CLI Dependencies
Assert-Command git
Assert-Command pwsh
if ([string]::IsNullOrWhiteSpace($repositoryUrl)) {
Write-Error "repository.url is required in scriptSettings.json."
exit 1
}
#endregion
#region Main
Write-Log -Level "INFO" -Message "========================================"
Write-Log -Level "INFO" -Message "Update RepoUtils Script"
Write-Log -Level "INFO" -Message "========================================"
Write-Log -Level "INFO" -Message "Target directory: $targetDirectory"
Write-Log -Level "INFO" -Message "Dry run: $dryRun"
$ownsTemporaryRoot = [string]::IsNullOrWhiteSpace($TemporaryRootOverride)
$temporaryRoot = if ($ownsTemporaryRoot) {
Join-Path ([System.IO.Path]::GetTempPath()) ("maksit-repoutils-update-" + [System.Guid]::NewGuid().ToString('N'))
}
else {
[System.IO.Path]::GetFullPath($TemporaryRootOverride)
}
try {
$clonedSourceDirectory = if ([string]::IsNullOrWhiteSpace($ClonedSourceDirectoryOverride)) {
Write-LogStep "Cloning latest repository snapshot..."
& git clone --depth $cloneDepth $repositoryUrl $temporaryRoot
if ($LASTEXITCODE -ne 0) {
throw "git clone failed with exit code $LASTEXITCODE."
}
Write-Log -Level "OK" -Message "Repository cloned"
Join-Path $temporaryRoot $sourceSubdirectory
}
else {
[System.IO.Path]::GetFullPath($ClonedSourceDirectoryOverride)
}
if (-not (Test-Path -Path $clonedSourceDirectory -PathType Container)) {
throw "The cloned repository does not contain the expected source directory: $clonedSourceDirectory"
}
if (-not $ContinueAfterSelfUpdate) {
if ($dryRun) {
Write-LogStep "Dry run self-update summary"
Write-Log -Level "INFO" -Message "Would refresh shared modules and $selfUpdateDirectory before relaunching the updater"
}
else {
Write-LogStep "Refreshing updater files..."
$selfUpdateFiles = Get-ChildItem -Path $clonedSourceDirectory -Recurse -Force -File |
Where-Object {
$relativePath = [System.IO.Path]::GetRelativePath($clonedSourceDirectory, $_.FullName)
$isRootFile = -not $relativePath.Contains([System.IO.Path]::DirectorySeparatorChar)
$isUpdaterFile = $relativePath.StartsWith($selfUpdateDirectory + [System.IO.Path]::DirectorySeparatorChar, [System.StringComparison]::OrdinalIgnoreCase)
$_.Name -ne $preserveFileName -and
($isRootFile -or $isUpdaterFile)
}
foreach ($sourceFile in $selfUpdateFiles) {
$relativePath = [System.IO.Path]::GetRelativePath($clonedSourceDirectory, $sourceFile.FullName)
$destinationPath = Join-Path $targetDirectory $relativePath
$destinationDirectory = Split-Path -Parent $destinationPath
if (-not (Test-Path -Path $destinationDirectory -PathType Container)) {
New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
}
Copy-Item -Path $sourceFile.FullName -Destination $destinationPath -Force
}
Write-Log -Level "OK" -Message "Updater files refreshed"
}
if ($dryRun) {
Write-LogStep "Dry run bootstrap completed"
Write-Log -Level "INFO" -Message "Continuing with phase two in the current process because no files were changed"
}
else {
Write-LogStep "Relaunching the updated updater..."
& pwsh -File $currentScriptPath `
-ContinueAfterSelfUpdate `
-TargetDirectoryOverride $targetDirectory `
-ClonedSourceDirectoryOverride $clonedSourceDirectory `
-TemporaryRootOverride $temporaryRoot
if ($LASTEXITCODE -ne 0) {
throw "Relaunched updater failed with exit code $LASTEXITCODE."
}
Write-Log -Level "OK" -Message "Bootstrap phase completed"
return
}
}
$preservedFiles = @()
[string[]]$updatePhaseSkippedDirectories = @($skippedRelativeDirectories) + $selfUpdateDirectory
$existingPreservedFiles = Get-ChildItem -Path $targetDirectory -Recurse -File -Filter $preserveFileName -ErrorAction SilentlyContinue
if ($existingPreservedFiles) {
foreach ($file in $existingPreservedFiles) {
$relativePath = [System.IO.Path]::GetRelativePath($targetDirectory, $file.FullName)
$backupPath = Join-Path $temporaryRoot ("preserved-" + ($relativePath -replace '[\\/:*?""<>|]', '_'))
$preservedFiles += [pscustomobject]@{
RelativePath = $relativePath
BackupPath = $backupPath
}
if (-not $dryRun) {
$backupDirectory = Split-Path -Parent $backupPath
if (-not (Test-Path -Path $backupDirectory -PathType Container)) {
New-Item -ItemType Directory -Path $backupDirectory -Force | Out-Null
}
Copy-Item -Path $file.FullName -Destination $backupPath -Force
}
}
Write-Log -Level "OK" -Message "Preserved $($preservedFiles.Count) existing $preserveFileName file(s)"
}
else {
Write-Log -Level "WARN" -Message "No existing $preserveFileName files found in subfolders"
}
if ($dryRun) {
Write-LogStep "Dry run summary"
Write-Log -Level "INFO" -Message "Would remove all files under target except preserved $preserveFileName files"
Write-Log -Level "INFO" -Message "Would skip phase-two refresh for: $($updatePhaseSkippedDirectories -join ', ')"
Write-Log -Level "INFO" -Message "Would copy refreshed files from: $clonedSourceDirectory"
if ($preservedFiles.Count -gt 0) {
$preservedList = ($preservedFiles | ForEach-Object { $_.RelativePath }) -join ", "
Write-Log -Level "INFO" -Message "Would restore preserved files: $preservedList"
}
Write-Log -Level "OK" -Message "Dry run completed. No files were modified."
return
}
Write-LogStep "Cleaning target directory..."
$filesToRemove = Get-ChildItem -Path $targetDirectory -Recurse -Force -File |
Where-Object {
$relativePath = [System.IO.Path]::GetRelativePath($targetDirectory, $_.FullName)
$isInSkippedDirectory = Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $updatePhaseSkippedDirectories
$_.Name -ne $preserveFileName -and
-not $isInSkippedDirectory
}
foreach ($file in $filesToRemove) {
Remove-Item -Path $file.FullName -Force
}
$directoriesToRemove = Get-ChildItem -Path $targetDirectory -Recurse -Force -Directory |
Sort-Object { $_.FullName.Length } -Descending
foreach ($directory in $directoriesToRemove) {
$relativePath = [System.IO.Path]::GetRelativePath($targetDirectory, $directory.FullName)
if (Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $updatePhaseSkippedDirectories) {
continue
}
$remainingItems = Get-ChildItem -Path $directory.FullName -Force -ErrorAction SilentlyContinue
if (-not $remainingItems) {
Remove-Item -Path $directory.FullName -Force
}
}
Write-Log -Level "OK" -Message "Target directory cleaned"
Write-LogStep "Copying refreshed source files..."
$sourceFilesToCopy = Get-ChildItem -Path $clonedSourceDirectory -Recurse -Force -File |
Where-Object {
$relativePath = [System.IO.Path]::GetRelativePath($clonedSourceDirectory, $_.FullName)
$isInSkippedDirectory = Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $updatePhaseSkippedDirectories
-not $isInSkippedDirectory
}
foreach ($sourceFile in $sourceFilesToCopy) {
$relativePath = [System.IO.Path]::GetRelativePath($clonedSourceDirectory, $sourceFile.FullName)
$destinationPath = Join-Path $targetDirectory $relativePath
$destinationDirectory = Split-Path -Parent $destinationPath
if (-not (Test-Path -Path $destinationDirectory -PathType Container)) {
New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
}
Copy-Item -Path $sourceFile.FullName -Destination $destinationPath -Force
}
foreach ($skippedDirectory in $updatePhaseSkippedDirectories) {
$skippedSourcePath = Join-Path $clonedSourceDirectory $skippedDirectory
if (Test-Path -Path $skippedSourcePath) {
Write-Log -Level "INFO" -Message "Skipped refresh for $skippedDirectory"
}
}
Write-Log -Level "OK" -Message "Source files copied"
if ($preservedFiles.Count -gt 0) {
foreach ($preservedFile in $preservedFiles) {
if (-not (Test-Path -Path $preservedFile.BackupPath -PathType Leaf)) {
continue
}
$restorePath = Join-Path $targetDirectory $preservedFile.RelativePath
$restoreDirectory = Split-Path -Parent $restorePath
if (-not (Test-Path -Path $restoreDirectory -PathType Container)) {
New-Item -ItemType Directory -Path $restoreDirectory -Force | Out-Null
}
Copy-Item -Path $preservedFile.BackupPath -Destination $restorePath -Force
}
Write-Log -Level "OK" -Message "$preserveFileName files restored"
}
Write-Log -Level "OK" -Message "========================================"
Write-Log -Level "OK" -Message "Update completed successfully!"
Write-Log -Level "OK" -Message "========================================"
}
finally {
if ($ownsTemporaryRoot -and (Test-Path -Path $temporaryRoot)) {
Remove-Item -Path $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
#endregion

View File

@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft-07/schema",
"title": "Update RepoUtils Script Settings",
"description": "Configuration for the Update-RepoUtils utility.",
"dryRun": true,
"repository": {
"url": "https://github.com/MAKS-IT-COM/maksit-repoutils.git",
"sourceSubdirectory": "src",
"preserveFileName": "scriptSettings.json",
"cloneDepth": 1,
"skippedRelativeDirectories": [
"engines/release/custom",
"engines/test/custom"
]
}
}