mirror of
https://github.com/tw93/Pake.git
synced 2026-03-26 09:21:21 +00:00
383 lines
14 KiB
YAML
Vendored
383 lines
14 KiB
YAML
Vendored
name: Build Single Popular App
|
|
|
|
env:
|
|
NODE_VERSION: "22"
|
|
PNPM_VERSION: "10.26.2"
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
name:
|
|
description: "App Name"
|
|
required: true
|
|
default: "twitter"
|
|
title:
|
|
description: "App Title"
|
|
required: true
|
|
default: "Twitter"
|
|
name_zh:
|
|
description: "App Name in Chinese"
|
|
required: true
|
|
default: "推特"
|
|
url:
|
|
description: "App URL"
|
|
required: true
|
|
default: "https://twitter.com/"
|
|
icon:
|
|
description: "App Icon"
|
|
required: false
|
|
new_window:
|
|
description: "Allow sites to open new windows"
|
|
required: false
|
|
default: false
|
|
workflow_call:
|
|
inputs:
|
|
name:
|
|
description: "App Name"
|
|
type: string
|
|
required: true
|
|
default: "twitter"
|
|
title:
|
|
description: "App Title"
|
|
required: true
|
|
type: string
|
|
default: "Twitter"
|
|
name_zh:
|
|
description: "App Name in Chinese"
|
|
required: true
|
|
type: string
|
|
default: "推特"
|
|
url:
|
|
description: "App URL"
|
|
required: true
|
|
type: string
|
|
default: "https://twitter.com/"
|
|
icon:
|
|
description: "App Icon"
|
|
type: string
|
|
required: false
|
|
new_window:
|
|
description: "Allow sites to open new windows"
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
secrets:
|
|
PAKE_SIGNING_IDENTITY:
|
|
required: false
|
|
PAKE_CERTIFICATE_P12:
|
|
required: false
|
|
PAKE_CERTIFICATE_PASSWORD:
|
|
required: false
|
|
PAKE_NOTARIZE_APPLE_ID:
|
|
required: false
|
|
PAKE_NOTARIZE_TEAM_ID:
|
|
required: false
|
|
PAKE_NOTARIZE_PASSWORD:
|
|
required: false
|
|
|
|
jobs:
|
|
build:
|
|
name: ${{ inputs.title }} (${{ matrix.build }})
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
build: [linux, macos, windows]
|
|
include:
|
|
- build: linux
|
|
os: ubuntu-latest
|
|
rust: stable
|
|
- build: windows
|
|
os: windows-latest
|
|
rust: stable-x86_64-msvc
|
|
- build: macos
|
|
os: macos-latest
|
|
rust: stable
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
toolchain: ${{ matrix.rust }}
|
|
|
|
- name: Setup Node.js Environment
|
|
uses: ./.github/actions/setup-env
|
|
with:
|
|
mode: build
|
|
|
|
- name: Download CLI Artifact
|
|
if: github.event_name != 'workflow_dispatch'
|
|
uses: actions/download-artifact@v7
|
|
with:
|
|
name: pake-cli-dist
|
|
path: dist
|
|
|
|
- name: Build CLI (workflow_dispatch)
|
|
if: github.event_name == 'workflow_dispatch'
|
|
run: pnpm run cli:build
|
|
|
|
- name: Setup mold linker
|
|
if: matrix.os == 'ubuntu-latest'
|
|
uses: rui314/setup-mold@v1
|
|
|
|
- name: Prepare macOS signing
|
|
if: matrix.os == 'macos-latest'
|
|
env:
|
|
PAKE_SIGNING_IDENTITY: ${{ secrets.PAKE_SIGNING_IDENTITY }}
|
|
PAKE_CERTIFICATE_P12: ${{ secrets.PAKE_CERTIFICATE_P12 }}
|
|
PAKE_CERTIFICATE_PASSWORD: ${{ secrets.PAKE_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
SIGNING_IDENTITY="${PAKE_SIGNING_IDENTITY:-}"
|
|
|
|
if [ -n "${PAKE_CERTIFICATE_P12:-}" ] && [ -n "${PAKE_CERTIFICATE_PASSWORD:-}" ]; then
|
|
echo "Importing macOS signing certificate..."
|
|
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
|
|
KEYCHAIN_PASSWORD="$(openssl rand -base64 16)"
|
|
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
|
|
echo "$PAKE_CERTIFICATE_P12" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
|
|
security import "$RUNNER_TEMP/certificate.p12" -P "$PAKE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
security list-keychain -d user -s "$KEYCHAIN_PATH"
|
|
|
|
if [ -z "$SIGNING_IDENTITY" ]; then
|
|
SIGNING_IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | sed -n 's/.*"\(Developer ID Application:.*\)".*/\1/p' | head -n 1)"
|
|
fi
|
|
else
|
|
echo "No certificate secret configured, fallback to ad-hoc signing."
|
|
fi
|
|
|
|
if [ -z "$SIGNING_IDENTITY" ]; then
|
|
SIGNING_IDENTITY="-"
|
|
fi
|
|
|
|
SIGNING_IDENTITY="$SIGNING_IDENTITY" node <<'NODE'
|
|
const fs = require('fs');
|
|
const file = 'src-tauri/tauri.macos.conf.json';
|
|
const config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
config.bundle ??= {};
|
|
config.bundle.macOS ??= {};
|
|
config.bundle.macOS.signingIdentity = process.env.SIGNING_IDENTITY || '-';
|
|
fs.writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
NODE
|
|
|
|
echo "Using signing identity: $SIGNING_IDENTITY"
|
|
|
|
- name: Build for Linux
|
|
if: matrix.os == 'ubuntu-latest'
|
|
timeout-minutes: 20
|
|
run: |
|
|
ARGS=("${{ inputs.url }}" "--name" "${{ inputs.name }}")
|
|
|
|
# Auto-detect local icon or use provided icon
|
|
if [ -n "${{ inputs.icon }}" ]; then
|
|
ARGS+=("--icon" "${{ inputs.icon }}")
|
|
elif [ -f "src-tauri/png/${{ inputs.name }}_512.png" ]; then
|
|
ARGS+=("--icon" "src-tauri/png/${{ inputs.name }}_512.png")
|
|
fi
|
|
|
|
if [ "${{ inputs.new_window }}" = "true" ]; then
|
|
ARGS+=("--new-window")
|
|
fi
|
|
|
|
# Build once with multiple targets (faster than separate builds)
|
|
node dist/cli.js "${ARGS[@]}" --targets deb,appimage
|
|
|
|
mkdir -p output/linux
|
|
|
|
# The CLI copies files to project root and removes them from bundle directory
|
|
# Check project root first, then fallback to bundle directory
|
|
if [ -f "${{ inputs.name }}.deb" ]; then
|
|
mv "${{ inputs.name }}.deb" output/linux/${{inputs.title}}_`arch`.deb
|
|
elif [ -n "$(ls src-tauri/target/release/bundle/deb/*.deb 2>/dev/null)" ]; then
|
|
mv src-tauri/target/release/bundle/deb/*.deb output/linux/${{inputs.title}}_`arch`.deb
|
|
else
|
|
echo "Error: No DEB file found"
|
|
find . -name "*.deb" -type f
|
|
exit 1
|
|
fi
|
|
|
|
if [ -f "${{ inputs.name }}.AppImage" ]; then
|
|
mv "${{ inputs.name }}.AppImage" output/linux/"${{inputs.title}}"_`arch`.AppImage
|
|
elif [ -n "$(ls src-tauri/target/release/bundle/appimage/*.AppImage 2>/dev/null)" ]; then
|
|
mv src-tauri/target/release/bundle/appimage/*.AppImage output/linux/"${{inputs.title}}"_`arch`.AppImage
|
|
else
|
|
echo "Error: No AppImage file found"
|
|
find . -name "*.AppImage" -type f
|
|
exit 1
|
|
fi
|
|
|
|
- name: Build for macOS
|
|
if: matrix.os == 'macos-latest'
|
|
timeout-minutes: 25
|
|
env:
|
|
TAURI_BUNDLER_DMG_IGNORE_CI: "true"
|
|
run: |
|
|
# Use title as app product name on macOS to preserve display casing in .app
|
|
ARGS=("${{ inputs.url }}" "--name" "${{ inputs.title }}" "--hide-title-bar")
|
|
|
|
# Auto-detect local icon or use provided icon
|
|
if [ -n "${{ inputs.icon }}" ]; then
|
|
ARGS+=("--icon" "${{ inputs.icon }}")
|
|
elif [ -f "src-tauri/icons/${{ inputs.name }}.icns" ]; then
|
|
ARGS+=("--icon" "src-tauri/icons/${{ inputs.name }}.icns")
|
|
fi
|
|
|
|
if [ "${{ inputs.new_window }}" = "true" ]; then
|
|
ARGS+=("--new-window")
|
|
fi
|
|
|
|
node dist/cli.js "${ARGS[@]}" --targets universal --multi-arch
|
|
|
|
mkdir -p output/macos
|
|
|
|
# The CLI copies the DMG to project root and removes it from bundle directory
|
|
# Check project root first, then fallback to bundle directory
|
|
if [ -f "${{ inputs.title }}.dmg" ]; then
|
|
mv "${{ inputs.title }}.dmg" output/macos/"${{inputs.title}}".dmg
|
|
elif [ -f "${{ inputs.name }}.dmg" ]; then
|
|
mv "${{ inputs.name }}.dmg" output/macos/"${{inputs.title}}".dmg
|
|
elif [ -n "$(ls src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg 2>/dev/null)" ]; then
|
|
mv src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg output/macos/"${{inputs.title}}".dmg
|
|
else
|
|
echo "Error: No DMG file found"
|
|
echo "Searched locations:"
|
|
echo " - ${{ inputs.name }}.dmg (project root)"
|
|
echo " - src-tauri/target/universal-apple-darwin/release/bundle/dmg/"
|
|
find . -name "*.dmg" -type f
|
|
exit 1
|
|
fi
|
|
|
|
- name: Notarize macOS DMG
|
|
if: matrix.os == 'macos-latest'
|
|
env:
|
|
PAKE_NOTARIZE_APPLE_ID: ${{ secrets.PAKE_NOTARIZE_APPLE_ID }}
|
|
PAKE_NOTARIZE_TEAM_ID: ${{ secrets.PAKE_NOTARIZE_TEAM_ID }}
|
|
PAKE_NOTARIZE_PASSWORD: ${{ secrets.PAKE_NOTARIZE_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ -z "${PAKE_NOTARIZE_APPLE_ID:-}" ] || [ -z "${PAKE_NOTARIZE_TEAM_ID:-}" ] || [ -z "${PAKE_NOTARIZE_PASSWORD:-}" ]; then
|
|
echo "Notarization secrets not configured, skipping notarization."
|
|
exit 0
|
|
fi
|
|
|
|
DMG_PATH="output/macos/${{inputs.title}}.dmg"
|
|
if [ ! -f "$DMG_PATH" ]; then
|
|
echo "Error: DMG file not found at $DMG_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Submitting DMG to Apple notarization service..."
|
|
xcrun notarytool submit "$DMG_PATH" \
|
|
--apple-id "$PAKE_NOTARIZE_APPLE_ID" \
|
|
--team-id "$PAKE_NOTARIZE_TEAM_ID" \
|
|
--password "$PAKE_NOTARIZE_PASSWORD" \
|
|
--wait
|
|
|
|
echo "Stapling notarization ticket..."
|
|
xcrun stapler staple "$DMG_PATH"
|
|
|
|
- name: Build for Windows
|
|
if: matrix.os == 'windows-latest'
|
|
timeout-minutes: 20
|
|
run: |
|
|
# Use title as app product name on Windows to preserve display casing
|
|
$args = "${{ inputs.url }}", "--name", "${{ inputs.title }}"
|
|
|
|
# Auto-detect local icon or use provided icon
|
|
if ("${{ inputs.icon }}" -ne "") {
|
|
$args += "--icon", "${{ inputs.icon }}"
|
|
} elseif (Test-Path "src-tauri\png\${{ inputs.name }}_256.ico") {
|
|
$args += "--icon", "src-tauri\png\${{ inputs.name }}_256.ico"
|
|
}
|
|
|
|
if ("${{ inputs.new_window }}" -eq "true") {
|
|
$args += "--new-window"
|
|
}
|
|
|
|
$args += "--targets", "x64"
|
|
|
|
node dist/cli.js $args
|
|
|
|
New-Item -Path "output\windows" -ItemType Directory -Force
|
|
|
|
# The CLI copies the MSI to project root and removes it from bundle directory
|
|
# Check project root first, then fallback to bundle directories
|
|
$projectRootMsi = "${{ inputs.title }}.msi"
|
|
$legacyProjectRootMsi = "${{ inputs.name }}.msi"
|
|
|
|
if (Test-Path $projectRootMsi) {
|
|
Move-Item -Path $projectRootMsi -Destination "output\windows\${{inputs.title}}_x64.msi"
|
|
} elseif (Test-Path $legacyProjectRootMsi) {
|
|
Move-Item -Path $legacyProjectRootMsi -Destination "output\windows\${{inputs.title}}_x64.msi"
|
|
} else {
|
|
# Check architecture-specific path (x64 builds)
|
|
$msiFiles = Get-ChildItem -Path "src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\*.msi" -ErrorAction SilentlyContinue
|
|
|
|
# Fallback to generic path
|
|
if (-not $msiFiles) {
|
|
$msiFiles = Get-ChildItem -Path "src-tauri\target\release\bundle\msi\*.msi" -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
if ($msiFiles) {
|
|
Move-Item -Path $msiFiles[0].FullName -Destination "output\windows\${{inputs.title}}_x64.msi"
|
|
} else {
|
|
Write-Error "No MSI files found in expected locations"
|
|
Write-Host "Searched paths:"
|
|
Write-Host " - $projectRootMsi (project root)"
|
|
Write-Host " - $legacyProjectRootMsi (project root)"
|
|
Write-Host " - src-tauri\target\x86_64-pc-windows-msvc\release\bundle\msi\"
|
|
Write-Host " - src-tauri\target\release\bundle\msi\"
|
|
Write-Host "`nAll MSI files in target directory:"
|
|
Get-ChildItem -Path "src-tauri\target\" -Recurse -Name "*.msi" | Write-Host
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
git checkout -- src-tauri/Cargo.lock
|
|
|
|
- name: Upload artifacts
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: ${{ inputs.title }}-${{ matrix.build }}
|
|
path: output/*/*.*
|
|
retention-days: 3
|
|
|
|
- name: Upload to release (Linux)
|
|
uses: ncipollo/release-action@v1 # cspell:disable-line
|
|
if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/')
|
|
with:
|
|
allowUpdates: true
|
|
omitBody: true
|
|
omitName: true
|
|
artifacts: "output/linux/*.deb,output/linux/*.AppImage"
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Upload to release (macOS)
|
|
uses: ncipollo/release-action@v1 # cspell:disable-line
|
|
if: matrix.os == 'macos-latest' && startsWith(github.ref, 'refs/tags/')
|
|
with:
|
|
allowUpdates: true
|
|
omitBody: true
|
|
omitName: true
|
|
artifacts: "output/macos/*.dmg"
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Upload to release (Windows)
|
|
uses: ncipollo/release-action@v1 # cspell:disable-line
|
|
if: matrix.os == 'windows-latest' && startsWith(github.ref, 'refs/tags/')
|
|
with:
|
|
allowUpdates: true
|
|
omitBody: true
|
|
omitName: true
|
|
artifacts: "output/windows/*.msi"
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|