Compare commits

..

173 Commits

Author SHA1 Message Date
vineyardbovines 4ad5871b11 Use structured content visibility query key 2026-08-16 16:59:48 -04:00
vineyardbovines f4a281e46f Use generated content visibility helpers 2026-08-14 13:36:07 -04:00
vineyardbovines f5e173f6cf Fix content visibility query formatting 2026-08-14 10:39:34 -04:00
vineyardbovines e945cda919 Merge branch 'main' of github.com:bluesky-social/social-app into app-2774
# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/screens/Settings/PrivacyAndSecuritySettings.tsx
2026-08-14 09:17:31 -04:00
Oleksii Bulenok 2d93273956 Fix metro failing to resolve @ipld/dag-cbor in dev mode (#11467) 2026-08-14 14:37:11 +02:00
Samuel Newman 70195d7059 Drop accidental eslint dependency (#11460) 2026-08-14 00:21:52 -07:00
pfrazee afb3cdc26f Nightly source-language update 2026-08-14 03:00:15 +00:00
Samuel Newman bb33eb91cb [SDK] Use finalised package (#11398) 2026-08-13 12:26:23 -07:00
Samuel Newman b89167f07c [SDK] Address stack review feedback (#11389)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:23 -07:00
Samuel Newman 3677c98873 [SDK] Validate and brand the persisted account did (#11387)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:22 -07:00
Samuel Newman 9fe0084874 [SDK] Remove @atproto/api (#11386)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:22 -07:00
Samuel Newman 54da80dfb0 [SDK] Delete the bridge agent and rework the session bundle onto lex clients (#11385)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:22 -07:00
Samuel Newman 59d2bf09d7 [SDK] Swap moderation to @bsky.app/sdk and move labeler config to Client statics (#11383) 2026-08-13 12:26:21 -07:00
Samuel Newman dc31e7fd12 [SDK] Migrate agent sugar to @bsky.app/sdk actions (#11382) 2026-08-13 12:26:21 -07:00
Samuel Newman a4f2811f39 [SDK] Add refreshSession and migrate the session-pinned infra (#11381)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:20 -07:00
Samuel Newman 0c93d1e416 [SDK] Migrate the post pipeline and blob uploads (#11380)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:20 -07:00
Samuel Newman 75e4180800 [SDK] Migrate the repo writes cluster (#11379)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:20 -07:00
Samuel Newman ca226f589d [SDK] Migrate the gate records to raw repo calls on the pds client (#11378)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:19 -07:00
Samuel Newman 3568cdc42d [SDK] Migrate getAllListMembers to the appview client (#11377)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:19 -07:00
Samuel Newman f6a22c4f05 [SDK] Adopt @bsky.app/sdk and migrate RichText (#11369) 2026-08-13 12:26:18 -07:00
Samuel Newman d991118215 [SDK] Route moderation reports through the appview client (#11368)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:18 -07:00
Samuel Newman 34d1c75756 [SDK] Migrate the account surface to the pds and service clients (#11367)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:17 -07:00
Samuel Newman f5c166a055 [SDK] Migrate the notifications cluster to the lex clients (#11362)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:17 -07:00
Samuel Newman 6f696e4b73 [SDK] Migrate the feed api classes to the appview client (#11361)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:17 -07:00
Samuel Newman 4aae6b2c22 [SDK] Migrate search, bookmarks, contacts and feed queries to the lex clients (#11360)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:16 -07:00
Samuel Newman ea4f70fb1e [SDK] Migrate group chat and join links to the chat client (#11358)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:16 -07:00
Samuel Newman 0e8e0b2ab4 [SDK] Migrate chat convo queries to the chat client (#11357)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Matthieu Sieben <matthieusieben@users.noreply.github.com>
2026-08-13 12:26:15 -07:00
Samuel Newman 0912119c77 [SDK] Migrate profile and graph read queries to the appview client (#11356)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:15 -07:00
Samuel Newman 855457c9fa [SDK] Migrate discovery and post-thread queries to the appview client (#11355)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:14 -07:00
Samuel Newman b804f267cc [SDK] Widen #/types/bsky views to dual-world unions (#11353)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:14 -07:00
Samuel Newman c52e730112 [SDK] Add pds and chat clients and the canonical client hooks (#11350)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:14 -07:00
Samuel Newman e1ba8fe1b0 [SDK] Add lexicon schema helpers to #/types/bsky (#11349)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:13 -07:00
Samuel Newman 837771a311 [SDK] Add lex client seam and typed xrpc error matching (#11348)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:13 -07:00
Samuel Newman 532681d5c0 [SDK] Add lexicon codegen pipeline (@atproto/lex) (#11346)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:12 -07:00
Samuel Newman 97b3978fb8 [SDK] Rebuild the session core on PasswordSession behind an AtpAgent compat shim (#11343)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 12:26:12 -07:00
DS Boyce 22b31c52bb Update image in deploy-ota.md (#11462) 2026-08-13 10:50:16 -07:00
vineyardbovines fdb85930d7 Gate content visibility setting 2026-08-13 09:22:12 -04:00
vineyardbovines de35ce6a44 Merge remote-tracking branch 'origin/main' into app-2774
# Conflicts:
#	package.json
#	pnpm-lock.yaml
2026-08-13 09:18:23 -04:00
vineyardbovines fc2fcf0e79 Update atproto API for content visibility lexicon 2026-08-13 09:16:35 -04:00
Darrin Loeliger 2f0d417417 Updated Media Icons (#11457)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 05:49:05 -07:00
Samuel Newman da93da4191 Fix display of link cards in DMs (#11458) 2026-08-13 05:48:56 -07:00
pfrazee 419a49019d Nightly source-language update 2026-08-13 03:00:24 +00:00
DS Boyce aafcb2eeff Remove workaround for iOS text clipping (#11455) 2026-08-12 14:55:25 -07:00
Spence Pope f5f980e58e APP-1645: Improve video upload progress indicator (#11248) 2026-08-12 17:14:06 -04:00
Michael Black 3d736c76b7 APP-2829 Update video lexicon and beta features for 10-minute uploads (#11445) 2026-08-12 14:48:23 -05:00
vineyardbovines 30c44f7df6 Rename content visibility declaration record 2026-08-12 14:12:45 -04:00
Samuel Newman 9b0adcb55f Fix starter pack icon in profile menu (#11452) 2026-08-12 19:03:54 +03:00
DS Boyce bd5e56ce25 Include user's handle in beta features feedback (#11432) 2026-08-12 08:59:21 -07:00
Alex Benzer 34a7bc72a9 Client events for beta features settings page (#11439) 2026-08-12 08:54:29 -07:00
DS Boyce 4ed34cb242 Fix frozen unknown device ID for new accounts (#11449) 2026-08-12 08:54:01 -07:00
Samuel Newman 34274647f2 Separate landing illustrations from splash assets (#11453) 2026-08-12 06:47:03 -07:00
Samuel Newman 1f3c619dd2 Add asset licensing notices (#11397)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2026-08-12 05:49:31 -07:00
Jeffrey Gleason 98fa8eafdf algo team reply feedback (#11444) 2026-08-12 04:30:14 -07:00
pfrazee e7d82063ae Nightly source-language update 2026-08-12 02:58:39 +00:00
DS Boyce b801a24ef2 Fix layout jog when rendering post numbers (#11447) 2026-08-11 13:57:48 -07:00
DS Boyce 82db01fc60 Prevent last line of post text from being clipped on Android (#11433) 2026-08-11 13:57:18 -07:00
Michael Black 9d5e634780 Disallow robots crawling compose intent paths (#11443) 2026-08-11 14:30:59 -05:00
DS Boyce 8c54f01b3e Fix appearance of post numbers on image posts (#11446) 2026-08-11 12:21:05 -07:00
Samuel Newman 17d5dcebf7 Update pnpm to 11.21.0 (#11427) 2026-08-11 06:53:57 -07:00
dependabot[bot] 87548a6578 Bump the actions group with 4 updates (#11435)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 06:30:41 -07:00
vineyardbovines 034345a9b6 Store content visibility in actor record 2026-08-11 09:29:51 -04:00
Tomasz Zawadzki 2a4378c40b perf: skip minute-tick subscription for actors without a live status (#11441)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 06:26:52 -07:00
vineyardbovines 19be24aded Update algorithmic visibility copy 2026-08-11 09:12:46 -04:00
Austin McKinley 89cb08e93c claude: use Opus 4.8 for GitHub workflows (#11436) 2026-08-10 16:34:13 -07:00
Samuel Newman 44a44e86be Bump version to v1.131.0 (#11429) 2026-08-10 09:21:37 -07:00
Austin McKinley 892dcf65c0 Route Claude workflows through Agent Gateway (#11428) 2026-08-10 08:16:53 -07:00
Samuel Newman 51401e4c0c Update claude action (#11420) 2026-08-08 11:41:38 +03:00
DS Boyce 126ffed4ea v1.130.0 Release Prep (#11414) 2026-08-07 09:20:50 -07:00
Samuel Newman 98b9f1c21d Work around reanimated bug in sign up screen (#11411) 2026-08-07 09:09:37 -07:00
RetroSunstar cc3b9ac0d9 Fix chat info panel not using square icons for labellers (#11415) 2026-08-07 07:40:14 -07:00
Tomasz Zawadzki cab5b69a5e Patch react-native-worklets: fix web scheduleOnUI on errors (#11416)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 06:23:04 -07:00
vineyardbovines bb4796de86 Add algorithmic visibility setting 2026-08-07 08:42:39 -04:00
pfrazee a3951c2bd7 Nightly source-language update 2026-08-07 03:07:00 +00:00
DS Boyce 7a47ad3a4d Add name and description for beta feature (#11409) 2026-08-06 16:41:59 -07:00
Samuel Newman d727be8c1a Fix profile icon on custom feeds (#11408)
Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
2026-08-06 16:37:21 -07:00
DS Boyce 1e6725c0a1 Fix post numbers being clipped in some cases (#11413) 2026-08-06 16:34:49 -07:00
DS Boyce 1c349ad7da Add timestamp field to video report dialog (#11339) 2026-08-06 12:11:05 -07:00
Eric Bailey 724013df1b Add repost-only mutes (#11223)
Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
2026-08-06 11:53:02 -07:00
Samuel Newman 1cbe7a39e6 increase ci node heap (#11406) 2026-08-06 05:20:11 -07:00
surfdude29 78a5c222bf Tweak strings (#11407) 2026-08-06 05:18:38 -07:00
Spence Pope 5cf6d8fa21 Use toolbox video compressor (#11246) 2026-08-06 08:16:18 -04:00
Samuel Newman 1ac5a71d61 Use Claude Opus 5 in GitHub workflows (#11335) 2026-08-06 04:14:06 -07:00
Samuel Newman 61283da526 Show app build numbers in CI summaries (#11267) 2026-08-06 04:13:29 -07:00
pfrazee dd7c950cc6 Nightly source-language update 2026-08-06 03:09:55 +00:00
Eric Bailey b79908ecea Add canonical OP thread numbering (#11184)
Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
2026-08-05 10:20:43 -07:00
Spence Pope 8cb6c7babe display backend-driven upload failure messages (#11329) 2026-08-05 12:12:03 -04:00
Spence Pope 32ec5330b0 Enrich fatal HLS errors in Sentry (#11359) 2026-08-05 10:15:26 -04:00
Spence Pope 17a8fe87c2 APP-2793: harden multipart video uploads (#11366) 2026-08-05 10:15:16 -04:00
Spence Pope 0ccc029716 Allow feature-gated 10-minute video uploads (#11388) 2026-08-05 10:15:06 -04:00
pfrazee 5db6b4e2fd Nightly source-language update 2026-08-05 03:10:14 +00:00
Alex Benzer 5494f7deb5 Test requiring at least one interest in onboarding (#11391)
Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
2026-08-04 14:57:43 -07:00
smileyhead ec49988fa7 Add unique context to ‘People I follow’ in FromDropdown.tsx (#11275)
Co-authored-by: Byte <byte@coreyja.com>
2026-08-04 10:47:31 -07:00
Spence Pope f1cfa37bab APP-2764: Remove unnecessary top padding on GIF posts (#11321) 2026-08-04 10:45:44 -04:00
pfrazee a1f094534a Nightly source-language update 2026-08-04 03:10:42 +00:00
dependabot[bot] 863fdcd8c1 Bump the actions group with 6 updates (#11370) 2026-08-03 23:33:04 +03:00
Samuel Newman 1cc942a869 Classify report dialog errors (#11354) 2026-08-03 08:00:25 -07:00
Samuel Newman e99e04a458 Simplify inline badge presentation, fix alignment (#11352) 2026-08-03 06:26:06 -07:00
Samuel Newman c2ceb8057f Handle camera cancellation (#11322) 2026-08-03 06:25:42 -07:00
pfrazee d58be1adea Nightly source-language update 2026-08-01 03:10:42 +00:00
DS Boyce f99a17bb6e Add additional tests for groupNotifications (#11332) 2026-07-31 10:01:14 -07:00
Samuel Newman ce28129674 Add nicer loading screen while OTA is loading (#9499) 2026-07-31 09:39:26 -07:00
Oleksii Bulenok 87a177bbe8 Fix profile badge sometimes displayed below on Android (#11337) 2026-07-31 08:55:08 -07:00
pfrazee 03f8020ee2 Nightly source-language update 2026-07-31 03:11:52 +00:00
Samuel Newman 07c34a6548 Fix PR OTA deployments reverting after apply (#11338)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 23:30:42 +03:00
surfdude29 1c02724ac4 Use Spanish for Aragonese intl-displaynames (#11327) 2026-07-30 22:44:45 +03:00
Samuel Newman 17c1ee4869 Handle PR OTA version mismatches and failures (#11330) 2026-07-30 07:40:49 -07:00
Oleksii Bulenok 157a1893fd fix TextInput resizes on Android when the first chat is typed (#11318) 2026-07-30 12:53:35 +02:00
Oleksii Bulenok cc3c01267d Fix alt text input field not growing more than 3 lines (#11328) 2026-07-30 12:53:09 +02:00
pfrazee 973d0b06f2 Nightly source-language update 2026-07-30 03:10:31 +00:00
DS Boyce 0be8b1dd93 Add a dialog for debugging feature flags to developer settings (#10988) 2026-07-29 16:49:32 -07:00
DS Boyce 91517af01c Log rank and parent index with trending topic events (#11331) 2026-07-29 16:14:44 -07:00
DS Boyce 15c6c7955d Get number of trending topics for Explore from GrowthBook (#11324) 2026-07-29 12:12:15 -07:00
Samuel Newman 904097e8c1 Fix peek menu crash (#11315) 2026-07-29 22:11:42 +03:00
DS Boyce b4bad1187a Indicate if someone followed you via a starter pack (#11320) 2026-07-29 12:11:28 -07:00
Samuel Newman 8e3b252b86 Use debugOptimized variant for Android dev (#11302) 2026-07-29 22:11:16 +03:00
DS Boyce e375ceb5b8 Update empty state for starter pack search (#11325) 2026-07-29 10:59:01 -07:00
DS Boyce 45bdba5dfa Let trending topic names wrap to two lines (#11323) 2026-07-29 10:58:08 -07:00
DS Boyce 196e81eb0c Get trending section indices from GrowthBook (#11319) 2026-07-29 10:24:50 -07:00
Spence Pope a35c4ac4ba APP-2670: add multipart video upload transport (#11222) 2026-07-29 12:54:03 -04:00
Foysal Ahamed c6a40088dd Add TIDA form URL for adult content report (#11150)
Co-authored-by: Eric Bailey <git@esb.lol>
2026-07-29 17:38:27 +02:00
Samuel Newman b57827f23f Enter build numbers manually for production OTA deploys (#11289)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-29 04:48:42 -07:00
pfrazee 27e4f84f3f Nightly source-language update 2026-07-29 03:09:19 +00:00
Samuel Newman 0751420a29 Drop react-native-progress fork (#11312) 2026-07-28 19:47:56 +03:00
Oleksii Bulenok 8263175a03 Fix caret misplaced upon pressing Return when using Japanese symbols … (#11316) 2026-07-28 17:17:29 +02:00
dependabot[bot] 2f36a6de5a Bump slackapi/slack-github-action from 3.0.5 to 4.0.0 (#11307)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-28 07:05:42 -07:00
Samuel Newman de348db116 Show a non-standard OTA channel notice in the drawer (#11288)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-28 06:59:17 -07:00
Samuel Newman 8a6347ad7c Restore version to v1.130.0 (#11314) 2026-07-28 06:58:51 -07:00
Samuel Newman 82c505f286 v1.129.1 Release Prep (#11303) 2026-07-27 23:43:11 -07:00
dependabot[bot] ffdfb3c0e7 Bump actions/setup-go from 6.5.0 to 7.0.0 (#11306)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 23:36:29 -07:00
dependabot[bot] aa06d68ca6 Bump the actions group with 6 updates (#11305)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 23:36:01 -07:00
pfrazee 3ddb4a419e Nightly source-language update 2026-07-28 03:10:21 +00:00
DS Boyce e4ba1b4378 Add trendingTopic:seen metric (#11309) 2026-07-27 17:06:10 -07:00
DS Boyce a5f0e8839a Update trending topics UI (#11304) 2026-07-27 16:34:15 -07:00
DS Boyce d9eacc3d98 Fix keyboard activation for signin button (#11308) 2026-07-27 15:18:43 -07:00
Samuel Newman bbf7603250 Fix Android pager crash during screen transitions (#11301) 2026-07-27 11:31:58 -07:00
Samuel Newman 0e63434bb4 Reenable wake-from-background Android OTA (#11298) 2026-07-27 09:20:10 -07:00
Samuel Newman eb8f5fc4bc Update sonner-native, remove patch (#11300) 2026-07-27 09:19:33 -07:00
Samuel Newman 2ad84e540f Update react-native-uitextview (#11299) 2026-07-27 09:09:48 -07:00
Samuel Newman 97c2c516bd Downbump version to v1.129.1 (#11297) 2026-07-27 07:52:10 -07:00
Samuel Newman 34827ef644 ci: only publish the per-PR OTA when the fingerprint is clean (#11287)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 07:51:55 -07:00
Samuel Newman 691619f297 fix: stop grouping all signup failures into one sentry issue (#11278)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 07:50:40 -07:00
Samuel Newman c2e5a9093c Update play store assetlinks (#11294) 2026-07-27 07:28:28 -07:00
Samuel Newman 9b89a9adba Remove unused ScrollForwarder React Native patches (#11295) 2026-07-27 13:57:46 +03:00
Tomasz Zawadzki 22d657986d Fix IllegalStateException: Required value was null. (#11293)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 03:38:03 -07:00
Samuel Newman abde10b7bd Fix tooltip layout measurement loop (#11284) 2026-07-27 03:16:31 -07:00
Austin McKinley 51bcacb20b Publish one bundle version to both OTA origins, not two (#11291)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:58:20 -07:00
Austin McKinley b662cd43b5 Fix legacy ota1 OTA upload broken by the denis cutover (#11290) 2026-07-27 00:42:38 +03:00
Samuel Newman 66ec855efc Add emoji to GitHub Actions step names for consistency (#11286)
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 00:03:45 +03:00
Samuel Newman d9e76c3954 Restore PR OTA QR comment (#11285) 2026-07-26 23:07:41 +03:00
Samuel Newman be70a31f4c Fix silently-frozen OTA fingerprint baseline with artifacts (#11231)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 23:07:20 +03:00
Tomasz Zawadzki 2b9deeeb6a Migrate runOnUI/runOnJS to scheduleOnUI/scheduleOnRN (#11249) 2026-07-26 12:00:35 -07:00
Samuel Newman d5e335e575 Update version to v1.130.0 (#11274) 2026-07-25 10:58:03 +03:00
Austin McKinley 76a928a7af ci: skip the per-PR OTA job for bot authors (#11273)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:07:57 -07:00
Austin McKinley 9eb78c0f6c ci: publish OTA to denis (S3) + automatic per-PR previews (#11235)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 18:18:00 -07:00
Eric Bailey fb8bda861b Add new config to lingui too (#11270) 2026-07-24 16:19:11 -05:00
estrattonbailey afd267bd2b Nightly source-language update 2026-07-24 17:57:36 +00:00
DS Boyce 93cb42b9f2 Tweak advanced search UI for better localization (#11266) 2026-07-24 10:40:37 -07:00
DS Boyce 549e3d7a7d Fix issue where form controls appear above sticky headers (#11254) 2026-07-24 10:40:16 -07:00
Samuel Newman 74a89832a3 v1.129.0 release prep (#11265) 2026-07-24 09:43:38 -07:00
Samuel Newman 19b0d89313 Update react-native-uitextview (#11264) 2026-07-24 07:40:46 -07:00
Spence Pope 98e135f489 Fix native signup captcha interactions (#11263) 2026-07-24 08:46:59 -04:00
Samuel Newman fbb0ee38c2 Add hitslop to switch account ... menu (#11262) 2026-07-24 05:44:45 -07:00
Eric Bailey 7bd27e4180 Add intl note on OTA flow (#11241) 2026-07-24 03:48:55 -07:00
dependabot[bot] 07a810945d Bump github.com/hashicorp/go-retryablehttp from 0.7.5 to 0.7.7 in /bskyweb (#11257)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 03:48:39 -07:00
Samuel Newman 63b284030a Fix Maestro E2E regressions on iOS and Android (#11258) 2026-07-24 03:48:03 -07:00
Samuel Newman 3b6fbf315b Fix Maestro E2E runtime environment (#11247) 2026-07-24 03:30:10 -07:00
DS Boyce 97169dbc35 Tweak appearance of trending topics (#11253) 2026-07-24 03:29:55 -07:00
Tomasz Zawadzki 22a4958321 Migrate useScrollViewOffset to useScrollOffset (#11250) 2026-07-24 07:09:01 +03:00
pfrazee a9df815bd0 Nightly source-language update 2026-07-24 03:10:54 +00:00
Eric Bailey a53df16e82 Update API package and filter AA configs by platform (#11244) 2026-07-23 14:15:09 -05:00
Samuel Newman 4e3d1704a6 Fix scroll forwarder web entrypoint (#11245) 2026-07-23 11:17:36 -07:00
DS Boyce 723a60e979 Display post counts greater than 1,000 (#11242) 2026-07-23 10:26:47 -07:00
Spence Pope 325246ec6c Stub RN codegen native component on web (#11240) 2026-07-23 12:52:47 -04:00
1109 changed files with 164997 additions and 74230 deletions
+50
View File
@@ -0,0 +1,50 @@
---
name: Setup denis CLI
description: >
Download and verify the denis OTA publish binary from the (private)
bluesky-social/tango releases and put it on PATH. Uses a short-lived GitHub
App token scoped to contents:read on tango, since the default GITHUB_TOKEN
cannot read a private repo's releases.
inputs:
release-tag:
description: denis release tag in bluesky-social/tango to download
required: true
app-id:
description: GitHub App ID for the token used to read tango releases
required: true
private-key:
description: GitHub App private key
required: true
runs:
using: composite
steps:
- name: 🔑 Mint tango read token
id: tango-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ inputs.app-id }}
private-key: ${{ inputs.private-key }}
repositories: tango
permission-contents: read
- name: ⬇️ Download and verify denis binary
shell: bash
env:
GH_TOKEN: ${{ steps.tango-token.outputs.token }}
RELEASE_TAG: ${{ inputs.release-tag }}
run: |
BIN_DIR="$RUNNER_TEMP/denis-bin"
mkdir -p "$BIN_DIR"
cd "$BIN_DIR"
gh release download "$RELEASE_TAG" \
--repo bluesky-social/tango \
--pattern denis-linux-amd64 \
--pattern denis-linux-amd64.sha256 \
--clobber
# Verify before making it executable / putting it on PATH.
sha256sum -c denis-linux-amd64.sha256
mv denis-linux-amd64 denis
chmod +x denis
echo "$BIN_DIR" >> "$GITHUB_PATH"
+1 -1
View File
@@ -136,7 +136,7 @@ wait_for_port 1986 "the E2E mock-server manager"
phase "Starting Metro"
EXPO_PUBLIC_ENV=e2e \
NODE_ENV=test \
NODE_ENV=development \
RN_SRC_EXT=e2e.ts,e2e.tsx \
pnpm exec expo start --dev-client --clear --port 8081 \
>"$artifact_dir/metro.log" 2>&1 &
+41
View File
@@ -0,0 +1,41 @@
name: Asset notices
# Verifies that every path named in ASSETS.md and NOTICE.md still exists.
#
# Those files tell forkers which assets our MIT license does not cover, and carry the third-party
# attribution notices we are required to pass along. If an asset moves and the notice is not
# updated, the notice silently stops meaning anything. This job makes that visible in review
# rather than a year later.
#
# No paths filter: the notices also reference files under src/ (e.g. the inline logo components),
# so any rename anywhere in the tree can rot a notice. The check runs in under a second.
on:
push:
branches: [main]
pull_request:
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
cancel-in-progress: true
permissions:
contents: read
jobs:
check:
name: Check asset licensing notices
runs-on: ubuntu-latest
steps:
- name: ⬇️ Check out Git repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: ⚙️ Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
- name: 📜 Verify asset notices
run: node scripts/check-asset-notices.mjs
@@ -21,20 +21,20 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Docker buildx
- name: 🔧 Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
- name: 🔑 Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USERNAME}}
password: ${{ env.PASSWORD }}
- name: Extract Docker metadata
- name: 🏷️ Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
@@ -43,13 +43,13 @@ jobs:
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
- name: Env
- name: 📝 Env
id: env
run: |
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Build and push Docker image
- name: 🚀 Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
@@ -22,20 +22,20 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Docker buildx
- name: 🔧 Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
- name: 🔑 Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USERNAME }}
password: ${{ env.PASSWORD }}
- name: Extract Docker metadata
- name: 🏷️ Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
@@ -44,7 +44,7 @@ jobs:
tags: |
type=sha,enable=true,priority=100,prefix=bskyweb:,suffix=,format=long
- name: Build and push Docker image
- name: 🚀 Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
@@ -21,20 +21,20 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Docker buildx
- name: 🔧 Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
- name: 🔑 Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USERNAME}}
password: ${{ env.PASSWORD }}
- name: Extract Docker metadata
- name: 🏷️ Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
@@ -43,7 +43,7 @@ jobs:
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
- name: Build and push Docker image
- name: 🚀 Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
@@ -21,20 +21,20 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Docker buildx
- name: 🔧 Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
- name: 🔑 Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USERNAME}}
password: ${{ env.PASSWORD }}
- name: Extract Docker metadata
- name: 🏷️ Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
@@ -43,7 +43,7 @@ jobs:
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
- name: Build and push Docker image
- name: 🚀 Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
@@ -21,20 +21,20 @@ jobs:
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Docker buildx
- name: 🔧 Setup Docker buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
- name: 🔑 Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USERNAME}}
password: ${{ env.PASSWORD }}
- name: Extract Docker metadata
- name: 🏷️ Extract Docker metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
@@ -43,7 +43,7 @@ jobs:
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
- name: Build and push Docker image
- name: 🚀 Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
+17 -23
View File
@@ -79,7 +79,7 @@ jobs:
version-code: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 5
@@ -88,7 +88,7 @@ jobs:
with:
expo-token: ${{ secrets.EXPO_TOKEN }}
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "17"
@@ -132,6 +132,16 @@ jobs:
if-no-files-found: error
path: build.aab
- name: 📝 Write build summary
env:
REMOTE_VERSION_CODE: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
run: |
{
echo "### Android build number"
echo
echo "\`$REMOTE_VERSION_CODE\`"
} >> "$GITHUB_STEP_SUMMARY"
submit:
name: Submit to Google Play
runs-on: ubuntu-latest
@@ -141,7 +151,7 @@ jobs:
steps:
# eas submit reads app config from the repo, so we need a checkout.
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 5
@@ -162,7 +172,7 @@ jobs:
- name: 🔔 Notify Slack of Play Store Submission
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
webhook-type: incoming-webhook
@@ -170,22 +180,6 @@ jobs:
payload: |
{"text": "Android ${{ inputs.profile || 'testflight-android' }} build submitted to Google Play!\n```Version Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.version-code }}```"}
# Record the commit only after a successful submit, so a failed submit doesn't
# advance the "most recent testflight" marker.
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: ${{ inputs.profile == 'testflight-android' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ inputs.profile == 'testflight-android' }}
env:
GITHUB_SHA: ${{ github.sha }}
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
# Runs in parallel with submit: the QA APK shouldn't be blocked by a Play submission failure.
universalApk:
name: Build universal APK
@@ -201,7 +195,7 @@ jobs:
# bundletool needs a JRE. ubuntu-latest ships a default JDK, but pin it explicitly
# like the build job so the toolchain is deterministic.
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "17"
@@ -251,7 +245,7 @@ jobs:
path: build.apk
- name: 🔔 Notify Slack of APK Artifact
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
webhook-type: incoming-webhook
@@ -314,7 +308,7 @@ jobs:
- name: 🔔 Notify Slack of Release Attachment
if: ${{ steps.release-check.outputs.exists == 'true' }}
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
webhook-type: incoming-webhook
+27 -20
View File
@@ -18,6 +18,11 @@ on:
- QA Team
- Software Mansion
default: none
changelog:
type: string
description: TestFlight "What to Test" notes (only applied when a group is selected)
required: false
default: ''
workflow_call:
inputs:
profile:
@@ -81,7 +86,7 @@ jobs:
build-number: ${{ steps.ipa-build-number.outputs.build-number }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 5
@@ -211,6 +216,16 @@ jobs:
${{ env.BUILD_DIR }}/Bluesky.ipa
${{ env.BUILD_DIR }}/Bluesky.app.dSYM.zip
- name: 📝 Write build summary
env:
REMOTE_BUILD_NUMBER: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
run: |
{
echo "### iOS build number"
echo
echo "\`$REMOTE_BUILD_NUMBER\`"
} >> "$GITHUB_STEP_SUMMARY"
submit:
name: Submit iOS
# Submission and dSYM upload are I/O bound and don't need the xlarge builder.
@@ -218,7 +233,7 @@ jobs:
needs: [build]
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# eas submit reads the app config from the repo
fetch-depth: 5
@@ -246,7 +261,7 @@ jobs:
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
webhook-type: incoming-webhook
@@ -254,22 +269,6 @@ jobs:
payload: |
{"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ needs.build.outputs.package-version }}\nBuild Number: ${{ needs.build.outputs.build-number }}```"}
# Record the commit only after a successful submit, so a failed submit doesn't advance
# the baseline used for the next testflight build's changelog.
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: ${{ inputs.profile == 'testflight' }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
env:
GITHUB_SHA: ${{ github.sha }}
if: ${{ inputs.profile == 'testflight' }}
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
distribute:
name: Assign build to TestFlight group
# fastlane and jq ship preinstalled on the macOS runner image, and this step mostly idles
@@ -286,6 +285,7 @@ jobs:
- name: 🧪 Assign build to TestFlight group
env:
TESTFLIGHT_GROUP: ${{ inputs.testFlightGroup }}
CHANGELOG: ${{ inputs.changelog }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_P8_BASE64: ${{ secrets.ASC_KEY_P8_BASE64 }}
@@ -304,6 +304,12 @@ jobs:
--arg key "$key_content" \
'{key_id: $key_id, issuer_id: $issuer_id, key: $key, in_house: false}' \
> asc_api_key.json
# changelog is optional, and passing it empty would blank out whatever "What to
# Test" notes the build already has, so only include the flag when it is set.
changelog_arg=()
if [ -n "$CHANGELOG" ]; then
changelog_arg=(changelog:"$CHANGELOG")
fi
# app_platform is required in non-interactive mode: distribute_only otherwise
# calls fetch_app_platform, which prompts for input and crashes without a TTY.
fastlane run upload_to_testflight \
@@ -314,4 +320,5 @@ jobs:
app_version:"$APP_VERSION" \
build_number:"$BUILD_NUMBER" \
groups:"$TESTFLIGHT_GROUP" \
notify_external_testers:true
notify_external_testers:true \
"${changelog_arg[@]}"
+193 -25
View File
@@ -17,16 +17,36 @@ on:
type: string
description: Runtime version (in x.x.x format) that this update is for
required: true
iosBuildNumber:
type: string
description: iOS build number of the native build this update targets
(required for production)
androidVersionCode:
type: string
description: Android version code of the native build this update
targets (required for production)
# Deploys happen via EAS using EXPO_TOKEN; the GITHUB_TOKEN only checks out code
permissions:
contents: read
# denis release tag in bluesky-social/tango whose linux-amd64 binary this
# workflow downloads to publish OTA bundles. Bump this one line to roll denis.
env:
DENIS_RELEASE_TAG: denis-v0.1.1
jobs:
bundleDeploy:
if: github.repository == 'bluesky-social/social-app'
name: Bundle and Deploy EAS Update
runs-on: ubuntu-latest
# id-token: write lets this job mint an OIDC token to assume the denis
# publish role; actions: read loads the fingerprint baseline artifact;
# contents: read is still needed for the checkout.
permissions:
id-token: write
actions: read
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-deploy
cancel-in-progress: true
@@ -36,7 +56,7 @@ jobs:
steps.version.outputs.version-changed }}
steps:
- name: Check for EXPO_TOKEN
- name: 🔑 Check for EXPO_TOKEN
run: >
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
@@ -49,12 +69,25 @@ jobs:
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
if: ${{ inputs.runtimeVersion }}
run: |
if [ -z "$RUNTIME_VERSION" ]; then
[[ "$RUNTIME_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1
fi
[[ "$RUNTIME_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1
# Production OTAs are bound to the specific native build they target, so
# the build numbers must be entered manually rather than read from the
# global EAS counters, which advance with every testflight build and so
# point past older production releases
- name: 🧐 Validate build numbers
if: ${{ inputs.channel == 'production' }}
env:
IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }}
ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }}
run: |
[[ "$IOS_BUILD_NUMBER" =~ ^[0-9]+$ ]] ||
(echo "A numeric iosBuildNumber is required for production updates" && exit 1)
[[ "$ANDROID_VERSION_CODE" =~ ^[0-9]+$ ]] ||
(echo "A numeric androidVersionCode is required for production updates" && exit 1)
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
@@ -83,7 +116,7 @@ jobs:
echo "version-changed=true" >> "$GITHUB_OUTPUT"
fi
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -91,23 +124,69 @@ jobs:
node-version-file: package.json
cache: pnpm
- name: ⬇️ Load fingerprint baseline
id: baseline
if: ${{ (inputs.channel || 'testflight') == 'testflight' }}
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY_ID: ${{ github.repository_id }}
run: |
url=$(gh api \
"repos/${GITHUB_REPOSITORY}/actions/artifacts?name=testflight-native-fingerprint&per_page=100" \
--jq "[.artifacts[] | select(
.expired == false and
.workflow_run.head_branch == \"main\" and
.workflow_run.head_repository_id == (\$ENV.REPOSITORY_ID | tonumber)
)] | max_by(.created_at) | .archive_download_url" \
2>/dev/null || true)
if [ -n "$url" ] && [ "$url" != "null" ]; then
mkdir baseline-artifact
if curl -sSL -H "Authorization: Bearer $GH_TOKEN" -o baseline.zip "$url" \
&& unzip -q baseline.zip -d baseline-artifact; then
if jq -e '.sources | type == "array"' \
baseline-artifact/native-fingerprint.json >/dev/null; then
echo "path=baseline-artifact/native-fingerprint.json" >> "$GITHUB_OUTPUT"
else
echo "::warning::Ignoring invalid fingerprint baseline artifact."
fi
else
echo "::warning::Could not download fingerprint baseline artifact."
fi
fi
- name: 📷 Check fingerprint and install dependencies
id: fingerprint
uses: bluesky-social/github-actions/fingerprint-native@b5556913e4aef3964cfd5936d0add3fc0d809bdb # v0.2.0
uses: bluesky-social/github-actions/fingerprint-native@abc6a46eb4badf243f55bfd7d6cec42722456300 # v0.3.0
with:
profile: ${{ inputs.channel || 'testflight' }}
previous-commit-tag: ${{ inputs.runtimeVersion }}
# The recordBaseline job uploads this marker after a successful deploy;
# on the native path, that requires both builds to succeed. A missing
# marker forces native builds so they can seed the baseline safely.
baseline-fingerprint-path: ${{ steps.baseline.outputs.path }}
# Hand the full fingerprint to recordBaseline through a short-lived
# artifact. It is uploaded unconditionally but promoted to the persistent
# baseline only after both native builds succeed.
- name: 🚀 Upload native fingerprint
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: native-fingerprint-${{ github.run_id }}
path: ${{ steps.fingerprint.outputs.current-fingerprint-path }}
retention-days: 1
if-no-files-found: error
- name: 🔤 Compile translations
uses: ./.github/actions/compile-i18n
- name: Lint check
- name: 🧹 Lint check
run: pnpm lint
- name: Prettier check
- name: 💅 Prettier check
run: pnpm prettier --check .
- name: Type check
- name: 🔎 Type check
run: pnpm typecheck
- name: 🔨 Setup EAS
@@ -146,7 +225,61 @@ jobs:
SENTRY_DIST=${{ steps.env.outputs.bundle-identifier }}
pnpm export
- name: 📦 Package Bundle and 🚀 Deploy
# Pin ONE bundle version for both publishes below. Each script used to call
# `date +%s` itself, so the same bytes reached denis and ota1 under versions
# seconds apart (observed: 1785102575 vs 1785102614). The version is part of
# the asset URL path, so each origin then served a manifest referencing a
# path only it had -- meaning a manifest fetched from one origin and assets
# fetched from the other 404. Both scripts fall back to `date +%s` when this
# is unset, so single-publisher callers are unaffected.
- name: 🔢 Pin bundle version
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
run: echo "BUNDLE_VERSION=$(date +%s)" >> "$GITHUB_ENV"
# denis on EKS has been the sole origin for updates.bsky.app since
# 2026-07-26, so it publishes FIRST: it is the path that actually serves
# clients. The legacy ota1 upload runs after it, and exists only so that
# rolling the Bunny origin back to ota1 would find current bundles there.
#
# The ordering is load-bearing, not cosmetic. While the legacy step ran
# first, its failure skipped these steps and nothing reached EITHER origin
# -- the dual-write took down the working path with it. Both steps are
# still required to pass, so a stale ota1 remains a loud failure, but the
# publish that serves users has already landed before the legacy one can
# fail.
#
# Both halves are removed together when ota1 is decommissioned (Phase 5).
- name: ☁️ Configure AWS credentials (denis)
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: arn:aws:iam::007404326489:role/denis-ci-publish
aws-region: us-east-2
- name: ⬇️ Setup denis CLI
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
uses: ./.github/actions/setup-denis
with:
release-tag: ${{ env.DENIS_RELEASE_TAG }}
app-id: ${{ vars.SYNC_INTERNAL_APP_ID }}
private-key: ${{ secrets.SYNC_INTERNAL_PK }}
- name: 🚀 Publish OTA to denis (S3)
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
run: pnpm use-build-number bash scripts/denisPublish.sh
env:
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
# When set (required for production), these take precedence over the
# global EAS counters inside the use-build-number wrapper
BSKY_IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }}
BSKY_ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }}
- name: 📦 Package Bundle and 🚀 Deploy (legacy ota1)
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
run: pnpm use-build-number bash scripts/bundleUpdate.sh
@@ -154,20 +287,10 @@ jobs:
DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }}
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
with:
path: most-recent-testflight-commit.txt
key: most-recent-testflight-commit
- name: ✏️ Write commit hash to cache
if: ${{ !steps.fingerprint.outputs.includes-changes &&
!steps.version.outputs.version-changed }}
run: echo $GITHUB_SHA > most-recent-testflight-commit.txt
# When set (required for production), these take precedence over the
# global EAS counters inside the use-build-number wrapper
BSKY_IOS_BUILD_NUMBER: ${{ inputs.iosBuildNumber }}
BSKY_ANDROID_VERSION_CODE: ${{ inputs.androidVersionCode }}
buildIfNecessaryIOS:
name: Build and Submit iOS
@@ -230,3 +353,48 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
# Advance the fingerprint baseline only after BOTH native builds have shipped
# the new native surface. This replaces the old actions/cache baseline, which
# only advanced on cache eviction and so silently froze - freezing meant every
# fingerprint looked changed and OTA updates stopped deploying entirely.
#
# On the native-build path, this runs only after both builds succeed. A
# successful OTA deploy also records its fingerprint, refreshing the
# persistent marker's retention without changing the native baseline.
#
# The persistent artifact replaces the old actions/cache marker without
# requiring a PAT or mutable repository variable. Each successful deploy adds
# an immutable marker; the next run reads the newest non-expired one using the
# built-in GITHUB_TOKEN.
recordBaseline:
name: Record fingerprint baseline
runs-on: ubuntu-latest
needs: [bundleDeploy, buildIfNecessaryIOS, buildIfNecessaryAndroid]
if: ${{ always() &&
(inputs.channel || 'testflight') == 'testflight' &&
needs.bundleDeploy.result == 'success' &&
(needs.bundleDeploy.outputs.changes-detected != 'true' ||
(needs.buildIfNecessaryIOS.result == 'success' &&
needs.buildIfNecessaryAndroid.result == 'success')) &&
github.repository == 'bluesky-social/social-app' }}
permissions:
actions: read
steps:
- name: ⬇️ Download native fingerprint
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: native-fingerprint-${{ github.run_id }}
- name: 🧐 Validate native fingerprint
run: >
jq -e '.sources | type == "array"' native-fingerprint.json >/dev/null ||
(echo "::error::native fingerprint artifact was invalid; refusing to record it as the baseline." && exit 1)
- name: 🚀 Record fingerprint baseline
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: testflight-native-fingerprint
path: native-fingerprint.json
retention-days: 90
if-no-files-found: error
+11 -12
View File
@@ -26,6 +26,7 @@ permissions:
pull-requests: write
issues: write
actions: read
# Required for claude-code-action's GitHub App token exchange.
id-token: write
jobs:
@@ -53,24 +54,22 @@ jobs:
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
- name: 🤖 Claude
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1.0.187
env:
ANTHROPIC_BASE_URL: https://agentgateway.k1.prod.bsky.dev
with:
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
aws-region: us-east-2
- name: Claude
uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171
with:
use_bedrock: 'true'
# Agent Gateway service keys use Authorization: Bearer, the wire
# shape emitted by the action's OAuth-token input.
claude_code_oauth_token: ${{ secrets.AGENT_GATEWAY_CLAUDE_GH_REVIEW_KEY }}
additional_permissions: |
actions: read
track_progress: true
claude_args: |
--model global.anthropic.claude-opus-4-8
--model claude-opus-4-8:api
--allowedTools "mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
+15 -17
View File
@@ -1,7 +1,7 @@
name: claude-review
# Automatic Claude review on PR creation/update, via Bedrock (OIDC, no
# long-lived tokens). Self-contained: this intentionally uses upstream
# Automatic Claude review on PR creation/update, via Agent Gateway.
# Self-contained: this intentionally uses upstream
# claude-code-action defaults rather than the org reusable workflows in
# bluesky-social/.github (which a public repo cannot call, and whose
# customizations added no value over upstream).
@@ -16,14 +16,14 @@ permissions:
contents: read
pull-requests: write
actions: read
# Required for claude-code-action's GitHub App token exchange.
id-token: write
jobs:
review:
# Internal PRs only. This repo is public: fork PRs are the bulk of
# community traffic and MUST NOT trigger reviews (no Bedrock spend on
# unvetted code, and fork PRs can't mint the OIDC token anyway —
# belt-and-braces with this explicit guard). Branch PRs can only be
# community traffic and MUST NOT trigger reviews (no gateway spend on
# unvetted code). Branch PRs can only be
# created by people with write access, i.e. org members.
# Bot-authored PRs (dependabot, changesets) are also skipped.
if: >
@@ -39,26 +39,24 @@ jobs:
cancel-in-progress: true
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
- name: 🤖 Claude review
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1.0.187
env:
ANTHROPIC_BASE_URL: https://agentgateway.k1.prod.bsky.dev
with:
role-to-assume: ${{ secrets.AWS_BEDROCK_REVIEW_ROLE_ARN }}
aws-region: us-east-2
- name: Claude review
uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171
with:
use_bedrock: 'true'
# Agent Gateway service keys use Authorization: Bearer, the wire
# shape emitted by the action's OAuth-token input.
claude_code_oauth_token: ${{ secrets.AGENT_GATEWAY_CLAUDE_GH_REVIEW_KEY }}
additional_permissions: |
actions: read
track_progress: true
claude_args: |
--model global.anthropic.claude-opus-4-8
--model claude-opus-4-8:api
--allowedTools "mcp__github_inline_comment__create_inline_comment,mcp__github_ci__get_ci_status,mcp__github_ci__download_job_log,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
prompt: |
REPO: ${{ github.repository }}
+14 -14
View File
@@ -17,32 +17,32 @@ jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Git Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Go tooling
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
- name: ⬇️ Git Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🔧 Set up Go tooling
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: bskyweb/go.mod
cache-dependency-path: bskyweb/go.sum
- name: Dummy Static Files
- name: 📄 Dummy Static Files
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Check
- name: 🔍 Check
run: cd bskyweb/ && make check
- name: Build (binary)
- name: 🏗️ Build (binary)
run: cd bskyweb/ && make build
- name: Test
- name: 🧪 Test
run: cd bskyweb/ && make test
lint:
runs-on: ubuntu-latest
steps:
- name: Git Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Go tooling
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
- name: ⬇️ Git Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🔧 Set up Go tooling
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: bskyweb/go.mod
cache-dependency-path: bskyweb/go.sum
- name: Dummy Static Files
- name: 📄 Dummy Static Files
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Lint
- name: 🧹 Lint
run: cd bskyweb/ && make lint
+25 -18
View File
@@ -22,11 +22,18 @@ jobs:
fail-fast: false
matrix:
job:
[lint, prettier, 'typecheck:ios', 'typecheck:android', 'typecheck:web']
[
lint,
prettier,
'lexicons:verify',
'typecheck:ios',
'typecheck:android',
'typecheck:web',
]
steps:
- name: Check out Git repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Verify Node version pins match package.json
- name: ⬇️ Check out Git repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: 🔍 Verify Node version pins match package.json
run: |
set -euo pipefail
expected=$(node -p "require('./package.json').engines.node.replace(/[^0-9.]/g, '')")
@@ -51,17 +58,17 @@ jobs:
v=$(grep -oE '"node":[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' eas.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -u)
check "eas.json" "$v"
exit $rc
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Install node
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Install node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
cache: pnpm
- name: pnpm install
- name: 📦 pnpm install
run: pnpm install --frozen-lockfile
- name: Check & compile i18n
- name: 🔤 Check & compile i18n
run: pnpm intl:build
- name: Lint checks
- name: 🧹 Lint checks
run: pnpm ${{ matrix.job }}
# Aggregates the matrix results into a single stable check name so branch
# protection can require "Run linters" regardless of how many matrix jobs run.
@@ -73,7 +80,7 @@ jobs:
needs: [linting]
runs-on: ubuntu-latest
steps:
- name: Require linting to have succeeded
- name: Require linting to have succeeded
env:
RESULT: ${{ needs.linting.result }}
run: |
@@ -87,19 +94,19 @@ jobs:
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Check out Git repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Install node
- name: ⬇️ Check out Git repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Install node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
cache: pnpm
- name: pnpm install
- name: 📦 pnpm install
run: pnpm install --frozen-lockfile
- name: Check & compile i18n
- name: 🔤 Check & compile i18n
run: pnpm intl:build
- name: Run tests
- name: 🧪 Run tests
run: |
NODE_ENV=test pnpm test --forceExit --shard=${{ matrix.shard }}/${{ strategy.job-total }}
# Aggregates the sharded test results into a single stable check name so branch
@@ -112,7 +119,7 @@ jobs:
needs: [testing]
runs-on: ubuntu-latest
steps:
- name: Require testing to have succeeded
- name: Require testing to have succeeded
env:
RESULT: ${{ needs.testing.result }}
run: |
+3 -3
View File
@@ -25,7 +25,7 @@ jobs:
notes: ${{ steps.notes.outputs.notes }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
@@ -144,7 +144,7 @@ jobs:
} >> "$GITHUB_OUTPUT"
- name: 🔔 Notify Slack
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
webhook-type: incoming-webhook
@@ -174,7 +174,7 @@ jobs:
} >> "$GITHUB_OUTPUT"
- name: 🔔 Notify Slack
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.NIGHTLY_BUILDS_SLACK_WEBHOOK }}
webhook-type: incoming-webhook
+44 -44
View File
@@ -28,41 +28,41 @@ jobs:
runs-on: macos-26-xlarge
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Select Xcode 26.4
- name: 🛠️ Select Xcode 26.4
uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0
with:
xcode-version: "26.4"
- name: Prepare E2E configuration
- name: 🧰 Prepare E2E configuration
run: |
mkdir -p artifacts/ios
echo "Installing dependencies" > artifacts/ios/phase.txt
cp .env.example .env.test
cp .env.example .env.development
cp google-services.json.example google-services.json
- name: Set up Expo project
- name: 🔧 Set up Expo project
uses: ./.github/actions/setup-expo-project
with:
expo-token: ${{ secrets.EXPO_TOKEN }}
- name: Set up Java 17
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- name: ☕️ Set up Java 17
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: "17"
- name: Install dev-env dependencies
- name: 📦 Install dev-env dependencies
run: pnpm --dir dev-env install --frozen-lockfile 2>&1 | tee artifacts/ios/dependencies.log
- name: Compile translations
- name: 🔤 Compile translations
uses: ./.github/actions/compile-i18n
- name: Install Maestro 2.6.1
- name: 📥 Install Maestro 2.6.1
run: |
echo "Installing Maestro" > artifacts/ios/phase.txt
curl -fsSL -o "$RUNNER_TEMP/maestro.zip" \
@@ -74,7 +74,7 @@ jobs:
"$RUNNER_TEMP/maestro/bin/maestro" --version | tee artifacts/ios/maestro-version.log
test "$("$RUNNER_TEMP/maestro/bin/maestro" --version)" = "$MAESTRO_VERSION"
- name: Boot one iOS simulator
- name: 📱 Boot one iOS simulator
run: |
echo "Booting iOS simulator" > artifacts/ios/phase.txt
device_name="iPhone 17"
@@ -112,10 +112,10 @@ jobs:
xcrun simctl bootstatus "$udid" -b
echo "Using $device_name on $runtime_name ($udid)"
- name: Mark iOS development client build phase
- name: 🏷️ Mark iOS development client build phase
run: echo "Building the iOS development client" > artifacts/ios/phase.txt
- name: Build iOS development client
- name: 🏗️ Build iOS development client
uses: ./.github/actions/eas-local-build
with:
platform: ios
@@ -123,7 +123,7 @@ jobs:
output: ${{ runner.temp }}/nightly-e2e-ios.tar.gz
log-path: artifacts/ios/build.log
- name: Install iOS development client
- name: 📲 Install iOS development client
run: |
build_contents="$RUNNER_TEMP/nightly-e2e-ios-build"
mkdir -p "$build_contents"
@@ -135,14 +135,14 @@ jobs:
fi
xcrun simctl install "$IOS_UDID" "$app_path" 2>&1 | tee -a artifacts/ios/build.log
- name: Run iOS Maestro suite
- name: 🧪 Run iOS Maestro suite
run: .github/scripts/run-nightly-e2e.sh ios "$IOS_UDID"
- name: Clean up iOS services and simulator
- name: 🧹 Clean up iOS services and simulator
if: always()
run: .github/scripts/cleanup-nightly-e2e.sh ios "${IOS_UDID:-}"
- name: Upload iOS E2E artifacts
- name: 🚀 Upload iOS E2E artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -158,36 +158,36 @@ jobs:
runs-on: Linux-x64-32core
timeout-minutes: 120
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Prepare E2E configuration
- name: 🧰 Prepare E2E configuration
run: |
mkdir -p artifacts/android
echo "Installing dependencies" > artifacts/android/phase.txt
cp .env.example .env.test
cp .env.example .env.development
cp google-services.json.example google-services.json
- name: Set up Expo project
- name: 🔧 Set up Expo project
uses: ./.github/actions/setup-expo-project
with:
expo-token: ${{ secrets.EXPO_TOKEN }}
- name: Set up Java 17
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- name: ☕️ Set up Java 17
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: "17"
- name: Install dev-env dependencies
- name: 📦 Install dev-env dependencies
run: pnpm --dir dev-env install --frozen-lockfile 2>&1 | tee artifacts/android/dependencies.log
- name: Compile translations
- name: 🔤 Compile translations
uses: ./.github/actions/compile-i18n
- name: Install Maestro 2.6.1
- name: 📥 Install Maestro 2.6.1
run: |
echo "Installing Maestro" > artifacts/android/phase.txt
curl -fsSL -o "$RUNNER_TEMP/maestro.zip" \
@@ -199,7 +199,7 @@ jobs:
"$RUNNER_TEMP/maestro/bin/maestro" --version | tee artifacts/android/maestro-version.log
test "$("$RUNNER_TEMP/maestro/bin/maestro" --version)" = "$MAESTRO_VERSION"
- name: Install and boot one Android emulator
- name: 📱 Install and boot one Android emulator
run: |
echo "Booting Android emulator" > artifacts/android/phase.txt
android_sdk="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-/usr/local/lib/android/sdk}}"
@@ -299,10 +299,10 @@ jobs:
adb -s emulator-5554 shell settings put global transition_animation_scale 0
adb -s emulator-5554 shell settings put global animator_duration_scale 0
- name: Mark Android development client build phase
- name: 🏷️ Mark Android development client build phase
run: echo "Building the Android development client" > artifacts/android/phase.txt
- name: Build Android development client
- name: 🏗️ Build Android development client
uses: ./.github/actions/eas-local-build
with:
platform: android
@@ -310,15 +310,15 @@ jobs:
output: ${{ runner.temp }}/nightly-e2e-android.apk
log-path: artifacts/android/build.log
- name: Install Android development client
- name: 📲 Install Android development client
run: |
adb -s emulator-5554 install -r "$RUNNER_TEMP/nightly-e2e-android.apk" \
2>&1 | tee -a artifacts/android/build.log
- name: Run Android Maestro suite
- name: 🧪 Run Android Maestro suite
run: .github/scripts/run-nightly-e2e.sh android emulator-5554
- name: Capture emulator crash diagnostics
- name: 🩺 Capture emulator crash diagnostics
if: always()
run: |
{
@@ -338,11 +338,11 @@ jobs:
ls -la /tmp/android-runner/emu-crash-*.db 2>/dev/null || echo "No crash database found"
} > artifacts/android/emulator-diagnostics.log 2>&1
- name: Clean up Android services and emulator
- name: 🧹 Clean up Android services and emulator
if: always()
run: .github/scripts/cleanup-nightly-e2e.sh android emulator-5554
- name: Upload Android E2E artifacts
- name: 🚀 Upload Android E2E artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@@ -360,26 +360,26 @@ jobs:
actions: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download iOS artifacts
- name: ⬇️ Download iOS artifacts
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: nightly-e2e-ios-${{ github.run_id }}
path: downloaded-artifacts/ios
- name: Download Android artifacts
- name: ⬇️ Download Android artifacts
continue-on-error: true
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: nightly-e2e-android-${{ github.run_id }}
path: downloaded-artifacts/android
- name: Resolve artifact links
- name: 🔗 Resolve artifact links
env:
GH_TOKEN: ${{ github.token }}
run: |
@@ -395,7 +395,7 @@ jobs:
}' artifact-response.json > artifact-links.json
fi
- name: Summarize platform results
- name: 📊 Summarize platform results
id: summary
env:
IOS_STATUS: ${{ needs.ios.result }}
@@ -415,9 +415,9 @@ jobs:
echo "payload=$(jq -c .payload e2e-summary.json)" >> "$GITHUB_OUTPUT"
jq -r .githubSummary e2e-summary.json >> "$GITHUB_STEP_SUMMARY"
- name: Notify Slack of E2E failures
- name: 🔔 Notify Slack of E2E failures
if: steps.summary.outputs.notify == 'true'
uses: slackapi/slack-github-action@0d95c9a7becc1e6e297d76df9bc735c44f4cbcbc # v3.0.5
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.E2E_FAILURES_SLACK_WEBHOOK }}
webhook-type: incoming-webhook
@@ -15,27 +15,27 @@ jobs:
contents: write
steps:
- name: Check out Git repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Check out Git repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}}
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Install node
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Install node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
cache: pnpm
- name: pnpm install
- name: 📦 pnpm install
run: pnpm install --frozen-lockfile
- name: Extract language strings
- name: 🔤 Extract language strings
run: pnpm intl:extract
- name: Create commit
- name: 📝 Create commit
uses: stefanzweifel/git-auto-commit-action@4a55954c782fc1ea30b9056cd3e7a2b40ca8887d # v7.2.0
with:
commit_message: Nightly source-language update
file_pattern: ./src/locale/locales/en/messages.po
- name: Push source lang to Crowdin
uses: crowdin/github-action@52aa776766211d83d975df51f3b9c53c2f8ba35f # v2.16.3
- name: 🚀 Push source lang to Crowdin
uses: crowdin/github-action@c7af9bc98b01694653031fef2a0dc6c7888ce9bc # v2.17.0
with:
upload_sources: true
upload_sources_args: "-b main"
-221
View File
@@ -1,221 +0,0 @@
---
name: PR Comment Trigger
on:
issue_comment:
types: [created]
# Permissions are granted per-job below; anything unlisted defaults to none
permissions: {}
jobs:
handle-comment:
if: github.event.issue.pull_request
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
should-deploy: ${{ steps.check-org.outputs.result }}
steps:
- name: Check if bot is mentioned
id: check-mention
env:
COMMENT: ${{ github.event.comment.body }}
run: |
if [[ "$COMMENT" == *"@github-actions"* ]] || \
[[ "$COMMENT" == *"github-actions[bot]"* ]]; then
bot_mentioned=true
else
bot_mentioned=false
fi
if [[ "$COMMENT" == *"ota"* ]]; then
has_ota=true
else
has_ota=false
fi
if [[ "$bot_mentioned" == "true" ]] && [[ "$has_ota" == "true" ]]; then
echo "mentioned=true" >> $GITHUB_OUTPUT
else
echo "mentioned=false" >> $GITHUB_OUTPUT
fi
- name: Check commenter has write access
if: steps.check-mention.outputs.mentioned == 'true'
id: check-org
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
try {
const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.payload.comment.user.login
});
const hasAccess = ['admin', 'write'].includes(perm.permission);
console.log(`User has ${perm.permission} access`);
return hasAccess;
} catch(error) {
console.log('User has no repository access');
return false;
}
bundle-deploy:
name: Bundle and Deploy EAS Update
runs-on: ubuntu-latest
needs: [handle-comment]
if: needs.handle-comment.outputs.should-deploy == 'true'
permissions:
contents: read
pull-requests: write
steps:
- name: Get PR HEAD SHA
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
id: pr-info
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: process.env.ISSUE_NUMBER,
});
// This workflow runs with repo secrets in scope, so never build
// code from a fork: the commenter authorizes the deploy, but a
// fork controls what code would run during it
const expected = `${context.repo.owner}/${context.repo.repo}`;
const head = pr.data.head.repo?.full_name;
if (head !== expected) {
core.setFailed(`OTA deploys are only allowed for branches in ${expected}, not forks (got ${head})`);
return;
}
console.log(`PR HEAD SHA: ${pr.data.head.sha}`);
console.log(`PR HEAD REF: ${pr.data.head.ref}`);
core.setOutput('head-sha', pr.data.head.sha);
core.setOutput('head-ref', pr.data.head.ref);
- name: 💬 Drop a comment
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }}
number: ${{ github.event.issue.number }}
message: |
An OTA deployment has been requested and is now running for `${{ steps.pr-info.outputs.head-sha }}`.
[Here is some music to listen to while you wait...](https://www.youtube.com/watch?v=VBlFHuCzPgY)
---
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
- name: Check for EXPO_TOKEN
run: >
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
exit 1
fi
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ steps.pr-info.outputs.head-sha }}
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: 🔧 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: 🔤 Compile translations
run: pnpm intl:build 2>&1 | tee i18n.log
- name: Check for i18n compilation errors
run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi
- name: Lint check
run: pnpm lint
- name: Type check
run: pnpm typecheck
- name: 🔨 Setup EAS
uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # 9.0.0
with:
eas-version: '19.0.5'
packager: 'pnpm --allow-build=dtrace-provider'
token: ${{ secrets.EXPO_TOKEN }}
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@4fcb5062d7ce9bc4382d1a352d19ba3ba2c317c1 # v4.0.1
- name: Env
id: env
run: |
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_ENV=testflight" >> .env
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env
echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env
echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env
echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env
echo "$json" > google-services.json
- name: 🏗️ Create Bundle
run: >
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }}
SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }}
pnpm export
- name: 📦 Package Bundle and 🚀 Deploy
run: pnpm use-build-number bash scripts/bundleUpdate.sh
env:
DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }}
CHANNEL_NAME: pull-request-${{ github.event.issue.number }}
RUNTIME_VERSION:
- name: 💬 Drop a comment
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
with:
header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }}
number: ${{ github.event.issue.number }}
message: |
Your requested OTA deployment was successful! You may now apply it by either scanning the QR code or opening the deep link below in your browser:
<img src="https://bsky-qr.vercel.app?channel=pull-request-$ISSUE_NUMBER" width=300 height=300>
`bluesky://intent/apply-ota?channel=pull-request-${{ github.event.issue.number }}`
---
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
- name: 💬 Drop a comment
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
if: failure()
with:
header: pull-request-eas-build-${{ steps.pr-info.outputs.head-sha }}
number: ${{ github.event.issue.number }}
message: |
Your requested OTA deployment was unsuccessful. See action logs for more details.
---
*Generated by [PR labeler](https://github.com/expo/expo/actions/workflows/pr-labeler.yml) 🤖*
+169 -12
View File
@@ -15,9 +15,15 @@ concurrency:
# Permissions are granted per-job below; anything unlisted defaults to none.
# pull-requests: write is needed by sticky-pull-request-comment to post the
# bundle-size and fingerprint diffs
# bundle-size and fingerprint diffs and the PR OTA install link
permissions: {}
# denis release tag in bluesky-social/tango whose linux-amd64 binary the PR OTA
# job downloads. Bump this one line to roll denis.
env:
DENIS_RELEASE_TAG: denis-v0.1.1
NODE_OPTIONS: --max-old-space-size=4096
jobs:
# Populate this from main so every PR can restore the same trusted baseline.
webpack-analyzer-base:
@@ -27,9 +33,9 @@ jobs:
contents: read
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -66,11 +72,11 @@ jobs:
pull-requests: write
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -78,7 +84,7 @@ jobs:
node-version-file: package.json
cache: pnpm
- name: Ensure tracking relevant branches and checkout base
- name: 🌿 Ensure tracking relevant branches and checkout base
env:
HEAD_REF: ${{ github.head_ref }}
BASE_REF: ${{ github.base_ref }}
@@ -86,13 +92,13 @@ jobs:
git checkout $HEAD_REF
git checkout $BASE_REF
- name: Get the base commit
- name: 🔍 Get the base commit
id: base-commit
env:
BASE_REF: ${{ github.base_ref }}
run: echo base-commit=$(git log -n 1 $BASE_REF --pretty=format:'%H') >> "$GITHUB_OUTPUT"
- name: Merge PR commit
- name: 🔀 Merge PR commit
env:
HEAD_REF: ${{ github.head_ref }}
run: |
@@ -116,7 +122,7 @@ jobs:
path: stats.json
key: stats-base-main-${{ steps.base-commit.outputs.base-commit }}
- name: Restore to base commit
- name: Restore to base commit
if: ${{ !steps.get-base-stats.outputs.cache-hit }}
env:
BASE_COMMIT: ${{ steps.base-commit.outputs.base-commit }}
@@ -155,9 +161,13 @@ jobs:
permissions:
contents: read
pull-requests: write
outputs:
# Empty when the native surface is unchanged, 'true' when it changed.
# publish-pr-ota gates on this.
includes-changes: ${{ steps.fingerprint.outputs.includes-changes }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 100
@@ -165,7 +175,7 @@ jobs:
run: git fetch origin main:main --depth 100
if: github.event_name == 'pull_request'
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: 🔧 Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -176,7 +186,7 @@ jobs:
- name: 📷 Check fingerprint and install dependencies
id: fingerprint
timeout-minutes: 5
uses: bluesky-social/github-actions/fingerprint-native@b5556913e4aef3964cfd5936d0add3fc0d809bdb # v0.2.0
uses: bluesky-social/github-actions/fingerprint-native@abc6a46eb4badf243f55bfd7d6cec42722456300 # v0.3.0
with:
profile: pull-request
@@ -205,6 +215,16 @@ jobs:
header: fingerprint-diff
delete: true
# publish-pr-ota is skipped once the fingerprint changes, so any install
# link left over from an earlier fingerprint-clean commit on this PR now
# points at a bundle that no longer matches the PR. Drop it.
- name: 💬 Delete stale OTA install comment
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
if: ${{ steps.fingerprint.outputs.includes-changes }}
with:
header: pull-request-ota
delete: true
- name: 🏷️ Label as fingerprint changed
if: ${{ steps.fingerprint.outputs.includes-changes }}
env:
@@ -220,3 +240,140 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh pr edit "$PR_NUMBER" --remove-label "bot: fingerprint changed" || true
# Automatic per-PR OTA preview, published to the pull-request-<N> channel on
# denis. Replaces the old `@github-actions ota` comment trigger. Gated to
# same-repo PRs (fork guard): a branch can only exist in this repo if someone
# with write access pushed it, so an outside contributor (who can only open a
# PR from a fork) never runs this job with the denis publish role in scope.
# This matches the fork-guard gate the other jobs in this workflow use;
# author_association is deliberately NOT checked (it can't identify a private
# org member and would skip their PRs).
#
# Bot authors are excluded: Dependabot pushes in-repo branches, so it passes
# the fork guard, but GitHub withholds repo secrets from Dependabot-triggered
# runs. EXPO_TOKEN is then empty and the job fails at setup — a red check on
# every dependabot PR. There is no OTA preview worth publishing for a
# dependency bump anyway.
#
# Gated on a clean fingerprint-native run: an OTA can only carry JS, so once
# the native surface changes the published bundle no longer represents the PR
# and installing it on a store/TestFlight client is misleading at best. Those
# PRs need a native build instead. A skipped or failed fingerprint job also
# skips this one - without a verdict we can't say the OTA is representative.
publish-pr-ota:
name: Publish PR OTA to denis
needs: fingerprint-native
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.type != 'Bot' &&
needs.fingerprint-native.outputs.includes-changes != 'true'
concurrency:
group: pr-ota-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
id-token: write
contents: read
outputs:
release-version: ${{ steps.env.outputs.release-version }}
ios-build-number: ${{ steps.build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
android-build-number: ${{ steps.build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: 🛠️ Setup Expo project
uses: ./.github/actions/setup-expo-project
with:
expo-token: ${{ secrets.EXPO_TOKEN }}
- name: 🔤 Compile translations
uses: ./.github/actions/compile-i18n
- name: ✏️ Write environment variables
id: env
uses: ./.github/actions/write-env
with:
env-token: ${{ secrets.ENV_TOKEN }}
sentry-dsn: ${{ secrets.SENTRY_DSN }}
bitdrift-api-key: ${{ secrets.BITDRIFT_API_KEY }}
gcp-project-id: ${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}
google-services-token: ${{ secrets.GOOGLE_SERVICES_TOKEN }}
expo-public-env: testflight
- name: 🏗️ Create Bundle
run: >
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_RELEASE=${{ steps.env.outputs.release-version }}
SENTRY_DIST=${{ steps.env.outputs.bundle-identifier }}
pnpm export
- name: ☁️ Configure AWS credentials (denis, PR-scoped)
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: arn:aws:iam::007404326489:role/denis-ci-publish-pr
aws-region: us-east-2
# Defense-in-depth: the base role is already scoped to pr/*, but narrow
# this session further to just THIS PR's prefix so a bug can't write to
# another PR's objects or the prod tree.
inline-session-policy: |-
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::bsky-denis-ota-prod/pr/${{ github.event.pull_request.number }}/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bsky-denis-ota-prod",
"Condition": {
"StringLike": { "s3:prefix": "pr/${{ github.event.pull_request.number }}/*" }
}
}
]
}
- name: ⬇️ Setup denis CLI
uses: ./.github/actions/setup-denis
with:
release-tag: ${{ env.DENIS_RELEASE_TAG }}
app-id: ${{ vars.SYNC_INTERNAL_APP_ID }}
private-key: ${{ secrets.SYNC_INTERNAL_PK }}
- name: 🔢 Get native build numbers
id: build-info
run: bash scripts/setGitHubOutput.sh
- name: 🚀 Publish OTA to denis (S3)
run: pnpm use-build-number bash scripts/denisPublish.sh
env:
RUNTIME_VERSION: ''
CHANNEL_NAME: pull-request-${{ github.event.pull_request.number }}
# Pin the publish to the same values exposed in the install link.
BSKY_IOS_BUILD_NUMBER: ${{ steps.build-info.outputs.BSKY_IOS_BUILD_NUMBER }}
BSKY_ANDROID_VERSION_CODE: ${{ steps.build-info.outputs.BSKY_ANDROID_VERSION_CODE }}
comment-pr-ota:
name: Comment PR OTA install link
needs: publish-pr-ota
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: 💬 Drop OTA install comment
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: pull-request-ota
message: |
The OTA deployment for this PR was successful! You may now apply it by either scanning the QR code or opening the deep link below in your browser:
<img src="https://bsky-qr.vercel.app?channel=pull-request-${{ github.event.pull_request.number }}&releaseVersion=${{ needs.publish-pr-ota.outputs.release-version }}&iosBuildNumber=${{ needs.publish-pr-ota.outputs.ios-build-number }}&androidBuildNumber=${{ needs.publish-pr-ota.outputs.android-build-number }}" width="300" height="300" alt="QR code for the PR OTA deployment">
`bluesky://intent/apply-ota?channel=pull-request-${{ github.event.pull_request.number }}&releaseVersion=${{ needs.publish-pr-ota.outputs.release-version }}&iosBuildNumber=${{ needs.publish-pr-ota.outputs.ios-build-number }}&androidBuildNumber=${{ needs.publish-pr-ota.outputs.android-build-number }}`
+4 -4
View File
@@ -13,14 +13,14 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'bluesky-social/social-app'
steps:
- name: Checkout public repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Checkout public repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# Don't persist the checkout auth header; the push below authenticates
# with the app token embedded in the remote URL instead
persist-credentials: false
- name: Generate GitHub App Token
- name: 🔑 Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
@@ -33,7 +33,7 @@ jobs:
# .github/workflows/, which GitHub refuses to push without it.
permission-contents: write
permission-workflows: write
- name: Push to internal repo
- name: 🚀 Push to internal repo
env:
TOKEN: ${{ steps.app-token.outputs.token }}
run: |
+8 -8
View File
@@ -14,33 +14,33 @@ jobs:
name: No manual pnpm-lock.yaml edits
runs-on: ubuntu-latest
steps:
- name: Check out PR HEAD
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Check out PR HEAD
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Fetch base branch
- name: ⬇️ Fetch base branch
env:
BASE_REF: ${{ github.base_ref }}
run: git fetch origin $BASE_REF --depth=1
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- name: Install node
- name: 🔧 Install node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json
- name: Reset pnpm-lock.yaml to base
- name: Reset pnpm-lock.yaml to base
env:
BASE_REF: ${{ github.base_ref }}
run: git show "origin/$BASE_REF:pnpm-lock.yaml" > pnpm-lock.yaml
- name: pnpm install
- name: 📦 pnpm install
# Fine to skip scripts since we don't run any code
run: pnpm clean && pnpm install --ignore-scripts --no-frozen-lockfile
- name: Verify pnpm-lock.yaml
- name: 🔍 Verify pnpm-lock.yaml
run: |
git diff --quiet --exit-code || {
echo '::error::`pnpm-lock.yaml` does not match what pnpm would generate given the base `pnpm-lock.yaml` and the head `package.json`.'
+4 -4
View File
@@ -20,13 +20,13 @@ jobs:
name: Audit workflows with zizmor
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: ⬇️ Check out Git repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
- name: 🛡️ Run zizmor
uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
with:
# Annotate the PR directly instead of uploading SARIF to the
# security tab, and fail the check on any finding
+6
View File
@@ -117,6 +117,9 @@ src/locale/locales/**/messages.js
src/locale/locales/**/messages.mjs
src/locale/locales/**/messages.ts
# generated lexicon schemas (pnpm lexicons:generate)
src/lexicons/
# local builds
*.apk
*.aab
@@ -134,3 +137,6 @@ bskyweb/static/media/*.svg
# superpowers plugin plans/specs — local-only workspace
docs/superpowers/
.claude/worktrees
# pnpm
.pnpm-store/
-62
View File
@@ -1,62 +0,0 @@
/**
* Codemod to replace BskyAgent with AtpAgent
*
* Before:
* import {BskyAgent} from '@atproto/api`
* BskyAgent.appLabelers.includes(labeler)
*
* After:
* import {AtpAgent} from '@atproto/api`
* AtpAgent.appLabelers.includes(labeler)
*
* Handles import specifiers, type annotations, static member access
* (BskyAgent.configure), `extends BskyAgent`, and `new BskyAgent()`. Whole
* identifiers only, so names like `OpaqueBskyAgent` are left untouched.
*
* Usage: jscodeshift -t .jscodeshift/repo/bsky-agent.js <file-path>
* Example: jscodeshift -t .jscodeshift/repo/bsky-agent.js src/lib/moderation.ts
*/
/* eslint-disable */
export const parser = 'tsx'
export default function transformer(file, api) {
const j = api.jscodeshift
const root = j(file.source)
// Replace every standalone `BskyAgent` identifier with `AtpAgent`. This
// covers imports, type references, member expressions, `extends`, and `new`.
root
.find(j.Identifier, {name: 'BskyAgent'})
.replaceWith(() => j.identifier('AtpAgent'))
// Renaming can leave a duplicate `AtpAgent` specifier on the @atproto/api
// import if the file already imported it. Dedupe by imported name, keeping
// the type-only modifier only if every duplicate was type-only.
root
.find(j.ImportDeclaration, {source: {value: '@atproto/api'}})
.forEach(path => {
const seen = new Map()
for (const spec of path.value.specifiers) {
if (spec.type !== 'ImportSpecifier') {
seen.set(Symbol(), spec)
continue
}
const name = spec.imported.name
const existing = seen.get(name)
if (!existing) {
seen.set(name, spec)
} else if (
existing.importKind === 'type' &&
spec.importKind !== 'type'
) {
// Prefer the value (non-type) import if either usage needs it.
seen.set(name, spec)
}
}
path.value.specifiers = Array.from(seen.values())
})
return root.toSource()
}
+4 -6
View File
@@ -60,8 +60,10 @@
"bskyweb/**",
"bskyembed/**",
"bskyogcard/**",
"lint-rules/**",
"src/locale/locales/_build/**",
"src/locale/locales/**/*.js",
"src/lexicons/**",
"*.e2e.ts",
"*.e2e.tsx",
"eslint.config.mjs",
@@ -238,7 +240,6 @@
}
}
],
"bsky-internal/use-exact-imports": "error",
"bsky-internal/use-prefixed-imports": "error",
"bsky-internal/lingui-msg-rule": "error",
"react/display-name": "error",
@@ -259,7 +260,6 @@
"react/no-unsafe": "off",
"react/react-in-jsx-scope": "off",
"react/hook-use-state": "warn",
"react-native/no-inline-styles": "off",
"react-native-a11y/has-accessibility-hint": "error",
"react-native-a11y/has-accessibility-props": "error",
"react-native-a11y/has-valid-accessibility-actions": "error",
@@ -274,7 +274,7 @@
"react-native-a11y/has-valid-accessibility-ignores-invert-colors": "error",
"react-native-a11y/has-valid-accessibility-live-region": "error",
"react-native-a11y/has-valid-important-for-accessibility": "error",
"react-compiler/react-compiler": "warn",
"react/react-compiler": "warn",
"simple-import-sort/imports": [
"error",
{
@@ -374,9 +374,7 @@
},
"jsPlugins": [
"eslint-plugin-bsky-internal",
"eslint-plugin-react-native",
"eslint-plugin-react-native-a11y",
"eslint-plugin-react-compiler",
"eslint-plugin-simple-import-sort"
],
"env": {
@@ -404,4 +402,4 @@
}
}
]
}
}
+1
View File
@@ -18,6 +18,7 @@
android
ios
src/locale/locales
src/lexicons
lib/react-compiler-runtime
bskyweb/static
coverage
+208
View File
@@ -0,0 +1,208 @@
# Asset licensing
The [MIT license](./LICENSE) in this repository covers our source code. It does not cover
every file in the tree.
Some of the images, icons, fonts, and brand assets here are licensed to Bluesky Social PBC
by third parties, or are our trademarks, or are third-party trademarks. We cannot pass those
rights on to you. This document identifies them.
We should have written this down sooner. If you have already forked this repository and
shipped one of the assets listed below, we are not treating that as bad faith on your part —
the repository did not tell you, and that is our fault. Please work through the
[If you are forking](#if-you-are-forking) checklist when you can.
Assets are scoped by directory wherever possible, so that adding a file to a carved-out
directory does not require an edit here. Individual paths are listed only where an asset does
not sit in a dedicated directory.
**The rule:** if a file is listed in this document, the MIT license does not grant you rights
to it. Everything in [Section 6](#6-third-party-assets-you-may-redistribute) is redistributable
on its own terms, which travel with the files.
---
## 1. Commissioned artwork — licensed to Bluesky only
**`assets/illustrations/`**
The landing-screen illustration, in light and dark variants, used by
`src/view/com/auth/SplashScreen.tsx` (`illustration-mobile.png` and
`illustration-mobile-dark.png`).
This artwork was commissioned by Bluesky Social PBC from illustrator Owen D. Pomery, through
his agent Brilliant Artists Ltd. Copyright remains with the artist. Our license is limited to
Bluesky's own products and channels, it is exclusive to us, and it does not permit us to
sublicense the artwork or to distribute modified versions of it.
**If you are forking this repository, replace these files.** They are not available for
third-party licensing while our license runs, so please do not contact the artist or his agent
to request permission — the answer is constrained by our agreement, not by their willingness.
See [`assets/illustrations/README.md`](./assets/illustrations/README.md).
## 2. Licensed icon system — not ours to pass on
**`assets/icons/` (top level), and the icon glyphs in `bskyembed/assets/`**
The user-interface glyphs at the top level of `assets/icons/`, plus copies of several in
`bskyembed/assets/`, come from the Central icon system by Iconists (David & Storm GbR). Bluesky
Social PBC licenses these icons for use in our own products. **That license is for our own use.
It does not include the right to pass any rights to the icons on to you.**
This is not us telling you that you cannot use these icons. It is us telling you that any right
you have to use them has to come from Iconists rather than from us, and that you should not
assume our MIT license gave you one. Licenses are available directly from
[iconists.co](https://iconists.co), and there are good openly licensed alternatives if you would
rather not buy one.
This section covers every file at the top level of `assets/icons/` **except** those named
elsewhere in this document — specifically `logomark.svg`, `newskie.svg`, `verifiedCheck.svg`,
`verifierCheck.svg`, `starterPack.svg`, `starterPack_stroke2_corner0_rounded.svg`, `custom_logo_japan.svg`,
`apple_logo.svg`, and `android_logo.svg`. The `assets/icons/flags/` and `assets/icons/community/`
subdirectories are covered by [Section 6](#6-third-party-assets-you-may-redistribute) and
[Section 5](#5-third-party-trademarks) respectively.
See [`assets/icons/README.md`](./assets/icons/README.md).
## 3. Bluesky trademarks and brand assets
Our name, logo, butterfly mark, logotype, and app icons are trademarks of Bluesky Social PBC.
They are not licensed to you under the MIT license or by this document. Use of them is governed
by our [Trademark Policy](https://bsky.social/about/support/trademarks) and [Brand Guidelines](https://bsky.social/about/support/branding).
You may refer to Bluesky by name to describe interoperability or origin — for example, "a client
for Bluesky," or "based on the Bluesky app." You may not use our marks as the identity of your
own product or service, or in any way likely to suggest that Bluesky publishes, endorses, or
supports it.
- `assets/app-icons/` — all iOS and Android app icon variants, including the `.icon` bundles
- `assets/favicon.png`
- `assets/logo.png`
- `assets/default-avatar.png`
- `assets/icon-android-foreground.png`
- `assets/icon-android-monochrome.png`
- `assets/icon-android-notification.png`
- `assets/splash/splash.png`
- `assets/splash/splash-dark.png`
- `assets/splash/android-splash-logo-white.png`
- `assets/icons/logomark.svg`
- `assets/icons/newskie.svg`
- `assets/icons/verifiedCheck.svg`
- `assets/icons/verifierCheck.svg`
- `assets/icons/starterPack.svg`, `assets/icons/starterPack_stroke2_corner0_rounded.svg`
- `bskyembed/assets/logo.svg`
- `bskyembed/assets/logo_full_name.svg`
- `bskyweb/static/favicon.png`, `favicon-16x16.png`, `favicon-32x32.png`
- `bskyweb/static/apple-touch-icon.png`
- `bskyweb/static/safari-pinned-tab.svg`
- `bskyweb/static/social-card-default.png`, `bskyweb/static/social-card-default-gradient.png`
- `bskyweb/embedr-static/favicon.png`, `favicon-16x16.png`, `favicon-32x32.png`
- `modules/BlueskyClip/Images.xcassets/AppIcon.appiconset/`
- Inline vector path data in `src/view/icons/Logo.tsx` and `src/view/icons/Logotype.tsx`
We are not asking anyone to remove these files from the repository. They are here because the
app needs them to build. Replacing them is a forker's responsibility, not ours.
## 4. Community and contest artwork — credited, but not ours to license
These are third-party artworks that appear in the app with attribution. We hold no license that
lets us pass rights to them on to you.
- `assets/kawaii.png`, `assets/kawaii_smol.png` — logo by
[@sawaratsuki.bsky.social](https://bsky.app/profile/sawaratsuki.bsky.social), shown as an
opt-in variant and credited in `src/view/shell/Drawer.tsx` and
`src/view/shell/desktop/RightNav.tsx`
- `assets/icons/custom_logo_japan.svg` — the winning entry from the Bluesky Japan logo contest,
created by a contest entrant
Replace these if you fork. If you want to use them, that is a conversation with the artist,
not with us.
## 5. Third-party trademarks
These marks belong to other companies. We include them to identify their services in our UI —
sign-in buttons, store badges, and links to third-party applications. We are neither granting
nor withholding permission, because it is not ours to give. Your use of them rests on your own
nominative-use basis or on permission from the mark owner.
- `assets/icons/apple_logo.svg` — Apple Inc.
- `assets/icons/android_logo.svg` — Google LLC
- `assets/icons/community/` — Leaflet, Offprint, pckt (`pckt.svg`, `pckt-full.svg`),
Standard.site, and Germ Network (`germ_logo.webp`)
Apple's and Google's marks in particular carry their own brand guidelines governing size,
spacing, and permitted contexts. If you ship a sign-in button or a store badge, follow theirs,
not ours.
## 6. Third-party assets you may redistribute
These are licensed on terms that permit redistribution. Nothing in this document restricts them.
Their license text travels with the files, and you must keep it there.
| Asset | Path | License | Notice |
|---|---|---|---|
| Inter typeface | `assets/fonts/inter/` | SIL Open Font License 1.1 | [`OFL.txt`](./assets/fonts/inter/OFL.txt) |
| Inter typeface (OG card service) | `bskyogcard/src/assets/fonts/` | SIL Open Font License 1.1 | [`README.md`](./bskyogcard/src/assets/fonts/README.md) |
| country-flag-icons | `assets/icons/flags/` | MIT, © @catamphetamine | [`README.md`](./assets/icons/flags/README.md) |
| Material Icons | `bskyweb/static/media/MaterialIcons.*.ttf` | Apache License 2.0 | [`NOTICE.md`](./NOTICE.md) |
Build output under `bskyweb/static/media/` also contains compiled Inter files. They are the same
OFL-licensed typeface, emitted by the web build.
**One thing to watch on Inter:** OFL 1.1 includes a Reserved Font Name provision. If you modify
or subset the font, the result cannot be distributed under the name "Inter."
See [`NOTICE.md`](./NOTICE.md) for the consolidated third-party notices.
## 7. Product imagery — treat as not licensed to you
**`assets/images/`**
Product illustration and announcement imagery — onboarding value-prop art, chat backgrounds,
feature announcement graphics, and similar.
**Treat everything in this directory as outside the MIT license and not licensed for your use.**
Some of it is commissioned work. Rather than have you guess file by file which is which, we are
drawing the line at the directory.
If you are forking, replace these or ship without them.
See [`assets/images/README.md`](./assets/images/README.md).
---
## If you are forking
You have our blessing to fork this application. To do it cleanly:
1. **Replace `assets/illustrations/`.** Commissioned artwork, licensed to Bluesky only. See
[Section 1](#1-commissioned-artwork--licensed-to-bluesky-only).
2. **Source your own UI icons.** The glyph set in `assets/icons/` is licensed to us for our own
use. See [Section 2](#2-licensed-icon-system--not-ours-to-pass-on).
3. **Replace `assets/images/`.** Treat as not licensed to you. See
[Section 7](#7-product-imagery--treat-as-not-licensed-to-you).
4. **Replace the Bluesky marks** listed in [Section 3](#3-bluesky-trademarks-and-brand-assets) —
app icons, favicons, logo files, and the inline logo paths in `src/view/icons/`.
5. **Replace or remove the community artwork** in
[Section 4](#4-community-and-contest-artwork--credited-but-not-ours-to-license).
6. **Check your own position on the third-party marks** in
[Section 5](#5-third-party-trademarks).
7. **Keep the license notices** for the assets in
[Section 6](#6-third-party-assets-you-may-redistribute).
8. **Change your branding, support links, and analytics** as described in the
[Forking guidelines](./README.md#forking-guidelines).
This list is about licensing. The [Forking guidelines](./README.md#forking-guidelines) in the
README cover the rest of what makes a fork clearly distinguishable from Bluesky, which matters
both for your users and for app store review.
## Questions
If something in this repository looks like it should be on this list and is not, or if you are
unsure whether an asset is covered, open an issue or email us and we will sort it out. We would
much rather answer the question than have someone guess.
---
*Last reviewed: August 2026. This document describes the licensing position of assets in this
repository. It is not a grant of rights, and it does not modify the [MIT license](./LICENSE) as
it applies to source code.*
+9 -1
View File
@@ -224,6 +224,7 @@ return conditional styles inline in a style array: `web({cursor: 'pointer'})`,
- Prefer prop destructuring via parameters over a const within the component.
- Prefer inline types over `Props` types or interfaces.
- Set reasonable defaults for optional props.
- Prefer the implicit global `React` for types over `type` imports.
```tsx
import {Fragment} from 'react'
@@ -232,7 +233,13 @@ import {Trans} from '@lingui/react/macro'
import {Text} from '#/components/Typography'
function MyComponent({items = []}: {items?: string[]}) {
function MyComponent({
items = [],
children,
}: {
items?: string[]
children: React.ReactNode
}) {
return (
<>
<View>
@@ -247,6 +254,7 @@ function MyComponent({items = []}: {items?: string[]}) {
<Text>{item}</Text>
</Fragment>
))}
{children}
</View>
</>
)
+1 -1
View File
@@ -9,7 +9,7 @@ WORKDIR /app
COPY ./bskyogcard/package.json ./
COPY ./bskyogcard/pnpm-lock.yaml ./
COPY ./bskyogcard/pnpm-workspace.yaml ./
RUN npm install --global pnpm@11.13.1
RUN npm install --global pnpm@11.21.0
RUN pnpm install --frozen-lockfile
COPY ./bskyogcard ./
+1 -1
View File
@@ -33,7 +33,7 @@ RUN mkdir --parents $NVM_DIR && \
RUN \. "$NVM_DIR/nvm.sh" && \
nvm install $NODE_VERSION && \
nvm use $NODE_VERSION && \
npm install --global pnpm@11.13.1 && \
npm install --global pnpm@11.21.0 && \
pnpm install --frozen-lockfile && \
cd bskyembed && pnpm install --frozen-lockfile && cd .. && \
pnpm intl:build && \
+62
View File
@@ -0,0 +1,62 @@
# Third-party notices
This file collects the attribution and license notices that third-party components in this
repository require us to carry. It is separate from [`ASSETS.md`](./ASSETS.md), which describes
which assets our [MIT license](./LICENSE) does and does not cover.
If you distribute this software or a fork of it, these notices need to travel with it.
---
## Material Icons
**Path:** `bskyweb/static/media/MaterialIcons.*.ttf`
**License:** Apache License, Version 2.0 — full text at [`licenses/APACHE-2.0.txt`](./licenses/APACHE-2.0.txt)
```
Copyright 2018 Google, Inc. All Rights Reserved.
```
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
*This font is emitted into the web build by `@expo/vector-icons`, which the Expo toolchain pulls
in transitively. It is a distributed artifact rather than a source asset, which is why the notice
lives here rather than beside the file.*
## Inter
**Paths:** `assets/fonts/inter/`, `bskyogcard/src/assets/fonts/`, and compiled copies under
`bskyweb/static/media/`
**License:** SIL Open Font License, Version 1.1 — full text at
[`assets/fonts/inter/OFL.txt`](./assets/fonts/inter/OFL.txt)
```
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
```
Inter is licensed under the SIL Open Font License, Version 1.1. The license includes a Reserved
Font Name provision: a modified or subsetted version of the font may not be distributed under the
name "Inter."
## country-flag-icons
**Path:** `assets/icons/flags/`
**License:** MIT — see [`assets/icons/flags/README.md`](./assets/icons/flags/README.md)
```
Copyright (c) 2020 @catamphetamine <purecatamphetamine@gmail.com>
```
---
For the licensing position of assets that are **not** covered by our MIT license — commissioned
artwork, the licensed icon system, Bluesky trademarks, and third-party marks — see
[`ASSETS.md`](./ASSETS.md).
+14 -1
View File
@@ -57,6 +57,15 @@ Please be sure to:
- Change all branding in the repository and UI to clearly differentiate from Bluesky.
- Change any support links (feedback, email, terms of service, etc) to your own systems.
- Replace any analytics or error-collection systems with your own so we don't get super confused.
- Replace the landing-screen illustration in `assets/illustrations/`. It is commissioned artwork licensed to Bluesky alone, and our MIT license does not cover it.
- Source your own UI icons. The glyph set in `assets/icons/` is licensed to us by a third party for our own use, and that license does not extend to you.
- Replace the Bluesky logo, app icons, and other brand assets. Our trademarks are not licensed with the code.
Please read [./ASSETS.md](./ASSETS.md) before you ship. Not every file in this repository is
covered by our MIT license — some of the artwork, icons, fonts, and brand assets are licensed to
us by third parties or are trademarks, and `ASSETS.md` says which ones and what to do about them.
That file is new. Its absence is why some forks have shipped assets they did not have rights to,
and that was our omission rather than theirs.
## Security disclosures
@@ -68,7 +77,11 @@ Bluesky is an open social network built on the AT Protocol, a flexible technolog
## License (MIT)
See [./LICENSE](./LICENSE) for the full license.
See [./LICENSE](./LICENSE) for the full license, which covers the source code in this repository.
It does not cover every file. Certain images, icons, fonts, and brand assets are licensed to us
by third parties, or are trademarks, and are carved out — see [./ASSETS.md](./ASSETS.md). Required
third-party attribution notices are collected in [./NOTICE.md](./NOTICE.md).
Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge).
+1 -1
View File
@@ -1,4 +1,4 @@
import {RichText} from '@atproto/api'
import {RichText} from '@bsky/sdk/richtext'
import {i18n} from '@lingui/core'
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
+16
View File
@@ -0,0 +1,16 @@
The app icons in this directory are trademarks of Bluesky Social
PBC — the butterfly mark and its variants, as shipped to the iOS App Store and Google Play.
**These are not covered by the [MIT license](../../LICENSE) that applies to the rest of this
repository.** Use of Bluesky's marks is governed by our Trademark Policy and Brand Guidelines,
not by the license on our source code.
You may say that your app is a client for Bluesky, or that it is based on the Bluesky app. You
may not use our marks as the identity of your own product, or in any way likely to suggest that
Bluesky publishes, endorses, or supports it.
If you are forking this repository, replace these files with your own icons. Shipping an app that
looks like Bluesky is also a problem under the app stores' own rules on copycat apps, quite apart
from trademark.
See [`ASSETS.md`](../../ASSETS.md) for the full asset licensing picture.
+92
View File
@@ -0,0 +1,92 @@
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org/
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+15
View File
@@ -0,0 +1,15 @@
[Inter](https://rsms.me/inter/) is a typeface by Rasmus
Andersson, licensed under the SIL Open Font License, Version 1.1.
The full license text is in [`OFL.txt`](./OFL.txt) and must travel with these files.
```
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
```
**You may redistribute these files.** Nothing in [`ASSETS.md`](../../../ASSETS.md) restricts them
— they are here because the OFL requires the license text to accompany the font, not because they
are carved out of our [MIT license](../../../LICENSE).
**One thing to watch:** OFL 1.1 includes a Reserved Font Name provision. If you modify or subset
the font, you may not distribute the result under the name "Inter."
+23
View File
@@ -0,0 +1,23 @@
The user-interface glyphs in this directory are from the
[Central icon system](https://iconists.co/central) by Iconists (David & Storm GbR). Bluesky
Social PBC licenses them for use in our own products. **That license is for our own use, and it
does not extend to you.**
**These icons are not covered by the [MIT license](../../LICENSE) that applies to the rest of
this repository.** If you want to use them, buy a license from
[iconists.co](https://iconists.co). There are also good openly licensed icon sets if you would
rather not.
This notice covers the SVG files at the top level of this directory. It does not cover:
- `flags/` — country-flag-icons, MIT licensed, see [`flags/README.md`](./flags/README.md)
- `community/` — third-party service marks, see [`community/README.md`](./community/README.md)
- `logomark.svg`, `newskie.svg`, `verifiedCheck.svg`, `verifierCheck.svg`, `starterPack.svg`,
`starterPack_stroke2_corner0_rounded.svg` — Bluesky trademarks and brand assets
- `custom_logo_japan.svg` — artwork by a Bluesky Japan logo contest entrant
- `apple_logo.svg`, `android_logo.svg` — Apple and Google trademarks
Each of those is described in [`ASSETS.md`](../../ASSETS.md).
Adding an icon here? If it came from Central, this notice already covers it. If it came from
anywhere else, add it to `ASSETS.md` so the notice does not go stale.
+14
View File
@@ -0,0 +1,14 @@
The icons in this directory are the marks of third-party
services that appear in the Bluesky app — [Leaflet](https://leaflet.pub),
[Offprint](https://offprint.net), [pckt](https://pckt.blog), [Standard.site](https://standard.site), and
[Germ Network](https://germnetwork.com).
**These marks belong to their respective owners.** They are not Bluesky trademarks, they are not
covered by the [MIT license](../../../LICENSE) that applies to the rest of this repository, and we
are not in a position to grant or withhold permission to use them. We include them to identify
those services in our UI.
If you fork this repository and keep these icons, your use of them rests on your own
nominative-use basis or on permission from the mark owner.
See [`ASSETS.md`](../../../ASSETS.md) for the full asset licensing picture.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4Zm1 16V5h14v14H5Zm10.725-5.2c0 .566-.283.872-.802.872-.538 0-.848-.318-.848-.872v-3.635c0-.512.314-.826.82-.826h2.496c.35 0 .609.272.609.64 0 .369-.26.629-.609.629h-1.666v.973h1.47c.365 0 .608.248.608.613 0 .36-.247.613-.608.613h-1.47v.993Zm-3.367.872c.526 0 .813-.31.813-.872v-3.627c0-.558-.295-.873-.825-.873s-.825.31-.825.873V13.8c0 .558.302.872.837.872Zm-3.879.078C6.92 14.75 6 13.827 6 12.287v-.617c0-1.47.955-2.42 2.472-2.42.589 0 1.139.147 1.548.388.404.236.664.562.664.915 0 .373-.271.636-.656.636a.8.8 0 0 1-.41-.108 2 2 0 0 1-.271-.177c-.208-.148-.421-.3-.746-.3-.644 0-.95.38-.95 1.155v.52c0 .768.306 1.168.903 1.168.436 0 .735-.248.735-.61v-.061h-.146c-.412 0-.632-.194-.632-.551 0-.353.216-.535.632-.535h.806c.617 0 .884.256.884.834v.166c0 1.253-.92 2.06-2.354 2.06Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 992 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6 12.287c0 1.54.92 2.463 2.48 2.463 1.434 0 2.353-.807 2.353-2.06v-.166c0-.578-.267-.834-.884-.834h-.806c-.416 0-.632.182-.632.535 0 .357.22.55.632.55h.146v.063c0 .36-.299.609-.735.609-.597 0-.904-.4-.904-1.168v-.52c0-.775.307-1.155.951-1.155.464 0 .7.31 1.018.477a.8.8 0 0 0 .409.108c.385 0 .656-.263.656-.636 0-.353-.26-.679-.664-.915-.409-.24-.96-.388-1.548-.388C6.955 9.25 6 10.2 6 11.67v.617Zm6.358 2.385c.526 0 .813-.31.813-.872v-3.627c0-.558-.295-.873-.825-.873s-.825.31-.825.873V13.8c0 .558.302.872.837.872Zm2.565 0c.519 0 .802-.306.802-.872v-.993h1.47c.361 0 .608-.252.608-.613 0-.365-.243-.613-.608-.613h-1.47v-.973h1.666c.35 0 .609-.26.609-.629 0-.368-.26-.64-.609-.64h-2.495c-.507 0-.821.314-.821.826V13.8c0 .554.31.872.848.872ZM19 7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7Zm2 10a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v10Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 514 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M19 15.355c-1.112.696-2.385 1.1-4 1.1-2.81 0-4.796-1.756-6.312-3.39a1 1 0 0 0-1.273-.16L5 14.71V17a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1.646ZM16 9a1 1 0 1 0-2 0 1 1 0 0 0 2 0Zm3-2a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v5.213l1.246-.932.044-.031a3 3 0 0 1 3.862.455c1.468 1.581 2.942 2.75 4.848 2.75 1.704 0 2.854-.558 4-1.621V7Zm-1 2a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm3 8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v10Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 563 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M2.293 2.293a1 1 0 0 1 1.414 0l18 18a1 1 0 0 1-1.414 1.414L17.586 19H6.414l1.293 1.293a1 1 0 0 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 1 1 1.414 1.414L6.414 17h9.172L5.909 7.323A2 2 0 0 0 5 9v2a1 1 0 0 1-2 0V9c0-1.255.579-2.372 1.482-3.104L2.293 3.707a1 1 0 0 1 0-1.414M20 12a1 1 0 0 1 1 1v2a1 1 0 0 1-2 0v-2a1 1 0 0 1 1-1m-3.707-9.707a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H11a1 1 0 0 1 0-2h6.586l-1.293-1.293a1 1 0 0 1 0-1.414"/></svg>

After

Width:  |  Height:  |  Size: 575 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M10.775 2.74a3 3 0 0 1 3.524 2.158l.57 2.13a7.5 7.5 0 1 1-6.12 10.744l-2.228.598-.15.036a3 3 0 0 1-3.48-2.009l-.044-.148L.777 8.52A3 3 0 0 1 2.75 4.89l.148-.044 7.728-2.07.149-.037Zm4.624 6.262.97 3.624.037.149a3 3 0 0 1-2.009 3.48l-.148.044-3.517.942A5.5 5.5 0 1 0 15.5 9l-.101.002Zm-3.031-3.586a1 1 0 0 0-1.225-.707l-7.727 2.07a1 1 0 0 0-.707 1.225l2.07 7.727a1 1 0 0 0 1.225.707l2.123-.57a7.504 7.504 0 0 1 4.788-8.412l-.547-2.04ZM13.435 9.4a5.5 5.5 0 0 0-3.37 5.948l3.666-.981a1 1 0 0 0 .707-1.225L13.435 9.4Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 656 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 153 133"><path fill="url(#a)" fill-rule="evenodd" d="m60.196 105.445-18.1 4.85c-11.73 3.143-23.788-3.819-26.931-15.55L1.19 42.597c-3.143-11.731 3.819-23.79 15.55-26.932L68.889 1.69C80.62-1.452 92.68 5.51 95.821 17.241l4.667 17.416a50 50 0 0 1 3.522-.125c27.053 0 48.984 21.931 48.984 48.984S131.063 132.5 104.01 132.5c-19.17 0-35.769-11.012-43.814-27.055ZM19.457 25.804 71.606 11.83c6.131-1.643 12.434 1.996 14.076 8.127l4.44 16.571c-20.289 5.987-35.096 24.758-35.096 46.988 0 4.157.517 8.193 1.492 12.047l-17.138 4.593c-6.131 1.642-12.434-1.996-14.077-8.128L11.33 39.88c-1.643-6.131 1.996-12.434 8.127-14.077Zm83.812 19.232q.369-.007.741-.007c21.256 0 38.487 17.231 38.487 38.487s-17.231 38.488-38.487 38.488c-14.29 0-26.76-7.788-33.4-19.35l23.635-6.333c11.731-3.143 18.693-15.2 15.55-26.932l-6.526-24.353Zm-10.428 1.638 6.815 25.432c1.642 6.131-1.996 12.434-8.128 14.076l-24.867 6.664a38.6 38.6 0 0 1-1.139-9.33c0-17.372 11.51-32.056 27.32-36.842Z" clip-rule="evenodd"/><defs><linearGradient id="a" x1="76.715" x2="76.715" y1=".937" y2="132.5" gradientUnits="userSpaceOnUse"><stop stop-color="#0a7aff"/><stop offset="1" stop-color="#59b9ff"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

+19
View File
@@ -0,0 +1,19 @@
The landing screen illustration in this directory was commissioned by Bluesky Social PBC from
illustrator [Owen D. Pomery](https://owenpomery.com/work), through his agent
[Brilliant Artists Ltd](https://brilliantartists.co.uk/). Copyright in the artwork remains with
the artist.
**This artwork is not covered by the [MIT license](../../LICENSE) that applies to the rest of
this repository.** Our license is limited to Bluesky's own products and channels, it is exclusive
to us, and it does not permit us to sublicense the artwork or to distribute modified versions
of it.
If you are forking this repository, replace these files. Because our license is exclusive, the
artwork is not available for separate third-party licensing while that license runs — please do
not approach the artist or his agent for permission.
Adding a file here? It is carved out of the MIT license by directory, so a new file inherits
that treatment automatically. If the file is not commissioned artwork covered by this notice,
put it somewhere else.
See [`ASSETS.md`](../../ASSETS.md) for the full asset licensing picture.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+14
View File
@@ -0,0 +1,14 @@
The product imagery in this directory — onboarding illustrations,
chat backgrounds, feature announcement graphics, and similar — is **not covered by the
[MIT license](../../LICENSE)** that applies to the rest of this repository.
Some of it is commissioned artwork that we are not able to sublicense. Rather than have you work
out file by file which is which, treat the whole directory as not licensed for your use.
If you are forking this repository, replace these files or ship without them.
**Internal note:** this directory is carved out of the MIT license as a whole. If you are adding
an asset that we do want forks to be able to reuse, put it somewhere else rather than adding an
exception here — the carve-out only stays maintainable if the directory boundary means something.
See [`ASSETS.md`](../../ASSETS.md) for the full asset licensing picture.
+19 -11
View File
@@ -1,6 +1,8 @@
/**
* @param {import("@babel/core").ConfigAPI} api
* @returns {import("@babel/core").InputOptions}
*/
module.exports = function (api) {
api.cache(true)
const isTestEnv = process.env.NODE_ENV === 'test'
return {
presets: [
[
@@ -10,7 +12,7 @@ module.exports = function (api) {
native: {
// Disable ESM -> CJS compilation because Metro takes care of it.
// However, we need it in Jest tests since those run without Metro.
disableImportExportTransform: !isTestEnv,
disableImportExportTransform: !api.env('test'),
},
},
],
@@ -29,15 +31,21 @@ module.exports = function (api) {
},
},
],
// cannot use `env` field because it will put them after
// the `react-native-worklets/plugin` plugin
...(api.env('test')
? [
'@babel/plugin-transform-class-static-block',
// Compile `import()` to require so jest (which runs without
// `--experimental-vm-modules`) can execute lazily-loaded modules
// like `@ipld/dag-cbor` via its moduleNameMapper.
'@babel/plugin-transform-dynamic-import',
]
: []),
...(api.env('production') ? ['transform-remove-console'] : []),
'react-native-worklets/plugin', // NOTE: this plugin MUST be last
],
env: {
production: {
plugins: ['transform-remove-console'],
},
test: {
plugins: ['@babel/plugin-transform-class-static-block'],
},
},
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
"devEngines": {
"packageManager": {
"name": "pnpm",
"version": "11.13.1",
"version": "11.21.0",
"onFail": "warn"
}
},
+38 -38
View File
@@ -7,52 +7,52 @@ importers:
configDependencies: {}
packageManagerDependencies:
'@pnpm/exe':
specifier: 11.13.1
version: 11.13.1
specifier: 11.21.0
version: 11.21.0
pnpm:
specifier: 11.13.1
version: 11.13.1
specifier: 11.21.0
version: 11.21.0
packages:
'@pnpm/exe@11.13.1':
resolution: {integrity: sha512-P4euEK6lOFnd5oTHEc5M/HhvyF4XUhTnVsklEcM6rmY0QJxPD6xbT+u1+gskEIBp4nSRorz20IJQtAU1Nerggg==}
'@pnpm/exe@11.21.0':
resolution: {integrity: sha512-zawQxIewH1od72HhlmXWq3No6XyuWn+nMvQ9BjWWGNBVskmS+RDlTu7ey2ruL650PbbyuCATYSal1DaXFKBdcw==}
hasBin: true
'@pnpm/linux-arm64@11.13.1':
resolution: {integrity: sha512-wB8zloqrYrudPyuA5qbuTCnJGe4eETPwqOjoPjoyyyvA4zFI5XfLpxgqOOcaY5UJBoqzckcGpRVDwhSRfsQ/6A==}
'@pnpm/linux-arm64@11.21.0':
resolution: {integrity: sha512-gOSfQKr6kZjEwHyoRwMt9qrqQ9sqbZmUm2hbgJJG8bp0ZR9YkQ4BZV2k4qlQA2jtsmHV1u1MwiaLcuK7DauvBg==}
cpu: [arm64]
os: [linux]
'@pnpm/linux-x64@11.13.1':
resolution: {integrity: sha512-A+wnEvzfWEvanXiwww3tnOPmtjPSrrf5tOP6vk8+K0BRFEe/Df0oPytm2nWgGcn5iwPnqtr1Btkof913McnSPA==}
'@pnpm/linux-x64@11.21.0':
resolution: {integrity: sha512-X+kBR8yscKyhhElO+WLrb6sFbl/3Ow70B+6fqZUYI8T8wtmlCw5GtcPXVBJPDJcLN5joe227h7lyCCZo4tdKdw==}
cpu: [x64]
os: [linux]
'@pnpm/linuxstatic-arm64@11.13.1':
resolution: {integrity: sha512-k4t65VeqRX4COMFe45TF58CVmCpmAsKZShaR1HobmUeleo98mWTctggKolrA2MHcVUeSS+12yB5Urb3uDazhmw==}
'@pnpm/linuxstatic-arm64@11.21.0':
resolution: {integrity: sha512-IUJfAclH0b3QxaHuQuVxXQIzEkDtTm0C+G3tgG0ET5tDRGc7wH7eU0GEM75ojHOwzqv7s0y00xPVVCIsxUM4Nw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@pnpm/linuxstatic-x64@11.13.1':
resolution: {integrity: sha512-A65GqPzwCl0bAMk3kRWfbjSRBm5RRaqR2oMxV/9AYZrwO0X9yEfngbLBISCPHjt6/Qe4nH7DFemyhy6yODYwEw==}
'@pnpm/linuxstatic-x64@11.21.0':
resolution: {integrity: sha512-6Y2u+AfOUuTqWgTCpFhySL8HAcONDucCFixKle9tWoW7bm8RF0+fwQBRWNWGdx+Toau07wZ1LNZPqN8gpgeBDQ==}
cpu: [x64]
os: [linux]
libc: [musl]
'@pnpm/macos-arm64@11.13.1':
resolution: {integrity: sha512-MJvOtyGOWSfBoqdVEfAH8ljmHs13mt82k/UxN4f+q7koDxJRR2n4Nie6Og6RwbnbaubCz0Fh2bTeL1+MxDSFpA==}
'@pnpm/macos-arm64@11.21.0':
resolution: {integrity: sha512-sLMGvVJXWdhFAouY2icjeZ2VCFmyPPZvvtHkfj1oeCWGppsbWcP5cExSw9yU1Uw7ALwrV0I2lRZv33yWYfDtcQ==}
cpu: [arm64]
os: [darwin]
'@pnpm/win-arm64@11.13.1':
resolution: {integrity: sha512-kl/g1cCKOJPe4HntspyrAJW0LRco0UHnVfxHSspezo4Zj4AanJAZ8WzLqfa6/w3lBSKHTEN4x0pb3m4J7B7Vpw==}
'@pnpm/win-arm64@11.21.0':
resolution: {integrity: sha512-79Nc+YI5B2ddH5MQD2YITL/PKnmXdcQKwmwx0HaD4QnsCco8DFaTho242e5sd9QfXKxYRvB9AOnuqIV4VjhAAw==}
cpu: [arm64]
os: [win32]
'@pnpm/win-x64@11.13.1':
resolution: {integrity: sha512-Bcb14NeBlbHS2Gq1qr8VnCiAz5eC1lYzXOls7zH0bnV0Taaj4/xyfm0HVO4dn9R2TQtVtz1qnBZHHL9PDFumqQ==}
'@pnpm/win-x64@11.21.0':
resolution: {integrity: sha512-zT3TufmVOroWPrzXTPPPgYvIsTZIsK13kjpmgXlICyEFrhd16RFLoTWFhP+8UqHXpTNmVbtTHzg+fEVYy9rlEQ==}
cpu: [x64]
os: [win32]
@@ -116,45 +116,45 @@ packages:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
pnpm@11.13.1:
resolution: {integrity: sha512-svx2g7imUlQU59E+G6KMqt3elr9m7FQL+ut+cCuB8+C+TR8pXt9/n+A5Z0Co3ORQnFgt33mJH0VD/qMtN2RfJQ==}
pnpm@11.21.0:
resolution: {integrity: sha512-UhcFvOaJkk6scvWjWHEi82JonvZXHlW6gAdv1jfBETLs/62ib61Op5xIW/3b/T1aKlsFgFp36JPeceyKbMo7sQ==}
engines: {node: '>=22.13'}
hasBin: true
snapshots:
'@pnpm/exe@11.13.1':
'@pnpm/exe@11.21.0':
dependencies:
'@reflink/reflink': 0.1.19
detect-libc: 2.1.2
optionalDependencies:
'@pnpm/linux-arm64': 11.13.1
'@pnpm/linux-x64': 11.13.1
'@pnpm/linuxstatic-arm64': 11.13.1
'@pnpm/linuxstatic-x64': 11.13.1
'@pnpm/macos-arm64': 11.13.1
'@pnpm/win-arm64': 11.13.1
'@pnpm/win-x64': 11.13.1
'@pnpm/linux-arm64': 11.21.0
'@pnpm/linux-x64': 11.21.0
'@pnpm/linuxstatic-arm64': 11.21.0
'@pnpm/linuxstatic-x64': 11.21.0
'@pnpm/macos-arm64': 11.21.0
'@pnpm/win-arm64': 11.21.0
'@pnpm/win-x64': 11.21.0
'@pnpm/linux-arm64@11.13.1':
'@pnpm/linux-arm64@11.21.0':
optional: true
'@pnpm/linux-x64@11.13.1':
'@pnpm/linux-x64@11.21.0':
optional: true
'@pnpm/linuxstatic-arm64@11.13.1':
'@pnpm/linuxstatic-arm64@11.21.0':
optional: true
'@pnpm/linuxstatic-x64@11.13.1':
'@pnpm/linuxstatic-x64@11.21.0':
optional: true
'@pnpm/macos-arm64@11.13.1':
'@pnpm/macos-arm64@11.21.0':
optional: true
'@pnpm/win-arm64@11.13.1':
'@pnpm/win-arm64@11.21.0':
optional: true
'@pnpm/win-x64@11.13.1':
'@pnpm/win-x64@11.21.0':
optional: true
'@reflink/reflink-darwin-arm64@0.1.19':
@@ -194,7 +194,7 @@ snapshots:
detect-libc@2.1.2: {}
pnpm@11.13.1: {}
pnpm@11.21.0: {}
---
lockfileVersion: '9.0'
+16
View File
@@ -0,0 +1,16 @@
These are subsets of
[Inter](https://rsms.me/inter/) by Rasmus Andersson, used by the OG card rendering service.
Inter is licensed under the SIL Open Font License, Version 1.1. The full license text is at
[`assets/fonts/inter/OFL.txt`](../../../../assets/fonts/inter/OFL.txt) and must travel with these
files.
```
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
```
**You may redistribute these files.** They are not carved out of our
[MIT license](../../../../LICENSE) — this notice exists because the OFL requires it.
**One thing to watch:** OFL 1.1 includes a Reserved Font Name provision. If you modify or subset
the font further, you may not distribute the result under the name "Inter."
+1 -2
View File
@@ -7,5 +7,4 @@ export const isView = AppBskyGraphDefs.isStarterPackView
* Matches any starter pack view exported by our SDK
*/
export type AnyStarterPackView =
| AppBskyGraphDefs.StarterPackViewBasic
| AppBskyGraphDefs.StarterPackView
AppBskyGraphDefs.StarterPackViewBasic | AppBskyGraphDefs.StarterPackView
+1 -1
View File
@@ -29,7 +29,7 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/ipfs/bbloom v0.0.4 // indirect
+6 -5
View File
@@ -17,6 +17,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg=
github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
@@ -40,10 +42,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
@@ -186,7 +188,6 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+8 -3
View File
@@ -1,11 +1,16 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"relation": [
"delegate_permission/common.handle_all_urls",
"delegate_permission/common.get_login_creds"
],
"target": {
"namespace": "android_app",
"package_name": "xyz.blueskyweb.app",
"sha256_cert_fingerprints":
["C1:4D:3C:6B:B5:D6:D9:AE:CF:C5:0B:BC:C1:9B:29:6D:D4:E6:87:46:36:D5:4C:1A:64:1C:14:08:BF:7E:F9:62", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"]
"sha256_cert_fingerprints": [
"C1:4D:3C:6B:B5:D6:D9:AE:CF:C5:0B:BC:C1:9B:29:6D:D4:E6:87:46:36:D5:4C:1A:64:1C:14:08:BF:7E:F9:62",
"FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"
]
}
}
]
+1
View File
@@ -6,6 +6,7 @@
# codes are used for rate-limiting. Up to a handful concurrent requests should
# be ok.
User-Agent: *
Disallow: /intent/compose
Allow: /
Sitemap: https://bsky.app/sitemap/users.xml.gz
+6 -12
View File
@@ -1,3 +1,7 @@
# Important: when a two letter code is ambiguous, we need
# to manually map it to the full locale code. This is done
# in the Crowdin language mapping config here:
# https://bluesky.crowdin.com/u/projects/1/languages
{
"project_id": "1",
"base_url": "https://bluesky.api.crowdin.com",
@@ -6,17 +10,7 @@
"files": [
{
"source": "/src/locale/locales/en/messages.po",
"translation": "/src/locale/locales/%two_letters_code%/messages.po",
"languages_mapping": {
"two_letters_code": {
"pt": "pt-PT",
"pt-BR": "pt-BR",
"en-GB": "en-GB",
"zh-CN": "zh-CN",
"zh-TW": "zh-TW",
"zh-HK": "zh-HK"
}
}
"translation": "/src/locale/locales/%two_letters_code%/messages.po"
}
]
}
}
+16 -39
View File
@@ -44,7 +44,7 @@ Double check yourself by ensuring that the `version` in `package.json` matches
what's in the latest release.
This new OTA branch should follow the format `1.X.0-ota-1`. If one or more OTAs
have already been deployed for this release, incremement the branch name e.g.
have already been deployed for this release, increment the branch name e.g.
`1.x.0-ota-2`.
### 4. Add commits to the OTA branch
@@ -52,29 +52,17 @@ have already been deployed for this release, incremement the branch name e.g.
Cherry pick in the commits that need to be deployed on top of the most recent
release or OTA.
### 5. Manually set build numbers
### 5. Pull translations
Log in to the EAS CLI with `eas login` and manually set the build numbers to the
values you found in **Step 1**.
Since translators may have added new strings, and positions within the code may
have shifted, it's typically best to pull the latest translations.
> [!NOTE]
> If you dont already have the EAS CLI, you can install it with `pnpm add -g eas-cli`.
Run this and commit the result as the last commit on the OTA branch.
```sh
> npx eas build:version:set -p ios
>
> Project @blueskysocial/bluesky with bundle identifier "xyz.blueskyweb.app" is configured with buildNumber 1011.
> ✔ What version would you like to set? … 1009
>
> npx eas build:version:set -p android
>
> Project @blueskysocial/bluesky with application ID "xyz.blueskyweb.app" is configured with versionCode 641.
> ✔ What version would you like to set? … 639
pnpm intl:release
```
👉 **Save the previous values,** in this case `1011` and `641`, so you can reset
them after the OTA completes.
### 6. Run the GitHub actions
You'll need to run two separate actions: one to deploy the iOS/Android OTA
itself, and one to build the web Docker container.
@@ -85,14 +73,20 @@ and run the action.
| Steps | |
| ----- | --- |
| Select your OTA branch `1.x.0-ota-x`, select `production` in the dropdown, enter the git tag of the latest release `1.x.0`, and click "Run workflow" | ![workflow](./img/ota_action.png) |
| Select your OTA branch `1.x.0-ota-x`, select `production` in the dropdown, enter the git tag of the latest release `1.x.0`, enter the iOS build number and Android version code you found in **Step 1**, and click "Run workflow" | ![workflow](./img/ota_action.png) |
> [!NOTE]
> Production OTAs are bound to the specific native build they target, so the
> workflow requires the build numbers to be entered manually. There is no need
> to change the global EAS build counters (and doing so is no longer necessary
> for OTAs - they are only used when producing new native builds).
> [!NOTE]
> If you do enter an incorrect version here, the deployment will either:
> - Fail, because the action cannot find a commit with your misentered version
> - Succeed, but with no users receiving the update. This is because the version
> you entered will not properly correlate to a _build number_ as well, so no
> clients in the wild will be able to receive the update.
> - Succeed, but with no users receiving the update. This is because the
> version and build numbers you entered will not match any clients in the
> wild, so none will be able to receive the update.
**For web,** head to [Actions >
build-and-push-bskyweb-aws](https://github.com/bluesky-social/social-app/actions/workflows/build-and-push-bskyweb-aws.yaml)
@@ -119,23 +113,6 @@ build from your device and re-install from the App Store. Then, you'll need to:
- Quit and reopen the app
- Check the `Settings > About` page and confirm the hash matches the most recent hash on your OTA branch
### 9. Reset build numbers
Grab the build numbers you saved in **Step 5** and reverse the EAS CLI commands
to reset the build numbers.
```sh
> npx eas build:version:set -p ios
>
> Project @blueskysocial/bluesky with bundle identifier "xyz.blueskyweb.app" is configured with buildNumber 1009.
> ✔ What version would you like to set? … 1011
>
> npx eas build:version:set -p android
>
> Project @blueskysocial/bluesky with application ID "xyz.blueskyweb.app" is configured with versionCode 639.
> ✔ What version would you like to set? … 641
```
## Overview diagram
![OTA Deployment](./img/ota-flow.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 50 KiB

+15 -3
View File
@@ -49,14 +49,26 @@ Every night, a GitHub action will run `pnpm intl:extract` to update the english
1. Pull main and create a branch.
1. Run `pnpm intl:release` to fetch all translation updates from Crowdin and extract all `.po` files so that they're synced with the latest code. Commit that.
1. Create a PR, ensure the translations all look correct, and merge.
1. If needed:
1. Merge all approved translation PRs (contributions from outside crowdin).
1. Run `pnpm intl:push` to sync Crowdin with the state of the repo.
1. If needed, merge all approved translation PRs (contributions from outside crowdin).
### Testing the translations in Crowdin
You can run `pnpm intl:pull` to pull the currently-approved translations from Crowdin.
### Pushing translations to Crowdin
Run `pnpm intl:push-sources` to push the current state of the english source file to Crowdin. Extract the latest strings first!
If necessary, you can also push translations to Crowdin manually using `pnpm intl:dangerously-push-translations`. **DO NOT DO THIS WITHOUT A GOOD REASON** - it overrides the translator's work and is very confusing. You're probably better off tweaking strings in Crowdin itself.
### Adding new languages
When a new language is added to Crowdin, it gets synced down the next time `pnpm intl:pull` is run. You then need to add the language in a few places - the AppLanguages array, `date-fns`, Intl polyfills etc.
Importantly, we use a two-letter language code (e.g. `en`, `fr`). Crowdin uses a full locale (e.g. `pt-BR`) and maps them to the two-letter code. This can become ambiguous if we have multiple languages that share a two-letter code (e.g. `fr-FR` and `fr-CA`), so we need to manually override the two-letter code into an unambiguous locale. This is done in the Crowdin language settings (*not* the `crowdin.yml` file!). When a new ambiguous language is added, go to https://bluesky.crowdin.com/u/projects/1/languages and map the language's two-letter code to it's locale. If not, the two languages may silently overwrite each other.
![crowdin language mapping config](./img/language-mapping.png)
## Developers
### Adding new strings
+1 -1
View File
@@ -28,7 +28,7 @@
},
"env": {
"EXPO_PUBLIC_ENV": "e2e",
"NODE_ENV": "test",
"NODE_ENV": "development",
"RN_SRC_EXT": "e2e.ts,e2e.tsx"
}
},
-32
View File
@@ -1,32 +0,0 @@
const BANNED_IMPORTS = [
'@fortawesome/free-regular-svg-icons',
'@fortawesome/free-solid-svg-icons',
]
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Prevent importing entire icon packages',
},
schema: [],
},
create(context) {
return {
ImportDeclaration(node) {
const source = node.source
if (typeof source.value !== 'string') {
return
}
if (BANNED_IMPORTS.includes(source.value)) {
context.report({
node,
message:
'Import the specific thing you want instead of the entire package',
})
}
},
}
},
}
+1332
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
{
"lexicon": 1,
"id": "app.bsky.actor.contentVisibilityDeclaration",
"defs": {
"main": {
"type": "record",
"description": "A declaration of an account's preferences for appearing in content discovery surfaces.",
"key": "literal:self",
"record": {
"type": "object",
"required": ["hideFromAlgorithmicRecommendations"],
"properties": {
"hideFromAlgorithmicRecommendations": {
"type": "boolean",
"description": "Whether the account requests that its posts be hidden from algorithmic recommendations. Consumers must treat a missing record as false."
}
}
}
}
}
}
+994
View File
@@ -0,0 +1,994 @@
{
"id": "app.bsky.actor.defs",
"defs": {
"nux": {
"type": "object",
"required": [
"id",
"completed"
],
"properties": {
"id": {
"type": "string",
"maxLength": 100
},
"data": {
"type": "string",
"maxLength": 3000,
"description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.",
"maxGraphemes": 300
},
"completed": {
"type": "boolean",
"default": false
},
"expiresAt": {
"type": "string",
"format": "datetime",
"description": "The date and time at which the NUX will expire and should be considered completed."
}
},
"description": "A new user experiences (NUX) storage object"
},
"mutedWord": {
"type": "object",
"required": [
"value",
"targets"
],
"properties": {
"id": {
"type": "string"
},
"value": {
"type": "string",
"maxLength": 10000,
"description": "The muted word itself.",
"maxGraphemes": 1000
},
"targets": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#mutedWordTarget",
"type": "ref"
},
"description": "The intended targets of the muted word."
},
"expiresAt": {
"type": "string",
"format": "datetime",
"description": "The date and time at which the muted word will expire and no longer be applied."
},
"actorTarget": {
"type": "string",
"default": "all",
"description": "Groups of users to apply the muted word to. If undefined, applies to all users.",
"knownValues": [
"all",
"exclude-following"
]
}
},
"description": "A word that the account owner has muted."
},
"savedFeed": {
"type": "object",
"required": [
"id",
"type",
"value",
"pinned"
],
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string",
"knownValues": [
"feed",
"list",
"timeline"
]
},
"value": {
"type": "string"
},
"pinned": {
"type": "boolean"
}
}
},
"statusView": {
"type": "object",
"required": [
"status",
"record"
],
"properties": {
"cid": {
"type": "string",
"format": "cid"
},
"uri": {
"type": "string",
"format": "at-uri"
},
"embed": {
"refs": [
"app.bsky.embed.external#view"
],
"type": "union",
"description": "An optional embed associated with the status."
},
"labels": {
"type": "array",
"items": {
"ref": "com.atproto.label.defs#label",
"type": "ref"
}
},
"record": {
"type": "unknown"
},
"status": {
"type": "string",
"description": "The status for the account.",
"knownValues": [
"app.bsky.actor.status#live"
]
},
"isActive": {
"type": "boolean",
"description": "True if the status is not expired, false if it is expired. Only present if expiration was set."
},
"expiresAt": {
"type": "string",
"format": "datetime",
"description": "The date when this status will expire. The application might choose to no longer return the status after expiration."
},
"isDisabled": {
"type": "boolean",
"description": "True if the user's go-live access has been disabled by a moderator, false otherwise."
}
}
},
"preferences": {
"type": "array",
"items": {
"refs": [
"#adultContentPref",
"#contentLabelPref",
"#savedFeedsPref",
"#savedFeedsPrefV2",
"#personalDetailsPref",
"#declaredAgePref",
"#feedViewPref",
"#threadViewPref",
"#interestsPref",
"#mutedWordsPref",
"#hiddenPostsPref",
"#bskyAppStatePref",
"#labelersPref",
"#postInteractionSettingsPref",
"#verificationPrefs",
"#liveEventPreferences"
],
"type": "union"
}
},
"profileView": {
"type": "object",
"required": [
"did",
"handle"
],
"properties": {
"did": {
"type": "string",
"format": "did"
},
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
},
"avatar": {
"type": "string",
"format": "uri"
},
"handle": {
"type": "string",
"format": "handle"
},
"labels": {
"type": "array",
"items": {
"ref": "com.atproto.label.defs#label",
"type": "ref"
}
},
"status": {
"ref": "#statusView",
"type": "ref"
},
"viewer": {
"ref": "#viewerState",
"type": "ref"
},
"pronouns": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"indexedAt": {
"type": "string",
"format": "datetime"
},
"associated": {
"ref": "#profileAssociated",
"type": "ref"
},
"description": {
"type": "string",
"maxLength": 2560,
"maxGraphemes": 256
},
"displayName": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
},
"verification": {
"ref": "#verificationState",
"type": "ref"
}
}
},
"viewerState": {
"type": "object",
"properties": {
"muted": {
"type": "boolean",
"description": "Whether the account is fully muted, directly or via a mutelist. False when the mute is scoped to specific kinds; see mutedOnlyReposts and mutedOnlyQuoteposts."
},
"blocking": {
"type": "string",
"format": "at-uri"
},
"blockedBy": {
"type": "boolean"
},
"following": {
"type": "string",
"format": "at-uri"
},
"followedBy": {
"type": "string",
"format": "at-uri"
},
"mutedByList": {
"ref": "app.bsky.graph.defs#listViewBasic",
"type": "ref"
},
"blockingByList": {
"ref": "app.bsky.graph.defs#listViewBasic",
"type": "ref"
},
"knownFollowers": {
"ref": "#knownFollowers",
"type": "ref",
"description": "This property is present only in selected cases, as an optimization."
},
"mutedOnlyReposts": {
"type": "boolean",
"description": "Whether the account's reposts are muted. Scoped mutes are exclusive with muted: this can be true while muted is false. If muted is true, this will be false."
},
"mutedOnlyQuoteposts": {
"type": "boolean",
"description": "Whether the account's quote posts are muted. Scoped mutes are exclusive with muted: this can be true while muted is false. If muted is true, this will be false."
},
"activitySubscription": {
"ref": "app.bsky.notification.defs#activitySubscription",
"type": "ref",
"description": "This property is present only in selected cases, as an optimization."
}
},
"description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests."
},
"feedViewPref": {
"type": "object",
"required": [
"feed"
],
"properties": {
"feed": {
"type": "string",
"description": "The URI of the feed, or an identifier which describes the feed."
},
"hideReplies": {
"type": "boolean",
"description": "Hide replies in the feed."
},
"hideReposts": {
"type": "boolean",
"description": "Hide reposts in the feed."
},
"hideQuotePosts": {
"type": "boolean",
"description": "Hide quote posts in the feed."
},
"hideRepliesByLikeCount": {
"type": "integer",
"description": "Hide replies in the feed if they do not have this number of likes."
},
"hideRepliesByUnfollowed": {
"type": "boolean",
"default": true,
"description": "Hide replies in the feed if they are not by followed users."
}
}
},
"labelersPref": {
"type": "object",
"required": [
"labelers"
],
"properties": {
"labelers": {
"type": "array",
"items": {
"ref": "#labelerPrefItem",
"type": "ref"
}
}
}
},
"interestsPref": {
"type": "object",
"required": [
"tags"
],
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
},
"maxLength": 100,
"description": "A list of tags which describe the account owner's interests gathered during onboarding."
}
}
},
"knownFollowers": {
"type": "object",
"required": [
"count",
"followers"
],
"properties": {
"count": {
"type": "integer"
},
"followers": {
"type": "array",
"items": {
"ref": "#profileViewBasic",
"type": "ref"
},
"maxLength": 5,
"minLength": 0
}
},
"description": "The subject's followers whom you also follow"
},
"mutedWordsPref": {
"type": "object",
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#mutedWord",
"type": "ref"
},
"description": "A list of words the account owner has muted."
}
}
},
"savedFeedsPref": {
"type": "object",
"required": [
"pinned",
"saved"
],
"properties": {
"saved": {
"type": "array",
"items": {
"type": "string",
"format": "at-uri"
}
},
"pinned": {
"type": "array",
"items": {
"type": "string",
"format": "at-uri"
}
},
"timelineIndex": {
"type": "integer"
}
}
},
"threadViewPref": {
"type": "object",
"properties": {
"sort": {
"type": "string",
"description": "Sorting mode for threads.",
"knownValues": [
"oldest",
"newest",
"most-likes",
"random",
"hotness"
]
}
}
},
"declaredAgePref": {
"type": "object",
"properties": {
"isOverAge13": {
"type": "boolean",
"description": "Indicates if the user has declared that they are over 13 years of age."
},
"isOverAge16": {
"type": "boolean",
"description": "Indicates if the user has declared that they are over 16 years of age."
},
"isOverAge18": {
"type": "boolean",
"description": "Indicates if the user has declared that they are over 18 years of age."
}
},
"description": "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration."
},
"hiddenPostsPref": {
"type": "object",
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"type": "string",
"format": "at-uri"
},
"description": "A list of URIs of posts the account owner has hidden."
}
}
},
"labelerPrefItem": {
"type": "object",
"required": [
"did"
],
"properties": {
"did": {
"type": "string",
"format": "did"
}
}
},
"mutedWordTarget": {
"type": "string",
"maxLength": 640,
"knownValues": [
"content",
"tag"
],
"maxGraphemes": 64
},
"adultContentPref": {
"type": "object",
"required": [
"enabled"
],
"properties": {
"enabled": {
"type": "boolean",
"default": false
}
}
},
"bskyAppStatePref": {
"type": "object",
"properties": {
"nuxs": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#nux",
"type": "ref"
},
"maxLength": 100,
"description": "Storage for NUXs the user has encountered."
},
"isBetaUser": {
"type": "boolean",
"description": "Indicates if the user is participating in the beta features program."
},
"queuedNudges": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"maxLength": 1000,
"description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user."
},
"activeProgressGuide": {
"ref": "#bskyAppProgressGuide",
"type": "ref"
}
},
"description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this."
},
"contentLabelPref": {
"type": "object",
"required": [
"label",
"visibility"
],
"properties": {
"label": {
"type": "string"
},
"labelerDid": {
"type": "string",
"format": "did",
"description": "Which labeler does this preference apply to? If undefined, applies globally."
},
"visibility": {
"type": "string",
"knownValues": [
"ignore",
"show",
"warn",
"hide"
]
}
}
},
"profileViewBasic": {
"type": "object",
"required": [
"did",
"handle"
],
"properties": {
"did": {
"type": "string",
"format": "did"
},
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
},
"avatar": {
"type": "string",
"format": "uri"
},
"handle": {
"type": "string",
"format": "handle"
},
"labels": {
"type": "array",
"items": {
"ref": "com.atproto.label.defs#label",
"type": "ref"
}
},
"status": {
"ref": "#statusView",
"type": "ref"
},
"viewer": {
"ref": "#viewerState",
"type": "ref"
},
"pronouns": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"associated": {
"ref": "#profileAssociated",
"type": "ref"
},
"displayName": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
},
"verification": {
"ref": "#verificationState",
"type": "ref"
}
}
},
"savedFeedsPrefV2": {
"type": "object",
"required": [
"items"
],
"properties": {
"items": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#savedFeed",
"type": "ref"
}
}
}
},
"verificationView": {
"type": "object",
"required": [
"issuer",
"uri",
"isValid",
"createdAt"
],
"properties": {
"uri": {
"type": "string",
"format": "at-uri",
"description": "The AT-URI of the verification record."
},
"issuer": {
"type": "string",
"format": "did",
"description": "The user who issued this verification."
},
"isValid": {
"type": "boolean",
"description": "True if the verification passes validation, otherwise false."
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Timestamp when the verification was created."
},
"issuerHandle": {
"type": "string",
"format": "handle",
"description": "The handle of the issuer."
},
"issuerDisplayName": {
"type": "string",
"description": "The display name of the issuer."
}
},
"description": "An individual verification for an associated subject."
},
"profileAssociated": {
"type": "object",
"properties": {
"chat": {
"ref": "#profileAssociatedChat",
"type": "ref"
},
"germ": {
"ref": "#profileAssociatedGerm",
"type": "ref"
},
"lists": {
"type": "integer"
},
"labeler": {
"type": "boolean"
},
"feedgens": {
"type": "integer"
},
"starterPacks": {
"type": "integer"
},
"activitySubscription": {
"ref": "#profileAssociatedActivitySubscription",
"type": "ref"
}
}
},
"verificationPrefs": {
"type": "object",
"required": [],
"properties": {
"hideBadges": {
"type": "boolean",
"default": false,
"description": "Hide the blue check badges for verified accounts and trusted verifiers."
}
},
"description": "Preferences for how verified accounts appear in the app."
},
"verificationState": {
"type": "object",
"required": [
"verifications",
"verifiedStatus",
"trustedVerifierStatus"
],
"properties": {
"verifications": {
"type": "array",
"items": {
"ref": "#verificationView",
"type": "ref"
},
"description": "All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included."
},
"verifiedStatus": {
"type": "string",
"description": "The user's status as a verified account.",
"knownValues": [
"valid",
"invalid",
"none"
]
},
"trustedVerifierStatus": {
"type": "string",
"description": "The user's status as a trusted verifier.",
"knownValues": [
"valid",
"invalid",
"none"
]
}
},
"description": "Represents the verification information about the user this object is attached to."
},
"personalDetailsPref": {
"type": "object",
"properties": {
"birthDate": {
"type": "string",
"format": "datetime",
"description": "The birth date of account owner."
}
}
},
"profileViewDetailed": {
"type": "object",
"required": [
"did",
"handle"
],
"properties": {
"did": {
"type": "string",
"format": "did"
},
"debug": {
"type": "unknown",
"description": "Debug information for internal development"
},
"avatar": {
"type": "string",
"format": "uri"
},
"banner": {
"type": "string",
"format": "uri"
},
"handle": {
"type": "string",
"format": "handle"
},
"labels": {
"type": "array",
"items": {
"ref": "com.atproto.label.defs#label",
"type": "ref"
}
},
"status": {
"ref": "#statusView",
"type": "ref"
},
"viewer": {
"ref": "#viewerState",
"type": "ref"
},
"website": {
"type": "string",
"format": "uri"
},
"pronouns": {
"type": "string"
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"indexedAt": {
"type": "string",
"format": "datetime"
},
"associated": {
"ref": "#profileAssociated",
"type": "ref"
},
"pinnedPost": {
"ref": "com.atproto.repo.strongRef",
"type": "ref"
},
"postsCount": {
"type": "integer"
},
"description": {
"type": "string",
"maxLength": 2560,
"maxGraphemes": 256
},
"displayName": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
},
"followsCount": {
"type": "integer"
},
"verification": {
"ref": "#verificationState",
"type": "ref"
},
"followersCount": {
"type": "integer"
},
"joinedViaStarterPack": {
"ref": "app.bsky.graph.defs#starterPackViewBasic",
"type": "ref"
}
}
},
"bskyAppProgressGuide": {
"type": "object",
"required": [
"guide"
],
"properties": {
"guide": {
"type": "string",
"maxLength": 100
}
},
"description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress."
},
"liveEventPreferences": {
"type": "object",
"properties": {
"hideAllFeeds": {
"type": "boolean",
"default": false,
"description": "Whether to hide all feeds from live events."
},
"hiddenFeedIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "A list of feed IDs that the user has hidden from live events."
}
},
"description": "Preferences for live events."
},
"profileAssociatedChat": {
"type": "object",
"required": [
"allowIncoming"
],
"properties": {
"allowIncoming": {
"type": "string",
"knownValues": [
"all",
"none",
"following"
]
},
"allowGroupInvites": {
"type": "string",
"knownValues": [
"all",
"none",
"following"
]
}
}
},
"profileAssociatedGerm": {
"type": "object",
"required": [
"showButtonTo",
"messageMeUrl"
],
"properties": {
"messageMeUrl": {
"type": "string",
"format": "uri"
},
"showButtonTo": {
"type": "string",
"knownValues": [
"usersIFollow",
"everyone"
]
}
}
},
"postInteractionSettingsPref": {
"type": "object",
"required": [],
"properties": {
"threadgateAllowRules": {
"type": "array",
"items": {
"refs": [
"app.bsky.feed.threadgate#mentionRule",
"app.bsky.feed.threadgate#followerRule",
"app.bsky.feed.threadgate#followingRule",
"app.bsky.feed.threadgate#listRule"
],
"type": "union"
},
"maxLength": 5,
"description": "Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply."
},
"postgateEmbeddingRules": {
"type": "array",
"items": {
"refs": [
"app.bsky.feed.postgate#disableRule"
],
"type": "union"
},
"maxLength": 5,
"description": "Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed."
}
},
"description": "Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly."
},
"profileAssociatedActivitySubscription": {
"type": "object",
"required": [
"allowSubscriptions"
],
"properties": {
"allowSubscriptions": {
"type": "string",
"knownValues": [
"followers",
"mutuals",
"none"
]
}
}
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,30 @@
{
"id": "app.bsky.actor.getPreferences",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"preferences"
],
"properties": {
"preferences": {
"ref": "app.bsky.actor.defs#preferences",
"type": "ref"
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {}
},
"description": "Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+31
View File
@@ -0,0 +1,31 @@
{
"id": "app.bsky.actor.getProfile",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"ref": "app.bsky.actor.defs#profileViewDetailed",
"type": "ref"
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"required": [
"actor"
],
"properties": {
"actor": {
"type": "string",
"format": "at-identifier",
"description": "Handle or DID of account to fetch profile of."
}
}
},
"description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+45
View File
@@ -0,0 +1,45 @@
{
"id": "app.bsky.actor.getProfiles",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"profiles"
],
"properties": {
"profiles": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#profileViewDetailed",
"type": "ref"
}
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"required": [
"actors"
],
"properties": {
"actors": {
"type": "array",
"items": {
"type": "string",
"format": "at-identifier"
},
"maxLength": 25
}
}
},
"description": "Get detailed profile views of multiple actors."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,54 @@
{
"id": "app.bsky.actor.getSuggestions",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"actors"
],
"properties": {
"recId": {
"type": "integer",
"description": "DEPRECATED: use recIdStr instead."
},
"actors": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#profileView",
"type": "ref"
}
},
"cursor": {
"type": "string"
},
"recIdStr": {
"type": "string",
"description": "Snowflake for this recommendation, use when submitting recommendation events."
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"default": 50,
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
}
},
"description": "Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+75
View File
@@ -0,0 +1,75 @@
{
"id": "app.bsky.actor.profile",
"defs": {
"main": {
"key": "literal:self",
"type": "record",
"record": {
"type": "object",
"properties": {
"avatar": {
"type": "blob",
"accept": [
"image/png",
"image/jpeg"
],
"maxSize": 1000000,
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'"
},
"banner": {
"type": "blob",
"accept": [
"image/png",
"image/jpeg"
],
"maxSize": 1000000,
"description": "Larger horizontal image to display behind profile view."
},
"labels": {
"refs": [
"com.atproto.label.defs#selfLabels"
],
"type": "union",
"description": "Self-label values, specific to the Bluesky application, on the overall account."
},
"website": {
"type": "string",
"format": "uri"
},
"pronouns": {
"type": "string",
"maxLength": 200,
"description": "Free-form pronouns text.",
"maxGraphemes": 20
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"pinnedPost": {
"ref": "com.atproto.repo.strongRef",
"type": "ref"
},
"description": {
"type": "string",
"maxLength": 2560,
"description": "Free-form profile description text.",
"maxGraphemes": 256
},
"displayName": {
"type": "string",
"maxLength": 640,
"maxGraphemes": 64
},
"joinedViaStarterPack": {
"ref": "com.atproto.repo.strongRef",
"type": "ref"
}
}
},
"description": "A declaration of a Bluesky account profile."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,26 @@
{
"id": "app.bsky.actor.putPreferences",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"preferences"
],
"properties": {
"preferences": {
"ref": "app.bsky.actor.defs#preferences",
"type": "ref"
}
}
},
"encoding": "application/json"
},
"description": "Set the private preferences attached to the account."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+54
View File
@@ -0,0 +1,54 @@
{
"id": "app.bsky.actor.searchActors",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"actors"
],
"properties": {
"actors": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#profileView",
"type": "ref"
}
},
"cursor": {
"type": "string"
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {
"q": {
"type": "string",
"description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended."
},
"term": {
"type": "string",
"description": "DEPRECATED: use 'q' instead."
},
"limit": {
"type": "integer",
"default": 25,
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
}
},
"description": "Find actors (profiles) matching search criteria. Does not require auth."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,48 @@
{
"id": "app.bsky.actor.searchActorsTypeahead",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"actors"
],
"properties": {
"actors": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#profileViewBasic",
"type": "ref"
}
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {
"q": {
"type": "string",
"description": "Search query prefix; not a full query string."
},
"term": {
"type": "string",
"description": "DEPRECATED: use 'q' instead."
},
"limit": {
"type": "integer",
"default": 10,
"maximum": 100,
"minimum": 1
}
}
},
"description": "Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+48
View File
@@ -0,0 +1,48 @@
{
"id": "app.bsky.actor.status",
"defs": {
"live": {
"type": "token",
"description": "Advertises an account as currently offering live content."
},
"main": {
"key": "literal:self",
"type": "record",
"record": {
"type": "object",
"required": [
"status",
"createdAt"
],
"properties": {
"embed": {
"refs": [
"app.bsky.embed.external"
],
"type": "union",
"description": "An optional embed associated with the status."
},
"status": {
"type": "string",
"description": "The status for the account.",
"knownValues": [
"app.bsky.actor.status#live"
]
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"durationMinutes": {
"type": "integer",
"minimum": 1,
"description": "The duration of the status in minutes. Applications can choose to impose minimum and maximum limits."
}
}
},
"description": "A declaration of a Bluesky account status."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+61
View File
@@ -0,0 +1,61 @@
{
"id": "app.bsky.ageassurance.begin",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"email",
"language",
"countryCode"
],
"properties": {
"email": {
"type": "string",
"description": "The user's email address to receive Age Assurance instructions."
},
"language": {
"type": "string",
"description": "The user's preferred language for communication during the Age Assurance process."
},
"regionCode": {
"type": "string",
"description": "An optional ISO 3166-2 code of the user's region or state within the country."
},
"countryCode": {
"type": "string",
"description": "An ISO 3166-1 alpha-2 code of the user's location."
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "InvalidEmail"
},
{
"name": "DidTooLong"
},
{
"name": "InvalidInitiation"
},
{
"name": "RegionNotSupported"
}
],
"output": {
"schema": {
"ref": "app.bsky.ageassurance.defs#state",
"type": "ref"
},
"encoding": "application/json"
},
"description": "Initiate Age Assurance for an account."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+333
View File
@@ -0,0 +1,333 @@
{
"id": "app.bsky.ageassurance.defs",
"defs": {
"event": {
"type": "object",
"required": [
"createdAt",
"status",
"access",
"attemptId",
"countryCode"
],
"properties": {
"email": {
"type": "string",
"description": "The email used for Age Assurance."
},
"access": {
"type": "string",
"description": "The access level granted based on Age Assurance data we've processed.",
"knownValues": [
"unknown",
"none",
"safe",
"full"
]
},
"initIp": {
"type": "string",
"description": "The IP address used when initiating the Age Assurance flow."
},
"initUa": {
"type": "string",
"description": "The user agent used when initiating the Age Assurance flow."
},
"status": {
"type": "string",
"description": "The status of the Age Assurance process.",
"knownValues": [
"unknown",
"pending",
"assured",
"blocked"
]
},
"attemptId": {
"type": "string",
"description": "The unique identifier for this instance of the Age Assurance flow, in UUID format."
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "The date and time of this write operation."
},
"completeIp": {
"type": "string",
"description": "The IP address used when completing the Age Assurance flow."
},
"completeUa": {
"type": "string",
"description": "The user agent used when completing the Age Assurance flow."
},
"regionCode": {
"type": "string",
"description": "The ISO 3166-2 region code provided when beginning the Age Assurance flow."
},
"countryCode": {
"type": "string",
"description": "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow."
}
},
"description": "Object used to store Age Assurance data in stash."
},
"state": {
"type": "object",
"required": [
"status",
"access"
],
"properties": {
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
},
"status": {
"ref": "app.bsky.ageassurance.defs#status",
"type": "ref"
},
"lastInitiatedAt": {
"type": "string",
"format": "datetime",
"description": "The timestamp when this state was last updated."
}
},
"description": "The user's computed Age Assurance state."
},
"access": {
"type": "string",
"description": "The access level granted based on Age Assurance data we've processed.",
"knownValues": [
"unknown",
"none",
"safe",
"full"
]
},
"config": {
"type": "object",
"required": [
"regions"
],
"properties": {
"regions": {
"type": "array",
"items": {
"ref": "app.bsky.ageassurance.defs#configRegion",
"type": "ref"
},
"description": "The per-region Age Assurance configuration."
}
},
"description": ""
},
"status": {
"type": "string",
"description": "The status of the Age Assurance process.",
"knownValues": [
"unknown",
"pending",
"assured",
"blocked"
]
},
"configRegion": {
"type": "object",
"required": [
"countryCode",
"minAccessAge",
"rules"
],
"properties": {
"rules": {
"type": "array",
"items": {
"refs": [
"#configRegionRuleDefault",
"#configRegionRuleIfDeclaredOverAge",
"#configRegionRuleIfDeclaredUnderAge",
"#configRegionRuleIfAssuredOverAge",
"#configRegionRuleIfAssuredUnderAge",
"#configRegionRuleIfAccountNewerThan",
"#configRegionRuleIfAccountOlderThan"
],
"type": "union"
},
"description": "The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item."
},
"platforms": {
"type": "array",
"items": {
"type": "string",
"knownValues": [
"web",
"ios",
"android"
]
},
"description": "The platforms this configuration applies to. If omitted, the configuration applies to all platforms."
},
"regionCode": {
"type": "string",
"description": "The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country."
},
"countryCode": {
"type": "string",
"description": "The ISO 3166-1 alpha-2 country code this configuration applies to."
},
"minAccessAge": {
"type": "integer",
"description": "The minimum age (as a whole integer) required to use Bluesky in this region."
},
"additionalVerificationMethods": {
"type": "array",
"items": {
"type": "string",
"knownValues": [
"device"
]
},
"description": "Verification methods permitted in this region in addition to the third-party (KWS) flow, which is always supported. `device` permits using the native on-device age APIs (e.g. Apple Declared Age Range, Google Play Age Signals)."
}
},
"description": "The Age Assurance configuration for a specific region."
},
"stateMetadata": {
"type": "object",
"required": [],
"properties": {
"accountCreatedAt": {
"type": "string",
"format": "datetime",
"description": "The account creation timestamp."
}
},
"description": "Additional metadata needed to compute Age Assurance state client-side."
},
"configRegionRuleDefault": {
"type": "object",
"required": [
"access"
],
"properties": {
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies by default."
},
"configRegionRuleIfAssuredOverAge": {
"type": "object",
"required": [
"age",
"access"
],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age."
},
"configRegionRuleIfAssuredUnderAge": {
"type": "object",
"required": [
"age",
"access"
],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies if the user has been assured to be under a certain age."
},
"configRegionRuleIfDeclaredOverAge": {
"type": "object",
"required": [
"age",
"access"
],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age."
},
"configRegionRuleIfAccountNewerThan": {
"type": "object",
"required": [
"date",
"access"
],
"properties": {
"date": {
"type": "string",
"format": "datetime",
"description": "The date threshold as a datetime string."
},
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies if the account is equal-to or newer than a certain date."
},
"configRegionRuleIfAccountOlderThan": {
"type": "object",
"required": [
"date",
"access"
],
"properties": {
"date": {
"type": "string",
"format": "datetime",
"description": "The date threshold as a datetime string."
},
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies if the account is older than a certain date."
},
"configRegionRuleIfDeclaredUnderAge": {
"type": "object",
"required": [
"age",
"access"
],
"properties": {
"age": {
"type": "integer",
"description": "The age threshold as a whole integer."
},
"access": {
"ref": "app.bsky.ageassurance.defs#access",
"type": "ref"
}
},
"description": "Age Assurance rule that applies if the user has declared themselves under a certain age."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,18 @@
{
"id": "app.bsky.ageassurance.getConfig",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"ref": "app.bsky.ageassurance.defs#config",
"type": "ref"
},
"encoding": "application/json"
},
"description": "Returns Age Assurance configuration for use on the client."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,45 @@
{
"id": "app.bsky.ageassurance.getState",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"state",
"metadata"
],
"properties": {
"state": {
"ref": "app.bsky.ageassurance.defs#state",
"type": "ref"
},
"metadata": {
"ref": "app.bsky.ageassurance.defs#stateMetadata",
"type": "ref"
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"required": [
"countryCode"
],
"properties": {
"regionCode": {
"type": "string"
},
"countryCode": {
"type": "string"
}
}
},
"description": "Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,37 @@
{
"id": "app.bsky.bookmark.createBookmark",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"uri",
"cid"
],
"properties": {
"cid": {
"type": "string",
"format": "cid"
},
"uri": {
"type": "string",
"format": "at-uri"
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "UnsupportedCollection",
"description": "The URI to be bookmarked is for an unsupported collection."
}
],
"description": "Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+47
View File
@@ -0,0 +1,47 @@
{
"id": "app.bsky.bookmark.defs",
"defs": {
"bookmark": {
"type": "object",
"required": [
"subject"
],
"properties": {
"subject": {
"ref": "com.atproto.repo.strongRef",
"type": "ref",
"description": "A strong ref to the record to be bookmarked. Currently, only `app.bsky.feed.post` records are supported."
}
},
"description": "Object used to store bookmark data in stash."
},
"bookmarkView": {
"type": "object",
"required": [
"subject",
"item"
],
"properties": {
"item": {
"refs": [
"app.bsky.feed.defs#blockedPost",
"app.bsky.feed.defs#notFoundPost",
"app.bsky.feed.defs#postView"
],
"type": "union"
},
"subject": {
"ref": "com.atproto.repo.strongRef",
"type": "ref",
"description": "A strong ref to the bookmarked record."
},
"createdAt": {
"type": "string",
"format": "datetime"
}
}
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,32 @@
{
"id": "app.bsky.bookmark.deleteBookmark",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"uri"
],
"properties": {
"uri": {
"type": "string",
"format": "at-uri"
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "UnsupportedCollection",
"description": "The URI to be bookmarked is for an unsupported collection."
}
],
"description": "Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,46 @@
{
"id": "app.bsky.bookmark.getBookmarks",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"bookmarks"
],
"properties": {
"cursor": {
"type": "string"
},
"bookmarks": {
"type": "array",
"items": {
"ref": "app.bsky.bookmark.defs#bookmarkView",
"type": "ref"
}
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"default": 50,
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
}
},
"description": "Gets views of records bookmarked by the authenticated user. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+67
View File
@@ -0,0 +1,67 @@
{
"id": "app.bsky.contact.defs",
"defs": {
"syncStatus": {
"type": "object",
"required": [
"syncedAt",
"matchesCount"
],
"properties": {
"syncedAt": {
"type": "string",
"format": "datetime",
"description": "Last date when contacts where imported."
},
"matchesCount": {
"type": "integer",
"minimum": 0,
"description": "Number of existing contact matches resulting of the user imports and of their imported contacts having imported the user. Matches stop being counted when the user either follows the matched contact or dismisses the match."
}
}
},
"notification": {
"type": "object",
"required": [
"from",
"to"
],
"properties": {
"to": {
"type": "string",
"format": "did",
"description": "The DID of who this notification should go to."
},
"from": {
"type": "string",
"format": "did",
"description": "The DID of who this notification comes from."
}
},
"description": "A stash object to be sent via bsync representing a notification to be created."
},
"matchAndContactIndex": {
"type": "object",
"required": [
"match",
"contactIndex"
],
"properties": {
"match": {
"ref": "app.bsky.actor.defs#profileView",
"type": "ref",
"description": "Profile of the matched user."
},
"contactIndex": {
"type": "integer",
"maximum": 999,
"minimum": 0,
"description": "The index of this match in the import contact input."
}
},
"description": "Associates a profile with the positional index of the contact import input in the call to `app.bsky.contact.importContacts`, so clients can know which phone caused a particular match."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,42 @@
{
"id": "app.bsky.contact.dismissMatch",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"subject"
],
"properties": {
"subject": {
"type": "string",
"format": "did",
"description": "The subject's DID to dismiss the match with."
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"properties": {}
},
"encoding": "application/json"
},
"description": "Removes a match that was found via contact import. It shouldn't appear again if the same contact is re-imported. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+60
View File
@@ -0,0 +1,60 @@
{
"id": "app.bsky.contact.getMatches",
"defs": {
"main": {
"type": "query",
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InvalidLimit"
},
{
"name": "InvalidCursor"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"required": [
"matches"
],
"properties": {
"cursor": {
"type": "string"
},
"matches": {
"type": "array",
"items": {
"ref": "app.bsky.actor.defs#profileView",
"type": "ref"
}
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"default": 50,
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
}
},
"description": "Returns the matched contacts (contacts that were mutually imported). Excludes dismissed matches. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,36 @@
{
"id": "app.bsky.contact.getSyncStatus",
"defs": {
"main": {
"type": "query",
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"properties": {
"syncStatus": {
"ref": "app.bsky.contact.defs#syncStatus",
"type": "ref",
"description": "If present, indicates the user has imported their contacts. If not present, indicates the user never used the feature or called `app.bsky.contact.removeData` and didn't import again since."
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {}
},
"description": "Gets the user's current contact import status. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,72 @@
{
"id": "app.bsky.contact.importContacts",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"token",
"contacts"
],
"properties": {
"token": {
"type": "string",
"description": "JWT to authenticate the call. Use the JWT received as a response to the call to `app.bsky.contact.verifyPhone`."
},
"contacts": {
"type": "array",
"items": {
"type": "string"
},
"maxLength": 1000,
"minLength": 1,
"description": "List of phone numbers in global E.164 format (e.g., '+12125550123'). Phone numbers that cannot be normalized into a valid phone number will be discarded. Should not repeat the 'phone' input used in `app.bsky.contact.verifyPhone`."
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InvalidContacts"
},
{
"name": "TooManyContacts"
},
{
"name": "InvalidToken"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"required": [
"matchesAndContactIndexes"
],
"properties": {
"matchesAndContactIndexes": {
"type": "array",
"items": {
"ref": "app.bsky.contact.defs#matchAndContactIndex",
"type": "ref"
},
"description": "The users that matched during import and their indexes on the input contacts, so the client can correlate with its local list."
}
}
},
"encoding": "application/json"
},
"description": "Import contacts for securely matching with other users. This follows the protocol explained in https://docs.bsky.app/blog/contact-import-rfc. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+33
View File
@@ -0,0 +1,33 @@
{
"id": "app.bsky.contact.removeData",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"properties": {}
},
"encoding": "application/json"
},
"errors": [
{
"name": "InvalidDid"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"properties": {}
},
"encoding": "application/json"
},
"description": "Removes all stored hashes used for contact matching, existing matches, and sync status. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,40 @@
{
"id": "app.bsky.contact.sendNotification",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"from",
"to"
],
"properties": {
"to": {
"type": "string",
"format": "did",
"description": "The DID of who this notification should go to."
},
"from": {
"type": "string",
"format": "did",
"description": "The DID of who this notification comes from."
}
}
},
"encoding": "application/json"
},
"output": {
"schema": {
"type": "object",
"properties": {}
},
"encoding": "application/json"
},
"description": "System endpoint to send notifications related to contact imports. Requires role authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,47 @@
{
"id": "app.bsky.contact.startPhoneVerification",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"phone"
],
"properties": {
"phone": {
"type": "string",
"description": "The phone number to receive the code via SMS."
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "RateLimitExceeded"
},
{
"name": "InvalidDid"
},
{
"name": "InvalidPhone"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"properties": {}
},
"encoding": "application/json"
},
"description": "Starts a phone verification flow. The phone passed will receive a code via SMS that should be passed to `app.bsky.contact.verifyPhone`. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
@@ -0,0 +1,63 @@
{
"id": "app.bsky.contact.verifyPhone",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"phone",
"code"
],
"properties": {
"code": {
"type": "string",
"description": "The code received via SMS as a result of the call to `app.bsky.contact.startPhoneVerification`."
},
"phone": {
"type": "string",
"description": "The phone number to verify. Should be the same as the one passed to `app.bsky.contact.startPhoneVerification`."
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "RateLimitExceeded"
},
{
"name": "InvalidDid"
},
{
"name": "InvalidPhone"
},
{
"name": "InvalidCode"
},
{
"name": "InternalError"
}
],
"output": {
"schema": {
"type": "object",
"required": [
"token"
],
"properties": {
"token": {
"type": "string",
"description": "JWT to be used in a call to `app.bsky.contact.importContacts`. It is only valid for a single call."
}
}
},
"encoding": "application/json"
},
"description": "Verifies control over a phone number with a code received via SMS and starts a contact import session. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+48
View File
@@ -0,0 +1,48 @@
{
"id": "app.bsky.draft.createDraft",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"draft"
],
"properties": {
"draft": {
"ref": "app.bsky.draft.defs#draft",
"type": "ref"
}
}
},
"encoding": "application/json"
},
"errors": [
{
"name": "DraftLimitReached",
"description": "Trying to insert a new draft when the limit was already reached."
}
],
"output": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"format": "tid",
"description": "The ID of the created draft."
}
}
},
"encoding": "application/json"
},
"description": "Inserts a draft using private storage (stash). An upper limit of drafts might be enforced. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+296
View File
@@ -0,0 +1,296 @@
{
"id": "app.bsky.draft.defs",
"defs": {
"draft": {
"type": "object",
"required": [
"posts"
],
"properties": {
"langs": {
"type": "array",
"items": {
"type": "string",
"format": "language"
},
"maxLength": 3,
"description": "Indicates human language of posts primary text content."
},
"posts": {
"type": "array",
"items": {
"ref": "#draftPost",
"type": "ref"
},
"maxLength": 100,
"minLength": 1,
"description": "Array of draft posts that compose this draft."
},
"deviceId": {
"type": "string",
"maxLength": 100,
"description": "UUIDv4 identifier of the device that created this draft."
},
"deviceName": {
"type": "string",
"maxLength": 100,
"description": "The device and/or platform on which the draft was created."
},
"threadgateAllow": {
"type": "array",
"items": {
"refs": [
"app.bsky.feed.threadgate#mentionRule",
"app.bsky.feed.threadgate#followerRule",
"app.bsky.feed.threadgate#followingRule",
"app.bsky.feed.threadgate#listRule"
],
"type": "union"
},
"maxLength": 5,
"description": "Allow-rules for the threadgate to be created when this draft is published."
},
"postgateEmbeddingRules": {
"type": "array",
"items": {
"refs": [
"app.bsky.feed.postgate#disableRule"
],
"type": "union"
},
"maxLength": 5,
"description": "Embedding rules for the postgates to be created when this draft is published."
}
},
"description": "A draft containing an array of draft posts."
},
"draftPost": {
"type": "object",
"required": [
"text"
],
"properties": {
"text": {
"type": "string",
"maxLength": 10000,
"description": "The primary post content. It has a higher limit than post contents to allow storing a larger text that can later be refined into smaller posts.",
"maxGraphemes": 1000
},
"labels": {
"refs": [
"com.atproto.label.defs#selfLabels"
],
"type": "union",
"description": "Self-label values for this post. Effectively content warnings."
},
"embedImages": {
"type": "array",
"items": {
"ref": "#draftEmbedImage",
"type": "ref"
},
"maxLength": 4
},
"embedVideos": {
"type": "array",
"items": {
"ref": "#draftEmbedVideo",
"type": "ref"
},
"maxLength": 1
},
"embedGallery": {
"ref": "#draftEmbedGallery",
"type": "ref"
},
"embedRecords": {
"type": "array",
"items": {
"ref": "#draftEmbedRecord",
"type": "ref"
},
"maxLength": 1
},
"embedExternals": {
"type": "array",
"items": {
"ref": "#draftEmbedExternal",
"type": "ref"
},
"maxLength": 1
}
},
"description": "One of the posts that compose a draft."
},
"draftView": {
"type": "object",
"required": [
"id",
"draft",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "tid",
"description": "A TID to be used as a draft identifier."
},
"draft": {
"ref": "#draft",
"type": "ref"
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "The time the draft was created."
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "The time the draft was last updated."
}
},
"description": "View to present drafts data to users."
},
"draftWithId": {
"type": "object",
"required": [
"id",
"draft"
],
"properties": {
"id": {
"type": "string",
"format": "tid",
"description": "A TID to be used as a draft identifier."
},
"draft": {
"ref": "#draft",
"type": "ref"
}
},
"description": "A draft with an identifier, used to store drafts in private storage (stash)."
},
"draftEmbedImage": {
"type": "object",
"required": [
"localRef"
],
"properties": {
"alt": {
"type": "string",
"maxGraphemes": 2000
},
"localRef": {
"ref": "#draftEmbedLocalRef",
"type": "ref"
}
}
},
"draftEmbedVideo": {
"type": "object",
"required": [
"localRef"
],
"properties": {
"alt": {
"type": "string",
"maxGraphemes": 2000
},
"captions": {
"type": "array",
"items": {
"ref": "#draftEmbedCaption",
"type": "ref"
},
"maxLength": 20
},
"localRef": {
"ref": "#draftEmbedLocalRef",
"type": "ref"
}
}
},
"draftEmbedRecord": {
"type": "object",
"required": [
"record"
],
"properties": {
"record": {
"ref": "com.atproto.repo.strongRef",
"type": "ref"
}
}
},
"draftEmbedCaption": {
"type": "object",
"required": [
"lang",
"content"
],
"properties": {
"lang": {
"type": "string",
"format": "language"
},
"content": {
"type": "string",
"maxLength": 10000
}
}
},
"draftEmbedGallery": {
"type": "object",
"required": [
"items"
],
"properties": {
"items": {
"ref": "#draftEmbedGalleryItems",
"type": "ref"
}
}
},
"draftEmbedExternal": {
"type": "object",
"required": [
"uri"
],
"properties": {
"uri": {
"type": "string",
"format": "uri"
}
}
},
"draftEmbedLocalRef": {
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string",
"maxLength": 1024,
"minLength": 1,
"description": "Local, on-device ref to file to be embedded. Embeds are currently device-bound for drafts."
}
}
},
"draftEmbedGalleryItems": {
"type": "array",
"items": {
"refs": [
"#draftEmbedImage"
],
"type": "union"
},
"maxLength": 20,
"description": "The schema-level maxLength of 20 is a future-proof ceiling. Clients should currently enforce a soft limit of 10 items in authoring UIs."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+26
View File
@@ -0,0 +1,26 @@
{
"id": "app.bsky.draft.deleteDraft",
"defs": {
"main": {
"type": "procedure",
"input": {
"schema": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"format": "tid"
}
}
},
"encoding": "application/json"
},
"description": "Deletes a draft by ID. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
+46
View File
@@ -0,0 +1,46 @@
{
"id": "app.bsky.draft.getDrafts",
"defs": {
"main": {
"type": "query",
"output": {
"schema": {
"type": "object",
"required": [
"drafts"
],
"properties": {
"cursor": {
"type": "string"
},
"drafts": {
"type": "array",
"items": {
"ref": "app.bsky.draft.defs#draftView",
"type": "ref"
}
}
}
},
"encoding": "application/json"
},
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"default": 50,
"maximum": 100,
"minimum": 1
},
"cursor": {
"type": "string"
}
}
},
"description": "Gets views of user drafts. Requires authentication."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}

Some files were not shown because too many files have changed in this diff Show More