fix(install.ps1): pre-warm electron-builder winCodeSign cache + fix Stage-Desktop $HasNode false-skip

Two bugs caught in the second VM end-to-end run:

1. electron-builder's winCodeSign extraction fails on grandma-class
   Windows boxes because the .7z archive contains macOS symlinks
   (darwin/10.12/lib/libcrypto.dylib and libssl.dylib pointing at
   versioned siblings). Creating symlinks on Windows requires
   SeCreateSymbolicLinkPrivilege, a per-user right that non-admin
   accounts don't have on stock Windows. Result: every fresh install
   on a non-admin user fails Stage-Desktop with a 7-Zip 'cannot create
   symbolic link' error, retried four times, then bails.

   Fix: Initialize-ElectronBuilderCache pre-extracts winCodeSign-2.6.0.7z
   ourselves with -snl (don't preserve symlinks, store as resolved file
   content) AND -x!darwin (skip the entire macOS subtree — irrelevant
   on Windows). Writes to electron-builder's expected cache dir before
   electron-builder gets a chance to try its own broken extraction.
   Idempotent — fast-paths via signtool.exe sentinel check.

2. Install-Desktop's first guard was 'if (-not $HasNode) skip'.
   $HasNode is set by Stage-Node into $script:HasNode, but in
   cross-process driver mode (each -Stage NAME is a fresh powershell.exe
   spawned by Hermes-Setup.exe), that script-scope variable from the
   PREVIOUS process is invisible — so the guard always fired and
   Install-Desktop returned in 900ms with a misleading
   'Node.js not available' reason. The real npm probe below it never
   got to run. Fix: re-probe npm directly via Get-Command when $HasNode
   is empty/false, since by that point Stage-Node has already verified
   Node is installed and the only question is whether *this* process
   can see it on PATH (it can — installer-wide PATH update from Stage-Node).
This commit is contained in:
emozilla 2026-05-28 03:05:26 -04:00
parent 0a079f7321
commit c7e46f9f3d

View file

