Electron Builder CI (WIN) workflow (jgraph/drawio-desktop)
The Electron Builder CI (WIN) workflow from jgraph/drawio-desktop, explained and optimized by Latchkey.
CI health: D - needs work
Run this on Latchkey for self-healing, caching, and up to 58% lower cost.
Grade your own workflow free or run it on Latchkey →What it does
This is the Electron Builder CI (WIN) workflow from the jgraph/drawio-desktop repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Electron Builder CI (WIN)
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: windows-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout reposistory
uses: actions/checkout@v6
with:
submodules: true
- name: Checkout drawio-dev
uses: actions/checkout@v6
with:
repository: jgraph/drawio-dev
token: ${{ secrets.GH_TOKEN }}
ref: release
path: drawio-dev
submodules: false
- name: Stage drawio-dev release into public submodule
shell: powershell
run: |
# Build from the current HEAD of drawio-dev's release branch (checked out
# above). We no longer require a matching diagramly-X_Y_Z tag, and the
# public drawio submodule can lag its own VERSION/tag behind this.
if (-not (Test-Path 'drawio-dev\VERSION')) {
Write-Host "::error::drawio-dev/VERSION not found on the release branch"
exit 1
}
$internalVersion = (Get-Content 'drawio-dev\VERSION' -Raw).Trim()
if ($internalVersion -notmatch '^\d+\.\d+\.\d+$') {
Write-Host "::error::drawio-dev/VERSION has unexpected format: $internalVersion"
exit 1
}
Write-Host "Internal drawio-dev VERSION: $internalVersion"
Write-Host "Public drawio submodule VERSION: $((Get-Content 'drawio\VERSION' -Raw).Trim())"
# Copy the built minified JS and the authoritative VERSION into the
# public submodule tree. `npm run sync` will then stamp the internal
# version into package.json without any changes to sync.cjs - keeping
# the public-facing build (which has no drawio-dev) working as-is.
Copy-Item -Path "drawio-dev\src\main\webapp\js\*.min.js" -Destination "drawio\src\main\webapp\js\"
Copy-Item -Path "drawio-dev\VERSION" -Destination "drawio\VERSION" -Force
Remove-Item 'drawio-dev' -Recurse -Force
cd drawio
Remove-Item 'docs','etc','src\main\java','src\main\webapp\connect','src\main\webapp\service-worker*','src\main\webapp\workbox-*' -Recurse -Force
cd src\main\webapp\js
# js/mermaid must survive: bootstrap.js eager-loads elk > mermaid > plantuml
# in a chain, so deleting it also kills PlantUML and PostConfig at runtime
Remove-Item 'atlas-viewer.min.js','atlas.min.js','cryptojs','deflate','dropbox','embed*','freehand','integrate.min.js','jquery','jszip','onedrive','orgchart','reader.min.js','rough','sanitizer','simplepeer','spin','viewer-static.min.js','viewer.min.js' -Recurse -Force
- name: Installing Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Prepare for Windows Build
shell: powershell
run: |
git config --global url."https://github.com/".insteadOf "git@github.com:"
npm ci
- name: Set up signing dependencies (Azure Trusted Signing)
shell: powershell
run: |
# Download Microsoft.ArtifactSigning.Client NuGet package and extract dlib.
# This is Microsoft's renamed successor to Microsoft.Trusted.Signing.Client
# (renamed during the Trusted Signing → Artifact Signing rebrand). Despite
# the package rename, the dlib filename and path inside the package are
# unchanged: bin/x64/Azure.CodeSigning.Dlib.dll. Pinned version keeps the
# build reproducible; bump deliberately when a new release is needed.
$package = "Microsoft.ArtifactSigning.Client"
$version = "1.0.128"
$url = "https://www.nuget.org/api/v2/package/$package/$version"
$nupkg = Join-Path $env:TEMP "$package.zip"
Invoke-WebRequest -Uri $url -OutFile $nupkg
$extract = Join-Path $env:TEMP "trusted-signing-dlib"
if (Test-Path $extract) { Remove-Item $extract -Recurse -Force }
Expand-Archive -Path $nupkg -DestinationPath $extract -Force
$dlib = Join-Path $extract "bin\x64\Azure.CodeSigning.Dlib.dll"
if (-not (Test-Path $dlib)) { throw "Trusted Signing dlib not found at $dlib" }
"TRUSTED_SIGNING_DLIB_PATH=$dlib" | Out-File -FilePath $env:GITHUB_ENV -Append
# Locate signtool.exe (Windows SDK is pre-installed on windows-latest).
# Microsoft explicitly notes that the 10.0.20348 SDK (Windows Server 2022) is
# not compatible with the Artifact Signing dlib, so filter it out before
# picking the highest version. On today's windows-latest the highest SDK is
# well above 20348 anyway; this is purely defensive against future runner
# image changes. See: https://learn.microsoft.com/azure/artifact-signing/how-to-signing-integrations
$signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '\\10\.0\.20348\.' } |
Sort-Object FullName -Descending |
Select-Object -First 1 -ExpandProperty FullName
if (-not $signtool) { throw "signtool.exe not found in Windows SDK (or only the unsupported 10.0.20348 was available)" }
"SIGNTOOL_PATH=$signtool" | Out-File -FilePath $env:GITHUB_ENV -Append
Write-Host "Trusted Signing dlib: $dlib"
Write-Host "signtool.exe: $signtool"
- name: Build for Windows (x32 & arm64)
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
shell: powershell
run: |
#Disable auto-update and build 32bit/arm64 first such that latest.yml is for 64bit only (64bit will overwrite 32bit one)
npm run sync -- disableUpdate
npm run release-win32
npm run release-win-arm64
- name: Build for Windows (x64)
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
shell: powershell
run: |
#Enable auto-update again
npm run sync
npm run release-win
- name: Build unpacked Windows x64 (for zip portable)
shell: powershell
run: |
npm run sync -- disableUpdate
npx electron-builder --win --x64 --dir
- name: Zip unpacked x64 build
shell: powershell
run: |
# Use package.json.version (set by `npm run sync` from the internal
# drawio-dev VERSION) so the zip filename matches the other
# electron-builder artifacts in the same release, even if the git tag
# that triggered this build differs from the built version.
$version = (Get-Content package.json -Raw | ConvertFrom-Json).version
cd dist
7z a "draw.io-$version-windows.zip" ".\win-unpacked\*"
- name: Build for Windows (APPX)
shell: powershell
run: |
#Disable auto-update for appx also
npm run sync -- disableUpdate
npm run release-appx
- name: Install GitHub CLI
shell: powershell
run: |
Invoke-WebRequest -Uri https://github.com/cli/cli/releases/download/v2.73.0/gh_2.73.0_windows_amd64.msi -OutFile ghcli.msi
Start-Process msiexec.exe -ArgumentList '/i', 'ghcli.msi', '/quiet', '/norestart' -Wait
- name: Upload portable zip to GitHub Release
shell: powershell
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# electron-builder publishes the draft release as v${package.json.version}
# - upload to that same release so the zip is grouped with the other
# artifacts. Reading from package.json (rather than github.ref) keeps
# this correct even if the triggering tag and the built version differ.
$version = (Get-Content package.json -Raw | ConvertFrom-Json).version
gh release upload "v$version" "dist/draw.io-$version-windows.zip" --clobberThe same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Electron Builder CI (WIN) on: push: tags: - 'v*' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: timeout-minutes: 30 runs-on: windows-latest env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout reposistory uses: actions/checkout@v6 with: submodules: true - name: Checkout drawio-dev uses: actions/checkout@v6 with: repository: jgraph/drawio-dev token: ${{ secrets.GH_TOKEN }} ref: release path: drawio-dev submodules: false - name: Stage drawio-dev release into public submodule shell: powershell run: | # Build from the current HEAD of drawio-dev's release branch (checked out # above). We no longer require a matching diagramly-X_Y_Z tag, and the # public drawio submodule can lag its own VERSION/tag behind this. if (-not (Test-Path 'drawio-dev\VERSION')) { Write-Host "::error::drawio-dev/VERSION not found on the release branch" exit 1 } $internalVersion = (Get-Content 'drawio-dev\VERSION' -Raw).Trim() if ($internalVersion -notmatch '^\d+\.\d+\.\d+$') { Write-Host "::error::drawio-dev/VERSION has unexpected format: $internalVersion" exit 1 } Write-Host "Internal drawio-dev VERSION: $internalVersion" Write-Host "Public drawio submodule VERSION: $((Get-Content 'drawio\VERSION' -Raw).Trim())" # Copy the built minified JS and the authoritative VERSION into the # public submodule tree. `npm run sync` will then stamp the internal # version into package.json without any changes to sync.cjs - keeping # the public-facing build (which has no drawio-dev) working as-is. Copy-Item -Path "drawio-dev\src\main\webapp\js\*.min.js" -Destination "drawio\src\main\webapp\js\" Copy-Item -Path "drawio-dev\VERSION" -Destination "drawio\VERSION" -Force Remove-Item 'drawio-dev' -Recurse -Force cd drawio Remove-Item 'docs','etc','src\main\java','src\main\webapp\connect','src\main\webapp\service-worker*','src\main\webapp\workbox-*' -Recurse -Force cd src\main\webapp\js # js/mermaid must survive: bootstrap.js eager-loads elk > mermaid > plantuml # in a chain, so deleting it also kills PlantUML and PostConfig at runtime Remove-Item 'atlas-viewer.min.js','atlas.min.js','cryptojs','deflate','dropbox','embed*','freehand','integrate.min.js','jquery','jszip','onedrive','orgchart','reader.min.js','rough','sanitizer','simplepeer','spin','viewer-static.min.js','viewer.min.js' -Recurse -Force - name: Installing Node uses: actions/setup-node@v6 with: cache: 'npm' node-version: 24 - name: Prepare for Windows Build shell: powershell run: | git config --global url."https://github.com/".insteadOf "git@github.com:" npm ci - name: Set up signing dependencies (Azure Trusted Signing) shell: powershell run: | # Download Microsoft.ArtifactSigning.Client NuGet package and extract dlib. # This is Microsoft's renamed successor to Microsoft.Trusted.Signing.Client # (renamed during the Trusted Signing → Artifact Signing rebrand). Despite # the package rename, the dlib filename and path inside the package are # unchanged: bin/x64/Azure.CodeSigning.Dlib.dll. Pinned version keeps the # build reproducible; bump deliberately when a new release is needed. $package = "Microsoft.ArtifactSigning.Client" $version = "1.0.128" $url = "https://www.nuget.org/api/v2/package/$package/$version" $nupkg = Join-Path $env:TEMP "$package.zip" Invoke-WebRequest -Uri $url -OutFile $nupkg $extract = Join-Path $env:TEMP "trusted-signing-dlib" if (Test-Path $extract) { Remove-Item $extract -Recurse -Force } Expand-Archive -Path $nupkg -DestinationPath $extract -Force $dlib = Join-Path $extract "bin\x64\Azure.CodeSigning.Dlib.dll" if (-not (Test-Path $dlib)) { throw "Trusted Signing dlib not found at $dlib" } "TRUSTED_SIGNING_DLIB_PATH=$dlib" | Out-File -FilePath $env:GITHUB_ENV -Append # Locate signtool.exe (Windows SDK is pre-installed on windows-latest). # Microsoft explicitly notes that the 10.0.20348 SDK (Windows Server 2022) is # not compatible with the Artifact Signing dlib, so filter it out before # picking the highest version. On today's windows-latest the highest SDK is # well above 20348 anyway; this is purely defensive against future runner # image changes. See: https://learn.microsoft.com/azure/artifact-signing/how-to-signing-integrations $signtool = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch '\\10\.0\.20348\.' } | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName if (-not $signtool) { throw "signtool.exe not found in Windows SDK (or only the unsupported 10.0.20348 was available)" } "SIGNTOOL_PATH=$signtool" | Out-File -FilePath $env:GITHUB_ENV -Append Write-Host "Trusted Signing dlib: $dlib" Write-Host "signtool.exe: $signtool" - name: Build for Windows (x32 & arm64) env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} shell: powershell run: | #Disable auto-update and build 32bit/arm64 first such that latest.yml is for 64bit only (64bit will overwrite 32bit one) npm run sync -- disableUpdate npm run release-win32 npm run release-win-arm64 - name: Build for Windows (x64) env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} shell: powershell run: | #Enable auto-update again npm run sync npm run release-win - name: Build unpacked Windows x64 (for zip portable) shell: powershell run: | npm run sync -- disableUpdate npx electron-builder --win --x64 --dir - name: Zip unpacked x64 build shell: powershell run: | # Use package.json.version (set by `npm run sync` from the internal # drawio-dev VERSION) so the zip filename matches the other # electron-builder artifacts in the same release, even if the git tag # that triggered this build differs from the built version. $version = (Get-Content package.json -Raw | ConvertFrom-Json).version cd dist 7z a "draw.io-$version-windows.zip" ".\win-unpacked\*" - name: Build for Windows (APPX) shell: powershell run: | #Disable auto-update for appx also npm run sync -- disableUpdate npm run release-appx - name: Install GitHub CLI shell: powershell run: | Invoke-WebRequest -Uri https://github.com/cli/cli/releases/download/v2.73.0/gh_2.73.0_windows_amd64.msi -OutFile ghcli.msi Start-Process msiexec.exe -ArgumentList '/i', 'ghcli.msi', '/quiet', '/norestart' -Wait - name: Upload portable zip to GitHub Release shell: powershell env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # electron-builder publishes the draft release as v${package.json.version} # - upload to that same release so the zip is grouped with the other # artifacts. Reading from package.json (rather than github.ref) keeps # this correct even if the triggering tag and the built version differ. $version = (Get-Content package.json -Raw | ConvertFrom-Json).version gh release upload "v$version" "dist/draw.io-$version-windows.zip" --clobber
What changed
- Cancel superseded runs when a branch or PR gets a newer push.
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.