Skip to content

feat: move driver controls to channels #2460

feat: move driver controls to channels

feat: move driver controls to channels #2460

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
should_release:
description: Build, sign, and publish a draft release
required: true
type: boolean
default: false
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
validation:
name: Validate (${{ matrix.check.name }})
runs-on: ${{ matrix.check.runner }}
strategy:
fail-fast: false
matrix:
check:
- name: Lint
command: npm run lint
runner: ubuntu-latest
- name: Storybook
command: npm run build-storybook
runner: ubuntu-latest
- name: Tests
command: npm test
runner: windows-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Cache node_modules and native builds
uses: actions/cache@v5
id: cache-deps
with:
path: |
node_modules
.vite
**/node_modules
**/*.node
**/build/Release
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/binding.gyp', '**/node_modules/**/*.node') }}
restore-keys: |
${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}-
${{ runner.os }}-node-
- name: Install dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: npm ci
- name: Run ${{ matrix.check.name }}
run: ${{ matrix.check.command }}
- name: Upload coverage report
if: matrix.check.name == 'Tests'
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: coverage
build:
name: build
needs: validation
if: always()
runs-on: ubuntu-latest
steps:
- name: Confirm validation passed
env:
VALIDATION_RESULT: ${{ needs.validation.result }}
run: |
if [[ "$VALIDATION_RESULT" != "success" ]]; then
echo "Validation result: $VALIDATION_RESULT" >&2
exit 1
fi
release-gate:
name: Determine release eligibility
if: github.event_name == 'push' || inputs.should_release
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.gate.outputs.should_release }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2 # Fetch the last 2 commits to compare versions
persist-credentials: false
- name: Check release conditions
id: gate
shell: bash
env:
DISPATCH_SHOULD_RELEASE: ${{ inputs.should_release }}
run: |
set -euo pipefail
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
echo "should_release=$DISPATCH_SHOULD_RELEASE" >> "$GITHUB_OUTPUT"
exit 0
fi
currentVersion=$(node -p "require('./package.json').version")
previousVersion=$(git show HEAD^:package.json | node -e \
"let input = ''; process.stdin.on('data', chunk => input += chunk); process.stdin.on('end', () => console.log(JSON.parse(input).version));")
if [[ -z "$currentVersion" || -z "$previousVersion" ]]; then
echo "Could not determine current and previous package versions." >&2
exit 1
fi
if [[ "$currentVersion" != "$previousVersion" ]]; then
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "Version changed from $previousVersion to $currentVersion"
else
echo "should_release=false" >> "$GITHUB_OUTPUT"
fi
release:
name: Build and publish signed release
needs: [build, release-gate]
if: needs.release-gate.outputs.should_release == 'true'
runs-on: windows-latest
permissions:
actions: read
contents: write
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'npm'
- name: Cache node_modules and native builds
uses: actions/cache@v5
id: cache-deps
with:
path: |
node_modules
.vite
**/node_modules
**/*.node
**/build/Release
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/binding.gyp', '**/node_modules/**/*.node') }}
restore-keys: |
${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}-
${{ runner.os }}-node-
- name: Install dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: npm ci
# --- Signed release ----------------------------------------------------
# Pushes release only when the version changes. Manual runs release only
# when the should_release workflow-dispatch input is enabled. Pull
# requests never enter this flow.
# Flow: package once -> sign the packaged application via SignPath -> make
# Squirrel artifacts once while saving a publish dry-run -> sign Setup.exe
# via SignPath -> publish from the saved dry-run.
# The dry-run manifest stores paths to out/make/... (no content hashing), so
# replacing Setup.exe is picked up by --from-dry-run. Making all Squirrel
# artifacts together keeps RELEASES and the .nupkg checksums consistent for
# auto-updates.
- name: Package unsigned Windows application
run: npx electron-forge package
env:
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- name: Archive packaged Windows application
shell: pwsh
run: |
$packageDirectory = 'out/irdashies-win32-x64'
if (-not (Test-Path -LiteralPath $packageDirectory -PathType Container)) {
throw "Packaged application not found: $packageDirectory"
}
Compress-Archive `
-Path "$packageDirectory/*" `
-DestinationPath packaged-windows-app.zip `
-CompressionLevel Optimal
- name: Upload packaged application for signing
id: upload-packaged-app
uses: actions/upload-artifact@v7
with:
name: unsigned-packaged-windows-app
if-no-files-found: error
path: packaged-windows-app.zip
- name: Sign packaged application
uses: signpath/github-action-submit-signing-request@v2
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
project-slug: irdashies
signing-policy-slug: release-signing
artifact-configuration-slug: packaged-app
github-artifact-id: ${{ steps.upload-packaged-app.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: signed-packaged-app
- name: Restore and verify signed packaged application
shell: pwsh
run: |
$signedArchive = Get-ChildItem `
-LiteralPath signed-packaged-app `
-Filter '*.zip' `
-File `
-Recurse |
Select-Object -First 1
if (-not $signedArchive) {
throw 'SignPath did not return the signed packaged application ZIP.'
}
Expand-Archive `
-LiteralPath $signedArchive.FullName `
-DestinationPath out/irdashies-win32-x64 `
-Force
$appExecutable = 'out/irdashies-win32-x64/irdashies.exe'
$signature = Get-AuthenticodeSignature -LiteralPath $appExecutable
if ($signature.Status -ne 'Valid') {
throw "Packaged application signature is $($signature.Status): $($signature.StatusMessage)"
}
Write-Output "Verified signed packaged application: $($signature.SignerCertificate.Subject)"
- name: Build Squirrel artifacts from signed application
run: node tools/publish-signed-dry-run.mjs
- name: Verify signed application in Squirrel package
shell: pwsh
run: |
if (-not (Test-Path -LiteralPath out/publish-dry-run -PathType Container)) {
throw 'Electron Forge publish dry-run state was not generated.'
}
$package = Get-ChildItem `
-Path out/make/squirrel.windows/x64 `
-Filter '*.nupkg' `
-File |
Where-Object { $_.Name -notlike '*-delta.nupkg' } |
Select-Object -First 1
if (-not $package) {
throw 'Squirrel full package was not generated.'
}
$extractDirectory = Join-Path $env:RUNNER_TEMP 'irdashies-nupkg'
New-Item -ItemType Directory -Path $extractDirectory -Force | Out-Null
$packageArchive = Join-Path $env:RUNNER_TEMP 'irdashies-nupkg.zip'
Copy-Item -LiteralPath $package.FullName -Destination $packageArchive -Force
Expand-Archive `
-LiteralPath $packageArchive `
-DestinationPath $extractDirectory `
-Force
$packagedExecutable = Get-ChildItem `
-LiteralPath $extractDirectory `
-Recurse `
-Filter 'irdashies.exe' `
-File |
Select-Object -First 1
if (-not $packagedExecutable) {
throw 'irdashies.exe was not found in the Squirrel package.'
}
$signature = Get-AuthenticodeSignature `
-LiteralPath $packagedExecutable.FullName
if ($signature.Status -ne 'Valid') {
throw "Squirrel package contains an application with signature status $($signature.Status)."
}
Write-Output "Verified signed application in $($package.Name)."
- name: Upload unsigned installer for signing
id: upload-unsigned
uses: actions/upload-artifact@v7
with:
name: unsigned-installer
if-no-files-found: error
path: out/make/squirrel.windows/x64/*Setup.exe
- name: Sign installer with SignPath
uses: signpath/github-action-submit-signing-request@v2
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
project-slug: irdashies
signing-policy-slug: release-signing
artifact-configuration-slug: initial
github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: signed-installer
- name: Swap in the signed installer
shell: pwsh
run: |
Copy-Item signed-installer/*Setup.exe out/make/squirrel.windows/x64/ -Force
$installer = Get-ChildItem `
-Path out/make/squirrel.windows/x64 `
-Filter '*Setup.exe' `
-File |
Select-Object -First 1
$signature = Get-AuthenticodeSignature -LiteralPath $installer.FullName
if ($signature.Status -ne 'Valid') {
throw "Installer signature is $($signature.Status): $($signature.StatusMessage)"
}
Write-Output "Verified signed installer: $($signature.SignerCertificate.Subject)"
- name: Publish signed release
run: npx electron-forge publish --from-dry-run
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}