(build): sync nuget deps with repoutils
This commit is contained in:
parent
b5f31faf9e
commit
434b569b3a
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.6.9] - 2026-08-14
|
||||
|
||||
### Changed
|
||||
- Updated NuGet dependencies: Microsoft.Extensions.* and System.Threading.RateLimiting 10.0.9 → 10.0.11, Microsoft.AspNetCore.* 2.3.11 → 2.3.12, Microsoft.IdentityModel.Tokens and System.IdentityModel.Tokens.Jwt 8.19.1 → 8.22.0, System.Linq.Dynamic.Core 1.7.2 → 1.7.3, Microsoft.NET.Test.Sdk 18.7.0 → 18.9.0
|
||||
- Synced RepoUtils: fail-fast external commands (`ThrowOnError`), exact plugin success checks (avoid polluted PowerShell return arrays), discovered plugin group loading, and removed `Update-RepoUtils` (local-copy sync only)
|
||||
|
||||
## [1.6.8] - 2026-06-27
|
||||
|
||||
### Changed
|
||||
|
||||
@ -14,8 +14,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.3.11" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.3.12" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>MaksIT.Core</PackageId>
|
||||
<Version>1.6.8</Version>
|
||||
<Version>1.6.9</Version>
|
||||
<Authors>Maksym Sadovnychyy</Authors>
|
||||
<Company>MAKS-IT</Company>
|
||||
<Product>MaksIT.Core</Product>
|
||||
@ -42,16 +42,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.7.2" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="10.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.12" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" 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="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.22.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.22.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.7.3" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="10.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
pwsh -NoProfile -ExecutionPolicy Bypass -File "%~dp0tools\Update-RepoUtils\Update-RepoUtils.ps1" %*
|
||||
pause
|
||||
@ -80,8 +80,9 @@ else {
|
||||
}
|
||||
}
|
||||
|
||||
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $sharedPluginSettings -EngineDirectory $PSScriptRoot -ContinueOnError:$false
|
||||
if (-not $pluginSucceeded) {
|
||||
$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) {
|
||||
$releaseHadPluginFailures = $true
|
||||
break
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
Plugin-driven test and coverage engine entry script.
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$srcDir = (Resolve-Path (Join-Path $scriptDir '..\..')).Path
|
||||
|
||||
@ -29,8 +31,9 @@ if ($configuredPlugins.Count -eq 0) {
|
||||
$testHadPluginFailures = $false
|
||||
|
||||
foreach ($plugin in $configuredPlugins) {
|
||||
$pluginSucceeded = Invoke-ConfiguredPlugin -Plugin $plugin -SharedSettings $engineContext -EngineDirectory $scriptDir -ContinueOnError:$false
|
||||
if (-not $pluginSucceeded) {
|
||||
$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) {
|
||||
$testHadPluginFailures = $true
|
||||
break
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ function Test-IsEngineRuntimeModuleName {
|
||||
[string]$ModuleName
|
||||
)
|
||||
|
||||
# Engine runtime under modules/ only — never dual-homed under plugins/.
|
||||
# Host engine runtime under modules/ (and optional modules/Extensions/) — never dual-homed under plugins/.
|
||||
$engineNames = [System.Collections.Generic.HashSet[string]]::new(
|
||||
[string[]]@(
|
||||
'ChangelogSupport',
|
||||
@ -34,11 +34,7 @@ function Test-IsEngineRuntimeModuleName {
|
||||
'EngineContext',
|
||||
'PluginSupport',
|
||||
'ReleaseSupport',
|
||||
'TestSupport',
|
||||
'DeployConfig',
|
||||
'EngineContextSupport',
|
||||
'OrchestratorSupport',
|
||||
'PluginPathSupport'
|
||||
'TestSupport'
|
||||
),
|
||||
[System.StringComparer]::OrdinalIgnoreCase
|
||||
)
|
||||
@ -46,6 +42,34 @@ 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)]
|
||||
@ -66,16 +90,16 @@ function Import-PluginDependency {
|
||||
$candidatePaths = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
if (Test-IsEngineRuntimeModuleName -ModuleName $ModuleName) {
|
||||
# Engine runtime: modules/ only (no plugins/ fallback).
|
||||
# Engine runtime: modules/ only (no plugins/ fallback). Optional Extensions/ for layered hosts.
|
||||
$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/ 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"))
|
||||
# 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"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,8 +368,8 @@ function Get-RegistryCredentialsFromRuntime {
|
||||
|
||||
.DESCRIPTION
|
||||
Looks up the environment variable named by SecretName. The value must be
|
||||
Base64(UTF8('username:password')). Used by Docker/Podman/Helm registry login
|
||||
and image-pull secret creation — never pass the password itself as a parameter.
|
||||
Base64(UTF8('username:password')). Used by 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.
|
||||
@ -671,10 +695,7 @@ function Invoke-ConfiguredPlugin {
|
||||
[psobject]$SharedSettings,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EngineDirectory,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[bool]$ContinueOnError = $false
|
||||
[string]$EngineDirectory
|
||||
)
|
||||
|
||||
if (-not (Test-PluginRunnable -Plugin $Plugin -SharedSettings $SharedSettings -EngineDirectory $EngineDirectory -WriteLogs:$true)) {
|
||||
@ -716,12 +737,16 @@ 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
|
||||
& $invokeCommand -Settings $pluginSettings | ForEach-Object { Write-Host $_ }
|
||||
Write-Log -Level "OK" -Message " Plugin '$($Plugin.name)' completed."
|
||||
return $true
|
||||
}
|
||||
|
||||
@ -1,6 +1,16 @@
|
||||
#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 = @{}
|
||||
|
||||
@ -40,7 +50,10 @@ function Invoke-ExternalCommand {
|
||||
|
||||
[string]$InputObject,
|
||||
|
||||
[switch]$MergeErrorOutput
|
||||
[switch]$MergeErrorOutput,
|
||||
|
||||
# Default true: fail fast. Soft callers (tests, nested loggers) pass $false.
|
||||
[bool]$ThrowOnError = $true
|
||||
)
|
||||
|
||||
$previousLocation = $null
|
||||
@ -51,6 +64,7 @@ function Invoke-ExternalCommand {
|
||||
|
||||
try {
|
||||
$effectiveWorkingDirectory = (Get-Location).Path
|
||||
$output = @()
|
||||
|
||||
if ($null -ne $script:ExternalCommandTestHandler) {
|
||||
$handlerResult = & $script:ExternalCommandTestHandler `
|
||||
@ -62,31 +76,46 @@ function Invoke-ExternalCommand {
|
||||
|
||||
$global:LASTEXITCODE = [int]$handlerResult.ExitCode
|
||||
if ($null -eq $handlerResult.Output) {
|
||||
return @()
|
||||
$output = @()
|
||||
}
|
||||
|
||||
if ($handlerResult.Output -is [System.Collections.IEnumerable] -and -not ($handlerResult.Output -is [string])) {
|
||||
return @($handlerResult.Output)
|
||||
elseif ($handlerResult.Output -is [System.Collections.IEnumerable] -and -not ($handlerResult.Output -is [string])) {
|
||||
$output = @($handlerResult.Output)
|
||||
}
|
||||
|
||||
return @([string]$handlerResult.Output)
|
||||
else {
|
||||
$output = @([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)) {
|
||||
$output = $InputObject | & $Name @ArgumentList 2>&1
|
||||
$raw = $InputObject | & $Name @ArgumentList 2>&1
|
||||
}
|
||||
elseif ($MergeErrorOutput) {
|
||||
$output = & $Name @ArgumentList 2>&1
|
||||
$raw = & $Name @ArgumentList 2>&1
|
||||
}
|
||||
else {
|
||||
$output = & $Name @ArgumentList
|
||||
$raw = & $Name @ArgumentList
|
||||
}
|
||||
|
||||
return @($output)
|
||||
$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
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $previousLocation) {
|
||||
|
||||
@ -53,21 +53,18 @@ function Invoke-GitInternal {
|
||||
|
||||
if (-not (Get-Command Invoke-ExternalCommand -ErrorAction SilentlyContinue)) {
|
||||
$srcDir = Split-Path $PSScriptRoot -Parent
|
||||
$externalCandidates = @(
|
||||
(Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1'),
|
||||
(Join-Path $srcDir 'plugins' 'Shared' 'ExternalCommandSupport.psm1')
|
||||
)
|
||||
foreach ($externalModule in $externalCandidates) {
|
||||
$externalModule = Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1'
|
||||
if (Test-Path -LiteralPath $externalModule -PathType Leaf) {
|
||||
Import-Module $externalModule -Global
|
||||
break
|
||||
}
|
||||
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
|
||||
$output = Invoke-ExternalCommand -Name git -ArgumentList $Arguments -MergeErrorOutput -ThrowOnError:$false
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0) {
|
||||
Write-Error "$ErrorMessage (exit code: $exitCode)"
|
||||
@ -96,7 +93,7 @@ function Invoke-GitInternal {
|
||||
}
|
||||
|
||||
if (Get-Command Invoke-ExternalCommand -ErrorAction SilentlyContinue) {
|
||||
Invoke-ExternalCommand -Name git -ArgumentList $Arguments -MergeErrorOutput | Out-Null
|
||||
Invoke-ExternalCommand -Name git -ArgumentList $Arguments -MergeErrorOutput -ThrowOnError:$false | Out-Null
|
||||
}
|
||||
else {
|
||||
& git @Arguments
|
||||
|
||||
@ -19,10 +19,8 @@ function Import-ExternalCommandSupportInternal {
|
||||
return
|
||||
}
|
||||
|
||||
$srcDir = Split-Path $PSScriptRoot -Parent
|
||||
$candidates = @(
|
||||
(Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1'),
|
||||
(Join-Path $srcDir 'plugins' 'Shared' 'ExternalCommandSupport.psm1')
|
||||
(Join-Path $PSScriptRoot 'ExternalCommandSupport.psm1')
|
||||
)
|
||||
foreach ($modulePath in $candidates) {
|
||||
if (Test-Path -LiteralPath $modulePath -PathType Leaf) {
|
||||
@ -175,10 +173,10 @@ function Invoke-TestsWithCoverage {
|
||||
|
||||
Import-ExternalCommandSupportInternal
|
||||
if ($Silent) {
|
||||
$null = Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs -MergeErrorOutput
|
||||
$null = Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs -MergeErrorOutput -ThrowOnError:$false
|
||||
}
|
||||
else {
|
||||
Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs | Out-Default
|
||||
Invoke-ExternalCommand -Name dotnet -ArgumentList $dotnetArgs -ThrowOnError:$false | Out-Default
|
||||
}
|
||||
|
||||
$testExitCode = $LASTEXITCODE
|
||||
@ -356,10 +354,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
|
||||
$null = Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs -MergeErrorOutput -ThrowOnError:$false
|
||||
}
|
||||
else {
|
||||
Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs | Out-Default
|
||||
Invoke-ExternalCommand -Name npm -ArgumentList $npmArgs -ThrowOnError:$false | Out-Default
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
Pester test plugin for the RepoUtils test engine.
|
||||
|
||||
.DESCRIPTION
|
||||
Runs the community Pester suite and publishes normalized coverage metrics on the
|
||||
Runs the RepoUtils Pester suite and publishes normalized coverage metrics on the
|
||||
shared engine context for QualityGate.
|
||||
#>
|
||||
|
||||
|
||||
@ -1,439 +0,0 @@
|
||||
#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 (preserve dest)
|
||||
- repository.omittedRelativeDirectories: Relative directories to delete from dest and never copy from source
|
||||
(product repos: ["tests"] — RepoUtils self-tests stay only in maksit-repoutils / enterprise)
|
||||
#>
|
||||
|
||||
[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
|
||||
}
|
||||
|
||||
function Get-ReleaseDeployPreserveFiles {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TargetDirectory
|
||||
)
|
||||
|
||||
return @()
|
||||
}
|
||||
|
||||
function Add-PreservedFileBackup {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IList]$PreservedFiles,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.IO.FileInfo]$File,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TargetDirectory,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TemporaryRoot,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[bool]$DryRun
|
||||
)
|
||||
|
||||
$relativePath = [System.IO.Path]::GetRelativePath($TargetDirectory, $File.FullName)
|
||||
foreach ($existing in $PreservedFiles) {
|
||||
if ($existing.RelativePath.Equals($relativePath, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
$backupPath = Join-Path $TemporaryRoot ("preserved-" + ($relativePath -replace '[\\/:*?""<>|]', '_'))
|
||||
$PreservedFiles.Add([pscustomobject]@{
|
||||
RelativePath = $relativePath
|
||||
BackupPath = $backupPath
|
||||
}) | Out-Null
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#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')
|
||||
)
|
||||
}
|
||||
[string[]]$omittedRelativeDirectories = if ($settings.repository.omittedRelativeDirectories) {
|
||||
@(
|
||||
$settings.repository.omittedRelativeDirectories |
|
||||
ForEach-Object {
|
||||
ConvertTo-NormalizedRelativePath -Path ([string]$_)
|
||||
} |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
)
|
||||
}
|
||||
else {
|
||||
@()
|
||||
}
|
||||
|
||||
#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 = [System.Collections.ArrayList]@()
|
||||
[string[]]$updatePhaseSkippedDirectories = @($skippedRelativeDirectories) + $selfUpdateDirectory
|
||||
$existingPreservedFiles = Get-ChildItem -Path $targetDirectory -Recurse -File -Filter $preserveFileName -ErrorAction SilentlyContinue
|
||||
if ($existingPreservedFiles) {
|
||||
foreach ($file in $existingPreservedFiles) {
|
||||
Add-PreservedFileBackup -PreservedFiles $preservedFiles -File $file -TargetDirectory $targetDirectory -TemporaryRoot $temporaryRoot -DryRun $dryRun
|
||||
}
|
||||
Write-Log -Level "OK" -Message "Preserved $($existingPreservedFiles.Count) existing $preserveFileName file(s)"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level "WARN" -Message "No existing $preserveFileName files found in subfolders"
|
||||
}
|
||||
|
||||
$releaseDeployPreserveFiles = @(Get-ReleaseDeployPreserveFiles -TargetDirectory $targetDirectory)
|
||||
if ($releaseDeployPreserveFiles.Count -gt 0) {
|
||||
foreach ($file in $releaseDeployPreserveFiles) {
|
||||
Add-PreservedFileBackup -PreservedFiles $preservedFiles -File $file -TargetDirectory $targetDirectory -TemporaryRoot $temporaryRoot -DryRun $dryRun
|
||||
}
|
||||
Write-Log -Level "OK" -Message "Preserved $($releaseDeployPreserveFiles.Count) release deploy file(s)"
|
||||
}
|
||||
|
||||
$preservedRelativePaths = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($preservedFile in $preservedFiles) {
|
||||
[void]$preservedRelativePaths.Add($preservedFile.RelativePath)
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
Write-LogStep "Dry run summary"
|
||||
Write-Log -Level "INFO" -Message "Would remove all files under target except preserved $preserveFileName and release deploy files"
|
||||
Write-Log -Level "INFO" -Message "Would skip phase-two refresh for: $($updatePhaseSkippedDirectories -join ', ')"
|
||||
if ($omittedRelativeDirectories.Count -gt 0) {
|
||||
Write-Log -Level "INFO" -Message "Would omit (delete dest + skip copy): $($omittedRelativeDirectories -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
|
||||
$isInOmittedDirectory = ($omittedRelativeDirectories.Count -gt 0) -and
|
||||
(Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $omittedRelativeDirectories)
|
||||
|
||||
$_.Name -ne $preserveFileName -and
|
||||
-not $preservedRelativePaths.Contains($relativePath) -and
|
||||
(-not $isInSkippedDirectory -or $isInOmittedDirectory)
|
||||
}
|
||||
|
||||
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)
|
||||
$isInOmittedDirectory = ($omittedRelativeDirectories.Count -gt 0) -and
|
||||
(Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $omittedRelativeDirectories)
|
||||
if ((Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $updatePhaseSkippedDirectories) -and
|
||||
-not $isInOmittedDirectory) {
|
||||
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
|
||||
$isInOmittedDirectory = ($omittedRelativeDirectories.Count -gt 0) -and
|
||||
(Test-IsInRelativeDirectory -RelativePath $relativePath -Directories $omittedRelativeDirectories)
|
||||
|
||||
-not $isInSkippedDirectory -and -not $isInOmittedDirectory
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
foreach ($omittedDirectory in $omittedRelativeDirectories) {
|
||||
Write-Log -Level "INFO" -Message "Omitted (not shipped): $omittedDirectory"
|
||||
}
|
||||
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 "Preserved files restored ($($preservedFiles.Count))"
|
||||
}
|
||||
|
||||
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
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"$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"
|
||||
],
|
||||
"omittedRelativeDirectories": [
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user