Build-View #154
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-View | |
| on: | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 120 | |
| strategy: | |
| matrix: | |
| include: | |
| - os: macos-latest | |
| arch: arm64 | |
| artifact_name: macos-arm64 | |
| - os: macos-15-intel | |
| arch: x64 | |
| artifact_name: macos-intel | |
| - os: windows-latest | |
| arch: x64 | |
| artifact_name: windows-latest | |
| - os: ubuntu-latest | |
| arch: x64 | |
| artifact_name: ubuntu-latest | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v6 | |
| with: | |
| clean: true | |
| # Clean node_modules on self-hosted runner to ensure fresh install | |
| - name: Clean node_modules (self-hosted) | |
| if: contains(matrix.os, 'self-hosted') | |
| run: | | |
| rm -rf node_modules | |
| rm -rf release | |
| - name: Setup Node.js | |
| if: "!contains(matrix.os, 'self-hosted')" | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 20 | |
| - name: Setup Python | |
| if: "!contains(matrix.os, 'self-hosted')" | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.11' | |
| - name: Install Python Dependencies | |
| run: | | |
| python3 -m pip install --upgrade pip | |
| pip3 install uv | |
| - name: Install bun | |
| run: npm install -g bun | |
| - name: Install Dependencies | |
| run: npm install | |
| # Verify Electron installation on macOS | |
| - name: Verify Electron Installation (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| echo "Checking Electron installation..." | |
| ls -la node_modules/electron/dist/ || echo "Electron dist not found" | |
| if [ -d "node_modules/electron/dist/Electron.app" ]; then | |
| echo "✅ Electron.app found" | |
| ls -la "node_modules/electron/dist/Electron.app/Contents/Frameworks/" | head -5 | |
| else | |
| echo "❌ Electron.app NOT found - this will cause build failure" | |
| exit 1 | |
| fi | |
| # Install libfuse2 for Linux AppImage builds | |
| - name: Install libfuse2 (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libfuse2 | |
| # Step for macOS builds with signing | |
| - name: Build Release Files (macOS with signing) | |
| if: runner.os == 'macOS' | |
| timeout-minutes: 90 | |
| run: | | |
| # Increase file descriptor limit to prevent EMFILE errors during signing | |
| # This is needed because electron-builder signs all files recursively, | |
| # and Python venvs contain thousands of files | |
| ulimit -n 65536 || ulimit -n 10240 | |
| echo "File descriptor limit set to: $(ulimit -n)" | |
| npm run build -- --arch ${{ matrix.arch }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CSC_LINK: ${{ secrets.CERT_P12 }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CERT_PASSWORD }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| VITE_BASE_URL: ${{ secrets.VITE_BASE_URL }} | |
| VITE_STACK_PROJECT_ID: ${{ secrets.VITE_STACK_PROJECT_ID }} | |
| VITE_STACK_PUBLISHABLE_CLIENT_KEY: ${{ secrets.VITE_STACK_PUBLISHABLE_CLIENT_KEY }} | |
| VITE_STACK_SECRET_SERVER_KEY: ${{ secrets.VITE_STACK_SECRET_SERVER_KEY }} | |
| USE_NPM_INSTALL_BUN: 'true' | |
| # Step for Windows builds without signing | |
| - name: Build Release Files (Windows without signing) | |
| if: runner.os == 'Windows' | |
| timeout-minutes: 90 | |
| run: npm run build -- --arch ${{ matrix.arch }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VITE_BASE_URL: ${{ secrets.VITE_BASE_URL }} | |
| VITE_STACK_PROJECT_ID: ${{ secrets.VITE_STACK_PROJECT_ID }} | |
| VITE_STACK_PUBLISHABLE_CLIENT_KEY: ${{ secrets.VITE_STACK_PUBLISHABLE_CLIENT_KEY }} | |
| VITE_STACK_SECRET_SERVER_KEY: ${{ secrets.VITE_STACK_SECRET_SERVER_KEY }} | |
| USE_NPM_INSTALL_BUN: 'true' | |
| # Step for Linux builds | |
| - name: Build Release Files (Linux) | |
| if: runner.os == 'Linux' | |
| timeout-minutes: 90 | |
| run: npm run build:linux | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VITE_BASE_URL: ${{ secrets.VITE_BASE_URL }} | |
| VITE_STACK_PROJECT_ID: ${{ secrets.VITE_STACK_PROJECT_ID }} | |
| VITE_STACK_PUBLISHABLE_CLIENT_KEY: ${{ secrets.VITE_STACK_PUBLISHABLE_CLIENT_KEY }} | |
| VITE_STACK_SECRET_SERVER_KEY: ${{ secrets.VITE_STACK_SECRET_SERVER_KEY }} | |
| USE_NPM_INSTALL_BUN: 'true' | |
| # Verify built app contains Electron Framework | |
| - name: Verify Built App (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| echo "Checking built app..." | |
| APP_PATH=$(find release -name "*.app" -type d | head -1) | |
| if [ -n "$APP_PATH" ]; then | |
| echo "Found app at: $APP_PATH" | |
| FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks" | |
| if [ -d "$FRAMEWORKS_PATH/Electron Framework.framework" ]; then | |
| echo "✅ Electron Framework found" | |
| ls -la "$FRAMEWORKS_PATH/" | head -10 | |
| else | |
| echo "❌ Electron Framework NOT found in built app!" | |
| echo "Contents of Frameworks directory:" | |
| ls -la "$FRAMEWORKS_PATH/" 2>/dev/null || echo "Frameworks directory does not exist" | |
| exit 1 | |
| fi | |
| else | |
| echo "No .app found in release directory" | |
| ls -la release/ | |
| fi | |
| - name: Upload Artifact (macOS - dmg only) | |
| if: runner.os == 'macOS' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: release-${{ matrix.artifact_name }}-${{ matrix.arch }} | |
| path: | | |
| release/*.dmg | |
| retention-days: 5 | |
| - name: Upload Artifact (Windows - exe only) | |
| if: runner.os == 'Windows' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: release-${{ matrix.artifact_name }}-${{ matrix.arch }} | |
| path: | | |
| release/*.exe | |
| retention-days: 5 | |
| - name: Upload Artifact (Linux - AppImage only) | |
| if: runner.os == 'Linux' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: release-${{ matrix.artifact_name }}-${{ matrix.arch }} | |
| path: | | |
| release/*.AppImage | |
| retention-days: 5 | |
| merge-release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create directories | |
| run: | | |
| mkdir -p release/mac-arm64 release/mac-intel release/win-x64 release/linux-x64 | |
| - name: Download mac-arm64 artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: release-macos-arm64-arm64 | |
| path: temp-mac-arm64 | |
| - name: Download mac-intel artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: release-macos-intel-x64 | |
| path: temp-mac-intel | |
| - name: Download win-x64 artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: release-windows-latest-x64 | |
| path: temp-win-x64 | |
| - name: Download linux-x64 artifact | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: release-ubuntu-latest-x64 | |
| path: temp-linux-x64 | |
| # Move only dmg files for macOS, exe files for Windows, and AppImage for Linux | |
| - name: Move files to clean folders | |
| shell: bash | |
| run: | | |
| # mac-arm64 - only move dmg files | |
| if [ -d "temp-mac-arm64/release" ]; then | |
| find temp-mac-arm64/release -name "*.dmg" -exec mv {} release/mac-arm64/ \; || true | |
| else | |
| find temp-mac-arm64 -name "*.dmg" -exec mv {} release/mac-arm64/ \; || true | |
| fi | |
| # mac-intel - only move dmg files | |
| if [ -d "temp-mac-intel/release" ]; then | |
| find temp-mac-intel/release -name "*.dmg" -exec mv {} release/mac-intel/ \; || true | |
| else | |
| find temp-mac-intel -name "*.dmg" -exec mv {} release/mac-intel/ \; || true | |
| fi | |
| # win-x64 - only move exe files | |
| if [ -d "temp-win-x64/release" ]; then | |
| find temp-win-x64/release -name "*.exe" -exec mv {} release/win-x64/ \; || true | |
| else | |
| find temp-win-x64 -name "*.exe" -exec mv {} release/win-x64/ \; || true | |
| fi | |
| # linux-x64 - only move AppImage files | |
| if [ -d "temp-linux-x64/release" ]; then | |
| find temp-linux-x64/release -name "*.AppImage" -exec mv {} release/linux-x64/ \; || true | |
| else | |
| find temp-linux-x64 -name "*.AppImage" -exec mv {} release/linux-x64/ \; || true | |
| fi | |
| # Extract version for test builds | |
| - name: Extract version | |
| id: version | |
| run: | | |
| # Create a version using timestamp for test builds | |
| VERSION="test-$(date +%Y%m%d-%H%M%S)" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Extracted version for test build: $VERSION" | |
| # Configure AWS credentials (skipped when AWS secrets are not configured) | |
| - name: Configure AWS credentials | |
| id: aws-check | |
| if: env.AWS_ACCESS_KEY_ID != '' | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ secrets.AWS_REGION }} | |
| # Upload to S3 - test directory | |
| - name: Upload to S3 (test build) | |
| if: steps.aws-check.outcome == 'success' | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| BUCKET="${{ secrets.AWS_S3_BUCKET }}" | |
| echo "Uploading test build $VERSION to S3 bucket: $BUCKET" | |
| declare -a targets=( | |
| "release/mac-arm64 mac-arm64" | |
| "release/mac-intel mac-intel" | |
| "release/win-x64 win-x64" | |
| "release/linux-x64 linux-x64" | |
| ) | |
| for t in "${targets[@]}"; do | |
| src_dir="${t%% *}" | |
| path_suffix="${t##* }" | |
| if [ -d "$src_dir" ] && [ "$(ls -A "$src_dir")" ]; then | |
| echo "Uploading $path_suffix files..." | |
| aws s3 sync "$src_dir/" "s3://$BUCKET/test-builds/$VERSION/$path_suffix/" \ | |
| --content-type "binary/octet-stream" \ | |
| --metadata-directive REPLACE \ | |
| --cache-control "public, max-age=300" | |
| fi | |
| done | |
| echo "Test build $VERSION uploaded successfully" | |
| # Generate download URLs | |
| - name: Generate download URLs | |
| if: steps.aws-check.outcome == 'success' | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| BUCKET="${{ secrets.AWS_S3_BUCKET }}" | |
| REGION="${{ secrets.AWS_REGION }}" | |
| # Determine S3 endpoint based on region | |
| if [[ "$REGION" == cn-* ]]; then | |
| ENDPOINT="s3.$REGION.amazonaws.com.cn" | |
| else | |
| ENDPOINT="s3.$REGION.amazonaws.com" | |
| fi | |
| echo "================================" | |
| echo "Test Build Download URLs" | |
| echo "Build ID: $VERSION" | |
| echo "================================" | |
| echo "" | |
| # Check which platforms exist and show URLs | |
| if [ -d "release/mac-arm64" ] && [ "$(ls -A release/mac-arm64)" ]; then | |
| echo "macOS (ARM64): https://$BUCKET.$ENDPOINT/test-builds/$VERSION/mac-arm64/" | |
| fi | |
| if [ -d "release/mac-intel" ] && [ "$(ls -A release/mac-intel)" ]; then | |
| echo "macOS (Intel): https://$BUCKET.$ENDPOINT/test-builds/$VERSION/mac-intel/" | |
| fi | |
| if [ -d "release/win-x64" ] && [ "$(ls -A release/win-x64)" ]; then | |
| echo "Windows (x64): https://$BUCKET.$ENDPOINT/test-builds/$VERSION/win-x64/" | |
| fi | |
| if [ -d "release/linux-x64" ] && [ "$(ls -A release/linux-x64)" ]; then | |
| echo "Linux (x64): https://$BUCKET.$ENDPOINT/test-builds/$VERSION/linux-x64/" | |
| fi | |
| echo "" | |
| echo "⚠️ Note: Test builds are stored in 'test-builds/' directory" | |
| echo "⚠️ Remember to delete old test builds to save storage costs" | |
| echo "" | |
| echo "To delete this test build:" | |
| echo "aws s3 rm s3://$BUCKET/test-builds/$VERSION/ --recursive --region $REGION" | |
| echo "================================" |