From 525164d5585a1ca79a588e34d5ff38d402cf2ee3 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 8 Aug 2026 09:03:30 +0200 Subject: [PATCH 1/5] fix: support wildcard maximum module versions --- .../helpers/Build/Build-PSModuleManifest.ps1 | 13 ++++---- .../src/PSModule/PSModule.psm1 | 30 ++++++++++++++++--- .../public/PSModule/Get-PSModuleTest.ps1 | 2 +- .../public/PSModule/Get-PSModuleTest.ps1 | 2 +- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleManifest.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleManifest.ps1 index fd564a61..8e37790f 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleManifest.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleManifest.ps1 @@ -260,7 +260,8 @@ Write-Host "Processing required module [$requiredModuleName]" $requiredVersion = $group.Group.RequiredVersion | ForEach-Object { [Version]$_ } | Sort-Object -Unique $minimumVersion = $group.Group.Version | ForEach-Object { [Version]$_ } | Sort-Object -Unique | Select-Object -Last 1 - $maximumVersion = $group.Group.MaximumVersion | ForEach-Object { [Version]$_ } | Sort-Object -Unique | Select-Object -First 1 + $maximumVersion = $group.Group.MaximumVersion | Sort-Object -Unique | Select-Object -First 1 + $maximumVersionBound = if ($maximumVersion -and ($maximumVersion -notmatch '[*]')) { [Version]$maximumVersion } else { $null } Write-Host "RequiredVersion: [$($requiredVersion -join ', ')]" Write-Host "ModuleVersion: [$minimumVersion]" Write-Host "MaximumVersion: [$maximumVersion]" @@ -274,17 +275,17 @@ } if (-not $maximumVersion) { - $maximumVersion = [Version]'9999.9999.9999' + $maximumVersionBound = [Version]'9999.9999.9999' } if ($requiredVersion -and ($minimumVersion -gt $requiredVersion)) { throw 'ModuleVersion is higher than RequiredVersion.' } - if ($minimumVersion -gt $maximumVersion) { + if ($maximumVersionBound -and ($minimumVersion -gt $maximumVersionBound)) { throw 'ModuleVersion is higher than MaximumVersion.' } - if ($requiredVersion -and ($requiredVersion -gt $maximumVersion)) { + if ($requiredVersion -and $maximumVersionBound -and ($requiredVersion -gt $maximumVersionBound)) { throw 'RequiredVersion is higher than MaximumVersion.' } @@ -294,7 +295,7 @@ ModuleName = $requiredModuleName RequiredVersion = $requiredVersion } - } elseif (($minimumVersion -ne [Version]'0.0.0') -or ($maximumVersion -ne [Version]'9999.9999.9999')) { + } elseif (($minimumVersion -ne [Version]'0.0.0') -or ($maximumVersionBound -ne [Version]'9999.9999.9999')) { Write-Host '[RequiredModules] - ModuleVersion/MaximumVersion' $uniqueModule = @{ ModuleName = $requiredModuleName @@ -302,7 +303,7 @@ if ($minimumVersion -ne [Version]'0.0.0') { $uniqueModule['ModuleVersion'] = $minimumVersion } - if ($maximumVersion -ne [Version]'9999.9999.9999') { + if ($maximumVersionBound -ne [Version]'9999.9999.9999') { $uniqueModule['MaximumVersion'] = $maximumVersion } } else { diff --git a/.github/actions/Install-PSModule/src/PSModule/PSModule.psm1 b/.github/actions/Install-PSModule/src/PSModule/PSModule.psm1 index 48b8fb85..555be40c 100644 --- a/.github/actions/Install-PSModule/src/PSModule/PSModule.psm1 +++ b/.github/actions/Install-PSModule/src/PSModule/PSModule.psm1 @@ -87,18 +87,40 @@ function Convert-VersionSpec { [string] $RequiredVersion ) + $maximumVersionIsWildcard = $MaximumVersion -and ($MaximumVersion -match '[*]') + if ($maximumVersionIsWildcard) { + $versionParts = @($MaximumVersion -split '\.') + $wildcardIndex = [Array]::IndexOf($versionParts, '*') + if ($wildcardIndex -lt 0) { + throw "MaximumVersion '$MaximumVersion' contains an unsupported wildcard pattern." + } + + if ($wildcardIndex -eq 0) { + throw "MaximumVersion '$MaximumVersion' contains an unsupported wildcard pattern." + } + + $upperBoundParts = @(0, 0, 0) + for ($i = 0; $i -lt $wildcardIndex; $i++) { + $upperBoundParts[$i] = [int]$versionParts[$i] + } + $upperBoundParts[$wildcardIndex - 1]++ + $MaximumVersion = $upperBoundParts -join '.' + } + if ($RequiredVersion) { # Use exact match in bracket notation. return "[$RequiredVersion]" } elseif ($MinimumVersion -and $MaximumVersion) { - # Both bounds provided; both are inclusive. - return "[$MinimumVersion,$MaximumVersion]" + # Wildcard maximum versions are translated to an exclusive upper bound. + $upperBound = $maximumVersionIsWildcard ? ')' : ']' + return "[$MinimumVersion,$MaximumVersion$upperBound" } elseif ($MinimumVersion) { # Only a minimum is provided. Use a minimum-inclusive range. return "[$MinimumVersion, ]" } elseif ($MaximumVersion) { - # Only a maximum is provided; lower bound open. - return "(, $MaximumVersion]" + # Only a maximum is provided; lower bound open. Wildcards are exclusive upper bounds. + $upperBound = $maximumVersionIsWildcard ? ')' : ']' + return "(, $MaximumVersion$upperBound" } else { return $null } diff --git a/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 index a07d05b5..f1b20479 100644 --- a/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 +++ b/tests/srcTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 @@ -1,6 +1,6 @@ #Requires -Modules Utilities #Requires -Modules @{ ModuleName = 'PSSemVer'; RequiredVersion = '1.1.4' } -#Requires -Modules @{ ModuleName = 'DynamicParams'; ModuleVersion = '1.1.8' } +#Requires -Modules @{ ModuleName = 'ThreadJob'; ModuleVersion = '1.0.0'; MaximumVersion = '1.*' } #Requires -Modules @{ ModuleName = 'Store'; ModuleVersion = '0.3.1' } function Get-PSModuleTest { diff --git a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 index a07d05b5..f1b20479 100644 --- a/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 +++ b/tests/srcWithManifestTestRepo/src/functions/public/PSModule/Get-PSModuleTest.ps1 @@ -1,6 +1,6 @@ #Requires -Modules Utilities #Requires -Modules @{ ModuleName = 'PSSemVer'; RequiredVersion = '1.1.4' } -#Requires -Modules @{ ModuleName = 'DynamicParams'; ModuleVersion = '1.1.8' } +#Requires -Modules @{ ModuleName = 'ThreadJob'; ModuleVersion = '1.0.0'; MaximumVersion = '1.*' } #Requires -Modules @{ ModuleName = 'Store'; ModuleVersion = '0.3.1' } function Get-PSModuleTest { From 0287cee8a1f71a6900e617da3aabcbca94a127a4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 8 Aug 2026 09:18:39 +0200 Subject: [PATCH 2/5] fix: build site from zensical config --- .github/workflows/Build-Site.yml | 40 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/.github/workflows/Build-Site.yml b/.github/workflows/Build-Site.yml index d128642e..3d298f7c 100644 --- a/.github/workflows/Build-Site.yml +++ b/.github/workflows/Build-Site.yml @@ -39,13 +39,15 @@ jobs: name: docs path: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs - - name: Install mkdocs-material + - name: Install site dependencies shell: pwsh run: | pip install mkdocs-material pip install mkdocs-git-authors-plugin pip install mkdocs-git-revision-date-localized-plugin pip install mkdocs-git-committers-plugin-2 + pip install pyyaml + pip install tomli - name: Structure site uses: PSModule/GitHub-Script@8083ec1f733f00357ee4d0db0c6056686e483bc0 # v1.9.0 @@ -111,36 +113,28 @@ jobs: Write-Host "Readme Target Path: $readmeTargetPath" } - LogGroup 'Build docs - Create mkdocs.yml' { + LogGroup 'Build docs - Create mkdocs.yml from zensical.toml' { $rootPath = Split-Path -Path $ModuleSourcePath -Parent - $possiblePaths = @( - '.github/mkdocs.yml', - 'docs/mkdocs.yml', - 'mkdocs.yml' - ) - - $mkdocsSourcePath = $null - foreach ($path in $possiblePaths) { - $candidatePath = Join-Path -Path $rootPath -ChildPath $path - if (Test-Path -Path $candidatePath) { - $mkdocsSourcePath = $candidatePath - break - } - } + $zensicalSourcePath = Join-Path -Path $rootPath -ChildPath '.github/zensical.toml' - if (-not $mkdocsSourcePath) { - throw "Mkdocs source file not found in any of the expected locations: $($possiblePaths -join ', ')" + if (-not (Test-Path -Path $zensicalSourcePath)) { + throw "Zensical source file not found at expected location: .github/zensical.toml" } $mkdocsTargetPath = Join-Path -Path $SiteOutputPath -ChildPath 'mkdocs.yml' - Write-Host "Mkdocs Source Path: $mkdocsSourcePath" + Write-Host "Zensical Source Path: $zensicalSourcePath" Write-Host "Mkdocs Target Path: $mkdocsTargetPath" - $mkdocsContent = Get-Content -Path $mkdocsSourcePath -Raw - $mkdocsContent = $mkdocsContent.Replace('-{{ REPO_NAME }}-', $ModuleName) - $mkdocsContent = $mkdocsContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) - $mkdocsContent | Set-Content -Path $mkdocsTargetPath -Force + $zensicalContent = Get-Content -Path $zensicalSourcePath -Raw + $zensicalContent = $zensicalContent.Replace('-{{ REPO_NAME }}-', $ModuleName) + $zensicalContent = $zensicalContent.Replace('-{{ REPO_OWNER }}-', $env:GITHUB_REPOSITORY_OWNER) + + $zensicalInputPath = Join-Path -Path $SiteOutputPath -ChildPath 'zensical.toml' + $zensicalContent | Set-Content -Path $zensicalInputPath -Force + + python3 -c "import pathlib, yaml;`ntry:`n import tomllib`nexcept ModuleNotFoundError:`n import tomli as tomllib`nsource_path = pathlib.Path('outputs/site/zensical.toml'); target_path = pathlib.Path('outputs/site/mkdocs.yml')`nwith source_path.open('rb') as source_file:`n zensical = tomllib.load(source_file)`nproject = zensical.get('project', {}); theme = dict(project.get('theme', {})); theme.pop('variant', None); theme = {'name': 'material', **theme}`nmarkdown_extensions = [{name: value} if value else name for name, value in project.get('markdown_extensions', {}).items()]`nmkdocs = {'site_name': project.get('site_name'), 'repo_name': project.get('repo_name'), 'repo_url': project.get('repo_url'), 'theme': theme, 'plugins': project.get('plugins', ['search']), 'markdown_extensions': markdown_extensions}`nif 'extra' in project:`n mkdocs['extra'] = project['extra']`nwith target_path.open('w', encoding='utf-8') as target_file:`n yaml.safe_dump(mkdocs, target_file, sort_keys=False, allow_unicode=True)" + Show-FileContent -Path $mkdocsTargetPath } From 474f158415b5264905f5f155b1576e2f72b26725 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 8 Aug 2026 17:15:33 +0200 Subject: [PATCH 3/5] Use markdownlint for generated docs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Docs.yml | 40 ++++---------------------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/.github/workflows/Build-Docs.yml b/.github/workflows/Build-Docs.yml index 65499b9a..27115d60 100644 --- a/.github/workflows/Build-Docs.yml +++ b/.github/workflows/Build-Docs.yml @@ -10,7 +10,6 @@ on: permissions: contents: read # to checkout the repo - statuses: write # to create commit status jobs: Build-Docs: @@ -79,38 +78,7 @@ jobs: } - name: Lint documentation - id: super-linter - uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0 - env: - RUN_LOCAL: true - DEFAULT_BRANCH: main - DEFAULT_WORKSPACE: ${{ fromJson(inputs.Settings).WorkingDirectory }} - FILTER_REGEX_INCLUDE: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs - ENABLE_GITHUB_ACTIONS_GROUP_TITLE: true - GITHUB_TOKEN: ${{ github.token }} - VALIDATE_ALL_CODEBASE: true - VALIDATE_BIOME_FORMAT: false - VALIDATE_BIOME_LINT: false - VALIDATE_GITHUB_ACTIONS_ZIZMOR: false - VALIDATE_GITLEAKS: false - VALIDATE_JSCPD: false - VALIDATE_JSON_PRETTIER: false - VALIDATE_MARKDOWN_PRETTIER: false - VALIDATE_YAML_PRETTIER: false - ENABLE_GITHUB_ACTIONS_STEP_SUMMARY: false - SAVE_SUPER_LINTER_SUMMARY: true - - - name: Post super-linter summary - if: failure() || fromJson(inputs.Settings).Build.Docs.ShowSummaryOnSuccess == true - shell: pwsh - env: - SUPER_LINTER_OUTCOME: ${{ steps.super-linter.outcome }} - run: | - $summaryPath = Join-Path $env:GITHUB_WORKSPACE 'super-linter-output' 'super-linter-summary.md' - Get-Content $summaryPath | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 - - $failed = $env:SUPER_LINTER_OUTCOME -eq 'failure' - if ($failed) { - Write-Host "::error::Super-linter found issues. Please review the summary above." - exit 1 - } + uses: DavidAnson/markdownlint-cli2-action@28a7e8bdb81fd8ad675883de92c758ab78e0ce10 # v24 + with: + config: .github/linters/.markdown-lint.yml + globs: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs/**/*.md From 4c299ab7f75c0905be2f1f7d4cb38903dc687d5e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 8 Aug 2026 17:18:14 +0200 Subject: [PATCH 4/5] Summarize markdown lint results Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Docs.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Build-Docs.yml b/.github/workflows/Build-Docs.yml index 27115d60..c789c244 100644 --- a/.github/workflows/Build-Docs.yml +++ b/.github/workflows/Build-Docs.yml @@ -78,7 +78,31 @@ jobs: } - name: Lint documentation - uses: DavidAnson/markdownlint-cli2-action@28a7e8bdb81fd8ad675883de92c758ab78e0ce10 # v24 + id: markdownlint + uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0 with: config: .github/linters/.markdown-lint.yml globs: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs/**/*.md + + - name: Summarize documentation lint + if: always() + shell: pwsh + env: + LINT_OUTCOME: ${{ steps.markdownlint.outcome }} + WORKING_DIRECTORY: ${{ fromJson(inputs.Settings).WorkingDirectory }} + run: | + $docsPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "$env:WORKING_DIRECTORY/outputs/docs" + $fileCount = if (Test-Path -Path $docsPath) { + @(Get-ChildItem -Path $docsPath -Recurse -File -Include '*.md', '*.markdown').Count + } else { + 0 + } + + @( + '## Documentation lint' + '' + '- Tool: `markdownlint-cli2-action v24.2.0`' + '- Configuration: `.github/linters/.markdown-lint.yml`' + "- Files checked: $fileCount" + "- Result: **$env:LINT_OUTCOME**" + ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append From 8c8ee676d6c2ccfc70e2bdceef8b4e1dea6b6bd3 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 8 Aug 2026 17:19:16 +0200 Subject: [PATCH 5/5] Restore documentation linter Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Build-Docs.yml | 54 ++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/.github/workflows/Build-Docs.yml b/.github/workflows/Build-Docs.yml index c789c244..65499b9a 100644 --- a/.github/workflows/Build-Docs.yml +++ b/.github/workflows/Build-Docs.yml @@ -10,6 +10,7 @@ on: permissions: contents: read # to checkout the repo + statuses: write # to create commit status jobs: Build-Docs: @@ -78,31 +79,38 @@ jobs: } - name: Lint documentation - id: markdownlint - uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0 - with: - config: .github/linters/.markdown-lint.yml - globs: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs/**/*.md + id: super-linter + uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0 + env: + RUN_LOCAL: true + DEFAULT_BRANCH: main + DEFAULT_WORKSPACE: ${{ fromJson(inputs.Settings).WorkingDirectory }} + FILTER_REGEX_INCLUDE: ${{ fromJson(inputs.Settings).WorkingDirectory }}/outputs/docs + ENABLE_GITHUB_ACTIONS_GROUP_TITLE: true + GITHUB_TOKEN: ${{ github.token }} + VALIDATE_ALL_CODEBASE: true + VALIDATE_BIOME_FORMAT: false + VALIDATE_BIOME_LINT: false + VALIDATE_GITHUB_ACTIONS_ZIZMOR: false + VALIDATE_GITLEAKS: false + VALIDATE_JSCPD: false + VALIDATE_JSON_PRETTIER: false + VALIDATE_MARKDOWN_PRETTIER: false + VALIDATE_YAML_PRETTIER: false + ENABLE_GITHUB_ACTIONS_STEP_SUMMARY: false + SAVE_SUPER_LINTER_SUMMARY: true - - name: Summarize documentation lint - if: always() + - name: Post super-linter summary + if: failure() || fromJson(inputs.Settings).Build.Docs.ShowSummaryOnSuccess == true shell: pwsh env: - LINT_OUTCOME: ${{ steps.markdownlint.outcome }} - WORKING_DIRECTORY: ${{ fromJson(inputs.Settings).WorkingDirectory }} + SUPER_LINTER_OUTCOME: ${{ steps.super-linter.outcome }} run: | - $docsPath = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "$env:WORKING_DIRECTORY/outputs/docs" - $fileCount = if (Test-Path -Path $docsPath) { - @(Get-ChildItem -Path $docsPath -Recurse -File -Include '*.md', '*.markdown').Count - } else { - 0 - } + $summaryPath = Join-Path $env:GITHUB_WORKSPACE 'super-linter-output' 'super-linter-summary.md' + Get-Content $summaryPath | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 - @( - '## Documentation lint' - '' - '- Tool: `markdownlint-cli2-action v24.2.0`' - '- Configuration: `.github/linters/.markdown-lint.yml`' - "- Files checked: $fileCount" - "- Result: **$env:LINT_OUTCOME**" - ) | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append + $failed = $env:SUPER_LINTER_OUTCOME -eq 'failure' + if ($failed) { + Write-Host "::error::Super-linter found issues. Please review the summary above." + exit 1 + }