name: Build Electron Desktop App on: push: tags: - "v*" workflow_dispatch: inputs: version: description: "Release version (e.g., v1.6.8)" required: true type: string permissions: contents: write id-token: write packages: write jobs: validate: name: Validate version runs-on: ubuntu-latest outputs: version: ${{ steps.validate.outputs.version }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Validate version format id: validate run: | if [[ "${{ github.event_name }}" == "push" ]]; then VERSION="${GITHUB_REF#refs/tags/}" else VERSION="${{ inputs.version }}" fi if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: Invalid version format. Expected: v1.6.8" exit 1 fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "✓ Valid version: $VERSION" build: name: Build Electron (${{ matrix.platform }}) needs: validate runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - platform: windows runner: windows-latest target: win ext: .exe - platform: macos-intel runner: macos-15-intel target: mac-x64 ext: .dmg - platform: macos-arm64 runner: macos-latest target: mac-arm64 ext: -arm64.dmg - platform: linux runner: ubuntu-latest target: linux ext: .AppImage deb_ext: .deb steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm - name: Cache node_modules uses: actions/cache@v4 with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install dependencies run: npm ci - name: Build Next.js standalone env: JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation run: npm run build - name: Sync version in electron/package.json shell: bash run: | VERSION="${{ needs.validate.outputs.version }}" VERSION_NO_V="${VERSION#v}" node -e " const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('electron/package.json')); pkg.version = '$VERSION_NO_V'; fs.writeFileSync('electron/package.json', JSON.stringify(pkg, null, 2) + '\\n'); " echo "✓ electron/package.json version set to $VERSION_NO_V" - name: Install fpm (Linux .deb packaging tool) if: matrix.platform == 'linux' run: sudo gem install fpm --no-document - name: Install Electron dependencies working-directory: electron run: npm install --no-audit --no-fund - name: Build Electron for ${{ matrix.platform }} working-directory: electron env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm run build:${{ matrix.target }} - name: Collect installers shell: bash run: | mkdir -p release-assets cd electron/dist-electron # Copy only installer files for this platform for file in *${{ matrix.ext }}; do [ -f "$file" ] && cp "$file" ../../release-assets/ done # Linux: also copy .deb package if [ "${{ matrix.platform }}" = "linux" ]; then for file in *.deb; do [ -f "$file" ] && cp "$file" ../../release-assets/ done fi # Windows: also copy portable standalone exe as OmniRoute.exe if [ "${{ matrix.platform }}" = "windows" ]; then for file in *.exe; do # Skip the NSIS installer (contains "Setup") case "$file" in *Setup*) continue ;; esac [ -f "$file" ] && cp "$file" "../../release-assets/OmniRoute.exe" && break done fi - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: electron-${{ matrix.platform }} path: release-assets/ release: name: Create Release needs: [validate, build] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: release-assets merge-multiple: true - name: Create source archives run: | # Create source code archives (excluding dev dependencies and build artifacts) export TARBALL="OmniRoute-${{ needs.validate.outputs.version }}.source.tar.gz" export ZIPBALL="OmniRoute-${{ needs.validate.outputs.version }}.source.zip" # Use git archive for clean source export git archive --format=tar.gz --prefix=OmniRoute-${{ needs.validate.outputs.version }}/ HEAD -o "release-assets/$TARBALL" git archive --format=zip --prefix=OmniRoute-${{ needs.validate.outputs.version }}/ HEAD -o "release-assets/$ZIPBALL" echo "✓ Created source archives:" ls -lh "release-assets/$TARBALL" "release-assets/$ZIPBALL" - name: List release files run: ls -la release-assets/ - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.validate.outputs.version }} draft: false prerelease: false generate_release_notes: true fail_on_unmatched_files: false files: | release-assets/*.dmg release-assets/*.exe release-assets/*.AppImage release-assets/*.deb release-assets/*.blockmap release-assets/*.source.tar.gz release-assets/*.source.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-npm: name: Publish to npm needs: [validate, release] uses: ./.github/workflows/npm-publish.yml with: version: ${{ needs.validate.outputs.version }} tag: latest secrets: NPM_TOKEN: ${{ secrets.NPM_TOKEN }}