Skip to content
16 changes: 11 additions & 5 deletions .github/workflows/publish-module-manualversionupdate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Download updated report template
if: needs.build-report.outputs.report-updated == 'true'
Expand All @@ -40,16 +42,20 @@ jobs:
name: updated-report-template
path: powershell/assets/

- name: Package ./tests to PowerShell module
id: package-tests
shell: pwsh
run: ./build/Copy-MaesterTestsToPSModule.ps1 -Force

- name: Get current module version
id: moduleversion
shell: pwsh
run: ./.github/workflows/get-version.ps1

- name: Stamp maester-config.json version fields
shell: pwsh
run: ./build/Update-MaesterConfigVersion.ps1 -ConfigPath tests/maester-config.json -ModuleVersion '${{ steps.moduleversion.outputs.tag }}'

- name: Package ./tests to PowerShell module
id: package-tests
shell: pwsh
run: ./build/Copy-MaesterTestsToPSModule.ps1 -Force

- name: Update PowerShell Module to PowerShell Gallery
id: publish-to-gallery
shell: pwsh
Expand Down
16 changes: 11 additions & 5 deletions .github/workflows/publish-module-preview.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Download updated report template
if: needs.build-report.outputs.report-updated == 'true'
Expand All @@ -40,16 +42,20 @@ jobs:
name: updated-report-template
path: powershell/assets/

- name: Package ./tests to PowerShell module
id: package-tests
shell: pwsh
run: ./build/Copy-MaesterTestsToPSModule.ps1 -Force

- name: Set module version
id: moduleversion
shell: pwsh
run: ./.github/workflows/minor-version-update.ps1 -preview

- name: Stamp maester-config.json version fields
shell: pwsh
run: ./build/Update-MaesterConfigVersion.ps1 -ConfigPath tests/maester-config.json -ModuleVersion '${{ steps.moduleversion.outputs.tag }}'

- name: Package ./tests to PowerShell module
id: package-tests
shell: pwsh
run: ./build/Copy-MaesterTestsToPSModule.ps1 -Force

- name: Archive PowerShell build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
16 changes: 11 additions & 5 deletions .github/workflows/publish-module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Download updated report template
if: needs.build-report.outputs.report-updated == 'true'
Expand All @@ -40,16 +42,20 @@ jobs:
name: updated-report-template
path: powershell/assets/

- name: Package ./tests to PowerShell module
id: package-tests
shell: pwsh
run: ./build/Copy-MaesterTestsToPSModule.ps1 -Force

- name: Set module version
id: moduleversion
shell: pwsh
run: ./.github/workflows/minor-version-update.ps1

- name: Stamp maester-config.json version fields
shell: pwsh
run: ./build/Update-MaesterConfigVersion.ps1 -ConfigPath tests/maester-config.json -ModuleVersion '${{ steps.moduleversion.outputs.tag }}'

- name: Package ./tests to PowerShell module
id: package-tests
shell: pwsh
run: ./build/Copy-MaesterTestsToPSModule.ps1 -Force

- name: Update PowerShell Module to PowerShell Gallery
id: publish-to-gallery
shell: pwsh
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/publish-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Resolve latest release tag
id: release_tag
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$tag = '${{ github.event.release.tag_name }}'
if ([string]::IsNullOrWhiteSpace($tag)) {
$tag = (gh release view --repo $env:GITHUB_REPOSITORY --json tagName -q .tagName)
}
if ([string]::IsNullOrWhiteSpace($tag)) {
throw "Could not resolve a release tag for the stamp step."
}
"tag=$tag" | Out-File -Append -FilePath $env:GITHUB_OUTPUT

- name: Stamp maester-config.json version fields
shell: pwsh
run: ./build/Update-MaesterConfigVersion.ps1 -ConfigPath ./tests/maester-config.json -ModuleVersion '${{ steps.release_tag.outputs.tag }}'

- name: Publish to maester-tests
id: push_directory
uses: cpina/github-action-push-to-another-repository@3fc9348237c8c6954ff88e58719af8a88af543f7
Expand Down
87 changes: 87 additions & 0 deletions build/Update-MaesterConfigVersion.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<#
.SYNOPSIS
Stamps the ModuleVersion and ConfigVersion fields at the top of a
maester-config.json file.

