Skip to content

Merge pull request #40 from ruslanmv/dev-v2.0.13.8 #74

Merge pull request #40 from ruslanmv/dev-v2.0.13.8

Merge pull request #40 from ruslanmv/dev-v2.0.13.8 #74

name: Build Windows Installer
on:
push:
branches: [ master, develop ]
tags:
- 'v*'
pull_request:
branches: [ master, develop ]
workflow_dispatch:
permissions:
contents: write
env:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
jobs:
build-windows:
name: Build Windows Installer
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'pip'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
src-tauri/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install Tauri CLI
run: cargo install tauri-cli --version "^1.0" --force
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip wheel setuptools
pip install pyinstaller
pip install -e .
- name: Verify and download driver installers
shell: pwsh
run: |
$interceptionPath = "frontend/input_record/install-interception.exe"
if (-not (Test-Path $interceptionPath)) {
Write-Host "::warning::Interception installer not found at $interceptionPath"
} else {
Write-Host "✓ Interception installer found"
}
# Download vJoySetup.exe from official GitHub release
$vjoyDir = "src-tauri/drivers/vjoy"
$vjoyPath = "$vjoyDir/vJoySetup.exe"
$vjoyUrl = "https://github.com/shauleiz/vJoy/releases/download/v2.1.9.1/vJoySetup.exe"
New-Item -Force -ItemType Directory $vjoyDir | Out-Null
if (-not (Test-Path $vjoyPath)) {
Write-Host "Downloading vJoySetup.exe from official release..."
try {
Invoke-WebRequest -Uri $vjoyUrl -OutFile $vjoyPath -UseBasicParsing
Write-Host "✓ vJoySetup.exe downloaded successfully"
} catch {
Write-Host "::warning::Failed to download vJoySetup.exe: $($_.Exception.Message)"
Write-Host "::warning::Build will continue without vJoy driver"
}
} else {
Write-Host "✓ vJoySetup.exe found"
}
- name: Build Python backend sidecar
shell: pwsh
run: |
Write-Host "Building Python backend with PyInstaller..."
pyinstaller --noconfirm --clean --onefile --name main-backend backend/main_backend.py
if (-not (Test-Path "dist/main-backend.exe")) {
Write-Error "PyInstaller failed to create main-backend.exe"
exit 1
}
Write-Host "✓ Backend sidecar built successfully"
- name: Prepare Tauri sidecar binary
shell: pwsh
run: |
$target = "x86_64-pc-windows-msvc"
$tauriSidecar = "src-tauri/binaries/main-backend-$target.exe"
New-Item -Force -ItemType Directory (Split-Path $tauriSidecar -Parent) | Out-Null
Copy-Item -Force "dist/main-backend.exe" $tauriSidecar
Write-Host "✓ Sidecar copied to: $tauriSidecar"
- name: Copy driver installers and scripts
shell: pwsh
run: |
New-Item -Force -ItemType Directory "src-tauri/drivers/interception" | Out-Null
New-Item -Force -ItemType Directory "src-tauri/drivers/vjoy" | Out-Null
New-Item -Force -ItemType Directory "src-tauri/resources/scripts" | Out-Null
# Copy interception driver (committed to repo)
Copy-Item -Force "frontend/input_record/install-interception.exe" "src-tauri/drivers/interception/install-interception.exe"
# vJoySetup.exe is downloaded in the previous step; verify it exists
if (Test-Path "src-tauri/drivers/vjoy/vJoySetup.exe") {
Write-Host "✓ vJoySetup.exe present"
} else {
Write-Host "::warning::vJoySetup.exe not available — installer will skip vJoy driver"
}
# Copy scripts
Copy-Item -Force "scripts/install_drivers.ps1" "src-tauri/resources/scripts/install_drivers.ps1"
if (Test-Path "scripts/install_ml_deps.ps1") {
Copy-Item -Force "scripts/install_ml_deps.ps1" "src-tauri/resources/scripts/install_ml_deps.ps1"
}
if (Test-Path "scripts/download_models.ps1") {
Copy-Item -Force "scripts/download_models.ps1" "src-tauri/resources/scripts/download_models.ps1"
}
Write-Host "✓ Driver installers and scripts copied"
# --- FIX: CREATE MISSING .ENV FILE BEFORE BUILD ---
- name: Create default .env file
shell: pwsh
run: |
if (-not (Test-Path ".env")) {
Write-Host "Creating default .env file to satisfy bundler resources..."
Set-Content -Path ".env" -Value "AI_PROVIDER=gemini"
}
Write-Host "✓ .env file present"
- name: Build Tauri application
shell: pwsh
run: |
Push-Location src-tauri
try {
cargo tauri build --verbose
if ($LASTEXITCODE -ne 0) {
Write-Error "Tauri build failed with exit code $LASTEXITCODE"
exit 1
}
} finally {
Pop-Location
}
- name: Verify installer was created
shell: pwsh
run: |
$installerDir = "src-tauri/target/release/bundle/nsis"
if (-not (Test-Path $installerDir)) {
Write-Error "Installer directory not found: $installerDir"
exit 1
}
$installers = Get-ChildItem -Path $installerDir -Filter "*.exe"
if ($installers.Count -eq 0) {
Write-Error "No installer executables found in $installerDir"
exit 1
}
foreach ($installer in $installers) {
Write-Host "✓ Created: $($installer.Name) ($($installer.Length / 1MB) MB)"
}
- name: Upload installer artifact
uses: actions/upload-artifact@v4
with:
name: windows-installer
path: src-tauri/target/release/bundle/nsis/*.exe
retention-days: 30
# Nightly: auto-publish EXE to a rolling "nightly" release on every master push
- name: Upload installer EXE to Nightly Release (every commit on master)
if: github.ref == 'refs/heads/master'
uses: softprops/action-gh-release@v2
with:
tag_name: nightly
name: Nightly (latest master build)
prerelease: true
generate_release_notes: false
body: |
## Nightly Build
**This is an automated nightly build from the latest `master` commit.**
The `.exe` installer below is always up-to-date with the latest code.
Download it directly — no need to unzip.
### How to install
1. Download the `.exe` file below
2. Run it with administrator privileges
3. Follow the setup wizard
> **Note:** Nightly builds may contain experimental features.
> For stable releases, see [tagged releases](../../releases).
files: src-tauri/target/release/bundle/nsis/*.exe
# Tagged releases: create a proper versioned release
- name: Create Tagged Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: src-tauri/target/release/bundle/nsis/*.exe
generate_release_notes: true
draft: false
prerelease: false
test-installer:
name: Test Installer
needs: build-windows
runs-on: windows-latest
if: "!startsWith(github.ref, 'refs/tags/')"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download installer
uses: actions/download-artifact@v4
with:
name: windows-installer
path: ./installer-test
- name: Basic installer validation
shell: pwsh
run: |
$installers = Get-ChildItem -Path ./installer-test -Filter "*.exe"
if ($installers.Count -eq 0) {
Write-Error "No installer found!"
exit 1
}
foreach ($installer in $installers) {
Write-Host "Validating: $($installer.Name)"
if ($installer.Length -lt 1MB) {
Write-Warning "Installer size is suspiciously small"
}
# FIX: PowerShell 7 does NOT support -Encoding Byte
$bytes = [System.IO.File]::ReadAllBytes($installer.FullName)
$header = $bytes[0..1]
if ($header[0] -eq 0x4D -and $header[1] -eq 0x5A) {
Write-Host "✓ Valid PE executable header"
} else {
Write-Error "Invalid executable header"
exit 1
}
}