macOS desktop: install + in-app self-update (#35607)
* fix(installer): align macOS HERMES_HOME with the rest of the stack
paths.rs computed the macOS Hermes home as ~/Library/Application Support/
hermes, but nothing else does: hermes_constants.get_hermes_home() (Python),
scripts/install.sh, and the Electron desktop's resolveHermesHome() all use
~/.hermes on macOS. The drift meant the Tauri installer wrote the install to
one directory and the desktop looked for it in another, so a fresh GUI
install never found its backend (the file's own comment warned this exact
drift would break things). Use ~/.hermes on macOS to match.
* fix(install.sh): always emit a stage result frame on failure
Stage helpers (clone_repo, install_deps, check_python, …) were written for
the monolithic flow and call `exit 1` on failure. Under `--stage`, that
terminated the process before the JSON result frame was printed, so the
installer's parse_stage_result saw "no frame" instead of a clean
{ok:false,...} contract response. Run the stage body in a subshell so an
`exit` only unwinds the subshell and the parent still emits the frame.
* feat(install.sh): auto-provision git on macOS/Linux (parity with install.ps1)
install.ps1 downloads PortableGit on Windows, but install.sh just printed a
"please install git" hint and exited — so a fresh Mac with no developer tools
(no Xcode CLT → no git) couldn't get past the clone step. check_git now tries
to install git before bailing:
- macOS: Homebrew if present (headless), else `xcode-select --install`
(the CLT prompt also provides the compiler some wheels need), polling for
git to appear.
- Linux: apt/dnf/pacman via sudo when available.
Falls back to the manual instructions only if auto-provision fails.
* feat(desktop): in-app GUI+backend self-update on macOS/Linux
On Windows the staged Hermes-Setup binary drives updates (quit → hermes
update → hermes desktop --build-only → relaunch). The mac drag-install has no
such binary, so "Update now" previously just printed `hermes update`.
Since there's no venv-shim file lock on POSIX, the desktop can drive the whole
update itself. applyUpdates now, when no staged updater exists on mac/linux:
1. runs `hermes update --yes [--branch <current>]` (backend git pull + deps),
2. runs `hermes desktop --build-only` (OS-aware GUI rebuild) with the
Hermes-managed Node + venv on PATH,
3. spawns a detached swapper that waits for this process to exit, dittos the
freshly built Hermes.app over the running bundle, clears quarantine, and
relaunches.
Degrades to "backend updated — restart to load the new GUI" if the rebuild
fails or there's no .app bundle to swap (dev run, Linux AppImage).
* chore: uptick
This commit is contained in:
@@ -9119,6 +9119,43 @@ def _run_pre_update_backup(args) -> None:
|
||||
print()
|
||||
|
||||
|
||||
def _discard_lockfile_churn(git_cmd, repo_root):
|
||||
"""Restore tracked ``package-lock.json`` files that npm dirtied locally.
|
||||
|
||||
npm rewrites lockfiles non-deterministically at install/build time. On a
|
||||
managed install those diffs are never intentional, so we discard them so
|
||||
``hermes update`` sees a clean tree instead of autostashing every run.
|
||||
Best-effort; only ever touches files named ``package-lock.json``.
|
||||
"""
|
||||
try:
|
||||
diff = subprocess.run(
|
||||
git_cmd + ["diff", "--name-only"],
|
||||
cwd=repo_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if diff.returncode != 0:
|
||||
return
|
||||
dirty = [
|
||||
line.strip()
|
||||
for line in diff.stdout.splitlines()
|
||||
if line.strip().endswith("package-lock.json")
|
||||
]
|
||||
if not dirty:
|
||||
return
|
||||
subprocess.run(
|
||||
git_cmd + ["checkout", "--", *dirty],
|
||||
cwd=repo_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
print(f"→ Discarded npm lockfile churn ({len(dirty)} file(s))")
|
||||
except Exception:
|
||||
# Never let lockfile cleanup block an update.
|
||||
pass
|
||||
|
||||
|
||||
def cmd_update(args):
|
||||
"""Update Hermes Agent to the latest version.
|
||||
|
||||
@@ -9296,6 +9333,15 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
||||
if sys.platform == "win32":
|
||||
git_cmd = ["git", "-c", "windows.appendAtomically=false"]
|
||||
|
||||
# Discard npm lockfile churn before any stash/branch logic. npm rewrites
|
||||
# tracked package-lock.json files non-deterministically at install/build
|
||||
# time (platform-specific optional deps, ideallyInert annotations, etc.),
|
||||
# which is never an intentional edit on a managed install but leaves the
|
||||
# tree dirty — forcing an autostash on every update and making branch
|
||||
# switches fragile. Restoring them first lets the common case (only
|
||||
# lockfile churn) update with a clean tree.
|
||||
_discard_lockfile_churn(git_cmd, PROJECT_ROOT)
|
||||
|
||||
# Detect if we're updating from a fork (before any branch logic)
|
||||
origin_url = _get_origin_url(git_cmd, PROJECT_ROOT)
|
||||
is_fork = _is_fork(origin_url)
|
||||
|
||||
Reference in New Issue
Block a user