Skip to content

Commit 320e18c

Browse files
committed
Merge remote-tracking branch 'origin/staging' into rt-rooms-sync
# Conflicts: # apps/sim/package.json # bun.lock # scripts/check-api-validation-contracts.ts
2 parents b9d2e67 + 5b7cf99 commit 320e18c

627 files changed

Lines changed: 88194 additions & 6232 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,48 @@ jobs:
6969
echo "ℹ️ Not a release commit"
7070
fi
7171
72+
# Detect shell-code changes on dev/staging pushes. Web-only changes never
73+
# need a desktop build (installed shells load the web app live); changes to
74+
# the Electron app or the bridge packages trigger a per-env prerelease build
75+
# (dev → alpha channel, staging → beta) that the env's update feed
76+
# (/api/desktop/update) starts offering automatically.
77+
detect-desktop-changes:
78+
name: Detect Desktop Changes
79+
runs-on: blacksmith-4vcpu-ubuntu-2404
80+
timeout-minutes: 5
81+
if: github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/staging')
82+
outputs:
83+
changed: ${{ steps.diff.outputs.changed }}
84+
steps:
85+
- name: Checkout code
86+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
87+
with:
88+
fetch-depth: 50
89+
90+
- name: Diff desktop paths
91+
id: diff
92+
env:
93+
BEFORE: ${{ github.event.before }}
94+
run: |
95+
# Force pushes (dev resets) can reference a BEFORE we don't have;
96+
# fall back to the previous commit, and to no build when even that
97+
# is unavailable.
98+
if [ -z "$BEFORE" ] || ! git cat-file -e "$BEFORE" 2>/dev/null; then
99+
BEFORE="$(git rev-parse HEAD^ 2>/dev/null || echo '')"
100+
fi
101+
if [ -z "$BEFORE" ]; then
102+
echo "changed=false" >> "$GITHUB_OUTPUT"
103+
echo "ℹ️ No comparable base commit; skipping desktop prerelease"
104+
exit 0
105+
fi
106+
if git diff --name-only "$BEFORE" HEAD | grep -qE '^(apps/desktop/|packages/desktop-bridge/|packages/browser-protocol/)'; then
107+
echo "changed=true" >> "$GITHUB_OUTPUT"
108+
echo "✅ Desktop shell code changed"
109+
else
110+
echo "changed=false" >> "$GITHUB_OUTPUT"
111+
echo "ℹ️ No desktop shell changes"
112+
fi
113+
72114
# Run database migrations before images are promoted: the ECR latest/staging
73115
# tag push triggers CodePipeline, so migrating first guarantees the schema is
74116
# in place before the new app version deploys (replaces the removed ECS
@@ -590,3 +632,172 @@ jobs:
590632
env:
591633
GH_PAT: ${{ secrets.GITHUB_TOKEN }}
592634
run: bun run scripts/create-single-release.ts ${{ needs.detect-version.outputs.version }}
635+
636+
# Desktop release: builds, signs, notarizes, and attaches the macOS app to
637+
# the GitHub release created above. Gated on the Apple signing secrets so a
638+
# release pipeline run skips cleanly (instead of failing) until the Apple
639+
# Developer account is provisioned — the moment the six secrets exist, the
640+
# next vX.Y.Z release ships desktop artifacts with no further changes.
641+
# Job-level `if:` cannot read the secrets context, hence the probe job.
642+
check-desktop-signing:
643+
name: Check Desktop Signing Secrets
644+
runs-on: blacksmith-4vcpu-ubuntu-2404
645+
timeout-minutes: 2
646+
needs: [detect-version, detect-desktop-changes]
647+
# !cancelled(): detect-desktop-changes is skipped on main (and
648+
# detect-version tags only on main); either path may need the probe.
649+
if: ${{ !cancelled() && (needs.detect-version.outputs.is_release == 'true' || needs.detect-desktop-changes.outputs.changed == 'true') }}
650+
outputs:
651+
configured: ${{ steps.check.outputs.configured }}
652+
steps:
653+
- name: Probe Apple signing secrets
654+
id: check
655+
env:
656+
CONFIGURED: ${{ secrets.CSC_LINK != '' && secrets.CSC_KEY_PASSWORD != '' && secrets.APPLE_API_KEY_P8 != '' && secrets.APPLE_API_KEY_ID != '' && secrets.APPLE_API_ISSUER != '' && secrets.APPLE_TEAM_ID != '' }}
657+
run: |
658+
echo "configured=${CONFIGURED}" >> "$GITHUB_OUTPUT"
659+
if [ "$CONFIGURED" != "true" ]; then
660+
echo "::warning::Desktop release skipped: Apple signing secrets are not configured (CSC_LINK, CSC_KEY_PASSWORD, APPLE_API_KEY_P8, APPLE_API_KEY_ID, APPLE_API_ISSUER, APPLE_TEAM_ID)."
661+
fi
662+
663+
desktop-release:
664+
name: Desktop Release
665+
needs: [create-release, check-desktop-signing, detect-version]
666+
if: needs.check-desktop-signing.outputs.configured == 'true'
667+
permissions:
668+
contents: write
669+
uses: ./.github/workflows/desktop-release.yml
670+
with:
671+
version: ${{ needs.detect-version.outputs.version }}
672+
publish: true
673+
secrets: inherit
674+
675+
# Per-env desktop prereleases: a dev/staging push that touches shell code
676+
# publishes a channel-tagged GitHub prerelease (vX.Y.Z-alpha.N from dev,
677+
# vX.Y.Z-beta.N from staging). Each environment's /api/desktop/update feed
678+
# offers only its channel, so dev-pointed shells pick up alpha builds,
679+
# staging-pointed shells beta builds, and prod-pointed shells stable
680+
# releases — independently. Unlike stable releases, prereleases build even
681+
# before the Apple signing secrets exist — unsigned, so the update pipeline
682+
# is testable end to end; installed shells detect the missing Developer ID
683+
# and offer a manual download instead of a Squirrel install.
684+
create-desktop-prerelease:
685+
name: Create Desktop Prerelease
686+
runs-on: blacksmith-4vcpu-ubuntu-2404
687+
timeout-minutes: 5
688+
needs: [detect-desktop-changes, check-desktop-signing]
689+
# Requires the signing probe to have actually succeeded (not just "not
690+
# cancelled") so a probe failure can't produce a release with no build.
691+
if: ${{ !cancelled() && needs.detect-desktop-changes.outputs.changed == 'true' && needs.check-desktop-signing.result == 'success' }}
692+
permissions:
693+
contents: write
694+
outputs:
695+
version: ${{ steps.version.outputs.version }}
696+
steps:
697+
- name: Checkout code
698+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
699+
700+
- name: Compute prerelease version and create draft release
701+
id: version
702+
env:
703+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
704+
GH_REPO: ${{ github.repository }}
705+
SIGNED: ${{ needs.check-desktop-signing.outputs.configured }}
706+
run: |
707+
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=alpha; APP_NAME="Sim Dev"; else CHANNEL=beta; APP_NAME="Sim Staging"; fi
708+
# Prerelease core = next patch after the latest stable release, so
709+
# channel builds always outrank the stable they are built on top of
710+
# and are always superseded by the next stable. The run-attempt
711+
# suffix keeps re-runs of the same workflow from colliding on the
712+
# tag while preserving semver ordering.
713+
LATEST="$(gh release list --exclude-pre-releases --limit 1 --json tagName --jq '.[0].tagName' || true)"
714+
LATEST="${LATEST:-v0.0.0}"
715+
IFS='.' read -r MAJOR MINOR PATCH <<< "${LATEST#v}"
716+
TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))-${CHANNEL}.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}"
717+
NOTES="Automated ${CHANNEL}-channel desktop build from ${GITHUB_REF_NAME} @ ${GITHUB_SHA::7}."
718+
if [ "$SIGNED" != "true" ]; then
719+
NOTES="$NOTES
720+
721+
⚠️ Unsigned test build (Apple signing secrets not configured). Gatekeeper will quarantine a downloaded copy: right-click → Open, or clear the flag with \`xattr -dr com.apple.quarantine \"/Applications/${APP_NAME}.app\"\`."
722+
fi
723+
# Draft until the build uploads its artifacts: drafts are invisible
724+
# to the update feed, so a failed or in-flight build can never take
725+
# the channel down with an assetless release. Publishing later also
726+
# defers tag creation, so failed builds strand no tags.
727+
gh release create "$TAG" \
728+
--draft \
729+
--prerelease \
730+
--target "$GITHUB_SHA" \
731+
--title "$TAG" \
732+
--notes "$NOTES"
733+
echo "version=$TAG" >> "$GITHUB_OUTPUT"
734+
echo "✅ Created draft prerelease $TAG"
735+
736+
desktop-prerelease:
737+
name: Desktop Prerelease Build
738+
needs: [create-desktop-prerelease, check-desktop-signing]
739+
permissions:
740+
contents: write
741+
uses: ./.github/workflows/desktop-release.yml
742+
with:
743+
version: ${{ needs.create-desktop-prerelease.outputs.version }}
744+
publish: true
745+
sign: ${{ needs.check-desktop-signing.outputs.configured == 'true' }}
746+
secrets: inherit
747+
748+
# The draft only becomes visible to the update feed once its artifacts are
749+
# attached — this is what makes a dev/staging push atomic from the shell's
750+
# point of view.
751+
publish-desktop-prerelease:
752+
name: Publish Desktop Prerelease
753+
runs-on: blacksmith-4vcpu-ubuntu-2404
754+
timeout-minutes: 5
755+
needs: [create-desktop-prerelease, desktop-prerelease]
756+
permissions:
757+
contents: write
758+
env:
759+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
760+
GH_REPO: ${{ github.repository }}
761+
TAG: ${{ needs.create-desktop-prerelease.outputs.version }}
762+
steps:
763+
- name: Publish the draft release
764+
run: gh release edit "$TAG" --draft=false
765+
766+
# Keep the release list tidy: per channel, retain the newest 5 prereleases
767+
# and delete the rest (with their tags, so dev force-resets don't strand
768+
# commits behind stale tags). Leftover drafts (failed or superseded builds)
769+
# are always garbage by this point — the current run's release is published.
770+
prune-desktop-prereleases:
771+
name: Prune Desktop Prereleases
772+
runs-on: blacksmith-4vcpu-ubuntu-2404
773+
timeout-minutes: 5
774+
needs: [publish-desktop-prerelease]
775+
permissions:
776+
contents: write
777+
env:
778+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
779+
GH_REPO: ${{ github.repository }}
780+
steps:
781+
- name: Delete stale prereleases
782+
run: |
783+
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=alpha; else CHANNEL=beta; fi
784+
gh release list --limit 100 --json tagName,isPrerelease,isDraft,createdAt \
785+
--jq "[.[] | select(.isPrerelease and (.isDraft | not) and (.tagName | test(\"-${CHANNEL}\\\\.\")))] | sort_by(.createdAt) | reverse | .[5:] | .[].tagName" |
786+
while read -r TAG; do
787+
[ -n "$TAG" ] || continue
788+
echo "Deleting stale prerelease $TAG"
789+
gh release delete "$TAG" --cleanup-tag --yes
790+
done
791+
792+
- name: Delete leftover draft prereleases
793+
run: |
794+
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=alpha; else CHANNEL=beta; fi
795+
# Drafts have no tag ref, so delete by release id via the API
796+
# (gh release delete resolves by tag, which is ambiguous for drafts).
797+
gh api "repos/${GH_REPO}/releases?per_page=100" \
798+
--jq ".[] | select(.draft and (.tag_name | test(\"-${CHANNEL}\\\\.\"))) | .id" |
799+
while read -r ID; do
800+
[ -n "$ID" ] || continue
801+
echo "Deleting leftover draft release $ID"
802+
gh api -X DELETE "repos/${GH_REPO}/releases/${ID}"
803+
done

.github/workflows/desktop-e2e.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: Desktop E2E
2+
3+
# Smoke coverage of the real Electron shell, plus an advisory canary leg
4+
# against electron@latest so Chromium-cadence breakage surfaces before an
5+
# upgrade is attempted (U18/U22).
6+
#
7+
# Manual-only for now: the desktop app is tested locally, so the
8+
# pull_request trigger is disabled until desktop CI is turned back on.
9+
10+
on:
11+
workflow_dispatch:
12+
13+
concurrency:
14+
group: desktop-e2e-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
e2e:
19+
name: E2E (${{ matrix.electron }})
20+
runs-on: macos-14
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
electron: [pinned, latest]
25+
continue-on-error: ${{ matrix.electron == 'latest' }}
26+
steps:
27+
- name: Checkout code
28+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
29+
30+
- name: Setup Bun
31+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
32+
with:
33+
bun-version: 1.3.13
34+
35+
- name: Install dependencies
36+
run: bun install --frozen-lockfile
37+
38+
- name: Switch to electron@latest (canary)
39+
if: matrix.electron == 'latest'
40+
working-directory: apps/desktop
41+
run: bun add -d electron@latest
42+
43+
- name: Bundle main and preload
44+
working-directory: apps/desktop
45+
run: bun run build
46+
47+
- name: Run Playwright _electron smoke suite
48+
working-directory: apps/desktop
49+
run: bunx playwright test
50+
51+
- name: Upload test results
52+
if: failure()
53+
uses: actions/upload-artifact@v4
54+
with:
55+
name: desktop-e2e-results-${{ matrix.electron }}
56+
path: apps/desktop/test-results
57+
retention-days: 7
58+
59+
package-smoke:
60+
name: Unsigned package smoke
61+
runs-on: macos-14
62+
steps:
63+
- name: Checkout code
64+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
65+
66+
- name: Setup Bun
67+
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
68+
with:
69+
bun-version: 1.3.13
70+
71+
- name: Setup Node
72+
uses: actions/setup-node@v4
73+
with:
74+
node-version: 22
75+
76+
- name: Install dependencies
77+
run: bun install --frozen-lockfile
78+
79+
- name: Bundle and package unsigned
80+
working-directory: apps/desktop
81+
env:
82+
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
83+
run: |
84+
bun run build
85+
bunx electron-builder --mac dir --publish never

0 commit comments

Comments
 (0)