From 405dfa77c4463c5315a7f1af44591aa3cce423ea Mon Sep 17 00:00:00 2001 From: Jokul Date: Sun, 5 Jul 2026 22:13:25 +0800 Subject: [PATCH] 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) --- ROADMAP.md | 2 +- electron/main/index.ts | 2 ++ electron/main/updater.ts | 71 +++++++++++++++++++++++++++++++++++++ electron/preload/index.d.ts | 6 ++++ electron/preload/index.ts | 8 +++++ package-lock.json | 57 ++++++++++++++++++++++++----- package.json | 8 ++++- src/i18n/locales/en.ts | 10 ++++++ src/i18n/locales/zh-CN.ts | 10 ++++++ 9 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 electron/main/updater.ts diff --git a/ROADMAP.md b/ROADMAP.md index 4803c62..2463a83 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,7 +15,7 @@ - [x] **内存分析** — `MEMORY USAGE` 扫描所有 key,虚拟化列表,按大小排序,暂停/恢复,大小过滤 - [x] **命令日志** — 记录每次命令的耗时/连接/时间,最多 5000 条,只写模式过滤 -- [ ] **自动更新** — electron-updater 集成,启动检查,下载进度,发行说明 +- [x] **自动更新** — electron-updater 集成,启动检查,下载进度,发行说明 - [ ] **多标签页** — 单连接支持同时打开 Status/Keys/CLI/SlowLog/内存分析/批量删除 多个标签 - [ ] **多连接并行** — 同时打开多个连接,各自独立标签页 - [ ] **Monaco 编辑器** — 替换 StringEditor 的 textarea,支持 JSON 折叠/展开/语法高亮 diff --git a/electron/main/index.ts b/electron/main/index.ts index dd7286d..a24c1a0 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -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() }) diff --git a/electron/main/updater.ts b/electron/main/updater.ts new file mode 100644 index 0000000..50ae6b5 --- /dev/null +++ b/electron/main/updater.ts @@ -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 }) + }) +} diff --git a/electron/preload/index.d.ts b/electron/preload/index.d.ts index 8c5f7e1..5f694b6 100644 --- a/electron/preload/index.d.ts +++ b/electron/preload/index.d.ts @@ -105,6 +105,12 @@ export interface ElectronAPI { getCommandLog: () => Promise clearCommandLog: () => Promise } + updater: { + check: () => Promise<{ available: boolean; version?: string }> + download: () => Promise<{ success: boolean; error?: string }> + install: () => void + onStatus: (callback: (status: any) => void) => void + } } declare global { diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 16fee0d..963737b 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -120,6 +120,14 @@ const electronAPI = { clearCommandLog: (): Promise => ipcRenderer.invoke('redis:clearCommandLog'), }, + updater: { + check: (): Promise => ipcRenderer.invoke('updater:check'), + download: (): Promise => 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) diff --git a/package-lock.json b/package-lock.json index 5ef49ec..3256a27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 1ad983c..cac66fe 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 64e5620..aaba0f8 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -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', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 5e42056..a24a29f 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -166,6 +166,16 @@ export default { clear: '清空', empty: '暂无命令记录', }, + updater: { + checking: '正在检查更新...', + available: '发现新版本 {version}', + notAvailable: '已是最新版本', + downloading: '正在下载... {percent}%', + downloaded: '更新已下载,重启安装?', + install: '立即重启', + later: '稍后', + error: '更新失败: {message}', + }, status: { title: '状态', database: '数据库',