Files
fusion/.github/workflows/test-release.yml
dependabot[bot] 6e416b044d chore(deps): bump actions/download-artifact from 4 to 8 (#3304)
Bumps
[actions/download-artifact](https://github.com/actions/download-artifact)
from 4 to 8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/download-artifact/releases">actions/download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v8.0.0</h2>
<h2>v8 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/download-artifact@v8 has been migrated to an ESM module. This
should be transparent to the caller but forks might need to make
significant changes.</p>
</blockquote>
<blockquote>
<p>[!IMPORTANT]
Hash mismatches will now error by default. Users can override this
behavior with a setting change (see below).</p>
</blockquote>
<h3>Direct downloads</h3>
<p>To support direct uploads in <code>actions/upload-artifact</code>,
the action will no longer attempt to unzip all downloaded files.
Instead, the action checks the <code>Content-Type</code> header ahead of
unzipping and skips non-zipped files. Callers wishing to download a
zipped file as-is can also set the new <code>skip-decompress</code>
parameter to <code>true</code>.</p>
<h3>Enforced checks (breaking)</h3>
<p>A previous release introduced digest checks on the download. If a
download hash didn't match the expected hash from the server, the action
would log a warning. Callers can now configure the behavior on mismatch
with the <code>digest-mismatch</code> parameter. To be secure by
default, we are now defaulting the behavior to <code>error</code> which
will fail the workflow run.</p>
<h3>ESM</h3>
<p>To support new versions of the @actions/* packages, we've upgraded
the package to ESM.</p>
<h2>What's Changed</h2>
<ul>
<li>Don't attempt to un-zip non-zipped downloads by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/460">actions/download-artifact#460</a></li>
<li>Add a setting to specify what to do on hash mismatch and default it
to <code>error</code> by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/download-artifact/pull/461">actions/download-artifact#461</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v7...v8.0.0">https://github.com/actions/download-artifact/compare/v7...v8.0.0</a></p>
<h2>v7.0.0</h2>
<h2>v7 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/download-artifact@v7 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v6 had preliminary
support for Node 24, however this action was by default still running on
Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li>Download Artifact Node24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
<li>fix: update <code>@​actions/artifact</code> to fix Node.js 24
punycode deprecation by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/451">actions/download-artifact#451</a></li>
<li>prepare release v7.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/download-artifact/pull/452">actions/download-artifact#452</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/440">actions/download-artifact#440</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/download-artifact/pull/415">actions/download-artifact#415</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0">https://github.com/actions/download-artifact/compare/v6.0.0...v7.0.0</a></p>
<h2>v6.0.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3e5f45b2cf"><code>3e5f45b</code></a>
Add regression tests for CJK characters (<a
href="https://redirect.github.com/actions/download-artifact/issues/471">#471</a>)</li>
<li><a
href="e6d03f6737"><code>e6d03f6</code></a>
Add a regression test for artifact name + content-type mismatches (<a
href="https://redirect.github.com/actions/download-artifact/issues/472">#472</a>)</li>
<li><a
href="70fc10c6e5"><code>70fc10c</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/461">#461</a>
from actions/danwkennedy/digest-mismatch-behavior</li>
<li><a
href="f258da9a50"><code>f258da9</code></a>
Add change docs</li>
<li><a
href="ccc058e5fb"><code>ccc058e</code></a>
Fix linting issues</li>
<li><a
href="bd7976ba57"><code>bd7976b</code></a>
Add a setting to specify what to do on hash mismatch and default it to
<code>error</code></li>
<li><a
href="ac21fcf45e"><code>ac21fcf</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/download-artifact/issues/460">#460</a>
from actions/danwkennedy/download-no-unzip</li>
<li><a
href="15999bff51"><code>15999bf</code></a>
Add note about package bumps</li>
<li><a
href="974686ed50"><code>974686e</code></a>
Bump the version to <code>v8</code> and add release notes</li>
<li><a
href="fbe48b1d27"><code>fbe48b1</code></a>
Update test names to make it clearer what they do</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/download-artifact/compare/v4...v8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/download-artifact&package-manager=github_actions&previous-version=4&new-version=8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-08-02 22:53:56 -07:00

539 lines
22 KiB
YAML

# Test Release workflow
#
# Manual workflow for testing binary builds without creating a real release.
# Triggered via workflow_dispatch from the GitHub Actions UI.
name: Test Release
on:
workflow_dispatch:
jobs:
# ── Build and test platform-specific binaries ─────────────────────────
build-binaries:
name: Build & Test ${{ matrix.target }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: bun-linux-x64
binary: fn-cli-linux-x64
- os: ubuntu-24.04-arm
target: bun-linux-arm64
binary: fn-cli-linux-arm64
- os: macos-latest
target: bun-darwin-arm64
binary: fn-cli-darwin-arm64
# bun-darwin-x64 (Intel) dropped: macos-13 runner scarcity — CLI is
# Apple-Silicon-only for macOS. Keep in sync with release.yml.
- os: windows-latest
target: bun-windows-x64
binary: fn-cli-windows-x64.exe
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Install Bun
uses: oven-sh/setup-bun@v2
- name: Build
run: pnpm build
- name: Build binary
run: pnpm --filter @runfusion/fusion build:exe -- --target ${{ matrix.target }}
- name: Verify binary exists
shell: bash
run: test -f packages/cli/dist/${{ matrix.binary }}
- name: Smoke test (Linux/macOS)
if: runner.os != 'Windows'
run: |
chmod +x packages/cli/dist/${{ matrix.binary }}
packages/cli/dist/${{ matrix.binary }} --help
- name: Smoke test (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
& packages/cli/dist/${{ matrix.binary }} --help
- name: Sign macOS binary
if: runner.os == 'macOS' && env.APPLE_CERTIFICATE_BASE64 != ''
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_IDENTITY: ${{ secrets.APPLE_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
run: bash scripts/sign-macos.sh packages/cli/dist/${{ matrix.binary }} packages/cli/dist/runtime
- name: Sign Windows binary
if: runner.os == 'Windows' && env.WINDOWS_CERTIFICATE_BASE64 != ''
env:
WINDOWS_CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_BASE64 }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: pwsh scripts/sign-windows.ps1 packages/cli/dist/${{ matrix.binary }}
- name: Generate checksum (Linux)
if: runner.os == 'Linux'
run: |
cd packages/cli/dist
sha256sum ${{ matrix.binary }} > ${{ matrix.binary }}.sha256
- name: Generate checksum (macOS)
if: runner.os == 'macOS'
run: |
cd packages/cli/dist
shasum -a 256 ${{ matrix.binary }} > ${{ matrix.binary }}.sha256
- name: Generate checksum (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
cd packages/cli/dist
$hash = (Get-FileHash ${{ matrix.binary }} -Algorithm SHA256).Hash.ToLower()
"$hash ${{ matrix.binary }}" | Out-File -Encoding ascii ${{ matrix.binary }}.sha256
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.binary }}
path: |
packages/cli/dist/${{ matrix.binary }}
packages/cli/dist/${{ matrix.binary }}.sha256
packages/cli/dist/runtime/**/*
# ── Build Windows desktop EXE artifacts ──────────────────────────────
# Code-signing with WINDOWS_CERTIFICATE_BASE64 / WINDOWS_CERTIFICATE_PASSWORD
# is intentionally deferred to FN-5592. Desktop Windows remains x64 because
# embedded-postgres does not publish a native Windows ARM64 payload.
build-desktop-windows:
name: Build Desktop Windows EXE
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
# FNXC:DesktopEmbeddedPostgres 2026-07-14-09:39:
# Test releases use the same native database proof as production releases.
- name: Smoke embedded Postgres on Windows
run: pnpm --filter @fusion/core test:embedded-postgres
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package Windows desktop EXE
run: pnpm --filter @fusion/desktop dist:win -- --publish never
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify desktop EXE artifacts
shell: pwsh
run: |
$exes = Get-ChildItem packages/desktop/dist-electron -Filter "Fusion-*-win-*.exe"
if ($exes.Count -eq 0) {
Write-Error "No Fusion Windows EXE artifacts produced"
exit 1
}
- name: Generate desktop EXE checksums
shell: pwsh
run: |
$exes = Get-ChildItem packages/desktop/dist-electron -Filter "Fusion-*-win-*.exe"
foreach ($exe in $exes) {
$hash = (Get-FileHash $exe.FullName -Algorithm SHA256).Hash.ToLower()
"$hash $($exe.Name)" | Out-File -Encoding ascii "$($exe.FullName).sha256"
}
- name: Upload desktop Windows artifacts
uses: actions/upload-artifact@v7
with:
name: fusion-desktop-windows
path: |
packages/desktop/dist-electron/Fusion-*-win-*.exe
packages/desktop/dist-electron/Fusion-*-win-*.exe.sha256
packages/desktop/dist-electron/Fusion-*-win-*.exe.blockmap
packages/desktop/dist-electron/latest.yml
# ── Build macOS desktop artifacts ────────────────────────────────────
build-desktop-macos:
name: Build Desktop macOS DMG/ZIP
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Smoke embedded Postgres on macOS
run: pnpm --filter @fusion/core test:embedded-postgres
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package signed macOS desktop DMG/ZIP
if: ${{ env.APPLE_CERTIFICATE_BASE64 != '' }}
run: pnpm --filter @fusion/desktop dist:mac -- --publish never
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_IDENTITY_AUTO_DISCOVERY: "true"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Package unsigned macOS desktop DMG/ZIP
if: ${{ env.APPLE_CERTIFICATE_BASE64 == '' }}
run: pnpm --filter @fusion/desktop dist:mac -- --publish never -c.mac.notarize=false
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
CSC_IDENTITY_AUTO_DISCOVERY: "false"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify signed and notarized macOS artifacts
if: ${{ env.APPLE_CERTIFICATE_BASE64 != '' }}
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
dmgs=(packages/desktop/dist-electron/Fusion-*-mac-*.dmg)
zips=(packages/desktop/dist-electron/Fusion-*-mac-*.zip)
if [ ${#dmgs[@]} -eq 0 ]; then
echo "No Fusion macOS DMG artifacts produced" >&2
exit 1
fi
if [ ${#zips[@]} -eq 0 ]; then
echo "No Fusion macOS ZIP artifacts produced" >&2
exit 1
fi
for dmg in "${dmgs[@]}"; do
echo "Verifying signed DMG: $dmg"
codesign --verify --deep --strict --verbose=2 "$dmg"
spctl --assess --type open --context context:primary-signature -v "$dmg"
xcrun stapler validate "$dmg"
done
for zip in "${zips[@]}"; do
echo "Verifying ZIP app bundle: $zip"
tmpdir="$(mktemp -d)"
unzip -q "$zip" -d "$tmpdir"
app="$(find "$tmpdir" -type d -name '*.app' -print -quit)"
if [ -z "$app" ]; then
echo "No .app bundle found in $zip" >&2
rm -rf "$tmpdir"
exit 1
fi
codesign --verify --deep --strict --verbose=2 "$app"
spctl --assess --type exec -v "$app"
xcrun stapler validate "$app"
rm -rf "$tmpdir"
done
- name: Generate desktop macOS checksums
shell: bash
run: |
shopt -s nullglob
for file in packages/desktop/dist-electron/Fusion-*-mac-*.dmg packages/desktop/dist-electron/Fusion-*-mac-*.zip; do
shasum -a 256 "$file" > "$file.sha256"
done
- name: Upload desktop macOS artifacts
uses: actions/upload-artifact@v7
with:
name: fusion-desktop-macos
path: |
packages/desktop/dist-electron/Fusion-*-mac-*.dmg
packages/desktop/dist-electron/Fusion-*-mac-*.dmg.sha256
packages/desktop/dist-electron/Fusion-*-mac-*.zip
packages/desktop/dist-electron/Fusion-*-mac-*.zip.sha256
packages/desktop/dist-electron/Fusion-*-mac-*.blockmap
packages/desktop/dist-electron/latest-mac.yml
# ── Build Linux desktop artifacts ────────────────────────────────────
build-desktop-linux:
name: Build Desktop Linux Artifacts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Smoke embedded Postgres on Linux
run: pnpm --filter @fusion/core test:embedded-postgres
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package Linux desktop artifacts
# Linux desktop code-signing is deferred to FN-5605; Linux ARM64 CLI binaries are tracked in FN-5606.
run: pnpm --filter @fusion/desktop dist:linux -- --x64 --arm64 --publish never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify desktop Linux AppImage artifacts
shell: bash
run: |
shopt -s nullglob
arm64_appimages=(packages/desktop/dist-electron/Fusion-*-linux-arm64.AppImage)
x64_appimages=(packages/desktop/dist-electron/Fusion-*-linux-x86_64.AppImage)
if [ ${#arm64_appimages[@]} -eq 0 ]; then
echo "No Fusion Linux arm64 AppImage artifacts produced" >&2
exit 1
fi
if [ ${#x64_appimages[@]} -eq 0 ]; then
echo "No Fusion Linux x64 AppImage artifacts produced" >&2
exit 1
fi
# FNXC:DesktopEmbeddedPostgres 2026-07-15-00:20:
# AppImage filename presence is not enough — v0.60.0 shipped without
# embedded-postgres / main-bootstrap / omp-runtime. Inspect the linux-*-unpacked
# trees electron-builder leaves beside the AppImage.
- name: Verify Linux AppImage embedded Postgres packaging
run: node scripts/verify-desktop-linux-pg-packaging.mjs
- name: Sign Linux desktop artifacts
if: ${{ env.LINUX_GPG_PRIVATE_KEY != '' }}
env:
LINUX_GPG_PRIVATE_KEY: ${{ secrets.LINUX_GPG_PRIVATE_KEY }}
LINUX_GPG_PASSPHRASE: ${{ secrets.LINUX_GPG_PASSPHRASE }}
LINUX_GPG_KEY_ID: ${{ secrets.LINUX_GPG_KEY_ID }}
shell: bash
run: |
shopt -s nullglob
artifacts=(
packages/desktop/dist-electron/Fusion-*-linux-*.AppImage
packages/desktop/dist-electron/Fusion-*-linux-*.deb
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz
)
bash scripts/sign-linux.sh "${artifacts[@]}"
- name: Generate desktop Linux checksums
shell: bash
run: |
shopt -s nullglob
for file in packages/desktop/dist-electron/Fusion-*-linux-*.AppImage packages/desktop/dist-electron/Fusion-*-linux-*.deb packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz; do
sha256sum "$file" > "$file.sha256"
done
- name: Upload desktop Linux artifacts
uses: actions/upload-artifact@v7
with:
# Single glob set covers both linux-x64 and linux-arm64 artifact filenames.
name: fusion-desktop-linux
if-no-files-found: ignore
path: |
packages/desktop/dist-electron/Fusion-*-linux-*.AppImage
packages/desktop/dist-electron/Fusion-*-linux-*.AppImage.sha256
packages/desktop/dist-electron/Fusion-*-linux-*.AppImage.asc
packages/desktop/dist-electron/Fusion-*-linux-*.deb
packages/desktop/dist-electron/Fusion-*-linux-*.deb.sha256
packages/desktop/dist-electron/Fusion-*-linux-*.deb.asc
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz.sha256
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz.asc
packages/desktop/dist-electron/latest-linux.yml
# ── Build Android APK/AAB artifacts ──────────────────────────────────
# FNXC:Release 2026-06-25-12:00:
# Keep the tag-less rehearsal workflow in parity with release.yml so APK
# generation, checksum output, and artifact collection are validated before a
# version tag tries to publish the Android asset publicly.
# FNXC:Release 2026-06-25-18:10:
# Android signing is optional and secret-gated on ANDROID_KEYSTORE_BASE64,
# ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and ANDROID_KEY_PASSWORD.
# The Capacitor Android project is gitignored/regenerated, so CI injects
# signing with android.injected.signing.* Gradle properties instead of
# committing native build.gradle edits. When the keystore is absent, keep the
# FN-7014 unsigned debug APK fallback; Play Store upload remains out of scope
# and is tracked separately from sideload release artifacts.
build-android:
name: Build Android APK/AAB
runs-on: ubuntu-latest
timeout-minutes: 30
# Job-level env mirrors the desktop signing pattern: step `if:` conditions
# can inspect env values, but cannot read secrets.* directly.
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
# FNXC:MobileAndroidBuild 2026-06-28-00:00:
# Capacitor 7 @capacitor/android compiles its Android library with JavaVersion.VERSION_21; rehearsal APK/AAB Gradle builds must provision JDK 21 because JDK 17 fails with `invalid source release: 21`.
- name: Setup Java 21
uses: actions/setup-java@v5.6.0
with:
distribution: temurin
java-version: "21"
- name: Cache Android Gradle caches
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('pnpm-lock.yaml', 'packages/mobile/capacitor.config.ts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build dashboard client
run: pnpm --filter @fusion/dashboard build
- name: Create and sync Capacitor Android project
run: |
# FNXC:Release 2026-06-25-12:00:
# The Capacitor Android platform directory is gitignored and absent in
# clean release checkouts, so initialize it from pinned package metadata
# before syncing web assets instead of silently skipping the APK leg.
if [ ! -d packages/mobile/android ]; then
pnpm --filter @fusion/mobile cap add android
fi
pnpm --filter @fusion/mobile cap sync android
- name: Decode Android signing keystore
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
run: |
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/fusion-release.keystore"
- name: Build signed Android release APK and AAB
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
env:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
cd packages/mobile/android
chmod +x gradlew
./gradlew assembleRelease bundleRelease \
-Pandroid.injected.signing.store.file="$RUNNER_TEMP/fusion-release.keystore" \
-Pandroid.injected.signing.store.password="$ANDROID_KEYSTORE_PASSWORD" \
-Pandroid.injected.signing.key.alias="$ANDROID_KEY_ALIAS" \
-Pandroid.injected.signing.key.password="$ANDROID_KEY_PASSWORD"
- name: Normalize signed Android release assets
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
run: |
APK="packages/mobile/android/app/build/outputs/apk/release/app-release.apk"
AAB="packages/mobile/android/app/build/outputs/bundle/release/app-release.aab"
if [ ! -f "$APK" ]; then
echo "::error::Expected signed Android APK missing at $APK" >&2
exit 1
fi
if [ ! -f "$AAB" ]; then
echo "::error::Expected signed Android AAB missing at $AAB" >&2
exit 1
fi
mkdir -p packages/mobile/dist
cp "$APK" packages/mobile/dist/fusion-android-release.apk
cp "$AAB" packages/mobile/dist/fusion-android-release.aab
- name: Verify signed Android APK signature
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
run: |
APK="packages/mobile/dist/fusion-android-release.apk"
APKSIGNER=""
if [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "$ANDROID_SDK_ROOT/build-tools" ]; then
APKSIGNER=$(find "$ANDROID_SDK_ROOT/build-tools" -maxdepth 2 -type f -name apksigner | sort -V | tail -n 1 || true)
fi
if [ -n "$APKSIGNER" ]; then
"$APKSIGNER" verify --verbose "$APK"
else
jarsigner -verify -strict "$APK"
fi
- name: Build unsigned Android debug APK
if: ${{ env.ANDROID_KEYSTORE_BASE64 == '' }}
run: |
cd packages/mobile/android
chmod +x gradlew
./gradlew assembleDebug
- name: Normalize unsigned Android APK asset
if: ${{ env.ANDROID_KEYSTORE_BASE64 == '' }}
run: |
APK="packages/mobile/android/app/build/outputs/apk/debug/app-debug.apk"
if [ ! -f "$APK" ]; then
echo "::error::Expected Android APK missing at $APK" >&2
exit 1
fi
mkdir -p packages/mobile/dist
cp "$APK" packages/mobile/dist/fusion-android.apk
- name: Generate Android artifact checksums
run: |
cd packages/mobile/dist
for file in fusion-android*.apk fusion-android-release.aab; do
[ -f "$file" ] || continue
sha256sum "$file" > "$file.sha256"
done
- name: Upload Android artifacts
uses: actions/upload-artifact@v7
with:
name: fusion-android-apk
path: |
packages/mobile/dist/fusion-android*.apk
packages/mobile/dist/fusion-android*.apk.sha256
packages/mobile/dist/fusion-android-release.aab
packages/mobile/dist/fusion-android-release.aab.sha256
# ── Collect all artifacts ─────────────────────────────────────────────
collect:
name: Collect Artifacts
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux, build-android]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
- name: Combine artifacts
run: |
mkdir combined
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -o -name "*.asc" -o -name "*.exe" -o -name "*.exe.sha256" -o -name "*.blockmap" -o -name "*.dmg" -o -name "*.dmg.sha256" -o -name "*.zip" -o -name "*.zip.sha256" -o -name "*.apk" -o -name "*.aab" -o -name "*.AppImage" -o -name "*.AppImage.sha256" -o -name "*.deb" -o -name "*.deb.sha256" -o -name "*.tar.gz" -o -name "*.tar.gz.sha256" -o -name "latest*.yml" \) -exec cp {} combined/ \;
ls -la combined/
- name: Upload combined archive
uses: actions/upload-artifact@v7
with:
name: all-binaries
path: combined/*