.DESCRIPTION
Reads and updates the JSON object directly, then writes UTF-8 without BOM.
Uses an explicit JSON depth when writing so future nested settings are not
truncated by the default ConvertTo-Json depth.

Both fields must already exist in the source file. The script does not
insert missing fields — if either is absent, it throws and asks the
caller to add them manually. This avoids fragile insertion logic and
makes schema changes explicit in source control.

ConfigVersion is a CalVer-style YYYY.MM.DD.N string derived from git
history of the config file: YYYY.MM.DD is the date of the most recent
commit to the file; N is the count of commits to the file on that date.
Auto-computed when -ConfigVersion is omitted (the normal CI path).

Requires sufficient git history to find the last commit touching the file,
so callers should run actions/checkout with fetch-depth: 0.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $ConfigPath,
[Parameter(Mandatory)] [string] $ModuleVersion,
[Parameter()] [string] $ConfigVersion
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

if (-not (Test-Path -LiteralPath $ConfigPath)) {
throw "Config file not found: $ConfigPath"
}

if (-not $PSBoundParameters.ContainsKey('ConfigVersion')) {
# Resolve to a repo-relative path so git lookup works regardless of CWD
# or whether ConfigPath was passed as relative or absolute.
$resolvedConfigPath = (Resolve-Path -LiteralPath $ConfigPath).ProviderPath
$configDir = Split-Path -Parent $resolvedConfigPath
$repoRoot = (& git -C $configDir rev-parse --show-toplevel 2>$null)
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($repoRoot)) {
throw "Could not determine ConfigVersion: $ConfigPath is not inside a git repository."
}
$repoRoot = $repoRoot.Trim()
$repoRelative = [System.IO.Path]::GetRelativePath($repoRoot, $resolvedConfigPath).Replace('\', '/')
$commitTimestamps = @(& git -C $repoRoot log --format=%ct -- $repoRelative)
if ($LASTEXITCODE -ne 0 -or $commitTimestamps.Count -eq 0) {
throw "Could not determine ConfigVersion: no git history found for $repoRelative in $repoRoot."
}
$utcDates = @($commitTimestamps | ForEach-Object {
[System.DateTimeOffset]::FromUnixTimeSeconds([long]$_).UtcDateTime.ToString('yyyy.MM.dd', [System.Globalization.CultureInfo]::InvariantCulture)
})
$lastDate = $utcDates[0]
$sameDayCount = @($utcDates | Where-Object { $_ -eq $lastDate }).Count
$ConfigVersion = "$lastDate.$sameDayCount"
Write-Verbose "Computed ConfigVersion=$ConfigVersion (date $lastDate, $sameDayCount commit(s) that day)"
}

$content = Get-Content -LiteralPath $ConfigPath -Raw
try {
$config = $content | ConvertFrom-Json
} catch {
throw "Input file is not valid JSON: $_"
}

if (-not ($config.PSObject.Properties.Name -contains 'ModuleVersion')) {
throw "Required field ModuleVersion not found in $ConfigPath. Add `"ModuleVersion`": `"<version>`" as a top-level key before re-running."
}

if (-not ($config.PSObject.Properties.Name -contains 'ConfigVersion')) {
throw "Required field ConfigVersion not found in $ConfigPath. Add `"ConfigVersion`": `"`" as a top-level key before re-running."
}

$config.ModuleVersion = $ModuleVersion
$config.ConfigVersion = $ConfigVersion
$updatedContent = $config | ConvertTo-Json -Depth 10 -WarningAction Stop

try { $null = $updatedContent | ConvertFrom-Json } catch { throw "Output is not valid JSON after stamping: $_" }

$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
$resolvedOutputPath = (Resolve-Path -LiteralPath $ConfigPath).ProviderPath
[System.IO.File]::WriteAllText($resolvedOutputPath, $updatedContent, $utf8NoBom)

