feat: auto-updater integration

- Install electron-updater
- New updater.ts: check/download/install with progress events
- IPC: updater:check, updater:download, updater:install, updater:status
- Preload bridge for updater API
- electron-builder publish config (github provider)
- Fix build files path: out -> electron-dist
- i18n keys for updater notifications (zh-CN + en)
This commit is contained in:
2026-07-05 22:13:25 +08:00
parent d131a2732d
commit 405dfa77c4
9 changed files with 163 additions and 11 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
- [x] **内存分析**`MEMORY USAGE` 扫描所有 key,虚拟化列表,按大小排序,暂停/恢复,大小过滤
- [x] **命令日志** — 记录每次命令的耗时/连接/时间,最多 5000 条,只写模式过滤
- [ ] **自动更新** — electron-updater 集成,启动检查,下载进度,发行说明
- [x] **自动更新** — electron-updater 集成,启动检查,下载进度,发行说明
- [ ] **多标签页** — 单连接支持同时打开 Status/Keys/CLI/SlowLog/内存分析/批量删除 多个标签
- [ ] **多连接并行** — 同时打开多个连接,各自独立标签页
- [ ] **Monaco 编辑器** — 替换 StringEditor 的 textarea,支持 JSON 折叠/展开/语法高亮
+2
View File
@@ -3,6 +3,7 @@ import { app, BrowserWindow, nativeTheme, nativeImage } from 'electron'
import { join } from 'path'
import { restoreAndTrack } from './win-state'
import { registerIpcHandlers } from './ipc-handlers'
import { initUpdater } from './updater'
let mainWindow: BrowserWindow | null = null
@@ -55,6 +56,7 @@ function createWindow(): void {
app.whenReady().then(() => {
registerIpcHandlers()
createWindow()
initUpdater(mainWindow!)
})
app.on('window-all-closed', () => { app.quit() })
+71
View File
@@ -0,0 +1,71 @@
// electron/main/updater.ts
// 自动更新模块
import { autoUpdater } from 'electron-updater'
import { BrowserWindow, ipcMain } from 'electron'
let mainWindow: BrowserWindow | null = null
export function initUpdater(win: BrowserWindow): void {
mainWindow = win
// 检查更新
ipcMain.handle('updater:check', async () => {
try {
const result = await autoUpdater.checkForUpdates()
return result?.updateInfo ? { available: true, version: result.updateInfo.version } : { available: false }
} catch {
return { available: false }
}
})
// 下载更新
ipcMain.handle('updater:download', async () => {
try {
await autoUpdater.downloadUpdate()
return { success: true }
} catch (err: any) {
return { success: false, error: err.message }
}
})
// 安装更新
ipcMain.handle('updater:install', () => {
autoUpdater.quitAndInstall()
})
autoUpdater.autoDownload = false
autoUpdater.on('checking-for-update', () => {
mainWindow?.webContents.send('updater:status', { status: 'checking' })
})
autoUpdater.on('update-available', (info) => {
mainWindow?.webContents.send('updater:status', {
status: 'available',
version: info.version,
releaseNotes: info.releaseNotes,
})
})
autoUpdater.on('update-not-available', () => {
mainWindow?.webContents.send('updater:status', { status: 'not-available' })
})
autoUpdater.on('download-progress', (progress) => {
mainWindow?.webContents.send('updater:status', {
status: 'downloading',
percent: progress.percent,
transferred: progress.transferred,
total: progress.total,
bytesPerSecond: progress.bytesPerSecond,
})
})
autoUpdater.on('update-downloaded', () => {
mainWindow?.webContents.send('updater:status', { status: 'downloaded' })
})
autoUpdater.on('error', (err) => {
mainWindow?.webContents.send('updater:status', { status: 'error', message: err.message })
})
}
+6
View File
@@ -105,6 +105,12 @@ export interface ElectronAPI {
getCommandLog: () => Promise<any[]>
clearCommandLog: () => Promise<void>
}
updater: {
check: () => Promise<{ available: boolean; version?: string }>
download: () => Promise<{ success: boolean; error?: string }>
install: () => void
onStatus: (callback: (status: any) => void) => void
}
}
declare global {
+8
View File
@@ -120,6 +120,14 @@ const electronAPI = {
clearCommandLog: (): Promise<void> =>
ipcRenderer.invoke('redis:clearCommandLog'),
},
updater: {
check: (): Promise<any> => ipcRenderer.invoke('updater:check'),
download: (): Promise<any> => ipcRenderer.invoke('updater:download'),
install: () => ipcRenderer.invoke('updater:install'),
onStatus: (callback: (status: any) => void) => {
ipcRenderer.on('updater:status', (_e, status) => callback(status))
},
},
}
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
+48 -9
View File
@@ -10,6 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"electron-store": "^11.0.2",
"electron-updater": "^6.8.9",
"ioredis": "^5.7.0",
"pinia": "^3.0.4",
"ssh2": "^1.17.0"
@@ -2289,7 +2290,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/asn1": {
@@ -2545,7 +2545,6 @@
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
"integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
@@ -3329,6 +3328,34 @@
"dev": true,
"license": "ISC"
},
"node_modules/electron-updater": {
"version": "6.8.9",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
"integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
"license": "MIT",
"dependencies": {
"builder-util-runtime": "9.7.0",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
"lodash.escaperegexp": "^4.1.2",
"lodash.isequal": "^4.5.0",
"semver": "~7.7.3",
"tiny-typed-emitter": "^2.1.0"
}
},
"node_modules/electron-updater/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/electron-vite": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-3.1.0.tgz",
@@ -3731,7 +3758,6 @@
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -3996,7 +4022,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/has-flag": {
@@ -4285,7 +4310,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"dev": true,
"funding": [
{
"type": "github",
@@ -4361,7 +4385,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
@@ -4384,7 +4407,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash": {
@@ -4413,6 +4435,19 @@
"lodash-es": "*"
}
},
"node_modules/lodash.escaperegexp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
"license": "MIT"
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@@ -5384,7 +5419,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
@@ -5737,6 +5771,12 @@
"semver": "bin/semver"
}
},
"node_modules/tiny-typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
@@ -5859,7 +5899,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
+7 -1
View File
@@ -31,9 +31,14 @@
"output": "release"
},
"files": [
"out/**/*",
"electron-dist/**/*",
"package.json"
],
"publish": {
"provider": "github",
"owner": "Jokul",
"repo": "JRedisDesktop"
},
"linux": {
"target": [
"AppImage",
@@ -60,6 +65,7 @@
},
"dependencies": {
"electron-store": "^11.0.2",
"electron-updater": "^6.8.9",
"ioredis": "^5.7.0",
"pinia": "^3.0.4",
"ssh2": "^1.17.0"
+10
View File
@@ -166,6 +166,16 @@ export default {
clear: 'Clear',
empty: 'No commands recorded',
},
updater: {
checking: 'Checking for updates...',
available: 'New version {version} available',
notAvailable: 'You are up to date',
downloading: 'Downloading... {percent}%',
downloaded: 'Update downloaded. Restart now?',
install: 'Restart Now',
later: 'Later',
error: 'Update failed: {message}',
},
status: {
title: 'Status',
database: 'Database',
+10
View File
@@ -166,6 +166,16 @@ export default {
clear: '清空',
empty: '暂无命令记录',
},
updater: {
checking: '正在检查更新...',
available: '发现新版本 {version}',
notAvailable: '已是最新版本',
downloading: '正在下载... {percent}%',
downloaded: '更新已下载,重启安装?',
install: '立即重启',
later: '稍后',
error: '更新失败: {message}',
},
status: {
title: '状态',
database: '数据库',