Merge pull request #63 from AmintaCCCP/fix/ai-reasoning-support #180
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Desktop App | |
| on: | |
| push: | |
| branches: [ main, master ] | |
| tags: [ 'v*' ] | |
| pull_request: | |
| branches: [ main, master ] | |
| workflow_dispatch: | |
| jobs: | |
| build: | |
| runs-on: ${{ matrix.os }} | |
| continue-on-error: false | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '18' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build web app | |
| run: npm run build | |
| # env: | |
| # VITE_BASE_PATH: './' | |
| - name: Verify and fix web build | |
| shell: bash | |
| run: | | |
| echo "Checking if dist directory exists and contains files:" | |
| if [ -d "dist" ] && [ -f "dist/index.html" ]; then | |
| echo "✓ dist directory and index.html found" | |
| echo "Contents of dist directory:" | |
| ls -la dist/ | head -10 | |
| # 检查 index.html 中的路径并修复 | |
| echo "Checking and fixing asset paths in index.html..." | |
| if [ -f "dist/index.html" ]; then | |
| # 确保所有资源路径都是相对路径 | |
| sed -i.bak 's|href="/|href="./|g' dist/index.html | |
| sed -i.bak 's|src="/|src="./|g' dist/index.html | |
| sed -i.bak 's|href="\([^"]*\)"|href="./\1"|g' dist/index.html | |
| sed -i.bak 's|src="\([^"]*\)"|src="./\1"|g' dist/index.html | |
| # 移除备份文件 | |
| rm -f dist/index.html.bak | |
| echo "✓ Asset paths fixed in index.html" | |
| fi | |
| else | |
| echo "Creating fallback dist directory and index.html" | |
| mkdir -p dist | |
| cp templates/fallback-index.html dist/index.html | |
| echo "✓ Fallback index.html created from template" | |
| fi | |
| # 显示最终的 index.html 内容(前几行) | |
| echo "Final index.html content (first 10 lines):" | |
| head -10 dist/index.html || echo "Could not read index.html" | |
| - name: Install sharp for icon generation | |
| run: npm install sharp --save-dev | |
| - name: Create build directory | |
| shell: bash | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| if (!fs.existsSync('build')) { | |
| fs.mkdirSync('build', { recursive: true }); | |
| } | |
| console.log('Build directory created'); | |
| " | |
| - name: Generate icons and app resources | |
| shell: bash | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| function generateIcons() { | |
| const buildDir = 'build'; | |
| if (!fs.existsSync(buildDir)) { | |
| fs.mkdirSync(buildDir, { recursive: true }); | |
| } | |
| // Look for source icon in common locations (优先使用 assets/icon.png) | |
| let sourceIcon = null; | |
| const possiblePaths = [ | |
| 'assets/icon.png', | |
| 'public/icon.png', | |
| 'src/assets/icon.png', | |
| 'icon.png' | |
| ]; | |
| for (const iconPath of possiblePaths) { | |
| if (fs.existsSync(iconPath)) { | |
| sourceIcon = iconPath; | |
| break; | |
| } | |
| } | |
| if (sourceIcon) { | |
| console.log('Using source icon:', sourceIcon); | |
| fs.copyFileSync(sourceIcon, 'build/icon.png'); | |
| fs.copyFileSync(sourceIcon, 'build/icon-512x512.png'); | |
| } else { | |
| console.log('Creating default application icon'); | |
| // Create a better default icon | |
| const iconSvg = \`<svg width=\"512\" height=\"512\" xmlns=\"http://www.w3.org/2000/svg\"> | |
| <defs> | |
| <linearGradient id=\"grad1\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\"> | |
| <stop offset=\"0%\" style=\"stop-color:#667eea;stop-opacity:1\" /> | |
| <stop offset=\"100%\" style=\"stop-color:#764ba2;stop-opacity:1\" /> | |
| </linearGradient> | |
| </defs> | |
| <rect width=\"512\" height=\"512\" fill=\"url(#grad1)\" rx=\"64\"/> | |
| <text x=\"256\" y=\"280\" text-anchor=\"middle\" fill=\"white\" font-size=\"120\" font-family=\"Arial, sans-serif\" font-weight=\"bold\">⭐</text> | |
| <text x=\"256\" y=\"380\" text-anchor=\"middle\" fill=\"white\" font-size=\"32\" font-family=\"Arial, sans-serif\">GitHub</text> | |
| <text x=\"256\" y=\"420\" text-anchor=\"middle\" fill=\"white\" font-size=\"32\" font-family=\"Arial, sans-serif\">Stars</text> | |
| </svg>\`; | |
| fs.writeFileSync('build/icon.svg', iconSvg); | |
| } | |
| console.log('Icon files prepared successfully'); | |
| } | |
| generateIcons(); | |
| " | |
| - name: Generate Windows ICO file | |
| if: matrix.os == 'windows-latest' | |
| shell: bash | |
| run: | | |
| # For Windows, electron-builder can handle PNG to ICO conversion | |
| if [ -f "build/icon.png" ]; then | |
| cp build/icon.png build/icon.ico | |
| else | |
| echo "No icon file found, electron-builder will use default" | |
| fi | |
| - name: Generate macOS ICNS file | |
| if: matrix.os == 'macos-latest' | |
| shell: bash | |
| run: | | |
| # For macOS, electron-builder can handle PNG to ICNS conversion | |
| if [ -f "build/icon.png" ]; then | |
| cp build/icon.png build/icon.icns | |
| else | |
| echo "No icon file found, electron-builder will use default" | |
| fi | |
| - name: Install system dependencies (Linux) | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libnss3-dev libatk-bridge2.0-dev libdrm2 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libxss1 libasound2-dev | |
| - name: Install Electron dependencies | |
| run: npm install --save-dev electron electron-builder | |
| - name: Setup Windows build environment | |
| if: matrix.os == 'windows-latest' | |
| run: | | |
| # Install Windows SDK components if needed | |
| echo "Setting up Windows build environment" | |
| - name: Create Electron main process | |
| shell: bash | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| if (!fs.existsSync('electron')) { | |
| fs.mkdirSync('electron', { recursive: true }); | |
| } | |
| const mainJsContent = 'const { app, BrowserWindow, Menu, shell, globalShortcut } = require(\\'electron\\');\\n' + | |
| 'const path = require(\\'path\\');\\n' + | |
| 'const fs = require(\\'fs\\');\\n' + | |
| 'const isDev = process.env.NODE_ENV === \\'development\\';\\n\\n' + | |
| 'let mainWindow;\\n\\n' + | |
| 'function createWindow() {\\n' + | |
| ' mainWindow = new BrowserWindow({\\n' + | |
| ' width: 1200,\\n' + | |
| ' height: 800,\\n' + | |
| ' minWidth: 800,\\n' + | |
| ' minHeight: 600,\\n' + | |
| ' webPreferences: {\\n' + | |
| ' nodeIntegration: false,\\n' + | |
| ' contextIsolation: true,\\n' + | |
| ' enableRemoteModule: false,\\n' + | |
| ' webSecurity: false,\\n' + | |
| ' allowRunningInsecureContent: true,\\n' + | |
| ' devTools: true // 生产环境也允许 DevTools 便于排障\\n' + | |
| ' },\\n' + | |
| ' icon: path.join(__dirname, \\'../build/icon.png\\'),\\n' + | |
| ' titleBarStyle: \\'default\\', // 使用默认标题栏,避免重叠问题\\n' + | |
| ' show: false,\\n' + | |
| ' autoHideMenuBar: false, // 显示菜单栏,确保编辑快捷键行为一致\\n' + | |
| ' frame: true, // 保持窗口框架\\n' + | |
| ' backgroundColor: \\'#ffffff\\', // 设置背景色,避免白屏闪烁\\n' + | |
| ' titleBarOverlay: false, // 禁用标题栏覆盖\\n' + | |
| ' trafficLightPosition: { x: 20, y: 20 } // macOS 交通灯按钮位置\\n' + | |
| ' });\\n\\n' + | |
| ' // 添加错误处理和加载事件\\n' + | |
| ' mainWindow.webContents.on(\\'did-fail-load\\', (event, errorCode, errorDescription, validatedURL) => {\\n' + | |
| ' console.error(\\'Failed to load:\\', errorCode, errorDescription, validatedURL);\\n' + | |
| ' // 如果主页面加载失败,尝试加载 fallback 页面\\n' + | |
| ' const fallbackPath = path.join(__dirname, \\'../dist/index.html\\');\\n' + | |
| ' if (fs.existsSync(fallbackPath)) {\\n' + | |
| ' console.log(\\'Loading fallback page:\\', fallbackPath);\\n' + | |
| ' mainWindow.loadFile(fallbackPath);\\n' + | |
| ' }\\n' + | |
| ' });\\n\\n' + | |
| ' mainWindow.webContents.on(\\'dom-ready\\', () => {\\n' + | |
| ' if (isDev) console.log(\\'DOM ready\\');\\n' + | |
| ' // 注入一些基础样式,防止白屏\\n' + | |
| ' mainWindow.webContents.insertCSS(\\'body { background-color: #ffffff; }\\');\\n' + | |
| ' });\\n\\n' + | |
| ' mainWindow.webContents.on(\\'did-finish-load\\', () => {\\n' + | |
| ' if (isDev) console.log(\\'Page finished loading\\');\\n' + | |
| ' // 页面加载完成后显示窗口\\n' + | |
| ' if (!mainWindow.isVisible()) {\\n' + | |
| ' mainWindow.show();\\n' + | |
| ' }\\n' + | |
| ' });\\n\\n' + | |
| ' if (isDev) {\\n' + | |
| ' mainWindow.loadURL(\\'http://localhost:5173\\');\\n' + | |
| ' mainWindow.webContents.openDevTools();\\n' + | |
| ' } else {\\n' + | |
| ' // 生产环境:尝试多个可能的路径\\n' + | |
| ' const possiblePaths = [\\n' + | |
| ' path.join(__dirname, \\'../dist/index.html\\'),\\n' + | |
| ' path.join(process.resourcesPath, \\'app.asar/dist/index.html\\'),\\n' + | |
| ' path.join(process.resourcesPath, \\'app/dist/index.html\\'),\\n' + | |
| ' path.join(process.resourcesPath, \\'dist/index.html\\'),\\n' + | |
| ' path.join(__dirname, \\'../build/index.html\\')\\n' + | |
| ' ];\\n\\n' + | |
| ' let indexPath = null;\\n' + | |
| ' for (const testPath of possiblePaths) {\\n' + | |
| ' try {\\n' + | |
| ' if (fs.existsSync(testPath)) {\\n' + | |
| ' indexPath = testPath;\\n' + | |
| ' break;\\n' + | |
| ' }\\n' + | |
| ' } catch (error) {\\n' + | |
| ' // 忽略文件系统错误,继续尝试下一个路径\\n' + | |
| ' continue;\\n' + | |
| ' }\\n' + | |
| ' }\\n\\n' + | |
| ' if (indexPath) {\\n' + | |
| ' console.log(\\'Loading application from:\\', indexPath);\\n' + | |
| ' mainWindow.loadFile(indexPath).catch(error => {\\n' + | |
| ' console.error(\\'Failed to load file:\\', error);\\n' + | |
| ' // 加载失败时显示错误页面\\n' + | |
| ' mainWindow.loadURL(\\'data:text/html,<h1>Application Load Error</h1><p>Could not load the main application. Please restart the app.</p>\\');\\n' + | |
| ' });\\n' + | |
| ' } else {\\n' + | |
| ' console.error(\\'Could not find index.html in any expected location\\');\\n' + | |
| ' console.log(\\'Checked paths:\\', possiblePaths);\\n' + | |
| ' console.log(\\'Current directory:\\', __dirname);\\n' + | |
| ' console.log(\\'Process resources path:\\', process.resourcesPath);\\n' + | |
| ' // 显示详细的错误信息\\n' + | |
| ' const errorHtml = \\'<h1>Application Not Found</h1><p>Could not locate the application files.</p><p>Please reinstall the application.</p>\\';\\n' + | |
| ' mainWindow.loadURL(\\'data:text/html,\\' + encodeURIComponent(errorHtml));\\n' + | |
| ' }\\n' + | |
| ' }\\n\\n' + | |
| ' mainWindow.once(\\'ready-to-show\\', () => {\\n' + | |
| ' mainWindow.show();\\n' + | |
| ' });\\n\\n' + | |
| ' // 提供稳定的菜单与编辑快捷键(生产环境)\n' + | |
| ' const menuTemplate = process.platform === \'darwin\' ? [\n' + | |
| ' {\n' + | |
| ' label: app.name,\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'about\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'services\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'hide\' },\n' + | |
| ' { role: \'hideOthers\' },\n' + | |
| ' { role: \'unhide\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'quit\' }\n' + | |
| ' ]\n' + | |
| ' },\n' + | |
| ' {\n' + | |
| ' label: \'Edit\',\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'undo\' },\n' + | |
| ' { role: \'redo\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'cut\' },\n' + | |
| ' { role: \'copy\' },\n' + | |
| ' { role: \'paste\' },\n' + | |
| ' { role: \'selectAll\' }\n' + | |
| ' ]\n' + | |
| ' },\n' + | |
| ' {\n' + | |
| ' label: \'View\',\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'reload\' },\n' + | |
| ' { role: \'forceReload\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'toggleDevTools\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'resetZoom\' },\n' + | |
| ' { role: \'zoomIn\' },\n' + | |
| ' { role: \'zoomOut\' },\n' + | |
| ' { role: \'togglefullscreen\' }\n' + | |
| ' ]\n' + | |
| ' },\n' + | |
| ' {\n' + | |
| ' label: \'Window\',\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'minimize\' },\n' + | |
| ' { role: \'close\' }\n' + | |
| ' ]\n' + | |
| ' }\n' + | |
| ' ] : [\n' + | |
| ' {\n' + | |
| ' label: \'Edit\',\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'undo\' },\n' + | |
| ' { role: \'redo\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'cut\' },\n' + | |
| ' { role: \'copy\' },\n' + | |
| ' { role: \'paste\' },\n' + | |
| ' { role: \'selectAll\' }\n' + | |
| ' ]\n' + | |
| ' },\n' + | |
| ' {\n' + | |
| ' label: \'View\',\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'reload\' },\n' + | |
| ' { role: \'forceReload\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'toggleDevTools\' },\n' + | |
| ' { type: \'separator\' },\n' + | |
| ' { role: \'resetZoom\' },\n' + | |
| ' { role: \'zoomIn\' },\n' + | |
| ' { role: \'zoomOut\' },\n' + | |
| ' { role: \'togglefullscreen\' }\n' + | |
| ' ]\n' + | |
| ' },\n' + | |
| ' {\n' + | |
| ' label: \'Window\',\n' + | |
| ' submenu: [\n' + | |
| ' { role: \'minimize\' },\n' + | |
| ' { role: \'close\' }\n' + | |
| ' ]\n' + | |
| ' }\n' + | |
| ' ];\n' + | |
| ' Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate));\n' + | |
| '\n' + | |
| ' mainWindow.webContents.setWindowOpenHandler(({ url }) => {\\n' + | |
| ' shell.openExternal(url);\\n' + | |
| ' return { action: \\'deny\\' };\\n' + | |
| ' });\\n\\n' + | |
| ' mainWindow.on(\\'closed\\', () => {\\n' + | |
| ' mainWindow = null;\\n' + | |
| ' });\\n' + | |
| '}\\n\\n' + | |
| 'app.whenReady().then(() => {\\n' + | |
| ' createWindow();\\n' + | |
| ' globalShortcut.register(\\'CommandOrControl+Shift+I\\', () => {\\n' + | |
| ' const focused = BrowserWindow.getFocusedWindow();\\n' + | |
| ' if (focused && !focused.isDestroyed()) {\\n' + | |
| ' focused.webContents.toggleDevTools();\\n' + | |
| ' }\\n' + | |
| ' });\\n' + | |
| '});\\n\\n' + | |
| 'app.on(\\'window-all-closed\\', () => {\\n' + | |
| ' if (process.platform !== \\'darwin\\') {\\n' + | |
| ' app.quit();\\n' + | |
| ' }\\n' + | |
| '});\\n\\n' + | |
| 'app.on(\\'will-quit\\', () => {\\n' + | |
| ' globalShortcut.unregisterAll();\\n' + | |
| '});\\n\\n' + | |
| 'app.on(\\'activate\\', () => {\\n' + | |
| ' if (BrowserWindow.getAllWindows().length === 0) {\\n' + | |
| ' createWindow();\\n' + | |
| ' }\\n' + | |
| '});'; | |
| fs.writeFileSync('electron/main.js', mainJsContent); | |
| const electronPackageJson = { | |
| name: 'github-stars-manager-desktop', | |
| version: '1.0.0', | |
| description: 'GitHub Stars Manager Desktop App', | |
| main: 'main.js', | |
| author: 'GitHub Stars Manager', | |
| license: 'MIT' | |
| }; | |
| fs.writeFileSync('electron/package.json', JSON.stringify(electronPackageJson, null, 2)); | |
| console.log('Electron files created successfully'); | |
| " | |
| - name: Update main package.json for Electron | |
| shell: bash | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| packageJson.main = 'electron/main.js'; | |
| packageJson.homepage = './'; | |
| packageJson.scripts = packageJson.scripts || {}; | |
| // 确保构建脚本使用正确的基础路径 | |
| packageJson.scripts.build = 'vite build --base=./'; | |
| packageJson.scripts['build:electron'] = 'vite build --base=./ && electron-builder'; | |
| packageJson.scripts.dist = 'electron-builder --publish=never'; | |
| // Ensure proper base path for Electron | |
| if (!packageJson.build) packageJson.build = {}; | |
| packageJson.build.extraMetadata = { | |
| main: 'electron/main.js' | |
| }; | |
| packageJson.scripts.electron = 'electron .'; | |
| packageJson.scripts['electron-dev'] = 'NODE_ENV=development electron .'; | |
| packageJson.scripts.dist = 'electron-builder'; | |
| packageJson.build = { | |
| appId: 'com.github-stars-manager.app', | |
| productName: 'GitHub Stars Manager', | |
| directories: { | |
| output: 'release', | |
| buildResources: 'build' | |
| }, | |
| files: [ | |
| 'dist/**/*', | |
| 'build/**/*', | |
| 'electron/**/*', | |
| 'node_modules/**/*', | |
| 'package.json', | |
| '!node_modules/.cache/**/*', | |
| '!**/*.map' | |
| ], | |
| extraResources: [ | |
| { | |
| from: 'dist', | |
| to: 'dist', | |
| filter: ['**/*'] | |
| } | |
| ], | |
| compression: 'normal', | |
| asar: false, // 暂时禁用 ASAR 以简化调试 | |
| publish: null // 确保不会尝试发布 | |
| }; | |
| fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2)); | |
| console.log('Package.json updated successfully'); | |
| " | |
| - name: Configure platform-specific build settings | |
| shell: bash | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| if ('${{ matrix.os }}' === 'windows-latest') { | |
| packageJson.build.win = { | |
| target: [ | |
| { | |
| target: 'nsis', | |
| arch: ['x64'] | |
| } | |
| ], | |
| icon: 'build/icon.png', | |
| requestedExecutionLevel: 'asInvoker' | |
| }; | |
| packageJson.build.nsis = { | |
| oneClick: false, | |
| allowToChangeInstallationDirectory: true, | |
| createDesktopShortcut: true, | |
| createStartMenuShortcut: true, | |
| shortcutName: 'GitHub Stars Manager' | |
| }; | |
| } else if ('${{ matrix.os }}' === 'macos-latest') { | |
| packageJson.build.mac = { | |
| target: [ | |
| { | |
| target: 'dmg', | |
| arch: ['x64', 'arm64'] | |
| } | |
| ], | |
| icon: 'build/icon.png', | |
| category: 'public.app-category.productivity', | |
| hardenedRuntime: true, | |
| gatekeeperAssess: false, | |
| identity: null | |
| }; | |
| packageJson.build.dmg = { | |
| title: 'GitHub Stars Manager', | |
| icon: 'build/icon.png' | |
| }; | |
| } else { | |
| packageJson.build.linux = { | |
| target: 'AppImage', | |
| icon: 'build/icon-512x512.png', | |
| category: 'Office' | |
| }; | |
| } | |
| fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2)); | |
| console.log('Platform-specific settings configured'); | |
| " | |
| - name: Debug before build | |
| shell: bash | |
| run: | | |
| echo "=== Pre-Build Debug Information ===" | |
| echo "Current directory contents:" | |
| ls -la | |
| echo "" | |
| echo "Dist directory contents:" | |
| ls -la dist/ || echo "No dist directory" | |
| echo "" | |
| echo "Electron directory contents:" | |
| ls -la electron/ || echo "No electron directory" | |
| echo "" | |
| echo "Build directory contents:" | |
| ls -la build/ || echo "No build directory" | |
| echo "" | |
| echo "Package.json build config:" | |
| node -e "console.log(JSON.stringify(require('./package.json').build, null, 2))" | |
| echo "" | |
| echo "Checking dist/index.html content:" | |
| if [ -f "dist/index.html" ]; then | |
| echo "First 20 lines of dist/index.html:" | |
| head -20 dist/index.html | |
| else | |
| echo "dist/index.html not found" | |
| fi | |
| echo "" | |
| echo "Setting proper permissions:" | |
| chmod -R 755 dist/ || echo "Could not set dist permissions" | |
| chmod -R 755 electron/ || echo "Could not set electron permissions" | |
| chmod -R 755 build/ || echo "Could not set build permissions" | |
| - name: Build Electron app | |
| run: npm run dist | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CI: true | |
| DEBUG: electron-builder | |
| # Linux 特定环境变量 | |
| ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true | |
| # macOS signing and notarization | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| - name: List build output | |
| shell: bash | |
| run: | | |
| echo "Build output directory contents:" | |
| ls -la release/ || echo "Release directory not found" | |
| echo "" | |
| echo "Looking for build artifacts:" | |
| find . -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.AppImage" || echo "No build artifacts found" | |
| echo "" | |
| if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then | |
| echo "Linux specific checks:" | |
| echo "AppImage files:" | |
| find . -name "*.AppImage" -exec ls -la {} \; || echo "No AppImage files found" | |
| echo "Checking AppImage permissions:" | |
| find . -name "*.AppImage" -exec file {} \; || echo "No AppImage files to check" | |
| fi | |
| - name: Test Electron app (Linux only) | |
| if: matrix.os == 'ubuntu-latest' | |
| shell: bash | |
| run: | | |
| # Install xvfb for headless testing | |
| sudo apt-get update | |
| sudo apt-get install -y xvfb | |
| # Test if the app can start (will exit quickly but should not crash) | |
| echo "Testing Electron app startup..." | |
| timeout 10s xvfb-run -a npm run electron || echo "App test completed (timeout expected)" | |
| - name: Upload artifacts (Windows) | |
| if: matrix.os == 'windows-latest' && success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-app | |
| path: | | |
| release/*.exe | |
| release/*.msi | |
| if-no-files-found: ignore | |
| - name: Upload artifacts (macOS) | |
| if: matrix.os == 'macos-latest' && success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-app | |
| path: release/*.dmg | |
| if-no-files-found: ignore | |
| - name: Upload artifacts (Linux) | |
| if: matrix.os == 'ubuntu-latest' && success() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-app | |
| path: release/*.AppImage | |
| if-no-files-found: ignore | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: startsWith(github.ref, 'refs/tags/v') && always() | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| - name: List downloaded files | |
| shell: bash | |
| run: | | |
| echo "Downloaded files structure:" | |
| find . -type f | head -20 | |
| echo "Looking for build artifacts:" | |
| find . -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.AppImage" | head -20 | |
| - name: Prepare release files | |
| shell: bash | |
| run: | | |
| mkdir -p release-files | |
| # Copy all found artifacts to a single directory | |
| find . -name "*.exe" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find . -name "*.msi" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find . -name "*.dmg" -exec cp {} release-files/ \; 2>/dev/null || true | |
| find . -name "*.AppImage" -exec cp {} release-files/ \; 2>/dev/null || true | |
| echo "Files prepared for release:" | |
| ls -la release-files/ || echo "No files found" | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| files: release-files/* | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| fail_on_unmatched_files: false | |
| body: | | |
| ## Desktop Application Release | |
| This release includes desktop applications for multiple platforms. | |
| ### Available Downloads: | |
| - Windows: `.exe` installer | |
| - macOS: `.dmg` installer | |
| - Linux: `.AppImage` portable executable | |
| Note: Some platform builds may not be available if they failed during the build process. | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |