more more more more more more more fixes #314
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Windows | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| branches: | |
| - main | |
| - feature | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Debug checkout info | |
| run: | | |
| Write-Host "=== DEBUG: Checkout completed ===" | |
| Write-Host "Working directory: $(Get-Location)" | |
| Write-Host "Git ref: ${{ github.ref }}" | |
| Write-Host "Git SHA: ${{ github.sha }}" | |
| Write-Host "Git ref name: ${{ github.ref_name }}" | |
| Write-Host "Repository: ${{ github.repository }}" | |
| Write-Host "Contents of root directory:" | |
| Get-ChildItem -ErrorAction SilentlyContinue | Select-Object Name, Length | Format-Table | |
| - name: Set up Python (pinned) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.13.7' | |
| cache: 'pip' | |
| - name: "Install dependencies (release: with hash checking per 2.8)" | |
| run: | | |
| Write-Host "=== DEBUG: Installing dependencies ===" | |
| python --version | |
| python -m pip install --upgrade pip | |
| if ("${{ github.ref }}" -like "refs/tags/v*") { | |
| Write-Host "Release build: using pip --require-hashes (2.8)" | |
| python -m pip install "pip<26" | |
| python -m pip install pip-tools | |
| python scripts/generate_requirements_hashes.py | |
| pip install -r requirements-build-hashed.txt --require-hashes | |
| } else { | |
| Write-Host "Non-release build: installing from requirements-build.txt" | |
| pip install -r requirements-build.txt | |
| } | |
| Write-Host "Installing build requirements (runtime deps for GUI app)..." | |
| Write-Host "PyInstaller version (pinned from requirements-build.txt):" | |
| pyinstaller --version | |
| Write-Host "Installing Playwright browsers (for testing, not bundled)..." | |
| python -m playwright install chromium | |
| Write-Host "Verifying installed packages match requirements.txt runtime dependencies..." | |
| Write-Host "Core packages:" | |
| pip list | Select-String -Pattern "PySide6|requests|aiohttp|beautifulsoup4|ddgs|rapidfuzz|dateutil|pyyaml|tqdm|requests-cache|playwright|selenium|openpyxl|pyinstaller" | |
| Write-Host "Checking for missing dependencies..." | |
| $required = @("PySide6", "requests", "aiohttp", "beautifulsoup4", "ddgs", "rapidfuzz", "python-dateutil", "pyyaml", "tqdm", "requests-cache", "playwright", "selenium", "openpyxl") | |
| $installed = pip list --format=json | ConvertFrom-Json | ForEach-Object { $_.name.ToLower() } | |
| $missing = @() | |
| foreach ($pkg in $required) { | |
| if ($installed -notcontains $pkg.ToLower()) { | |
| $missing += $pkg | |
| } | |
| } | |
| if ($missing.Count -gt 0) { | |
| Write-Warning "Missing packages: $($missing -join ', ')" | |
| } else { | |
| Write-Host "[OK] All required runtime dependencies are installed" | |
| } | |
| - name: Generate third-party licenses file (Step 11) | |
| run: | | |
| Write-Host "=== Generating third-party licenses (Step 11) ===" | |
| python scripts/generate_licenses.py --output THIRD_PARTY_LICENSES.txt | |
| if (-not (Test-Path "THIRD_PARTY_LICENSES.txt")) { | |
| Write-Error "License bundle generation failed - THIRD_PARTY_LICENSES.txt not created" | |
| exit 1 | |
| } | |
| $content = Get-Content "THIRD_PARTY_LICENSES.txt" -Raw | |
| if (-not $content -match "Package:") { | |
| Write-Error "License bundle appears empty or invalid" | |
| exit 1 | |
| } | |
| Write-Host "Licenses file created: $(Get-Item THIRD_PARTY_LICENSES.txt | Select-Object -ExpandProperty Length) bytes" | |
| - name: Install NSIS | |
| uses: negrutiu/nsis-install@v2 | |
| with: | |
| distro: negrutiu | |
| arch: x86 | |
| - name: Sync version from git tag | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| Write-Host "=== DEBUG: Syncing version from git tag ===" | |
| Write-Host "Git ref: ${{ github.ref }}" | |
| Write-Host "Git ref name: ${{ github.ref_name }}" | |
| Write-Host "Running: python scripts/sync_version.py --tag ${{ github.ref_name }}" | |
| python scripts/sync_version.py --tag ${{ github.ref_name }} | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "Version sync failed" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Version synced successfully" | |
| - name: Set build info | |
| run: | | |
| Write-Host "=== DEBUG: Setting build info ===" | |
| Write-Host "GITHUB_RUN_NUMBER: ${{ github.run_number }}" | |
| Write-Host "GITHUB_RUN_ID: ${{ github.run_id }}" | |
| Write-Host "Running: python scripts/set_build_info.py" | |
| python scripts/set_build_info.py | |
| Write-Host "Build info script completed" | |
| env: | |
| GITHUB_RUN_NUMBER: ${{ github.run_number }} | |
| GITHUB_RUN_ID: ${{ github.run_id }} | |
| - name: Generate version info | |
| run: | | |
| Write-Host "=== DEBUG: Generating version info ===" | |
| Write-Host "Current directory: $(Get-Location)" | |
| Write-Host "Running: python scripts/generate_version_info.py" | |
| python scripts/generate_version_info.py | |
| if (Test-Path "build\version_info.txt") { | |
| Write-Host "[OK] version_info.txt created" | |
| Write-Host "File size: $(Get-Item build\version_info.txt | Select-Object -ExpandProperty Length) bytes" | |
| Write-Host "First 20 lines:" | |
| Get-Content build\version_info.txt -Head 20 | |
| } else { | |
| Write-Error "version_info.txt not found after generation" | |
| exit 1 | |
| } | |
| - name: Validate version | |
| run: | | |
| Write-Host "=== DEBUG: Validating version ===" | |
| Write-Host "Git ref: ${{ github.ref }}" | |
| Write-Host "Git ref name: ${{ github.ref_name }}" | |
| Write-Host "Running: python scripts/validate_version.py" | |
| python scripts/validate_version.py | |
| Write-Host "Version validation completed" | |
| - name: Generate application icons | |
| run: | | |
| Write-Host "=== Generating application icons (taskbar/dock) ===" | |
| python scripts/generate_icons.py | |
| if (Test-Path "build\icon.ico") { | |
| Write-Host "[OK] icon.ico created for Windows" | |
| } else { | |
| Write-Error "icon.ico not found after generate_icons.py" | |
| exit 1 | |
| } | |
| - name: Verify Python DLL exists | |
| run: | | |
| Write-Host "=== DEBUG: Verifying Python DLL ===" | |
| $pythonVersion = python --version | |
| Write-Host "Python version: $pythonVersion" | |
| $pythonExe = python -c "import sys; print(sys.executable)" | |
| Write-Host "Python executable: $pythonExe" | |
| $pythonDir = Split-Path -Parent $pythonExe | |
| Write-Host "Python directory: $pythonDir" | |
| $pythonDllName = python -c "import sys; print(f'python{sys.version_info.major}{sys.version_info.minor}.dll')" | |
| Write-Host "Python DLL name: $pythonDllName" | |
| $pythonDllPath = Join-Path $pythonDir $pythonDllName | |
| Write-Host "Python DLL path: $pythonDllPath" | |
| if (Test-Path $pythonDllPath) { | |
| Write-Host "[OK] Python DLL found at: $pythonDllPath" | |
| $dllInfo = Get-Item $pythonDllPath | |
| Write-Host " Size: $($dllInfo.Length) bytes" | |
| Write-Host " Last modified: $($dllInfo.LastWriteTime)" | |
| } else { | |
| Write-Error "Python DLL NOT FOUND at: $pythonDllPath" | |
| Write-Host "Checking alternative locations..." | |
| $altPaths = @( | |
| (Join-Path $pythonDir "DLLs\$pythonDllName"), | |
| (Join-Path $pythonDir "python3.dll") | |
| ) | |
| foreach ($altPath in $altPaths) { | |
| if (Test-Path $altPath) { | |
| Write-Host " Found at alternative location: $altPath" | |
| } | |
| } | |
| exit 1 | |
| } | |
| - name: Build with PyInstaller | |
| run: | | |
| Write-Host "=== DEBUG: Building with PyInstaller ===" | |
| Write-Host "Current directory: $(Get-Location)" | |
| Write-Host "Python version:" | |
| python --version | |
| Write-Host "PyInstaller version:" | |
| pyinstaller --version | |
| Write-Host "Checking if build directory exists:" | |
| if (Test-Path "build") { | |
| Write-Host " build/ exists" | |
| Write-Host " Contents:" | |
| Get-ChildItem build\ -ErrorAction SilentlyContinue | Format-Table Name, Length | |
| } else { | |
| Write-Host " build/ does not exist (will be created)" | |
| } | |
| Write-Host "Checking spec file for DLL inclusion:" | |
| $specFile = "build\pyinstaller.spec" | |
| if (Test-Path $specFile) { | |
| $specContent = Get-Content $specFile -Raw | |
| if ($specContent -match "python313\.dll|python_dll_name") { | |
| Write-Host " [OK] Spec file includes Python DLL collection code" | |
| } else { | |
| Write-Host " [WARNING] Spec file may not include DLL collection" | |
| } | |
| } | |
| Write-Host "Running: python scripts/build_pyinstaller.py" | |
| python scripts/build_pyinstaller.py | |
| Write-Host "PyInstaller build completed" | |
| Write-Host "Checking dist/ directory:" | |
| if (Test-Path "dist") { | |
| Write-Host " dist/ exists" | |
| Write-Host " Contents:" | |
| Get-ChildItem dist\ -ErrorAction SilentlyContinue | Format-Table Name, Length, LastWriteTime | |
| } else { | |
| Write-Error "dist/ directory not found after build" | |
| exit 1 | |
| } | |
| Write-Host "=== Verifying DLL inclusion in built executable ===" | |
| $exePath = "dist\CuePoint.exe" | |
| if (Test-Path $exePath) { | |
| Write-Host "Checking if python313.dll is in the executable bundle..." | |
| # Note: We can't easily check inside the exe without extracting it | |
| # But we can verify the build logs showed DLL inclusion | |
| Write-Host " [INFO] DLL inclusion should be verified in build logs above" | |
| Write-Host " [INFO] Look for messages like: '[PyInstaller] Including Python DLL'" | |
| Write-Host " [INFO] Or: '[PyInstaller] Verified: python313.dll is in binaries list'" | |
| } else { | |
| Write-Host " [WARNING] CuePoint.exe not found in dist/" | |
| } | |
| - name: Import certificate | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| WINDOWS_CERT_PFX: ${{ secrets.WINDOWS_CERT_PFX }} | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| Write-Host "=== DEBUG: Importing certificate ===" | |
| Write-Host "Git ref: ${{ github.ref }}" | |
| Write-Host "Checking for certificate secrets..." | |
| $hasCert = -not [string]::IsNullOrEmpty($env:WINDOWS_CERT_PFX) | |
| $hasPassword = -not [string]::IsNullOrEmpty($env:WINDOWS_CERT_PASSWORD) | |
| Write-Host " WINDOWS_CERT_PFX present: $hasCert" | |
| Write-Host " WINDOWS_CERT_PASSWORD present: $hasPassword" | |
| if (-not $hasCert -or -not $hasPassword) { | |
| Write-Host "[SKIP] Certificate secrets not available, skipping import" | |
| exit 0 | |
| } | |
| Write-Host "Certificate data length: $($env:WINDOWS_CERT_PFX.Length) characters" | |
| Write-Host "Decoding certificate from base64..." | |
| $certBytes = [System.Convert]::FromBase64String($env:WINDOWS_CERT_PFX) | |
| Write-Host "Certificate binary size: $($certBytes.Length) bytes" | |
| $certPath = "cert.pfx" | |
| Write-Host "Writing certificate to: $certPath" | |
| [System.IO.File]::WriteAllBytes($certPath, $certBytes) | |
| if (Test-Path $certPath) { | |
| Write-Host "[OK] Certificate imported successfully" | |
| Write-Host "Certificate file size: $(Get-Item $certPath | Select-Object -ExpandProperty Length) bytes" | |
| } else { | |
| Write-Error "Certificate file not found after write" | |
| exit 1 | |
| } | |
| - name: Sign executable | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| WINDOWS_CERT_PFX: ${{ secrets.WINDOWS_CERT_PFX }} | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| Write-Host "=== DEBUG: Signing executable ===" | |
| Write-Host "Checking for certificate secrets..." | |
| $hasCert = -not [string]::IsNullOrEmpty($env:WINDOWS_CERT_PFX) | |
| $hasPassword = -not [string]::IsNullOrEmpty($env:WINDOWS_CERT_PASSWORD) | |
| Write-Host " WINDOWS_CERT_PFX present: $hasCert" | |
| Write-Host " WINDOWS_CERT_PASSWORD present: $hasPassword" | |
| if (-not $hasCert -or -not $hasPassword) { | |
| Write-Host "[SKIP] Certificate secrets not available, skipping signing" | |
| exit 0 | |
| } | |
| $certPath = "cert.pfx" | |
| $exePath = "dist/CuePoint.exe" | |
| Write-Host "Certificate path: $certPath" | |
| Write-Host "Executable path: $exePath" | |
| if (-not (Test-Path $certPath)) { | |
| Write-Error "Certificate file not found: $certPath" | |
| exit 1 | |
| } | |
| if (-not (Test-Path $exePath)) { | |
| Write-Error "Executable not found: $exePath" | |
| exit 1 | |
| } | |
| Write-Host "Executable size before signing: $(Get-Item $exePath | Select-Object -ExpandProperty Length) bytes" | |
| Write-Host "Running signtool sign..." | |
| signtool sign /f $certPath /p "$env:WINDOWS_CERT_PASSWORD" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 $exePath | |
| $signExitCode = $LASTEXITCODE | |
| Write-Host "signtool sign exit code: $signExitCode" | |
| if ($signExitCode -ne 0) { | |
| Write-Error "signtool sign failed with exit code $signExitCode" | |
| exit 1 | |
| } | |
| Write-Host "Executable size after signing: $(Get-Item $exePath | Select-Object -ExpandProperty Length) bytes" | |
| Write-Host "Running signtool verify..." | |
| signtool verify /pa /v $exePath | |
| $verifyExitCode = $LASTEXITCODE | |
| Write-Host "signtool verify exit code: $verifyExitCode" | |
| if ($verifyExitCode -ne 0) { | |
| Write-Error "signtool verify failed with exit code $verifyExitCode" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Executable signed and verified successfully" | |
| - name: Build installer | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| Write-Host "=== DEBUG: Building installer ===" | |
| Write-Host "Git ref: ${{ github.ref }}" | |
| Write-Host "Current directory: $(Get-Location)" | |
| # Verify PyInstaller build completed successfully | |
| Write-Host "Checking for PyInstaller build output..." | |
| $exeFound = $false | |
| if (Test-Path "dist\CuePoint.exe") { | |
| Write-Host "[OK] Found CuePoint.exe in dist/ (onefile mode)" | |
| $exeInfo = Get-Item "dist\CuePoint.exe" | |
| Write-Host " Size: $($exeInfo.Length) bytes" | |
| Write-Host " Last modified: $($exeInfo.LastWriteTime)" | |
| $exeFound = $true | |
| } elseif (Test-Path "dist\CuePoint\CuePoint.exe") { | |
| Write-Host "[OK] Found CuePoint.exe in dist/CuePoint/ (onedir mode)" | |
| $exeInfo = Get-Item "dist\CuePoint\CuePoint.exe" | |
| Write-Host " Size: $($exeInfo.Length) bytes" | |
| Write-Host " Last modified: $($exeInfo.LastWriteTime)" | |
| $exeFound = $true | |
| } | |
| if (-not $exeFound) { | |
| Write-Error "CuePoint.exe not found in dist/ or dist/CuePoint/" | |
| Write-Host "Contents of dist/:" | |
| $distContents = Get-ChildItem dist\ -ErrorAction SilentlyContinue | |
| if ($distContents) { | |
| $distContents | Format-Table Name, Length, LastWriteTime, Attributes | |
| } else { | |
| Write-Host " dist/ is empty or doesn't exist" | |
| } | |
| exit 1 | |
| } | |
| Write-Host "Getting version from version.py..." | |
| $env:PYTHONIOENCODING = "utf-8" | |
| $env:PYTHONUTF8 = "1" | |
| $fullVersion = python -c "import sys; sys.path.insert(0, 'src'); from cuepoint.version import __version__; sys.stdout.write(__version__)" | |
| Write-Host "Full version: $fullVersion" | |
| # Extract base version (X.Y.Z) for NSIS - NSIS requires format X.Y.Z (no prerelease suffixes) | |
| # Remove prerelease suffix (everything after -) and build metadata (everything after +) | |
| $version = $fullVersion | |
| if ($version -match '^([^-+]+)') { | |
| $version = $matches[1] | |
| } | |
| Write-Host "Base version for NSIS: $version" | |
| # Try to find makensis in common locations or PATH | |
| Write-Host "Searching for makensis..." | |
| $makensis = $null | |
| $possiblePaths = @( | |
| "${env:ProgramFiles(x86)}\NSIS\makensis.exe", | |
| "${env:ProgramFiles}\NSIS\makensis.exe", | |
| "C:\Program Files (x86)\NSIS\makensis.exe", | |
| "C:\Program Files\NSIS\makensis.exe" | |
| ) | |
| foreach ($path in $possiblePaths) { | |
| Write-Host " Checking: $path" | |
| if (Test-Path $path) { | |
| $makensis = $path | |
| Write-Host "[OK] Found makensis at: $path" | |
| $versionInfo = & $path /VERSION 2>&1 | |
| Write-Host " NSIS version: $versionInfo" | |
| break | |
| } | |
| } | |
| if (-not $makensis) { | |
| Write-Host " Not found in common paths, checking PATH..." | |
| $makensisCmd = Get-Command makensis -ErrorAction SilentlyContinue | |
| if ($makensisCmd) { | |
| $makensis = $makensisCmd.Path | |
| Write-Host "[OK] Found makensis in PATH at: $makensis" | |
| } | |
| } | |
| if (-not $makensis) { | |
| Write-Error "makensis not found. Please ensure NSIS is installed." | |
| Write-Host "Searched paths:" | |
| foreach ($path in $possiblePaths) { | |
| Write-Host " $path : $(if (Test-Path $path) { 'EXISTS' } else { 'NOT FOUND' })" | |
| } | |
| exit 1 | |
| } | |
| # NSIS File command resolves paths relative to the .nsi file's directory (scripts/) | |
| # The installer script uses relative paths (..\dist\) which work reliably | |
| $currentDir = Get-Location | |
| Write-Host "Current directory: $currentDir" | |
| $exePath = "$currentDir\dist\CuePoint.exe" | |
| Write-Host "Verifying executable exists: $exePath" | |
| if (-not (Test-Path $exePath)) { | |
| Write-Error "CuePoint.exe not found. Current dir: $currentDir" | |
| Write-Host "Contents of dist/:" | |
| Get-ChildItem dist\ -ErrorAction SilentlyContinue | Format-Table Name, Length, LastWriteTime | |
| exit 1 | |
| } | |
| Write-Host "[OK] Executable verified" | |
| # Check if installer script exists | |
| $installerScript = "scripts\installer.nsi" | |
| Write-Host "Checking installer script: $installerScript" | |
| if (-not (Test-Path $installerScript)) { | |
| Write-Error "Installer script not found: $installerScript" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Installer script found" | |
| Write-Host "Script size: $(Get-Item $installerScript | Select-Object -ExpandProperty Length) bytes" | |
| # Run makensis from project root | |
| # The installer script uses relative paths (..\dist\) relative to scripts/ directory | |
| Write-Host "Running makensis..." | |
| Write-Host " Command: $makensis /DVERSION=$version $installerScript" | |
| Write-Host " Working directory: $currentDir" | |
| $makensisResult = & $makensis /DVERSION=$version $installerScript 2>&1 | |
| $makensisExitCode = $LASTEXITCODE | |
| Write-Host "Makensis exit code: $makensisExitCode" | |
| Write-Host "Makensis output:" | |
| Write-Host $makensisResult | |
| if ($makensisExitCode -ne 0) { | |
| Write-Error "makensis failed with exit code $makensisExitCode" | |
| exit 1 | |
| } | |
| # NSIS creates installer with base version in filename | |
| # Rename it to include full version (with prerelease suffix) if different | |
| $baseInstallerName = "CuePoint-Setup-v$version.exe" | |
| $installerName = "CuePoint-Setup-v$fullVersion.exe" | |
| $baseInstallerPath = "dist\$baseInstallerName" | |
| $installerPath = "dist\$installerName" | |
| Write-Host "Waiting for file system to sync..." | |
| Start-Sleep -Seconds 2 | |
| # Rename installer to include full version if different | |
| if ($fullVersion -ne $version -and (Test-Path $baseInstallerPath)) { | |
| Write-Host "Renaming installer to include full version..." | |
| Write-Host " From: $baseInstallerName" | |
| Write-Host " To: $installerName" | |
| Rename-Item -Path $baseInstallerPath -NewName $installerName -Force | |
| Write-Host "[OK] Installer renamed successfully" | |
| } | |
| # Check for installer using expected filename (full version) | |
| $expectedInstaller = "dist\$installerName" | |
| $absInstallerPath = Join-Path $currentDir $expectedInstaller | |
| Write-Host "Checking for installer..." | |
| Write-Host " Expected relative path: $expectedInstaller" | |
| Write-Host " Expected absolute path: $absInstallerPath" | |
| # Try multiple methods to find the file | |
| $installerFiles = @() | |
| # Method 1: Exact path | |
| Write-Host "Method 1: Checking exact path..." | |
| if (Test-Path $expectedInstaller) { | |
| $file = Get-Item $expectedInstaller | |
| $installerFiles += $file | |
| Write-Host "[OK] Found installer using exact path" | |
| Write-Host " File: $($file.FullName)" | |
| Write-Host " Size: $($file.Length) bytes" | |
| Write-Host " Last modified: $($file.LastWriteTime)" | |
| } else { | |
| Write-Host " Not found at relative path" | |
| } | |
| # Method 2: Pattern matching | |
| Write-Host "Method 2: Checking pattern dist\CuePoint-Setup-v*.exe..." | |
| $patternFiles = Get-ChildItem "dist\CuePoint-Setup-v*.exe" -ErrorAction SilentlyContinue | |
| if ($patternFiles) { | |
| $installerFiles += $patternFiles | |
| Write-Host "[OK] Found $($patternFiles.Count) file(s) using pattern matching" | |
| $patternFiles | ForEach-Object { Write-Host " - $($_.Name) ($($_.Length) bytes)" } | |
| } else { | |
| Write-Host " No files found with pattern" | |
| } | |
| # Method 3: All exe files with "Setup" in name | |
| Write-Host "Method 3: Checking all .exe files with 'Setup' in name..." | |
| $allSetupFiles = Get-ChildItem "dist\*.exe" -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*Setup*" } | |
| if ($allSetupFiles) { | |
| $installerFiles += $allSetupFiles | |
| Write-Host "[OK] Found $($allSetupFiles.Count) file(s) using wildcard search" | |
| $allSetupFiles | ForEach-Object { Write-Host " - $($_.Name) ($($_.Length) bytes)" } | |
| } else { | |
| Write-Host " No files found with 'Setup' in name" | |
| } | |
| # Remove duplicates | |
| $installerFiles = $installerFiles | Select-Object -Unique | |
| Write-Host "Total unique installer files found: $($installerFiles.Count)" | |
| if ($installerFiles.Count -eq 0) { | |
| Write-Error "Installer was not found. Expected: $expectedInstaller" | |
| Write-Host "Makensis reported creating the file, but it's not accessible." | |
| Write-Host "All files in dist directory:" | |
| $allFiles = Get-ChildItem "dist\" -ErrorAction SilentlyContinue | |
| if ($allFiles) { | |
| $allFiles | Format-Table Name, Length, LastWriteTime, Attributes | |
| } else { | |
| Write-Host " dist/ directory is empty or doesn't exist" | |
| } | |
| Write-Host "Current working directory: $(Get-Location)" | |
| Write-Host "Absolute path check:" | |
| Write-Host " Expected: $absInstallerPath" | |
| Write-Host " Exists: $(Test-Path $absInstallerPath)" | |
| if (Test-Path $absInstallerPath) { | |
| Write-Host " File info:" | |
| Get-Item $absInstallerPath | Format-List | |
| } | |
| exit 1 | |
| } | |
| Write-Host "[OK] Installer found successfully:" | |
| $installerFiles | Format-Table Name, Length, LastWriteTime, FullName | |
| - name: Sign installer | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| env: | |
| WINDOWS_CERT_PFX: ${{ secrets.WINDOWS_CERT_PFX }} | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| Write-Host "=== DEBUG: Signing installer ===" | |
| Write-Host "Checking for certificate secrets..." | |
| $hasCert = -not [string]::IsNullOrEmpty($env:WINDOWS_CERT_PFX) | |
| $hasPassword = -not [string]::IsNullOrEmpty($env:WINDOWS_CERT_PASSWORD) | |
| Write-Host " WINDOWS_CERT_PFX present: $hasCert" | |
| Write-Host " WINDOWS_CERT_PASSWORD present: $hasPassword" | |
| if (-not $hasCert -or -not $hasPassword) { | |
| Write-Host "[SKIP] Certificate secrets not available, skipping installer signing" | |
| exit 0 | |
| } | |
| $certPath = "cert.pfx" | |
| Write-Host "Certificate path: $certPath" | |
| if (-not (Test-Path $certPath)) { | |
| Write-Error "Certificate file not found: $certPath" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Certificate file found" | |
| Write-Host "Searching for installer files..." | |
| Write-Host " Pattern 1: dist\CuePoint-Setup-v*-setup.exe" | |
| $installer = Get-ChildItem dist\CuePoint-Setup-v*-setup.exe -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| if (-not $installer) { | |
| Write-Host " Pattern 1: Not found, trying pattern 2..." | |
| Write-Host " Pattern 2: dist\CuePoint-Setup-v*.exe" | |
| $installer = Get-ChildItem dist\CuePoint-Setup-v*.exe -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| } | |
| if (-not $installer) { | |
| Write-Host " Pattern 2: Not found, trying pattern 3..." | |
| Write-Host " Pattern 3: dist\*-setup.exe" | |
| $installer = Get-ChildItem dist\*-setup.exe -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| } | |
| if (-not $installer) { | |
| Write-Error "Installer not found with any pattern" | |
| Write-Host "All files in dist/:" | |
| Get-ChildItem dist\ -ErrorAction SilentlyContinue | Format-Table Name, Length, LastWriteTime | |
| exit 1 | |
| } | |
| Write-Host "[OK] Found installer: $($installer.FullName)" | |
| Write-Host " Size before signing: $($installer.Length) bytes" | |
| Write-Host " Last modified: $($installer.LastWriteTime)" | |
| Write-Host "Running signtool sign..." | |
| signtool sign /f $certPath /p "$env:WINDOWS_CERT_PASSWORD" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 "$($installer.FullName)" | |
| $signExitCode = $LASTEXITCODE | |
| Write-Host "signtool sign exit code: $signExitCode" | |
| if ($signExitCode -ne 0) { | |
| Write-Error "signtool sign failed with exit code $signExitCode" | |
| exit 1 | |
| } | |
| # Refresh file info after signing | |
| $installer = Get-Item $installer.FullName | |
| Write-Host " Size after signing: $($installer.Length) bytes" | |
| Write-Host " Last modified: $($installer.LastWriteTime)" | |
| Write-Host "Running signtool verify..." | |
| signtool verify /pa /v "$($installer.FullName)" | |
| $verifyExitCode = $LASTEXITCODE | |
| Write-Host "signtool verify exit code: $verifyExitCode" | |
| if ($verifyExitCode -ne 0) { | |
| Write-Error "signtool verify failed with exit code $verifyExitCode" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Installer signed and verified successfully" | |
| - name: Generate build metadata | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| python scripts/generate_build_metadata.py --output dist/build_info.json | |
| env: | |
| GITHUB_RUN_ID: ${{ github.run_id }} | |
| - name: Verify installer (checksums and signatures, Design 2.38) | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| python scripts/generate_checksums.py --output dist/SHA256SUMS.txt --algorithms sha256 | |
| python scripts/verify_installer.py --dir dist/ --checksums dist/SHA256SUMS.txt | |
| python scripts/validate_signatures.py --dir dist/ | |
| - name: Verify version embedding | |
| run: | | |
| Write-Host "=== DEBUG: Verifying version embedding ===" | |
| Write-Host "Current directory: $(Get-Location)" | |
| Write-Host "Running: python scripts/verify_version_embedding.py" | |
| python scripts/verify_version_embedding.py | |
| $verifyExitCode = $LASTEXITCODE | |
| Write-Host "Version embedding verification exit code: $verifyExitCode" | |
| if ($verifyExitCode -ne 0) { | |
| Write-Error "Version embedding verification failed" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Version embedding verified" | |
| - name: Verify installer exists before upload | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| Write-Host "=== DEBUG: Verifying installer exists before upload ===" | |
| Write-Host "Git ref: ${{ github.ref }}" | |
| Write-Host "Current directory: $(Get-Location)" | |
| # Get version from environment or git tag | |
| $version = "${{ env.VERSION }}" | |
| Write-Host "env.VERSION: $version" | |
| if (-not $version -or $version -eq "") { | |
| $version = "${{ github.ref_name }}" | |
| Write-Host "Using github.ref_name: $version" | |
| $version = $version -replace '^v', '' | |
| Write-Host "Version after removing 'v' prefix: $version" | |
| } | |
| Write-Host "Final version: $version" | |
| Write-Host "Looking for installer with version: $version" | |
| # Try multiple patterns to find installer | |
| Write-Host "Searching for installer files..." | |
| $installerFiles = @() | |
| Write-Host "Pattern 1: dist\CuePoint-Setup-v*.exe" | |
| $pattern1 = Get-ChildItem "dist\CuePoint-Setup-v*.exe" -ErrorAction SilentlyContinue | |
| if ($pattern1) { | |
| Write-Host " Found $($pattern1.Count) file(s)" | |
| $pattern1 | ForEach-Object { Write-Host " - $($_.Name) ($($_.Length) bytes)" } | |
| $installerFiles += $pattern1 | |
| } else { | |
| Write-Host " No files found" | |
| } | |
| Write-Host "Pattern 2: dist\*-setup.exe" | |
| $pattern2 = Get-ChildItem "dist\*-setup.exe" -ErrorAction SilentlyContinue | |
| if ($pattern2) { | |
| Write-Host " Found $($pattern2.Count) file(s)" | |
| $pattern2 | ForEach-Object { Write-Host " - $($_.Name) ($($_.Length) bytes)" } | |
| $installerFiles += $pattern2 | |
| } else { | |
| Write-Host " No files found" | |
| } | |
| Write-Host "Pattern 3: dist\*.exe with 'Setup' in name" | |
| $pattern3 = Get-ChildItem "dist\*.exe" -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*Setup*" } | |
| if ($pattern3) { | |
| Write-Host " Found $($pattern3.Count) file(s)" | |
| $pattern3 | ForEach-Object { Write-Host " - $($_.Name) ($($_.Length) bytes)" } | |
| $installerFiles += $pattern3 | |
| } else { | |
| Write-Host " No files found" | |
| } | |
| # Remove duplicates | |
| $installerFiles = $installerFiles | Select-Object -Unique | |
| Write-Host "Total unique installer files found: $($installerFiles.Count)" | |
| if ($installerFiles.Count -eq 0) { | |
| Write-Error "No installer files found in dist/" | |
| Write-Host "All files in dist directory:" | |
| $allFiles = Get-ChildItem dist\ -ErrorAction SilentlyContinue | |
| if ($allFiles) { | |
| $allFiles | Format-Table Name, Length, LastWriteTime, Attributes, FullName | |
| } else { | |
| Write-Host " dist/ directory is empty or doesn't exist" | |
| } | |
| Write-Host "Checking explicit path:" | |
| $explicitPath = "dist\CuePoint-Setup-v$version.exe" | |
| $absExplicitPath = Join-Path (Get-Location) $explicitPath | |
| Write-Host " Relative path: $explicitPath" | |
| Write-Host " Absolute path: $absExplicitPath" | |
| Write-Host " Exists (relative): $(Test-Path $explicitPath)" | |
| Write-Host " Exists (absolute): $(Test-Path $absExplicitPath)" | |
| if (Test-Path $explicitPath) { | |
| Write-Host " File info:" | |
| Get-Item $explicitPath | Format-List * | |
| } | |
| exit 1 | |
| } | |
| Write-Host "[OK] Found $($installerFiles.Count) installer file(s):" | |
| $installerFiles | Format-Table Name, Length, LastWriteTime, FullName | |
| - name: Prepare artifact upload | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| Write-Host "=== DEBUG: Preparing artifact upload ===" | |
| Write-Host "Current directory: $(Get-Location)" | |
| # Find installer using the actual pattern | |
| Write-Host "Searching for installer files..." | |
| $installerFiles = @() | |
| # Try the actual pattern first: CuePoint-Setup-v*.exe | |
| Write-Host "Pattern 1: dist\CuePoint-Setup-v*.exe" | |
| $pattern1 = Get-ChildItem dist\CuePoint-Setup-v*.exe -ErrorAction SilentlyContinue | |
| if ($pattern1) { | |
| Write-Host "[OK] Found $($pattern1.Count) file(s) with pattern 1:" | |
| $pattern1 | ForEach-Object { Write-Host " - $($_.FullName) ($($_.Length) bytes)" } | |
| $installerFiles += $pattern1 | |
| } | |
| # Try alternative pattern: *-setup.exe (in case naming changes) | |
| Write-Host "Pattern 2: dist\*-setup.exe" | |
| $pattern2 = Get-ChildItem dist\*-setup.exe -ErrorAction SilentlyContinue | |
| if ($pattern2) { | |
| Write-Host "[OK] Found $($pattern2.Count) file(s) with pattern 2:" | |
| $pattern2 | ForEach-Object { Write-Host " - $($_.FullName) ($($_.Length) bytes)" } | |
| $installerFiles += $pattern2 | |
| } | |
| # Try any .exe with "Setup" in name | |
| Write-Host "Pattern 3: dist\*.exe with 'Setup' in name" | |
| $pattern3 = Get-ChildItem dist\*.exe -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*Setup*" } | |
| if ($pattern3) { | |
| Write-Host "[OK] Found $($pattern3.Count) file(s) with pattern 3:" | |
| $pattern3 | ForEach-Object { Write-Host " - $($_.FullName) ($($_.Length) bytes)" } | |
| $installerFiles += $pattern3 | |
| } | |
| # Remove duplicates | |
| $installerFiles = $installerFiles | Select-Object -Unique | |
| if ($installerFiles.Count -eq 0) { | |
| Write-Error "No installer files found for upload" | |
| Write-Host "All files in dist/:" | |
| Get-ChildItem dist\ -ErrorAction SilentlyContinue | Format-Table Name, Length, LastWriteTime | |
| exit 1 | |
| } | |
| Write-Host "[OK] Found $($installerFiles.Count) installer file(s) to upload:" | |
| $installerFiles | Format-Table Name, Length, LastWriteTime, FullName | |
| # Verify the pattern matches before upload | |
| Write-Host "Verifying upload pattern will match files..." | |
| $verifyPattern = Get-ChildItem dist\CuePoint-Setup-v*.exe -ErrorAction SilentlyContinue | |
| if ($verifyPattern) { | |
| Write-Host "[OK] Pattern 'dist\CuePoint-Setup-v*.exe' matches $($verifyPattern.Count) file(s)" | |
| } else { | |
| Write-Error "Pattern 'dist\CuePoint-Setup-v*.exe' does not match any files!" | |
| exit 1 | |
| } | |
| - name: Upload artifact | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| id: upload_installer | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-installer | |
| path: dist/CuePoint-Setup-v*.exe | |
| retention-days: 30 | |
| if-no-files-found: error | |
| - name: Verify artifact upload | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| run: | | |
| Write-Host "=== DEBUG: Verifying artifact upload ===" | |
| Write-Host "Upload step outcome: ${{ steps.upload_installer.outcome }}" | |
| Write-Host "Upload step conclusion: ${{ steps.upload_installer.conclusion }}" | |
| if ("${{ steps.upload_installer.outcome }}" -ne "success") { | |
| Write-Error "Artifact upload failed" | |
| Write-Host "Upload step details:" | |
| Write-Host " Outcome: ${{ steps.upload_installer.outcome }}" | |
| Write-Host " Conclusion: ${{ steps.upload_installer.conclusion }}" | |
| exit 1 | |
| } | |
| Write-Host "[OK] Artifact uploaded successfully" |