Skip to main content

Electron build / release pipeline — fix log

The first attempted release (v0.1.0) failed in CI because the Windows installer step couldn't satisfy electron-builder's asar sanity check:

⨯ Application entry file "src\js\main_electron.js" in the
"...\dist\win-unpacked\resources\app.asar" does not exist.

That single error masks several compounding misconfigurations. This doc records all of them so the fix branch (and any future regression) has a clear reference.

Bug 1 — files glob matches nothing​

In frontend/package.json:

"build": {
"files": [
"frontend/**/*",
"main_electron.js",
"preload.cjs"
]
}

electron-builder runs from working-directory: frontend (set in .github/workflows/release.yml), so:

  • frontend/**/* resolves to frontend/frontend/**/* — no matches.
  • main_electron.js and preload.cjs are looked up at the package root (frontend/), but they actually live at frontend/src/js/main_electron.js and frontend/src/js/preload.cjs.

Net effect: nothing in src/ reaches the asar, the main entry points at a missing file, and the sanity check fails.

Fix. Replace with a glob that's relative to the package root:

"files": [
"src/**/*",
"package.json"
]

Bug 2 — production page-load path is wrong​

frontend/src/js/main_electron.js line 136:

win.loadFile(path.join(__dirname, 'dist', 'index.html'));

__dirname of the main file inside the asar is <asar>/src/js/, so this resolves to <asar>/src/js/dist/index.html — but Vite outputs to frontend/dist/, one level above src/. Even with Bug 1 fixed, the packaged app would launch a white window because the renderer HTML isn't where the main process is looking.

Fix. Use app.getAppPath() (the asar root) and add the Vite output to the files glob so dist/ ends up inside the asar:

const appRoot = app.getAppPath(); // <asar> root
win.loadFile(path.join(appRoot, 'dist', 'index.html'));
"files": [
"src/**/*",
"dist/**/*",
"package.json"
]

Bug 3 — Vite and electron-builder share frontend/dist/​

vite.config.js:

build: { outDir: '../dist' } // → frontend/dist/

…and electron-builder's default output is also dist/ relative to the project root (i.e. frontend/dist/). The CI sequence is:

  1. npm run build → Vite writes the renderer bundle to frontend/dist/.
  2. npm run dist → electron-builder cleans and reuses frontend/dist/ for the installer + win-unpacked/.

The two clobber each other, which is why the installer artifact ends up there but the renderer bundle inside the asar can be incomplete.

Fix. Give electron-builder its own output directory and update the upload + release paths in release.yml:

"directories": {
"buildResources": "assets",
"output": "dist-electron"
}
- name: Upload installer artifact
uses: actions/upload-artifact@v4
with:
name: EdgeWeave-windows-installer
path: frontend/dist-electron/*.exe

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: frontend/dist-electron/*.exe

frontend/dist/ is then exclusively the Vite output and gets bundled into the asar via the files glob from Bug 2's fix.

Bug 5 — empty description warning​

electron-builder logs:

description is missed in the package.json

Cosmetic, but it ends up on the installer's "About" panel and in the NSIS metadata, so worth filling in. Resolved upstream by the user.

Bug 4 — backend + Python aren't packaged at all (resolved — see python-electron-bundling)​

frontend/src/js/main_electron.js resolves the worker process from process.resourcesPath:

const pythonPath = isWin
? path.join(process.resourcesPath, 'python', 'python.exe')
: path.join(process.resourcesPath, 'python', 'bin', 'python3');

const backendDir = path.join(process.resourcesPath, 'backend');

Neither python/ nor backend/ is currently copied into process.resourcesPath by the installer — there's no extraResources block in the build config. Even after Bugs 1–3 are resolved, the packaged .exe will launch its window but fail to start the worker subprocess.

Resolved in a separate PR — see python-electron-bundling for the full design rationale. Decisions taken:

  • Python runtime: python-build-standalone (PBS) install_only tarballs, downloaded at CI time and cached. Chosen over the embedded distribution and PyInstaller because PBS ships a complete, relocatable CPython with stdlib intact (no python311.zip weirdness) and works identically across Win/Mac/Linux.
  • Dependencies: vendored at build time via pip install --target backend/_vendor -r backend/requirements.txt, surfaced to the interpreter at runtime by inserting _vendor near the top of sys.path in backend/fastapi_main.py. No first-launch pip step on the user's machine.
  • Spawn environment: main_electron.js clears any inherited PYTHONHOME (which would break PBS's relocatability) and sets PYTHONPATH=<resources>/backend/_vendor. We also set PYTHONDONTWRITEBYTECODE=1 so the read-only resource tree isn't scribbled with .pyc files.
  • Cross-platform parity: the same shape works on all three OSes; only the PBS download URL and the python.exe vs bin/python3 path fork. CI currently builds the Windows installer; macOS/Linux jobs follow the same recipe.

The implementing changes are:

"extraResources": [
{ "from": "../backend", "to": "backend",
"filter": ["**/*", "!**/__pycache__/**", "!tests/**", ...] },
{ "from": "python-portable", "to": "python" }
]
- name: Download & extract python-build-standalone (Windows x86_64)
run: |
curl -fsSL -o pbs.tar.gz "${URL}"
tar -xzf pbs.tar.gz
mv python python-portable

- name: Vendor backend dependencies into _vendor
run: |
./python-portable/python.exe -m pip install \
--target ../backend/_vendor --no-compile \
-r ../backend/requirements.txt

One commit per bug, with clear subjects, all on a single fix/electron-build branch off dev:

  1. fix(build): correct files glob in package.json (Bug 1)
  2. fix(electron): use app.getAppPath() for renderer load (Bug 2)
  3. fix(build): give electron-builder its own output dir (Bug 3)
  4. chore: update release.yml paths after Bug 3

The description already added by the user (Bug 5) belongs in its own commit too if it's not already merged.

What this PR does not fix​

Bug 4 (backend / Python bundling) — resolved by the follow-up branch documented in python-electron-bundling. The installer now ships a relocatable CPython under <resources>/python/ and the backend tree under <resources>/backend/ (with vendored deps in _vendor/). Renderer + backend should both come up on a clean Windows machine without any system Python install.

The full design discussion for Bug 4 — python-build-standalone choice, extraResources block, vendoring strategy, signing / notarization caveats — lives in python-electron-bundling.