name: Build Desktop on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: inputs: signing_mode: description: Build/signing mode (publish requires selecting a v* tag) required: true type: choice options: [none, test, production, publish] default: none concurrency: group: desktop-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: actions: read contents: read env: WAILS_VERSION: v3.0.0-alpha2.119 INSTALLER_PATH: desktop/bin/HypoMux-amd64-installer.exe jobs: build: name: Validate and package Wails desktop runs-on: windows-2025 steps: - name: Checkout uses: actions/checkout@v5 - name: Validate release options if: github.event_name == 'workflow_dispatch' shell: pwsh run: | if ("${{ inputs.signing_mode }}" -eq "production" -and "${{ github.ref }}" -ne "refs/heads/main") { throw "Production-signing tests must be started from main." } if ("${{ inputs.signing_mode }}" -eq "publish" -and "${{ github.ref }}" -notlike "refs/tags/v*") { throw "Publishing must be started from a v* tag." } if ("${{ inputs.signing_mode }}" -eq "test" -and "${{ github.ref }}" -like "refs/tags/*") { throw "Test signing must not be started from a release tag." } - name: Set up Go uses: actions/setup-go@v7 with: go-version-file: engine/go.mod cache-dependency-path: | engine/go.sum desktop/go.sum - name: Set up pnpm uses: pnpm/action-setup@v6 with: version: 10 run_install: false - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm cache-dependency-path: desktop/frontend/pnpm-lock.yaml - name: Install Wails and NSIS shell: pwsh run: | go install "github.com/wailsapp/wails/v3/cmd/wails3@${env:WAILS_VERSION}" choco install nsis --yes --no-progress $nsisDirectory = Join-Path ${env:ProgramFiles(x86)} 'NSIS' $makensis = Join-Path $nsisDirectory 'makensis.exe' if (-not (Test-Path -LiteralPath $makensis -PathType Leaf)) { throw "NSIS compiler was not found at $makensis." } & $makensis /VERSION if ($LASTEXITCODE -ne 0) { throw "makensis version check failed with exit code $LASTEXITCODE." } Add-Content -LiteralPath $env:GITHUB_PATH -Value $nsisDirectory -Encoding utf8 Add-Content -LiteralPath $env:GITHUB_ENV -Value "MAKENSIS=$makensis" -Encoding utf8 - name: Install frontend dependencies shell: pwsh run: pnpm --dir desktop/frontend install --frozen-lockfile - name: Generate Wails frontend bindings working-directory: desktop shell: pwsh run: wails3 generate bindings -clean=true -ts -i - name: Run frontend tests shell: pwsh run: pnpm --dir desktop/frontend test - name: Build frontend assets for Go validation shell: pwsh run: pnpm --dir desktop/frontend build - name: Validate Go modules shell: pwsh run: | go -C engine mod verify go -C desktop mod verify go -C engine test ./... go -C desktop test ./... go -C engine vet ./... go -C desktop vet ./... - name: Verify Go formatting shell: pwsh run: | $goFiles = @( git ls-files -- '*.go' | Where-Object { $_ -like 'engine/*' -or $_ -like 'desktop/*' } ) if ($goFiles.Count -eq 0) { throw "No tracked Go source files were found." } $unformatted = @(gofmt -l @goFiles) if ($LASTEXITCODE -ne 0) { throw "gofmt failed with exit code $LASTEXITCODE." } if ($unformatted.Count -gt 0) { $unformatted | ForEach-Object { Write-Error "Not gofmt-formatted: $_" } exit 1 } - name: Build and package Wails desktop working-directory: desktop shell: pwsh run: wails3 task windows:package - name: Stage unsigned executables for SignPath if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' shell: pwsh run: | New-Item -ItemType Directory -Force -Path signing-input | Out-Null Copy-Item desktop\bin\hypomux.exe signing-input\hypomux-unsigned.exe -Force Copy-Item desktop\bin\hypomux-engine.exe signing-input\hypomux-engine-unsigned.exe -Force - name: Upload desktop executable for SignPath if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' id: upload-desktop uses: actions/upload-artifact@v7 with: path: signing-input/hypomux-unsigned.exe archive: false if-no-files-found: error - name: Sign desktop executable if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' uses: signpath/github-action-submit-signing-request@v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: 4463263d-e740-4262-ae16-7eac788453ea project-slug: HypoMux signing-policy-slug: ${{ inputs.signing_mode == 'test' && 'test-signing' || 'release-signing' }} github-artifact-id: ${{ steps.upload-desktop.outputs.artifact-id }} github-token: ${{ secrets.GITHUB_TOKEN }} wait-for-completion: true wait-for-completion-timeout-in-seconds: 3600 output-artifact-directory: signed-desktop skip-decompress: true - name: Upload Core executable for SignPath if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' id: upload-core uses: actions/upload-artifact@v7 with: path: signing-input/hypomux-engine-unsigned.exe archive: false if-no-files-found: error - name: Sign Core executable if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' uses: signpath/github-action-submit-signing-request@v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: 4463263d-e740-4262-ae16-7eac788453ea project-slug: HypoMux signing-policy-slug: ${{ inputs.signing_mode == 'test' && 'test-signing' || 'release-signing' }} github-artifact-id: ${{ steps.upload-core.outputs.artifact-id }} github-token: ${{ secrets.GITHUB_TOKEN }} wait-for-completion: true wait-for-completion-timeout-in-seconds: 3600 output-artifact-directory: signed-core skip-decompress: true - name: Repackage signed executables if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' shell: pwsh run: | Copy-Item signed-desktop\hypomux-unsigned.exe desktop\bin\hypomux.exe -Force Copy-Item signed-core\hypomux-engine-unsigned.exe desktop\bin\hypomux-engine.exe -Force Push-Location desktop\build\windows\nsis & $env:MAKENSIS "-DARG_WAILS_AMD64_BINARY=..\..\..\bin\hypomux.exe" project.nsi if ($LASTEXITCODE -ne 0) { throw "Signed installer repackaging failed with exit code $LASTEXITCODE." } Pop-Location - name: Stage unsigned installer for SignPath if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' shell: pwsh run: Copy-Item "${{ env.INSTALLER_PATH }}" signing-input\HypoMux-amd64-installer-unsigned.exe -Force - name: Upload installer for SignPath if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' id: upload-installer uses: actions/upload-artifact@v7 with: path: signing-input/HypoMux-amd64-installer-unsigned.exe archive: false if-no-files-found: error - name: Sign installer if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' uses: signpath/github-action-submit-signing-request@v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: 4463263d-e740-4262-ae16-7eac788453ea project-slug: HypoMux signing-policy-slug: ${{ inputs.signing_mode == 'test' && 'test-signing' || 'release-signing' }} github-artifact-id: ${{ steps.upload-installer.outputs.artifact-id }} github-token: ${{ secrets.GITHUB_TOKEN }} wait-for-completion: true wait-for-completion-timeout-in-seconds: 3600 output-artifact-directory: signed-installer skip-decompress: true - name: Use signed installer if: github.event_name == 'workflow_dispatch' && inputs.signing_mode != 'none' shell: pwsh run: Copy-Item signed-installer\HypoMux-amd64-installer-unsigned.exe "${{ env.INSTALLER_PATH }}" -Force - name: Upload build artifacts uses: actions/upload-artifact@v7 with: name: HypoMux-Windows-${{ github.sha }}-${{ github.run_attempt }} path: | desktop/bin/hypomux.exe desktop/bin/hypomux-engine.exe ${{ env.INSTALLER_PATH }} if-no-files-found: error release: name: Publish release installer needs: build if: github.event_name == 'workflow_dispatch' && inputs.signing_mode == 'publish' && startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: actions: read contents: write steps: - name: Download build artifacts uses: actions/download-artifact@v8 with: name: HypoMux-Windows-${{ github.sha }}-${{ github.run_attempt }} path: artifacts - name: Stage legacy-updater-compatible installer name shell: bash run: | version="${GITHUB_REF_NAME#v}" cp artifacts/HypoMux-amd64-installer.exe "artifacts/HypoMux_Setup_${version}.exe" - name: Upload installer to GitHub Release uses: softprops/action-gh-release@v3 with: files: | artifacts/HypoMux_Setup_*.exe fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}