Fix visual smoke shutdown in CI #23
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: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| WINDOWS_TARGET: x86_64-pc-windows-gnu | |
| HIDMAESTRO_RELEASE_URL: https://github.com/hifihedgehog/HIDMaestro/releases/download/v1.3.13/HIDMaestro-v1.3.13.zip | |
| HIDMAESTRO_RELEASE_SHA256: 4948B815E65AFDD72BED8C1E29E8108ED8388B58F805FECA2C452E06CF499227 | |
| jobs: | |
| rust-checks: | |
| name: Rust release checks (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: "" | |
| target-arg: "" | |
| - os: windows-latest | |
| target: x86_64-pc-windows-gnu | |
| target-arg: --target x86_64-pc-windows-gnu | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Add Windows Rust target | |
| if: runner.os == 'Windows' | |
| run: rustup target add ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Add MinGW to PATH | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: '"C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append' | |
| - name: Install Linux build deps | |
| if: runner.os == 'Linux' | |
| run: sudo apt-get update && sudo apt-get install -y libudev-dev | |
| - name: Format check | |
| run: cargo fmt --all -- --check | |
| - name: Clippy | |
| run: cargo clippy --workspace --all-targets ${{ matrix.target-arg }} -- -D warnings | |
| - name: Test | |
| run: cargo test --workspace ${{ matrix.target-arg }} | |
| web-checks: | |
| name: Web release checks | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: web | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: npm ci | |
| - run: npx playwright install --with-deps chromium | |
| - run: npm run typecheck | |
| - run: npm run test:source-audit | |
| - run: npm run test:button-map | |
| - run: npm run build | |
| - run: npm run test:release-size | |
| - run: npm run test:visual-smoke | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: release-web-dist | |
| path: web/dist/** | |
| if-no-files-found: error | |
| windows-artifacts: | |
| name: Windows unsigned beta artifacts | |
| runs-on: windows-latest | |
| needs: | |
| - rust-checks | |
| - web-checks | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - uses: actions/download-artifact@v6 | |
| with: | |
| name: release-web-dist | |
| path: web/dist | |
| - name: Add MinGW to PATH | |
| shell: pwsh | |
| run: '"C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append' | |
| - name: Add Windows Rust target | |
| shell: pwsh | |
| run: rustup target add $env:WINDOWS_TARGET | |
| - name: Resolve release version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $tag = '${{ github.ref_name }}' | |
| if ($tag -notmatch '^v?([0-9]+\.[0-9]+)(?:\.([0-9]+))?(?:[-+].*)?$') { | |
| throw "Release tag '$tag' must look like v0.3, v0.3.2, or v0.3.2-beta.1." | |
| } | |
| $patch = if ([string]::IsNullOrWhiteSpace($Matches[2])) { '0' } else { $Matches[2] } | |
| "msi_version=$($Matches[1]).$patch" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| "artifact_suffix=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Build Windows GNU binaries | |
| shell: pwsh | |
| run: cargo build -p dscc-agent -p dscc-tray -p dscc-cli --release --target $env:WINDOWS_TARGET | |
| - name: Resolve HIDMaestro broker dependency | |
| id: hidmaestro | |
| shell: pwsh | |
| run: | | |
| $root = Join-Path $PWD 'target\hidmaestro' | |
| $zipPath = Join-Path $root 'HIDMaestro-v1.3.13.zip' | |
| $extractPath = Join-Path $root 'release' | |
| New-Item -ItemType Directory -Path $root -Force | Out-Null | |
| Invoke-WebRequest -Uri $env:HIDMAESTRO_RELEASE_URL -OutFile $zipPath | |
| $hash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToUpperInvariant() | |
| if ($hash -ne $env:HIDMAESTRO_RELEASE_SHA256.ToUpperInvariant()) { | |
| throw "HIDMaestro release hash mismatch. Expected $env:HIDMAESTRO_RELEASE_SHA256 but found $hash." | |
| } | |
| if (Test-Path -LiteralPath $extractPath) { | |
| Remove-Item -LiteralPath $extractPath -Recurse -Force | |
| } | |
| Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force | |
| $coreDll = Join-Path $extractPath 'HIDMaestro.Core.dll' | |
| if (-not (Test-Path -LiteralPath $coreDll)) { | |
| throw "HIDMaestro.Core.dll was not found in the approved release archive." | |
| } | |
| "core_dll=$coreDll" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - name: Build HIDMaestro broker package flavors | |
| shell: pwsh | |
| run: | | |
| $coreDll = '${{ steps.hidmaestro.outputs.core_dll }}' | |
| $publishRoot = Join-Path $PWD 'target\hidmaestro-broker' | |
| $selfContained = Join-Path $publishRoot 'self-contained' | |
| $frameworkDependent = Join-Path $publishRoot 'framework-dependent' | |
| foreach ($dir in @($selfContained, $frameworkDependent)) { | |
| if (Test-Path -LiteralPath $dir) { | |
| Remove-Item -LiteralPath $dir -Recurse -Force | |
| } | |
| } | |
| dotnet publish tools/dscc-hidmaestro-broker ` | |
| -c Release ` | |
| -r win-x64 ` | |
| -o $selfContained ` | |
| --self-contained true ` | |
| -p:PublishSingleFile=true ` | |
| -p:EnableCompressionInSingleFile=true ` | |
| -p:DebugType=None ` | |
| -p:DebugSymbols=false ` | |
| -p:HidMaestroCoreDll="$coreDll" | |
| dotnet publish tools/dscc-hidmaestro-broker ` | |
| -c Release ` | |
| -r win-x64 ` | |
| -o $frameworkDependent ` | |
| --self-contained false ` | |
| -p:PublishSingleFile=false ` | |
| -p:DebugType=None ` | |
| -p:DebugSymbols=false ` | |
| -p:HidMaestroCoreDll="$coreDll" | |
| - name: Build unsigned MSIs | |
| shell: pwsh | |
| run: | | |
| $selfContainedBroker = Join-Path $PWD 'target\hidmaestro-broker\self-contained' | |
| $frameworkDependentBroker = Join-Path $PWD 'target\hidmaestro-broker\framework-dependent' | |
| powershell -NoProfile -ExecutionPolicy Bypass -File packaging\package-msi.ps1 ` | |
| -Version '${{ steps.version.outputs.msi_version }}' ` | |
| -TargetTriple $env:WINDOWS_TARGET ` | |
| -SkipWebBuild ` | |
| -InstallerFlavor Standard | |
| powershell -NoProfile -ExecutionPolicy Bypass -File packaging\package-msi.ps1 ` | |
| -Version '${{ steps.version.outputs.msi_version }}' ` | |
| -TargetTriple $env:WINDOWS_TARGET ` | |
| -SkipWebBuild ` | |
| -InstallerFlavor Bridge ` | |
| -BrokerPublishPath $selfContainedBroker | |
| powershell -NoProfile -ExecutionPolicy Bypass -File packaging\package-msi.ps1 ` | |
| -Version '${{ steps.version.outputs.msi_version }}' ` | |
| -TargetTriple $env:WINDOWS_TARGET ` | |
| -SkipWebBuild ` | |
| -InstallerFlavor BridgeFrameworkDependent ` | |
| -BrokerPublishPath $frameworkDependentBroker | |
| - name: Stage Windows artifacts and checksums | |
| shell: pwsh | |
| run: | | |
| $suffix = '${{ steps.version.outputs.artifact_suffix }}' | |
| $artifactRoot = Join-Path $PWD 'target\release-artifacts\windows' | |
| New-Item -ItemType Directory -Path $artifactRoot -Force | Out-Null | |
| $msiArtifacts = @( | |
| @{ Flavor = 'standard'; Name = 'standard' }, | |
| @{ Flavor = 'bridge'; Name = 'bridge' }, | |
| @{ Flavor = 'bridge-framework-dependent'; Name = 'bridge-framework-dependent' } | |
| ) | |
| foreach ($artifact in $msiArtifacts) { | |
| $msiSource = Join-Path $PWD "target\installer\DualSenseCommandCenter-${{ steps.version.outputs.msi_version }}-$($artifact.Flavor).msi" | |
| $msiDest = Join-Path $artifactRoot "DualSenseCommandCenter-$suffix-windows-x86_64-$($artifact.Name)-unsigned.msi" | |
| Copy-Item -LiteralPath $msiSource -Destination $msiDest -Force | |
| } | |
| $binaryStage = Join-Path $PWD 'target\release-artifacts\windows-bin' | |
| if (Test-Path -LiteralPath $binaryStage) { | |
| Remove-Item -LiteralPath $binaryStage -Recurse -Force | |
| } | |
| New-Item -ItemType Directory -Path $binaryStage -Force | Out-Null | |
| Copy-Item -LiteralPath "target\$env:WINDOWS_TARGET\release\dscc-agent.exe" -Destination $binaryStage -Force | |
| Copy-Item -LiteralPath "target\$env:WINDOWS_TARGET\release\dscc-tray.exe" -Destination $binaryStage -Force | |
| Copy-Item -LiteralPath "target\$env:WINDOWS_TARGET\release\dscc-cli.exe" -Destination $binaryStage -Force | |
| Set-Content -LiteralPath (Join-Path $binaryStage 'README.txt') -Encoding ASCII -Value @( | |
| 'DualSense Command Center unsigned Windows beta binaries.' | |
| 'Install the MSI for normal testing; these raw binaries are provided for diagnostics.' | |
| 'Use dscc-cli.exe support-bundle if the UI will not open and you need a sanitized report.' | |
| ) | |
| $zipDest = Join-Path $artifactRoot "DualSenseCommandCenter-$suffix-windows-x86_64-binaries.zip" | |
| Compress-Archive -Path (Join-Path $binaryStage '*') -DestinationPath $zipDest -Force | |
| $checksumPath = Join-Path $artifactRoot 'SHA256SUMS-windows.txt' | |
| Get-ChildItem -LiteralPath $artifactRoot -File | | |
| Sort-Object Name | | |
| ForEach-Object { | |
| $hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() | |
| "$hash $($_.Name)" | |
| } | | |
| Set-Content -LiteralPath $checksumPath -Encoding ASCII | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows-unsigned-beta | |
| path: target/release-artifacts/windows/* | |
| if-no-files-found: error | |
| linux-artifacts: | |
| name: Linux bundled beta artifact | |
| runs-on: ubuntu-latest | |
| needs: | |
| - rust-checks | |
| - web-checks | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: actions/download-artifact@v6 | |
| with: | |
| name: release-web-dist | |
| path: web/dist | |
| - name: Install Linux build deps | |
| run: sudo apt-get update && sudo apt-get install -y libudev-dev | |
| - name: Build Linux binaries | |
| run: cargo build -p dscc-agent -p dscc-cli --release | |
| - name: Stage Linux artifacts and checksums | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| tag="${GITHUB_REF_NAME}" | |
| artifact_root="$PWD/target/release-artifacts/linux" | |
| stage="$PWD/target/release-artifacts/linux-bin" | |
| rm -rf "$artifact_root" "$stage" | |
| mkdir -p "$artifact_root" "$stage" | |
| cp target/release/dscc-agent "$stage/" | |
| cp target/release/dscc-cli "$stage/" | |
| mkdir -p "$stage/web" | |
| cp -R web/dist "$stage/web/dist" | |
| cp packaging/linux/70-dualsense-command-center.rules "$stage/" | |
| cat > "$stage/README.txt" <<'README' | |
| DualSense Command Center Linux beta archive. | |
| Contents: | |
| - dscc-cli: CLI launcher and diagnostics | |
| - dscc-agent: local agent binary used by dscc-cli serve | |
| - web/dist: bundled production web UI | |
| - 70-dualsense-command-center.rules: optional udev rule for desktop hidraw access | |
| Start DSCC from the extracted folder: | |
| ./dscc-cli serve --addr 127.0.0.1:43473 | |
| Then open http://127.0.0.1:43473/. Vite is not required for release | |
| archives. If you move the web assets, set DSCC_WEB_DIST to the | |
| absolute path of the dist folder before starting the agent. | |
| If the binary reports a missing udev library, install the runtime | |
| package for your distro, such as libudev1 on Debian/Ubuntu or | |
| systemd-libs on Fedora. | |
| Linux hardware output is still beta. Test HID access as a normal user: | |
| ./dscc-cli devices list-hid --experimental --probe-open | |
| ./dscc-cli devices diagnose | |
| If the controller is visible but cannot be opened without sudo, | |
| install the included udev rule, reload udev, and reconnect: | |
| sudo install -m 0644 70-dualsense-command-center.rules /etc/udev/rules.d/ | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger | |
| Do not run DSCC with sudo for normal use; fix hidraw permissions | |
| instead so config files stay owned by your user. | |
| WSL can serve the UI, but native Linux is recommended for hardware | |
| testing because WSL does not expose the same HID/Bluetooth model. | |
| Full setup notes: docs/linux-beta.md in the release tag. | |
| README | |
| tar -C "$stage" -czf "$artifact_root/DualSenseCommandCenter-$tag-linux-x86_64-experimental.tar.gz" . | |
| ( | |
| cd "$artifact_root" | |
| sha256sum * > SHA256SUMS-linux.txt | |
| ) | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: linux-bundled-beta | |
| path: target/release-artifacts/linux/* | |
| if-no-files-found: error | |
| draft-release: | |
| name: Create published release | |
| runs-on: ubuntu-latest | |
| needs: | |
| - windows-artifacts | |
| - linux-artifacts | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download Windows release artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: windows-unsigned-beta | |
| path: release-artifacts/windows | |
| - name: Download Linux release artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| name: linux-bundled-beta | |
| path: release-artifacts/linux | |
| - name: Create release notes | |
| shell: bash | |
| run: | | |
| awk ' | |
| /^# DualSense Command Center / { | |
| if (found) { | |
| exit | |
| } | |
| found = 1 | |
| } | |
| found { | |
| } | |
| ' CHANGELOG.md > release-notes.md | |
| if ! grep -q '^# DualSense Command Center ' release-notes.md; then | |
| cat > release-notes.md <<'EOF' | |
| # DualSense Command Center | |
| DualSense Command Center release artifacts. | |
| EOF | |
| fi | |
| cat >> release-notes.md <<'EOF' | |
| ## Release Artifacts | |
| Verify downloaded files against the published SHA256 checksum files. | |
| Windows users should choose one installer: | |
| - `standard`: recommended for most users. Includes controller tuning, | |
| profiles, haptics, diagnostics, telemetry, and Steam Input support. | |
| It does not bundle the optional non-Steam Input Bridge broker. | |
| - `bridge`: includes the self-contained HIDMaestro broker for DSCC | |
| Input Bridge testing with local non-Steam apps. Larger download. | |
| - `bridge-framework-dependent`: advanced bridge build for machines | |
| that already have the matching x64 .NET runtime installed. Smaller | |
| than `bridge`, less plug-and-play than `standard`. | |
| The Linux archive includes the bundled production web UI, so | |
| `./dscc-cli serve --addr 127.0.0.1:43473` can serve the app without | |
| running Vite. Linux HID permissions and hardware validation remain | |
| beta-readiness work. See `docs/linux-beta.md` in this release tag for | |
| udev setup, WSL notes, and artifact validation commands. | |
| EOF | |
| - name: Create published release and upload artifacts | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t files < <(find release-artifacts -type f | sort) | |
| declare -A expected_assets=() | |
| for file in "${files[@]}"; do | |
| expected_assets["$(basename "$file")"]=1 | |
| done | |
| if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| gh release edit "${GITHUB_REF_NAME}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --draft=false \ | |
| --prerelease=false \ | |
| --latest \ | |
| --title "DualSense Command Center ${GITHUB_REF_NAME}" \ | |
| --notes-file release-notes.md | |
| while IFS=$'\t' read -r asset_id asset_name; do | |
| if [[ -n "${asset_id}" && -z "${expected_assets[$asset_name]:-}" ]]; then | |
| gh api \ | |
| --method DELETE \ | |
| "repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}" | |
| fi | |
| done < <( | |
| gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}" \ | |
| --jq '.assets[] | [.id, .name] | @tsv' | |
| ) | |
| gh release upload "${GITHUB_REF_NAME}" "${files[@]}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --clobber | |
| else | |
| gh release create "${GITHUB_REF_NAME}" "${files[@]}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --latest \ | |
| --title "DualSense Command Center ${GITHUB_REF_NAME}" \ | |
| --notes-file release-notes.md | |
| fi |