Skip to content
Latchkey

Electron Builder CI (WIN) workflow (jgraph/drawio-desktop)

The Electron Builder CI (WIN) workflow from jgraph/drawio-desktop, explained and optimized by Latchkey.

D

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 →
Source: jgraph/drawio-desktop.github/workflows/electron-builder-win.ymlLicense Apache-2.0View source

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

workflow (.yml)
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" --clobber

The 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

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:

This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow