From 73081a2b82e0335c4189120825758648c0fcfe8c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:10:27 +0200 Subject: [PATCH 01/13] Make class and enum loader dependency aware Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/helpers/Build/Add-ContentFromItem.ps1 | 139 ++++++++++++++++-- .../Build/Build-PSModuleRootModule.ps1 | 37 +++-- .../src/classes/public/A-BookList.ps1 | 86 +++++++++++ tests/srcTestRepo/src/classes/public/Book.ps1 | 87 ----------- .../src/classes/public/A-BookList.ps1 | 86 +++++++++++ .../src/classes/public/Book.ps1 | 87 ----------- 6 files changed, 312 insertions(+), 210 deletions(-) create mode 100644 tests/srcTestRepo/src/classes/public/A-BookList.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/A-BookList.ps1 diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 index 9046b204..1b4e0c8d 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 @@ -1,4 +1,103 @@ -function Add-ContentFromItem { +function Get-DependencyOrderedScriptFiles { + [OutputType([System.IO.FileInfo[]])] + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.IO.FileInfo[]] $Files + ) + + $sortedFiles = $Files | Sort-Object -Property FullName + if ($sortedFiles.Count -le 1) { + return $sortedFiles + } + + $metadataByPath = @{} + $typeToPath = @{} + $duplicateTypes = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + + foreach ($file in $sortedFiles) { + $content = Get-Content -Path $file.FullName -Raw + $declaredTypes = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + [Regex]::Matches($content, '(?im)^\s*(class|enum)\s+([^\s:{]+)') | ForEach-Object { + [void] $declaredTypes.Add($_.Groups[2].Value) + } + + $metadataByPath[$file.FullName] = [pscustomobject]@{ + File = $file + Content = $content + DeclaredTypes = $declaredTypes + } + + foreach ($typeName in $declaredTypes) { + if ($typeToPath.ContainsKey($typeName)) { + [void]$duplicateTypes.Add($typeName) + continue + } + + $typeToPath[$typeName] = $file.FullName + } + } + + $dependenciesByPath = @{} + $dependentsByPath = @{} + foreach ($file in $sortedFiles) { + $dependenciesByPath[$file.FullName] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + $dependentsByPath[$file.FullName] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + } + + foreach ($metadata in $metadataByPath.Values) { + foreach ($typeName in $typeToPath.Keys) { + if ($duplicateTypes.Contains($typeName)) { + continue + } + + if ($metadata.DeclaredTypes.Contains($typeName)) { + continue + } + + $typePattern = "(? Date: Sat, 18 Jul 2026 20:45:07 +0200 Subject: [PATCH 02/13] Extract dependency ordering helper script Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/helpers/Build/Add-ContentFromItem.ps1 | 99 ------------------- .../Get-DependencyOrderedScriptFiles.ps1 | 98 ++++++++++++++++++ 2 files changed, 98 insertions(+), 99 deletions(-) create mode 100644 .github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 index 1b4e0c8d..c3aa61bb 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 @@ -1,102 +1,3 @@ -function Get-DependencyOrderedScriptFiles { - [OutputType([System.IO.FileInfo[]])] - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [System.IO.FileInfo[]] $Files - ) - - $sortedFiles = $Files | Sort-Object -Property FullName - if ($sortedFiles.Count -le 1) { - return $sortedFiles - } - - $metadataByPath = @{} - $typeToPath = @{} - $duplicateTypes = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - - foreach ($file in $sortedFiles) { - $content = Get-Content -Path $file.FullName -Raw - $declaredTypes = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - [Regex]::Matches($content, '(?im)^\s*(class|enum)\s+([^\s:{]+)') | ForEach-Object { - [void] $declaredTypes.Add($_.Groups[2].Value) - } - - $metadataByPath[$file.FullName] = [pscustomobject]@{ - File = $file - Content = $content - DeclaredTypes = $declaredTypes - } - - foreach ($typeName in $declaredTypes) { - if ($typeToPath.ContainsKey($typeName)) { - [void]$duplicateTypes.Add($typeName) - continue - } - - $typeToPath[$typeName] = $file.FullName - } - } - - $dependenciesByPath = @{} - $dependentsByPath = @{} - foreach ($file in $sortedFiles) { - $dependenciesByPath[$file.FullName] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - $dependentsByPath[$file.FullName] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - } - - foreach ($metadata in $metadataByPath.Values) { - foreach ($typeName in $typeToPath.Keys) { - if ($duplicateTypes.Contains($typeName)) { - continue - } - - if ($metadata.DeclaredTypes.Contains($typeName)) { - continue - } - - $typePattern = "(? Date: Sat, 18 Jul 2026 20:49:30 +0200 Subject: [PATCH 03/13] Trigger workflow tests for action changes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Workflow-Test-Default.yml | 2 ++ .github/workflows/Workflow-Test-WithManifest.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/Workflow-Test-Default.yml b/.github/workflows/Workflow-Test-Default.yml index e5b012e5..511b4b1c 100644 --- a/.github/workflows/Workflow-Test-Default.yml +++ b/.github/workflows/Workflow-Test-Default.yml @@ -6,6 +6,7 @@ on: workflow_dispatch: pull_request: paths: + - '.github/actions/**' - '.github/workflows/**' - '!.github/workflows/Release.yml' - '!.github/workflows/Linter.yml' @@ -43,4 +44,5 @@ jobs: ImportantFilePatterns: | ^src/ ^README\.md$ + ^\.github/actions/ ^\.github/workflows/(?!Release\.yml$|Linter\.yml$) diff --git a/.github/workflows/Workflow-Test-WithManifest.yml b/.github/workflows/Workflow-Test-WithManifest.yml index 4b179d9a..82f80f55 100644 --- a/.github/workflows/Workflow-Test-WithManifest.yml +++ b/.github/workflows/Workflow-Test-WithManifest.yml @@ -6,6 +6,7 @@ on: workflow_dispatch: pull_request: paths: + - '.github/actions/**' - '.github/workflows/**' - '!.github/workflows/Release.yml' - '!.github/workflows/Linter.yml' @@ -43,4 +44,5 @@ jobs: ImportantFilePatterns: | ^src/ ^README\.md$ + ^\.github/actions/ ^\.github/workflows/(?!Release\.yml$|Linter\.yml$) From 94c68b98c2e2a5a0ef4e28e598857e9566977cc6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:50:19 +0200 Subject: [PATCH 04/13] Run release workflow for action changes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index a845c2a4..1e346454 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -13,6 +13,7 @@ on: - synchronize - labeled paths: + - '.github/actions/**' - '.github/workflows/**' - '!.github/workflows/Release.yml' - '!.github/workflows/Linter.yml' From 676c55c539fc477c251eb3ae5446a12925521cf6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:50:59 +0200 Subject: [PATCH 05/13] Treat action files as important by default Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Plan.yml | 3 ++- .github/workflows/workflow.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index 442e14fe..d349b1c1 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -44,11 +44,12 @@ on: description: | Newline-separated list of regex patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. - When set, fully replaces the defaults (^src/ and ^README\.md$). + When set, fully replaces the defaults (^src/, ^README\.md$, and ^\.github/actions/). required: false default: | ^src/ ^README\.md$ + ^\.github/actions/ outputs: Settings: diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1b9839a2..b0a6ed46 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -56,11 +56,12 @@ on: description: | Newline-separated list of regex patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. - When set, fully replaces the defaults (^src/ and ^README\.md$). + When set, fully replaces the defaults (^src/, ^README\.md$, and ^\.github/actions/). required: false default: | ^src/ ^README\.md$ + ^\.github/actions/ permissions: contents: write # to checkout the repo and create releases on the repo From eb844428da7a472456f12fea2c47a234e1673227 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:52:01 +0200 Subject: [PATCH 06/13] Keep default important patterns consumer-focused Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Plan.yml | 3 +-- .github/workflows/workflow.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Plan.yml b/.github/workflows/Plan.yml index d349b1c1..442e14fe 100644 --- a/.github/workflows/Plan.yml +++ b/.github/workflows/Plan.yml @@ -44,12 +44,11 @@ on: description: | Newline-separated list of regex patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. - When set, fully replaces the defaults (^src/, ^README\.md$, and ^\.github/actions/). + When set, fully replaces the defaults (^src/ and ^README\.md$). required: false default: | ^src/ ^README\.md$ - ^\.github/actions/ outputs: Settings: diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index b0a6ed46..1b9839a2 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -56,12 +56,11 @@ on: description: | Newline-separated list of regex patterns that identify important files. Changes matching these patterns trigger build, test, and publish stages. - When set, fully replaces the defaults (^src/, ^README\.md$, and ^\.github/actions/). + When set, fully replaces the defaults (^src/ and ^README\.md$). required: false default: | ^src/ ^README\.md$ - ^\.github/actions/ permissions: contents: write # to checkout the repo and create releases on the repo From 617de7406816c0446f6c3bfc8baf3ee210e7a6cd Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 20:57:45 +0200 Subject: [PATCH 07/13] Expand class dependency fixtures for loader stress Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Get-DependencyOrderedScriptFiles.ps1 | 2 +- .../src/classes/public/A-BookList.ps1 | 86 ------------------- .../src/classes/public/ActiveLoanRegister.ps1 | 11 +++ .../src/classes/public/AuthorIndex.ps1 | 11 +++ .../src/classes/public/AuthorProfile.ps1 | 9 ++ .../src/classes/public/BookCopy.ps1 | 11 +++ .../src/classes/public/BookInventory.ps1 | 11 +++ .../src/classes/public/BookList.ps1 | 57 ++++++++++++ .../src/classes/public/BookLoan.ps1 | 13 +++ .../src/classes/public/BranchLibrary.ps1 | 11 +++ .../src/classes/public/CatalogSection.ps1 | 9 ++ .../src/classes/public/DueDatePolicy.ps1 | 9 ++ .../src/classes/public/LibraryReport.ps1 | 18 ++++ .../src/classes/public/MemberAccount.ps1 | 11 +++ .../src/classes/public/OpeningHours.ps1 | 9 ++ .../src/classes/public/ReadingList.ps1 | 7 ++ .../src/classes/public/ShelfLocation.ps1 | 11 +++ .../src/classes/public/ZCatalog.ps1 | 11 +++ .../src/classes/public/A-BookList.ps1 | 86 ------------------- .../src/classes/public/ActiveLoanRegister.ps1 | 11 +++ .../src/classes/public/AuthorIndex.ps1 | 11 +++ .../src/classes/public/AuthorProfile.ps1 | 9 ++ .../src/classes/public/BookCopy.ps1 | 11 +++ .../src/classes/public/BookInventory.ps1 | 11 +++ .../src/classes/public/BookList.ps1 | 57 ++++++++++++ .../src/classes/public/BookLoan.ps1 | 13 +++ .../src/classes/public/BranchLibrary.ps1 | 11 +++ .../src/classes/public/CatalogSection.ps1 | 9 ++ .../src/classes/public/DueDatePolicy.ps1 | 9 ++ .../src/classes/public/LibraryReport.ps1 | 18 ++++ .../src/classes/public/MemberAccount.ps1 | 11 +++ .../src/classes/public/OpeningHours.ps1 | 9 ++ .../src/classes/public/ReadingList.ps1 | 7 ++ .../src/classes/public/ShelfLocation.ps1 | 11 +++ .../src/classes/public/ZCatalog.ps1 | 11 +++ 35 files changed, 439 insertions(+), 173 deletions(-) delete mode 100644 tests/srcTestRepo/src/classes/public/A-BookList.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/ActiveLoanRegister.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/AuthorIndex.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/AuthorProfile.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/BookCopy.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/BookInventory.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/BookList.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/BookLoan.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/BranchLibrary.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/CatalogSection.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/DueDatePolicy.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/LibraryReport.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/MemberAccount.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/OpeningHours.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/ReadingList.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/ShelfLocation.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/ZCatalog.ps1 delete mode 100644 tests/srcWithManifestTestRepo/src/classes/public/A-BookList.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/ActiveLoanRegister.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/AuthorIndex.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/AuthorProfile.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/BookCopy.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/BookInventory.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/BookList.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/BookLoan.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/BranchLibrary.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/CatalogSection.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/DueDatePolicy.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/LibraryReport.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/MemberAccount.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/OpeningHours.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/ReadingList.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/ShelfLocation.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/ZCatalog.ps1 diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 index 45bc1f6e..6f2e7ac4 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 @@ -55,7 +55,7 @@ function Get-DependencyOrderedScriptFiles { continue } - $typePattern = "(? Date: Sat, 18 Jul 2026 21:14:49 +0200 Subject: [PATCH 08/13] Warn explicitly on cyclical class dependencies Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../helpers/Build/Get-DependencyOrderedScriptFiles.ps1 | 3 ++- .../src/classes/public/AlphaCirculationNode.ps1 | 9 +++++++++ .../src/classes/public/ZetaCirculationNode.ps1 | 9 +++++++++ .../src/classes/public/AlphaCirculationNode.ps1 | 9 +++++++++ .../src/classes/public/ZetaCirculationNode.ps1 | 9 +++++++++ 5 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/srcTestRepo/src/classes/public/AlphaCirculationNode.ps1 create mode 100644 tests/srcTestRepo/src/classes/public/ZetaCirculationNode.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/AlphaCirculationNode.ps1 create mode 100644 tests/srcWithManifestTestRepo/src/classes/public/ZetaCirculationNode.ps1 diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 index 6f2e7ac4..8fcddb3c 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 @@ -88,7 +88,8 @@ function Get-DependencyOrderedScriptFiles { } if ($orderedPaths.Count -lt $sortedFiles.Count) { - Write-Warning "Detected cyclical or unresolved class/enum dependencies. Falling back to lexical order for remaining files in [$($sortedFiles[0].DirectoryName)]." + $remainingNames = ($remainingPaths | ForEach-Object { [System.IO.Path]::GetFileName($_) }) -join ', ' + Write-Warning "Cyclical class/enum dependencies detected in [$($sortedFiles[0].DirectoryName)]. Falling back to lexical order for unresolved files: $remainingNames" $remainingPaths | Sort-Object | ForEach-Object { $orderedPaths.Add($_) } diff --git a/tests/srcTestRepo/src/classes/public/AlphaCirculationNode.ps1 b/tests/srcTestRepo/src/classes/public/AlphaCirculationNode.ps1 new file mode 100644 index 00000000..d7e10b5e --- /dev/null +++ b/tests/srcTestRepo/src/classes/public/AlphaCirculationNode.ps1 @@ -0,0 +1,9 @@ +class AlphaCirculationNode { + [string] $Id + [ZetaCirculationNode] $Next + + AlphaCirculationNode([string]$Id, [ZetaCirculationNode]$Next) { + $this.Id = $Id + $this.Next = $Next + } +} diff --git a/tests/srcTestRepo/src/classes/public/ZetaCirculationNode.ps1 b/tests/srcTestRepo/src/classes/public/ZetaCirculationNode.ps1 new file mode 100644 index 00000000..5ecd524e --- /dev/null +++ b/tests/srcTestRepo/src/classes/public/ZetaCirculationNode.ps1 @@ -0,0 +1,9 @@ +class ZetaCirculationNode { + [string] $Id + [AlphaCirculationNode] $Previous + + ZetaCirculationNode([string]$Id, [AlphaCirculationNode]$Previous) { + $this.Id = $Id + $this.Previous = $Previous + } +} diff --git a/tests/srcWithManifestTestRepo/src/classes/public/AlphaCirculationNode.ps1 b/tests/srcWithManifestTestRepo/src/classes/public/AlphaCirculationNode.ps1 new file mode 100644 index 00000000..d7e10b5e --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/classes/public/AlphaCirculationNode.ps1 @@ -0,0 +1,9 @@ +class AlphaCirculationNode { + [string] $Id + [ZetaCirculationNode] $Next + + AlphaCirculationNode([string]$Id, [ZetaCirculationNode]$Next) { + $this.Id = $Id + $this.Next = $Next + } +} diff --git a/tests/srcWithManifestTestRepo/src/classes/public/ZetaCirculationNode.ps1 b/tests/srcWithManifestTestRepo/src/classes/public/ZetaCirculationNode.ps1 new file mode 100644 index 00000000..5ecd524e --- /dev/null +++ b/tests/srcWithManifestTestRepo/src/classes/public/ZetaCirculationNode.ps1 @@ -0,0 +1,9 @@ +class ZetaCirculationNode { + [string] $Id + [AlphaCirculationNode] $Previous + + ZetaCirculationNode([string]$Id, [AlphaCirculationNode]$Previous) { + $this.Id = $Id + $this.Previous = $Previous + } +} From 8d85266df491157a4591fb25363e23de93263885 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 21:26:09 +0200 Subject: [PATCH 09/13] Fail build on cyclical class dependencies Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/helpers/Build/Add-ContentFromItem.ps1 | 2 +- .../Build/Build-PSModuleRootModule.ps1 | 8 ++++- ...s1 => Get-DependencyOrderedScriptFile.ps1} | 32 +++++++++++++------ 3 files changed, 31 insertions(+), 11 deletions(-) rename .github/actions/Build-PSModule/src/helpers/Build/{Get-DependencyOrderedScriptFiles.ps1 => Get-DependencyOrderedScriptFile.ps1} (75%) diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 index c3aa61bb..2e1c0b54 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 @@ -39,7 +39,7 @@ Write-Debug "[`$scriptName] - $relativeFolderPath - Processing folder" if ($DependencyAware) { $files = $Path | Get-ChildItem -Recurse -File -Force -Filter '*.ps1' | Sort-Object -Property FullName - $files = Get-DependencyOrderedScriptFiles -Files $files + $files = Get-DependencyOrderedScriptFile -Files $files } else { $files = $Path | Get-ChildItem -File -Force -Filter '*.ps1' | Sort-Object -Property FullName } diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 index 4fa36797..f31667e3 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 @@ -187,7 +187,13 @@ Write-Debug "[$scriptName] - [data] - Done" if (-not (Test-Path -Path $scriptFolderPath)) { continue } - Add-ContentFromItem -Path $scriptFolderPath -RootModuleFilePath $rootModuleFile -RootPath $ModuleOutputFolder -DependencyAware:$dependencyAware + $addContentParams = @{ + Path = $scriptFolderPath + RootModuleFilePath = $rootModuleFile + RootPath = $ModuleOutputFolder + DependencyAware = $dependencyAware + } + Add-ContentFromItem @addContentParams Remove-Item -Path $scriptFolderPath -Force -Recurse } #endregion - Add content from subfolders diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 similarity index 75% rename from .github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 rename to .github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 index 8fcddb3c..c3774e24 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFiles.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 @@ -1,4 +1,16 @@ -function Get-DependencyOrderedScriptFiles { +function Get-DependencyOrderedScriptFile { + <# + .SYNOPSIS + Sorts script files so class/enum dependencies are loaded before dependents. + + .DESCRIPTION + Reads declared class/enum types and type references from each script and returns + files in dependency order. When a cyclical dependency is detected, the function + warns and throws to fail the build early. + + .EXAMPLE + Get-DependencyOrderedScriptFile -Files (Get-ChildItem -Path '.\classes' -Filter '*.ps1') + #> [OutputType([System.IO.FileInfo[]])] [CmdletBinding()] param( @@ -6,7 +18,7 @@ function Get-DependencyOrderedScriptFiles { [System.IO.FileInfo[]] $Files ) - $sortedFiles = $Files | Sort-Object -Property FullName + [System.IO.FileInfo[]]$sortedFiles = $Files | Sort-Object -Property FullName if ($sortedFiles.Count -le 1) { return $sortedFiles } @@ -67,8 +79,8 @@ function Get-DependencyOrderedScriptFiles { $remainingPaths = @($sortedFiles.FullName) $ready = @( $remainingPaths | - Where-Object { $dependenciesByPath[$_].Count -eq 0 } | - Sort-Object + Where-Object { $dependenciesByPath[$_].Count -eq 0 } | + Sort-Object ) $orderedPaths = [System.Collections.Generic.List[string]]::new() @@ -89,11 +101,13 @@ function Get-DependencyOrderedScriptFiles { if ($orderedPaths.Count -lt $sortedFiles.Count) { $remainingNames = ($remainingPaths | ForEach-Object { [System.IO.Path]::GetFileName($_) }) -join ', ' - Write-Warning "Cyclical class/enum dependencies detected in [$($sortedFiles[0].DirectoryName)]. Falling back to lexical order for unresolved files: $remainingNames" - $remainingPaths | Sort-Object | ForEach-Object { - $orderedPaths.Add($_) - } + $message = @( + "Cyclical class/enum dependencies detected in [$($sortedFiles[0].DirectoryName)]." + "Build cannot continue with unresolved files: $remainingNames" + ) -join ' ' + Write-Warning $message + throw $message } - return @($orderedPaths | ForEach-Object { $metadataByPath[$_].File }) + return [System.IO.FileInfo[]]@($orderedPaths | ForEach-Object { $metadataByPath[$_].File }) } From 53fc898197031b3ee2534702e7f70d9a7f655ede Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 21:42:40 +0200 Subject: [PATCH 10/13] Keep cycle detection as warning fallback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 index c3774e24..13adb811 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 @@ -103,10 +103,12 @@ function Get-DependencyOrderedScriptFile { $remainingNames = ($remainingPaths | ForEach-Object { [System.IO.Path]::GetFileName($_) }) -join ', ' $message = @( "Cyclical class/enum dependencies detected in [$($sortedFiles[0].DirectoryName)]." - "Build cannot continue with unresolved files: $remainingNames" + "Falling back to lexical order for unresolved files: $remainingNames" ) -join ' ' Write-Warning $message - throw $message + $remainingPaths | Sort-Object | ForEach-Object { + $orderedPaths.Add($_) + } } return [System.IO.FileInfo[]]@($orderedPaths | ForEach-Object { $metadataByPath[$_].File }) From 8c099789938f60d4cce1aacf8b0377791b7b0751 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 21:43:48 +0200 Subject: [PATCH 11/13] Exclude class fixture files from repo linter Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 2c5664a2..e57c709c 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -28,6 +28,7 @@ jobs: uses: super-linter/super-linter@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0 env: GITHUB_TOKEN: ${{ github.token }} + FILTER_REGEX_EXCLUDE: 'tests/src(TestRepo|WithManifestTestRepo)/src/classes/public/.*\.ps1$' VALIDATE_BIOME_FORMAT: false VALIDATE_GITHUB_ACTIONS: false VALIDATE_JSCPD: false From edc5b8c31afc199235c8f7447ef216b454ce1703 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 21:46:25 +0200 Subject: [PATCH 12/13] Scope repo-linter excludes to working directory Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/Lint-Repository.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Lint-Repository.yml b/.github/workflows/Lint-Repository.yml index 02c77590..52822090 100644 --- a/.github/workflows/Lint-Repository.yml +++ b/.github/workflows/Lint-Repository.yml @@ -54,6 +54,7 @@ jobs: GITHUB_TOKEN: ${{ github.token }} DEFAULT_WORKSPACE: ${{ fromJson(env.Settings).WorkingDirectory }} FILTER_REGEX_INCLUDE: ${{ fromJson(env.Settings).WorkingDirectory }} + FILTER_REGEX_EXCLUDE: ${{ fromJson(env.Settings).WorkingDirectory }}/src/classes/public/.*\.ps1$ ENABLE_GITHUB_ACTIONS_STEP_SUMMARY: false SAVE_SUPER_LINTER_SUMMARY: true From 16be1e4ebd76683bb8405fc8093d96e2e2b66454 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 18 Jul 2026 21:48:25 +0200 Subject: [PATCH 13/13] Rollback class sorting in build pipeline Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/helpers/Build/Add-ContentFromItem.ps1 | 21 +--- .../Build/Build-PSModuleRootModule.ps1 | 27 ++-- .../Build/Get-DependencyOrderedScriptFile.ps1 | 115 ------------------ 3 files changed, 16 insertions(+), 147 deletions(-) delete mode 100644 .github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 index 2e1c0b54..095b7d98 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Add-ContentFromItem.ps1 @@ -20,11 +20,7 @@ function Add-ContentFromItem { # The root path of the module. [Parameter(Mandatory)] - [string] $RootPath, - - # Whether the folder should be loaded using dependency ordering (class/enum aware). - [Parameter()] - [switch] $DependencyAware + [string] $RootPath ) $separators = [char[]]@([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) @@ -37,12 +33,7 @@ function Add-ContentFromItem { Write-Debug "[`$scriptName] - $relativeFolderPath - Processing folder" "@ - if ($DependencyAware) { - $files = $Path | Get-ChildItem -Recurse -File -Force -Filter '*.ps1' | Sort-Object -Property FullName - $files = Get-DependencyOrderedScriptFile -Files $files - } else { - $files = $Path | Get-ChildItem -File -Force -Filter '*.ps1' | Sort-Object -Property FullName - } + $files = $Path | Get-ChildItem -File -Force -Filter '*.ps1' | Sort-Object -Property FullName foreach ($file in $files) { $relativeFilePath = [System.IO.Path]::GetRelativePath($RootPath, $file.FullName) @@ -61,11 +52,9 @@ Write-Debug "[`$scriptName] - $relativeFilePath - Done" "@ } - if (-not $DependencyAware) { - $subFolders = $Path | Get-ChildItem -Directory -Force | Sort-Object -Property Name - foreach ($subFolder in $subFolders) { - Add-ContentFromItem -Path $subFolder.FullName -RootModuleFilePath $RootModuleFilePath -RootPath $RootPath - } + $subFolders = $Path | Get-ChildItem -Directory -Force | Sort-Object -Property Name + foreach ($subFolder in $subFolders) { + Add-ContentFromItem -Path $subFolder.FullName -RootModuleFilePath $RootModuleFilePath -RootPath $RootPath } Add-Content -Path $RootModuleFilePath -Force -Value @" Write-Debug "[`$scriptName] - $relativeFolderPath - Done" diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 index f31667e3..619a5d95 100644 --- a/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 +++ b/.github/actions/Build-PSModule/src/helpers/Build/Build-PSModuleRootModule.ps1 @@ -10,14 +10,15 @@ 1. Module header from header.ps1 file. Usually to suppress code analysis warnings/errors and to add [CmdletBinding()] to the module. 2. Data loader is added if data files are available. - 3. Combines *.ps1 files from the following folders: + 3. Combines *.ps1 files from the following folders in alphabetical order from each folder: 1. init - 2. classes (dependency-aware ordering for class/enum references) - 3. functions/private - 4. functions/public - 5. variables/private - 6. variables/public - 7. Any remaining *.ps1 on module root. + 2. classes/private + 3. classes/public + 4. functions/private + 5. functions/public + 6. variables/private + 7. variables/public + 8. Any remaining *.ps1 on module root. 4. Adds a class loader for classes found in the classes/public folder. 5. Export-ModuleMember by using the functions, cmdlets, variables and aliases found in the source files. - `Functions` will only contain functions that are from the `functions/public` folder. @@ -174,7 +175,8 @@ Write-Debug "[$scriptName] - [data] - Done" #region - Add content from subfolders $scriptFoldersToProcess = @( 'init', - 'classes', + 'classes/private', + 'classes/public', 'functions/private', 'functions/public', 'variables/private', @@ -182,18 +184,11 @@ Write-Debug "[$scriptName] - [data] - Done" ) foreach ($scriptFolder in $scriptFoldersToProcess) { - $dependencyAware = $scriptFolder -eq 'classes' $scriptFolderPath = Join-Path -Path $ModuleOutputFolder -ChildPath $scriptFolder if (-not (Test-Path -Path $scriptFolderPath)) { continue } - $addContentParams = @{ - Path = $scriptFolderPath - RootModuleFilePath = $rootModuleFile - RootPath = $ModuleOutputFolder - DependencyAware = $dependencyAware - } - Add-ContentFromItem @addContentParams + Add-ContentFromItem -Path $scriptFolderPath -RootModuleFilePath $rootModuleFile -RootPath $ModuleOutputFolder Remove-Item -Path $scriptFolderPath -Force -Recurse } #endregion - Add content from subfolders diff --git a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 b/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 deleted file mode 100644 index 13adb811..00000000 --- a/.github/actions/Build-PSModule/src/helpers/Build/Get-DependencyOrderedScriptFile.ps1 +++ /dev/null @@ -1,115 +0,0 @@ -function Get-DependencyOrderedScriptFile { - <# - .SYNOPSIS - Sorts script files so class/enum dependencies are loaded before dependents. - - .DESCRIPTION - Reads declared class/enum types and type references from each script and returns - files in dependency order. When a cyclical dependency is detected, the function - warns and throws to fail the build early. - - .EXAMPLE - Get-DependencyOrderedScriptFile -Files (Get-ChildItem -Path '.\classes' -Filter '*.ps1') - #> - [OutputType([System.IO.FileInfo[]])] - [CmdletBinding()] - param( - [Parameter(Mandatory)] - [System.IO.FileInfo[]] $Files - ) - - [System.IO.FileInfo[]]$sortedFiles = $Files | Sort-Object -Property FullName - if ($sortedFiles.Count -le 1) { - return $sortedFiles - } - - $metadataByPath = @{} - $typeToPath = @{} - $duplicateTypes = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - - foreach ($file in $sortedFiles) { - $content = Get-Content -Path $file.FullName -Raw - $declaredTypes = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - [Regex]::Matches($content, '(?im)^\s*(class|enum)\s+([^\s:{]+)') | ForEach-Object { - [void] $declaredTypes.Add($_.Groups[2].Value) - } - - $metadataByPath[$file.FullName] = [pscustomobject]@{ - File = $file - Content = $content - DeclaredTypes = $declaredTypes - } - - foreach ($typeName in $declaredTypes) { - if ($typeToPath.ContainsKey($typeName)) { - [void]$duplicateTypes.Add($typeName) - continue - } - - $typeToPath[$typeName] = $file.FullName - } - } - - $dependenciesByPath = @{} - $dependentsByPath = @{} - foreach ($file in $sortedFiles) { - $dependenciesByPath[$file.FullName] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - $dependentsByPath[$file.FullName] = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) - } - - foreach ($metadata in $metadataByPath.Values) { - foreach ($typeName in $typeToPath.Keys) { - if ($duplicateTypes.Contains($typeName)) { - continue - } - - if ($metadata.DeclaredTypes.Contains($typeName)) { - continue - } - - $typePattern = "(?