集成自动更新

This commit is contained in:
Tthfyth
2025-12-08 17:23:31 +08:00
parent e266754f46
commit cfac80a3ed
5 changed files with 393 additions and 68 deletions

View File

@@ -1,47 +1,167 @@
name: Publish
name: Build and Release
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., 1.0.0)'
required: false
default: ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
publish:
# To enable auto publishing to github, update your electron publisher
# config in package.json > "build" and remove the conditional below
if: ${{ github.repository_owner == 'electron-react-boilerplate' }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest]
# 构建 Windows 版本
build-windows:
runs-on: windows-latest
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node and NPM
uses: actions/setup-node@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
node-version: 22
cache: npm
version: 8
- name: Install and build
run: |
npm install
npm run postinstall
npm run build
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Publish releases
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build application
run: pnpm run build
- name: Package for Windows
run: pnpm exec electron-builder --win --publish never
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: windows-build
path: |
release/build/*.exe
release/build/*.exe.blockmap
retention-days: 5
# 构建 macOS 版本
build-macos:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build application
run: pnpm run build
- name: Package for macOS
env:
# The APPLE_* values are used for auto updates signing
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASS }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# macOS 签名(可选)
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# This is used for uploading release assets to github
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm exec electron-builder -- --publish always --win --mac --linux
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASS }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: pnpm exec electron-builder --mac --publish never
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-build
path: |
release/build/*.dmg
release/build/*.dmg.blockmap
release/build/*.zip
retention-days: 5
# 构建 Linux 版本
build-linux:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build application
run: pnpm run build
- name: Package for Linux
run: pnpm exec electron-builder --linux --publish never
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: linux-build
path: |
release/build/*.AppImage
release/build/*.deb
release/build/*.rpm
retention-days: 5
# 发布 Release
release:
needs: [build-windows, build-macos, build-linux]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Display structure of downloaded files
run: ls -R artifacts
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
artifacts/windows-build/*
artifacts/macos-build/*
artifacts/linux-build/*
draft: false
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,34 +1,74 @@
name: Test
name: CI
on: [push, pull_request]
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ${{ matrix.os }}
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm run lint
- name: TypeScript check
run: pnpm exec tsc --noEmit
build:
runs-on: ${{ matrix.os }}
needs: lint
strategy:
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
os: [windows-latest, macos-latest, ubuntu-latest]
fail-fast: false
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Checkout code
uses: actions/checkout@v4
- name: Install Node.js and NPM
uses: actions/setup-node@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
node-version: 22
cache: npm
version: 8
- name: npm install
run: |
npm install
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: npm test
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build application
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run package
npm run lint
npm exec tsc
npm test
run: pnpm run build
- name: Run tests
run: pnpm test
- name: Package application
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run package

View File

@@ -12,13 +12,13 @@
"hot",
"reload"
],
"homepage": "https://github.com/electron-react-boilerplate/electron-react-boilerplate#readme",
"homepage": "https://github.com/Tthfyth/source#readme",
"bugs": {
"url": "https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues"
"url": "https://github.com/Tthfyth/source/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/electron-react-boilerplate/electron-react-boilerplate.git"
"url": "git+https://github.com/Tthfyth/source.git"
},
"license": "MIT",
"author": {
@@ -204,8 +204,8 @@
"webpack-merge": "^6.0.1"
},
"build": {
"productName": "ElectronReact",
"appId": "org.erb.ElectronReact",
"productName": "SourceDebug",
"appId": "com.aerowang.sourcedebug",
"asar": true,
"afterSign": ".erb/scripts/notarize.js",
"asarUnpack": "**\\*.{node,dll}",
@@ -264,8 +264,9 @@
],
"publish": {
"provider": "github",
"owner": "electron-react-boilerplate",
"repo": "electron-react-boilerplate"
"owner": "Tthfyth",
"repo": "source",
"releaseType": "release"
}
},
"collective": {
@@ -274,12 +275,7 @@
"devEngines": {
"runtime": {
"name": "node",
"version": ">=14.x",
"onFail": "error"
},
"packageManager": {
"name": "npm",
"version": ">=7.x",
"version": ">=18.x",
"onFail": "error"
}
},

View File

@@ -22,7 +22,78 @@ class AppUpdater {
constructor() {
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.checkForUpdatesAndNotify();
// 配置自动更新
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
// 检查更新事件
autoUpdater.on('checking-for-update', () => {
log.info('正在检查更新...');
this.sendStatusToWindow('checking-for-update');
});
autoUpdater.on('update-available', (info) => {
log.info('发现新版本:', info.version);
this.sendStatusToWindow('update-available', info);
// 显示更新对话框
if (mainWindow) {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: '发现新版本',
message: `发现新版本 ${info.version},是否立即下载?`,
buttons: ['立即下载', '稍后提醒'],
defaultId: 0,
}).then(({ response }) => {
if (response === 0) {
autoUpdater.downloadUpdate();
}
});
}
});
autoUpdater.on('update-not-available', (info) => {
log.info('当前已是最新版本');
this.sendStatusToWindow('update-not-available', info);
});
autoUpdater.on('error', (err) => {
log.error('更新错误:', err);
this.sendStatusToWindow('error', err.message);
});
autoUpdater.on('download-progress', (progressObj) => {
log.info(`下载进度: ${progressObj.percent.toFixed(1)}%`);
this.sendStatusToWindow('download-progress', progressObj);
});
autoUpdater.on('update-downloaded', (info) => {
log.info('更新下载完成:', info.version);
this.sendStatusToWindow('update-downloaded', info);
// 显示安装对话框
if (mainWindow) {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: '更新已就绪',
message: `新版本 ${info.version} 已下载完成,是否立即安装并重启?`,
buttons: ['立即安装', '稍后安装'],
defaultId: 0,
}).then(({ response }) => {
if (response === 0) {
autoUpdater.quitAndInstall();
}
});
}
});
// 启动时检查更新
autoUpdater.checkForUpdates();
}
sendStatusToWindow(status: string, data?: any) {
if (mainWindow) {
mainWindow.webContents.send('update-status', { status, data });
}
}
}
@@ -251,6 +322,57 @@ ipcMain.handle('file:openFile', async () => {
}
});
// ============================================
// IPC 通信接口 - 应用更新
// ============================================
/**
* 手动检查更新
*/
ipcMain.handle('app:checkForUpdates', async () => {
try {
const result = await autoUpdater.checkForUpdates();
return {
success: true,
updateInfo: result?.updateInfo,
};
} catch (error: any) {
return {
success: false,
error: error.message || String(error),
};
}
});
/**
* 下载更新
*/
ipcMain.handle('app:downloadUpdate', async () => {
try {
await autoUpdater.downloadUpdate();
return { success: true };
} catch (error: any) {
return {
success: false,
error: error.message || String(error),
};
}
});
/**
* 安装更新并重启
*/
ipcMain.handle('app:quitAndInstall', () => {
autoUpdater.quitAndInstall();
});
/**
* 获取应用版本
*/
ipcMain.handle('app:getVersion', () => {
return app.getVersion();
});
// ============================================
// IPC 通信接口 - AI 服务
// ============================================

