Skip to content
Merged
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: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ npm run serve # Build and run production server
# `npm run check` for safe verification, or build from a worktree.
```

Windows desktop builds (`npm run electron:build:win`) must run on native Windows — see [docs/development/windows-electron-build.md](docs/development/windows-electron-build.md).

### Testing
```bash
npm test # Coordinated full suite (default + server configs)
Expand Down
48 changes: 48 additions & 0 deletions assets/electron/installer.nsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
!ifndef nsProcess::FindProcess
!include "nsProcess.nsh"
!endif

!macro quitIfFreshellIsRunning
${nsProcess::FindProcess} "${APP_EXECUTABLE_FILENAME}" $R0
${if} $R0 == 0
SetErrorLevel 1
${if} ${Silent}
DetailPrint "${PRODUCT_NAME} is running. Quit ${PRODUCT_NAME} before running this installer."
${else}
MessageBox MB_OK|MB_ICONEXCLAMATION|MB_TOPMOST "${PRODUCT_NAME} is running. Quit ${PRODUCT_NAME} before running this installer."
${endIf}
${nsProcess::Unload}
Quit
${endIf}
${nsProcess::Unload}
!macroend

!macro customInit
!insertmacro quitIfFreshellIsRunning
!macroend

!macro customCheckAppRunning
!insertmacro quitIfFreshellIsRunning
!macroend

!macro customInstall
${StdUtils.GetParameter} $0 "FRESHELL_REMOTE_URL" ""
${StdUtils.GetParameter} $1 "FRESHELL_TOKEN" ""

${if} $0 != ""
${andIf} $1 != ""
CreateDirectory "$PROFILE\.freshell"
FileOpen $2 "$PROFILE\.freshell\desktop.json" w
FileWrite $2 "{$\r$\n"
FileWrite $2 " $\"serverMode$\": $\"remote$\",$\r$\n"
FileWrite $2 " $\"port$\": 3001,$\r$\n"
FileWrite $2 " $\"remoteUrl$\": $\"$0$\",$\r$\n"
FileWrite $2 " $\"remoteToken$\": $\"$1$\",$\r$\n"
FileWrite $2 " $\"globalHotkey$\": $\"CommandOrControl+`$\",$\r$\n"
FileWrite $2 " $\"startOnLogin$\": false,$\r$\n"
FileWrite $2 " $\"minimizeToTray$\": true,$\r$\n"
FileWrite $2 " $\"setupCompleted$\": true$\r$\n"
FileWrite $2 "}$\r$\n"
FileClose $2
${endIf}
!macroend
98 changes: 98 additions & 0 deletions docs/development/windows-electron-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Building the Windows Electron App

This documents how to produce the Windows desktop installer
(`release/Freshell Setup <version>.exe`).

## Key constraint: it must run on native Windows

The Windows build **cannot be produced from WSL/Linux**. `npm run
electron:build:win` begins with `scripts/assert-native-windows-build.ts`, which
hard-fails unless `process.platform === 'win32'` — because `node-pty` has to be
compiled for win32. Running the pipeline from Linux produces a broken installer
(a tiny NSIS stub with no bundled `node.exe`) and, if you let it, a Linux
AppImage instead. If you see a ~few-hundred-KB `Freshell Setup *.exe`, you built
on the wrong platform.

## Prerequisites (on the Windows side)

- Node.js (matching `engines.node`, currently `>=22.5.0`) and npm.
- Visual Studio Build Tools with the **Desktop development with C++** workload,
and Python 3 — required for `node-gyp` to compile `node-pty`.
- `curl`, `tar`, and `unzip` on `PATH` — `scripts/prepare-bundled-node.ts` shells
out to them to download the standalone Node binary and headers. Windows 10+
ships `curl`/`tar`; `unzip` is provided by Git for Windows (`usr/bin`).

## Option A — from a native Windows shell

```powershell
npm install # installs Windows-native deps (compiles node-pty for win32)
$env:CI = "true"
npm run electron:build:win # assert win32 → build → prepare:bundled-node → electron-builder --win nsis
```

`electron:build:win` runs, in order: the platform assert, `npm run build`
(typecheck + client + server), `build:electron`, `build:wizard`,
`build:launch-chooser`, `prepare:bundled-node` (downloads the standalone Node,
recompiles `node-pty`, prunes `server-node-modules`), then `electron-builder
--win nsis --publish never`.

Output lands in `release/`.

## Option B — driving the Windows build from WSL

Your dev checkout usually lives on the WSL filesystem, but the build must run as
a native Windows process. **Do not** build over the `\\wsl.localhost\...` UNC
path (slow and fragile over 9p). Instead, copy the working tree to a
Windows-local path and run Windows' own npm against it via interop.

1. Copy the worktree to a Windows-local dir, excluding regenerable/platform dirs:

```bash
rsync -rlt --delete --no-perms --no-owner --no-group \
--exclude='.git' --exclude='node_modules/' --exclude='dist/' \
--exclude='release/' --exclude='bundled-node/' --exclude='server-node-modules/' \
./ "/mnt/c/Users/<you>/AppData/Local/Temp/freshell-electron-build/"
```

2. Run Windows npm in that dir via `cmd.exe`. Always `cd /d` to a real Windows
path first — `cmd.exe` launched from WSL inherits the UNC cwd and will warn
and mangle relative paths:

```bash
cmd.exe /c 'cd /d C:\Users\<you>\AppData\Local\Temp\freshell-electron-build && set "CI=true" && set "PORT=39517" && npm install && npm run electron:build:win'
```

- `PORT=<unused>` is belt-and-suspenders for the `prebuild` guard. (It
normally auto-skips here because the copied `.git` is a worktree pointer,
so `isLinkedWorktreeCheckout` is true — but WSL2 forwards `localhost`, so a
live dev server on the default port is otherwise visible to the guard.)
- Reusing a previous build dir keeps its warm Windows `node_modules` (with the
already-compiled win32 `node-pty`), making `npm install` a fast no-op.

3. To move artifacts off `/mnt/c`, prefer WSL `cp` over `cmd copy` — `cmd`'s
quote/path handling through interop is unreliable for paths with spaces.

## What you get

`electron-builder.yml` targets **`nsis`** for Windows: a one-click, per-user
installer (`oneClick: true`, `perMachine: false`).

- `release/Freshell Setup <version>.exe` — the installer. Running it installs to
`%LOCALAPPDATA%\Programs\Freshell\Freshell.exe` and (with `runAfterFinish`)
launches the app.
- `release/win-unpacked/Freshell.exe` — the app executable itself; run it
directly to launch without installing.

The installer is **unsigned** unless a code-signing certificate is configured, so
Windows SmartScreen will warn on first run.

## Sanity-check a build

A good build should show:

- `release/Freshell Setup <version>.exe` is full size (hundreds of MB), not a
small stub.
- `release/win-unpacked/resources/bundled-node/bin/node.exe` exists (the bundled
server runtime — absent in broken cross-builds).
- `release/win-unpacked/resources/server-node-modules/node-pty/prebuilds/win32-x64/conpty.node`
exists.
Loading
Loading