Build for Windows #106
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: 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: Cache/restore Wolfram Engine install | |
| id: cache-restore | |
| uses: actions/cache@v4 | |
| env: | |
| WOLFRAMENGINE_INSTALLATION_DIRECTORY: '${{ runner.temp }}\${{ env.WOLFRAMENGINE_INSTALLATION_SUBDIRECTORY }}' | |
| with: | |
| path: ${{ env.WOLFRAMENGINE_INSTALLATION_DIRECTORY }} | |
| key: wolframengine-${{ env.WOLFRAM_SYSTEM_ID }}-${{ env.WOLFRAMENGINE_CACHE_KEY }} | |
| - name: Download and install Wolfram Engine | |
| if: steps.cache-restore.outputs.cache-hit != 'true' | |
| env: | |
| WOLFRAMENGINE_INSTALLATION_DIRECTORY: '${{ runner.temp }}\${{ env.WOLFRAMENGINE_INSTALLATION_SUBDIRECTORY }}' | |
| WOLFRAMENGINE_INSTALL_MSI_PATH: '${{ runner.temp }}\WolframEngine-Install.msi' | |
| WOLFRAMENGINE_INSTALL_LOG_PATH: '${{ runner.temp }}\WolframEngine-Install.log' | |
| run: | | |
| echo 'Downloading Wolfram Engine installer...' | |
| $msiFile = '${{ env.WOLFRAMENGINE_INSTALL_MSI_PATH }}' | |
| $logFile = '${{ env.WOLFRAMENGINE_INSTALL_LOG_PATH }}' | |
| Import-Module BitsTransfer | |
| Start-BitsTransfer '${{ env.WOLFRAMENGINE_INSTALL_MSI_DOWNLOAD_URL }}' $msiFile | |
| echo 'Downloaded Wolfram Engine installer.' | |
| $DataStamp = get-date -Format yyyyMMddTHHmmss | |
| $MSIArguments = @( | |
| "/i" | |
| ('"{0}"' -f $msiFile) | |
| 'INSTALLLOCATION="${{ env.WOLFRAMENGINE_INSTALLATION_DIRECTORY }}"' | |
| "/qn" | |
| "/norestart" | |
| "/L*v" | |
| $logFile | |
| ) | |
| echo 'Installing Wolfram Engine...' | |
| Start-Process "msiexec.exe" -ArgumentList $MSIArguments -Wait -NoNewWindow | |
| echo 'Installed Wolfram Engine.' | |
| - name: Bundle files | |
| env: | |
| WOLFRAMENGINE_INSTALLATION_DIRECTORY: '${{ runner.temp }}\${{ env.WOLFRAMENGINE_INSTALLATION_SUBDIRECTORY }}' | |
| WOLFRAMINIT: "-pwfile !cloudlm.wolfram.com -entitlement ${{ secrets.WOLFRAM_LICENSE_ENTITLEMENT_ID }}" | |
| run: | | |
| $env:Path += ';${{ env.WOLFRAMENGINE_INSTALLATION_DIRECTORY }}\' | |
| wolfram -script ./Scripts/bundle.wls | |
| - name: Define DLL dirs | |
| shell: pwsh | |
| run: | | |
| $dirs = @( | |
| "wl_packages\KirillBelov_CSockets\Fallback", | |
| "wl_packages\KirillBelov_CSockets\LibraryResources\Windows-x86-64-v6", | |
| "wl_packages\KirillBelov_CSockets\LibraryResources\Windows-x86-64-v7", | |
| "wl_packages\KirillBelov_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: 'test-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 (with logging) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| Write-Host "Signed output directory: $env:SIGNED_DLL_DIR" | |
| $signedZip = Get-ChildItem -Path "${{ env.SIGNED_DLL_DIR }}" -Recurse -Filter *.zip | Select-Object -First 1 | |
| if (-not $signedZip) { throw "Signed ZIP not found in $env:SIGNED_DLL_DIR" } | |
| Write-Host "Using 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 | |
| $dirs = $env:DLL_DIRS -split ';' | |
| Write-Host "DLL directories to restore:" ($dirs -join ', ') | |
| foreach ($dir in $dirs) { | |
| $srcDir = Join-Path $env:DLL_EXTRACT $dir | |
| if (-not (Test-Path $srcDir)) { throw "Signed folder not found in ZIP: $srcDir" } | |
| # Only 1 DLL expected per folder | |
| $signed = Get-ChildItem $srcDir -Filter *.dll -File | Select-Object -First 1 | |
| if (-not $signed) { throw "No signed .dll found under $srcDir" } | |
| $destDir = $dir | |
| $destPath = Join-Path $destDir $signed.Name | |
| # Log the copy with absolute paths | |
| $fromAbs = (Resolve-Path $signed.FullName).Path | |
| $toAbs = (Resolve-Path $destDir).Path | |
| $toFull = Join-Path $toAbs $signed.Name | |
| Write-Host "Copying signed DLL:" | |
| Write-Host " FROM: $fromAbs" | |
| Write-Host " TO: $toFull" | |
| Copy-Item -Path $signed.FullName -Destination $destDir -Force | |
| # Quick confirmation | |
| if (Test-Path $destPath) { | |
| $size = (Get-Item $destPath).Length | |
| Write-Host " ✔ Placed: $destPath ($size bytes)" | |
| } else { | |
| throw "Copy failed: $destPath 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: 'test-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 }} | |