diff --git a/.github/workflows/chocolatey-publish-cfl.yml b/.github/workflows/chocolatey-publish-cfl.yml index aa44e669..9c9e8c1d 100644 --- a/.github/workflows/chocolatey-publish-cfl.yml +++ b/.github/workflows/chocolatey-publish-cfl.yml @@ -11,20 +11,29 @@ on: jobs: publish: runs-on: windows-latest + env: + VERSION: ${{ inputs.version }} steps: - uses: actions/checkout@v4 + - name: Validate version + shell: pwsh + run: | + if ($env:VERSION -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') { + throw "Version must be numeric SemVer (major.minor.patch): $env:VERSION" + } + - name: Download release assets env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - $version = "${{ inputs.version }}" + $version = $env:VERSION $tag = "cfl-v$version" gh release download $tag --pattern "cfl_${version}_windows_*.zip" --pattern "checksums.txt" --dir artifacts - name: Extract checksums run: | - $version = "${{ inputs.version }}" + $version = $env:VERSION $checksums = Get-Content artifacts/checksums.txt $amd64 = ($checksums | Select-String "cfl_${version}_windows_amd64.zip").ToString().Split(" ")[0] $arm64 = ($checksums | Select-String "cfl_${version}_windows_arm64.zip").ToString().Split(" ")[0] @@ -33,28 +42,22 @@ jobs: - name: Update nuspec version run: | - $version = "${{ inputs.version }}" + $version = $env:VERSION $nuspec = Get-Content tools/cfl/packaging/chocolatey/confluence-cli.nuspec $nuspec = $nuspec -replace '.*', "$version" $nuspec | Set-Content tools/cfl/packaging/chocolatey/confluence-cli.nuspec - - name: Update install script checksums - run: | - $script = Get-Content tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 - $script = $script -replace "CHECKSUM_AMD64_PLACEHOLDER", $env:CHECKSUM_AMD64 - $script = $script -replace "CHECKSUM_ARM64_PLACEHOLDER", $env:CHECKSUM_ARM64 - $script | Set-Content tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 - - - name: Update install script URLs + - name: Render install script run: | - $version = "${{ inputs.version }}" - $script = Get-Content tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 - $script = $script -replace 'https://github.com/open-cli-collective/confluence-cli/', 'https://github.com/open-cli-collective/atlassian-cli/' - $script = $script -replace '/v\$\{version\}', "/cfl-v`${version}" - $script | Set-Content tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 + pwsh -File tools/cfl/scripts/render-chocolatey-install.ps1 ` + -Version $env:VERSION ` + -ScriptPath tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 ` + -ChecksumAmd64 $env:CHECKSUM_AMD64 ` + -ChecksumArm64 $env:CHECKSUM_ARM64 - name: Pack and push run: | cd tools/cfl/packaging/chocolatey choco pack - choco push confluence-cli.${{ inputs.version }}.nupkg --source https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }} + $package = "confluence-cli.$($env:VERSION).nupkg" + choco push $package --source https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }} diff --git a/.github/workflows/test-chocolatey.yml b/.github/workflows/test-chocolatey.yml index 7c9ff03c..8379bd45 100644 --- a/.github/workflows/test-chocolatey.yml +++ b/.github/workflows/test-chocolatey.yml @@ -5,6 +5,15 @@ on: branches: [main] paths: - 'tools/*/packaging/chocolatey/**' + - 'tools/cfl/scripts/**' + - '.github/workflows/chocolatey-publish-cfl.yml' + - '.github/workflows/test-chocolatey.yml' + pull_request: + paths: + - 'tools/*/packaging/chocolatey/**' + - 'tools/cfl/scripts/**' + - '.github/workflows/chocolatey-publish-cfl.yml' + - '.github/workflows/test-chocolatey.yml' jobs: test-cfl: @@ -34,14 +43,85 @@ jobs: } Write-Host "Install script syntax is valid" - - name: Test pack (dry run) + - name: Test rendered package + shell: pwsh run: | - cd tools/cfl/packaging/chocolatey - # Update version for test - $nuspec = Get-Content confluence-cli.nuspec - $nuspec = $nuspec -replace '.*', '0.0.1-test' - $nuspec | Set-Content confluence-cli.nuspec - choco pack --verbose + $version = '0.0.1' + $packageDir = 'tools/cfl/packaging/chocolatey' + $nuspecPath = Join-Path $packageDir 'confluence-cli.nuspec' + $nuspec = (Get-Content $nuspecPath -Raw).Replace('0.0.0', "$version") + Set-Content $nuspecPath $nuspec + + $scriptPath = Join-Path $packageDir 'tools/chocolateyInstall.ps1' + $checksumAmd64 = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' + $checksumArm64 = 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210' + pwsh -File tools/cfl/scripts/render-chocolatey-install.ps1 ` + -Version $version ` + -ScriptPath $scriptPath ` + -ChecksumAmd64 $checksumAmd64 ` + -ChecksumArm64 $checksumArm64 + + Push-Location $packageDir + try { + choco pack --verbose + } finally { + Pop-Location + } + + $packagePath = Join-Path $packageDir "confluence-cli.$version.nupkg" + if (-not (Test-Path $packagePath)) { throw "Expected package was not created: $packagePath" } + $extractDir = Join-Path $env:RUNNER_TEMP "cfl-choco-$([guid]::NewGuid())" + [IO.Compression.ZipFile]::ExtractToDirectory($packagePath, $extractDir) + $renderedPath = Join-Path $extractDir 'tools/chocolateyInstall.ps1' + $rendered = Get-Content $renderedPath -Raw + if ($rendered -match 'URL_AMD64_PLACEHOLDER|URL_ARM64_PLACEHOLDER|CHECKSUM_AMD64_PLACEHOLDER|CHECKSUM_ARM64_PLACEHOLDER|ChocolateyPackageVersion|\$\{version\}') { + throw 'Rendered cfl package still contains a URL/checksum placeholder or runtime package-version expression' + } + $expectedAmd64 = "https://github.com/open-cli-collective/atlassian-cli/releases/download/cfl-v$version/cfl_${version}_windows_amd64.zip" + $expectedArm64 = "https://github.com/open-cli-collective/atlassian-cli/releases/download/cfl-v$version/cfl_${version}_windows_arm64.zip" + if (-not $rendered.Contains($expectedAmd64)) { throw "Rendered package is missing $expectedAmd64" } + if (-not $rendered.Contains($expectedArm64)) { throw "Rendered package is missing $expectedArm64" } + + function Install-ChocolateyZipPackage { + param( + [string]$PackageName, + [string]$Url, + [string]$UnzipLocation, + [string]$Checksum, + [string]$ChecksumType + ) + $global:capturedPackageName = $PackageName + $global:capturedUrl = $Url + $global:capturedChecksum = $Checksum + $global:capturedChecksumType = $ChecksumType + } + + $env:ChocolateyPackageName = 'confluence-cli' + $architectures = @( + @{ Name = 'AMD64'; Environment = 'AMD64'; Url = $expectedAmd64; Checksum = $checksumAmd64 }, + @{ Name = 'ARM64'; Environment = 'ARM64'; Url = $expectedArm64; Checksum = $checksumArm64 } + ) + foreach ($architecture in $architectures) { + $previousArchitecture = $env:PROCESSOR_ARCHITECTURE + try { + $env:PROCESSOR_ARCHITECTURE = $architecture.Environment + $global:capturedUrl = $null + $global:capturedChecksum = $null + $global:capturedChecksumType = $null + & $renderedPath + } finally { + $env:PROCESSOR_ARCHITECTURE = $previousArchitecture + } + if ($global:capturedUrl -ne $architecture.Url) { + throw "$($architecture.Name) selected URL '$global:capturedUrl', expected '$($architecture.Url)'" + } + if ($global:capturedChecksum -ne $architecture.Checksum) { + throw "$($architecture.Name) selected checksum '$global:capturedChecksum', expected '$($architecture.Checksum)'" + } + if ($global:capturedChecksumType -ne 'sha256') { + throw "$($architecture.Name) selected checksum type '$global:capturedChecksumType', expected 'sha256'" + } + } test-jtk: runs-on: windows-latest diff --git a/tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 b/tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 index 13b5e4e6..ab5f85b2 100644 --- a/tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 +++ b/tools/cfl/packaging/chocolatey/tools/chocolateyInstall.ps1 @@ -1,28 +1,27 @@ $ErrorActionPreference = 'Stop' -$version = $env:ChocolateyPackageVersion $toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition -# Checksums injected by release workflow - DO NOT EDIT MANUALLY +# URLs and checksums injected by release workflow - DO NOT EDIT MANUALLY +$urlAmd64 = 'URL_AMD64_PLACEHOLDER' +$urlArm64 = 'URL_ARM64_PLACEHOLDER' $checksumAmd64 = 'CHECKSUM_AMD64_PLACEHOLDER' $checksumArm64 = 'CHECKSUM_ARM64_PLACEHOLDER' # Architecture detection with ARM64 support if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { $arch = 'arm64' + $url = $urlArm64 $checksum = $checksumArm64 } elseif ([Environment]::Is64BitOperatingSystem) { $arch = 'amd64' + $url = $urlAmd64 $checksum = $checksumAmd64 } else { throw "32-bit Windows is not supported. confluence-cli requires 64-bit Windows." } -$baseUrl = "https://github.com/open-cli-collective/atlassian-cli/releases/download/cfl-v${version}" -$zipFile = "cfl_${version}_windows_${arch}.zip" -$url = "${baseUrl}/${zipFile}" - -Write-Host "Installing confluence-cli ${version} for Windows ${arch}..." +Write-Host "Installing confluence-cli for Windows ${arch}..." Write-Host "URL: ${url}" Write-Host "Checksum (SHA256): ${checksum}" diff --git a/tools/cfl/scripts/render-chocolatey-install.ps1 b/tools/cfl/scripts/render-chocolatey-install.ps1 new file mode 100644 index 00000000..53edbb2f --- /dev/null +++ b/tools/cfl/scripts/render-chocolatey-install.ps1 @@ -0,0 +1,42 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidatePattern('^[0-9]+\.[0-9]+\.[0-9]+$')] + [string]$Version, + + [Parameter(Mandatory)] + [string]$ScriptPath, + + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{64}$')] + [string]$ChecksumAmd64, + + [Parameter(Mandatory)] + [ValidatePattern('^[0-9a-fA-F]{64}$')] + [string]$ChecksumArm64 +) + +$ErrorActionPreference = 'Stop' + +$amd64Url = "https://github.com/open-cli-collective/atlassian-cli/releases/download/cfl-v$Version/cfl_${Version}_windows_amd64.zip" +$arm64Url = "https://github.com/open-cli-collective/atlassian-cli/releases/download/cfl-v$Version/cfl_${Version}_windows_arm64.zip" +$script = Get-Content $ScriptPath -Raw + +foreach ($placeholder in 'URL_AMD64_PLACEHOLDER', 'URL_ARM64_PLACEHOLDER', 'CHECKSUM_AMD64_PLACEHOLDER', 'CHECKSUM_ARM64_PLACEHOLDER') { + if (-not $script.Contains($placeholder)) { throw "Missing $placeholder in $ScriptPath" } +} + +$script = $script.Replace('URL_AMD64_PLACEHOLDER', $amd64Url) +$script = $script.Replace('URL_ARM64_PLACEHOLDER', $arm64Url) +$script = $script.Replace('CHECKSUM_AMD64_PLACEHOLDER', $ChecksumAmd64) +$script = $script.Replace('CHECKSUM_ARM64_PLACEHOLDER', $ChecksumArm64) + +if ($script -match 'URL_AMD64_PLACEHOLDER|URL_ARM64_PLACEHOLDER|CHECKSUM_AMD64_PLACEHOLDER|CHECKSUM_ARM64_PLACEHOLDER|ChocolateyPackageVersion|\$\{version\}') { + throw "Rendered $ScriptPath still contains a URL/checksum placeholder or runtime package-version expression" +} +if (-not $script.Contains($amd64Url)) { throw "Rendered script is missing $amd64Url" } +if (-not $script.Contains($arm64Url)) { throw "Rendered script is missing $arm64Url" } +if (-not $script.Contains($ChecksumAmd64)) { throw "Rendered script is missing the AMD64 checksum" } +if (-not $script.Contains($ChecksumArm64)) { throw "Rendered script is missing the ARM64 checksum" } + +Set-Content $ScriptPath $script