View File

@@ -192,6 +192,53 @@ const aiApiHandler = {
contextBridge.exposeInMainWorld('aiApi', aiApiHandler);
// --------- 应用更新 API ---------
const appApiHandler = {
/**
* 获取应用版本
*/
getVersion: () => {
return ipcRenderer.invoke('app:getVersion');
},
/**
* 检查更新
*/
checkForUpdates: () => {
return ipcRenderer.invoke('app:checkForUpdates');
},
/**
* 下载更新
*/
downloadUpdate: () => {
return ipcRenderer.invoke('app:downloadUpdate');
},
/**
* 安装更新并重启
*/
quitAndInstall: () => {
return ipcRenderer.invoke('app:quitAndInstall');
},
/**
* 监听更新状态
*/
onUpdateStatus: (callback: (status: { status: string; data?: any }) => void) => {
const subscription = (_event: IpcRendererEvent, data: { status: string; data?: any }) => {
callback(data);
};
ipcRenderer.on('update-status', subscription);
return () => {
ipcRenderer.removeListener('update-status', subscription);
};
},
};
contextBridge.exposeInMainWorld('appApi', appApiHandler);
export type ElectronHandler = typeof electronHandler;
export type DebugApiHandler = typeof debugApiHandler;
export type AIApiHandler = typeof aiApiHandler;
export type AppApiHandler = typeof appApiHandler;