From 0eb2dcf8c2e51337134c4675db9aa04ffa5ea572 Mon Sep 17 00:00:00 2001 From: Jasery Date: Tue, 28 Oct 2025 22:53:26 +0800 Subject: [PATCH] feat: implement auto-update functionality with GitHub integration - Added AutoUpdater class to manage update checks and downloads. - Integrated auto-updater in the main process after the main window is created. - Enhanced preload script to expose update-related IPC channels. - Created UpdateDialog component to handle user interactions for updates. - Implemented state management for update status using Pinia store. - Added localization support for update messages in multiple languages. - Documented auto-update configuration steps for private GitHub repositories. - Created .env.example for GitHub token configuration. --- .env.example | 6 + README.md | 1 + buildAssets/builder/config.js | 13 +- docs/auto-update-config.md | 61 +++++++ package.json | 2 +- src/main/AutoUpdater.ts | 89 ++++++++++ src/main/IPCs.ts | 80 +++++++++ src/main/index.ts | 7 + src/preload/index.ts | 14 +- src/renderer/App.vue | 67 ++++++++ src/renderer/components/UpdateDialog.vue | 196 +++++++++++++++++++++++ src/renderer/locales/de.json | 49 ++++-- src/renderer/locales/en.json | 19 +++ src/renderer/locales/es.json | 45 ++++-- src/renderer/locales/fr.json | 41 +++-- src/renderer/locales/ja.json | 43 +++-- src/renderer/locales/ko.json | 39 +++-- src/renderer/locales/nl.json | 41 +++-- src/renderer/locales/pt.json | 45 ++++-- src/renderer/locales/ru.json | 39 +++-- src/renderer/locales/zh-hans.json | 19 +++ src/renderer/store/app.ts | 58 +++++++ 22 files changed, 882 insertions(+), 92 deletions(-) create mode 100644 .env.example create mode 100644 docs/auto-update-config.md create mode 100644 src/main/AutoUpdater.ts create mode 100644 src/renderer/components/UpdateDialog.vue create mode 100644 src/renderer/store/app.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e63ef5a --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# GitHub Personal Access Token for private repository access +# Required for auto-updates from private repositories +GH_TOKEN= + +# For private repositories, you might also need: +# GITHUB_TOKEN= # Alternative token name that some tools might use \ No newline at end of file diff --git a/README.md b/README.md index 8cb20ed..bb87da0 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Quick Start Templates for **[Vite](https://vitejs.dev)** + **[Vue 3](https://vue - ⚡️ Basic layout manager - ⚡️ Global state management through the Pinia store - ⚡️ Quick support through the GitHub community and official documentation +- ⚡️ Auto-update support (including private repositories with proper authentication) ## [Documentation (Getting Started)](https://vutron.cdget.com/installation-and-build/getting-started) diff --git a/buildAssets/builder/config.js b/buildAssets/builder/config.js index 6b425ae..3d0dbb7 100644 --- a/buildAssets/builder/config.js +++ b/buildAssets/builder/config.js @@ -2,12 +2,23 @@ const dotenv = require('dotenv') const packageJson = require('../../package.json') +dotenv.config() + const baseConfig = { productName: packageJson.name, appId: packageJson.appId, asar: true, extends: null, compression: 'maximum', + publish: [ + { + provider: 'github', + owner: 'Jasery', + repo: 'gdt-tool', + releaseType: 'release', + token: process.env.GH_TOKEN || '' // 可选:从环境变量获取 GitHub Token,私有仓库必需 + } + ], artifactName: '${productName} ${version}_${arch}.${ext}', directories: { output: './release/${version}' @@ -90,8 +101,6 @@ const baseConfig = { } } -dotenv.config() - baseConfig.copyright = `ⓒ ${new Date().getFullYear()} $\{author}` baseConfig.files = [ /* A list of files not to be included in the build. */ diff --git a/docs/auto-update-config.md b/docs/auto-update-config.md new file mode 100644 index 0000000..a5fa769 --- /dev/null +++ b/docs/auto-update-config.md @@ -0,0 +1,61 @@ +# 自动更新配置 + +本项目使用 `electron-updater` 库实现自动更新功能,支持从私有 GitHub 仓库获取更新。 + +## 私有仓库配置 + +要启用私有仓库的自动更新功能,请按以下步骤操作: + +### 1. 创建 GitHub Personal Access Token + +1. 访问 GitHub 设置页面 (https://github.com/settings/tokens) +2. 点击 "Generate new token" (Classic tokens) +3. 选择适当的权限(至少需要 `repo` 权限来访问私有仓库) +4. 生成并复制 token + +### 2. 配置环境变量 + +在项目根目录创建 `.env` 文件: + +```bash +cp .env.example .env +``` + +编辑 `.env` 文件,添加你的 GitHub token: + +```env +GH_TOKEN=your_github_personal_access_token +``` + +### 3. 构建配置 + +构建配置文件 `buildAssets/builder/config.js` 已经配置为使用环境变量中的 token: + +```javascript +publish: [ + { + provider: 'github', + owner: 'your_username', + repo: 'your_repository', + releaseType: 'release', + token: process.env.GH_TOKEN || '' // 从环境变量获取 + } +] +``` + +## 发布更新 + +1. 更新 `package.json` 中的版本号 +2. 提交更改并推送至 GitHub +3. 创建新的 GitHub Release +4. 运行构建命令: + +```bash +pnpm build:all +``` + +## 注意事项 + +- 请确保 GitHub token 权限最小化,仅授予必要的访问权限 +- 对于分发给公众的私有应用,用户需要有访问仓库的权限才能下载更新 +- 在 CI/CD 环境中,确保在环境变量中配置了相应的 token \ No newline at end of file diff --git a/package.json b/package.json index 7e3d8c8..cde556d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "private": true, "repository": { "type": "git", - "url": "https://github.com/jooy2/vutron.git" + "url": "https://github.com/Jasery/gdt-tool.git" }, "bugs": { "url": "https://github.com/jooy2/vutron/issues" diff --git a/src/main/AutoUpdater.ts b/src/main/AutoUpdater.ts new file mode 100644 index 0000000..550e90b --- /dev/null +++ b/src/main/AutoUpdater.ts @@ -0,0 +1,89 @@ +import { dialog, ipcMain, app } from 'electron' +import { autoUpdater } from 'electron-updater' +import log from 'electron-log/main' + +// 配置日志 +autoUpdater.logger = log +log.transports.file.level = 'info' + +export class AutoUpdater { + private mainWindow: Electron.BrowserWindow | null = null + + constructor(mainWindow: Electron.BrowserWindow) { + this.mainWindow = mainWindow + + // 配置更新检查间隔 + autoUpdater.autoDownload = false + autoUpdater.allowPrerelease = false + autoUpdater.channel = 'latest' + + // 设置事件监听器 + this.setupEventListeners() + } + + private setupEventListeners() { + // 检查更新 + autoUpdater.on('checking-for-update', () => { + log.info('正在检查更新...') + this.mainWindow?.webContents.send('update-checking') + }) + + // 发现新版本 + autoUpdater.on('update-available', (info) => { + log.info('发现新版本:', info.version) + this.mainWindow?.webContents.send('update-available', info) + }) + + // 没有新版本 + autoUpdater.on('update-not-available', (info) => { + log.info('当前已是最新版本') + this.mainWindow?.webContents.send('update-not-available', info) + }) + + // 更新下载进度 + autoUpdater.on('download-progress', (progress) => { + log.info('下载进度:', progress.percent, '%') + this.mainWindow?.webContents.send('update-download-progress', progress) + }) + + // 更新下载完成 + autoUpdater.on('update-downloaded', (info) => { + log.info('更新下载完成') + this.mainWindow?.webContents.send('update-downloaded', info) + + // 提示用户是否重启应用安装更新 + dialog.showMessageBox({ + type: 'info', + title: '应用更新', + message: `新版本 ${info.version} 已下载`, + detail: '是否现在重启应用以安装更新?', + buttons: ['稍后重启', '立即重启'], + defaultId: 1, + cancelId: 0 + }).then((result) => { + if (result.response === 1) { + // 安装更新并重启应用 + setImmediate(() => { + autoUpdater.quitAndInstall() + }) + } + }) + }) + + // 更新错误 + autoUpdater.on('error', (error) => { + log.error('自动更新出错:', error) + this.mainWindow?.webContents.send('update-error', error) + }) + } + + public checkForUpdates() { + log.info('开始检查更新...') + autoUpdater.checkForUpdates() + } + + public downloadUpdate() { + log.info('开始下载更新...') + autoUpdater.downloadUpdate() + } +} \ No newline at end of file diff --git a/src/main/IPCs.ts b/src/main/IPCs.ts index 4524462..d72290f 100644 --- a/src/main/IPCs.ts +++ b/src/main/IPCs.ts @@ -8,6 +8,7 @@ import { ensureDirSync, copyFileSync } from 'fs-extra' +import { autoUpdater } from 'electron-updater' /* * IPC Communications @@ -222,5 +223,84 @@ export default class IPCs { } } ) + + // 检查更新 + ipcMain.handle('msgCheckForUpdates', async () => { + if (Constants.IS_DEV_ENV) { + // 在开发环境中模拟更新检查 + console.log('开发环境中模拟检查更新...') + return { updateAvailable: false, currentVersion: Constants.APP_VERSION } + } + + return new Promise((resolve) => { + // 监听更新事件 + const removeListeners = () => { + autoUpdater.removeListener('update-available', onUpdateAvailable) + autoUpdater.removeListener('update-not-available', onUpdateNotAvailable) + autoUpdater.removeListener('error', onError) + } + + const onUpdateAvailable = () => { + removeListeners() + resolve({ updateAvailable: true, currentVersion: Constants.APP_VERSION }) + } + + const onUpdateNotAvailable = () => { + removeListeners() + resolve({ updateAvailable: false, currentVersion: Constants.APP_VERSION }) + } + + const onError = (error: Error) => { + removeListeners() + console.error('检查更新时出错:', error) + resolve({ updateAvailable: false, currentVersion: Constants.APP_VERSION, error: error.message }) + } + + // 添加事件监听器 + autoUpdater.once('update-available', onUpdateAvailable) + autoUpdater.once('update-not-available', onUpdateNotAvailable) + autoUpdater.once('error', onError) + + // 开始检查更新 + autoUpdater.checkForUpdates() + }) + }) + + // 下载更新 + ipcMain.handle('msgDownloadUpdate', async () => { + if (Constants.IS_DEV_ENV) { + // 在开发环境中模拟下载更新 + console.log('开发环境中模拟下载更新...') + return { success: true } + } + + try { + await autoUpdater.downloadUpdate() + return { success: true } + } catch (error) { + console.error('下载更新时出错:', error) + return { success: false, error: error instanceof Error ? error.message : String(error) } + } + }) + + // 安装更新 + ipcMain.handle('msgInstallUpdate', async () => { + if (Constants.IS_DEV_ENV) { + // 在开发环境中模拟安装更新 + console.log('开发环境中模拟安装更新...') + return { success: true } + } + + try { + // 立即退出并安装更新 + setImmediate(() => { + autoUpdater.quitAndInstall() + }) + return { success: true } + } catch (error) { + console.error('安装更新时出错:', error) + return { success: false, error: error instanceof Error ? error.message : String(error) } + } + }) } } diff --git a/src/main/index.ts b/src/main/index.ts index 4ea3068..9f4adf0 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,9 +3,11 @@ import Constants from './utils/Constants' import { createErrorWindow, createMainWindow } from './MainRunner' import log from 'electron-log/main' import { join } from 'path' +import { AutoUpdater } from './AutoUpdater' let mainWindow let errorWindow +let autoUpdater: AutoUpdater | null = null const initializeMainLogger = () => { log.initialize({ @@ -52,6 +54,11 @@ app.on('ready', async () => { initializeMainLogger() mainWindow = await createMainWindow() + + // Initialize auto updater after main window is created + if (!Constants.IS_DEV_ENV) { + autoUpdater = new AutoUpdater(mainWindow) + } }) app.on('activate', async () => { diff --git a/src/preload/index.ts b/src/preload/index.ts index a812abc..b707727 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -14,9 +14,19 @@ const mainAvailChannels: string[] = [ 'msgLoadData', 'msgSaveData', 'msgExecuteTemplate', - 'msgFindFilesInFolder' + 'msgFindFilesInFolder', + 'msgCheckForUpdates', + 'msgDownloadUpdate', + 'msgInstallUpdate' +] +const rendererAvailChannels: string[] = [ + 'update-checking', + 'update-available', + 'update-not-available', + 'update-download-progress', + 'update-downloaded', + 'update-error' ] -const rendererAvailChannels: string[] = [] contextBridge.exposeInMainWorld('mainApi', { send: (channel: string, ...data: any[]): void => { diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 9af8785..cd1d162 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -1,6 +1,72 @@