mateclaw/mateclaw-desktop/electron-builder.cjs
Joe0720 e79fb00fec
feat(desktop): support remote lite build mode without bundled JRE/JAR (#417)
* feat(desktop): support remote lite build mode without bundled JRE/JAR

Add a dual packaging mode system controlled by the BUILD_MODE env var:

- **local** (default): Full build bundling JRE + Spring Boot JAR, identical
  to the previous behavior.  Supports both embedded local backend and
  remote server connection.

- **remote** (lite): Omits the ~530 MB JRE/JAR resources, producing an
  installer that is ~81% smaller (97 MB vs 523 MB on macOS arm64).  The
  app only supports connecting to a remote server; the "local" option is
  hidden from the splash connection chooser.

Changes:
- Replace static electron-builder.json with dynamic electron-builder.cjs
  that conditionally includes extraResources based on BUILD_MODE
- Add build mode detection at runtime (checks JAR existence) with graceful
  fallback to remote-only mode
- Add IPC handler app:get-build-mode and expose via preload
- Hide "本地运行" option in splash when running a remote build
- Ignore stale 'local' saved config in remote builds
- Add package scripts: package:mac:local, package:mac:remote, etc.
- Add missing build scripts: build.sh, download-jre.sh, build-all-platforms.sh
- Add no-op afterPack hook (trim-playwright-driver.cjs) placeholder
- Add cross-env devDependency for cross-platform BUILD_MODE support

* feat(desktop): add white-label branding system for build-time rebranding

Add a Vite plugin (scripts/branding.cjs) that replaces hardcoded "MateClaw"
strings at build time, enabling white-label/OEM rebranding without modifying
any source code.

Configuration:
- Edit branding.config.json (name, tagline, team, copyright, appId, githubUrl)
- Or set BRAND_* env vars (BRAND_NAME, BRAND_TAGLINE, BRAND_TEAM, etc.)

Usage:
  # Default build (MateClaw brand)
  npm run package:mac

  # Custom brand via env vars
  BRAND_NAME=MyAI BRAND_TAGLINE="Smart AI Helper" npm run package:mac:remote

  # Or edit branding.config.json and build normally
  npm run package:mac:remote

Replacements applied at build time:
- Brand name (window title, About dialog, error messages, console logs)
- Tagline, team name, copyright line
- GitHub repo/issues URLs
- Logo file path
- electron-builder config (productName, appId, artifactName, dmg title, publish repo)

The branding plugin runs in Vite's transform hook, covering the renderer
(App.vue, index.html), electron main process, and preload script.

Server-coupled strings (H2 database name, Spring Boot property names) are
intentionally NOT replaced to avoid breaking backend compatibility.

---------

Co-authored-by: qiaozhipeng <qiaozhipeng@daojia-inc.com>
2026-06-26 09:29:24 +08:00

140 lines
4.1 KiB
JavaScript

/**
* electron-builder.cjs — Dynamic build configuration.
*
* Two packaging modes are controlled by the BUILD_MODE environment variable:
*
* BUILD_MODE=local (default) Full build: bundles the embedded JRE and
* Spring Boot JAR so the desktop app can run a
* local backend. Original behavior.
*
* BUILD_MODE=remote Lightweight build: omits the JRE/JAR
* resources (~530 MB smaller on macOS). The app
* can only connect to a remote server — the
* "local" connection option is hidden in the
* splash UI.
*
* Branding is controlled by branding.config.json or BRAND_* env vars.
* See scripts/branding.cjs for details.
*
* Usage:
* BUILD_MODE=remote npx electron-builder --mac
* npm run package:mac:remote
* BRAND_NAME=MyAI npm run package:mac:remote
*/
'use strict'
const { loadBrandConfig } = require('./scripts/branding.cjs')
const mode = process.env.BUILD_MODE === 'remote' ? 'remote' : 'local'
const brand = loadBrandConfig(__dirname)
// Derive a short slug from the brand name for artifact file names.
// "MyAI" → "MyAI", "Cool App" → "Cool_App"
const brandSlug = brand.name.replace(/\s+/g, '_')
// Parse GitHub URL for publish config (owner/repo)
let githubOwner = 'matevip'
let githubRepo = 'mateclaw'
const ghMatch = brand.githubUrl.match(/github\.com\/([^/]+)\/([^/]+)/)
if (ghMatch) {
githubOwner = ghMatch[1]
githubRepo = ghMatch[2]
}
/** @type {import('electron-builder').Configuration} */
const config = {
appId: brand.appId,
productName: brand.name,
copyright: brand.copyright,
directories: { output: 'release' },
publish: [
{
provider: 'github',
owner: githubOwner,
repo: githubRepo,
},
],
files: ['dist-electron', 'dist'],
afterPack: 'scripts/trim-playwright-driver.cjs',
// extraResources: only bundle JRE + JAR in local mode.
// In remote mode this array is empty — the packaged app contains only the
// Electron + Vue shell, cutting ~530 MB from the installer.
extraResources:
mode === 'local'
? [
{
from: 'resources/jre/${os}-${arch}/',
to: 'jre/',
filter: ['**/*'],
},
{
from: 'resources/app.jar',
to: 'app.jar',
},
]
: [],
mac: {
category: 'public.app-category.productivity',
target: [
{ target: 'dmg', arch: ['arm64', 'x64'] },
{ target: 'zip', arch: ['arm64', 'x64'] },
],
icon: 'build/icon.icns',
hardenedRuntime: true,
gatekeeperAssess: false,
entitlements: 'build/entitlements.mac.plist',
entitlementsInherit: 'build/entitlements.mac.inherit.plist',
// Differentiate installers so users can tell local vs remote builds apart.
artifactName:
mode === 'remote'
? `${brandSlug}_Remote_${'$'}{version}_${'$'}{arch}.${'$'}{ext}`
: `${brandSlug}_${'$'}{version}_${'$'}{arch}.${'$'}{ext}`,
},
dmg: {
contents: [
{ x: 130, y: 220 },
{ x: 410, y: 220, type: 'link', path: '/Applications' },
],
title: `${brand.name} ${'$'}{version}`,
},
win: {
target: [
{ target: 'nsis', arch: 'x64' },
{ target: 'nsis', arch: 'arm64' },
],
icon: 'build/icon.ico',
artifactName:
mode === 'remote'
? `${brandSlug}_Remote_${'$'}{version}_${'$'}{arch}_Setup.${'$'}{ext}`
: `${brandSlug}_${'$'}{version}_${'$'}{arch}_Setup.${'$'}{ext}`,
},
nsis: {
oneClick: false,
perMachine: false,
allowToChangeInstallationDirectory: true,
deleteAppDataOnUninstall: false,
installerIcon: 'build/icon.ico',
uninstallerIcon: 'build/icon.ico',
installerHeaderIcon: 'build/icon.ico',
createDesktopShortcut: true,
createStartMenuShortcut: true,
},
linux: {
target: ['AppImage'],
icon: 'build/icon.png',
category: 'Utility',
artifactName:
mode === 'remote'
? `${brandSlug}_Remote_${'$'}{version}.${'$'}{ext}`
: `${brandSlug}_${'$'}{version}.${'$'}{ext}`,
},
}
module.exports = config