Sync latest TrueGrowth updates
Some checks failed
CI / main (push) Has been cancelled
CI / release-e2e (push) Has been cancelled

This commit is contained in:
jiam
2026-07-08 02:03:18 +08:00
parent 52636c91ae
commit 5119ac0ef8
102 changed files with 20985 additions and 1760 deletions

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>

View File

@@ -7,14 +7,25 @@ import {
shell,
utilityProcess,
} from 'electron';
import electronUpdater from 'electron-updater';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const { autoUpdater } = electronUpdater;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const brandName = 'TrueGrowth';
const rendererUrl = process.env.VITE_DEV_SERVER_URL;
const localApiPort = Number(process.env.TRUEGROWTH_LOCAL_API_PORT || 48177);
const updateFeedUrl =
process.env.TRUEGROWTH_UPDATE_FEED_URL ||
'https://truegrowth.benchu.cloud/desktop/stable/';
const updateCheckDelayMs = Number(
process.env.TRUEGROWTH_UPDATE_CHECK_DELAY_MS || 15000
);
const updateCheckIntervalMs = Number(
process.env.TRUEGROWTH_UPDATE_CHECK_INTERVAL_MS || 4 * 60 * 60 * 1000
);
const userDataPath =
process.env.TRUEGROWTH_ELECTRON_USER_DATA ||
path.join(app.getPath('appData'), brandName);
@@ -28,6 +39,10 @@ const brandIconPath = resolveAppPath(
let mainWindow = null;
let localApi = null;
let updateCheckTimer = null;
let updateCheckInFlight = null;
let updateDownloadInFlight = null;
let updateState = null;
function readAppVersion() {
try {
@@ -41,11 +56,282 @@ function readAppVersion() {
}
const appVersion = readAppVersion();
updateState = createInitialUpdateState();
function resolveAppPath(...segments) {
return path.join(appRoot, ...segments);
}
function createInitialUpdateState() {
return {
appVersion: appVersion || app.getVersion(),
available: false,
autoDownload: false,
canCheck: true,
canDownload: false,
canInstall: false,
channelUrl: updateFeedUrl,
downloaded: false,
downloading: false,
error: '',
feedReady: false,
isPackaged: app.isPackaged,
lastCheckedAt: null,
notes: '',
percent: 0,
platform: process.platform,
status: 'idle',
updateVersion: null,
};
}
function normalizeReleaseNotes(releaseNotes) {
if (Array.isArray(releaseNotes)) {
return releaseNotes
.map((item) => {
if (typeof item === 'string') {
return item;
}
if (item && typeof item === 'object') {
return String(item.note || item.notes || item.version || '');
}
return '';
})
.filter(Boolean)
.join('\n');
}
if (typeof releaseNotes === 'string') {
return releaseNotes;
}
return '';
}
function updateUpdaterState(patch) {
updateState = {
...updateState,
...patch,
appVersion: appVersion || app.getVersion(),
channelUrl: updateFeedUrl,
isPackaged: app.isPackaged,
platform: process.platform,
};
broadcastUpdaterState();
return updateState;
}
function broadcastUpdaterState() {
BrowserWindow.getAllWindows().forEach((window) => {
if (!window.isDestroyed()) {
window.webContents.send('truegrowth-update:state', updateState);
}
});
}
function configureAutoUpdater() {
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.allowDowngrade = false;
autoUpdater.allowPrerelease = false;
autoUpdater.logger = console;
autoUpdater.setFeedURL({
provider: 'generic',
url: updateFeedUrl,
});
updateUpdaterState({
autoDownload: autoUpdater.autoDownload,
feedReady: true,
});
autoUpdater.on('checking-for-update', () => {
updateUpdaterState({
canCheck: false,
error: '',
status: 'checking',
});
});
autoUpdater.on('update-not-available', () => {
updateUpdaterState({
available: false,
canCheck: true,
canDownload: false,
canInstall: false,
downloaded: false,
downloading: false,
error: '',
lastCheckedAt: Date.now(),
percent: 0,
status: 'not-available',
updateVersion: null,
});
});
autoUpdater.on('update-available', (info) => {
updateUpdaterState({
available: true,
canCheck: true,
canDownload: true,
canInstall: false,
downloaded: false,
downloading: false,
error: '',
lastCheckedAt: Date.now(),
notes: normalizeReleaseNotes(info?.releaseNotes),
percent: 0,
status: 'available',
updateVersion: info?.version || null,
});
});
autoUpdater.on('download-progress', (progress) => {
updateUpdaterState({
available: true,
canCheck: false,
canDownload: false,
canInstall: false,
downloaded: false,
downloading: true,
error: '',
percent: Math.max(0, Math.min(100, Number(progress?.percent || 0))),
status: 'downloading',
});
});
autoUpdater.on('update-downloaded', (info) => {
updateDownloadInFlight = null;
updateUpdaterState({
available: true,
canCheck: true,
canDownload: false,
canInstall: true,
downloaded: true,
downloading: false,
error: '',
notes: normalizeReleaseNotes(info?.releaseNotes) || updateState.notes,
percent: 100,
status: 'downloaded',
updateVersion: info?.version || updateState.updateVersion,
});
});
autoUpdater.on('error', (error) => {
updateCheckInFlight = null;
updateDownloadInFlight = null;
updateUpdaterState({
canCheck: true,
canDownload: Boolean(updateState.available && !updateState.downloaded),
downloading: false,
error:
error instanceof Error
? error.message
: String(error || '更新检查失败'),
status: 'error',
});
});
}
async function checkForDesktopUpdate({ manual = false } = {}) {
if (!app.isPackaged && !process.env.TRUEGROWTH_ALLOW_DEV_UPDATES) {
return updateUpdaterState({
canCheck: true,
error: manual ? '开发模式下不会连接正式更新通道。' : '',
feedReady: true,
status: manual ? 'dev-disabled' : 'idle',
});
}
if (updateCheckInFlight) {
return updateCheckInFlight;
}
updateCheckInFlight = autoUpdater
.checkForUpdates()
.then(() => updateState)
.finally(() => {
updateCheckInFlight = null;
});
return updateCheckInFlight;
}
async function downloadDesktopUpdate() {
if (!app.isPackaged && !process.env.TRUEGROWTH_ALLOW_DEV_UPDATES) {
return updateUpdaterState({
error: '开发模式下不会下载正式更新包。',
status: 'dev-disabled',
});
}
if (!updateState.available) {
return updateUpdaterState({
error: '当前没有可下载的新版本。',
status: 'error',
});
}
if (updateDownloadInFlight) {
return updateDownloadInFlight;
}
updateDownloadInFlight = autoUpdater
.downloadUpdate()
.then(() => updateState)
.finally(() => {
if (updateState.status !== 'downloaded') {
updateDownloadInFlight = null;
}
});
updateUpdaterState({
canCheck: false,
canDownload: false,
downloading: true,
error: '',
status: 'downloading',
});
return updateDownloadInFlight;
}
function installDesktopUpdate() {
if (!updateState.downloaded) {
return updateUpdaterState({
error: '更新包尚未下载完成。',
status: 'error',
});
}
updateUpdaterState({
canCheck: false,
canDownload: false,
canInstall: false,
status: 'installing',
});
autoUpdater.quitAndInstall(false, true);
return updateState;
}
function openDesktopUpdateChannel() {
void shell.openExternal(updateFeedUrl);
return updateState;
}
function installUpdateIpc() {
ipcMain.handle('truegrowth-update:get-state', () => updateState);
ipcMain.handle('truegrowth-update:check', () =>
checkForDesktopUpdate({ manual: true })
);
ipcMain.handle('truegrowth-update:download', () => downloadDesktopUpdate());
ipcMain.handle('truegrowth-update:install', () => installDesktopUpdate());
ipcMain.handle('truegrowth-update:open-channel', () =>
openDesktopUpdateChannel()
);
}
function scheduleDesktopUpdateChecks() {
if (updateCheckTimer) {
clearInterval(updateCheckTimer);
updateCheckTimer = null;
}
setTimeout(() => {
void checkForDesktopUpdate().catch(() => undefined);
}, Math.max(0, updateCheckDelayMs));
updateCheckTimer = setInterval(() => {
void checkForDesktopUpdate().catch(() => undefined);
}, Math.max(60_000, updateCheckIntervalMs));
}
function createDesktopLocalRendererEntry(indexHtmlPath) {
const html = fs.readFileSync(indexHtmlPath, 'utf8');
const appAssetPattern =
@@ -72,8 +358,8 @@ process.env.TRUEGROWTH_DESKTOP_RESOURCE_DIR =
process.env.TRUEGROWTH_DESKTOP_RESOURCE_DIR ||
(app.isPackaged ? process.resourcesPath : appRoot);
const hasSingleInstanceLock = app.requestSingleInstanceLock({
rendererUrl,
const hasSingleInstanceLock = app.requestSingleInstanceLock({
rendererUrl,
userDataPath,
});
@@ -203,6 +489,20 @@ function installApplicationMenu() {
? [{ type: 'separator' }, { role: 'front' }]
: [{ role: 'close' }]),
];
const helpMenu = [
{
label: 'Check for Updates',
click: () => {
void checkForDesktopUpdate({ manual: true });
},
},
{
label: 'Open Update Channel',
click: () => {
openDesktopUpdateChannel();
},
},
];
const template = [
...(process.platform === 'darwin'
? [
@@ -231,7 +531,7 @@ function installApplicationMenu() {
{ label: 'Edit', submenu: editMenu },
{ label: 'View', submenu: viewMenu },
{ label: 'Window', submenu: windowMenu },
{ label: 'Help', submenu: [] },
{ label: 'Help', submenu: helpMenu },
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
@@ -280,6 +580,7 @@ function createWindow() {
mainWindow.webContents.once('did-finish-load', () => {
sendWindowState(mainWindow);
sendSystemThemeState(mainWindow);
broadcastUpdaterState();
});
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
@@ -327,9 +628,11 @@ async function startLocalApi() {
}
app.whenReady().then(async () => {
configureAutoUpdater();
installApplicationMenu();
installWindowControlsIpc();
installThemeIpc();
installUpdateIpc();
if (process.platform === 'darwin' && app.dock) {
app.dock.setIcon(brandIconPath);
}
@@ -341,6 +644,7 @@ app.whenReady().then(async () => {
});
await startLocalApi();
createWindow();
scheduleDesktopUpdateChecks();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
@@ -356,5 +660,9 @@ app.on('window-all-closed', () => {
});
app.on('before-quit', () => {
if (updateCheckTimer) {
clearInterval(updateCheckTimer);
updateCheckTimer = null;
}
localApi?.close();
});

View File

@@ -35,6 +35,23 @@ const themeBridge = {
},
};
const updatesBridge = {
getState: () => ipcRenderer.invoke('truegrowth-update:get-state'),
check: () => ipcRenderer.invoke('truegrowth-update:check'),
download: () => ipcRenderer.invoke('truegrowth-update:download'),
install: () => ipcRenderer.invoke('truegrowth-update:install'),
openChannel: () => ipcRenderer.invoke('truegrowth-update:open-channel'),
onStateChange: (callback) => {
if (typeof callback !== 'function') {
return () => undefined;
}
const listener = (_event, state) => callback(state);
ipcRenderer.on('truegrowth-update:state', listener);
return () =>
ipcRenderer.removeListener('truegrowth-update:state', listener);
},
};
contextBridge.exposeInMainWorld('trueGrowthRuntime', {
app: 'TrueGrowth',
platform: process.platform,
@@ -42,6 +59,7 @@ contextBridge.exposeInMainWorld('trueGrowthRuntime', {
localApiBaseUrl: 'http://127.0.0.1:48177/local-api',
windowControls,
theme: themeBridge,
updates: updatesBridge,
});
contextBridge.exposeInMainWorld('__OPENTU_DESKTOP__', true);