Build for Windows #134
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: Build for Windows | |
| on: workflow_dispatch | |
| # SignPath needs access to artifacts; set minimal permissions. | |
| permissions: | |
| contents: write | |
| actions: read | |
| jobs: | |
| build-x86: | |
| runs-on: windows-latest | |
| env: | |
| WOLFRAM_SYSTEM_ID: Windows-x86-64-v7 | |
| WOLFRAMENGINE_INSTALL_MSI_DOWNLOAD_URL: https://files.wolframcdn.com/packages/winget/14.0.0.0/WolframEngine_14.0.0_WIN.msi | |
| WOLFRAMENGINE_CACHE_KEY: WolframEngine-B | |
| WOLFRAMENGINE_INSTALLATION_SUBDIRECTORY: WolframEngine | |
| # Adjust this if your build outputs to a different pattern/name. | |
| # Common electron-builder outputs: dist/*.exe, dist/*.msi | |
| unsigned_glob: dist/*.exe | |
| steps: | |
| - name: Resolve temp-based paths | |
| shell: pwsh | |
| run: | | |
| echo "SIGNED_OUT_DIR=$env:RUNNER_TEMP\signed-artifacts" >> $env:GITHUB_ENV | |
| - name: Check out repository | |
| uses: actions/checkout@v2 | |
| with: | |
| token: ${{ secrets.GH_TOKEN }} | |
| - name: Patch specific dependencies from package.json | |
| shell: pwsh | |
| run: | | |
| $pkgPath = "package.json" | |
| $json = Get-Content $pkgPath -Raw | ConvertFrom-Json | |
| $dependenciesToRemove = @( | |
| "dmg-license", | |
| "electron-trackpad-utils" | |
| ) | |
| foreach ($dep in $dependenciesToRemove) { | |
| if ($json.dependencies.$dep) { | |
| $json.dependencies.PSObject.Properties.Remove($dep) | |
| } | |
| if ($json.devDependencies.$dep) { | |
| $json.devDependencies.PSObject.Properties.Remove($dep) | |
| } | |
| } | |
| $json | ConvertTo-Json -Depth 10 | Out-File -Encoding UTF8 $pkgPath | |
| - name: Install Node.js manually | |
| run: | | |
| Invoke-WebRequest https://nodejs.org/dist/v23.9.0/node-v23.9.0-x64.msi -OutFile nodejs.msi | |
| Start-Process msiexec.exe -Wait -ArgumentList '/quiet', '/i', 'nodejs.msi' | |
| shell: powershell | |
| - name: Check Node version | |
| run: | | |
| node -v | |
| npm -v | |
| shell: powershell | |
| - name: Install Node.js dependencies | |
| run: | | |
| npm install | |
| - name: Define DLL dirs | |
| shell: pwsh | |
| run: | | |
| $dirs = @( | |
| "Packages\CSockets\Fallback", | |
| "Packages\CSockets\LibraryResources\Windows-x86-64-v6", | |
| "Packages\CSockets\LibraryResources\Windows-x86-64-v7", | |
| "Packages\CSockets\UV\Windows-x86-64" | |
| ) | |
| # Persist for later steps (semicolon-separated) | |
| "DLL_DIRS=$($dirs -join ';')" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| # Common paths used below | |
| "DLL_STAGE=$env:RUNNER_TEMP\dll-stage" | Out-File $env:GITHUB_ENV -Append | |
| "DLL_ZIP=$env:RUNNER_TEMP\dlls-to-sign.zip" | Out-File $env:GITHUB_ENV -Append | |
| "SIGNED_DLL_DIR=$env:RUNNER_TEMP\signed-dlls" | Out-File $env:GITHUB_ENV -Append | |
| "DLL_EXTRACT=$env:RUNNER_TEMP\dlls-signed-extract" | Out-File $env:GITHUB_ENV -Append | |
| # Stage DLLs preserving relative paths (same as before, but no Compress-Archive) | |
| - name: Stage DLLs | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $dirs = $env:DLL_DIRS -split ';' | |
| if (Test-Path $env:DLL_STAGE) { Remove-Item $env:DLL_STAGE -Recurse -Force } | |
| New-Item -ItemType Directory -Force -Path $env:DLL_STAGE | Out-Null | |
| foreach ($dir in $dirs) { | |
| if (-not (Test-Path $dir)) { throw "Folder not found: $dir" } | |
| $dlls = Get-ChildItem -Path $dir -Filter *.dll -File | |
| if ($dlls.Count -ne 1) { throw "Expected exactly 1 .dll in $dir, found $($dlls.Count)" } | |
| $dll = $dlls[0] | |
| $dest = Join-Path $env:DLL_STAGE $dir | |
| New-Item -ItemType Directory -Force -Path $dest | Out-Null | |
| Copy-Item $dll.FullName -Destination $dest -Force | |
| } | |
| - name: Upload unsigned DLL artifact (folder, not zip) | |
| id: upload-unsigned-dlls | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unsigned-dlls-zip | |
| path: ${{ env.DLL_STAGE }}/* | |
| if-no-files-found: error | |
| compression-level: 0 | |
| - name: Submit DLL signing request to SignPath | |
| id: sign-dlls | |
| uses: signpath/github-action-submit-signing-request@v1 | |
| with: | |
| api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' | |
| organization-id: 'a11e9ec9-516b-42a1-97d7-8a62e7508a48' | |
| project-slug: 'wolfram-js-frontend' | |
| signing-policy-slug: 'release-signing' | |
| artifact-configuration-slug: 'dll' | |
| github-artifact-id: '${{ steps.upload-unsigned-dlls.outputs.artifact-id }}' | |
| wait-for-completion: true | |
| output-artifact-directory: '${{ env.SIGNED_DLL_DIR }}' | |
| - name: Put signed DLLs back into their original folders (handles extracted or zip; deep search + logging) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| Write-Host "Signed output directory: $env:SIGNED_DLL_DIR" | |
| # If the connector downloaded a .zip, extract it; otherwise use the extracted dir as-is. | |
| $root = $env:SIGNED_DLL_DIR | |
| $signedZip = Get-ChildItem -Path $root -Recurse -Filter *.zip -File | Select-Object -First 1 | |
| if ($signedZip) { | |
| Write-Host "Found signed ZIP: $($signedZip.FullName)" | |
| if (Test-Path $env:DLL_EXTRACT) { | |
| Write-Host "Cleaning previous extract folder: $env:DLL_EXTRACT" | |
| Remove-Item $env:DLL_EXTRACT -Recurse -Force | |
| } | |
| New-Item -ItemType Directory -Force -Path $env:DLL_EXTRACT | Out-Null | |
| Write-Host "Extracting ZIP to: $env:DLL_EXTRACT" | |
| Expand-Archive -Path $signedZip.FullName -DestinationPath $env:DLL_EXTRACT -Force | |
| $root = $env:DLL_EXTRACT | |
| } else { | |
| Write-Host "No ZIP found; assuming files were already extracted under: $root" | |
| } | |
| $dirs = $env:DLL_DIRS -split ';' | |
| Write-Host "DLL directories to restore:" ($dirs -join ', ') | |
| foreach ($dir in $dirs) { | |
| if (-not (Test-Path $dir)) { throw "Destination folder not found: $dir" } | |
| # We expect exactly one original DLL in each destination folder (non-recursive). | |
| $origDlls = Get-ChildItem -Path $dir -Filter *.dll -File | |
| if ($origDlls.Count -eq 0) { throw "No original .dll found in destination: $dir" } | |
| if ($origDlls.Count -gt 1) { throw "Expected exactly 1 original .dll in $dir, found $($origDlls.Count)" } | |
| $origDll = $origDlls[0] | |
| # First try to find the signed DLL in the same relative subfolder we staged. | |
| $relCandidateDir = Join-Path $root $dir | |
| $signed = $null | |
| if (Test-Path $relCandidateDir) { | |
| $signed = Get-ChildItem -Path $relCandidateDir -Filter $origDll.Name -File -Recurse | Select-Object -First 1 | |
| if ($signed) { | |
| Write-Host "Found signed DLL under expected relative path: $($relCandidateDir)" | |
| } | |
| } | |
| # Fallback: deep search anywhere under $root by filename. | |
| if (-not $signed) { | |
| Write-Host "Searching deeply for signed '$($origDll.Name)' under: $root" | |
| $signedMatches = Get-ChildItem -Path $root -Recurse -Filter $origDll.Name -File | |
| if ($signedMatches.Count -eq 0) { | |
| Write-Host "Signed DLL named '$($origDll.Name)' not found. Listing DLLs present for debugging:" | |
| Get-ChildItem -Path $root -Recurse -Filter *.dll -File | ForEach-Object { Write-Host " $_" } | |
| throw "Missing signed DLL for: $($origDll.FullName)" | |
| } | |
| if ($signedMatches.Count -gt 1) { | |
| Write-Host "::warning::Multiple signed matches for '$($origDll.Name)'. Using the first match." | |
| } | |
| $signed = $signedMatches[0] | |
| } | |
| # Log the copy with absolute paths | |
| $fromAbs = (Resolve-Path $signed.FullName).Path | |
| $toAbsDir = (Resolve-Path $dir).Path | |
| $toAbs = Join-Path $toAbsDir $origDll.Name | |
| Write-Host "Copying signed DLL:" | |
| Write-Host " FROM: $fromAbs" | |
| Write-Host " TO: $toAbs" | |
| Copy-Item -Path $signed.FullName -Destination $dir -Force | |
| if (Test-Path $toAbs) { | |
| $size = (Get-Item $toAbs).Length | |
| Write-Host " ✔ Placed: $toAbs ($size bytes)" | |
| } else { | |
| throw "Copy failed: $toAbs not found after copy." | |
| } | |
| } | |
| Write-Host "All signed DLLs restored to their original folders." | |
| - name: Build Electron (no publish) | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | |
| run: | | |
| npx electron-builder --win --x64 --publish never | |
| # ───────────────────────────── | |
| # SignPath integration | |
| # ───────────────────────────── | |
| - name: Upload unsigned artifact (for SignPath) | |
| id: upload-unsigned-artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unsigned-windows-artifact | |
| path: ${{ env.unsigned_glob }} # e.g. dist/wljs-notebook-*-x64-win.exe | |
| if-no-files-found: error | |
| compression-level: 0 | |
| - name: Submit signing request to SignPath | |
| id: sign-with-signpath | |
| uses: signpath/github-action-submit-signing-request@v1 | |
| with: | |
| api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' | |
| organization-id: 'a11e9ec9-516b-42a1-97d7-8a62e7508a48' | |
| project-slug: 'wolfram-js-frontend' | |
| signing-policy-slug: 'release-signing' | |
| github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}' | |
| wait-for-completion: true | |
| output-artifact-directory: ${{ env.SIGNED_OUT_DIR }} | |
| # Read app version (PowerShell, works on Windows) | |
| - name: Read app version | |
| id: appver | |
| shell: pwsh | |
| run: | | |
| $ver = (Get-Content package.json -Raw | ConvertFrom-Json).version | |
| "version=$ver" >> $env:GITHUB_OUTPUT | |
| # (Optional) List what we actually got back from SignPath | |
| - name: Debug signed folder | |
| shell: pwsh | |
| run: | | |
| Write-Host "SIGNED_OUT_DIR: $env:SIGNED_OUT_DIR" | |
| Get-ChildItem -Recurse -Force "$env:SIGNED_OUT_DIR" | Format-List FullName,Length | |
| # Find the signed installer (EXE preferred, else MSI) | |
| - name: Find signed installer | |
| id: find_signed | |
| shell: pwsh | |
| run: | | |
| $dir = "$env:SIGNED_OUT_DIR" | |
| $exe = Get-ChildItem -Path $dir -Recurse -Filter *.exe | Select-Object -First 1 | |
| $msi = Get-ChildItem -Path $dir -Recurse -Filter *.msi | Select-Object -First 1 | |
| if ($exe) { | |
| "file=$($exe.FullName)" >> $env:GITHUB_OUTPUT | |
| "ext=.exe" >> $env:GITHUB_OUTPUT | |
| } elseif ($msi) { | |
| "file=$($msi.FullName)" >> $env:GITHUB_OUTPUT | |
| "ext=.msi" >> $env:GITHUB_OUTPUT | |
| } else { | |
| Write-Error "No .exe or .msi found under $dir" | |
| exit 1 | |
| } | |
| # Publish the FOUND file (exact path) to a release | |
| - name: Publish signed artifact to GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.appver.outputs.version }} | |
| name: v${{ steps.appver.outputs.version }} | |
| draft: true | |
| prerelease: false | |
| files: ${{ steps.find_signed.outputs.file }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} | |