Write-Host "Stamped ${ConfigPath}: ModuleVersion=$ModuleVersion, ConfigVersion=$ConfigVersion"
4 changes: 4 additions & 0 deletions powershell/internal/Get-MtMaesterConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ function Get-MtMaesterConfig {
Write-Verbose "Loading Maester config from: $ConfigFilePath"
$maesterConfig = Get-Content -Path $ConfigFilePath -Raw | ConvertFrom-Json

$loadedModuleVersion = if ($maesterConfig.PSObject.Properties.Name -contains 'ModuleVersion') { $maesterConfig.ModuleVersion } else { '<none>' }
$loadedConfigVersion = if ($maesterConfig.PSObject.Properties.Name -contains 'ConfigVersion') { $maesterConfig.ConfigVersion } else { '<none>' }
Write-Verbose "Loaded Maester config: ModuleVersion=$loadedModuleVersion, ConfigVersion=$loadedConfigVersion"

# Store the source file name so the report can show which config was loaded
$configFileName = Split-Path -Path $ConfigFilePath -Leaf
Add-Member -InputObject $maesterConfig -MemberType NoteProperty -Name 'ConfigSource' -Value $configFileName
Expand Down
5 changes: 5 additions & 0 deletions powershell/tests/functions/Get-MtMaesterConfig.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
#$sample.Title | Should -Not -Be 'Overridden Title from Custom Config'

$result.ConfigSource | Should -Be 'maester-config.json'

# Version fields survive load
$result.PSObject.Properties.Name | Should -Contain 'ModuleVersion'
$result.ModuleVersion | Should -Not -BeNullOrEmpty
$result.PSObject.Properties.Name | Should -Contain 'ConfigVersion'
}

Context 'Tenant-specific config' {
Expand Down
37 changes: 37 additions & 0 deletions powershell/tests/functions/MaesterConfig.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
Describe 'Maester Configuration File - tests/maester-config.json' {
Context 'Version fields' {
It 'has a top-level ModuleVersion string' {
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '../../..')
$configPath = Join-Path $repoRoot 'tests/maester-config.json'
$configJson = Get-Content -Path $configPath -Raw | ConvertFrom-Json

$configJson.PSObject.Properties.Name | Should -Contain 'ModuleVersion'
$configJson.ModuleVersion | Should -BeOfType [string]
$configJson.ModuleVersion | Should -Not -BeNullOrEmpty
Comment thread
SamErde marked this conversation as resolved.
}

It 'source ModuleVersion matches powershell/Maester.psd1 ModuleVersion' {
# The published artifact's ModuleVersion is stamped by CI, but the
# source-tree value should track Maester.psd1 so a clone shows a
# sensible number. Drift between the two is a maintenance bug.
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '../../..')
$configPath = Join-Path $repoRoot 'tests/maester-config.json'
$manifestPath = Join-Path $repoRoot 'powershell/Maester.psd1'

$configJson = Get-Content -Path $configPath -Raw | ConvertFrom-Json
$manifest = Import-PowerShellDataFile -Path $manifestPath

$configJson.ModuleVersion | Should -Be $manifest.ModuleVersion -Because "tests/maester-config.json ModuleVersion ($($configJson.ModuleVersion)) should match powershell/Maester.psd1 ModuleVersion ($($manifest.ModuleVersion))"
}

It 'has a top-level ConfigVersion string (CalVer YYYY.MM.DD.N or empty sentinel)' {
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '../../..')
$configPath = Join-Path $repoRoot 'tests/maester-config.json'
$configJson = Get-Content -Path $configPath -Raw | ConvertFrom-Json

$configJson.PSObject.Properties.Name | Should -Contain 'ConfigVersion'
$configJson.ConfigVersion | Should -BeOfType [string]
# Empty (source sentinel) or CalVer format. CI stamps the CalVer at publish time.
$configJson.ConfigVersion | Should -Match '^$|^\d{4}\.\d{2}\.\d{2}\.\d+$'
}
}

Context 'TestSettings array' {
It 'should be sorted by Id' {
# Correctly join paths to find the repo root and config file
Expand Down
Loading
Loading