gh release download can see no assets after a draft is published, so the Linux and Android publication audits fail and the Release returns to Draft. Download those audit files through the release API URL.
908 lines
36 KiB
YAML
908 lines
36 KiB
YAML
name: Desktop build
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
release_tag:
|
|
description: "Prepare macOS, Windows, and Linux assets on this existing Draft Release (optional)"
|
|
required: false
|
|
type: string
|
|
release:
|
|
types: [published]
|
|
|
|
permissions:
|
|
actions: read
|
|
contents: write
|
|
|
|
jobs:
|
|
release-plan:
|
|
if: >-
|
|
${{
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
(
|
|
github.event_name == 'release' ||
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag != '')
|
|
)
|
|
}}
|
|
name: Plan desktop release asset
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
asset_ready: ${{ steps.plan.outputs.asset_ready }}
|
|
previous_tag: ${{ steps.plan.outputs.previous_tag }}
|
|
rebuild: ${{ steps.plan.outputs.rebuild }}
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Validate Draft Release target
|
|
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.release_tag }}
|
|
run: |
|
|
expected_version="$(node -p "JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version")"
|
|
test "$RELEASE_TAG" = "v${expected_version}"
|
|
test "$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft')" = "true"
|
|
target_commitish="$(gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json targetCommitish --jq '.targetCommitish')"
|
|
test "$(git rev-parse "${target_commitish}^{commit}")" = "$(git rev-parse "${GITHUB_SHA}^{commit}")"
|
|
|
|
- name: Compare with previous formal release
|
|
id: plan
|
|
env:
|
|
CURRENT_TAG: ${{ inputs.release_tag || github.event.release.tag_name }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
HEAD_REF: ${{ github.event_name == 'release' && github.event.release.tag_name || github.sha }}
|
|
run: |
|
|
mapfile -t release_tags < <(
|
|
gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \
|
|
--jq '.[] | select(.draft == false and .prerelease == false) | .tag_name'
|
|
)
|
|
previous_tag=""
|
|
for release_tag in "${release_tags[@]}"; do
|
|
if [[ "$release_tag" != "$CURRENT_TAG" ]]; then
|
|
previous_tag="$release_tag"
|
|
break
|
|
fi
|
|
done
|
|
test -n "$previous_tag"
|
|
git fetch origin "refs/tags/${previous_tag}:refs/tags/${previous_tag}"
|
|
echo "previous_tag=$previous_tag" >> "$GITHUB_OUTPUT"
|
|
plan_output="$(node scripts/plan-native-release.mjs desktop "$previous_tag" "$HEAD_REF")"
|
|
printf '%s\n' "$plan_output" >> "$GITHUB_OUTPUT"
|
|
rebuild="$(printf '%s\n' "$plan_output" | sed -n 's/^rebuild=//p')"
|
|
expected_version="$(node -p "JSON.parse(require('fs').readFileSync('apps/desktop/package.json', 'utf8')).version")"
|
|
if [[ "$rebuild" = "true" ]]; then
|
|
root_version="$(node -p "JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version")"
|
|
test "$expected_version" = "$root_version"
|
|
fi
|
|
|
|
# `gh release view --json assets` stays empty after a draft is published.
|
|
# The API URL is addressed by release id and still lists the uploads.
|
|
release_api="$(gh release view "$CURRENT_TAG" --repo "$GITHUB_REPOSITORY" --json apiUrl --jq .apiUrl)"
|
|
mapfile -t current_assets < <(
|
|
gh api "$release_api" --jq '.assets[].name'
|
|
)
|
|
asset_ready="$(
|
|
printf '%s\n' "${current_assets[@]}" |
|
|
node scripts/check-native-release-assets.mjs desktop "$rebuild" "$CURRENT_TAG" "$expected_version"
|
|
)"
|
|
echo "asset_ready=$asset_ready" >> "$GITHUB_OUTPUT"
|
|
if [[ "$GITHUB_EVENT_NAME" = "release" && "$asset_ready" != "true" ]]; then
|
|
echo "::error::Published Release assets are incomplete or incompatible. Restore the Release to Draft and prepare assets with workflow_dispatch."
|
|
exit 1
|
|
fi
|
|
|
|
reuse-release-desktop:
|
|
needs: release-plan
|
|
if: >-
|
|
${{
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
github.event_name == 'workflow_dispatch' &&
|
|
inputs.release_tag != '' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'false' &&
|
|
needs.release-plan.outputs.rebuild == 'false'
|
|
}}
|
|
name: Reuse desktop release assets
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Copy latest compatible desktop assets without renaming
|
|
env:
|
|
CURRENT_TAG: ${{ inputs.release_tag || github.event.release.tag_name }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
PREVIOUS_TAG: ${{ needs.release-plan.outputs.previous_tag }}
|
|
run: |
|
|
mkdir -p "$RUNNER_TEMP/native-assets"
|
|
gh release download "$PREVIOUS_TAG" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--pattern 'EdgeEver-*-mac-arm64.dmg' \
|
|
--pattern 'EdgeEver-*-mac-arm64.dmg.blockmap' \
|
|
--pattern 'EdgeEver-*-mac-arm64.zip' \
|
|
--pattern 'EdgeEver-*-mac-arm64.zip.blockmap' \
|
|
--pattern 'EdgeEver-*-mac-x64.dmg' \
|
|
--pattern 'EdgeEver-*-mac-x64.dmg.blockmap' \
|
|
--pattern 'EdgeEver-*-mac-x64.zip' \
|
|
--pattern 'EdgeEver-*-mac-x64.zip.blockmap' \
|
|
--pattern 'latest-mac.yml' \
|
|
--pattern 'EdgeEver-*-windows-x64.exe' \
|
|
--pattern 'latest.yml' \
|
|
--pattern 'latest-windows.json' \
|
|
--pattern 'latest-windows.json.sig' \
|
|
--pattern 'SHA256SUMS-windows.txt' \
|
|
--pattern 'EdgeEver-*-linux-x64.AppImage' \
|
|
--pattern 'latest-linux.yml' \
|
|
--pattern 'SHA256SUMS-linux.txt' \
|
|
--dir "$RUNNER_TEMP/native-assets"
|
|
test "$(find "$RUNNER_TEMP/native-assets" -type f | wc -l | tr -d ' ')" = "17"
|
|
gh release upload "$CURRENT_TAG" "$RUNNER_TEMP/native-assets/"* \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--clobber
|
|
|
|
desktop:
|
|
needs: release-plan
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
(
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag == '') ||
|
|
(
|
|
github.event_name == 'workflow_dispatch' &&
|
|
inputs.release_tag != '' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'false' &&
|
|
needs.release-plan.outputs.rebuild == 'true'
|
|
)
|
|
)
|
|
}}
|
|
name: macOS ${{ matrix.arch }}
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: arm64
|
|
runner: macos-15
|
|
shared_validation: true
|
|
- arch: x64
|
|
runner: macos-15-intel
|
|
shared_validation: false
|
|
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Set up Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
# Restoring this large cache is consistently slower than a cold install on
|
|
# the Intel runner. The arm64 job owns the shared validation and keeps the
|
|
# cache because its restore path is substantially faster.
|
|
- name: Cache Bun dependencies for shared validation
|
|
if: matrix.shared_validation
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~/.bun/install/cache
|
|
key: ${{ runner.os }}-${{ matrix.arch }}-bun-${{ hashFiles('bun.lock') }}
|
|
|
|
- name: Cache Rust dependencies and sidecar builds
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
crates/desktop-sidecar/target
|
|
key: ${{ runner.os }}-${{ matrix.arch }}-rust-${{ hashFiles('crates/desktop-sidecar/Cargo.lock', 'crates/desktop-sidecar/**/*.rs') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ matrix.arch }}-rust-
|
|
|
|
- name: Cache Electron downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/Library/Caches/electron
|
|
~/Library/Caches/electron-builder
|
|
key: ${{ runner.os }}-${{ matrix.arch }}-electron-${{ hashFiles('bun.lock', 'apps/desktop/package.json') }}
|
|
|
|
- name: Import macOS signing identity
|
|
env:
|
|
MAC_CERTIFICATE_DER_BASE64: ${{ secrets.EDGEEVER_MAC_CERTIFICATE_DER_BASE64 }}
|
|
MAC_PRIVATE_KEY_BASE64: ${{ secrets.EDGEEVER_MAC_PRIVATE_KEY_BASE64 }}
|
|
run: |
|
|
if [[ -z "$MAC_CERTIFICATE_DER_BASE64" || -z "$MAC_PRIVATE_KEY_BASE64" ]]; then
|
|
echo "::error::EDGEEVER_MAC_CERTIFICATE_DER_BASE64 and EDGEEVER_MAC_PRIVATE_KEY_BASE64 are required"
|
|
exit 1
|
|
fi
|
|
|
|
keychain_path="$RUNNER_TEMP/edgeever-build.keychain-db"
|
|
keychain_password="$(openssl rand -hex 32)"
|
|
certificate_path="$RUNNER_TEMP/edgeever-mac-signing.cer"
|
|
private_key_path="$RUNNER_TEMP/edgeever-mac-signing.pem"
|
|
traditional_key_path="$RUNNER_TEMP/edgeever-mac-signing-rsa.pem"
|
|
|
|
printf '%s' "$MAC_CERTIFICATE_DER_BASE64" | base64 -D > "$certificate_path"
|
|
printf '%s' "$MAC_PRIVATE_KEY_BASE64" | base64 -D > "$private_key_path"
|
|
openssl rsa -in "$private_key_path" -out "$traditional_key_path"
|
|
|
|
security create-keychain -p "$keychain_password" "$keychain_path"
|
|
security set-keychain-settings -lut 21600 "$keychain_path"
|
|
security unlock-keychain -p "$keychain_password" "$keychain_path"
|
|
security import "$certificate_path" \
|
|
-k "$keychain_path" \
|
|
-f x509 \
|
|
-t cert \
|
|
-T /usr/bin/codesign
|
|
security import "$traditional_key_path" \
|
|
-k "$keychain_path" \
|
|
-f openssl \
|
|
-t priv \
|
|
-P '' \
|
|
-T /usr/bin/codesign
|
|
security set-key-partition-list \
|
|
-S apple-tool:,apple: \
|
|
-s \
|
|
-k "$keychain_password" \
|
|
"$keychain_path"
|
|
security list-keychains -d user -s "$keychain_path" "$HOME/Library/Keychains/login.keychain-db"
|
|
security find-identity -v -p codesigning "$keychain_path"
|
|
|
|
echo "CSC_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"
|
|
echo "CSC_IDENTITY_AUTO_DISCOVERY=true" >> "$GITHUB_ENV"
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Build Web renderer
|
|
env:
|
|
EDGE_EVER_DESKTOP_BUILD: "1"
|
|
run: bun run build:web
|
|
|
|
- name: Verify Web performance budget
|
|
if: matrix.shared_validation
|
|
run: bun run verify:web-performance
|
|
|
|
- name: Run project type checks
|
|
if: matrix.shared_validation
|
|
run: |
|
|
bun run typecheck
|
|
bun run typecheck:mobile
|
|
|
|
- name: Build debug sidecar for integration tests
|
|
if: matrix.shared_validation
|
|
run: cargo build --manifest-path crates/desktop-sidecar/Cargo.toml
|
|
|
|
- name: Run desktop regression tests
|
|
if: matrix.shared_validation
|
|
run: bun run test:desktop
|
|
|
|
- name: Verify renderer origin storage migration
|
|
if: matrix.shared_validation
|
|
run: bun run verify:renderer-origin-migration
|
|
|
|
- name: Verify packaged renderer startup
|
|
if: matrix.shared_validation
|
|
run: bun run verify:desktop-renderer
|
|
|
|
- name: Validate Rust sidecar
|
|
if: matrix.shared_validation
|
|
run: |
|
|
cargo fmt --manifest-path crates/desktop-sidecar/Cargo.toml -- --check
|
|
cargo clippy --manifest-path crates/desktop-sidecar/Cargo.toml --all-targets -- -D warnings
|
|
cargo test --manifest-path crates/desktop-sidecar/Cargo.toml
|
|
|
|
- name: Build architecture-specific Rust sidecar
|
|
run: cargo build --manifest-path crates/desktop-sidecar/Cargo.toml --release
|
|
|
|
- name: Prepare Apple notarization API key
|
|
env:
|
|
APPLE_API_KEY_BASE64: ${{ secrets.EDGEEVER_APPLE_API_KEY_BASE64 }}
|
|
APPLE_API_KEY_ID: ${{ secrets.EDGEEVER_APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.EDGEEVER_APPLE_API_ISSUER }}
|
|
run: |
|
|
if [[ -z "$APPLE_API_KEY_BASE64" || -z "$APPLE_API_KEY_ID" || -z "$APPLE_API_ISSUER" ]]; then
|
|
echo "::error::Apple notarization API key secrets are required"
|
|
exit 1
|
|
fi
|
|
|
|
apple_api_key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
|
|
printf '%s' "$APPLE_API_KEY_BASE64" | base64 -D > "$apple_api_key_path"
|
|
chmod 600 "$apple_api_key_path"
|
|
echo "APPLE_API_KEY=$apple_api_key_path" >> "$GITHUB_ENV"
|
|
echo "APPLE_API_KEY_ID=$APPLE_API_KEY_ID" >> "$GITHUB_ENV"
|
|
echo "APPLE_API_ISSUER=$APPLE_API_ISSUER" >> "$GITHUB_ENV"
|
|
|
|
- name: Package desktop installer
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: ${{ matrix.arch }}
|
|
run: bun scripts/run-desktop-builder.mjs --publish never
|
|
|
|
- name: Verify packaged macOS cross-version startup
|
|
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PREVIOUS_TAG: ${{ needs.release-plan.outputs.previous_tag }}
|
|
run: |
|
|
previous_directory="$RUNNER_TEMP/edgeever-previous-${{ matrix.arch }}"
|
|
mkdir -p "$previous_directory/archive" "$previous_directory/app"
|
|
gh release download "$PREVIOUS_TAG" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--pattern 'EdgeEver-*-mac-${{ matrix.arch }}.zip' \
|
|
--dir "$previous_directory/archive"
|
|
previous_archive="$(find "$previous_directory/archive" -maxdepth 1 -type f -name '*.zip' -print -quit)"
|
|
test -n "$previous_archive"
|
|
ditto -x -k "$previous_archive" "$previous_directory/app"
|
|
previous_executable="$previous_directory/app/EdgeEver.app/Contents/MacOS/EdgeEver"
|
|
unpacked_directory="mac-${{ matrix.arch }}"
|
|
if [[ "${{ matrix.arch }}" == "x64" ]]; then
|
|
unpacked_directory="mac"
|
|
fi
|
|
current_executable="release/desktop/$unpacked_directory/EdgeEver.app/Contents/MacOS/EdgeEver"
|
|
bun run verify:desktop-cross-version-startup -- "$previous_executable" "$current_executable"
|
|
|
|
- name: Verify packaged macOS first launch
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: ${{ matrix.arch }}
|
|
run: |
|
|
unpacked_directory="mac-${{ matrix.arch }}"
|
|
if [[ "${{ matrix.arch }}" == "x64" ]]; then
|
|
unpacked_directory="mac"
|
|
fi
|
|
bun run verify:packaged-desktop-startup -- "release/desktop/$unpacked_directory/EdgeEver.app/Contents/MacOS/EdgeEver"
|
|
|
|
- name: Verify packaged macOS private protocol file flows
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: ${{ matrix.arch }}
|
|
run: |
|
|
unpacked_directory="mac-${{ matrix.arch }}"
|
|
if [[ "${{ matrix.arch }}" == "x64" ]]; then
|
|
unpacked_directory="mac"
|
|
fi
|
|
bun run verify:desktop-protocol-e2e -- "release/desktop/$unpacked_directory/EdgeEver.app/Contents/MacOS/EdgeEver"
|
|
|
|
- name: Verify desktop installer
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: ${{ matrix.arch }}
|
|
EDGE_EVER_VERIFY_TARGET: darwin
|
|
run: bun scripts/verify-desktop-package.mjs
|
|
|
|
- name: Stage architecture-specific desktop assets
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: edgeever-desktop-macOS-${{ matrix.arch }}-staging
|
|
path: |
|
|
release/desktop/EdgeEver-*-mac-${{ matrix.arch }}.dmg
|
|
release/desktop/EdgeEver-*-mac-${{ matrix.arch }}.dmg.blockmap
|
|
release/desktop/EdgeEver-*-mac-${{ matrix.arch }}.zip
|
|
release/desktop/EdgeEver-*-mac-${{ matrix.arch }}.zip.blockmap
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
|
|
windows:
|
|
needs: release-plan
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
(
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag == '') ||
|
|
(
|
|
github.event_name == 'workflow_dispatch' &&
|
|
inputs.release_tag != '' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'false' &&
|
|
needs.release-plan.outputs.rebuild == 'true'
|
|
)
|
|
)
|
|
}}
|
|
name: Windows x64 unsigned Preview
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Set up Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Cache Bun dependencies
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~\AppData\Local\bun\install\cache
|
|
key: ${{ runner.os }}-x64-bun-${{ hashFiles('bun.lock') }}
|
|
|
|
- name: Cache Rust dependencies and sidecar builds
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~\.cargo\registry
|
|
~\.cargo\git
|
|
crates\desktop-sidecar\target
|
|
key: ${{ runner.os }}-x64-rust-${{ hashFiles('crates/desktop-sidecar/Cargo.lock', 'crates/desktop-sidecar/**/*.rs') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-x64-rust-
|
|
|
|
- name: Cache Electron downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~\AppData\Local\electron\Cache
|
|
~\AppData\Local\electron-builder\Cache
|
|
key: ${{ runner.os }}-x64-electron-${{ hashFiles('bun.lock', 'apps/desktop/package.json') }}
|
|
|
|
- name: Install dependencies
|
|
shell: pwsh
|
|
run: |
|
|
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
|
bun install --frozen-lockfile
|
|
$installExitCode = $LASTEXITCODE
|
|
if ($installExitCode -eq 0) { exit 0 }
|
|
if ($attempt -eq 3) { exit $installExitCode }
|
|
Write-Warning "Dependency installation failed on attempt $attempt; retrying."
|
|
Start-Sleep -Seconds (15 * $attempt)
|
|
}
|
|
|
|
- name: Build Web renderer
|
|
env:
|
|
EDGE_EVER_DESKTOP_BUILD: "1"
|
|
run: bun run build:web
|
|
|
|
- name: Build x64 Rust sidecar
|
|
run: cargo build --manifest-path crates/desktop-sidecar/Cargo.toml --release
|
|
|
|
- name: Package unsigned Windows installer
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: x64
|
|
EDGE_EVER_DESKTOP_TARGET: win
|
|
run: bun scripts/run-desktop-builder.mjs --publish never
|
|
|
|
- name: Verify packaged Windows cross-version startup
|
|
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
|
|
shell: pwsh
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PREVIOUS_TAG: ${{ needs.release-plan.outputs.previous_tag }}
|
|
run: |
|
|
$previousDirectory = Join-Path $env:RUNNER_TEMP "edgeever-previous-windows-x64"
|
|
New-Item -ItemType Directory -Force -Path $previousDirectory | Out-Null
|
|
gh release download $env:PREVIOUS_TAG `
|
|
--repo $env:GITHUB_REPOSITORY `
|
|
--pattern 'EdgeEver-*-windows-x64.exe' `
|
|
--dir $previousDirectory
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
$previousInstaller = Get-ChildItem -LiteralPath $previousDirectory -Filter '*.exe' | Select-Object -First 1
|
|
if (-not $previousInstaller) { throw "Previous Windows installer was not downloaded" }
|
|
$installerProcess = Start-Process -FilePath $previousInstaller.FullName -ArgumentList '/S' -Wait -PassThru
|
|
if ($installerProcess.ExitCode -ne 0) {
|
|
throw "Previous Windows installer exited with code $($installerProcess.ExitCode)"
|
|
}
|
|
Get-Process -Name EdgeEver -ErrorAction SilentlyContinue | Stop-Process -Force
|
|
$previousExecutable = Get-ChildItem -LiteralPath (Join-Path $env:LOCALAPPDATA 'Programs') `
|
|
-Filter 'EdgeEver.exe' -File -Recurse | Select-Object -First 1
|
|
if (-not $previousExecutable) { throw "Previous installed EdgeEver executable was not found" }
|
|
bun run verify:desktop-cross-version-startup -- `
|
|
$previousExecutable.FullName `
|
|
(Resolve-Path 'release/desktop/win-unpacked/EdgeEver.exe').Path
|
|
|
|
- name: Verify Windows package contents and architecture
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: x64
|
|
EDGE_EVER_VERIFY_TARGET: win32
|
|
run: bun scripts/verify-desktop-package.mjs
|
|
|
|
- name: Run packaged Windows sidecar integration tests
|
|
shell: pwsh
|
|
run: |
|
|
$env:EDGE_EVER_SIDECAR_PATH = (Resolve-Path "release/desktop/win-unpacked/resources/sidecar/edgeever-sidecar.exe").Path
|
|
$env:EDGE_EVER_MIGRATIONS_PATH = (Resolve-Path "release/desktop/win-unpacked/resources/migrations").Path
|
|
bun scripts/test-desktop-sidecar.mjs
|
|
|
|
- name: Verify packaged Windows first launch
|
|
run: bun run verify:packaged-desktop-startup -- release/desktop/win-unpacked/EdgeEver.exe
|
|
|
|
- name: Verify packaged Windows private protocol file flows
|
|
run: bun run verify:desktop-protocol-e2e -- release/desktop/win-unpacked/EdgeEver.exe
|
|
|
|
- name: Verify that Preview executables are unsigned
|
|
shell: pwsh
|
|
run: |
|
|
$version = (Get-Content apps/desktop/package.json | ConvertFrom-Json).version
|
|
$paths = @(
|
|
"release/desktop/EdgeEver-$version-windows-x64.exe",
|
|
"release/desktop/win-unpacked/EdgeEver.exe",
|
|
"release/desktop/win-unpacked/resources/sidecar/edgeever-sidecar.exe"
|
|
)
|
|
foreach ($path in $paths) {
|
|
$signature = Get-AuthenticodeSignature -LiteralPath $path
|
|
if ($signature.Status -ne "NotSigned") {
|
|
throw "Unsigned Preview asset unexpectedly has an Authenticode signature: $path ($($signature.Status))"
|
|
}
|
|
}
|
|
|
|
- name: Create Windows update metadata
|
|
shell: pwsh
|
|
run: |
|
|
$version = (Get-Content apps/desktop/package.json | ConvertFrom-Json).version
|
|
node scripts/create-windows-update-metadata.mjs release/desktop $version
|
|
|
|
- name: Stage Windows Preview assets
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: edgeever-desktop-Windows-x64-staging
|
|
path: |
|
|
release/desktop/EdgeEver-*-windows-x64.exe
|
|
release/desktop/latest.yml
|
|
release/desktop/latest-windows.json
|
|
release/desktop/SHA256SUMS-windows.txt
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
|
|
linux:
|
|
needs: release-plan
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
(
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag == '') ||
|
|
(
|
|
github.event_name == 'workflow_dispatch' &&
|
|
inputs.release_tag != '' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'false' &&
|
|
needs.release-plan.outputs.rebuild == 'true'
|
|
)
|
|
)
|
|
}}
|
|
name: Linux x64 AppImage Preview
|
|
# The Rust sidecar is dynamically linked. Pinning the oldest supported
|
|
# Ubuntu runner keeps its glibc requirement at or below 2.35.
|
|
runs-on: ubuntu-22.04
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Set up Bun
|
|
uses: oven-sh/setup-bun@v2
|
|
|
|
- name: Set up Rust
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Install AppImage runtime dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install --yes libfuse2
|
|
|
|
- name: Cache Bun dependencies
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~/.bun/install/cache
|
|
key: ${{ runner.os }}-x64-bun-${{ hashFiles('bun.lock') }}
|
|
|
|
- name: Cache Rust dependencies and sidecar builds
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
crates/desktop-sidecar/target
|
|
key: ${{ runner.os }}-x64-rust-${{ hashFiles('crates/desktop-sidecar/Cargo.lock', 'crates/desktop-sidecar/**/*.rs') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-x64-rust-
|
|
|
|
- name: Cache Electron downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.cache/electron
|
|
~/.cache/electron-builder
|
|
key: ${{ runner.os }}-x64-electron-${{ hashFiles('bun.lock', 'apps/desktop/package.json') }}
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
for attempt in 1 2 3; do
|
|
if bun install --frozen-lockfile; then
|
|
exit 0
|
|
fi
|
|
if [[ "$attempt" = "3" ]]; then
|
|
exit 1
|
|
fi
|
|
sleep "$((15 * attempt))"
|
|
done
|
|
|
|
- name: Build Web renderer
|
|
env:
|
|
EDGE_EVER_DESKTOP_BUILD: "1"
|
|
run: bun run build:web
|
|
|
|
- name: Build x64 Rust sidecar
|
|
run: cargo build --manifest-path crates/desktop-sidecar/Cargo.toml --release
|
|
|
|
- name: Build Linux automatic update predecessor
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PREVIOUS_TAG: ${{ needs.release-plan.outputs.previous_tag }}
|
|
run: |
|
|
if [[ -n "$PREVIOUS_TAG" ]]; then
|
|
git fetch --depth=1 origin "refs/tags/${PREVIOUS_TAG}:refs/tags/${PREVIOUS_TAG}"
|
|
if git show "${PREVIOUS_TAG}:apps/desktop/src/main/index.mjs" | grep -q 'linuxUpdateTestMode'; then
|
|
previous_directory="$RUNNER_TEMP/edgeever-previous-linux-x64"
|
|
mkdir -p "$previous_directory"
|
|
gh release download "$PREVIOUS_TAG" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--pattern 'EdgeEver-*-linux-x64.AppImage' \
|
|
--dir "$previous_directory"
|
|
previous_app_image="$(find "$previous_directory" -maxdepth 1 -name 'EdgeEver-*-linux-x64.AppImage' -type f -print -quit)"
|
|
test -n "$previous_app_image"
|
|
test "$(find "$previous_directory" -maxdepth 1 -name 'EdgeEver-*-linux-x64.AppImage' -type f | wc -l | tr -d ' ')" = "1"
|
|
mv "$previous_app_image" "$RUNNER_TEMP/EdgeEver-linux-update-source.AppImage"
|
|
chmod +x "$RUNNER_TEMP/EdgeEver-linux-update-source.AppImage"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# The first release that enables Linux updates has no update-capable
|
|
# predecessor in the wild. Build the same packaged updater at an older
|
|
# version so the bootstrap release still exercises a real replacement.
|
|
package_backup="$RUNNER_TEMP/edgeever-desktop-package.json"
|
|
cp apps/desktop/package.json "$package_backup"
|
|
trap 'cp "$package_backup" apps/desktop/package.json' EXIT
|
|
node -e "const fs=require('fs');const path='apps/desktop/package.json';const value=JSON.parse(fs.readFileSync(path,'utf8'));value.version='0.0.1';fs.writeFileSync(path, JSON.stringify(value, null, 2)+'\n')"
|
|
EDGE_EVER_DESKTOP_ARCH=x64 EDGE_EVER_DESKTOP_TARGET=linux \
|
|
bun scripts/run-desktop-builder.mjs --publish never
|
|
mv release/desktop/EdgeEver-0.0.1-linux-x64.AppImage \
|
|
"$RUNNER_TEMP/EdgeEver-linux-update-source.AppImage"
|
|
cp "$package_backup" apps/desktop/package.json
|
|
trap - EXIT
|
|
|
|
- name: Package Linux AppImage
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: x64
|
|
EDGE_EVER_DESKTOP_TARGET: linux
|
|
run: bun scripts/run-desktop-builder.mjs --publish never
|
|
|
|
- name: Verify Linux package contents, architecture, and glibc baseline
|
|
env:
|
|
EDGE_EVER_DESKTOP_ARCH: x64
|
|
EDGE_EVER_VERIFY_TARGET: linux
|
|
run: bun scripts/verify-desktop-package.mjs
|
|
|
|
- name: Run packaged Linux sidecar integration tests
|
|
env:
|
|
EDGE_EVER_SIDECAR_PATH: ${{ github.workspace }}/release/desktop/linux-unpacked/resources/sidecar/edgeever-sidecar
|
|
EDGE_EVER_MIGRATIONS_PATH: ${{ github.workspace }}/release/desktop/linux-unpacked/resources/migrations
|
|
run: bun scripts/test-desktop-sidecar.mjs
|
|
|
|
- name: Verify packaged Linux first launch
|
|
run: |
|
|
executable=""
|
|
for candidate in release/desktop/linux-unpacked/EdgeEver release/desktop/linux-unpacked/edgeever; do
|
|
if [[ -x "$candidate" ]]; then
|
|
executable="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
test -n "$executable"
|
|
xvfb-run -a bun run verify:packaged-desktop-startup -- "$executable"
|
|
|
|
- name: Verify packaged Linux private protocol file flows
|
|
run: |
|
|
executable=""
|
|
for candidate in release/desktop/linux-unpacked/EdgeEver release/desktop/linux-unpacked/edgeever; do
|
|
if [[ -x "$candidate" ]]; then
|
|
executable="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
test -n "$executable"
|
|
xvfb-run -a bun run verify:desktop-protocol-e2e -- "$executable"
|
|
|
|
- name: Create Linux checksum
|
|
run: |
|
|
version="$(node -p "JSON.parse(require('fs').readFileSync('apps/desktop/package.json', 'utf8')).version")"
|
|
cd release/desktop
|
|
sha256sum "EdgeEver-${version}-linux-x64.AppImage" > SHA256SUMS-linux.txt
|
|
sha256sum --check SHA256SUMS-linux.txt
|
|
|
|
- name: Verify Linux automatic update metadata
|
|
run: |
|
|
version="$(node -p "JSON.parse(require('fs').readFileSync('apps/desktop/package.json', 'utf8')).version")"
|
|
node scripts/verify-linux-update-release.mjs release/desktop "$version"
|
|
|
|
- name: Verify real Linux AppImage automatic update
|
|
run: |
|
|
version="$(node -p "JSON.parse(require('fs').readFileSync('apps/desktop/package.json', 'utf8')).version")"
|
|
xvfb-run -a bun scripts/verify-linux-appimage-update.mjs \
|
|
"$RUNNER_TEMP/EdgeEver-linux-update-source.AppImage" \
|
|
"release/desktop/EdgeEver-${version}-linux-x64.AppImage" \
|
|
"$version"
|
|
|
|
- name: Stage Linux Preview assets
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: edgeever-desktop-Linux-x64-staging
|
|
path: |
|
|
release/desktop/EdgeEver-*-linux-x64.AppImage
|
|
release/desktop/latest-linux.yml
|
|
release/desktop/SHA256SUMS-linux.txt
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
|
|
native-build-timings:
|
|
needs: [desktop, windows, linux]
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
(needs.desktop.result != 'skipped' || needs.windows.result != 'skipped' || needs.linux.result != 'skipped')
|
|
}}
|
|
name: Report desktop build timings
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out timing reporter
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Collect native build timings
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
gh api \
|
|
"repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?filter=latest&per_page=100" \
|
|
> "$RUNNER_TEMP/actions-jobs.json"
|
|
node scripts/report-native-build-timings.mjs \
|
|
--input "$RUNNER_TEMP/actions-jobs.json" \
|
|
--platform desktop \
|
|
--json "$RUNNER_TEMP/native-build-timings.json" \
|
|
--markdown "$RUNNER_TEMP/native-build-timings.md"
|
|
cat "$RUNNER_TEMP/native-build-timings.md" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload machine-readable timing report
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: edgeever-desktop-build-timings-attempt-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/native-build-timings.json
|
|
if-no-files-found: error
|
|
retention-days: 90
|
|
|
|
desktop-assets:
|
|
needs: [release-plan, desktop, windows, linux]
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
needs.desktop.result == 'success' &&
|
|
needs.windows.result == 'success' &&
|
|
needs.linux.result == 'success' &&
|
|
(
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag == '') ||
|
|
(
|
|
github.event_name == 'workflow_dispatch' &&
|
|
inputs.release_tag != '' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'false' &&
|
|
needs.release-plan.outputs.rebuild == 'true'
|
|
)
|
|
)
|
|
}}
|
|
name: Finalize desktop release assets
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Download platform-specific assets
|
|
uses: actions/download-artifact@v7
|
|
with:
|
|
pattern: edgeever-desktop-*-staging
|
|
path: release/desktop
|
|
merge-multiple: true
|
|
|
|
- name: Create and audit combined update metadata
|
|
run: |
|
|
version="$(node -p "JSON.parse(require('fs').readFileSync('apps/desktop/package.json', 'utf8')).version")"
|
|
node scripts/create-mac-update-metadata.mjs release/desktop "$version"
|
|
node scripts/verify-linux-update-release.mjs release/desktop "$version"
|
|
asset_ready="$(
|
|
find release/desktop -maxdepth 1 -type f -exec basename {} \; |
|
|
node scripts/check-native-release-assets.mjs desktop true "v${version}" "$version" allow-missing-windows-signature
|
|
)"
|
|
test "$asset_ready" = "true"
|
|
|
|
- name: Upload verified desktop assets to Release
|
|
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.release_tag }}
|
|
run: |
|
|
gh release upload "$RELEASE_TAG" release/desktop/* \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--clobber
|
|
|
|
- name: Upload combined desktop installers and metadata
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: edgeever-desktop-macOS-Windows-and-Linux-x64
|
|
path: release/desktop/*
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
|
|
windows-update-audit:
|
|
needs: release-plan
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'true' &&
|
|
(
|
|
github.event_name == 'release' ||
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag != '')
|
|
)
|
|
}}
|
|
name: Audit signed Windows update
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Download Windows release assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.release_tag || github.event.release.tag_name }}
|
|
run: |
|
|
mkdir -p release/desktop
|
|
node scripts/download-release-assets.mjs \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--tag "$RELEASE_TAG" \
|
|
--dir release/desktop \
|
|
--pattern 'EdgeEver-*-windows-x64.exe' \
|
|
--pattern 'latest.yml' \
|
|
--pattern 'latest-windows.json' \
|
|
--pattern 'latest-windows.json.sig' \
|
|
--pattern 'SHA256SUMS-windows.txt'
|
|
|
|
- name: Verify signature and installer digests
|
|
run: node scripts/verify-windows-update-release.mjs release/desktop
|
|
|
|
linux-release-audit:
|
|
needs: release-plan
|
|
if: >-
|
|
${{
|
|
always() &&
|
|
github.repository == 'tianma-if/edgeever' &&
|
|
needs.release-plan.result == 'success' &&
|
|
needs.release-plan.outputs.asset_ready == 'true' &&
|
|
(
|
|
github.event_name == 'release' ||
|
|
(github.event_name == 'workflow_dispatch' && inputs.release_tag != '')
|
|
)
|
|
}}
|
|
name: Audit Linux Preview asset
|
|
runs-on: ubuntu-22.04
|
|
steps:
|
|
- name: Check out source
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Download Linux release assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.release_tag || github.event.release.tag_name }}
|
|
run: |
|
|
mkdir -p release/desktop
|
|
node scripts/download-release-assets.mjs \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--tag "$RELEASE_TAG" \
|
|
--dir release/desktop \
|
|
--pattern 'EdgeEver-*-linux-x64.AppImage' \
|
|
--pattern 'latest-linux.yml' \
|
|
--pattern 'SHA256SUMS-linux.txt'
|
|
|
|
- name: Verify Linux automatic update release
|
|
run: node scripts/verify-linux-update-release.mjs release/desktop
|