From 3d313cf8a31057143547050588711e4ee02d8e1c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 28 Apr 2026 10:45:44 +0900 Subject: [PATCH 1/3] fix(release): build actual desktop installers instead of raw binaries in zip This updates the CI to build Tauri properly so users get DMG/EXE/MSI files instead of zip files containing just the executable and raw frontend files. --- .github/workflows/build-baseline.yml | 36 +++--- scripts/release/package_desktop_artifact.py | 123 +++++++++----------- 2 files changed, 71 insertions(+), 88 deletions(-) diff --git a/.github/workflows/build-baseline.yml b/.github/workflows/build-baseline.yml index 3b211dfc..7c204df2 100644 --- a/.github/workflows/build-baseline.yml +++ b/.github/workflows/build-baseline.yml @@ -87,17 +87,15 @@ jobs: - name: Build frontend run: npm run build --workspace @bandscope/desktop - name: Build native shell - run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target $env:BANDSCOPE_TARGET_TRIPLE + working-directory: apps/desktop + run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE - name: Package Windows amd64 artifact run: python scripts/release/package_desktop_artifact.py - name: Upload Windows amd64 artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: bandscope-windows-amd64-${{ github.sha }} - path: | - artifacts/*.zip - artifacts/*.sha256 - artifacts/*.manifest.txt + path: artifacts/* build-windows-arm64: name: build / windows / arm64 @@ -168,17 +166,15 @@ jobs: - name: Build frontend run: npm run build --workspace @bandscope/desktop - name: Build native shell - run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target $env:BANDSCOPE_TARGET_TRIPLE + working-directory: apps/desktop + run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE - name: Package Windows arm64 artifact run: python scripts/release/package_desktop_artifact.py - name: Upload Windows arm64 artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: bandscope-windows-arm64-${{ github.sha }} - path: | - artifacts/*.zip - artifacts/*.sha256 - artifacts/*.manifest.txt + path: artifacts/* gate-windows: name: gate / build / windows @@ -226,17 +222,15 @@ jobs: - name: Build frontend run: npm run build --workspace @bandscope/desktop - name: Build native shell - run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target "$BANDSCOPE_TARGET_TRIPLE" + working-directory: apps/desktop + run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE" - name: Package macOS amd64 artifact run: python3 scripts/release/package_desktop_artifact.py - name: Upload macOS amd64 artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: bandscope-macos-amd64-${{ github.sha }} - path: | - artifacts/*.zip - artifacts/*.sha256 - artifacts/*.manifest.txt + path: artifacts/* build-macos-arm64: name: build / macos / arm64 @@ -274,17 +268,15 @@ jobs: - name: Build frontend run: npm run build --workspace @bandscope/desktop - name: Build native shell - run: cargo +stable build --manifest-path apps/desktop/src-tauri/Cargo.toml --release --locked --target "$BANDSCOPE_TARGET_TRIPLE" + working-directory: apps/desktop + run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE" - name: Package macOS arm64 artifact run: python3 scripts/release/package_desktop_artifact.py - name: Upload macOS arm64 artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: bandscope-macos-arm64-${{ github.sha }} - path: | - artifacts/*.zip - artifacts/*.sha256 - artifacts/*.manifest.txt + path: artifacts/* gate-macos: name: gate / build / macos @@ -315,7 +307,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ github.event.release.tag_name }} - run: gh release upload "$RELEASE_TAG" artifacts/*.zip artifacts/*.sha256 artifacts/*.manifest.txt --clobber --repo ${{ github.repository }} + run: gh release upload "$RELEASE_TAG" artifacts/* --clobber --repo ${{ github.repository }} attach-macos-release-artifact: name: release-artifact / macos @@ -336,4 +328,4 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ github.event.release.tag_name }} - run: gh release upload "$RELEASE_TAG" artifacts/*.zip artifacts/*.sha256 artifacts/*.manifest.txt --clobber --repo ${{ github.repository }} + run: gh release upload "$RELEASE_TAG" artifacts/* --clobber --repo ${{ github.repository }} diff --git a/scripts/release/package_desktop_artifact.py b/scripts/release/package_desktop_artifact.py index 72f01c87..81df8c09 100644 --- a/scripts/release/package_desktop_artifact.py +++ b/scripts/release/package_desktop_artifact.py @@ -5,7 +5,7 @@ import hashlib import os import platform -import zipfile +import shutil from pathlib import Path @@ -61,94 +61,85 @@ def resolved_artifact_target() -> tuple[str, str]: return normalized_platform(), normalized_architecture() -def artifact_identity() -> dict[str, str]: +def artifact_identity(filename: str) -> dict[str, str]: """Build the archive and manifest names for the current artifact target.""" git_sha = os.environ.get("GITHUB_SHA", "local")[:12] target_platform, target_arch = resolved_artifact_target() suffix = f"bandscope-{target_platform}-{target_arch}-{git_sha}" + ext = Path(filename).suffix return { "platform": target_platform, "arch": target_arch, - "archive_name": f"{suffix}.zip", - "manifest_name": f"{suffix}.manifest.txt", + "archive_name": f"{suffix}{ext}", + "manifest_name": f"{suffix}{ext}.manifest.txt", } -def expected_binary_path(repo_root: Path) -> Path: - """Return the expected desktop binary path for the selected target triple.""" +def find_installer_packages(repo_root: Path) -> list[Path]: + """Find built Tauri installers (DMG, EXE, MSI).""" target_triple = os.environ.get("BANDSCOPE_TARGET_TRIPLE") - if target_triple and "windows" in target_triple: - system = "windows" - elif target_triple and "apple-darwin" in target_triple: - system = "macos" - else: - system = normalized_platform() - binary_name = "bandscope-desktop.exe" if system == "windows" else "bandscope-desktop" target_root = repo_root / "apps" / "desktop" / "src-tauri" / "target" if target_triple: target_root = target_root / target_triple - return target_root / "release" / binary_name + + bundle_dir = target_root / "release" / "bundle" + installers = [] + + if bundle_dir.exists(): + # macOS DMG + installers.extend(bundle_dir.glob("dmg/*.dmg")) + # Windows EXE/MSI + installers.extend(bundle_dir.glob("nsis/*.exe")) + installers.extend(bundle_dir.glob("msi/*.msi")) + + return installers def main() -> int: - """Package the desktop binary, frontend assets, and metadata into a zip archive.""" + """Find the built installer packages, rename them, and calculate checksums.""" repo_root = Path(__file__).resolve().parents[2] - binary_path = expected_binary_path(repo_root) - frontend_dist = repo_root / "apps" / "desktop" / "dist" output_dir = repo_root / "artifacts" output_dir.mkdir(parents=True, exist_ok=True) - if not binary_path.exists(): - raise FileNotFoundError(f"Missing built binary: {binary_path}") - if not frontend_dist.exists(): - raise FileNotFoundError(f"Missing frontend dist directory: {frontend_dist}") - - metadata_paths = [ - repo_root / "services" / "analysis-engine" / "uv.lock", - repo_root / "package-lock.json", - repo_root / "apps" / "desktop" / "src-tauri" / "Cargo.lock", - repo_root / "supply-chain" / "supplemental-component-inventory.json", - ] - missing_metadata = [str(path) for path in metadata_paths if not path.exists()] - if missing_metadata: - missing_list = ", ".join(missing_metadata) - raise FileNotFoundError(f"Missing release metadata files: {missing_list}") - - identity = artifact_identity() - archive_name = identity["archive_name"] - archive_path = output_dir / archive_name - - with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive: - archive.write(binary_path, arcname=f"bin/{binary_path.name}") - for path in frontend_dist.rglob("*"): - if path.is_file(): - archive.write( - path, - arcname=str(Path("frontend") / path.relative_to(frontend_dist)), - ) - for extra_path in metadata_paths: - archive.write(extra_path, arcname=str(Path("metadata") / extra_path.name)) - - checksum_path = output_dir / f"{archive_name}.sha256" - checksum_path.write_text(f"{sha256_file(archive_path)} {archive_name}\n", encoding="utf-8") - - manifest_path = output_dir / identity["manifest_name"] - manifest_path.write_text( - "\n".join( - [ - f"platform={identity['platform']}", - f"arch={identity['arch']}", - f"target_triple={os.environ.get('BANDSCOPE_TARGET_TRIPLE', 'native')}", - f"binary={binary_path.name}", - f"archive={archive_name}", - f"checksum={checksum_path.name}", - ] + installers = find_installer_packages(repo_root) + if not installers: + raise FileNotFoundError("Could not find any built installers (DMG/EXE) in target/release/bundle/") + + # For safety, ensure we only pick one installer per run, or handle multiple + # Typically we might have both EXE and MSI for Windows. + for installer_path in installers: + identity = artifact_identity(installer_path.name) + archive_name = identity["archive_name"] + + # If there are multiple (e.g. EXE and MSI), add original extension to avoid overwrite + if len(installers) > 1: + ext = installer_path.suffix + archive_name = archive_name.replace(ext, f"{ext}") + + archive_path = output_dir / archive_name + shutil.copy2(installer_path, archive_path) + + checksum_path = output_dir / f"{archive_name}.sha256" + checksum_path.write_text(f"{sha256_file(archive_path)} {archive_name}\n", encoding="utf-8") + + manifest_path = output_dir / identity["manifest_name"] + manifest_path.write_text( + "\n".join( + [ + f"platform={identity['platform']}", + f"arch={identity['arch']}", + f"target_triple={os.environ.get('BANDSCOPE_TARGET_TRIPLE', 'native')}", + f"original_file={installer_path.name}", + f"archive={archive_name}", + f"checksum={checksum_path.name}", + ] + ) + + "\n", + encoding="utf-8", ) - + "\n", - encoding="utf-8", - ) - print(str(archive_path.relative_to(repo_root))) + print(f"Packaged {installer_path.name} to artifacts/{archive_name}") + return 0 From 98ea20e1dbb33f10fc783b12137f76ecc4354378 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 28 Apr 2026 10:59:13 +0900 Subject: [PATCH 2/3] trigger ci From 5aa238ac184ff7e42bcf2a15376f3a75c0e72d49 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 28 Apr 2026 13:06:33 +0900 Subject: [PATCH 3/3] fix(ci): pass --bundles to ensure Tauri builds installers --- .github/workflows/build-baseline.yml | 8 ++++---- apps/desktop/src-tauri/Cargo.toml | 4 ++-- scripts/release/package_desktop_artifact.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-baseline.yml b/.github/workflows/build-baseline.yml index 7c204df2..d3af455e 100644 --- a/.github/workflows/build-baseline.yml +++ b/.github/workflows/build-baseline.yml @@ -88,7 +88,7 @@ jobs: run: npm run build --workspace @bandscope/desktop - name: Build native shell working-directory: apps/desktop - run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE + run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE --bundles nsis - name: Package Windows amd64 artifact run: python scripts/release/package_desktop_artifact.py - name: Upload Windows amd64 artifact @@ -167,7 +167,7 @@ jobs: run: npm run build --workspace @bandscope/desktop - name: Build native shell working-directory: apps/desktop - run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE + run: npx @tauri-apps/cli build --target $env:BANDSCOPE_TARGET_TRIPLE --bundles nsis - name: Package Windows arm64 artifact run: python scripts/release/package_desktop_artifact.py - name: Upload Windows arm64 artifact @@ -223,7 +223,7 @@ jobs: run: npm run build --workspace @bandscope/desktop - name: Build native shell working-directory: apps/desktop - run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE" + run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE" --bundles dmg - name: Package macOS amd64 artifact run: python3 scripts/release/package_desktop_artifact.py - name: Upload macOS amd64 artifact @@ -269,7 +269,7 @@ jobs: run: npm run build --workspace @bandscope/desktop - name: Build native shell working-directory: apps/desktop - run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE" + run: npx @tauri-apps/cli build --target "$BANDSCOPE_TARGET_TRIPLE" --bundles dmg - name: Package macOS arm64 artifact run: python3 scripts/release/package_desktop_artifact.py - name: Upload macOS arm64 artifact diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 71b11ac9..8fc55a85 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" edition = "2021" [build-dependencies] -tauri-build = { version = "2" } +tauri-build = { version = "2", features = [] } [dependencies] rfd = "0.17.2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tauri = { version = "2.3.1" } +tauri = { version = "2.3.1", features = [] } time = { version = "0.3", features = ["formatting", "macros"] } tokio = { version = "1.50.0", features = ["time"] } url = "2.5.8" diff --git a/scripts/release/package_desktop_artifact.py b/scripts/release/package_desktop_artifact.py index 81df8c09..6b544aab 100644 --- a/scripts/release/package_desktop_artifact.py +++ b/scripts/release/package_desktop_artifact.py @@ -87,10 +87,10 @@ def find_installer_packages(repo_root: Path) -> list[Path]: if bundle_dir.exists(): # macOS DMG - installers.extend(bundle_dir.glob("dmg/*.dmg")) + installers.extend(bundle_dir.rglob("*.dmg")) # Windows EXE/MSI - installers.extend(bundle_dir.glob("nsis/*.exe")) - installers.extend(bundle_dir.glob("msi/*.msi")) + installers.extend(bundle_dir.rglob("*.exe")) + installers.extend(bundle_dir.rglob("*.msi")) return installers