-
Notifications
You must be signed in to change notification settings - Fork 47
refactor(scripts): standardize entry point guard pattern #346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add Invoke-* orchestration functions to all scripts - Apply guard pattern: $MyInvocation.InvocationName -ne '.' - Document pattern in scripts/README.md - Simplify test dot-sourcing (removes AST workaround) - Scripts: 12 files updated across security, linting, extension
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #346 +/- ##
==========================================
+ Coverage 41.08% 42.87% +1.79%
==========================================
Files 15 15
Lines 2870 2904 +34
==========================================
+ Hits 1179 1245 +66
+ Misses 1691 1659 -32
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR standardizes a PowerShell script entry-point pattern across the repo, adding Invoke-* orchestration functions and a guard that distinguishes direct invocation from dot-sourcing, plus documentation for the pattern. The changes improve testability, make exit-code behavior more consistent, and clean up the SHA staleness tests by removing the AST-based workaround.
Changes:
- Added
Invoke-*orchestration functions and$MyInvocation.InvocationName -ne '.'guards to security, linting, and extension scripts, converting in-scriptexitcalls into integer return codes propagated by the guard. - Simplified
Test-SHAStalenessPester tests by switching from AST-based function extraction to simple dot-sourcing now that scripts can be safely imported without executing main logic. - Documented the standard entry-point pattern and naming conventions for scripts in
scripts/README.md.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
scripts/tests/security/Test-SHAStaleness.Tests.ps1 |
Updates tests to dot-source Test-SHAStaleness.ps1 directly, relying on the new guard pattern to prevent main execution during testing. |
scripts/security/Update-ActionSHAPinning.ps1 |
Introduces Invoke-ActionSHAUpdate as the main orchestration function, returns integer exit codes, and guards main execution behind the $MyInvocation.InvocationName -ne '.' check. |
scripts/security/Test-SHAStaleness.ps1 |
Adds Invoke-SHAStalenessTest orchestration, converts inline exits to integer returns, and uses a guarded main section that calls the orchestrator and exits with its code. |
scripts/security/Test-DependencyPinning.ps1 |
Extracts the main dependency-pinning logic into Invoke-DependencyPinningTest, replaces direct exits with returns, and adds a guard so dot-sourcing no longer fails the script. |
scripts/linting/Markdown-Link-Check.ps1 |
Wraps existing markdown link checking logic in Invoke-MarkdownLinkCheck with integer return codes and a guarded main execution path. |
scripts/linting/Link-Lang-Check.ps1 |
Adds Invoke-LinkLanguageCheck as the orchestration function, returning 0 consistently while the top-level guard handles exit behavior. |
scripts/linting/Invoke-YamlLint.ps1 |
Refactors main YAML linting logic into Invoke-YamlLintValidation with structured return codes and creates a guarded main section that exits with the function’s result. |
scripts/linting/Invoke-PSScriptAnalyzer.ps1 |
Moves PSScriptAnalyzer execution into Invoke-PSScriptAnalysis, returning an integer summary result and wiring it through a guarded main block. |
scripts/linting/Invoke-LinkLanguageCheck.ps1 |
Wraps Link-Lang-Check.ps1 with Invoke-LinkLanguageCheckWrapper, returning explicit exit codes and guarding the main entry for dot-sourcing scenarios. |
scripts/extension/Prepare-Extension.ps1 |
Extracts the extension preparation workflow into Invoke-ExtensionPreparation, uses $PSScriptRoot for path resolution, and returns integer codes that the guarded main section converts into process exits. |
scripts/extension/Package-Extension.ps1 |
Refactors packaging logic into Invoke-ExtensionPackaging, ensures all paths return explicit exit codes, and adds a guard-based main entry that calls the orchestrator and exits accordingly. |
scripts/README.md |
Documents the new script entry-point pattern, including structure, guard usage, and naming conventions, with a minor inconsistency in the naming table noted separately. |
scripts/README.md
Outdated
| | `Package-*.ps1` | `Invoke-*Packaging` | | ||
| | `Prepare-*.ps1` | `Invoke-*Preparation` | | ||
| | `Update-*.ps1` | `Invoke-*Update` | | ||
| | `Validate-*.ps1` | `Test-*Validation` | |
Copilot
AI
Jan 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the naming convention table, the last row lists Test-*Validation as the "Invoke Function" for Validate-*.ps1 scripts, which is inconsistent with the preceding description that orchestration entrypoints follow the Invoke-* pattern and with new functions like Invoke-YamlLintValidation. To avoid confusion for future contributors, this row should be updated to use an Invoke-*Validation name to match the documented pattern and actual script implementations.
| | `Validate-*.ps1` | `Test-*Validation` | | |
| | `Validate-*.ps1` | `Invoke-*Validation` | |
I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
|
📝 - Generated by Copilot
Add comprehensive test coverage improvements: P1 - Entry point tests: - Update-ActionSHAPinning: Invoke-ActionSHAUpdate tests - Markdown-Link-Check: Invoke-MarkdownLinkCheck tests P2 - Error path and boundary tests: - Package-Extension: null manifest, empty hashtable, edge cases - Prepare-Extension: malformed YAML, missing frontmatter handling - Test-DependencyPinning: empty inputs, SHA format validation - Test-SHAStaleness: version comparison edge cases, token validation - Link-Lang-Check: fix flaky test Coverage: 36.17% -> 53.71% (+17.54pp) Tests: 742 -> 766 (+24 tests, 760 passing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
| .DESCRIPTION | ||
| Coordinates staleness checking for GitHub Actions and tools, then outputs results. | ||
| .PARAMETER OutputFormat | ||
| Output format: 'json', 'azdo', 'github', or 'console'. |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.PARAMETER OutputFormat documentation here only lists json, azdo, github, and console, but the parameter's ValidateSet also accepts BuildWarning and Summary, so the comment is out of date with the actual supported values. Please update the parameter description to reflect the full set of allowed formats so that callers using this function directly do not miss the additional options.
| Output format: 'json', 'azdo', 'github', or 'console'. | |
| Output format: 'json', 'azdo', 'github', 'console', 'BuildWarning', or 'Summary'. |
| $repoRoot = git rev-parse --show-toplevel 2>$null | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "Not in a git repository" | ||
| return 1 | ||
| } | ||
|
|
||
| # Create logs directory if it doesn't exist | ||
| $logsDir = Join-Path $repoRoot "logs" | ||
| if (-not (Test-Path $logsDir)) { | ||
| New-Item -ItemType Directory -Path $logsDir -Force | Out-Null | ||
| } |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic to resolve the git repository root and ensure a logs directory exists is now duplicated both in the top-level script (lines 17–28) and again inside Invoke-LinkLanguageCheckWrapper (lines 59–69). This duplication makes future changes to log location or git detection easy to forget in one place; consider centralizing this into a helper function or keeping it only in the wrapper so that behavior stays consistent and easier to maintain.
Add comprehensive tests for: - Package-Extension.ps1: version validation, parameter handling - Prepare-Extension.ps1: frontmatter parsing, discovery functions - Invoke-LinkLanguageCheck.ps1: wrapper function, output handling - Markdown-Link-Check.ps1: extended path handling, CLI scenarios Fix zero-coverage issue in 4 test files by replacing AST parsing with direct dot-sourcing for proper coverage tracking. Coverage improvement: 53.71% -> 63.98% (+10.27pp)
- add phase 4 tests to Package-Extension.Tests.ps1 and Prepare-Extension.Tests.ps1 - add orchestration path tests to Invoke-LinkLanguageCheck.Tests.ps1 - add function tests to Invoke-PSScriptAnalyzer.Tests.ps1 and Invoke-YamlLint.Tests.ps1 - add error handling and fix mode tests to Link-Lang-Check.Tests.ps1 - add XML parsing and integration tests to Markdown-Link-Check.Tests.ps1 🧪 - Generated by Copilot
|
@azurecloudkevin - Since this PR fell pretty far behind main, I took your branch and re-fired it up as PR #477. I'm going to close this one out given the merge conflicts are pretty hairy. Can you give me eyes on the new PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.
| function Invoke-DependencyPinningTest { | ||
| <# | ||
| .SYNOPSIS | ||
| Main orchestration function for dependency pinning compliance analysis. | ||
| .DESCRIPTION | ||
| Coordinates the scanning and reporting of dependency pinning compliance. | ||
| .PARAMETER Path | ||
| Root path to scan for dependency files. | ||
| .PARAMETER Recursive | ||
| Scan recursively through subdirectories. | ||
| .PARAMETER Format | ||
| Output format for compliance report. | ||
| .PARAMETER OutputPath | ||
| Path where compliance results should be saved. | ||
| .PARAMETER FailOnUnpinned | ||
| Exit with error code if pinning violations are found. | ||
| .PARAMETER ExcludePaths | ||
| Comma-separated list of paths to exclude from scanning. | ||
| .PARAMETER IncludeTypes | ||
| Comma-separated list of dependency types to check. | ||
| .PARAMETER Threshold | ||
| Minimum compliance score percentage required for passing grade. | ||
| .PARAMETER Remediate | ||
| Generate remediation suggestions with specific SHA pins. | ||
| .OUTPUTS | ||
| System.Int32 - Exit code (0 for success, 1 for failure) | ||
| #> | ||
| [CmdletBinding()] | ||
| [OutputType([int])] | ||
| param( | ||
| [Parameter(Mandatory = $false)] | ||
| [string]$Path = ".", | ||
|
|
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new dot-sourcing approach, script-scope preference changes like $ErrorActionPreference = 'Stop' (currently set at top-level) will leak into the caller scope and can affect unrelated tests. Consider setting $ErrorActionPreference inside Invoke-DependencyPinningTest (or saving/restoring it) so importing functions does not mutate the caller environment.
| Invoke-MyOperation -InputPath $InputPath | ||
| exit 0 |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README example exits with 0 unconditionally after calling Invoke-MyOperation, which hides failures and does not match the pattern used elsewhere in this PR (capture function return code, then exit $exitCode). Update the example to assign the orchestration function’s return value to $exitCode and exit with that value.
| Invoke-MyOperation -InputPath $InputPath | |
| exit 0 | |
| $exitCode = Invoke-MyOperation -InputPath $InputPath | |
| exit $exitCode |
| It 'Returns 1 when no markdown files found' { | ||
| $result = Invoke-MarkdownLinkCheck -Path $script:EmptyDir -ConfigPath $script:ConfigFile -ErrorAction SilentlyContinue 2>&1 | ||
| # Function returns 1 when no files found | ||
| $true | Should -BeTrue | ||
| } |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests don’t assert the function’s behavior: they call Invoke-MarkdownLinkCheck and then only check $true | Should -BeTrue. This will pass even if the function returns the wrong exit code or throws. Assert on the returned exit code (and/or expected error message) so failures are caught.
| It 'Returns null on API error without throwing' { | ||
| Mock Invoke-RestMethod { | ||
| throw [System.Exception]::new('Network error') | ||
| # Create a WebException-like exception with Response property | ||
| $webException = [System.Net.WebException]::new('Network error') | ||
| Mock Invoke-GitHubAPIWithRetry { | ||
| throw $webException | ||
| } | ||
| Mock Test-GitHubToken { | ||
| return @{ Valid = $false; Message = 'No token' } | ||
| } | ||
| Mock Write-SecurityLog { } | ||
|
|
||
| # Function should handle error gracefully and return null | ||
| $result = Get-LatestCommitSHA -Owner 'actions' -Repo 'checkout' -Branch 'main' | ||
| $result | Should -BeNullOrEmpty | ||
| { $result = Get-LatestCommitSHA -Owner 'actions' -Repo 'checkout' -Branch 'main' } | Should -Not -Throw | ||
| } |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test no longer verifies the documented behavior (“returns null on API error”): it only asserts the call does not throw, but never checks $result is $null/empty. Add an assertion on $result after invocation so regressions in the return value are caught.
| function Invoke-YamlLintValidation { | ||
| <# | ||
| .SYNOPSIS | ||
| Main orchestration function for YAML lint validation. | ||
| .DESCRIPTION | ||
| Coordinates the validation of GitHub Actions workflow files using actionlint. | ||
| .PARAMETER ChangedFilesOnly | ||
| Validate only changed YAML files. |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new guard pattern only prevents the bottom main block from running when dot-sourced, but this script still executes top-level logic (Write-Host + Get-Command actionlint + exit 1) before Invoke-YamlLintValidation is defined. Dot-sourcing in tests or other scripts will still terminate the session if actionlint is missing. Move the actionlint availability check (and any other side-effecting top-level code) inside Invoke-YamlLintValidation or inside the direct-invocation guard so dot-sourcing is safe.
| function Invoke-PSScriptAnalysis { | ||
| <# | ||
| .SYNOPSIS | ||
| Main orchestration function for PSScriptAnalyzer validation. | ||
| .DESCRIPTION | ||
| Coordinates the analysis of PowerShell files using PSScriptAnalyzer. | ||
| .PARAMETER FilesToAnalyze |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This script still runs significant side effects at top-level (module installation, file discovery, and exit 0 when no files are found) before the new Invoke-PSScriptAnalysis guard. If the file is dot-sourced for testing, it can unexpectedly install modules or terminate the calling session. Move the top-level execution logic into Invoke-PSScriptAnalysis, and ensure any exit calls occur only inside the direct-invocation guard.
| function Invoke-LinkLanguageCheckWrapper { | ||
| <# | ||
| .SYNOPSIS | ||
| Main orchestration function for link language check wrapper. | ||
| .DESCRIPTION | ||
| Coordinates the link language check with GitHub Actions integration. | ||
| .PARAMETER ExcludePaths | ||
| Paths to exclude from checking. | ||
| .OUTPUTS | ||
| System.Int32 - Exit code (0 for success, 1 for issues found) |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invoke-LinkLanguageCheckWrapper duplicates repo-root detection and logs directory creation that already happens at the script top-level. More importantly, the script currently does git rev-parse + exit 1 at top-level, which still runs when dot-sourced, defeating the guard pattern and making tests brittle. Move repo-root/logs initialization into Invoke-LinkLanguageCheckWrapper (or the direct-invocation guard) and avoid any top-level exit so dot-sourcing only loads functions.
| function Invoke-SHAStalenessTest { | ||
| <# | ||
| .SYNOPSIS | ||
| Main orchestration function for SHA staleness monitoring. | ||
| .DESCRIPTION | ||
| Coordinates staleness checking for GitHub Actions and tools, then outputs results. | ||
| .PARAMETER OutputFormat | ||
| Output format: 'json', 'azdo', 'github', or 'console'. | ||
| .PARAMETER MaxAge | ||
| Maximum age in days before considering a dependency stale. | ||
| .PARAMETER LogPath | ||
| Path for security logging. | ||
| .PARAMETER OutputPath | ||
| Path to write structured output file. | ||
| .PARAMETER FailOnStale | ||
| Exit with code 1 if stale dependencies are found. | ||
| .PARAMETER GraphQLBatchSize | ||
| Batch size for GraphQL queries. | ||
| .OUTPUTS | ||
| System.Int32 - Exit code (0 for success, 1 for failure/stale dependencies with FailOnStale) | ||
| #> | ||
| [CmdletBinding()] | ||
| [OutputType([int])] | ||
| param( | ||
| [Parameter(Mandatory = $false)] |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because tests now dot-source this script, the top-level side effects (for example, creating the log directory before any function calls) will run during import and can write to the working directory unexpectedly. Consider moving the log directory creation and any other setup work into Invoke-SHAStalenessTest so dot-sourcing only defines functions and state initialization is explicit.
| function Invoke-ActionSHAUpdate { | ||
| <# | ||
| .SYNOPSIS | ||
| Main orchestration function for GitHub Actions SHA pinning updates. | ||
| .DESCRIPTION | ||
| Coordinates the scanning and updating of GitHub Actions workflows with SHA pins. | ||
| .PARAMETER WorkflowPath | ||
| Path to the .github/workflows directory. | ||
| .PARAMETER OutputReport | ||
| Generate detailed report of changes and pinning status. | ||
| .PARAMETER OutputFormat | ||
| Output format for results. | ||
| .PARAMETER UpdateStale | ||
| Update already-pinned-but-stale GitHub Actions to their latest commit SHAs. | ||
| .OUTPUTS | ||
| System.Int32 - Exit code (0 for success, 1 for failure) | ||
| #> | ||
| [CmdletBinding(SupportsShouldProcess)] | ||
| [OutputType([int])] | ||
| param( |
Copilot
AI
Feb 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that tests dot-source this script, Set-StrictMode -Version Latest (near the top of the file) will apply to the caller scope and can break Pester or any script that imports these functions. To keep dot-sourcing safe, move Set-StrictMode (and any other global preference changes) inside Invoke-ActionSHAUpdate (or a child scope) so importing functions does not modify the caller environment.
Pull Request
Description
This PR standardizes the "direct invocation vs dot-sourced" guard pattern across all PowerShell scripts in the repository. This improves testability by allowing scripts to be dot-sourced without executing their main logic, enabling Pester tests to directly call individual functions.
Related Issue(s)
Closes issue 325
Type of Change
Select all that apply:
Code & Documentation:
Infrastructure & Configuration:
AI Artifacts:
prompt-builderagent and addressed all feedback.github/instructions/*.instructions.md).github/prompts/*.prompt.md).github/agents/*.agent.md)Other:
.ps1,.sh,.py)Testing
Checklist
Required Checks
AI Artifact Contributions
/prompt-analyzeto review contributionprompt-builderreviewRequired Automated Checks
The following validation commands must pass before merging:
npm run lint:mdnpm run spell-checknpm run lint:frontmatternpm run lint:md-linksnpm run lint:psSecurity Considerations
Additional Notes