FN-7014: publish Android APK release artifacts
Adds Android APK generation to binary release and rehearsal workflows so GitHub releases ship mobile assets. - Add Android build jobs that sync Capacitor, assemble the debug APK, and upload APK/checksum artifacts. - Include Android artifacts in release and test-release collection dependencies and file matching. - Document Android release outputs and extend workflow shape tests for the new asset path. Files changed: .github/workflows/release.yml | 86 +++++++++++++++++++++- .github/workflows/test-release.yml | 85 ++++++++++++++++++++- MOBILE.md | 2 +- RELEASING.md | 10 ++- packages/cli/src/__tests__/ci-workflow.test.ts | 6 +- packages/desktop/README.md | 1 + .../desktop/src/__tests__/release-workflow.test.ts | 24 +++++- 7 files changed, 201 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-7014 Fusion-Task-Lineage: 8ed94f64-399b-433f-add4-0294fc5722d1
This commit is contained in:
86
.github/workflows/release.yml
vendored
86
.github/workflows/release.yml
vendored
@@ -358,10 +358,92 @@ jobs:
|
||||
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz.asc
|
||||
packages/desktop/dist-electron/latest-linux.yml
|
||||
|
||||
|
||||
# ── Build Android APK artifact ───────────────────────────────────────
|
||||
# FNXC:Release 2026-06-25-12:00:
|
||||
# Android release assets used to be limited to the manual mobile workflow's
|
||||
# short-lived CI artifacts. Tagged binary releases now build the Capacitor
|
||||
# Android shell in this workflow so the public GitHub Release includes a
|
||||
# stable APK and checksum beside desktop and CLI binaries.
|
||||
build-android:
|
||||
name: Build Android APK
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node and install dependencies
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
|
||||
- name: Cache Android Gradle caches
|
||||
uses: actions/cache@v4
|
||||
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: Build Android APK
|
||||
run: |
|
||||
cd packages/mobile/android
|
||||
chmod +x gradlew
|
||||
./gradlew assembleDebug
|
||||
|
||||
- name: Normalize Android APK asset
|
||||
run: |
|
||||
# FNXC:Release 2026-06-25-12:00:
|
||||
# Ship the secret-free debug APK because this repo has no Android
|
||||
# signing keystore configured; signed release APK/AAB distribution is a
|
||||
# separate product task, not a binary-release plumbing prerequisite.
|
||||
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 APK checksum
|
||||
run: |
|
||||
cd packages/mobile/dist
|
||||
sha256sum fusion-android.apk > fusion-android.apk.sha256
|
||||
|
||||
- name: Upload Android APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fusion-android-apk
|
||||
path: |
|
||||
packages/mobile/dist/fusion-android.apk
|
||||
packages/mobile/dist/fusion-android.apk.sha256
|
||||
|
||||
# ── Create GitHub Release ─────────────────────────────────────────────
|
||||
github-release:
|
||||
name: Create GitHub Release
|
||||
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]
|
||||
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux, build-android]
|
||||
# Run as long as the workflow wasn't cancelled, even if some build legs failed,
|
||||
# so a single failing matrix leg doesn't suppress publishing the ones that did
|
||||
# build. Gated to tag pushes only: a workflow_dispatch run on a branch is a
|
||||
@@ -394,7 +476,7 @@ jobs:
|
||||
id: collect
|
||||
run: |
|
||||
mkdir release-files
|
||||
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 "*.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 {} release-files/ \;
|
||||
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 "*.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 {} release-files/ \;
|
||||
ls -la release-files/
|
||||
count=$(find release-files -type f | wc -l | tr -d ' ')
|
||||
echo "count=$count" >> "$GITHUB_OUTPUT"
|
||||
|
||||
85
.github/workflows/test-release.yml
vendored
85
.github/workflows/test-release.yml
vendored
@@ -345,10 +345,91 @@ jobs:
|
||||
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz.asc
|
||||
packages/desktop/dist-electron/latest-linux.yml
|
||||
|
||||
|
||||
# ── Build Android APK artifact ───────────────────────────────────────
|
||||
# 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.
|
||||
build-android:
|
||||
name: Build Android APK
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node and install dependencies
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
|
||||
- name: Setup Java 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
|
||||
- name: Cache Android Gradle caches
|
||||
uses: actions/cache@v4
|
||||
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: Build Android APK
|
||||
run: |
|
||||
cd packages/mobile/android
|
||||
chmod +x gradlew
|
||||
./gradlew assembleDebug
|
||||
|
||||
- name: Normalize Android APK asset
|
||||
run: |
|
||||
# FNXC:Release 2026-06-25-12:00:
|
||||
# Ship the secret-free debug APK because this repo has no Android
|
||||
# signing keystore configured; signed release APK/AAB distribution is a
|
||||
# separate product task, not a binary-release plumbing prerequisite.
|
||||
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 APK checksum
|
||||
run: |
|
||||
cd packages/mobile/dist
|
||||
sha256sum fusion-android.apk > fusion-android.apk.sha256
|
||||
|
||||
- name: Upload Android APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fusion-android-apk
|
||||
path: |
|
||||
packages/mobile/dist/fusion-android.apk
|
||||
packages/mobile/dist/fusion-android.apk.sha256
|
||||
|
||||
# ── Collect all artifacts ─────────────────────────────────────────────
|
||||
collect:
|
||||
name: Collect Artifacts
|
||||
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]
|
||||
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux, build-android]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -360,7 +441,7 @@ jobs:
|
||||
- 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 "*.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/ \;
|
||||
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 "*.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
|
||||
|
||||
@@ -111,7 +111,7 @@ Mobile CI is defined in `.github/workflows/mobile.yml`.
|
||||
- `build-ios` (sync/build iOS when `packages/mobile/ios/` exists)
|
||||
- `build-android` (sync/build Android when `packages/mobile/android/` exists)
|
||||
|
||||
Artifacts are retained for 30 days.
|
||||
Artifacts from the Mobile Builds workflow are retained for 30 days. Tagged binary releases also run the Android build leg in `.github/workflows/release.yml` and publish `fusion-android.apk` plus `fusion-android.apk.sha256` as GitHub Release assets; `.github/workflows/test-release.yml` mirrors that path in its tag-less rehearsal artifact.
|
||||
|
||||
## Replacing PWA Icons
|
||||
|
||||
|
||||
10
RELEASING.md
10
RELEASING.md
@@ -57,16 +57,17 @@ When you merge the Version Packages PR:
|
||||
- It creates a git tag `v{version}` based on the `kb` CLI package version
|
||||
- The tag push triggers `release.yml`, which:
|
||||
- Builds platform-specific binaries for Linux x64, macOS x64, macOS arm64, and Windows x64
|
||||
- Builds the Android APK as `fusion-android.apk`
|
||||
- Signs macOS binaries (codesign + notarization) and Windows binaries (Authenticode)
|
||||
- Generates SHA256 checksums for all binaries
|
||||
- Creates a **GitHub Release** with all binaries and checksums attached
|
||||
- Generates SHA256 checksums for all binaries and the Android APK
|
||||
- Creates a **GitHub Release** with all binaries, the Android APK, and checksums attached
|
||||
|
||||
## Release channels
|
||||
|
||||
| Channel | Workflow | Trigger | Output |
|
||||
|---------|----------|---------|--------|
|
||||
| npm | `version.yml` | Push to `main` | npm packages with provenance |
|
||||
| GitHub Release | `release.yml` | Version tag (`v*`) | Signed platform binaries + checksums |
|
||||
| GitHub Release | `release.yml` | Version tag (`v*`) | Signed platform binaries, Android APK + checksums |
|
||||
|
||||
## Platform binaries
|
||||
|
||||
@@ -75,6 +76,7 @@ When you merge the Version Packages PR:
|
||||
| Linux x64 | `fusion-linux-x64` | — |
|
||||
| macOS arm64 | `fusion-darwin-arm64` | ✓ (codesign + notarization) |
|
||||
| Windows x64 | `fusion-windows-x64.exe` | ✓ (Authenticode) |
|
||||
| Android | `fusion-android.apk` | — (debug/unsigned APK) |
|
||||
|
||||
> macOS Intel (`darwin-x64`) is intentionally not shipped: the CLI is Apple-Silicon-only because `macos-13` GitHub runners are too scarce to build reliably. The desktop macOS DMG/ZIP remains universal.
|
||||
|
||||
@@ -83,7 +85,7 @@ When you merge the Version Packages PR:
|
||||
Use the **Test Release** workflow (`test-release.yml`) to manually test binary builds without creating a real release:
|
||||
|
||||
1. Go to **Actions** → **Test Release** → **Run workflow**
|
||||
2. The workflow builds all 4 platform binaries, runs smoke tests, and uploads artifacts
|
||||
2. The workflow builds all 4 platform binaries plus the Android APK, runs smoke tests, and uploads artifacts
|
||||
3. Download the `all-binaries` artifact to inspect the output
|
||||
|
||||
## Manual release (fallback)
|
||||
|
||||
@@ -414,8 +414,9 @@ describe("Binary release workflow (.github/workflows/release.yml)", () => {
|
||||
expect(workflow.permissions.contents).toBe("write");
|
||||
});
|
||||
|
||||
it("has github-release job that depends on build-binaries", () => {
|
||||
it("has github-release job that depends on binary and Android builds", () => {
|
||||
expect(workflow.jobs["github-release"].needs).toContain("build-binaries");
|
||||
expect(workflow.jobs["github-release"].needs).toContain("build-android");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -478,9 +479,10 @@ describe("Test-release workflow (.github/workflows/test-release.yml)", () => {
|
||||
expect(content).toContain("actions/upload-artifact");
|
||||
});
|
||||
|
||||
it("has a collect job that combines artifacts", () => {
|
||||
it("has a collect job that combines binary and Android artifacts", () => {
|
||||
expect(workflow.jobs.collect).toBeDefined();
|
||||
expect(workflow.jobs.collect.needs).toContain("build-binaries");
|
||||
expect(workflow.jobs.collect.needs).toContain("build-android");
|
||||
expect(content).toContain("all-binaries");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -364,6 +364,7 @@ Desktop packaging is configured in `electron-builder.yml`.
|
||||
- Windows: x64 + arm64 outputs (NSIS + portable), matching `.exe.sha256` sidecars, and `.blockmap` files.
|
||||
- macOS: `Fusion-<version>-mac-arm64.dmg`, `Fusion-<version>-mac-x64.dmg`, matching `.zip` variants, `.sha256` sidecars, and `.blockmap` files.
|
||||
- Linux: `Fusion-<version>-linux-x64.AppImage` and `Fusion-<version>-linux-arm64.AppImage` with matching `.sha256` sidecars, plus best-effort `.deb` and `.tar.gz` outputs per arch (`Fusion-<version>-linux-x64.{deb,tar.gz}` / `Fusion-<version>-linux-arm64.{deb,tar.gz}`) and sidecars when available on the runner image.
|
||||
- Android: `fusion-android.apk` and `fusion-android.apk.sha256` from the Capacitor/Gradle debug APK build.
|
||||
- Tag-less release rehearsal workflow (`.github/workflows/test-release.yml`) mirrors that artifact collection path without publishing a real GitHub Release.
|
||||
- Linux ARM64 artifacts are cross-built from the `ubuntu-latest` x64 runner by passing `electron-builder --linux --x64 --arm64`; running/validating arm64 installers still requires an arm64 Linux device or emulator.
|
||||
- Linux desktop artifacts can include detached GPG signature sidecars (`*.AppImage.asc`, `*.deb.asc`, `*.tar.gz.asc`) when Linux signing secrets are configured in CI; full Linux desktop code-signing rollout remains tracked in FN-5605.
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("desktop release workflow wiring", () => {
|
||||
const release = await readRepoFile(".github/workflows/release.yml");
|
||||
|
||||
expect(release).toContain(
|
||||
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]",
|
||||
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux, build-android]",
|
||||
);
|
||||
expect(release).toContain('find artifacts -type f \\(');
|
||||
expect(release).toContain('-name "*.exe"');
|
||||
@@ -63,10 +63,30 @@ describe("desktop release workflow wiring", () => {
|
||||
const testRelease = await readRepoFile(".github/workflows/test-release.yml");
|
||||
|
||||
expect(testRelease).toContain(
|
||||
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]",
|
||||
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux, build-android]",
|
||||
);
|
||||
expect(testRelease).toContain('-name "latest*.yml"');
|
||||
});
|
||||
|
||||
it("adds Android APK build and aggregation wiring to release workflows", async () => {
|
||||
const release = await readRepoFile(".github/workflows/release.yml");
|
||||
const testRelease = await readRepoFile(".github/workflows/test-release.yml");
|
||||
|
||||
for (const workflow of [release, testRelease]) {
|
||||
expect(workflow).toContain("build-android:");
|
||||
expect(workflow).toContain("runs-on: ubuntu-latest");
|
||||
expect(workflow).toContain("actions/setup-java@v4");
|
||||
expect(workflow).toContain('java-version: "17"');
|
||||
expect(workflow).toContain("pnpm --filter @fusion/mobile cap add android");
|
||||
expect(workflow).toContain("pnpm --filter @fusion/mobile cap sync android");
|
||||
expect(workflow).toContain("./gradlew assembleDebug");
|
||||
expect(workflow).toContain("packages/mobile/android/app/build/outputs/apk/debug/app-debug.apk");
|
||||
expect(workflow).toContain("packages/mobile/dist/fusion-android.apk");
|
||||
expect(workflow).toContain("sha256sum fusion-android.apk > fusion-android.apk.sha256");
|
||||
expect(workflow).toContain("name: fusion-android-apk");
|
||||
expect(workflow).toContain('-name "*.apk"');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("desktop macos signing wiring", () => {
|
||||
|
||||
Reference in New Issue
Block a user