Skip to content

Commit 74a30bb

Browse files
refactor(scripts): replace raw GITHUB_OUTPUT with Set-CIOutput in Package-Extension (#391)
# Pull Request ## Description Refactors `Package-Extension.ps1` to use the existing `CIHelpers` module instead of raw `GITHUB_OUTPUT` file writes. This change: - Replaces 4-line conditional block with 3 `Set-CIOutput` calls - Provides consistent CI output handling across GitHub Actions and Azure DevOps - Adds automatic escaping for special characters via `CIHelpers` module - Removes platform-specific conditional logic from the script The `CIHelpers.psm1` module handles injection prevention by escaping `%`, `\r`, `\n`, `::`, `[`, `]`, and `;` characters in output values. ## Related Issue(s) Fixes #350 ## Type of Change Select all that apply: **Code & Documentation:** - [ ] Bug fix (non-breaking change fixing an issue) - [ ] New feature (non-breaking change adding functionality) - [ ] Breaking change (fix or feature causing existing functionality to change) - [ ] Documentation update **Infrastructure & Configuration:** - [ ] GitHub Actions workflow - [ ] Linting configuration (markdown, PowerShell, etc.) - [ ] Security configuration - [ ] DevContainer configuration - [ ] Dependency update **AI Artifacts:** - [ ] Reviewed contribution with `prompt-builder` agent and addressed all feedback - [ ] Copilot instructions (`.github/instructions/*.instructions.md`) - [ ] Copilot prompt (`.github/prompts/*.prompt.md`) - [ ] Copilot agent (`.github/agents/*.agent.md`) **Other:** - [x] Script/automation (`.ps1`, `.sh`, `.py`) - [ ] Other (please describe): ## Testing - **PSScriptAnalyzer**: 38 files analyzed, all passed - **Pester Tests**: 42 tests passed (includes CIHelpers.Tests.ps1 injection prevention tests) - **Manual Verification**: Confirmed `Set-CIOutput` correctly outputs values for `version`, `vsix-file`, and `pre-release` ## Checklist ### Required Checks - [x] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) - [ ] Tests added for new functionality (if applicable) ### Required Automated Checks The following validation commands must pass before merging: - [x] Markdown linting: `npm run lint:md` - [x] Spell checking: `npm run spell-check` - [x] Frontmatter validation: `npm run lint:frontmatter` - [x] Link validation: `npm run lint:md-links` - [x] PowerShell analysis: `npm run lint:ps` ## Security Considerations - [x] This PR does not contain any sensitive or NDA information - [x] Any new dependencies have been reviewed for security issues - [x] Security-related scripts follow the principle of least privilege **Security Analysis:** The refactoring improves security by leveraging `CIHelpers` module's built-in escaping functions: - `ConvertTo-GitHubActionsEscaped`: Escapes `%`, `\r`, `\n`, `::` to prevent workflow command injection - `ConvertTo-AzureDevOpsEscaped`: Escapes `%`, `\r`, `\n`, `[`, `]`, `;` to prevent logging command injection Test coverage for injection prevention exists in `CIHelpers.Tests.ps1` (lines 192-209 for Azure DevOps, lines 340-400 for GitHub Actions). ## Additional Notes - The `$PreRelease.IsPresent` boolean auto-converts to `"True"/"False"` string, matching original behavior - The `CIHelpers` module was already imported at line 73 of `Package-Extension.ps1` - `Set-CIOutput` is a no-op when not running in CI environments (both `$env:GITHUB_OUTPUT` and `$env:TF_BUILD` are absent)
1 parent 085a38b commit 74a30bb

2 files changed

Lines changed: 145 additions & 5 deletions

File tree

scripts/extension/Package-Extension.ps1

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -535,11 +535,9 @@ function Invoke-PackageExtension {
535535
Write-Host " Version: $packageVersion" -ForegroundColor Cyan
536536

537537
# Output for CI/CD consumption
538-
if ($env:GITHUB_OUTPUT) {
539-
"version=$packageVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
540-
"vsix-file=$($vsixFile.Name)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
541-
"pre-release=$($PreRelease.IsPresent)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
542-
}
538+
Set-CIOutput -Name 'version' -Value $packageVersion
539+
Set-CIOutput -Name 'vsix-file' -Value $vsixFile.Name
540+
Set-CIOutput -Name 'pre-release' -Value $PreRelease.IsPresent
543541

544542
Write-Host ""
545543
Write-Host "🎉 Done!" -ForegroundColor Green

scripts/tests/extension/Package-Extension.Tests.ps1

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
BeforeAll {
66
. $PSScriptRoot/../../extension/Package-Extension.ps1
7+
Import-Module "$PSScriptRoot/../Mocks/GitMocks.psm1" -Force
8+
Import-Module "$PSScriptRoot/../../lib/Modules/CIHelpers.psm1" -Force
79
}
810

911
Describe 'Test-VsceAvailable' {
@@ -460,3 +462,143 @@ Describe 'Invoke-PackageExtension' {
460462
$result.ErrorMessage | Should -Match 'vsce package command failed|The term'
461463
}
462464
}
465+
466+
Describe 'CI Integration - Package-Extension' {
467+
BeforeAll {
468+
$script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "ci-int-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
469+
$script:extDir = Join-Path $script:testRoot 'extension'
470+
$script:repoRoot = Join-Path $script:testRoot 'repo'
471+
}
472+
473+
AfterAll {
474+
if (Test-Path $script:testRoot) {
475+
Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
476+
}
477+
}
478+
479+
Context 'GitHub Actions environment' {
480+
BeforeEach {
481+
Initialize-MockGitHubEnvironment
482+
New-Item -Path $script:extDir -ItemType Directory -Force | Out-Null
483+
New-Item -Path $script:repoRoot -ItemType Directory -Force | Out-Null
484+
New-Item -Path (Join-Path $script:repoRoot '.github') -ItemType Directory -Force | Out-Null
485+
New-Item -Path (Join-Path $script:repoRoot 'scripts/dev-tools') -ItemType Directory -Force | Out-Null
486+
New-Item -Path (Join-Path $script:repoRoot 'docs/templates') -ItemType Directory -Force | Out-Null
487+
488+
$manifest = @{
489+
name = 'test-ext'
490+
version = '1.0.0'
491+
publisher = 'test'
492+
engines = @{ vscode = '^1.80.0' }
493+
}
494+
$manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
495+
}
496+
497+
AfterEach {
498+
Clear-MockGitHubEnvironment
499+
if (Test-Path $script:testRoot) {
500+
Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
501+
}
502+
}
503+
504+
It 'Sets version output variable on successful package' {
505+
Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
506+
Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
507+
508+
$vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
509+
Set-Content -Path $vsixPath -Value 'fake-vsix'
510+
511+
$null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
512+
513+
$outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
514+
$outputContent | Should -Match 'version=1\.0\.0'
515+
}
516+
517+
It 'Sets vsix-file output variable on successful package' {
518+
Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
519+
Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
520+
521+
$vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
522+
Set-Content -Path $vsixPath -Value 'fake-vsix'
523+
524+
$null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
525+
526+
$outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
527+
$outputContent | Should -Match 'vsix-file=test-ext-1\.0\.0\.vsix'
528+
}
529+
530+
It 'Sets pre-release output variable when PreRelease specified' {
531+
Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
532+
Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
533+
534+
$vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
535+
Set-Content -Path $vsixPath -Value 'fake-vsix'
536+
537+
$null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot -PreRelease
538+
539+
$outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
540+
$outputContent | Should -Match 'pre-release=True'
541+
}
542+
543+
It 'Sets pre-release output variable to false when PreRelease not specified' {
544+
Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
545+
Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
546+
547+
$vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
548+
Set-Content -Path $vsixPath -Value 'fake-vsix'
549+
550+
$null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
551+
552+
$outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
553+
$outputContent | Should -Match 'pre-release=False'
554+
}
555+
556+
It 'Returns failure result when vsce command fails' {
557+
Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
558+
Mock Get-VscePackageCommand { return @{ Executable = 'pwsh'; Arguments = @('-Command', 'exit 1') } }
559+
560+
$result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
561+
562+
$result.Success | Should -BeFalse
563+
$result.ErrorMessage | Should -Match 'vsce package command failed'
564+
}
565+
}
566+
567+
Context 'Local environment' {
568+
BeforeEach {
569+
Clear-MockGitHubEnvironment
570+
571+
New-Item -Path $script:extDir -ItemType Directory -Force | Out-Null
572+
New-Item -Path $script:repoRoot -ItemType Directory -Force | Out-Null
573+
New-Item -Path (Join-Path $script:repoRoot '.github') -ItemType Directory -Force | Out-Null
574+
New-Item -Path (Join-Path $script:repoRoot 'scripts/dev-tools') -ItemType Directory -Force | Out-Null
575+
New-Item -Path (Join-Path $script:repoRoot 'docs/templates') -ItemType Directory -Force | Out-Null
576+
577+
$manifest = @{
578+
name = 'test-ext'
579+
version = '1.0.0'
580+
publisher = 'test'
581+
engines = @{ vscode = '^1.80.0' }
582+
}
583+
$manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
584+
}
585+
586+
AfterEach {
587+
if (Test-Path $script:testRoot) {
588+
Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
589+
}
590+
}
591+
592+
It 'Completes without error when not in CI environment' {
593+
Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
594+
Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
595+
596+
$vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
597+
Set-Content -Path $vsixPath -Value 'fake-vsix'
598+
599+
$result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
600+
601+
$result.Success | Should -BeTrue
602+
}
603+
}
604+
}

0 commit comments

Comments
 (0)