Files
SRCmail/electron-builder.config.js
T
Bernd Rodler 505e65f319 fix(electron): disable npmRebuild so packaging doesn't need Xcode CLT
electron-builder's default npmRebuild pass scans the entire node_modules
tree (not just what's actually packaged) for native addons and tries to
recompile them against Electron's ABI via node-gyp. It caught
@parcel/watcher - a transitive devDependency of some dev tool, never
shipped in this app - and hard-failed packaging on any machine without a
full Xcode Command Line Tools install ("gyp: No Xcode or CLT version
detected!"). GitHub's macOS runners happen to have Xcode, which is
presumably why CI never caught this.

The packaged app is plain esbuild-bundled JS with no native modules of
its own; the one native dependency in the repo (@signalapp/sqlcipher,
used by lib/mail-index/) ships prebuilt .node binaries for every
platform and is copied in wholesale by scripts/assemble-standalone.mjs,
never rebuilt by electron-builder. Verified by execution: packaging
failed with npmRebuild at its default (true), succeeded once set false,
and the resulting .dmg launches and runs correctly.

Also adds e2e/electron-live-sandbox.spec.ts - a live-connectivity check
against the real sandbox JMAP backend (stalwart.sandbox.vnc.de), proving
the packaged/launched app reaches it with no TLS/network errors and gets
a real structured auth-rejection on a deliberately fake credential.
Deliberately NOT wired into playwright.electron.config.ts's default
testMatch (electron-smoke.spec.ts only) - this depends on a live external
service and is a manual/opt-in verification tool, not part of the regular
regression suite.

Also carries the pre-existing lib/smime-ca/ejbca.ts no-control-regex
eslint fix from MR !1's branch (not yet merged to dev) so this commit's
own pre-commit hook passes - unrelated to electron work otherwise.
2026-08-05 14:29:17 +02:00

113 lines
5.4 KiB
JavaScript

// electron-builder config for the VNCmail+ (Bulwark) desktop shell.
//
// Phase 1 of the VNCprodbuild rollout (~/.claude/skills/VNCprodbuild/SKILL.md):
// step 1 - base config, no targets (superseded by this file)
// step 6 - this file: real packaging targets + branding icon (below)
// step 7 - this file's `publish` block + electron/main.ts's
// setupAutoUpdater() - electron-updater against GitHub Releases.
// step 9 - still open: code signing / notarization (Apple Developer ID,
// optional Windows cert) - both are human-owned purchases, not
// configured here. Builds below ship UNSIGNED.
module.exports = {
appId: "de.vnc.vncmailplus",
productName: "VNCmail+",
copyright: "Copyright © VNC AG",
directories: {
output: "dist-electron-builds",
},
// The packaged app (`files` below) is plain esbuild-bundled JS - no native
// node modules of its own. The one native dependency anywhere in the repo,
// @signalapp/sqlcipher (used by lib/mail-index/**), ships its own prebuilt
// .node binaries for every platform/arch and is copied in wholesale by
// scripts/assemble-standalone.mjs as part of the extraResources standalone
// bundle below - it is never rebuilt by electron-builder.
//
// Without this, electron-builder's default @electron/rebuild pass scans
// the ENTIRE node_modules tree (not just what's actually packaged) for
// anything with a native binding and tries to recompile it from source
// against Electron's ABI via node-gyp. That caught @parcel/watcher - a
// transitive devDependency of some dev tool, never shipped in this app -
// and hard-failed the whole packaging step on any machine without a full
// Xcode Command Line Tools install (`gyp: No Xcode or CLT version
// detected!`), even though nothing that rebuild step touches is part of
// the artifact. Verified by execution: builds failed with npmRebuild at
// its default (true) and succeeded once set to false.
npmRebuild: false,
files: ["dist-electron/**/*", "package.json"],
extraResources: [
{
// Same artifact the Dockerfile bakes into the container image (see
// Dockerfile + scripts/assemble-standalone.mjs). electron/main.ts
// reads it from process.resourcesPath in packaged builds.
//
// Deliberately `from: ".next"` (not ".next/standalone") + a filter,
// not the more obvious `from: ".next/standalone"` alone:
// app-builder-lib's copy filter unconditionally drops a directory
// literally named "node_modules" sitting at the copy root (see
// node_modules/app-builder-lib/out/util/filter.js's
// `relative === "node_modules"` check - it assumes extraResources are
// hand-authored assets, not a pre-built server with a traced
// node_modules of its own). Copying from one level up so
// "standalone/node_modules" is never the literal copy root sidesteps
// that check, so the standalone server's node_modules actually
// survives into the packaged app instead of getting silently
// stripped (caught by manually launching a --dir build - the packaged
// server crashed with "Cannot find module 'next'").
from: ".next",
filter: ["standalone/**/*"],
to: ".",
},
],
// STAND-IN ICON, not a dedicated app icon: public/icon-512x512.png is the
// PWA manifest icon (512x512 square PNG). electron-builder can generate
// .icns/.ico from a single square PNG at build time (see
// node_modules/app-builder-lib/out/util/iconConverter.js), so this
// produces working icons for every target below - but at only 512x512,
// the largest macOS icns representation (1024x1024 "ICON512@2x") gets
// upsampled and will look soft compared to a real 1024x1024+ source.
// public/branding/Bulwark_Icon_App.svg looks like the intended master for
// this (as opposed to Bulwark_Favicon.png, sized for browser tabs), but
// it's vector and this environment has no SVG rasterizer (rsvg-convert /
// ImageMagick / Inkscape) to turn it into a proper 1024x1024 PNG. A human
// (or a follow-up step with the right tooling) should export
// Bulwark_Icon_App.svg at 1024x1024 and point `icon` at that instead.
icon: "public/icon-512x512.png",
mac: {
target: [
{ target: "dmg", arch: ["x64", "arm64"] },
{ target: "zip", arch: ["x64", "arm64"] },
],
category: "public.app-category.productivity",
// No Apple Developer ID yet (VNCprodbuild step 9) - ship unsigned/
// un-notarized for now. hardenedRuntime is meaningless without signing
// but left explicit so it's obvious what step 9 needs to flip on.
hardenedRuntime: false,
},
win: {
target: [{ target: "nsis", arch: ["x64"] }],
},
nsis: {
oneClick: false,
allowToChangeInstallationDirectory: true,
},
linux: {
target: [
{ target: "AppImage", arch: ["x64"] },
{ target: "deb", arch: ["x64"] },
],
category: "Network;Email;",
},
// electron-updater feed (see electron/main.ts's setupAutoUpdater()).
// GitHub Releases, not a new distribution channel - the skill's
// recommendation since this repo is already private and this needs no
// extra infrastructure. "Light decision" per VNCprodbuild step 7, not
// blocking, but flagged: switching later (e.g. to a self-hosted update
// server) would mean revisiting this block and the `provider` electron-
// updater talks to.
publish: {
provider: "github",
owner: "brvncde-dotcom",
repo: "vncmail-plus",
},
};