diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3b7de3c..5d5ecf7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 69f3521..8c56acb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/package.json b/package.json index 56ffbc5..e5a8240 100644 --- a/package.json +++ b/package.json @@ -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" } }, diff --git a/src/main/main.ts b/src/main/main.ts index eeb6bca..600be0b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -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 服务 // ============================================ diff --git a/src/main/preload.ts b/src/main/preload.ts index 69ee14d..30f8867 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -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;