@ -1837,6 +1837,106 @@ function Install-NodeDeps {
}
}
function Initialize-ElectronBuilderCache {
# Pre-warm electron-builder's winCodeSign cache so it never tries to
# extract the .7z archive itself.
#
# The bug we're working around: winCodeSign-2.6.0.7z contains macOS
# symbolic links under darwin/10.12/lib/ (libcrypto.dylib, libssl.dylib
# pointing at versioned siblings). On Windows, 7-Zip's extraction of
# those symlinks requires SeCreateSymbolicLinkPrivilege — a per-user
# right that non-admin accounts on stock Windows don't have. So
# electron-builder fails the extraction every time it pulls a fresh
# cache, on every grandma-class box.
#
# The fix: do the extraction ourselves with `-x!darwin` to skip the
# entire macOS subtree. electron-builder is doing a WINDOWS build —
# it never reads anything under darwin/ on this code path. As long
# as the cache directory exists with the Windows-relevant files,
# electron-builder's "is the cache present?" check passes and it
# skips its own extraction entirely.
#
# Tooling: we use 7za.exe from the 7zip-bin npm package that
# electron-builder itself depends on (so we know it's present after
# the workspace `npm install` finishes). Falls back to no-op if
# neither is found — electron-builder will then attempt its own
# broken extraction and fail with a recognizable error.
$cacheRoot = "$env:LOCALAPPDATA\electron-builder\Cache\winCodeSign"
$extractedDir = "$cacheRoot\winCodeSign-2.6.0"
$sentinel = "$extractedDir\windows-10\x64\signtool.exe"
# Fast-path: already populated from a prior run.
if (Test-Path $sentinel) {
Write-Info "electron-builder winCodeSign cache already populated"
return
}
# Locate 7za.exe. electron-builder hoists 7zip-bin to the workspace
# root node_modules so apps/desktop's pack step can find it.
$sevenZip = "$InstallDir\node_modules\7zip-bin\win\x64\7za.exe"
if (-not (Test-Path $sevenZip)) {
# Some npm versions don't hoist; check apps/desktop's local copy.
$sevenZip = "$InstallDir\apps\desktop\node_modules\7zip-bin\win\x64\7za.exe"
}
if (-not (Test-Path $sevenZip)) {
Write-Warn "7za.exe not found in node_modules; electron-builder may fail to extract winCodeSign"
Write-Warn " Looked at: $InstallDir\node_modules\7zip-bin\win\x64\7za.exe"
Write-Warn " and: $InstallDir\apps\desktop\node_modules\7zip-bin\win\x64\7za.exe"
return
}
New-Item -ItemType Directory -Force -Path $cacheRoot | Out-Null
# Download to a TEMP path; electron-builder doesn't care about the
# archive itself once the extracted dir exists.
$tmpArchive = "$env:TEMP\hermes-wincodesign-$(Get-Random).7z"
$url = "https://github.com/electron-userland/electron-builder-binaries/releases/download/winCodeSign-2.6.0/winCodeSign-2.6.0.7z"
Write-Info "Pre-extracting winCodeSign to skip electron-builder's broken extraction..."
try {
Invoke-WebRequest -Uri $url -OutFile $tmpArchive -UseBasicParsing -ErrorAction Stop
} catch {
Write-Warn "Failed to download winCodeSign: $_"
Write-Warn " electron-builder will fall back to its own extraction (which fails on non-admin Windows)"
return
}
# 7-Zip flags:
# x extract with full paths
# -y assume yes on all prompts
# -bd no progress bar (silent)
# -snl do NOT extract symbolic links as links — store as the
# resolved file content instead. This is what stops the
# privilege-not-held crash on the darwin/*.dylib symlinks.
# -x!darwin exclude the entire darwin/ subtree (macOS-specific
# code-signing tools that a Windows build doesn't need)
# -o<dir> output directory
#
# Belt-and-suspenders: we BOTH skip the darwin subtree AND tell 7-Zip
# to dereference any remaining symlinks. Either alone would fix it;
# together they're defensive against winCodeSign adding more symlinks
# under linux/ or appxAssets/ in some future release.
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = "Continue"
& $sevenZip x -y -bd -snl "-x!darwin" "-o$cacheRoot" $tmpArchive 2>&1 | Out-Null
$code = $LASTEXITCODE
$ErrorActionPreference = $prevEAP
Remove-Item -Force $tmpArchive -ErrorAction SilentlyContinue
if ($code -ne 0) {
Write-Warn "Pre-extraction of winCodeSign failed (7-Zip exit $code)"
return
}
if (Test-Path $sentinel) {
Write-Success "winCodeSign cache pre-populated at $extractedDir"
} else {
Write-Warn "winCodeSign extraction completed but expected file is missing: $sentinel"
}
}
function Install-Desktop {
# Build apps/desktop into a launchable Hermes.exe. Only called from
# Stage-Desktop, which is itself only included in the manifest when
@ -1856,9 +1956,15 @@ function Install-Desktop {
# don't need to produce an NSIS/MSI artifact here.
if (-not $HasNode) {
Write-Warn "Skipping desktop build (Node.js not installed)"
$script:_StageSkippedReason = "Node.js not available"
return
# Cross-process driver mode: each `-Stage NAME` invocation runs in a
# fresh PowerShell process, so $script:HasNode set by Stage-Node
# in the previous process isn't visible. Re-detect rather than
# trusting the global.
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
Write-Warn "Skipping desktop build (Node.js / npm not on PATH)"
$script:_StageSkippedReason = "Node.js not available"
return
}
}
$desktopDir = "$InstallDir\apps\desktop"
@ -1913,6 +2019,13 @@ function Install-Desktop {
}
Pop-Location
# Pre-warm electron-builder's winCodeSign cache so its own broken
# 7-Zip extraction doesn't fire later. Has to happen AFTER the
# workspace npm install (we need 7za.exe from 7zip-bin) but BEFORE
# `npm run pack` (which triggers electron-builder's first cache miss).
# See Initialize-ElectronBuilderCache for the full rationale.
Initialize-ElectronBuilderCache
# 2. Build apps/desktop. `npm run pack` runs:
# assert-root-install + write-build-stamp + stage-native-deps +
# tsc -b + vite build + electron-builder --dir