Skip to content

ci: prefer standalone sass install in windows release workflow #11

ci: prefer standalone sass install in windows release workflow

ci: prefer standalone sass install in windows release workflow #11

Workflow file for this run

name: Release Windows
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g. v0.1.5)"
required: true
type: string
permissions:
contents: write
id-token: write
attestations: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
release:
name: Build and publish signed Windows release
runs-on: windows-2022
environment: production-release
timeout-minutes: 120
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Checkout tag/ref
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
- name: Verify tag matches package version
shell: pwsh
run: |
$pkgVersion = (Get-Content package.json -Raw | ConvertFrom-Json).version
$expectedTag = "v$pkgVersion"
$actualTag = if ('${{ github.event_name }}' -eq 'workflow_dispatch') { '${{ inputs.tag }}' } else { '${{ github.ref_name }}' }
if ($actualTag -ne $expectedTag) {
throw "Tag/package mismatch: got '$actualTag' but package.json version expects '$expectedTag'."
}
- name: Setup pnpm
uses: pnpm/action-setup@v5
with:
version: 9.15.0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- name: Setup Hugo Extended
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
function Test-HugoExtended {
if (-not (Get-Command hugo -ErrorAction SilentlyContinue)) {
return $false
}
$versionText = & hugo version
if ($versionText -match 'extended') {
Write-Host "Hugo Extended ready: $versionText"
return $true
}
return $false
}
function Try-ChocoInstall {
if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
return $false
}
Write-Host 'Attempting Hugo Extended install via Chocolatey...'
choco install hugo-extended -y --no-progress
return $LASTEXITCODE -eq 0
}
function Try-WingetInstall {
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
return $false
}
Write-Host 'Attempting Hugo Extended install via Winget...'
winget install --id Hugo.Hugo.Extended --silent --accept-package-agreements --accept-source-agreements --disable-interactivity
return $LASTEXITCODE -eq 0
}
function Try-DirectZipInstall {
Write-Host 'Attempting Hugo Extended direct download install...'
$apiUrl = 'https://api.github.com/repos/gohugoio/hugo/releases/latest'
$release = Invoke-RestMethod -Uri $apiUrl -Headers @{ 'User-Agent' = 'BlogForEveryone-CI' }
$asset = $release.assets | Where-Object { $_.name -match '^hugo_extended_.*_windows-amd64\.zip$' } | Select-Object -First 1
if (-not $asset) {
return $false
}
$zipPath = Join-Path $env:RUNNER_TEMP 'hugo-extended.zip'
$extractDir = Join-Path $env:RUNNER_TEMP 'hugo-extended-bin'
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zipPath
if (Test-Path $extractDir) {
Remove-Item -Recurse -Force $extractDir
}
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
$envPath = $env:GITHUB_PATH
if (-not $envPath) {
return $false
}
Add-Content -Path $envPath -Value $extractDir
$env:PATH = "$extractDir;$env:PATH"
return $true
}
if (Test-HugoExtended) {
exit 0
}
$installed = $false
try { if (Try-ChocoInstall) { $installed = Test-HugoExtended } } catch { Write-Warning "Chocolatey install failed: $($_.Exception.Message)" }
if (-not $installed) {
try { if (Try-WingetInstall) { $installed = Test-HugoExtended } } catch { Write-Warning "Winget install failed: $($_.Exception.Message)" }
}
if (-not $installed) {
try { if (Try-DirectZipInstall) { $installed = Test-HugoExtended } } catch { Write-Warning "Direct ZIP install failed: $($_.Exception.Message)" }
}
if (-not $installed) {
throw 'Failed to install Hugo Extended via Chocolatey, Winget, or direct ZIP download.'
}
$hugoCommand = Get-Command hugo -ErrorAction Stop
"HUGO_EXECUTABLE=$($hugoCommand.Source)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Pinned HUGO_EXECUTABLE to $($hugoCommand.Source)"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup Dart Sass
shell: pwsh
run: |
$installed = $false
try {
choco install sass --yes --no-progress
$installed = $true
} catch {
Write-Warning "Chocolatey Sass install failed: $($_.Exception.Message)"
}
if (-not $installed) {
Write-Host "Falling back to pnpm global sass install"
pnpm add -g sass
}
$sassCommand = Get-Command sass -ErrorAction Stop
$sassExecutable = $sassCommand.Source
if ($sassExecutable -match '\.ps1$') {
$cmdCandidate = [IO.Path]::ChangeExtension($sassExecutable, '.cmd')
if (Test-Path $cmdCandidate) {
$sassExecutable = $cmdCandidate
} else {
$batCandidate = [IO.Path]::ChangeExtension($sassExecutable, '.bat')
if (Test-Path $batCandidate) {
$sassExecutable = $batCandidate
}
}
}
if (-not (Test-Path $sassExecutable)) {
throw "Resolved Sass executable does not exist: $sassExecutable"
}
& $sassExecutable --version
"SASS_EXECUTABLE=$sassExecutable" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Pinned SASS_EXECUTABLE to $sassExecutable"
- name: Restore signing certificate into temp file
shell: pwsh
run: |
$pfxPath = Join-Path $env:RUNNER_TEMP "codesign.pfx"
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String('${{ secrets.BFE_CODESIGN_PFX_B64 }}'))
"CSC_LINK=$pfxPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"CSC_KEY_PASSWORD=${{ secrets.BFE_CODESIGN_PFX_PASSWORD }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Verify signing env
run: pnpm run verify:windows-signing-env
- name: Run full release pipeline
run: pnpm run release
- name: Verify installer signature
shell: pwsh
run: |
$exe = Get-ChildItem dist -Filter "*Setup-*.exe" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if (-not $exe) { throw "No installer found in dist/." }
$signTool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
Sort-Object FullName -Descending | Select-Object -First 1
if (-not $signTool) { throw "signtool.exe not found" }
& $signTool.FullName verify /pa /v $exe.FullName
- name: Upload release artifacts for audit
uses: actions/upload-artifact@v4
with:
name: windows-release-assets-${{ github.ref_name }}
path: |
dist/*.exe
dist/*.blockmap
dist/latest.yml
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: |
dist/*.exe
dist/*.blockmap
dist/latest.yml
- name: Cleanup certificate file
if: always()
shell: pwsh
run: |
Remove-Item (Join-Path $env:RUNNER_TEMP "codesign.pfx") -ErrorAction SilentlyContinue