mirror of
https://github.com/AmintaCCCP/GithubStarsManager.git
synced 2025-11-25 02:34:54 +08:00
417 lines
14 KiB
YAML
417 lines
14 KiB
YAML
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
|
|
|
|
- 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
|
|
shell: bash
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const sharp = require('sharp');
|
|
const path = require('path');
|
|
|
|
async function generateIcons() {
|
|
const buildDir = 'build';
|
|
if (!fs.existsSync(buildDir)) {
|
|
fs.mkdirSync(buildDir, { recursive: true });
|
|
}
|
|
|
|
// Look for source icon in common locations
|
|
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('No source icon found, creating default icon...');
|
|
// Create a simple colored square as default
|
|
await sharp({
|
|
create: {
|
|
width: 512,
|
|
height: 512,
|
|
channels: 4,
|
|
background: { r: 59, g: 130, b: 246, alpha: 1 }
|
|
}
|
|
})
|
|
.png()
|
|
.toFile('build/icon-512x512.png');
|
|
|
|
// Copy for other formats
|
|
fs.copyFileSync('build/icon-512x512.png', 'build/icon.png');
|
|
} else {
|
|
console.log('Using source icon:', sourceIcon);
|
|
|
|
// Generate PNG icons for Linux
|
|
await sharp(sourceIcon)
|
|
.resize(512, 512)
|
|
.png()
|
|
.toFile('build/icon-512x512.png');
|
|
|
|
await sharp(sourceIcon)
|
|
.resize(256, 256)
|
|
.png()
|
|
.toFile('build/icon-256x256.png');
|
|
|
|
// Copy original for general use
|
|
fs.copyFileSync(sourceIcon, 'build/icon.png');
|
|
}
|
|
|
|
console.log('Icon files generated successfully');
|
|
}
|
|
|
|
generateIcons().catch(console.error);
|
|
"
|
|
|
|
- name: Generate Windows ICO file
|
|
if: matrix.os == 'windows-latest'
|
|
shell: bash
|
|
run: |
|
|
# Install imagemagick for ICO conversion
|
|
if command -v magick >/dev/null 2>&1; then
|
|
magick build/icon.png -define icon:auto-resize=256,128,64,48,32,16 build/icon.ico
|
|
elif command -v convert >/dev/null 2>&1; then
|
|
convert build/icon.png -define icon:auto-resize=256,128,64,48,32,16 build/icon.ico
|
|
else
|
|
echo "ImageMagick not available, using PNG as fallback"
|
|
cp build/icon-512x512.png build/icon.ico
|
|
fi
|
|
|
|
- name: Generate macOS ICNS file
|
|
if: matrix.os == 'macos-latest'
|
|
shell: bash
|
|
run: |
|
|
# Create iconset directory
|
|
mkdir -p build/icon.iconset
|
|
|
|
# Generate different sizes for ICNS
|
|
node -e "
|
|
const sharp = require('sharp');
|
|
const fs = require('fs');
|
|
|
|
async function generateIconSet() {
|
|
const sizes = [16, 32, 64, 128, 256, 512, 1024];
|
|
const sourceIcon = fs.existsSync('build/icon.png') ? 'build/icon.png' : 'build/icon-512x512.png';
|
|
|
|
for (const size of sizes) {
|
|
await sharp(sourceIcon)
|
|
.resize(size, size)
|
|
.png()
|
|
.toFile(\`build/icon.iconset/icon_\${size}x\${size}.png\`);
|
|
|
|
if (size <= 512) {
|
|
await sharp(sourceIcon)
|
|
.resize(size * 2, size * 2)
|
|
.png()
|
|
.toFile(\`build/icon.iconset/icon_\${size}x\${size}@2x.png\`);
|
|
}
|
|
}
|
|
|
|
console.log('IconSet generated');
|
|
}
|
|
|
|
generateIconSet().catch(console.error);
|
|
"
|
|
|
|
# Convert to ICNS using iconutil (macOS only)
|
|
if command -v iconutil >/dev/null 2>&1; then
|
|
iconutil -c icns build/icon.iconset -o build/icon.icns
|
|
else
|
|
echo "iconutil not available, using PNG as fallback"
|
|
cp build/icon-512x512.png build/icon.icns
|
|
fi
|
|
|
|
- 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 } = require(\\'electron\\');\\n' +
|
|
'const path = require(\\'path\\');\\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: true\\n' +
|
|
' },\\n' +
|
|
' icon: path.join(__dirname, \\'../public/icon.svg\\'),\\n' +
|
|
' titleBarStyle: process.platform === \\'darwin\\' ? \\'hiddenInset\\' : \\'default\\',\\n' +
|
|
' show: false\\n' +
|
|
' });\\n\\n' +
|
|
' if (isDev) {\\n' +
|
|
' mainWindow.loadURL(\\'http://localhost:5173\\');\\n' +
|
|
' mainWindow.webContents.openDevTools();\\n' +
|
|
' } else {\\n' +
|
|
' mainWindow.loadFile(path.join(__dirname, \\'../dist/index.html\\'));\\n' +
|
|
' }\\n\\n' +
|
|
' mainWindow.once(\\'ready-to-show\\', () => {\\n' +
|
|
' mainWindow.show();\\n' +
|
|
' });\\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(createWindow);\\n\\n' +
|
|
'app.on(\\'window-all-closed\\', () => {\\n' +
|
|
' if (process.platform !== \\'darwin\\') {\\n' +
|
|
' app.quit();\\n' +
|
|
' }\\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.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'
|
|
},
|
|
files: [
|
|
'dist/**/*',
|
|
'electron/**/*',
|
|
'node_modules/**/*',
|
|
'package.json'
|
|
]
|
|
};
|
|
|
|
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: 'nsis',
|
|
icon: 'build/icon.ico'
|
|
};
|
|
packageJson.build.nsis = {
|
|
oneClick: false,
|
|
allowToChangeInstallationDirectory: true
|
|
};
|
|
} else if ('${{ matrix.os }}' === 'macos-latest') {
|
|
packageJson.build.mac = {
|
|
target: 'dmg',
|
|
icon: 'build/icon.icns',
|
|
category: 'public.app-category.productivity'
|
|
};
|
|
} 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: Build Electron app
|
|
run: npm run dist
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CI: true
|
|
|
|
- name: List build output
|
|
run: |
|
|
echo "Build output directory contents:"
|
|
ls -la release/ || echo "Release directory not found"
|
|
find . -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.AppImage" || echo "No build artifacts found"
|
|
|
|
- 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
|
|
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
|
|
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 }} |