fix(desktop): improve generation defaults and empty state (#403) #194
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Desktop | |
| # CI for the experimental Tauri desktop app (desktop/). The root ci.yml is | |
| # untouched by this surface: desktop/src-tauri is excluded from the cargo | |
| # workspace, so --workspace gates never see it. | |
| # These jobs are NOT required status checks, so plain `paths` trigger filters | |
| # are safe here (a filtered-out run reports nothing and blocks nothing) — the | |
| # required checks in ci.yml use job-level `if:` gating instead. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "desktop/**" | |
| - "crates/mold-core/**" | |
| - "crates/mold-catalog/**" | |
| - "crates/mold-db/**" | |
| - "crates/mold-inference/**" | |
| - "crates/mold-server/**" | |
| - "flake.nix" | |
| - "Cargo.toml" | |
| - ".github/workflows/desktop.yml" | |
| - ".github/workflows/desktop-distribution.yml" | |
| - "scripts/check-desktop-nightly-main-head.sh" | |
| - "scripts/create-desktop-bundle-version.sh" | |
| - "scripts/create-desktop-nightly-version.sh" | |
| - "scripts/create-desktop-update-manifest.sh" | |
| - "scripts/fix-desktop-macos-linkage.sh" | |
| - "scripts/notarize-desktop-dmg.sh" | |
| - "scripts/prune-desktop-nightly-assets.sh" | |
| - "scripts/select-desktop-stable-latest.sh" | |
| - "scripts/tests/check-desktop-nightly-main-head.sh" | |
| - "scripts/tests/create-desktop-bundle-version.sh" | |
| - "scripts/tests/create-desktop-nightly-version.sh" | |
| - "scripts/tests/create-desktop-update-manifest.sh" | |
| - "scripts/tests/desktop-nightly-race-guards.sh" | |
| - "scripts/tests/prune-desktop-nightly-assets.sh" | |
| - "scripts/tests/select-desktop-stable-latest.sh" | |
| - "scripts/tests/verify-desktop-updater-signature.sh" | |
| - "scripts/verify-desktop-release.sh" | |
| - "scripts/verify-desktop-updater-signature.sh" | |
| pull_request: | |
| paths: | |
| - "desktop/**" | |
| - "crates/mold-core/**" | |
| - "crates/mold-catalog/**" | |
| - "crates/mold-db/**" | |
| - "crates/mold-inference/**" | |
| - "crates/mold-server/**" | |
| - "flake.nix" | |
| - "Cargo.toml" | |
| - ".github/workflows/desktop.yml" | |
| - ".github/workflows/desktop-distribution.yml" | |
| - "scripts/check-desktop-nightly-main-head.sh" | |
| - "scripts/create-desktop-bundle-version.sh" | |
| - "scripts/create-desktop-nightly-version.sh" | |
| - "scripts/create-desktop-update-manifest.sh" | |
| - "scripts/fix-desktop-macos-linkage.sh" | |
| - "scripts/notarize-desktop-dmg.sh" | |
| - "scripts/prune-desktop-nightly-assets.sh" | |
| - "scripts/select-desktop-stable-latest.sh" | |
| - "scripts/tests/check-desktop-nightly-main-head.sh" | |
| - "scripts/tests/create-desktop-bundle-version.sh" | |
| - "scripts/tests/create-desktop-nightly-version.sh" | |
| - "scripts/tests/create-desktop-update-manifest.sh" | |
| - "scripts/tests/desktop-nightly-race-guards.sh" | |
| - "scripts/tests/prune-desktop-nightly-assets.sh" | |
| - "scripts/tests/select-desktop-stable-latest.sh" | |
| - "scripts/tests/verify-desktop-updater-signature.sh" | |
| - "scripts/verify-desktop-release.sh" | |
| - "scripts/verify-desktop-updater-signature.sh" | |
| workflow_dispatch: | |
| concurrency: | |
| # Superseded PR commits cancel within their own PR. First-attempt main pushes | |
| # stay in one non-canceling primary lane; reruns/manual runs remain isolated. | |
| group: desktop-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.event_name == 'push' && github.run_attempt == 1 && 'primary' || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| desktop-frontend: | |
| name: Frontend (typecheck, tests, format) | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: desktop | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| - name: Install updater verification tooling | |
| run: sudo apt-get update && sudo apt-get install -y minisign | |
| - name: Test updater release scripts | |
| run: | | |
| bash ../scripts/tests/check-desktop-nightly-main-head.sh | |
| bash ../scripts/tests/create-desktop-bundle-version.sh | |
| bash ../scripts/tests/create-desktop-nightly-version.sh | |
| bash ../scripts/tests/create-desktop-update-manifest.sh | |
| bash ../scripts/tests/desktop-nightly-race-guards.sh | |
| bash ../scripts/tests/verify-desktop-updater-signature.sh | |
| bash ../scripts/tests/prune-desktop-nightly-assets.sh | |
| bash ../scripts/tests/select-desktop-stable-latest.sh | |
| - run: bun install --frozen-lockfile | |
| - run: bun run fmt:check | |
| - run: bunx vue-tsc -b | |
| - run: bun run test | |
| - run: bun run build | |
| desktop-rust: | |
| name: Rust (fmt, clippy, test) | |
| runs-on: macos-14 | |
| defaults: | |
| run: | |
| working-directory: desktop | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: oven-sh/setup-bun@v2 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: desktop/src-tauri | |
| - run: bun install --frozen-lockfile | |
| - run: cargo fmt --manifest-path src-tauri/Cargo.toml -- --check | |
| - run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings | |
| # CPU-only: the metal feature is deliberately off in CI. | |
| - run: cargo test --manifest-path src-tauri/Cargo.toml | |
| # Reuse the test-profile artifacts for a fast PR packaging smoke test. | |
| # Main immediately runs the real signed/notarized release build below. | |
| - name: Fast bundle proof (debug .app) | |
| if: github.event_name != 'push' | |
| run: bunx tauri build --debug --bundles app --ci | |
| # Every desktop-relevant main commit that passes both CI jobs becomes a | |
| # signed, notarized nightly. The updater-artifact overlay stays isolated | |
| # from the fast debug PR bundle proof above. | |
| desktop-nightly: | |
| name: Build nightly distribution | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| needs: [desktop-frontend, desktop-rust] | |
| uses: ./.github/workflows/desktop-distribution.yml | |
| with: | |
| channel: nightly | |
| secrets: inherit | |
| publish-desktop-nightly: | |
| name: Publish nightly update | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| needs: [desktop-nightly] | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: desktop-nightly-publication | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install updater verification tooling | |
| run: sudo apt-get update && sudo apt-get install -y minisign | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: mold-desktop-macos-nightly | |
| path: artifacts | |
| - name: Upload immutable nightly payloads | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| VERSION: ${{ needs.desktop-nightly.outputs.version }} | |
| UPDATER_ARTIFACT_NAME: ${{ needs.desktop-nightly.outputs.updater_artifact_name }} | |
| DMG_NAME: ${{ needs.desktop-nightly.outputs.dmg_name }} | |
| MANIFEST_NAME: ${{ needs.desktop-nightly.outputs.manifest_name }} | |
| run: | | |
| set -euo pipefail | |
| payload="artifacts/$UPDATER_ARTIFACT_NAME" | |
| signature="$payload.sig" | |
| dmg="artifacts/$DMG_NAME" | |
| manifest="artifacts/$MANIFEST_NAME" | |
| for path in "$payload" "$signature" "$dmg" "$manifest"; do | |
| if [[ ! -f "$path" ]]; then | |
| echo "::error::missing nightly artifact $path" | |
| exit 1 | |
| fi | |
| done | |
| if scripts/check-desktop-nightly-main-head.sh "$GITHUB_SHA"; then | |
| : | |
| else | |
| head_status=$? | |
| if [[ "$head_status" -eq 10 ]]; then | |
| echo "::notice::Skipping stale desktop nightly $VERSION; main advanced during the build." | |
| exit 0 | |
| fi | |
| exit "$head_status" | |
| fi | |
| scripts/verify-desktop-updater-signature.sh \ | |
| --artifact "$payload" \ | |
| --signature "$signature" \ | |
| --config desktop/src-tauri/tauri.conf.json | |
| # release.yml normally owns this rolling prerelease. Handle the first | |
| # run (or a race with that workflow) without changing its body later. | |
| if ! gh release view latest --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then | |
| gh release create latest \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --target "$GITHUB_SHA" \ | |
| --title "Latest Build" \ | |
| --notes "Rolling pre-release from main." \ | |
| --prerelease \ | |
| || gh release view latest --repo "$GITHUB_REPOSITORY" > /dev/null | |
| fi | |
| # The manifest is the channel pointer, so publish and prove every | |
| # version-unique target before changing that pointer. | |
| gh release upload latest "$payload" "$signature" "$dmg" \ | |
| --repo "$GITHUB_REPOSITORY" --clobber | |
| payload_url=$(jq -r '.platforms["darwin-aarch64"].url' "$manifest") | |
| expected_sha=$(sha256sum "$payload" | awk '{print $1}') | |
| remote_payload="$RUNNER_TEMP/$UPDATER_ARTIFACT_NAME" | |
| payload_ready=false | |
| for attempt in $(seq 1 20); do | |
| if curl -fsSL "$payload_url" -o "$remote_payload"; then | |
| actual_sha=$(sha256sum "$remote_payload" | awk '{print $1}') | |
| if [[ "$actual_sha" == "$expected_sha" ]]; then | |
| payload_ready=true | |
| break | |
| fi | |
| fi | |
| if [[ "$attempt" -lt 20 ]]; then | |
| echo "Nightly payload not anonymously available yet (attempt $attempt/20); retrying in 15s" | |
| sleep 15 | |
| fi | |
| done | |
| if [[ "$payload_ready" != true ]]; then | |
| echo "::error::nightly updater payload failed anonymous SHA-256 verification" | |
| exit 1 | |
| fi | |
| # Main can advance while the immutable payload is propagating. Check | |
| # again at the channel-pointer boundary so a stale build cannot flip | |
| # the manifest after a newer primary run has started. | |
| if scripts/check-desktop-nightly-main-head.sh "$GITHUB_SHA"; then | |
| : | |
| else | |
| head_status=$? | |
| if [[ "$head_status" -eq 10 ]]; then | |
| echo "::notice::Leaving verified stale payload $VERSION unpublished; main advanced before the manifest flip." | |
| exit 0 | |
| fi | |
| exit "$head_status" | |
| fi | |
| gh release upload latest "$manifest" \ | |
| --repo "$GITHUB_REPOSITORY" --clobber | |
| manifest_url="https://github.com/$GITHUB_REPOSITORY/releases/download/latest/$MANIFEST_NAME" | |
| expected_signature=$(tr -d '\r\n' < "$signature") | |
| remote_manifest="$RUNNER_TEMP/$MANIFEST_NAME" | |
| manifest_ready=false | |
| for attempt in $(seq 1 20); do | |
| if curl -fsSL "$manifest_url" -o "$remote_manifest" \ | |
| && jq -e \ | |
| --arg version "$VERSION" \ | |
| --arg url "$payload_url" \ | |
| --arg signature "$expected_signature" \ | |
| '.version == $version | |
| and .platforms["darwin-aarch64"].url == $url | |
| and .platforms["darwin-aarch64"].signature == $signature' \ | |
| "$remote_manifest" > /dev/null; then | |
| manifest_ready=true | |
| break | |
| fi | |
| if [[ "$attempt" -lt 20 ]]; then | |
| echo "Nightly manifest not anonymously available yet (attempt $attempt/20); retrying in 15s" | |
| sleep 15 | |
| fi | |
| done | |
| if [[ "$manifest_ready" != true ]]; then | |
| echo "::error::nightly updater manifest failed anonymous verification" | |
| exit 1 | |
| fi | |
| # The manifest now points at a fully verified immutable payload. Only | |
| # after that atomic flip is it safe to remove older generations. | |
| scripts/prune-desktop-nightly-assets.sh \ | |
| --repository "$GITHUB_REPOSITORY" \ | |
| --release-tag latest \ | |
| --current-version "$VERSION" \ | |
| --keep 10 | |
| echo "Published Mold desktop nightly $VERSION" | |
| echo "Manifest: $manifest_url" |