From f142b9bfb1b92dbbc753f58c98c68300cf110ecf Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 18:49:37 +0200 Subject: [PATCH 1/9] Remove Dashboard Extension page Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/docs/Modules/Dashboard-Extension/index.md | 11 ------- src/docs/Modules/Dashboard-Extension/spec.md | 30 ------------------- src/docs/Modules/index.md | 2 -- src/docs/index.md | 1 - src/zensical.toml | 4 --- 5 files changed, 48 deletions(-) delete mode 100644 src/docs/Modules/Dashboard-Extension/index.md delete mode 100644 src/docs/Modules/Dashboard-Extension/spec.md diff --git a/src/docs/Modules/Dashboard-Extension/index.md b/src/docs/Modules/Dashboard-Extension/index.md deleted file mode 100644 index a2e2e96..0000000 --- a/src/docs/Modules/Dashboard-Extension/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Dashboard Extension - -This section describes the optional module dashboard extension concept for PSModule. - -The extension updates a dashboard with metadata for managed modules, such as: - -- module version -- repository stars -- maintenance and release signals - -- [Spec](spec.md) diff --git a/src/docs/Modules/Dashboard-Extension/spec.md b/src/docs/Modules/Dashboard-Extension/spec.md deleted file mode 100644 index ccc8f60..0000000 --- a/src/docs/Modules/Dashboard-Extension/spec.md +++ /dev/null @@ -1,30 +0,0 @@ -# Module Dashboard Extension Spec - -## Objective - -Provide an automated way to publish and refresh module portfolio visibility for PSModule repositories. - -## Minimum data points - -- module name -- latest released version -- repository star count -- last release date -- repository status metadata - -## Update model - -- source data from GitHub APIs and module metadata -- scheduled refresh via workflow or service process -- deterministic rendering to docs pages or dashboard output - -## Non-goals - -- replacing module documentation pages -- acting as release orchestration - -## Open decisions - -- static docs generation versus live service-backed dashboard -- update cadence and API rate limit strategy -- source of truth for module ownership metadata diff --git a/src/docs/Modules/index.md b/src/docs/Modules/index.md index 2989ec8..e0b57a7 100644 --- a/src/docs/Modules/index.md +++ b/src/docs/Modules/index.md @@ -7,7 +7,6 @@ This section is the local source of truth for: - module catalog and module-level specs - Process-PSModule structure and build flow - repository anatomy and template onboarding -- optional dashboard extension specification for module telemetry ## Sections @@ -18,4 +17,3 @@ This section is the local source of truth for: - [Versioning](Versioning.md) - [Catalog](Catalog/index.md) - [Process-PSModule](Process-PSModule/index.md) -- [Dashboard Extension](Dashboard-Extension/index.md) diff --git a/src/docs/index.md b/src/docs/index.md index 113f220..29e8277 100644 --- a/src/docs/index.md +++ b/src/docs/index.md @@ -40,7 +40,6 @@ This site is now focused on PSModule-org specific documentation: - [Modules](Modules/index.md): module catalog and module-specific standards - [Process-PSModule](Modules/Process-PSModule/index.md): how modules are structured and built - [Template onboarding](Modules/Process-PSModule/template-quickstart.md): how to start from the template -- [Dashboard extension](Modules/Dashboard-Extension/index.md): module telemetry documentation spec ## Cross-org guidance has moved diff --git a/src/zensical.toml b/src/zensical.toml index 7894e6f..5990c31 100644 --- a/src/zensical.toml +++ b/src/zensical.toml @@ -34,10 +34,6 @@ nav = [ {"Build, Test, Pack, Publish" = "Modules/Process-PSModule/build-test-pack-publish.md"}, {"Template Quickstart" = "Modules/Process-PSModule/template-quickstart.md"}, ]}, - {"Dashboard Extension" = [ - "Modules/Dashboard-Extension/index.md", - {"Spec" = "Modules/Dashboard-Extension/spec.md"}, - ]}, ]}, {"Dictionary" = [ "Dictionary/index.md", From b3dda99613f4d2a4088fd516426579cf75cbd37f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 19:07:18 +0200 Subject: [PATCH 2/9] Ignore generated site output Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/.gitignore diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..46ff246 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +/site/ From 7fd5ed135b044445df5c2439211ba1523a9820fb Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:05:06 +0200 Subject: [PATCH 3/9] Allow Write-Host in lint config Replace update-index Write-Output calls with Write-Host. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 4 ++-- .github/linters/.powershell-psscriptanalyzer.psd1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index 25b10ec..2e4d16e 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -40,14 +40,14 @@ function Show-RepoList { } elseif ($currentContext.AuthType -eq 'App') { Connect-GitHubApp -Organization $Owner -Default } else { - Write-Output "Using existing GitHub context [$($currentContext.Name)] with auth type [$($currentContext.AuthType)]" + Write-Host "Using existing GitHub context [$($currentContext.Name)] with auth type [$($currentContext.AuthType)]" } Get-GitHubContext | Select-Object * | Format-List | Out-String } LogGroup "Get repositories for organization [$Owner]" { $rawRepos = Get-GitHubRepository -Owner $Owner - Write-Output "Found $($rawRepos.Count) repositories" + Write-Host "Found $($rawRepos.Count) repositories" $repos = $rawRepos | ForEach-Object { $rawRepo = $_ $rawRepo.CustomProperties | Where-Object { $_.Name -eq 'Type' } | ForEach-Object { diff --git a/.github/linters/.powershell-psscriptanalyzer.psd1 b/.github/linters/.powershell-psscriptanalyzer.psd1 index 6530ae8..f6f9dd0 100644 --- a/.github/linters/.powershell-psscriptanalyzer.psd1 +++ b/.github/linters/.powershell-psscriptanalyzer.psd1 @@ -50,7 +50,7 @@ } } ExcludeRules = @( - 'PSAvoidUsingWriteHost', # Write-Host is acceptable in guidance scripts. + 'PSAvoidUsingWriteHost', # Write-Host is acceptable in scripts used for docs tooling and guidance. 'PSMissingModuleManifestField', # This rule is not applicable until the module is built. 'PSUseToExportFieldsInManifest' ) From 2a4d541af3b9f499c33e52868e70d55ad03e5ec4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:07:41 +0200 Subject: [PATCH 4/9] Add staged logging to update-index Use LogGroup and Write-Host messages to show processing stages and per-module progress. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 208 +++++++++++-------- .github/actions/update-index/src/main.ps1 | 25 ++- 2 files changed, 139 insertions(+), 94 deletions(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index 2e4d16e..26decb1 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -64,6 +64,12 @@ function Show-RepoList { } } } | Sort-Object Type, Name + $reposByType = $repos | Group-Object Type | Sort-Object Name + Write-Host 'Repository type distribution:' + $reposByType | ForEach-Object { + Write-Host " - $($_.Name): $($_.Count)" + } + Write-Host 'Repository table preview:' $repos | Format-Table -AutoSize } @@ -106,25 +112,28 @@ function Update-MDSection { [string] $Content ) - $startSegment = "" - $endSegment = "" - $currentContent = Get-Content -Path $Path - $startIndex = $currentContent.IndexOf($startSegment) - $endIndex = $currentContent.IndexOf($endSegment) + LogGroup "Update markdown section [$Name] in [$Path]" { + $startSegment = "" + $endSegment = "" + $currentContent = Get-Content -Path $Path + $startIndex = $currentContent.IndexOf($startSegment) + $endIndex = $currentContent.IndexOf($endSegment) - if ($startIndex -lt 0) { - throw "[$Name] The start comment segment was not found in the file." - } - if ($endIndex -lt 0) { - throw "[$Name] The end comment segment was not found in the file." - } - if ($endIndex -lt $startIndex) { - throw "[$Name] The end comment segment was found before the start comment segment." - } + if ($startIndex -lt 0) { + throw "[$Name] The start comment segment was not found in the file." + } + if ($endIndex -lt 0) { + throw "[$Name] The end comment segment was not found in the file." + } + if ($endIndex -lt $startIndex) { + throw "[$Name] The end comment segment was found before the start comment segment." + } - $updatedContent = $currentContent[0..$startIndex] + $Content + $currentContent[($endIndex)..($currentContent.Length - 1)] - if ($PSCmdlet.ShouldProcess('Readme section', 'Update')) { - Set-Content -Path $Path -Value $updatedContent + $updatedContent = $currentContent[0..$startIndex] + $Content + $currentContent[($endIndex)..($currentContent.Length - 1)] + if ($PSCmdlet.ShouldProcess('Readme section', 'Update')) { + Set-Content -Path $Path -Value $updatedContent + Write-Host "Section [$Name] updated in [$Path]" + } } } @@ -654,96 +663,115 @@ function Update-ModuleList { [object[]] $Repos = @() ) - if ($Repos.Count -eq 0) { - $Repos = Show-RepoList - } - - $moduleCatalogTemplateVersion = 'v2' - $moduleCatalogTemplateFolder = Join-Path (Join-Path $PSScriptRoot '..') 'templates\module-catalog' - $moduleCatalogRowTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-row.html" - $moduleCatalogTableTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-table.html" - $moduleCatalogRowTemplate = Get-TemplateContent -Path $moduleCatalogRowTemplatePath - $moduleCatalogTableTemplate = Get-TemplateContent -Path $moduleCatalogTableTemplatePath + LogGroup 'Prepare module catalog generation' { + if ($Repos.Count -eq 0) { + Write-Host 'No repository list was provided, retrieving repositories now' + $Repos = Show-RepoList + } - $moduleRepos = $Repos | Where-Object { - $_.Type -eq 'Module' -and $_.Owner -eq 'PSModule' - } | Sort-Object Name + $moduleCatalogTemplateVersion = 'v2' + $moduleCatalogTemplateFolder = Join-Path (Join-Path $PSScriptRoot '..') 'templates\module-catalog' + $moduleCatalogRowTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-row.html" + $moduleCatalogTableTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-table.html" + $moduleCatalogRowTemplate = Get-TemplateContent -Path $moduleCatalogRowTemplatePath + $moduleCatalogTableTemplate = Get-TemplateContent -Path $moduleCatalogTableTemplatePath + + $moduleRepos = $Repos | Where-Object { + $_.Type -eq 'Module' -and $_.Owner -eq 'PSModule' + } | Sort-Object Name + Write-Host "Module repositories to process: $($moduleRepos.Count)" + + $catalogFolderPath = Join-Path 'src\docs\Modules\Catalog' 'Repositories' + if (-not (Test-Path $catalogFolderPath)) { + Write-Host "Creating catalog folder [$catalogFolderPath]" + $null = New-Item -Path $catalogFolderPath -ItemType Directory + } - $catalogFolderPath = Join-Path 'src\docs\Modules\Catalog' 'Repositories' - if (-not (Test-Path $catalogFolderPath)) { - $null = New-Item -Path $catalogFolderPath -ItemType Directory + $processLatestVersion = Get-RepositoryVersion -Owner 'PSModule' -Name 'Process-PSModule' + Write-Host "Latest Process-PSModule version: $processLatestVersion" + $moduleTableRows = '' } - $processLatestVersion = Get-RepositoryVersion -Owner 'PSModule' -Name 'Process-PSModule' - $moduleTableRows = '' - + $moduleRepoTotal = $moduleRepos.Count + $moduleRepoIndex = 0 foreach ($repo in $moduleRepos) { + $moduleRepoIndex++ $owner = [string](Get-PropertyValue -InputObject $repo -Names @('Owner') -Default 'PSModule') $name = [string](Get-PropertyValue -InputObject $repo -Names @('Name') -Default '') if ([string]::IsNullOrWhiteSpace($name)) { + Write-Host "Skipping module at index [$moduleRepoIndex] because the repository name is empty" continue } - $description = [string](Get-PropertyValue -InputObject $repo -Names @('Description') -Default 'No description available.') - if ([string]::IsNullOrWhiteSpace($description)) { - $description = 'No description available.' - } + LogGroup "Process module [$moduleRepoIndex/$moduleRepoTotal] [$owner/$name]" { + $description = [string](Get-PropertyValue -InputObject $repo -Names @('Description') -Default 'No description available.') + if ([string]::IsNullOrWhiteSpace($description)) { + $description = 'No description available.' + } - $defaultBranch = [string](Get-PropertyValue -InputObject $repo -Names @('DefaultBranch', 'default_branch') -Default 'main') - if ([string]::IsNullOrWhiteSpace($defaultBranch)) { - $defaultBranch = 'main' - } + $defaultBranch = [string](Get-PropertyValue -InputObject $repo -Names @('DefaultBranch', 'default_branch') -Default 'main') + if ([string]::IsNullOrWhiteSpace($defaultBranch)) { + $defaultBranch = 'main' + } - $readmeContent = Get-RepositoryReadmeContent -Owner $owner -Name $name - $aboutSummary = Get-MarkdownSummary -Markdown $readmeContent - if ([string]::IsNullOrWhiteSpace($aboutSummary)) { - $aboutSummary = $description - } + Write-Host "Collecting metadata from branch [$defaultBranch]" + $readmeContent = Get-RepositoryReadmeContent -Owner $owner -Name $name + $aboutSummary = Get-MarkdownSummary -Markdown $readmeContent + if ([string]::IsNullOrWhiteSpace($aboutSummary)) { + $aboutSummary = $description + } - $titleSummary = ConvertTo-HtmlAttributeValue -Value $aboutSummary - $version = Get-RepositoryVersion -Owner $owner -Name $name - $processReference = Get-WorkflowReference -Owner $owner -Name $name -DefaultBranch $defaultBranch - $processStatus = Get-ProcessReferenceStatus -Reference $processReference -LatestVersion $processLatestVersion - $issues = Get-OpenItemCount -Owner $owner -Name $name -Type issue - $pullRequests = Get-OpenItemCount -Owner $owner -Name $name -Type pr - $stars = [int](Get-PropertyValue -InputObject $repo -Names @('Stars', 'stargazers_count', 'StargazersCount') -Default 0) - - $modulePageFileName = "$name.md" - $modulePagePath = Join-Path $catalogFolderPath $modulePageFileName - $modulePageRelativeLink = "./Repositories/$modulePageFileName" - - $moduleData = [pscustomobject]@{ - Owner = $owner - Name = $name - Description = $description - Version = $version - ProcessReference = $processReference - ProcessStatus = $processStatus - Issues = $issues - PullRequests = $pullRequests - Stars = $stars - About = $aboutSummary + $titleSummary = ConvertTo-HtmlAttributeValue -Value $aboutSummary + $version = Get-RepositoryVersion -Owner $owner -Name $name + $processReference = Get-WorkflowReference -Owner $owner -Name $name -DefaultBranch $defaultBranch + $processStatus = Get-ProcessReferenceStatus -Reference $processReference -LatestVersion $processLatestVersion + $issues = Get-OpenItemCount -Owner $owner -Name $name -Type issue + $pullRequests = Get-OpenItemCount -Owner $owner -Name $name -Type pr + $stars = [int](Get-PropertyValue -InputObject $repo -Names @('Stars', 'stargazers_count', 'StargazersCount') -Default 0) + + Write-Host "Version [$version], Process ref [$processReference], status [$processStatus]" + Write-Host "Open issues [$issues], open PRs [$pullRequests], stars [$stars]" + + $modulePageFileName = "$name.md" + $modulePagePath = Join-Path $catalogFolderPath $modulePageFileName + $modulePageRelativeLink = "./Repositories/$modulePageFileName" + + $moduleData = [pscustomobject]@{ + Owner = $owner + Name = $name + Description = $description + Version = $version + ProcessReference = $processReference + ProcessStatus = $processStatus + Issues = $issues + PullRequests = $pullRequests + Stars = $stars + About = $aboutSummary + } + New-ModuleCatalogPage -Path $modulePagePath -ModuleData $moduleData + Write-Host "Wrote repository page [$modulePagePath]" + + $moduleTableRow = $moduleCatalogRowTemplate + $moduleTableRow = $moduleTableRow.Replace('{{ MODULE_PAGE_LINK }}', $modulePageRelativeLink) + $moduleTableRow = $moduleTableRow.Replace('{{ TITLE_SUMMARY }}', $titleSummary) + $moduleTableRow = $moduleTableRow.Replace('{{ NAME }}', $name) + $moduleTableRow = $moduleTableRow.Replace('{{ VERSION }}', $version) + $moduleTableRow = $moduleTableRow.Replace('{{ PROCESS_REFERENCE }}', $processReference) + $moduleTableRow = $moduleTableRow.Replace('{{ PROCESS_STATUS }}', $processStatus) + $moduleTableRow = $moduleTableRow.Replace('{{ OWNER }}', $owner) + $moduleTableRow = $moduleTableRow.Replace('{{ ISSUES }}', [string]$issues) + $moduleTableRow = $moduleTableRow.Replace('{{ PULL_REQUESTS }}', [string]$pullRequests) + $moduleTableRow = $moduleTableRow.Replace('{{ STARS }}', [string]$stars) + $moduleTableRows += $moduleTableRow.TrimEnd() + $moduleTableRows += [Environment]::NewLine } - New-ModuleCatalogPage -Path $modulePagePath -ModuleData $moduleData - - $moduleTableRow = $moduleCatalogRowTemplate - $moduleTableRow = $moduleTableRow.Replace('{{ MODULE_PAGE_LINK }}', $modulePageRelativeLink) - $moduleTableRow = $moduleTableRow.Replace('{{ TITLE_SUMMARY }}', $titleSummary) - $moduleTableRow = $moduleTableRow.Replace('{{ NAME }}', $name) - $moduleTableRow = $moduleTableRow.Replace('{{ VERSION }}', $version) - $moduleTableRow = $moduleTableRow.Replace('{{ PROCESS_REFERENCE }}', $processReference) - $moduleTableRow = $moduleTableRow.Replace('{{ PROCESS_STATUS }}', $processStatus) - $moduleTableRow = $moduleTableRow.Replace('{{ OWNER }}', $owner) - $moduleTableRow = $moduleTableRow.Replace('{{ ISSUES }}', [string]$issues) - $moduleTableRow = $moduleTableRow.Replace('{{ PULL_REQUESTS }}', [string]$pullRequests) - $moduleTableRow = $moduleTableRow.Replace('{{ STARS }}', [string]$stars) - $moduleTableRows += $moduleTableRow.TrimEnd() - $moduleTableRows += [Environment]::NewLine } - $moduleTable = $moduleCatalogTableTemplate.Replace('{{ ROWS }}', $moduleTableRows.TrimEnd()) - - Update-MDSection -Path '.\src\docs\Modules\Catalog\index.md' -Name 'MODULE_CATALOG' -Content $moduleTable + LogGroup 'Write module catalog table to docs index' { + $moduleTable = $moduleCatalogTableTemplate.Replace('{{ ROWS }}', $moduleTableRows.TrimEnd()) + Update-MDSection -Path '.\src\docs\Modules\Catalog\index.md' -Name 'MODULE_CATALOG' -Content $moduleTable + Write-Host 'Module catalog table update completed' + } } function Update-FunctionAppList { diff --git a/.github/actions/update-index/src/main.ps1 b/.github/actions/update-index/src/main.ps1 index 81fc345..dbcdf25 100644 --- a/.github/actions/update-index/src/main.ps1 +++ b/.github/actions/update-index/src/main.ps1 @@ -1,6 +1,23 @@ Import-Module -Name (Join-Path $PSScriptRoot 'Helper.psm1') -$repos = Show-RepoList -# Update-ActionList -# Update-FunctionAppList -Update-ModuleList -Repos $repos +LogGroup 'Initialize update-index run' { + Write-Host "Starting update-index in [$PSScriptRoot]" +} + +LogGroup 'Collect repositories' { + $repos = Show-RepoList + Write-Host "Repository collection complete: $($repos.Count) records" +} + +LogGroup 'Skipped generators' { + Write-Host 'Update-ActionList is currently disabled in main.ps1' + Write-Host 'Update-FunctionAppList is currently disabled in main.ps1' +} + +LogGroup 'Update module catalog docs' { + Update-ModuleList -Repos $repos +} + +LogGroup 'Finalize update-index run' { + Write-Host 'update-index run completed' +} From d5e6ca742668e431a97b47e8fd42250b40079b8e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:11:00 +0200 Subject: [PATCH 5/9] Fix update-index lint warnings Keep stage logging while moving variable usage out of LogGroup scriptblock scopes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 80 ++++++++++---------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index 26decb1..7fcc154 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -112,25 +112,26 @@ function Update-MDSection { [string] $Content ) - LogGroup "Update markdown section [$Name] in [$Path]" { - $startSegment = "" - $endSegment = "" - $currentContent = Get-Content -Path $Path - $startIndex = $currentContent.IndexOf($startSegment) - $endIndex = $currentContent.IndexOf($endSegment) - - if ($startIndex -lt 0) { - throw "[$Name] The start comment segment was not found in the file." - } - if ($endIndex -lt 0) { - throw "[$Name] The end comment segment was not found in the file." - } - if ($endIndex -lt $startIndex) { - throw "[$Name] The end comment segment was found before the start comment segment." - } + Write-Host "Preparing markdown section update [$Name] in [$Path]" + $startSegment = "" + $endSegment = "" + $currentContent = Get-Content -Path $Path + $startIndex = $currentContent.IndexOf($startSegment) + $endIndex = $currentContent.IndexOf($endSegment) + + if ($startIndex -lt 0) { + throw "[$Name] The start comment segment was not found in the file." + } + if ($endIndex -lt 0) { + throw "[$Name] The end comment segment was not found in the file." + } + if ($endIndex -lt $startIndex) { + throw "[$Name] The end comment segment was found before the start comment segment." + } - $updatedContent = $currentContent[0..$startIndex] + $Content + $currentContent[($endIndex)..($currentContent.Length - 1)] - if ($PSCmdlet.ShouldProcess('Readme section', 'Update')) { + $updatedContent = $currentContent[0..$startIndex] + $Content + $currentContent[($endIndex)..($currentContent.Length - 1)] + if ($PSCmdlet.ShouldProcess('Readme section', 'Update')) { + LogGroup "Update markdown section [$Name] in [$Path]" { Set-Content -Path $Path -Value $updatedContent Write-Host "Section [$Name] updated in [$Path]" } @@ -663,33 +664,34 @@ function Update-ModuleList { [object[]] $Repos = @() ) - LogGroup 'Prepare module catalog generation' { - if ($Repos.Count -eq 0) { + if ($Repos.Count -eq 0) { + LogGroup 'Prepare module catalog generation' { Write-Host 'No repository list was provided, retrieving repositories now' - $Repos = Show-RepoList } + $Repos = Show-RepoList + } - $moduleCatalogTemplateVersion = 'v2' - $moduleCatalogTemplateFolder = Join-Path (Join-Path $PSScriptRoot '..') 'templates\module-catalog' - $moduleCatalogRowTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-row.html" - $moduleCatalogTableTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-table.html" - $moduleCatalogRowTemplate = Get-TemplateContent -Path $moduleCatalogRowTemplatePath - $moduleCatalogTableTemplate = Get-TemplateContent -Path $moduleCatalogTableTemplatePath + $moduleCatalogTemplateVersion = 'v2' + $moduleCatalogTemplateFolder = Join-Path (Join-Path $PSScriptRoot '..') 'templates\module-catalog' + $moduleCatalogRowTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-row.html" + $moduleCatalogTableTemplatePath = Join-Path $moduleCatalogTemplateFolder "$moduleCatalogTemplateVersion-table.html" + $moduleCatalogRowTemplate = Get-TemplateContent -Path $moduleCatalogRowTemplatePath + $moduleCatalogTableTemplate = Get-TemplateContent -Path $moduleCatalogTableTemplatePath + + $moduleRepos = $Repos | Where-Object { + $_.Type -eq 'Module' -and $_.Owner -eq 'PSModule' + } | Sort-Object Name + $catalogFolderPath = Join-Path 'src\docs\Modules\Catalog' 'Repositories' + if (-not (Test-Path $catalogFolderPath)) { + Write-Host "Creating catalog folder [$catalogFolderPath]" + $null = New-Item -Path $catalogFolderPath -ItemType Directory + } - $moduleRepos = $Repos | Where-Object { - $_.Type -eq 'Module' -and $_.Owner -eq 'PSModule' - } | Sort-Object Name + $processLatestVersion = Get-RepositoryVersion -Owner 'PSModule' -Name 'Process-PSModule' + $moduleTableRows = '' + LogGroup 'Prepare module catalog generation' { Write-Host "Module repositories to process: $($moduleRepos.Count)" - - $catalogFolderPath = Join-Path 'src\docs\Modules\Catalog' 'Repositories' - if (-not (Test-Path $catalogFolderPath)) { - Write-Host "Creating catalog folder [$catalogFolderPath]" - $null = New-Item -Path $catalogFolderPath -ItemType Directory - } - - $processLatestVersion = Get-RepositoryVersion -Owner 'PSModule' -Name 'Process-PSModule' Write-Host "Latest Process-PSModule version: $processLatestVersion" - $moduleTableRows = '' } $moduleRepoTotal = $moduleRepos.Count From 42adddf0d3970bf1b925db3482f0bf4b64629040 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:12:55 +0200 Subject: [PATCH 6/9] Improve Process-PSModule reference diagnostics Log workflow paths and the exact uses lines being parsed; support quoted uses values when extracting refs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index 7fcc154..af4cfab 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -474,25 +474,42 @@ function Get-WorkflowReference { ) foreach ($workflowPath in @('.github/workflows/workflow.yml', '.github/workflows/workflow.yaml')) { + Write-Host "Checking workflow path [$workflowPath] for [$Owner/$Name] on [$DefaultBranch]" $encodedRef = [uri]::EscapeDataString($DefaultBranch) $uri = "https://api.github.com/repos/$Owner/$Name/contents/${workflowPath}?ref=$encodedRef" $response = Invoke-GitHubApi -Uri $uri if ($null -eq $response) { + Write-Host "Workflow path not found: [$workflowPath]" continue } $content = Get-PropertyValue -InputObject $response -Names @('content') if ([string]::IsNullOrWhiteSpace([string]$content)) { + Write-Host "Workflow content is empty for path [$workflowPath]" continue } $decoded = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(([string]$content).Replace("`n", '').Replace("`r", ''))) - $match = [regex]::Match($decoded, '(?m)uses:\s*PSModule/Process-PSModule/.+?@(?[^\s#]+)') + $processLinePattern = '(?m)^\s*uses:\s*["'']?PSModule/Process-PSModule/.+$' + $processReferencePattern = '(?m)uses:\s*["'']?PSModule/Process-PSModule/.+?@(?[^"''\s#]+)' + $processLines = [regex]::Matches($decoded, $processLinePattern) + if ($processLines.Count -gt 0) { + foreach ($processLine in $processLines) { + Write-Host "Processing workflow line: $($processLine.Value.Trim())" + } + } else { + Write-Host "No Process-PSModule uses line found in [$workflowPath]" + } + + $match = [regex]::Match($decoded, $processReferencePattern) if ($match.Success) { + Write-Host "Resolved Process-PSModule ref [$($match.Groups['ref'].Value)] from [$workflowPath]" return $match.Groups['ref'].Value } + Write-Host "Unable to parse Process-PSModule ref in [$workflowPath]" } + Write-Host "No Process-PSModule workflow reference found for [$Owner/$Name]" 'N/A' } From 4fa296f013b5e38ef97bcb448a44f61ccfcbd172 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:15:15 +0200 Subject: [PATCH 7/9] Discover workflow files for Process ref Enumerate .github/workflows YAML files, handle single vs multi-file cases, and keep detailed parsing logs including the exact uses line. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 74 ++++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index af4cfab..75789d4 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -473,9 +473,62 @@ function Get-WorkflowReference { [string] $DefaultBranch ) - foreach ($workflowPath in @('.github/workflows/workflow.yml', '.github/workflows/workflow.yaml')) { + $encodedRef = [uri]::EscapeDataString($DefaultBranch) + $workflowFolderPath = '.github/workflows' + $workflowFolderUri = "https://api.github.com/repos/$Owner/$Name/contents/$workflowFolderPath?ref=$encodedRef" + Write-Host "Discovering workflow files under [$workflowFolderPath] for [$Owner/$Name] on [$DefaultBranch]" + $workflowEntries = Invoke-GitHubApi -Uri $workflowFolderUri + if ($null -eq $workflowEntries) { + Write-Host "Workflow folder not found: [$workflowFolderPath]" + return 'N/A' + } + + $workflowFiles = @( + $workflowEntries | + Where-Object { + (Get-PropertyValue -InputObject $_ -Names @('type') -Default '') -eq 'file' -and + [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') -match '\.ya?ml$' + } | + Sort-Object { [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') } + ) + + if ($workflowFiles.Count -eq 0) { + Write-Host 'No workflow .yml/.yaml files found' + return 'N/A' + } + + if ($workflowFiles.Count -eq 1) { + Write-Host "Single workflow file found: [$([string](Get-PropertyValue -InputObject $workflowFiles[0] -Names @('name') -Default 'unknown'))]" + } else { + Write-Host "Multiple workflow files found: $($workflowFiles.Count)" + $workflowFiles | ForEach-Object { + $workflowName = [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default 'unknown') + Write-Host " - $workflowName" + } + } + + $candidateWorkflowFiles = $workflowFiles + if ($workflowFiles.Count -gt 1) { + $preferredFiles = @( + $workflowFiles | Where-Object { + [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') -match '(?i)^process-psmodule\.ya?ml$' + } + ) + if ($preferredFiles.Count -eq 1) { + $preferredFileName = [string](Get-PropertyValue -InputObject $preferredFiles[0] -Names @('name') -Default 'unknown') + Write-Host "Multiple workflows detected; preferring canonical workflow file [$preferredFileName]" + $candidateWorkflowFiles = $preferredFiles + } + } + + $resolvedRefs = @() + foreach ($workflowFile in $candidateWorkflowFiles) { + $workflowPath = [string](Get-PropertyValue -InputObject $workflowFile -Names @('path') -Default '') + if ([string]::IsNullOrWhiteSpace($workflowPath)) { + continue + } + Write-Host "Checking workflow path [$workflowPath] for [$Owner/$Name] on [$DefaultBranch]" - $encodedRef = [uri]::EscapeDataString($DefaultBranch) $uri = "https://api.github.com/repos/$Owner/$Name/contents/${workflowPath}?ref=$encodedRef" $response = Invoke-GitHubApi -Uri $uri if ($null -eq $response) { @@ -503,10 +556,21 @@ function Get-WorkflowReference { $match = [regex]::Match($decoded, $processReferencePattern) if ($match.Success) { - Write-Host "Resolved Process-PSModule ref [$($match.Groups['ref'].Value)] from [$workflowPath]" - return $match.Groups['ref'].Value + $resolvedRef = $match.Groups['ref'].Value + Write-Host "Resolved Process-PSModule ref [$resolvedRef] from [$workflowPath]" + $resolvedRefs += $resolvedRef + } else { + Write-Host "Unable to parse Process-PSModule ref in [$workflowPath]" } - Write-Host "Unable to parse Process-PSModule ref in [$workflowPath]" + } + + $uniqueResolvedRefs = @($resolvedRefs | Select-Object -Unique) + if ($uniqueResolvedRefs.Count -eq 1) { + return [string]$uniqueResolvedRefs[0] + } + if ($uniqueResolvedRefs.Count -gt 1) { + Write-Host "Multiple Process-PSModule refs resolved for [$Owner/$Name]: $($uniqueResolvedRefs -join ', ')" + return 'N/A' } Write-Host "No Process-PSModule workflow reference found for [$Owner/$Name]" From d6193e8978fcd0cbb35819adb6622e015eeca7d7 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:19:00 +0200 Subject: [PATCH 8/9] Add workflow resolution diagnostics Log discovery strategy, listing shape, canonical fallback hits, and final resolution outcome for Process-PSModule ref detection. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 92 +++++++++++++++----- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index 75789d4..02a6fef 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -209,7 +209,9 @@ function Invoke-GitHubApi { [CmdletBinding()] param( [Parameter(Mandatory)] - [string] $Uri + [string] $Uri, + [Parameter()] + [switch] $Anonymous ) try { @@ -219,9 +221,13 @@ function Invoke-GitHubApi { ErrorAction = 'Stop' } - $availableContexts = Get-GitHubContext -ListAvailable -ErrorAction SilentlyContinue - if ($null -eq $availableContexts -or $availableContexts.Count -eq 0) { + if ($Anonymous) { $apiParameters.Anonymous = $true + } else { + $availableContexts = Get-GitHubContext -ListAvailable -ErrorAction SilentlyContinue + if ($null -eq $availableContexts -or $availableContexts.Count -eq 0) { + $apiParameters.Anonymous = $true + } } $rawResponse = GitHub\Invoke-GitHubAPI @apiParameters @@ -477,24 +483,49 @@ function Get-WorkflowReference { $workflowFolderPath = '.github/workflows' $workflowFolderUri = "https://api.github.com/repos/$Owner/$Name/contents/$workflowFolderPath?ref=$encodedRef" Write-Host "Discovering workflow files under [$workflowFolderPath] for [$Owner/$Name] on [$DefaultBranch]" + $workflowDiscoveryStrategy = 'folder-listing' $workflowEntries = Invoke-GitHubApi -Uri $workflowFolderUri if ($null -eq $workflowEntries) { - Write-Host "Workflow folder not found: [$workflowFolderPath]" - return 'N/A' + Write-Host "Workflow folder lookup failed with current auth; retrying anonymously for [$workflowFolderPath]" + $workflowEntries = Invoke-GitHubApi -Uri $workflowFolderUri -Anonymous + } + + $workflowFiles = @() + $prefetchedWorkflowResponses = @{} + if ($null -ne $workflowEntries) { + $workflowEntryItems = @($workflowEntries) + Write-Host "Workflow folder listing returned [$($workflowEntryItems.Count)] item(s)" + $workflowFiles = @( + $workflowEntryItems | + Where-Object { + (Get-PropertyValue -InputObject $_ -Names @('type') -Default '') -eq 'file' -and + [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') -match '\.ya?ml$' + } | + Sort-Object { [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') } + ) } - $workflowFiles = @( - $workflowEntries | - Where-Object { - (Get-PropertyValue -InputObject $_ -Names @('type') -Default '') -eq 'file' -and - [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') -match '\.ya?ml$' - } | - Sort-Object { [string](Get-PropertyValue -InputObject $_ -Names @('name') -Default '') } - ) - if ($workflowFiles.Count -eq 0) { - Write-Host 'No workflow .yml/.yaml files found' - return 'N/A' + $workflowDiscoveryStrategy = 'canonical-fallback' + Write-Host "Workflow folder listing unavailable or empty; trying canonical workflow files directly" + foreach ($canonicalWorkflowPath in @('.github/workflows/Process-PSModule.yml', '.github/workflows/Process-PSModule.yaml')) { + $canonicalWorkflowUri = "https://api.github.com/repos/$Owner/$Name/contents/${canonicalWorkflowPath}?ref=$encodedRef" + $canonicalWorkflowResponse = Invoke-GitHubApi -Uri $canonicalWorkflowUri + if ($null -eq $canonicalWorkflowResponse) { + $canonicalWorkflowResponse = Invoke-GitHubApi -Uri $canonicalWorkflowUri -Anonymous + } + if ($null -eq $canonicalWorkflowResponse) { + continue + } + + Write-Host "Canonical workflow candidate found: [$canonicalWorkflowPath]" + $prefetchedWorkflowResponses[$canonicalWorkflowPath] = $canonicalWorkflowResponse + $workflowFiles += [pscustomobject]@{ + name = [IO.Path]::GetFileName($canonicalWorkflowPath) + path = $canonicalWorkflowPath + type = 'file' + } + } } if ($workflowFiles.Count -eq 1) { @@ -506,6 +537,7 @@ function Get-WorkflowReference { Write-Host " - $workflowName" } } + Write-Host "Workflow discovery strategy used: [$workflowDiscoveryStrategy]" $candidateWorkflowFiles = $workflowFiles if ($workflowFiles.Count -gt 1) { @@ -522,6 +554,7 @@ function Get-WorkflowReference { } $resolvedRefs = @() + $foundWorkflowFile = $false foreach ($workflowFile in $candidateWorkflowFiles) { $workflowPath = [string](Get-PropertyValue -InputObject $workflowFile -Names @('path') -Default '') if ([string]::IsNullOrWhiteSpace($workflowPath)) { @@ -529,12 +562,21 @@ function Get-WorkflowReference { } Write-Host "Checking workflow path [$workflowPath] for [$Owner/$Name] on [$DefaultBranch]" - $uri = "https://api.github.com/repos/$Owner/$Name/contents/${workflowPath}?ref=$encodedRef" - $response = Invoke-GitHubApi -Uri $uri - if ($null -eq $response) { - Write-Host "Workflow path not found: [$workflowPath]" - continue + if ($prefetchedWorkflowResponses.ContainsKey($workflowPath)) { + $response = $prefetchedWorkflowResponses[$workflowPath] + } else { + $uri = "https://api.github.com/repos/$Owner/$Name/contents/${workflowPath}?ref=$encodedRef" + $response = Invoke-GitHubApi -Uri $uri + if ($null -eq $response) { + Write-Host "Workflow path lookup failed with current auth; retrying anonymously for [$workflowPath]" + $response = Invoke-GitHubApi -Uri $uri -Anonymous + if ($null -eq $response) { + Write-Host "Workflow path not found: [$workflowPath]" + continue + } + } } + $foundWorkflowFile = $true $content = Get-PropertyValue -InputObject $response -Names @('content') if ([string]::IsNullOrWhiteSpace([string]$content)) { @@ -564,16 +606,24 @@ function Get-WorkflowReference { } } + if (-not $foundWorkflowFile) { + Write-Host "No workflow files could be fetched for [$Owner/$Name]" + return 'N/A' + } + $uniqueResolvedRefs = @($resolvedRefs | Select-Object -Unique) if ($uniqueResolvedRefs.Count -eq 1) { + Write-Host "Workflow reference resolution succeeded using strategy [$workflowDiscoveryStrategy]" return [string]$uniqueResolvedRefs[0] } if ($uniqueResolvedRefs.Count -gt 1) { Write-Host "Multiple Process-PSModule refs resolved for [$Owner/$Name]: $($uniqueResolvedRefs -join ', ')" + Write-Host "Workflow reference resolution failed using strategy [$workflowDiscoveryStrategy]" return 'N/A' } Write-Host "No Process-PSModule workflow reference found for [$Owner/$Name]" + Write-Host "Workflow reference resolution failed using strategy [$workflowDiscoveryStrategy]" 'N/A' } From 993f72005fac76f22a3eb59afed739a69e560685 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 19 Jul 2026 22:31:44 +0200 Subject: [PATCH 9/9] Use default app context for API calls Connect with Connect-GitHubApp -Default in repo discovery flow and stop auto-forcing anonymous requests in Invoke-GitHubApi. Also fix workflow folder URI interpolation for reliable workflow listing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/update-index/src/Helper.psm1 | 51 ++++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/.github/actions/update-index/src/Helper.psm1 b/.github/actions/update-index/src/Helper.psm1 index 02a6fef..481d3b1 100644 --- a/.github/actions/update-index/src/Helper.psm1 +++ b/.github/actions/update-index/src/Helper.psm1 @@ -7,6 +7,35 @@ [CmdletBinding()] param() +function Connect-GitHubAppDefaultContext { + <# + .SYNOPSIS + Ensures a default GitHub App context is active. + + .DESCRIPTION + Uses Connect-GitHubApp with -Default so subsequent GitHub module calls + use an authenticated default context instead of anonymous access. + #> + [CmdletBinding()] + param( + [Parameter()] + [string] $Owner = $env:GITHUB_REPOSITORY_OWNER + ) + + if ([string]::IsNullOrWhiteSpace($Owner)) { + throw 'Owner is required to establish a default GitHub App context.' + } + + $currentContext = Get-GitHubContext -ErrorAction SilentlyContinue + if ($null -ne $currentContext -and $currentContext.AuthType -eq 'App' -and $currentContext.Name -eq $Owner) { + Write-Host "Using existing default GitHub App context [$($currentContext.Name)]" + return + } + + Write-Host "Connecting GitHub App context as default for organization [$Owner]" + Connect-GitHubApp -Organization $Owner -Default +} + function Show-RepoList { <# .SYNOPSIS @@ -34,14 +63,7 @@ function Show-RepoList { ) LogGroup "Connect to organization [$Owner]" { - $currentContext = Get-GitHubContext -ErrorAction SilentlyContinue - if ($null -eq $currentContext) { - Connect-GitHubApp -Organization $Owner -Default - } elseif ($currentContext.AuthType -eq 'App') { - Connect-GitHubApp -Organization $Owner -Default - } else { - Write-Host "Using existing GitHub context [$($currentContext.Name)] with auth type [$($currentContext.AuthType)]" - } + Connect-GitHubAppDefaultContext -Owner $Owner Get-GitHubContext | Select-Object * | Format-List | Out-String } @@ -202,8 +224,9 @@ function Invoke-GitHubApi { Invokes a GitHub REST API GET request. .DESCRIPTION - Calls the GitHub module API wrapper, auto-selects anonymous mode when no context - is configured, normalizes wrapped responses, and treats HTTP 404 as missing data. + Calls the GitHub module API wrapper, ensures a default GitHub App context + for authenticated requests (unless -Anonymous is used), normalizes wrapped + responses, and treats HTTP 404 as missing data. #> [OutputType([object])] [CmdletBinding()] @@ -222,12 +245,8 @@ function Invoke-GitHubApi { } if ($Anonymous) { + Write-Host "Invoking GitHub API anonymously for [$Uri]" $apiParameters.Anonymous = $true - } else { - $availableContexts = Get-GitHubContext -ListAvailable -ErrorAction SilentlyContinue - if ($null -eq $availableContexts -or $availableContexts.Count -eq 0) { - $apiParameters.Anonymous = $true - } } $rawResponse = GitHub\Invoke-GitHubAPI @apiParameters @@ -481,7 +500,7 @@ function Get-WorkflowReference { $encodedRef = [uri]::EscapeDataString($DefaultBranch) $workflowFolderPath = '.github/workflows' - $workflowFolderUri = "https://api.github.com/repos/$Owner/$Name/contents/$workflowFolderPath?ref=$encodedRef" + $workflowFolderUri = "https://api.github.com/repos/$Owner/$Name/contents/${workflowFolderPath}?ref=$encodedRef" Write-Host "Discovering workflow files under [$workflowFolderPath] for [$Owner/$Name] on [$DefaultBranch]" $workflowDiscoveryStrategy = 'folder-listing' $workflowEntries = Invoke-GitHubApi -Uri $workflowFolderUri