mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 03:33:43 +08:00
* 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>
154 lines
4.8 KiB
JavaScript
154 lines
4.8 KiB
JavaScript
/**
|
|
* 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 }
|