Skip to content

Build and Test - Windows #6

Build and Test - Windows

Build and Test - Windows #6

Workflow file for this run

name: "Build and Test - Windows"
on:
workflow_dispatch:
inputs:
build-type:
description: 'Build type'
required: true
type: choice
options:
- debug
- release
default: release
sign-build:
description: 'Sign the build'
required: true
type: boolean
default: true
test-signing:
description: 'Run pre-build signing test (optional)'
required: false
type: boolean
default: false
upload-artifacts:
description: 'Upload build artifacts'
required: true
type: boolean
default: true
# Cancel duplicate workflow runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
jobs:
build-windows:
name: Build Windows (x64)
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version from tauri.conf.json
id: get-version
shell: bash
run: |
VERSION=$(grep -o '"version": "[^"]*"' frontend/src-tauri/tauri.conf.json | cut -d'"' -f4)
echo "Application version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Determine build profile
id: build-profile
shell: bash
run: |
if [[ "${{ github.event.inputs.build-type }}" == "debug" ]]; then
echo "profile=debug" >> "$GITHUB_OUTPUT"
echo "args=--debug" >> "$GITHUB_OUTPUT"
echo "Build profile: debug"
else
echo "profile=release" >> "$GITHUB_OUTPUT"
echo "args=" >> "$GITHUB_OUTPUT"
echo "Build profile: release"
fi
- name: Setup DigiCert Environment
if: ${{ github.event.inputs.sign-build == 'true' }}
shell: pwsh
run: |
# Set environment variables
"SM_HOST=${{ secrets.SM_HOST }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"SM_API_KEY=${{ secrets.SM_API_KEY }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
"SM_CODE_SIGNING_CERT_SHA1_HASH=${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
# Decode and save client certificate using PowerShell
$certPath = "D:\Certificate_pkcs12.p12"
$base64String = "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}"
$certBytes = [System.Convert]::FromBase64String($base64String)
[System.IO.File]::WriteAllBytes($certPath, $certBytes)
# Verify the certificate file was created and has content
if (Test-Path $certPath) {
$certFile = Get-Item $certPath
Write-Host "Certificate file created: $certPath"
Write-Host " Size: $($certFile.Length) bytes"
Write-Host " Last Modified: $($certFile.LastWriteTime)"
# Verify it's a valid PKCS12 file by checking if we can load it
try {
$testCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "${{ secrets.SM_CLIENT_CERT_PASSWORD }}")
Write-Host " ✓ Certificate file is valid PKCS12 format"
Write-Host " ✓ Password is correct"
Write-Host " Certificate Subject: $($testCert.Subject)"
Write-Host " Certificate Thumbprint: $($testCert.Thumbprint)"
} catch {
Write-Warning "⚠ Certificate validation failed"
Write-Warning "Error: $($_.Exception.Message)"
Write-Warning "This may cause issues with signing, but we'll continue..."
}
} else {
Write-Error "Certificate file was not created at $certPath"
exit 1
}
# Set the environment variable with Windows-style path
"SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Setup DigiCert KeyLocker
if: ${{ github.event.inputs.sign-build == 'true' }}
uses: digicert/ssm-code-signing@v1.1.1
- name: Sync Certificate to Windows Certificate Store
if: ${{ github.event.inputs.sign-build == 'true' }}
shell: pwsh
run: |
Write-Host "============================================================"
Write-Host "=== SYNCING CERTIFICATE TO WINDOWS CERTIFICATE STORE ==="
Write-Host "============================================================"
Write-Host "This step syncs the DigiCert certificate from KeyLocker HSM"
Write-Host "to the Windows Certificate Store (required for signing)"
Write-Host ""
# First, get the keypair alias from smctl keypair ls
Write-Host "Retrieving keypair alias from DigiCert KeyLocker..."
$keypairOutput = smctl keypair ls 2>&1 | Out-String
Write-Host $keypairOutput
# Extract the alias (looking for pattern like "key_XXXXXXXXXX")
$aliasMatch = [regex]::Match($keypairOutput, 'key_\d+')
if ($aliasMatch.Success) {
$keypairAlias = $aliasMatch.Value
Write-Host "✓ Found keypair alias: $keypairAlias"
} else {
Write-Error "✗ Could not find keypair alias in smctl output"
Write-Error "Output was: $keypairOutput"
exit 1
}
Write-Host ""
Write-Host "Syncing certificate using alias: $keypairAlias"
$certsyncOutput = smctl windows certsync --keypair-alias $keypairAlias 2>&1
Write-Host $certsyncOutput
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Error "✗ Certificate sync FAILED"
Write-Error "Exit code: $LASTEXITCODE"
Write-Error "Output: $certsyncOutput"
Write-Error ""
Write-Error "Possible causes:"
Write-Error " 1. Keypair alias '$keypairAlias' not found in KeyLocker"
Write-Error " 2. Certificate is revoked or expired"
Write-Error " 3. API credentials are invalid"
exit 1
}
Write-Host ""
Write-Host "✓ Certificate synced successfully"
Write-Host ""
# Verify certificate is now in Windows store
Write-Host "Verifying certificate in Windows Certificate Store..."
$cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1
if (-not $cert) {
# Try LocalMachine store
$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1
}
if ($cert) {
Write-Host "✓ Certificate found in Windows Certificate Store"
Write-Host " Subject: $($cert.Subject)"
Write-Host " Issuer: $($cert.Issuer)"
Write-Host " Thumbprint: $($cert.Thumbprint)"
Write-Host " Valid From: $($cert.NotBefore)"
Write-Host " Valid Until: $($cert.NotAfter)"
} else {
Write-Warning "Certificate not found in Windows Certificate Store after sync"
Write-Warning "Signing may fail. Continuing anyway..."
}
# Export keypair alias for Tauri signCommand
Write-Host ""
Write-Host "Exporting DIGICERT_KEYPAIR_ALIAS for Tauri signCommand..."
"DIGICERT_KEYPAIR_ALIAS=$keypairAlias" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "✓ DIGICERT_KEYPAIR_ALIAS=$keypairAlias"
Write-Host ""
Write-Host "============================================================"
Write-Host ""
- name: Verify DigiCert Setup
if: ${{ github.event.inputs.sign-build == 'true' }}
shell: pwsh
run: |
Write-Host "=== DigiCert Setup Verification ==="
Write-Host ""
# Verify SMCTL is available
Write-Host "SMCTL Version:"
smctl --version
Write-Host ""
# Check environment variables
Write-Host "Environment Variables:"
Write-Host " SM_HOST: $env:SM_HOST"
Write-Host " SM_API_KEY: $($env:SM_API_KEY.Substring(0, [Math]::Min(20, $env:SM_API_KEY.Length)))..."
Write-Host " SM_CLIENT_CERT_FILE: $env:SM_CLIENT_CERT_FILE"
Write-Host " SM_CLIENT_CERT_PASSWORD: $(if ($env:SM_CLIENT_CERT_PASSWORD) { '***SET***' } else { 'NOT SET' })"
Write-Host ""
# Verify client certificate file exists and check size
if (Test-Path $env:SM_CLIENT_CERT_FILE) {
$certFile = Get-Item $env:SM_CLIENT_CERT_FILE
Write-Host "Client Certificate File:"
Write-Host " Path: $($certFile.FullName)"
Write-Host " Size: $($certFile.Length) bytes"
Write-Host " Last Modified: $($certFile.LastWriteTime)"
} else {
Write-Error "Client certificate file NOT FOUND: $env:SM_CLIENT_CERT_FILE"
}
Write-Host ""
# List available keypairs (verifies API authentication works)
Write-Host "Available Keypairs:"
smctl keypair ls
Write-Host ""
# Verify certificate (healthcheck)
Write-Host "DigiCert Healthcheck:"
smctl healthcheck
Write-Host ""
# Note: Healthcheck may show cert path/password warning but if keypair ls works,
# the signing should still work. We'll test in the pre-build signing test.
- name: Pre-Build Signing Test
if: ${{ github.event.inputs.sign-build == 'true' && github.event.inputs.test-signing == 'true' }}
shell: pwsh
run: |
Write-Host "============================================================"
Write-Host "=== PRE-BUILD SIGNING TEST ==="
Write-Host "============================================================"
Write-Host "Testing signing before Tauri build to fail fast if signing is broken"
Write-Host ""
Write-Host "Environment Configuration:"
Write-Host " SM_HOST: $($env:SM_HOST ?? 'NOT SET')"
Write-Host " SM_API_KEY: $(if ($env:SM_API_KEY) { $env:SM_API_KEY.Substring(0, [Math]::Min(20, $env:SM_API_KEY.Length)) + '...' } else { 'NOT SET' })"
Write-Host " SM_CLIENT_CERT_FILE: $($env:SM_CLIENT_CERT_FILE ?? 'NOT SET')"
Write-Host " SM_CODE_SIGNING_CERT_SHA1_HASH: $($env:SM_CODE_SIGNING_CERT_SHA1_HASH ?? 'NOT SET')"
# Verify environment is set up
if (-not $env:SM_HOST -or -not $env:SM_API_KEY) {
Write-Error "DigiCert environment not configured. Make sure 'Sign the build' is enabled."
exit 1
}
Write-Host ""
# Verify client certificate exists
if (Test-Path $env:SM_CLIENT_CERT_FILE) {
Write-Host "✓ Client certificate file exists"
$certInfo = Get-Item $env:SM_CLIENT_CERT_FILE
Write-Host " File size: $($certInfo.Length) bytes"
} else {
Write-Error "✗ Client certificate file not found: $env:SM_CLIENT_CERT_FILE"
exit 1
}
Write-Host ""
Write-Host "--- Available Keypairs in DigiCert KeyLocker ---"
$keypairOutput = smctl keypair ls 2>&1
Write-Host $keypairOutput
Write-Host ""
Write-Host "Note: Certificate was already synced to Windows Certificate Store in previous step"
Write-Host ""
# Create a minimal test executable using dotnet
Write-Host "--- Creating Test Executable ---"
Write-Host "Creating minimal C# console app..."
# Create simple C# file
$testCode = @"
using System;
class Program {
static void Main() {
Console.WriteLine("Test signing executable");
}
}
"@
Set-Content -Path "TestSigning.cs" -Value $testCode
# Create minimal project file
$projectFile = @"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
"@
Set-Content -Path "TestSigning.csproj" -Value $projectFile
# Build executable in one command
Write-Host "Building with dotnet publish..."
dotnet publish TestSigning.csproj -c Release -o . --self-contained false 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to build test executable"
Write-Host "Attempting to list dotnet info for debugging:"
dotnet --version
exit 1
}
if (-not (Test-Path "TestSigning.exe")) {
Write-Error "TestSigning.exe was not created"
Get-ChildItem -Filter "*.exe" | Format-Table
exit 1
}
$exeInfo = Get-Item "TestSigning.exe"
Write-Host "✓ Test executable compiled successfully"
Write-Host " File: $($exeInfo.FullName)"
Write-Host " Size: $($exeInfo.Length) bytes"
# Verify unsigned state
Write-Host ""
Write-Host "--- Checking Unsigned State ---"
$unsignedSig = Get-AuthenticodeSignature -FilePath "TestSigning.exe"
Write-Host "Pre-signing status: $($unsignedSig.Status) (expected: NotSigned)"
# Sign the test executable with signtool (certificate already in Windows store)
Write-Host ""
Write-Host "--- Signing Test Executable with SignTool ---"
Write-Host "Certificate is already synced to Windows Certificate Store"
Write-Host "Detecting certificate store location..."
Write-Host ""
# Extract keypair alias (needed for /k flag with /csp)
$aliasMatch = [regex]::Match($keypairOutput, 'key_\d+')
if ($aliasMatch.Success) {
$keypairAlias = $aliasMatch.Value
Write-Host "✓ Found keypair alias: $keypairAlias"
} else {
Write-Error "✗ Could not find keypair alias in smctl output"
exit 1
}
Write-Host ""
# Detect which store the certificate is in
$certCurrentUser = Get-ChildItem -Path Cert:\CurrentUser\My -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1
$certLocalMachine = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $env:SM_CODE_SIGNING_CERT_SHA1_HASH } | Select-Object -First 1
$storeFlag = ""
$storeName = ""
if ($certCurrentUser) {
Write-Host "✓ Certificate found in CurrentUser\My store"
$storeFlag = "/s My"
$storeName = "CurrentUser\My"
} elseif ($certLocalMachine) {
Write-Host "✓ Certificate found in LocalMachine\My store"
$storeFlag = "/sm /s My"
$storeName = "LocalMachine\My"
} else {
Write-Error "✗ Certificate not found in CurrentUser\My or LocalMachine\My stores"
Write-Error "SHA1 hash: $env:SM_CODE_SIGNING_CERT_SHA1_HASH"
exit 1
}
Write-Host "Using store: $storeName"
Write-Host "SHA1 hash: $env:SM_CODE_SIGNING_CERT_SHA1_HASH"
Write-Host "Keypair alias: $keypairAlias"
Write-Host ""
# Test using the exact same command as tauri.conf.json signCommand
Write-Host "Testing sign-windows.ps1 script (same as Tauri signCommand)..."
Write-Host "Command: powershell -ExecutionPolicy Bypass -File frontend/src-tauri/scripts/sign-windows.ps1 -FilePath TestSigning.exe"
Write-Host ""
# Set DIGICERT_KEYPAIR_ALIAS for the script (already exported to GITHUB_ENV, but set locally too)
$env:DIGICERT_KEYPAIR_ALIAS = $keypairAlias
$signOutput = powershell -ExecutionPolicy Bypass -File "frontend/src-tauri/scripts/sign-windows.ps1" -FilePath "TestSigning.exe" 2>&1
Write-Host $signOutput
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host "============================================================"
Write-Error "✗✗✗ SIGNING FAILED ✗✗✗"
Write-Host "============================================================"
Write-Error "Exit code: $LASTEXITCODE"
Write-Error "Script output: $signOutput"
Write-Error ""
Write-Error "Certificate was found in: $storeName"
Write-Error "DIGICERT_KEYPAIR_ALIAS: $keypairAlias"
Write-Error ""
exit 1
}
Write-Host ""
Write-Host "✓ Test executable signed successfully using sign-windows.ps1"
# Verify the signature
Write-Host ""
Write-Host "--- Verifying Signature ---"
$signature = Get-AuthenticodeSignature -FilePath "TestSigning.exe"
Write-Host "Signature Details:"
Write-Host " Status: $($signature.Status)"
Write-Host " Status Message: $($signature.StatusMessage)"
Write-Host " Signer Certificate:"
Write-Host " Subject: $($signature.SignerCertificate.Subject)"
Write-Host " Issuer: $($signature.SignerCertificate.Issuer)"
Write-Host " Thumbprint: $($signature.SignerCertificate.Thumbprint)"
Write-Host " Valid From: $($signature.SignerCertificate.NotBefore)"
Write-Host " Valid To: $($signature.SignerCertificate.NotAfter)"
if ($signature.TimeStamperCertificate) {
Write-Host " Timestamp Certificate:"
Write-Host " Subject: $($signature.TimeStamperCertificate.Subject)"
Write-Host " Issuer: $($signature.TimeStamperCertificate.Issuer)"
}
# Validate signature
if ($signature.Status -ne 'Valid') {
Write-Host ""
Write-Host "============================================================"
Write-Error "✗✗✗ SIGNATURE VERIFICATION FAILED ✗✗✗"
Write-Host "============================================================"
Write-Error "Expected Status: Valid"
Write-Error "Actual Status: $($signature.Status)"
Write-Error "Status Message: $($signature.StatusMessage)"
exit 1
}
if (-not $signature.TimeStamperCertificate) {
Write-Host ""
Write-Host "============================================================"
Write-Error "✗✗✗ MISSING TIMESTAMP ✗✗✗"
Write-Host "============================================================"
Write-Error "Timestamp certificate is missing!"
Write-Error "Without timestamp, signature will become invalid when cert expires"
exit 1
}
Write-Host ""
Write-Host "✓ Signature verified successfully"
Write-Host "✓ Timestamp present"
Write-Host ""
Write-Host "--- Cleanup ---"
Remove-Item "TestSigning.exe" -ErrorAction SilentlyContinue
Remove-Item "TestSigning.cs" -ErrorAction SilentlyContinue
Remove-Item "TestSigning.csproj" -ErrorAction SilentlyContinue
Remove-Item "TestSigning.dll" -ErrorAction SilentlyContinue
Remove-Item "TestSigning.pdb" -ErrorAction SilentlyContinue
Remove-Item "TestSigning.deps.json" -ErrorAction SilentlyContinue
Remove-Item "TestSigning.runtimeconfig.json" -ErrorAction SilentlyContinue
Write-Host "✓ Test files cleaned up"
Write-Host ""
Write-Host "============================================================"
Write-Host "=== PRE-BUILD SIGNING TEST PASSED ==="
Write-Host "============================================================"
Write-Host "Signing infrastructure is working correctly"
Write-Host "Proceeding with Tauri build..."
Write-Host ""
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
run_install: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: ". -> target"
key: windows-x64-vulkan-v2
- name: Install Vulkan SDK
uses: humbletim/install-vulkan-sdk@v1.2
with:
version: 1.4.309.0
cache: true
- name: Copy Vulkan runtime for bundling
shell: pwsh
run: |
# Create runtime directory
$runtimeDir = "frontend/src-tauri/vulkan-runtime"
if (!(Test-Path $runtimeDir)) {
New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
}
# Copy Vulkan loader DLL from SDK
$vulkanSdk = $env:VULKAN_SDK
Write-Host "Vulkan SDK path: $vulkanSdk"
$vulkanDll = "$vulkanSdk\Bin\vulkan-1.dll"
if (Test-Path $vulkanDll) {
Copy-Item $vulkanDll -Destination $runtimeDir
Write-Host "Copied vulkan-1.dll to $runtimeDir"
} else {
Write-Warning "vulkan-1.dll not found at $vulkanDll"
}
# Verify
Write-Host "Contents of vulkan-runtime directory:"
Get-ChildItem $runtimeDir
- name: Install frontend dependencies
run: |
cd frontend
pnpm install
- name: Build llama-helper sidecar
shell: bash
run: |
echo "Building llama-helper sidecar with Vulkan support..."
cargo build --release -p llama-helper --features vulkan
# Copy binary to binaries directory
mkdir -p frontend/src-tauri/binaries
cp target/release/llama-helper.exe frontend/src-tauri/binaries/llama-helper-x86_64-pc-windows-msvc.exe
echo "Copied llama-helper to frontend/src-tauri/binaries/"
ls -la frontend/src-tauri/binaries/
- name: Build Tauri app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Tauri updater signing (ALWAYS enabled for .sig files)
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# License validation RSA public key (embedded at build time)
MEETILY_RSA_PUBLIC_KEY: ${{ secrets.MEETILY_RSA_PUBLIC_KEY }}
# Supabase configuration (for online license verification)
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
with:
projectPath: frontend
args: --target x86_64-pc-windows-msvc --features vulkan ${{ steps.build-profile.outputs.args }}
- name: Verify build artifacts
shell: bash
run: |
echo "## Build Artifacts"
find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle -type f \( -name "*.msi" -o -name "*.exe" \) -exec ls -lh {} \;
- name: Upload artifacts
if: ${{ github.event.inputs.upload-artifacts == 'true' }}
uses: actions/upload-artifact@v4
with:
name: meetily-windows-x64-${{ steps.build-profile.outputs.profile }}-${{ steps.get-version.outputs.version }}
path: |
target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi/*.msi
target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi/*.msi.sig
target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis/*.exe
target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis/*.exe.sig
retention-days: 30
- name: Generate build summary
shell: bash
run: |
echo "## 🪟 Windows Build Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| **Version** | ${{ steps.get-version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Build Profile** | ${{ steps.build-profile.outputs.profile }} |" >> $GITHUB_STEP_SUMMARY
echo "| **Target** | x86_64-pc-windows-msvc |" >> $GITHUB_STEP_SUMMARY
echo "| **Signed** | ${{ (github.event.inputs.sign-build == 'true') && '✅ Yes' || '❌ No' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Build Artifacts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| File Type | Count |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| MSI Installers | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi -name "*.msi" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY
echo "| MSI Signatures | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/msi -name "*.msi.sig" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY
echo "| NSIS Installers | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis -name "*.exe" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY
echo "| NSIS Signatures | $(find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle/nsis -name "*.exe.sig" 2>/dev/null | wc -l) |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "<details>" >> $GITHUB_STEP_SUMMARY
echo "<summary>📋 File List</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
find target/x86_64-pc-windows-msvc/${{ steps.build-profile.outputs.profile }}/bundle -type f \( -name "*.msi" -o -name "*.exe" -o -name "*.sig" \) 2>/dev/null >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY