fix(desktop): retry the Electron mirror only when the Electron distribution is missing

The pack-recovery mirror rung fired on "the pack failed and the staging
output holds no packaged executable". Every failure before electron-builder
is also true of that condition, and no mirror can repair a compile, bundler
or native-link failure — the rung just re-ran the whole pack while printing
that the Electron download from GitHub looked blocked.

The retry now also requires _electron_dist_ok to be false, matching the
gate the re-download rung already used. Late failures are unchanged: a
packaged executable in place still means exactly one attempt (#40187).
This commit is contained in:
ahrazzle
2026-09-16 23:15:15 -04:00
committed by Teknium
parent 08e5e9d514
commit d3c012ffd2
3 changed files with 118 additions and 4 deletions

View File

@@ -1454,6 +1454,11 @@ def _run_desktop_pack_with_recovery(
A MISSING exe is the signature of the corrupt-download class; a late failure
(e.g. macOS signing) leaves it in place and a redownload retry would only
repeat the same slow failure.
Both rungs additionally require the Electron distributable to be MISSING.
"No staged exe" is also true of every failure before electron-builder ever
runs (compile, bundler, native link), and switching mirrors cannot repair
those — it just re-runs the whole pack behind a message blaming GitHub.
"""
from hermes_cli.main import PROJECT_ROOT
def _staged_exe() -> Optional[Path]:
@@ -1487,13 +1492,13 @@ def _run_desktop_pack_with_recovery(
build_result.returncode != 0
and staging_dir is not None
and not env.get("ELECTRON_MIRROR")
and _staged_exe() is None):
and _staged_exe() is None
and not _electron_dist_ok(PROJECT_ROOT)):
print(" ⚠ Desktop build still failing; the Electron download from "
"GitHub looks blocked. Re-downloading via a public mirror "
"(npmmirror.com)... (set ELECTRON_MIRROR to use another mirror)")
mirror_env = {**npm_build_env, "ELECTRON_MIRROR": _ELECTRON_FALLBACK_MIRROR}
if not _electron_dist_ok(PROJECT_ROOT):
_redownload_electron_dist(PROJECT_ROOT, env, mirror=_ELECTRON_FALLBACK_MIRROR)
_redownload_electron_dist(PROJECT_ROOT, env, mirror=_ELECTRON_FALLBACK_MIRROR)
_stop_desktop_processes_locking_build(desktop_dir)
build_result = _pack(mirror_env)
return build_result

View File

@@ -337,6 +337,115 @@ def test_gui_does_not_retry_after_packaged_executable_exists(tmp_path, monkeypat
assert "Desktop GUI build failed" in capsys.readouterr().out
def _make_electron_dist(root: Path) -> Path:
"""Lay a complete Electron install where ``_electron_dist_ok`` looks on THIS host.
Resolved through ``_electron_dist_binary()`` rather than a hardcoded path so
the fixture and the code agree by construction on every OS lane.
"""
binary = main_desktop._electron_dist_binary(root)
binary.parent.mkdir(parents=True, exist_ok=True)
binary.write_text("", encoding="utf-8")
return binary
def _pack_that_never_reaches_the_builder():
"""A ``subprocess.run`` stand-in for a pack that dies before electron-builder.
It lays NOTHING down in the staging output — that is the signature of a
compile, bundler or native-link failure — and records each attempt's env so
a test can see whether the mirror rung was entered.
"""
attempts: list[dict] = []
def _run(cmd, **kwargs):
if len(cmd) >= 3 and cmd[1:3] == ["run", "pack"]:
attempts.append(dict(kwargs.get("env") or {}))
return subprocess.CompletedProcess(cmd, 1)
return subprocess.CompletedProcess(cmd, 0)
return attempts, _run
def test_gui_skips_mirror_retry_when_electron_dist_is_intact(tmp_path, monkeypatch, capsys):
"""A pack that never reached electron-builder has no mirror problem to repair.
"The pack failed and the staging output holds no exe" is ALSO true of a
compile / bundler / native-link failure, so on its own it cannot select the
mirror rung — yet that rung re-ran the whole pack while telling the user the
Electron download from GitHub looked blocked. The mirror retry additionally
requires the Electron distributable to be missing.
"""
root = _make_desktop_tree(tmp_path)
monkeypatch.setattr(cli_main, "PROJECT_ROOT", root)
_make_electron_dist(root)
live_exe = _make_packaged_executable(root, monkeypatch)
live_exe.write_text("good build", encoding="utf-8")
monkeypatch.delenv("ELECTRON_MIRROR", raising=False)
attempts, run_pack = _pack_that_never_reaches_the_builder()
install_ok = subprocess.CompletedProcess(["npm", "ci"], 0)
with patch("hermes_cli.main.shutil.which", return_value="/usr/bin/npm"), \
patch("hermes_cli.main_web_build._run_npm_install_deterministic", return_value=install_ok), \
patch("hermes_cli.main_desktop._desktop_macos_relaunchable_fixup"), \
patch("hermes_cli.main_desktop._purge_electron_build_cache", return_value=[]) as mock_purge, \
patch("hermes_cli.main_desktop._redownload_electron_dist", return_value=True) as mock_dl, \
patch("hermes_cli.main.subprocess.run", side_effect=run_pack), \
pytest.raises(SystemExit) as exc:
cli_main.cmd_gui(_ns())
assert exc.value.code == 1
# One pack, no mirror env on it, no false accusation of a blocked download.
assert len(attempts) == 1
assert not any(a.get("ELECTRON_MIRROR") for a in attempts)
assert "looks blocked" not in capsys.readouterr().out
# Nothing recovery-shaped ran either.
mock_purge.assert_not_called()
mock_dl.assert_not_called()
def test_gui_still_retries_via_mirror_when_electron_dist_is_missing(tmp_path, monkeypatch, capsys):
"""The blocked-download recovery the mirror rung exists for still fires.
Guard so a later tightening of the gate cannot silently drop the last rung:
Electron staged its package but ``dist`` was never populated, the pack
produces no exe, and the user hasn't pinned a mirror — refresh via
npmmirror.com and pack once more.
"""
root = _make_desktop_tree(tmp_path)
monkeypatch.setattr(cli_main, "PROJECT_ROOT", root)
# electron present, dist half-populated (a blocked postinstall download).
(root / "node_modules" / "electron" / "dist").mkdir(parents=True)
live_exe = _make_packaged_executable(root, monkeypatch)
live_exe.write_text("good build", encoding="utf-8")
monkeypatch.delenv("ELECTRON_MIRROR", raising=False)
attempts, run_pack = _pack_that_never_reaches_the_builder()
install_ok = subprocess.CompletedProcess(["npm", "ci"], 0)
with patch("hermes_cli.main.shutil.which", return_value="/usr/bin/npm"), \
patch("hermes_cli.main_web_build._run_npm_install_deterministic", return_value=install_ok), \
patch("hermes_cli.main_desktop._desktop_macos_relaunchable_fixup"), \
patch("hermes_cli.main_desktop._purge_electron_build_cache", return_value=[]) as mock_purge, \
patch("hermes_cli.main_desktop._redownload_electron_dist", return_value=True) as mock_dl, \
patch("hermes_cli.main.subprocess.run", side_effect=run_pack), \
pytest.raises(SystemExit) as exc:
cli_main.cmd_gui(_ns())
assert exc.value.code == 1
# The whole ladder still runs: pack, refreshed-download retry, mirror retry.
assert len(attempts) == 3
assert not attempts[0].get("ELECTRON_MIRROR")
assert not attempts[1].get("ELECTRON_MIRROR")
assert attempts[2].get("ELECTRON_MIRROR") == main_desktop._ELECTRON_FALLBACK_MIRROR
assert "looks blocked" in capsys.readouterr().out
# And the mirror run reached the dist re-download, mirror in hand.
assert mock_purge.called
assert any(a.kwargs.get("mirror") == main_desktop._ELECTRON_FALLBACK_MIRROR
for a in mock_dl.call_args_list)
# ── electronDist (re)download helper tests (#47266) ───────────────────

View File

@@ -596,7 +596,7 @@ clearing the entry — the latch resets and the next boot dials fresh.
The build downloads the Electron runtime (~114 MB) from `github.com/electron/electron/releases`. If the installer hangs on the **Build desktop app** step with the live output repeating `retrying attempt=…`, GitHub is being blocked or throttled on your network (firewall, proxy, or region).
The installer self-heals this automatically: on a failed build it (1) clears a corrupt cached Electron zip and retries, then (2) if it still fails and you haven't set `ELECTRON_MIRROR`, retries once more through `npmmirror.com`, the de-facto Electron community mirror. `@electron/get` SHASUM-checks the download, but the checksums come from the same mirror — that catches a corrupt or partial download, not a compromised mirror. If you'd rather not trust a third-party host, pin your own `ELECTRON_MIRROR` (below); the build never overrides one you've set.
The installer self-heals this automatically: on a failed build it (1) clears a corrupt cached Electron zip and retries, then (2) if it still fails, the Electron distributable is still missing, and you haven't set `ELECTRON_MIRROR`, retries once more through `npmmirror.com`, the de-facto Electron community mirror. `@electron/get` SHASUM-checks the download, but the checksums come from the same mirror — that catches a corrupt or partial download, not a compromised mirror. If you'd rather not trust a third-party host, pin your own `ELECTRON_MIRROR` (below); the build never overrides one you've set.
To **choose your own mirror** (e.g. a corporate/trusted one), set `ELECTRON_MIRROR` before installing or rebuild manually — the build honors it and won't override it: