Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
},
"build": {
"electronVersion": "40.10.2",
"electronDist": "../../node_modules/electron/dist",
"electronDist": "node_modules/electron/dist",
"appId": "com.nousresearch.hermes",
"productName": "Hermes",
"executableName": "Hermes",
Expand Down
36 changes: 18 additions & 18 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5110,44 +5110,44 @@ def _purge_electron_build_cache(desktop_dir: Path) -> list[Path]:
return removed


def _electron_dist_binary(project_root: Path) -> Path:
def _electron_dist_binary(desktop_dir: Path) -> Path:
"""Return the path to the Electron main binary inside ``node_modules``.

electron-builder reads the binary from ``build.electronDist``
(``node_modules/electron/dist``) since #38673, so this is the exact file
whose absence makes a pack fail with "The specified electronDist does not
exist". The basename differs per OS (the platform Electron is named for the
host the build runs on).
(``node_modules/electron/dist`` relative to ``apps/desktop``) since #38673,
so this is the exact file whose absence makes a pack fail with "The
specified electronDist does not exist". The basename differs per OS (the
platform Electron is named for the host the build runs on).
"""
dist = project_root / "node_modules" / "electron" / "dist"
dist = desktop_dir / "node_modules" / "electron" / "dist"
if sys.platform == "darwin":
return dist / "Electron.app" / "Contents" / "MacOS" / "Electron"
if sys.platform == "win32":
return dist / "electron.exe"
return dist / "electron"


def _electron_dist_ok(project_root: Path) -> bool:
"""True when ``node_modules/electron/dist`` holds a usable Electron binary.
def _electron_dist_ok(desktop_dir: Path) -> bool:
"""True when the desktop workspace Electron dist holds a usable binary.

A directory that exists but is missing the binary (a partial extraction from
a corrupt cached zip, or an interrupted postinstall) counts as NOT ok, since
that is exactly the shape that makes electron-builder throw on the pinned
electronDist.
"""
try:
return _electron_dist_binary(project_root).exists()
return _electron_dist_binary(desktop_dir).exists()
except OSError:
return False


def _redownload_electron_dist(
project_root: Path,
desktop_dir: Path,
env: dict,
*,
mirror: Optional[str] = None,
) -> bool:
"""(Re)populate ``node_modules/electron/dist`` via electron's own downloader.
"""(Re)populate desktop ``node_modules/electron/dist`` via electron's downloader.

Since #38673 the desktop build pins ``build.electronDist`` to
``node_modules/electron/dist``, so electron-builder reads the Electron binary
Expand All @@ -5166,10 +5166,10 @@ def _redownload_electron_dist(
mirror. Best-effort: never raises. Returns True iff the dist binary exists
afterward.
"""
if _electron_dist_ok(project_root):
if _electron_dist_ok(desktop_dir):
return True

electron_dir = project_root / "node_modules" / "electron"
electron_dir = desktop_dir / "node_modules" / "electron"
installer = electron_dir / "install.js"
if not installer.is_file():
return False
Expand All @@ -5191,7 +5191,7 @@ def _redownload_electron_dist(
subprocess.run([node, str(installer)], cwd=str(electron_dir), env=dl_env, check=False)
except OSError:
return False
return _electron_dist_ok(project_root)
return _electron_dist_ok(desktop_dir)


def _stop_desktop_processes_locking_build(desktop_dir: Path) -> list[int]:
Expand Down Expand Up @@ -5456,8 +5456,8 @@ def cmd_gui(args: argparse.Namespace):
# a binary to read. Gated on the dist check so an unrelated build
# failure (tsc/vite) doesn't trigger a pointless ~200 MB refetch.
restored = False
if not _electron_dist_ok(PROJECT_ROOT):
restored = _redownload_electron_dist(PROJECT_ROOT, env)
if not _electron_dist_ok(desktop_dir):
restored = _redownload_electron_dist(desktop_dir, env)
if purged or restored:
print(" ⚠ Desktop build failed; refreshed the Electron download and retrying once...")
for p in purged:
Expand Down Expand Up @@ -5487,9 +5487,9 @@ def cmd_gui(args: argparse.Namespace):
# electron's own downloader. Re-fetch the binary through the
# mirror first; otherwise the retry just re-reads the same missing
# dist and re-throws "electronDist does not exist" (#47266).
have_dist = _electron_dist_ok(PROJECT_ROOT)
have_dist = _electron_dist_ok(desktop_dir)
if not have_dist:
have_dist = _redownload_electron_dist(PROJECT_ROOT, env, mirror=mirror)
have_dist = _redownload_electron_dist(desktop_dir, env, mirror=mirror)
if have_dist:
_stop_desktop_processes_locking_build(desktop_dir)
build_result = subprocess.run([npm, "run", build_script], cwd=desktop_dir, env=mirror_env, check=False)
Expand Down
30 changes: 15 additions & 15 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2161,15 +2161,15 @@ function Clear-ElectronBuildCache {
return $removed
}

# True when node_modules\electron\dist holds a usable Electron binary.
# electron-builder reads the binary from build.electronDist
# (node_modules\electron\dist) since #38673, so this is the exact file whose
# absence makes a pack fail with "The specified electronDist does not exist". A
# dist dir that exists but is missing electron.exe (partial extraction / aborted
# postinstall) is NOT ok.
# True when apps\desktop\node_modules\electron\dist holds a usable Electron
# binary. electron-builder reads the binary from build.electronDist
# (node_modules\electron\dist relative to apps\desktop) since #38673, so this is
# the exact file whose absence makes a pack fail with "The specified
# electronDist does not exist". A dist dir that exists but is missing
# electron.exe (partial extraction / aborted postinstall) is NOT ok.
function Test-ElectronDist {
param([string]$InstallDir)
$distExe = Join-Path $InstallDir 'node_modules\electron\dist\electron.exe'
param([string]$DesktopDir)
$distExe = Join-Path $DesktopDir 'node_modules\electron\dist\electron.exe'
return (Test-Path -LiteralPath $distExe)
}

Expand All @@ -2190,10 +2190,10 @@ function Test-ElectronDist {
# mirror. Best-effort: never throws. Returns $true iff the dist binary exists
# afterward.
function Restore-ElectronDist {
param([string]$InstallDir, [string]$Mirror)
if (Test-ElectronDist -InstallDir $InstallDir) { return $true }
param([string]$DesktopDir, [string]$Mirror)
if (Test-ElectronDist -DesktopDir $DesktopDir) { return $true }

$electronDir = Join-Path $InstallDir 'node_modules\electron'
$electronDir = Join-Path $DesktopDir 'node_modules\electron'
$distExe = Join-Path $electronDir 'dist\electron.exe'
$installer = Join-Path $electronDir 'install.js'
if (-not (Test-Path -LiteralPath $installer)) { return $false }
Expand Down Expand Up @@ -2378,8 +2378,8 @@ function Install-Desktop {
# read. Gated on the dist check so an unrelated build failure
# (tsc/vite) doesn't trigger a pointless ~200MB refetch.
$restored = $false
if (-not (Test-ElectronDist -InstallDir $InstallDir)) {
$restored = Restore-ElectronDist -InstallDir $InstallDir
if (-not (Test-ElectronDist -DesktopDir $desktopDir)) {
$restored = Restore-ElectronDist -DesktopDir $desktopDir
}
if ($purged.Count -gt 0 -or $restored) {
Write-Warn "Desktop build failed - refreshed the Electron download, retrying once:"
Expand All @@ -2406,8 +2406,8 @@ function Install-Desktop {
# downloader. Re-fetch the binary through the mirror first; otherwise
# the retry just re-reads the same missing dist and re-throws
# "The specified electronDist does not exist" (#47266).
$haveDist = Test-ElectronDist -InstallDir $InstallDir
if (-not $haveDist) { $haveDist = Restore-ElectronDist -InstallDir $InstallDir -Mirror $mirror }
$haveDist = Test-ElectronDist -DesktopDir $desktopDir
if (-not $haveDist) { $haveDist = Restore-ElectronDist -DesktopDir $desktopDir -Mirror $mirror }
if ($haveDist) {
& $npmExe run pack 2>&1 | ForEach-Object { "$_" } | Tee-Object -FilePath $buildLog
$code = $LASTEXITCODE
Expand Down
36 changes: 18 additions & 18 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2407,15 +2407,15 @@ _desktop_pack() {
# failed, and we never override a user-pinned ELECTRON_MIRROR.
DESKTOP_ELECTRON_FALLBACK_MIRROR="https://npmmirror.com/mirrors/electron/"

# True (returns 0) when node_modules/electron/dist holds a usable Electron
# binary. electron-builder reads the binary from build.electronDist
# (node_modules/electron/dist) since #38673, so this is the exact file whose
# absence makes a pack fail with "The specified electronDist does not exist". A
# dist dir that exists but is missing the binary (partial extraction / aborted
# postinstall) is NOT ok. $1 = the workspace root holding node_modules.
# True (returns 0) when apps/desktop/node_modules/electron/dist holds a usable
# Electron binary. electron-builder reads the binary from build.electronDist
# (node_modules/electron/dist relative to apps/desktop) since #38673, so this is
# the exact file whose absence makes a pack fail with "The specified
# electronDist does not exist". A dist dir that exists but is missing the binary
# (partial extraction / aborted postinstall) is NOT ok. $1 = apps/desktop.
_electron_dist_ok() {
local install_dir="$1"
local electron_dir="$install_dir/node_modules/electron"
local desktop_dir="$1"
local electron_dir="$desktop_dir/node_modules/electron"
if [ "$OS" = "macos" ]; then
[ -e "$electron_dir/dist/Electron.app/Contents/MacOS/Electron" ]
else
Expand All @@ -2436,14 +2436,14 @@ _electron_dist_ok() {
#
# No-op (returns 0) when the dist binary is already present. Otherwise drops a
# partial dist + version marker (electron's install.js short-circuits when
# path.txt already matches) and runs the downloader once. $1 = the workspace root
# holding node_modules; optional $2 = an ELECTRON_MIRROR base URL. Best-effort:
# path.txt already matches) and runs the downloader once. $1 = apps/desktop;
# optional $2 = an ELECTRON_MIRROR base URL. Best-effort:
# returns 0 iff the dist binary exists afterward.
_restore_electron_dist() {
local install_dir="$1"
local desktop_dir="$1"
local mirror="${2:-}"
local electron_dir="$install_dir/node_modules/electron"
_electron_dist_ok "$install_dir" && return 0
local electron_dir="$desktop_dir/node_modules/electron"
_electron_dist_ok "$desktop_dir" && return 0

[ -f "$electron_dir/install.js" ] || return 1
command -v node >/dev/null 2>&1 || return 1
Expand All @@ -2456,7 +2456,7 @@ _restore_electron_dist() {
else
( cd "$electron_dir" && node install.js ) || true
fi
_electron_dist_ok "$install_dir"
_electron_dist_ok "$desktop_dir"
}

# Build apps/desktop into a launchable native app. Mirrors install.ps1's
Expand Down Expand Up @@ -2539,8 +2539,8 @@ install_desktop() {
# check so an unrelated build failure (tsc/vite) doesn't trigger a
# pointless ~200MB refetch.
local restored=false
if ! _electron_dist_ok "$INSTALL_DIR"; then
if _restore_electron_dist "$INSTALL_DIR"; then restored=true; fi
if ! _electron_dist_ok "$desktop_dir"; then
if _restore_electron_dist "$desktop_dir"; then restored=true; fi
fi
if [ -n "$purged" ] || [ "$restored" = true ]; then
log_warn "Desktop build failed; refreshed the Electron download and retrying once..."
Expand All @@ -2560,9 +2560,9 @@ install_desktop() {
log_warn "Re-downloading Electron via a public mirror ($DESKTOP_ELECTRON_FALLBACK_MIRROR), then rebuilding..."
log_warn " (set ELECTRON_MIRROR yourself to use a different/trusted mirror)"
local have_dist=false
if _electron_dist_ok "$INSTALL_DIR"; then
if _electron_dist_ok "$desktop_dir"; then
have_dist=true
elif _restore_electron_dist "$INSTALL_DIR" "$DESKTOP_ELECTRON_FALLBACK_MIRROR"; then
elif _restore_electron_dist "$desktop_dir" "$DESKTOP_ELECTRON_FALLBACK_MIRROR"; then
have_dist=true
fi
if [ "$have_dist" = true ]; then
Expand Down
22 changes: 22 additions & 0 deletions tests/test_desktop_electron_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
installed binary to match ``electronVersion`` / ``electronDist``), and
2. the dependency, ``build.electronVersion``, and the resolved lockfile entry
all agree — so ``npm ci`` installs exactly what the build packages.
3. ``build.electronDist`` points at the desktop workspace's own
``node_modules/electron/dist`` directory, matching the root workspace
lockfile layout.
"""

from __future__ import annotations
Expand Down Expand Up @@ -94,3 +97,22 @@ def test_lockfile_resolves_the_pinned_electron():
f"but the pin is {spec!r}; run `npm install --package-lock-only` so "
"`npm ci` stays consistent."
)


def test_electron_dist_matches_workspace_install_location():
"""electronDist must point where npm installs Electron for the workspace."""
pkg = _desktop_pkg()
electron_dist = pkg.get("build", {}).get("electronDist")
assert electron_dist == "node_modules/electron/dist", (
"build.electronDist is resolved relative to apps/desktop by "
"electron-builder, so it must point at the desktop workspace's own "
f"node_modules/electron/dist; got {electron_dist!r}."
)

if not ROOT_LOCK.is_file():
pytest.skip("root package-lock.json not present")
lock = json.loads(ROOT_LOCK.read_text(encoding="utf-8"))
assert "apps/desktop/node_modules/electron" in lock.get("packages", {}), (
"package-lock.json installs Electron under apps/desktop/node_modules, "
"so electronDist must remain relative to apps/desktop."
)