hololake-system-architecture/product-source/hololake-desktop/electron/main.ts
冰朔 047f37e87e feat(product-source): HoloLake Desktop v0.5.0 — Electron 桌面知识库应用
铸渊 2026-08-08 开发,冰朔架构决策:
- 知识库放桌面,不放服务器
- Forgejo 是 Git 引擎,光湖自己维护更新,不接上游
- 不部署 Forgejo 源码,只引用 MANIFEST + 离线包

架构:
- Electron v35(Mac arm64)+ Vite + React 19
- 复用 guanghu-knowledge-base 前端组件
- Electron 主进程自动启动 Express + Git 引擎后端
- 数据存储:~/Library/Application Support/HoloLake Era/data/
- Forgejo 离线包(桌面)作为 Git 远程仓库对接

构建:
- npx electron-builder --mac --dir → HoloLake Era.app
- 签名后续用 Developer ID 补(CSC_IDENTITY_AUTO_DISCOVERY=false)

文件:
- electron/main.ts(Electron 主进程:窗口+服务器+生命周期)
- electron/preload.ts(IPC 安全桥梁:Forgejo 远程管理接口)
- forgejo/INTEGRATION.md(Forgejo 集成声明 + 更新策略)
- forgejo/MANIFEST.sha256(离线包校验清单)
- forgejo/icon.icns(应用图标,来自 HoloLake Era v0.4.6)
- vite.config.ts(复用知识库前端,构建到 dist/)
- tsconfig.electron.json(Electron TypeScript 配置)
2026-08-08 07:21:04 +08:00

148 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* HoloLake Desktop · Electron 主进程
*
* 职责:
* 1. 创建应用窗口
* 2. 启动知识库后端服务器Express + Git 引擎)
* 3. 管理应用生命周期
*
* 架构:
* Electron Main Process
* ├── 启动 Express 服务器Git 引擎层,端口 3890
* ├── 创建 BrowserWindow加载 Vite 前端或打包后的 dist
* └── 数据存储:~/Library/Application Support/HoloLake Era/data/
*/
import { app, BrowserWindow, shell, dialog } from 'electron';
import path from 'path';
import { spawn, ChildProcess } from 'child_process';
// ─── 配置 ───
const isDev = process.env.NODE_ENV !== 'production';
const SERVER_PORT = 3890;
const CLIENT_PORT = 5180;
// 数据目录macOS 标准位置
const DATA_DIR = path.join(app.getPath('userData'), 'data');
const KB_REPO_PATH = path.join(DATA_DIR, 'knowledge-base');
// ─── 后端服务器 ───
let serverProcess: ChildProcess | null = null;
function startServer(): Promise<void> {
return new Promise((resolve, reject) => {
// 在开发模式下,服务器由 concurrently 启动
if (isDev && process.env.KB_SERVER_RUNNING === '1') {
resolve();
return;
}
const serverScript = path.resolve(__dirname, '../../guanghu-knowledge-base/server/index.ts');
serverProcess = spawn('npx', ['tsx', serverScript], {
env: {
...process.env,
KB_PORT: String(SERVER_PORT),
KB_REPO_PATH,
NODE_ENV: 'production',
},
cwd: path.resolve(__dirname, '../..'),
stdio: ['pipe', 'pipe', 'pipe'],
});
serverProcess.stdout?.on('data', (data: Buffer) => {
const msg = data.toString().trim();
console.log(`[KB Server] ${msg}`);
if (msg.includes('已启动')) resolve();
});
serverProcess.stderr?.on('data', (data: Buffer) => {
console.error(`[KB Server Error] ${data.toString().trim()}`);
});
serverProcess.on('error', (err: Error) => {
console.error('服务器启动失败:', err);
reject(err);
});
serverProcess.on('exit', (code: number | null) => {
console.log(`[KB Server] 退出code=${code}`);
serverProcess = null;
});
// 超时兜底
setTimeout(() => resolve(), 5000);
});
}
function stopServer(): void {
if (serverProcess) {
serverProcess.kill('SIGTERM');
serverProcess = null;
}
}
// ─── 窗口 ───
let mainWindow: BrowserWindow | null = null;
function createWindow(): void {
mainWindow = new BrowserWindow({
width: 1280,
height: 860,
minWidth: 800,
minHeight: 600,
titleBarStyle: 'hiddenInset',
trafficLightPosition: { x: 16, y: 16 },
backgroundColor: '#0d1117',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
if (isDev) {
mainWindow.loadURL(`http://localhost:${CLIENT_PORT}`);
mainWindow.webContents.openDevTools({ mode: 'detach' });
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}
// 外部链接用系统浏览器打开
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('http')) shell.openExternal(url);
return { action: 'deny' };
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
// ─── 应用生命周期 ───
app.whenReady().then(async () => {
try {
await startServer();
createWindow();
} catch (err) {
dialog.showErrorBox('启动失败', `知识库引擎启动失败:${err}`);
app.quit();
}
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
stopServer();
if (process.platform !== 'darwin') app.quit();
});
app.on('before-quit', () => {
stopServer();
});