mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
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>
This commit is contained in:
parent
d7d409245b
commit
e79fb00fec
9
mateclaw-desktop/branding.config.json
Normal file
9
mateclaw-desktop/branding.config.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "MateClaw",
|
||||
"tagline": "AI Personal Assistant",
|
||||
"team": "MateClaw Team",
|
||||
"copyright": "Copyright © 2026 MateClaw Team",
|
||||
"appId": "vip.mate.mateclaw",
|
||||
"githubUrl": "https://github.com/matevip/mateclaw",
|
||||
"logoFile": "mateclaw_logo_s.png"
|
||||
}
|
||||
139
mateclaw-desktop/electron-builder.cjs
Normal file
139
mateclaw-desktop/electron-builder.cjs
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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
|
||||
@ -1,96 +0,0 @@
|
||||
{
|
||||
"appId": "vip.mate.mateclaw",
|
||||
"productName": "MateClaw",
|
||||
"copyright": "Copyright © 2026 MateClaw Team",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github",
|
||||
"owner": "matevip",
|
||||
"repo": "mateclaw"
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
"dist-electron",
|
||||
"dist"
|
||||
],
|
||||
"afterPack": "scripts/trim-playwright-driver.cjs",
|
||||
"extraResources": [
|
||||
{
|
||||
"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",
|
||||
"artifactName": "MateClaw_${version}_${arch}.${ext}"
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
],
|
||||
"title": "MateClaw ${version}"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": "x64"
|
||||
},
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": "arm64"
|
||||
}
|
||||
],
|
||||
"icon": "build/icon.ico",
|
||||
"artifactName": "MateClaw_${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": "MateClaw_${version}.${ext}"
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,31 @@ const trustedCertHosts = new Set<string>()
|
||||
// handler, which prompts the user before loading the page.
|
||||
const insecureAgent = new https.Agent({ rejectUnauthorized: false })
|
||||
|
||||
// ─── Build Mode Detection ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* The desktop app ships in two variants:
|
||||
*
|
||||
* "local" — bundles the JRE and Spring Boot JAR in extraResources so the
|
||||
* app can run an embedded backend. This is the traditional full
|
||||
* build.
|
||||
*
|
||||
* "remote" — omits the JRE/JAR (~530 MB lighter). The app can only connect
|
||||
* to a remote server. The "local" option is hidden in the splash
|
||||
* connection chooser.
|
||||
*
|
||||
* Detection is done at runtime by checking whether the JAR exists in the
|
||||
* resources directory. This avoids any build-time code injection — the same
|
||||
* main-process code runs in both builds; the only difference is whether the
|
||||
* JAR/JRE files are present.
|
||||
*/
|
||||
function detectBuildMode(): 'local' | 'remote' {
|
||||
const jarPath = getJarPath()
|
||||
return existsSync(jarPath) ? 'local' : 'remote'
|
||||
}
|
||||
|
||||
const BUILD_MODE = detectBuildMode()
|
||||
|
||||
interface UpdaterState {
|
||||
status: 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error'
|
||||
version?: string
|
||||
@ -131,6 +156,15 @@ function getAvailablePort(): Promise<number> {
|
||||
}
|
||||
|
||||
async function startJavaBackend(): Promise<void> {
|
||||
// Remote builds have no bundled JRE/JAR — refuse to start the local backend
|
||||
// and guide the user toward the connection chooser instead of showing a
|
||||
// generic "file not found" error.
|
||||
if (BUILD_MODE === 'remote') {
|
||||
console.log('[MateClaw] Remote build — local backend unavailable, showing connection chooser')
|
||||
sendToWindow('backend:status', 'choose')
|
||||
return
|
||||
}
|
||||
|
||||
BACKEND_PORT = await getAvailablePort()
|
||||
BACKEND_URL = `http://localhost:${BACKEND_PORT}`
|
||||
console.log(`[MateClaw] Using dynamic port: ${BACKEND_PORT}`)
|
||||
@ -308,6 +342,16 @@ async function bootConnection(): Promise<void> {
|
||||
}
|
||||
|
||||
const cfg = loadConfig()
|
||||
|
||||
// Remote builds cannot start a local backend. If the user previously
|
||||
// saved 'local' mode (e.g. they upgraded from a full build), ignore the
|
||||
// stale preference and fall through to the connection chooser.
|
||||
if (BUILD_MODE === 'remote' && cfg.mode === 'local') {
|
||||
connectionMode = null
|
||||
sendToWindow('backend:status', 'choose')
|
||||
return
|
||||
}
|
||||
|
||||
if (cfg.mode === 'local') {
|
||||
connectionMode = 'local'
|
||||
await startJavaBackend()
|
||||
@ -544,6 +588,10 @@ function registerIpcHandlers(): void {
|
||||
|
||||
ipcMain.handle('app:get-version', () => app.getVersion())
|
||||
|
||||
// Let the renderer know whether this is a local (full) or remote (lite) build
|
||||
// so the splash can hide the "local" connection option in remote builds.
|
||||
ipcMain.handle('app:get-build-mode', () => BUILD_MODE)
|
||||
|
||||
ipcMain.handle('app:get-backend-url', () => BACKEND_URL)
|
||||
|
||||
ipcMain.handle('app:is-backend-ready', () => backendReady)
|
||||
@ -576,6 +624,9 @@ function registerIpcHandlers(): void {
|
||||
servers: cfg.servers,
|
||||
// The renderer shows the chooser on first run or when "Switch Server" forced it.
|
||||
forceChoose: forceChooser,
|
||||
// Tell the renderer which build variant is running so it can hide the
|
||||
// "local" option in remote (lite) builds.
|
||||
buildMode: BUILD_MODE,
|
||||
}
|
||||
})
|
||||
|
||||
@ -584,6 +635,11 @@ function registerIpcHandlers(): void {
|
||||
})
|
||||
|
||||
ipcMain.handle('connection:use-local', async () => {
|
||||
// Remote builds have no bundled JRE/JAR — reject the local mode request.
|
||||
if (BUILD_MODE === 'remote') {
|
||||
sendToWindow('backend:crashed', '此版本为轻量版(Remote),不支持本地内嵌后端。请选择连接远程服务器。')
|
||||
return
|
||||
}
|
||||
forceChooser = false
|
||||
saveConfig({ mode: 'local' })
|
||||
connectionMode = 'local'
|
||||
|
||||
@ -5,6 +5,7 @@ contextBridge.exposeInMainWorld('mateClawAPI', {
|
||||
// Platform info
|
||||
getPlatform: () => ipcRenderer.invoke('app:get-platform'),
|
||||
getVersion: () => ipcRenderer.invoke('app:get-version'),
|
||||
getBuildMode: () => ipcRenderer.invoke('app:get-build-mode'),
|
||||
getBackendUrl: () => ipcRenderer.invoke('app:get-backend-url'),
|
||||
isBackendReady: () => ipcRenderer.invoke('app:is-backend-ready'),
|
||||
getUserDataPath: () => ipcRenderer.invoke('app:get-user-data-path'),
|
||||
|
||||
@ -14,8 +14,14 @@
|
||||
"setup": "npm run setup:jar && npm run setup:jre",
|
||||
"setup:all-platforms": "bash scripts/build-all-platforms.sh --all",
|
||||
"package:mac": "npm run build && electron-builder --mac",
|
||||
"package:mac:local": "npm run build && cross-env BUILD_MODE=local electron-builder --mac",
|
||||
"package:mac:remote": "npm run build && cross-env BUILD_MODE=remote electron-builder --mac",
|
||||
"package:win": "npm run build && electron-builder --win",
|
||||
"package:win:local": "npm run build && cross-env BUILD_MODE=local electron-builder --win",
|
||||
"package:win:remote": "npm run build && cross-env BUILD_MODE=remote electron-builder --win",
|
||||
"package:all": "bash scripts/build-all-platforms.sh --all",
|
||||
"package:all:local": "bash scripts/build-all-platforms.sh --local",
|
||||
"package:all:remote": "bash scripts/build-all-platforms.sh --remote",
|
||||
"publish:github": "bash scripts/publish-github.sh",
|
||||
"publish:github:draft": "bash scripts/publish-github.sh --draft"
|
||||
},
|
||||
@ -25,6 +31,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^33.3.1",
|
||||
"electron-builder": "^25.1.8",
|
||||
"typescript": "^5.7.3",
|
||||
@ -32,5 +39,11 @@
|
||||
"vite-plugin-electron": "^0.28.8",
|
||||
"vite-plugin-electron-renderer": "^0.14.6",
|
||||
"vue-tsc": "^2.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"electron",
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
119
mateclaw-desktop/pnpm-lock.yaml
generated
119
mateclaw-desktop/pnpm-lock.yaml
generated
@ -8,13 +8,19 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
electron-updater:
|
||||
specifier: ^6.3.9
|
||||
version: 6.8.9
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.31(typescript@5.9.3)
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.4(vite@6.4.1(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3))
|
||||
version: 5.2.4(vite@6.4.3(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3))
|
||||
cross-env:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0
|
||||
electron:
|
||||
specifier: ^33.3.1
|
||||
version: 33.4.11
|
||||
@ -26,13 +32,13 @@ importers:
|
||||
version: 5.9.3
|
||||
vite:
|
||||
specifier: ^6.0.7
|
||||
version: 6.4.1(@types/node@25.5.0)
|
||||
version: 6.4.3(@types/node@25.5.0)
|
||||
vite-plugin-electron:
|
||||
specifier: ^0.28.8
|
||||
version: 0.28.8(vite-plugin-electron-renderer@0.14.6)
|
||||
version: 0.28.8(vite-plugin-electron-renderer@0.14.7)
|
||||
vite-plugin-electron-renderer:
|
||||
specifier: ^0.14.6
|
||||
version: 0.14.6
|
||||
version: 0.14.7
|
||||
vue-tsc:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.12(typescript@5.9.3)
|
||||
@ -90,6 +96,9 @@ packages:
|
||||
resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==}
|
||||
engines: {node: '>=16.4'}
|
||||
|
||||
'@epic-web/invariant@1.0.0':
|
||||
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.12':
|
||||
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -673,6 +682,10 @@ packages:
|
||||
resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
builder-util-runtime@9.7.0:
|
||||
resolution: {integrity: sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
builder-util@25.1.7:
|
||||
resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==}
|
||||
|
||||
@ -788,6 +801,11 @@ packages:
|
||||
crc@3.8.0:
|
||||
resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}
|
||||
|
||||
cross-env@10.1.0:
|
||||
resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -883,6 +901,9 @@ packages:
|
||||
electron-publish@25.1.7:
|
||||
resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==}
|
||||
|
||||
electron-updater@6.8.9:
|
||||
resolution: {integrity: sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==}
|
||||
|
||||
electron@33.4.11:
|
||||
resolution: {integrity: sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==}
|
||||
engines: {node: '>= 12.20.55'}
|
||||
@ -1046,6 +1067,10 @@ packages:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
||||
glob@7.2.0:
|
||||
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
@ -1205,8 +1230,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
js-yaml@4.2.0:
|
||||
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
|
||||
hasBin: true
|
||||
|
||||
json-buffer@3.0.1:
|
||||
@ -1245,9 +1270,16 @@ packages:
|
||||
lodash.difference@4.5.0:
|
||||
resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==}
|
||||
|
||||
lodash.escaperegexp@4.1.2:
|
||||
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
|
||||
|
||||
lodash.flatten@4.4.0:
|
||||
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
|
||||
|
||||
lodash.isequal@4.5.0:
|
||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
|
||||
@ -1703,6 +1735,9 @@ packages:
|
||||
temp-file@3.4.0:
|
||||
resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
|
||||
|
||||
tiny-typed-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@ -1761,8 +1796,8 @@ packages:
|
||||
resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
|
||||
vite-plugin-electron-renderer@0.14.6:
|
||||
resolution: {integrity: sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==}
|
||||
vite-plugin-electron-renderer@0.14.7:
|
||||
resolution: {integrity: sha512-hHBMKuZ24MB2SIxG7U7ix+DDEnvxou7Bgy/TdhYxNz3S5N3Yh7Hjvj9blfMeGEJ0oaZJn7y5z0V/RyDmJ5OuCA==}
|
||||
|
||||
vite-plugin-electron@0.28.8:
|
||||
resolution: {integrity: sha512-ir+B21oSGK9j23OEvt4EXyco9xDCaF6OGFe0V/8Zc0yL2+HMyQ6mmNQEIhXsEsZCSfIowBpwQBeHH4wVsfraeg==}
|
||||
@ -1772,8 +1807,8 @@ packages:
|
||||
vite-plugin-electron-renderer:
|
||||
optional: true
|
||||
|
||||
vite@6.4.1:
|
||||
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
|
||||
vite@6.4.3:
|
||||
resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -1974,6 +2009,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@epic-web/invariant@1.0.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.12':
|
||||
optional: true
|
||||
|
||||
@ -2225,9 +2262,9 @@ snapshots:
|
||||
'@types/node': 20.19.37
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3))':
|
||||
'@vitejs/plugin-vue@5.2.4(vite@6.4.3(@types/node@25.5.0))(vue@3.5.31(typescript@5.9.3))':
|
||||
dependencies:
|
||||
vite: 6.4.1(@types/node@25.5.0)
|
||||
vite: 6.4.3(@types/node@25.5.0)
|
||||
vue: 3.5.31(typescript@5.9.3)
|
||||
|
||||
'@volar/language-core@2.4.15':
|
||||
@ -2387,7 +2424,7 @@ snapshots:
|
||||
hosted-git-info: 4.1.0
|
||||
is-ci: 3.0.1
|
||||
isbinaryfile: 5.0.7
|
||||
js-yaml: 4.1.1
|
||||
js-yaml: 4.2.0
|
||||
json5: 2.2.3
|
||||
lazy-val: 1.0.5
|
||||
minimatch: 10.2.5
|
||||
@ -2404,7 +2441,7 @@ snapshots:
|
||||
|
||||
archiver-utils@2.1.0:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
glob: 7.2.0
|
||||
graceful-fs: 4.2.11
|
||||
lazystream: 1.0.1
|
||||
lodash.defaults: 4.2.0
|
||||
@ -2509,6 +2546,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
builder-util-runtime@9.7.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
sax: 1.6.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
builder-util@25.1.7:
|
||||
dependencies:
|
||||
7zip-bin: 5.2.0
|
||||
@ -2523,7 +2567,7 @@ snapshots:
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
is-ci: 3.0.1
|
||||
js-yaml: 4.1.1
|
||||
js-yaml: 4.2.0
|
||||
source-map-support: 0.5.21
|
||||
stat-mode: 1.0.0
|
||||
temp-file: 3.4.0
|
||||
@ -2656,6 +2700,11 @@ snapshots:
|
||||
buffer: 5.7.1
|
||||
optional: true
|
||||
|
||||
cross-env@10.1.0:
|
||||
dependencies:
|
||||
'@epic-web/invariant': 1.0.0
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@ -2715,7 +2764,7 @@ snapshots:
|
||||
builder-util-runtime: 9.2.10
|
||||
fs-extra: 10.1.0
|
||||
iconv-lite: 0.6.3
|
||||
js-yaml: 4.1.1
|
||||
js-yaml: 4.2.0
|
||||
optionalDependencies:
|
||||
dmg-license: 1.0.11
|
||||
transitivePeerDependencies:
|
||||
@ -2793,6 +2842,19 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron-updater@6.8.9:
|
||||
dependencies:
|
||||
builder-util-runtime: 9.7.0
|
||||
fs-extra: 10.1.0
|
||||
js-yaml: 4.2.0
|
||||
lazy-val: 1.0.5
|
||||
lodash.escaperegexp: 4.1.2
|
||||
lodash.isequal: 4.5.0
|
||||
semver: 7.7.4
|
||||
tiny-typed-emitter: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron@33.4.11:
|
||||
dependencies:
|
||||
'@electron/get': 2.0.3
|
||||
@ -3000,6 +3062,15 @@ snapshots:
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
glob@7.2.0:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.5
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
|
||||
glob@7.2.3:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
@ -3175,7 +3246,7 @@ snapshots:
|
||||
filelist: 1.0.6
|
||||
picocolors: 1.1.1
|
||||
|
||||
js-yaml@4.1.1:
|
||||
js-yaml@4.2.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@ -3212,8 +3283,12 @@ snapshots:
|
||||
|
||||
lodash.difference@4.5.0: {}
|
||||
|
||||
lodash.escaperegexp@4.1.2: {}
|
||||
|
||||
lodash.flatten@4.4.0: {}
|
||||
|
||||
lodash.isequal@4.5.0: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.union@4.6.0: {}
|
||||
@ -3706,6 +3781,8 @@ snapshots:
|
||||
async-exit-hook: 2.0.1
|
||||
fs-extra: 10.1.0
|
||||
|
||||
tiny-typed-emitter@2.1.0: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
@ -3757,13 +3834,13 @@ snapshots:
|
||||
extsprintf: 1.4.1
|
||||
optional: true
|
||||
|
||||
vite-plugin-electron-renderer@0.14.6: {}
|
||||
vite-plugin-electron-renderer@0.14.7: {}
|
||||
|
||||
vite-plugin-electron@0.28.8(vite-plugin-electron-renderer@0.14.6):
|
||||
vite-plugin-electron@0.28.8(vite-plugin-electron-renderer@0.14.7):
|
||||
optionalDependencies:
|
||||
vite-plugin-electron-renderer: 0.14.6
|
||||
vite-plugin-electron-renderer: 0.14.7
|
||||
|
||||
vite@6.4.1(@types/node@25.5.0):
|
||||
vite@6.4.3(@types/node@25.5.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
|
||||
153
mateclaw-desktop/scripts/branding.cjs
Normal file
153
mateclaw-desktop/scripts/branding.cjs
Normal file
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* scripts/branding.cjs — Vite plugin for build-time white-label branding.
|
||||
*
|
||||
* Reads brand settings from branding.config.json (or BRAND_* env overrides)
|
||||
* and replaces hardcoded "MateClaw" strings in all built files — source code
|
||||
* stays untouched.
|
||||
*
|
||||
* Supported env overrides:
|
||||
* BRAND_NAME, BRAND_TAGLINE, BRAND_TEAM, BRAND_COPYRIGHT,
|
||||
* BRAND_APP_ID, BRAND_GITHUB_URL, BRAND_LOGO_FILE
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
function loadBrandConfig(rootDir) {
|
||||
const configPath = path.join(rootDir, 'branding.config.json')
|
||||
let config = {}
|
||||
if (fs.existsSync(configPath)) {
|
||||
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
}
|
||||
|
||||
// Env vars override the config file.
|
||||
const env = process.env
|
||||
return {
|
||||
name: env.BRAND_NAME || config.name || 'MateClaw',
|
||||
tagline: env.BRAND_TAGLINE || config.tagline || 'AI Personal Assistant',
|
||||
team: env.BRAND_TEAM || config.team || 'MateClaw Team',
|
||||
copyright: env.BRAND_COPYRIGHT || config.copyright || 'Copyright © 2026 MateClaw Team',
|
||||
appId: env.BRAND_APP_ID || config.appId || 'vip.mate.mateclaw',
|
||||
githubUrl: env.BRAND_GITHUB_URL || config.githubUrl || 'https://github.com/matevip/mateclaw',
|
||||
logoFile: env.BRAND_LOGO_FILE || config.logoFile || 'mateclaw_logo_s.png',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the string-replacement table.
|
||||
*
|
||||
* Order matters: longer/more-specific patterns are replaced first to avoid
|
||||
* partial matches (e.g. "MateClaw Team" before "MateClaw").
|
||||
*/
|
||||
function buildReplacements(brand) {
|
||||
const replacements = []
|
||||
|
||||
// 1. Copyright line (most specific)
|
||||
replacements.push([
|
||||
'Copyright © 2026 MateClaw Team',
|
||||
brand.copyright,
|
||||
])
|
||||
|
||||
// 2. Team name
|
||||
replacements.push(['MateClaw Team', brand.team])
|
||||
|
||||
// 3. GitHub URLs
|
||||
replacements.push([
|
||||
'https://github.com/matevip/mateclaw/issues',
|
||||
brand.githubUrl + '/issues',
|
||||
])
|
||||
replacements.push([
|
||||
'https://github.com/matevip/mateclaw',
|
||||
brand.githubUrl,
|
||||
])
|
||||
|
||||
// 4. Logo file path
|
||||
replacements.push([
|
||||
'mateclaw_logo_s.png',
|
||||
brand.logoFile,
|
||||
])
|
||||
|
||||
// 5. Tagline
|
||||
replacements.push([
|
||||
'AI Personal Assistant',
|
||||
brand.tagline,
|
||||
])
|
||||
|
||||
// 6. Split-span brand name in App.vue template:
|
||||
// <span class="mate">Mate</span><span class="claw">Claw</span>
|
||||
// Replace the inner text so styling classes are preserved but the text
|
||||
// changes. We split the brand name: first half gets "mate" class, second
|
||||
// half gets "claw" class. If it's a single word, it all goes in "mate".
|
||||
var half = Math.ceil(brand.name.length / 2)
|
||||
var firstPart = brand.name.slice(0, half)
|
||||
var secondPart = brand.name.slice(half)
|
||||
replacements.push([
|
||||
'>Mate</span><span class="claw">Claw<',
|
||||
'>' + firstPart + '</span><span class="claw">' + secondPart + '<',
|
||||
])
|
||||
|
||||
// 7. Brand name (catch-all, must come last)
|
||||
// Only replace the exact word "MateClaw", not "mateclaw" (lowercase,
|
||||
// which is used in H2 database paths and Spring Boot properties that
|
||||
// are coupled with the server and must NOT change).
|
||||
replacements.push(['MateClaw', brand.name])
|
||||
|
||||
return replacements
|
||||
}
|
||||
|
||||
function applyReplacements(code, replacements) {
|
||||
var result = code
|
||||
for (var i = 0; i < replacements.length; i++) {
|
||||
var from = replacements[i][0]
|
||||
var to = replacements[i][1]
|
||||
// Use split/join for reliable literal string replacement (no regex
|
||||
// escaping issues).
|
||||
result = result.split(from).join(to)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin entry point.
|
||||
*
|
||||
* Usage in vite.config.ts:
|
||||
* import { brandingPlugin } from './scripts/branding.cjs'
|
||||
* plugins: [brandingPlugin()]
|
||||
*/
|
||||
function brandingPlugin(options) {
|
||||
options = options || {}
|
||||
var rootDir = options.rootDir || process.cwd()
|
||||
var brand = loadBrandConfig(rootDir)
|
||||
var replacements = buildReplacements(brand)
|
||||
|
||||
var isDefault =
|
||||
brand.name === 'MateClaw' &&
|
||||
brand.tagline === 'AI Personal Assistant' &&
|
||||
brand.team === 'MateClaw Team'
|
||||
|
||||
if (!isDefault) {
|
||||
console.log('[branding] White-label build: "' + brand.name + '" (tagline: "' + brand.tagline + '")')
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'mateclaw-branding',
|
||||
enforce: 'pre',
|
||||
|
||||
// Transform JS/TS/Vue source before compilation
|
||||
transform: function (code, id) {
|
||||
if (id.indexOf('node_modules') !== -1) return null
|
||||
// Only process source files that might contain brand strings.
|
||||
if (!/\.(ts|js|vue|html|css|cjs|mjs)$/.test(id)) return null
|
||||
var result = applyReplacements(code, replacements)
|
||||
return result !== code ? { code: result, map: null } : null
|
||||
},
|
||||
|
||||
// Transform index.html
|
||||
transformIndexHtml: function (html) {
|
||||
return applyReplacements(html, replacements)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { brandingPlugin: brandingPlugin, loadBrandConfig: loadBrandConfig, buildReplacements: buildReplacements }
|
||||
35
mateclaw-desktop/scripts/build-all-platforms.sh
Executable file
35
mateclaw-desktop/scripts/build-all-platforms.sh
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# scripts/build-all-platforms.sh — Build MateClaw desktop packages for all
|
||||
# platforms (macOS + Windows) in the specified build mode.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-all-platforms.sh --all # local mode (default), both platforms
|
||||
# scripts/build-all-platforms.sh --local # local mode, both platforms
|
||||
# scripts/build-all-platforms.sh --remote # remote mode, both platforms
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
MODE="--all"
|
||||
case "${1:-}" in
|
||||
--all) MODE="local" ;;
|
||||
--local) MODE="local" ;;
|
||||
--remote) MODE="remote" ;;
|
||||
*) echo "Usage: $0 [--all|--local|--remote]"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "==> Building all platforms, BUILD_MODE=$MODE"
|
||||
|
||||
if [ "$MODE" = "remote" ]; then
|
||||
echo "==> macOS (remote/lite)"
|
||||
BUILD_MODE=remote npx electron-builder --mac
|
||||
echo "==> Windows (remote/lite)"
|
||||
BUILD_MODE=remote npx electron-builder --win
|
||||
else
|
||||
echo "==> macOS (local/full)"
|
||||
BUILD_MODE=local npx electron-builder --mac
|
||||
echo "==> Windows (local/full)"
|
||||
BUILD_MODE=local npx electron-builder --win
|
||||
fi
|
||||
|
||||
echo "==> All builds complete."
|
||||
30
mateclaw-desktop/scripts/build.sh
Executable file
30
mateclaw-desktop/scripts/build.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# scripts/build.sh — Build the MateClaw Spring Boot backend JAR and place it
|
||||
# at resources/app.jar so electron-builder can bundle it into the desktop app.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SERVER_DIR="$(cd "$PROJECT_ROOT/../mateclaw-server" && pwd)"
|
||||
RESOURCES_DIR="$PROJECT_ROOT/resources"
|
||||
|
||||
echo "==> Building mateclaw-server JAR from $SERVER_DIR"
|
||||
|
||||
# Build the Spring Boot fat JAR (skip tests for packaging speed)
|
||||
cd "$SERVER_DIR"
|
||||
mvn clean package -DskipTests -Dmaven.test.skip=true -q
|
||||
|
||||
# Locate the built JAR
|
||||
JAR_FILE=$(ls "$SERVER_DIR"/target/mateclaw-server-*.jar 2>/dev/null | head -1)
|
||||
if [ -z "$JAR_FILE" ]; then
|
||||
echo "ERROR: Could not find built JAR in $SERVER_DIR/target/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Copying $JAR_FILE → $RESOURCES_DIR/app.jar"
|
||||
mkdir -p "$RESOURCES_DIR"
|
||||
cp "$JAR_FILE" "$RESOURCES_DIR/app.jar"
|
||||
|
||||
echo "==> Done. JAR size: $(du -h "$RESOURCES_DIR/app.jar" | cut -f1)"
|
||||
86
mateclaw-desktop/scripts/download-jre.sh
Executable file
86
mateclaw-desktop/scripts/download-jre.sh
Executable file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# scripts/download-jre.sh — Download Eclipse Temurin JRE 21 for the current
|
||||
# macOS architecture (or both) and extract into resources/jre/<platform>/.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/download-jre.sh # auto-detect current arch
|
||||
# scripts/download-jre.sh arm64 # arm64 only
|
||||
# scripts/download-jre.sh x64 # x64 only
|
||||
# scripts/download-jre.sh all # both arches
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
JRE_DIR="$PROJECT_ROOT/resources/jre"
|
||||
|
||||
# Temurin 21 (LTS) JRE downloads via Adoptium API
|
||||
ADOPTIUM_BASE="https://api.adoptium.net/v3/binary/latest/21/ga/mac"
|
||||
|
||||
download_and_extract() {
|
||||
local arch="$1"
|
||||
local folder="$2"
|
||||
local url="$ADOPTIUM_BASE/$arch/jre/hotspot/normal/eclipse?project=jdk"
|
||||
local tmpfile="$JRE_DIR/jre-mac-$arch.tar.gz"
|
||||
|
||||
echo "==> Downloading Temurin 21 JRE for macOS $arch"
|
||||
mkdir -p "$JRE_DIR"
|
||||
curl -L --fail -o "$tmpfile" "$url"
|
||||
|
||||
echo "==> Extracting to $JRE_DIR/$folder"
|
||||
rm -rf "$JRE_DIR/$folder"
|
||||
mkdir -p "$JRE_DIR/$folder"
|
||||
|
||||
# Temurin macOS tar.gz extracts to: jdk-21.x.x+jre/Contents/Home/...
|
||||
# We want $folder/Contents/Home/... so move the inner Contents up.
|
||||
local extract_tmp="$JRE_DIR/.tmp-$arch"
|
||||
rm -rf "$extract_tmp"
|
||||
mkdir -p "$extract_tmp"
|
||||
tar -xzf "$tmpfile" -C "$extract_tmp"
|
||||
|
||||
# Find the extracted top-level directory and move its Contents
|
||||
local extracted_dir
|
||||
extracted_dir=$(find "$extract_tmp" -maxdepth 1 -type d -name "jdk-*" | head -1)
|
||||
if [ -z "$extracted_dir" ]; then
|
||||
# Fallback: some tarballs extract Contents directly
|
||||
if [ -d "$extract_tmp/Contents" ]; then
|
||||
mv "$extract_tmp/Contents" "$JRE_DIR/$folder/Contents"
|
||||
else
|
||||
echo "ERROR: Could not find extracted JDK directory"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
mv "$extracted_dir/Contents" "$JRE_DIR/$folder/Contents"
|
||||
fi
|
||||
|
||||
rm -rf "$extract_tmp" "$tmpfile"
|
||||
|
||||
# Verify java binary exists
|
||||
local java_bin="$JRE_DIR/$folder/Contents/Home/bin/java"
|
||||
if [ -f "$java_bin" ]; then
|
||||
echo "==> OK: $java_bin"
|
||||
else
|
||||
echo "ERROR: java binary not found at $java_bin"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
TARGET="${1:-auto}"
|
||||
if [ "$TARGET" = "auto" ]; then
|
||||
case "$(uname -m)" in
|
||||
arm64) TARGET="arm64" ;;
|
||||
x86_64) TARGET="x64" ;;
|
||||
*) echo "Unsupported arch: $(uname -m)"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$TARGET" in
|
||||
arm64) download_and_extract "aarch64" "mac-arm64" ;;
|
||||
x64) download_and_extract "x64" "mac-x64" ;;
|
||||
all) download_and_extract "aarch64" "mac-arm64"
|
||||
download_and_extract "x64" "mac-x64" ;;
|
||||
*) echo "Usage: $0 [arm64|x64|all]"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "==> JRE setup complete."
|
||||
6
mateclaw-desktop/scripts/trim-playwright-driver.cjs
Normal file
6
mateclaw-desktop/scripts/trim-playwright-driver.cjs
Normal file
@ -0,0 +1,6 @@
|
||||
// afterPack hook: no-op placeholder
|
||||
// The original trim-playwright-driver.cjs was not included in the open-source release.
|
||||
// This no-op allows electron-builder to complete packaging.
|
||||
exports.default = async function () {
|
||||
// intentionally empty
|
||||
}
|
||||
@ -16,6 +16,8 @@ const remoteUrlInput = ref('')
|
||||
const testing = ref(false)
|
||||
const testResult = ref<{ ok: boolean; msg: string } | null>(null)
|
||||
const recentServers = ref<RemoteServer[]>([])
|
||||
// Build variant: 'local' = full (bundles JRE+JAR), 'remote' = lite (connect to remote server only)
|
||||
const buildMode = ref<'local' | 'remote'>('local')
|
||||
|
||||
let BACKEND_URL = ''
|
||||
|
||||
@ -235,7 +237,21 @@ onMounted(async () => {
|
||||
const cfg = await window.mateClawAPI.getConnectionConfig()
|
||||
recentServers.value = cfg.servers || []
|
||||
remoteUrlInput.value = cfg.remoteUrl || ''
|
||||
if (cfg.forceChoose || !cfg.mode) {
|
||||
buildMode.value = cfg.buildMode || 'local'
|
||||
|
||||
// Remote (lite) builds: skip the mode chooser, go straight to the
|
||||
// remote server form — the "local" option is not available.
|
||||
if (buildMode.value === 'remote') {
|
||||
if (cfg.forceChoose || !cfg.mode || cfg.mode === 'local') {
|
||||
status.value = 'connection-select'
|
||||
connView.value = 'remote-form'
|
||||
} else {
|
||||
connectionMode.value = cfg.mode
|
||||
if (await window.mateClawAPI.isBackendReady()) {
|
||||
handleBackendReady()
|
||||
}
|
||||
}
|
||||
} else if (cfg.forceChoose || !cfg.mode) {
|
||||
status.value = 'connection-select'
|
||||
connView.value = 'choose'
|
||||
} else {
|
||||
@ -325,7 +341,11 @@ function createParticles() {
|
||||
<template v-if="connView === 'choose'">
|
||||
<div class="lang-title">选择连接方式 / Connection</div>
|
||||
<div class="lang-options">
|
||||
<button class="lang-card" @click="chooseLocal">
|
||||
<button
|
||||
v-if="buildMode === 'local'"
|
||||
class="lang-card"
|
||||
@click="chooseLocal"
|
||||
>
|
||||
<span class="lang-flag">💻</span>
|
||||
<span class="lang-label">本地运行</span>
|
||||
<span class="lang-desc">在本机内嵌运行服务</span>
|
||||
@ -336,6 +356,10 @@ function createParticles() {
|
||||
<span class="lang-desc">接入集中部署的服务器</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Remote (lite) build notice -->
|
||||
<div v-if="buildMode === 'remote'" class="remote-build-notice">
|
||||
当前为轻量版客户端,仅支持连接远程服务器
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Remote server form -->
|
||||
@ -900,6 +924,16 @@ function createParticles() {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
.remote-build-notice {
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════
|
||||
Remote Connection Form
|
||||
|
||||
2
mateclaw-desktop/src/env.d.ts
vendored
2
mateclaw-desktop/src/env.d.ts
vendored
@ -25,6 +25,7 @@ interface ConnectionConfigState {
|
||||
remoteUrl: string
|
||||
servers: RemoteServer[]
|
||||
forceChoose: boolean
|
||||
buildMode: 'local' | 'remote'
|
||||
}
|
||||
|
||||
interface ConnectionTestResult {
|
||||
@ -36,6 +37,7 @@ interface ConnectionTestResult {
|
||||
interface MateClawAPI {
|
||||
getPlatform: () => Promise<string>
|
||||
getVersion: () => Promise<string>
|
||||
getBuildMode: () => Promise<'local' | 'remote'>
|
||||
getBackendUrl: () => Promise<string>
|
||||
isBackendReady: () => Promise<boolean>
|
||||
getUserDataPath: () => Promise<string>
|
||||
|
||||
@ -3,14 +3,24 @@ import vue from '@vitejs/plugin-vue'
|
||||
import electron from 'vite-plugin-electron'
|
||||
import renderer from 'vite-plugin-electron-renderer'
|
||||
import { resolve } from 'path'
|
||||
import { brandingPlugin } from './scripts/branding.cjs'
|
||||
|
||||
export default defineConfig(({ command }) => {
|
||||
const isServe = command === 'serve'
|
||||
const isBuild = command === 'build'
|
||||
|
||||
// Shared branding plugin instance — applied to the renderer build as well
|
||||
// as the electron main/preload builds so brand strings are replaced
|
||||
// everywhere without touching source code.
|
||||
const brand = brandingPlugin()
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
// White-label branding: replaces "MateClaw" with the configured brand
|
||||
// name at build time. Source code stays untouched. Configure via
|
||||
// branding.config.json or BRAND_* env vars.
|
||||
brand,
|
||||
electron([
|
||||
{
|
||||
entry: 'electron/main/index.ts',
|
||||
@ -18,6 +28,7 @@ export default defineConfig(({ command }) => {
|
||||
args.startup()
|
||||
},
|
||||
vite: {
|
||||
plugins: [brand],
|
||||
build: {
|
||||
sourcemap: isServe,
|
||||
minify: isBuild,
|
||||
@ -34,6 +45,7 @@ export default defineConfig(({ command }) => {
|
||||
args.reload()
|
||||
},
|
||||
vite: {
|
||||
plugins: [brand],
|
||||
build: {
|
||||
sourcemap: isServe ? 'inline' : undefined,
|
||||
minify: isBuild,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user