diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index 911e26e1106..1c30f0f9e09 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -95,6 +95,7 @@ try { nodePty = require(nodePtyDir) } } catch { + console.log(`[terminal] failed to load node-pty from path ${nodePtyDir}`) nodePty = null nodePtyDir = null } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a552f950f20..a1b8f5495d7 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -18,7 +18,8 @@ "profile:main": "wait-on http://127.0.0.1:5174 && cross-env XCURSOR_SIZE=24 HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron --inspect=9229 .", "profile:main:cpu": "wait-on http://127.0.0.1:5174 && cross-env XCURSOR_SIZE=24 NODE_OPTIONS=--cpu-prof HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 electron .", "start": "npm run build && electron .", - "build": "node scripts/assert-root-install.cjs && node scripts/write-build-stamp.cjs && node scripts/stage-native-deps.cjs && tsc -b && vite build && node scripts/assert-dist-built.cjs", + "build": "node scripts/assert-root-install.cjs && node scripts/write-build-stamp.cjs && node scripts/stage-native-deps.cjs && tsc -b && vite build && npm run postbuild", + "postbuild": "node scripts/assert-dist-built.cjs", "builder": "cross-env NODE_OPTIONS=--max-old-space-size=16384 electron-builder", "pack": "npm run build && npm run builder -- --dir", "dist": "npm run build && npm run builder", diff --git a/nix/desktop.nix b/nix/desktop.nix index 87cf5d11fda..d1c312b9b2d 100644 --- a/nix/desktop.nix +++ b/nix/desktop.nix @@ -6,63 +6,99 @@ # `HERMES_DESKTOP_HERMES` override env var, so the desktop's resolver # uses our fully wrapped binary at step 4 ("existing Hermes CLI"). # No reimplementation of the agent resolution in this wrapper. -{ pkgs, lib, stdenv, makeWrapper, hermesNpmLib, electron, hermesAgent, ... }: +{ + pkgs, + lib, + stdenv, + makeWrapper, + hermesNpmLib, + electron, + hermesAgent, + ... +}: let - npm = hermesNpmLib.mkNpmPassthru { folder = "apps/desktop"; attr = "desktop"; pname = "hermes-desktop"; }; + npm = hermesNpmLib.mkNpmPassthru { + folder = "apps/desktop"; + attr = "desktop"; + pname = "hermes-desktop"; + }; packageJson = builtins.fromJSON (builtins.readFile (npm.src + "/apps/desktop/package.json")); version = packageJson.version; # Build the renderer (dist/ + electron/ + package.json). - renderer = pkgs.buildNpmPackage (npm // { - pname = "hermes-desktop-renderer"; - inherit version; + renderer = pkgs.buildNpmPackage ( + npm + // { + pname = "hermes-desktop-renderer"; + inherit version; + doCheck = true; - doCheck = false; - # The workspace lockfile resolves all peer deps - # correctly so --legacy-peer-deps is not needed. - # --ignore-scripts comes from mkNpmPassthru (shared). - makeCacheWritable = true; + buildPhase = '' + runHook preBuild - buildPhase = '' - runHook preBuild + # write-build-stamp.cjs replacement. Packaged Electron reads this + # at first-launch to pin the install.ps1 git ref; informational in + # nix builds (the backend comes from the derivation directly). + mkdir -p apps/desktop/build + echo '{"schemaVersion":1,"commit":"nix","branch":"nix","dirty":false,"source":"nix"}' > apps/desktop/build/install-stamp.json - # write-build-stamp.cjs replacement. Packaged Electron reads this - # at first-launch to pin the install.ps1 git ref; informational in - # nix builds (the backend comes from the derivation directly). - mkdir -p apps/desktop/build - echo '{"schemaVersion":1,"commit":"nix","branch":"nix","dirty":false,"source":"nix"}' > apps/desktop/build/install-stamp.json + # patch shebangs in node_modules/.bin so npm exec can find the + # nix-store equivalents of /usr/bin/env (which doesn't exist in the sandbox) + patchShebangs . - # Build from apps/desktop/ so vite.config.ts resolves correctly. - # The workspace root's node_modules/ is accessible as ../../node_modules/. - cd apps/desktop + pushd apps/desktop + # stage node-pty native binaries into build/native-deps for the final nix output + npm rebuild node-pty --build-from-source + node scripts/stage-native-deps.cjs + + npm exec tsc -b + npm exec vite build + popd - # vite handles TS transpilation via esbuild — no type-checking. - # We skip `tsc -b` to avoid type errors in test files that don't - # ship in the bundle (real upstream peer-dep version mismatches - # in @testing-library/react v16 — not blocking the build). - # Call vite directly from root node_modules to avoid npx resolving - # through unpatched workspace symlinks. - node ../../node_modules/vite/bin/vite.js build --outDir dist + runHook postBuild + ''; - # Return to source root so installPhase paths are correct. - cd ../.. + checkPhase = '' + runHook preCheck - runHook postBuild - ''; + pushd apps/desktop - installPhase = '' - runHook preInstall - mkdir -p $out - # vite writes to apps/desktop/dist/ (we cd'd there in buildPhase). - # apps/desktop/build was created before the cd. electron/ is source. - cp -r apps/desktop/dist $out/ - cp -r apps/desktop/electron $out/ - cp -r apps/desktop/build $out/ - cp apps/desktop/package.json $out/ - runHook postInstall - ''; - }); + npm run postbuild + + # validate staged node-pty native binary is present + STAGED_PTY_NODE="./build/native-deps/node-pty/build/Release/pty.node" + + if [ ! -f "$STAGED_PTY_NODE" ]; then + echo "FATAL: Missing staged node-pty native binary at $STAGED_PTY_NODE" + echo "node-pty must be compiled natively" + exit 1 + fi + + popd + + runHook postCheck + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out + # vite writes to apps/desktop/dist/ (we cd'd there in buildPhase). + # apps/desktop/build was created before the cd. electron/ is source. + cp -rn apps/desktop/dist $out/ + cp -rn apps/desktop/electron $out/ + + # flatten native-deps and install-stamp.json to the root level, exactly like + # electron-builder's extraResources does ("from": "build/native-deps", "to": "native-deps") + # so main.cjs can find it at process.resourcesPath + '/native-deps/node-pty' + cp -rn apps/desktop/build/native-deps $out/ + cp -n apps/desktop/build/install-stamp.json $out/ + + cp -n apps/desktop/package.json $out/ + runHook postInstall + ''; + } + ); in # Electron wrapper: nixpkgs' electron binary pointed at the renderer dir. @@ -81,6 +117,12 @@ stdenv.mkDerivation { mkdir -p $out/share/hermes-desktop $out/bin cp -r ${renderer}/* $out/share/hermes-desktop/ + # Standard nixpkgs pattern for electron-builder apps: patch process.resourcesPath + # to point to the app's directory. In Nix, unpackaged electron defaults this + # to the electron distribution's resources path, breaking extraResources lookups. + substituteInPlace $out/share/hermes-desktop/electron/main.cjs \ + --replace-fail "process.resourcesPath" "'$out/share/hermes-desktop'" + # Wrap the nixpkgs electron binary to launch our app. Set # HERMES_DESKTOP_HERMES to the absolute path of the nix-built `hermes` # binary so the desktop's resolver step 4 ("existing Hermes CLI on diff --git a/nix/lib.nix b/nix/lib.nix index 7d2fe511a40..1e6ad96a43c 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -65,11 +65,7 @@ in npmRoot = "."; npmDepsFetcherVersion = 2; - # --ignore-scripts: the workspace includes electron (apps/desktop) - # which has a postinstall that tries to download from github.com. - # nix builds are offline, so all scripts must be skipped. Each - # package sets up its own build commands in buildPhase instead. - npmFlags = [ "--ignore-scripts" ]; + ELECTRON_SKIP_BINARY_DOWNLOAD = 1; patchPhase = '' runHook prePatch