feat(FN-5603): add desktop release jobs for macOS and Linux with artifact a

Added GitHub Actions workflows for macOS and Linux desktop release artifacts (`release.yml` and `test-release.yml`), wired in new `dist` scripts across platforms, and extended test coverage for electron-builder config and release workflow logic, with updated documentation for the new artifacts.

Fusion-Task-Id: FN-5603

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5603
This commit is contained in:
gsxdsm
2026-05-27 00:24:05 -07:00
parent 699556d5a0
commit 0d0d07ded4
6 changed files with 296 additions and 11 deletions

View File

@@ -159,10 +159,124 @@ jobs:
packages/desktop/dist-electron/Fusion-*-win-*.exe.sha256
packages/desktop/dist-electron/Fusion-*-win-*.exe.blockmap
# ── Build macOS desktop artifacts ────────────────────────────────────
# macOS Developer ID signing/notarization is intentionally deferred;
# this job emits unsigned .dmg/.zip release artifacts.
build-desktop-macos:
name: Build Desktop macOS DMG/ZIP
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package macOS desktop artifacts
run: pnpm --filter @fusion/desktop dist:mac -- --publish never
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify desktop macOS artifacts
shell: bash
run: |
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
- 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@v4
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
# ── Build Linux desktop artifacts ────────────────────────────────────
build-desktop-linux:
name: Build Desktop Linux Artifacts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package Linux desktop artifacts
run: pnpm --filter @fusion/desktop dist:linux -- --publish never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify desktop Linux AppImage artifact
shell: bash
run: |
shopt -s nullglob
appimages=(packages/desktop/dist-electron/Fusion-*-linux-*.AppImage)
if [ ${#appimages[@]} -eq 0 ]; then
echo "No Fusion Linux AppImage artifacts produced" >&2
exit 1
fi
- 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@v4
with:
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-*.deb
packages/desktop/dist-electron/Fusion-*-linux-*.deb.sha256
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz.sha256
# ── Create GitHub Release ─────────────────────────────────────────────
github-release:
name: Create GitHub Release
needs: [build-binaries, build-desktop-windows]
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -176,7 +290,7 @@ jobs:
- name: Collect release files
run: |
mkdir release-files
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -o -name "*.exe" -o -name "*.exe.sha256" -o -name "*.blockmap" \) -exec cp {} release-files/ \;
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -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" \) -exec cp {} release-files/ \;
ls -la release-files/
- name: Create GitHub Release

View File

@@ -162,10 +162,124 @@ jobs:
packages/desktop/dist-electron/Fusion-*-win-*.exe.sha256
packages/desktop/dist-electron/Fusion-*-win-*.exe.blockmap
# ── Build macOS desktop artifacts ────────────────────────────────────
# macOS Developer ID signing/notarization is intentionally deferred;
# this job emits unsigned .dmg/.zip release artifacts.
build-desktop-macos:
name: Build Desktop macOS DMG/ZIP
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package macOS desktop artifacts
run: pnpm --filter @fusion/desktop dist:mac -- --publish never
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify desktop macOS artifacts
shell: bash
run: |
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
- 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@v4
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
# ── Build Linux desktop artifacts ────────────────────────────────────
build-desktop-linux:
name: Build Desktop Linux Artifacts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Build
run: pnpm build
- name: Build desktop package
run: pnpm --filter @fusion/desktop build
- name: Package Linux desktop artifacts
run: pnpm --filter @fusion/desktop dist:linux -- --publish never
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify desktop Linux AppImage artifact
shell: bash
run: |
shopt -s nullglob
appimages=(packages/desktop/dist-electron/Fusion-*-linux-*.AppImage)
if [ ${#appimages[@]} -eq 0 ]; then
echo "No Fusion Linux AppImage artifacts produced" >&2
exit 1
fi
- 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@v4
with:
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-*.deb
packages/desktop/dist-electron/Fusion-*-linux-*.deb.sha256
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz
packages/desktop/dist-electron/Fusion-*-linux-*.tar.gz.sha256
# ── Collect all artifacts ─────────────────────────────────────────────
collect:
name: Collect Artifacts
needs: [build-binaries, build-desktop-windows]
needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]
runs-on: ubuntu-latest
steps:
@@ -177,7 +291,7 @@ jobs:
- name: Combine artifacts
run: |
mkdir combined
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -o -name "*.exe" -o -name "*.exe.sha256" -o -name "*.blockmap" \) -exec cp {} combined/ \;
find artifacts -type f \( -name "fn-*" -o -name "*.sha256" -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" \) -exec cp {} combined/ \;
ls -la combined/
- name: Upload combined archive

View File

@@ -341,6 +341,8 @@ Tray icons are generated from `packages/dashboard/app/public/logo.svg`.
- `pnpm --filter @fusion/desktop pack` — generate unpacked artifacts via electron-builder (`--dir`)
- `pnpm --filter @fusion/desktop dist` — generate installable desktop artifacts via electron-builder
- `pnpm --filter @fusion/desktop dist:win` — generate Windows installable artifacts (`--win`)
- `pnpm --filter @fusion/desktop dist:mac` — generate macOS installable artifacts (`--mac`)
- `pnpm --filter @fusion/desktop dist:linux` — generate Linux installable artifacts (`--linux`)
- `pnpm dist:desktop:win` — workspace alias to build desktop assets then run Windows packaging
## Packaging
@@ -350,8 +352,13 @@ Desktop packaging is configured in `electron-builder.yml`.
- Output directory: `packages/desktop/dist-electron`
- Targets: macOS (`dmg`, `zip`), Windows (`nsis`, `portable`), Linux (`AppImage`, `deb`, `tar.gz`)
- Windows artifacts: `Fusion-<version>-win-x64.exe` and `Fusion-<version>-win-arm64.exe` (both NSIS + portable variants) in `packages/desktop/dist-electron/`
- Binary GitHub Release workflow (`.github/workflows/release.yml`) now attaches Windows desktop artifacts: x64 + arm64 outputs (NSIS + portable), matching `.exe.sha256` sidecars, and `.blockmap` files.
- Binary GitHub Release workflow (`.github/workflows/release.yml`) now attaches desktop artifacts for all supported platforms:
- 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` with `.sha256`, plus best-effort `.deb` and `.tar.gz` outputs (and sidecars) when available on the runner image.
- Tag-less release rehearsal workflow (`.github/workflows/test-release.yml`) mirrors that artifact collection path without publishing a real GitHub Release.
- macOS Developer ID signing/notarization and Linux signing are not wired in these release jobs yet; release-attached macOS/Linux artifacts are currently unsigned and follow-up tasks will add signing flows.
- Linux `.deb` and `.tar.gz` outputs are best-effort and may be absent on some runner images without failing the release.
- Isolated manual Windows build path: `.github/workflows/desktop-windows.yml` (`workflow_dispatch` on `windows-latest`) runs `electron-builder --win --x64 --arm64 --publish never`.
- ARM64 artifacts are cross-built on the `windows-latest` x64 runner; execution/validation still requires a Windows ARM64 device or emulator.
- Deep link protocol: `fusion://`

View File

@@ -27,7 +27,9 @@
"generate:icons": "tsx scripts/generate-icons.ts",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"dist:win": "electron-builder --win"
"dist:win": "electron-builder --win",
"dist:mac": "electron-builder --mac",
"dist:linux": "electron-builder --linux"
},
"dependencies": {
"@fusion/core": "workspace:*",

View File

@@ -11,7 +11,7 @@ async function readDesktopFile(relativePath: string): Promise<string> {
return readFile(path.join(desktopRoot, relativePath), "utf-8");
}
describe("electron-builder windows config", () => {
describe("electron-builder desktop config", () => {
it("keeps required Windows packaging targets and metadata", async () => {
const builderConfig = await readDesktopFile("electron-builder.yml");
@@ -53,11 +53,34 @@ describe("electron-builder windows config", () => {
expect(builderConfig).not.toContain("certificateSubjectName:");
});
it("exposes a dedicated dist:win script", async () => {
it("exposes dedicated platform-specific dist scripts", async () => {
const packageJsonRaw = await readDesktopFile("package.json");
const packageJson = JSON.parse(packageJsonRaw) as { scripts?: Record<string, string> };
expect(packageJson.scripts?.["dist:win"]).toBe("electron-builder --win");
expect(packageJson.scripts?.["dist:mac"]).toBe("electron-builder --mac");
expect(packageJson.scripts?.["dist:linux"]).toBe("electron-builder --linux");
});
it("keeps required mac and linux targets", async () => {
const builderConfig = await readDesktopFile("electron-builder.yml");
expect(builderConfig).toMatch(/mac:\s*[\s\S]*?target:\s*[\s\S]*?-\s*target:\s*dmg/m);
expect(builderConfig).toMatch(/mac:\s*[\s\S]*?target:\s*[\s\S]*?-\s*target:\s*zip/m);
const linuxSectionMatch = builderConfig.match(/linux:\s*[\s\S]*$/m);
expect(linuxSectionMatch?.[0]).toBeDefined();
const linuxSection = linuxSectionMatch![0];
expect(linuxSection).toMatch(/target:\s*[\s\S]*?-\s*target:\s*AppImage/m);
expect(linuxSection).toMatch(/target:\s*[\s\S]*?-\s*target:\s*deb/m);
expect(linuxSection).toMatch(/target:\s*[\s\S]*?-\s*target:\s*tar\.gz/m);
const linuxTargetEntries = Array.from(
linuxSection.matchAll(/-\s*target:\s*([^\n\r]+)/g),
(match) => match[1]?.trim(),
);
expect(linuxTargetEntries[0]).toBe("AppImage");
});
});

View File

@@ -12,7 +12,7 @@ async function readRepoFile(relativePath: string): Promise<string> {
}
describe("desktop release workflow wiring", () => {
it("adds windows desktop build job to release and test-release workflows", async () => {
it("adds desktop build jobs for windows, macOS, and linux", async () => {
const release = await readRepoFile(".github/workflows/release.yml");
const testRelease = await readRepoFile(".github/workflows/test-release.yml");
@@ -21,16 +21,41 @@ describe("desktop release workflow wiring", () => {
expect(workflow).toContain("runs-on: windows-latest");
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:win|electron-builder --win/);
expect(workflow).toContain("name: fusion-desktop-windows");
expect(workflow).toContain("build-desktop-macos:");
expect(workflow).toContain("runs-on: macos-latest");
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:mac|electron-builder --mac/);
expect(workflow).toContain("name: fusion-desktop-macos");
expect(workflow).toContain("build-desktop-linux:");
expect(workflow).toContain("runs-on: ubuntu-latest");
expect(workflow).toMatch(/pnpm --filter @fusion\/desktop dist:linux|electron-builder --linux/);
expect(workflow).toContain("name: fusion-desktop-linux");
}
});
it("wires release aggregation to include desktop exe assets", async () => {
it("wires release aggregation to include desktop assets across platforms", async () => {
const release = await readRepoFile(".github/workflows/release.yml");
expect(release).toContain("needs: [build-binaries, build-desktop-windows]");
expect(release).toContain(
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]",
);
expect(release).toContain('find artifacts -type f \\(');
expect(release).toContain('-name "*.exe"');
expect(release).toContain('-name "*.exe.sha256"');
expect(release).toContain('-name "*.blockmap"');
expect(release).toContain('-name "*.dmg"');
expect(release).toContain('-name "*.zip"');
expect(release).toContain('-name "*.AppImage"');
expect(release).toContain('-name "*.deb"');
expect(release).toContain('-name "*.tar.gz"');
});
it("wires test-release collect job to wait for all desktop build jobs", async () => {
const testRelease = await readRepoFile(".github/workflows/test-release.yml");
expect(testRelease).toContain(
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux]",
);
});
});