Skip to content

Commit 768a88d

Browse files
authored
Merge branch 'main' into rob/mobile-560
2 parents e3909ec + 774304d commit 768a88d

5 files changed

Lines changed: 154 additions & 12 deletions

File tree

.changeset/expo-e2e-split-job.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

.changeset/tidy-tokens-activate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/backend': minor
3+
---
4+
5+
Add an optional `orgId` parameter to `createSignInToken()` for activating an Organization when the token is redeemed.

.github/workflows/expo-native-build.yml

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Expo native build (@clerk/expo)'
1+
name: 'Expo native (@clerk/expo)'
22

33
on:
44
pull_request:
@@ -17,7 +17,7 @@ permissions:
1717
contents: read
1818

1919
concurrency:
20-
group: expo-native-build-${{ github.head_ref || github.ref }}
20+
group: expo-native-${{ github.head_ref || github.ref }}
2121
cancel-in-progress: true
2222

2323
env:
@@ -32,9 +32,9 @@ env:
3232
jobs:
3333
native-build:
3434
if: ${{ github.head_ref != 'changeset-release/main' }}
35-
name: Expo ${{ matrix.expo-sdk }} / ${{ matrix.platform }}
35+
name: Expo ${{ matrix.expo-sdk }} Build / ${{ matrix.platform }}
3636
runs-on: ${{ matrix.runner }}
37-
timeout-minutes: ${{ matrix.run-e2e == true && 60 || 45 }}
37+
timeout-minutes: 45
3838
strategy:
3939
fail-fast: false
4040
matrix:
@@ -153,6 +153,93 @@ jobs:
153153
working-directory: ${{ env.FIXTURE_DIR }}
154154
run: pnpm build:ios
155155

156+
# tar the .app so its bundle structure survives the artifact round-trip.
157+
- name: Package iOS app for e2e
158+
if: matrix.platform == 'ios' && matrix.run-e2e == true && steps.keys.outputs.pk != ''
159+
working-directory: ${{ env.FIXTURE_DIR }}
160+
run: |
161+
tar -C ios/build/Build/Products/Release-iphonesimulator \
162+
-cf "$RUNNER_TEMP/ClerkExpoNativeBuildFixture.app.tar" \
163+
ClerkExpoNativeBuildFixture.app
164+
165+
- name: Upload iOS fixture app
166+
if: matrix.platform == 'ios' && matrix.run-e2e == true && steps.keys.outputs.pk != ''
167+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
168+
with:
169+
name: expo-fixture-ios
170+
path: ${{ runner.temp }}/ClerkExpoNativeBuildFixture.app.tar
171+
retention-days: 1
172+
if-no-files-found: error
173+
174+
- name: Upload Android fixture app
175+
if: matrix.platform == 'android' && matrix.run-e2e == true && steps.keys.outputs.pk != ''
176+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
177+
with:
178+
name: expo-fixture-android
179+
path: ${{ env.FIXTURE_DIR }}/android/app/build/outputs/apk/release/app-release.apk
180+
retention-days: 1
181+
if-no-files-found: error
182+
183+
e2e:
184+
name: E2E / ${{ matrix.platform }}
185+
needs: native-build
186+
runs-on: ${{ matrix.runner }}
187+
timeout-minutes: 30
188+
strategy:
189+
fail-fast: false
190+
matrix:
191+
include:
192+
- platform: ios
193+
runner: blacksmith-6vcpu-macos-26
194+
- platform: android
195+
runner: blacksmith-8vcpu-ubuntu-2204
196+
197+
steps:
198+
- name: Checkout repo
199+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
200+
with:
201+
persist-credentials: false
202+
203+
# Only Node (for the key resolver) is needed; the e2e job builds nothing.
204+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
205+
with:
206+
node-version: 24.15.0
207+
208+
- name: Check e2e secret availability
209+
id: gate
210+
env:
211+
KEYS: ${{ secrets.INTEGRATION_STAGING_INSTANCE_KEYS }}
212+
run: |
213+
if [ -n "$KEYS" ]; then
214+
echo "ok=true" >> "$GITHUB_OUTPUT"
215+
else
216+
echo "ok=false" >> "$GITHUB_OUTPUT"
217+
echo "::notice::INTEGRATION_STAGING_INSTANCE_KEYS unavailable (fork PR?); skipping e2e."
218+
fi
219+
220+
- name: Resolve Clerk instance keys
221+
id: keys
222+
if: steps.gate.outputs.ok == 'true'
223+
continue-on-error: true
224+
env:
225+
INTEGRATION_STAGING_INSTANCE_KEYS: ${{ secrets.INTEGRATION_STAGING_INSTANCE_KEYS }}
226+
run: node scripts/resolve-instance-keys.mjs INTEGRATION_STAGING_INSTANCE_KEYS "$E2E_INSTANCE_NAME"
227+
228+
- name: Download fixture app
229+
if: steps.keys.outputs.pk != ''
230+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
231+
with:
232+
name: expo-fixture-${{ matrix.platform }}
233+
path: ${{ runner.temp }}/fixture-app
234+
235+
# Maestro is a JVM app and needs a JDK on the Linux runner (macOS ships one).
236+
- name: Set up JDK 17
237+
if: matrix.platform == 'android' && steps.keys.outputs.pk != ''
238+
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
239+
with:
240+
distribution: temurin
241+
java-version: 17
242+
156243
- name: Cache Maestro
157244
if: steps.keys.outputs.pk != ''
158245
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
@@ -189,8 +276,8 @@ jobs:
189276
with:
190277
model: 'iPhone 17 Pro'
191278

192-
# Provisioned as late as possible (after the expensive native build and
193-
# simulator boot) so live credentials exist for the shortest window.
279+
# Provisioned as late as possible so live credentials exist for the
280+
# shortest window.
194281
- name: Provision test user via BAPI
195282
id: user
196283
if: steps.keys.outputs.pk != ''
@@ -224,7 +311,6 @@ jobs:
224311
# Burn-in: e2e cannot fail the check yet. Flip to hard-fail in a
225312
# follow-up once the suite has proven quiet.
226313
continue-on-error: true
227-
working-directory: ${{ env.FIXTURE_DIR }}
228314
env:
229315
CLERK_TEST_EMAIL: ${{ steps.user.outputs.email }}
230316
CLERK_TEST_PASSWORD: ${{ steps.user.outputs.password }}
@@ -239,7 +325,8 @@ jobs:
239325
xcrun simctl spawn "$SIM_UDID" defaults write com.apple.keyboard.AutoCapitalization -bool NO || true
240326
xcrun simctl spawn "$SIM_UDID" defaults write com.apple.keyboard.AutoCorrection -bool NO || true
241327
xcrun simctl spawn "$SIM_UDID" defaults write com.apple.keyboard.Prediction -bool NO || true
242-
xcrun simctl install "$SIM_UDID" ios/build/Build/Products/Release-iphonesimulator/ClerkExpoNativeBuildFixture.app
328+
tar -C "$RUNNER_TEMP/fixture-app" -xf "$RUNNER_TEMP/fixture-app/ClerkExpoNativeBuildFixture.app.tar"
329+
xcrun simctl install "$SIM_UDID" "$RUNNER_TEMP/fixture-app/ClerkExpoNativeBuildFixture.app"
243330
# Stream the app's console output into the debug artifact so a hang has
244331
# actionable evidence (keychain/network errors) instead of just screenshots.
245332
mkdir -p "$RUNNER_TEMP/maestro-debug"
@@ -301,7 +388,7 @@ jobs:
301388
# The action runs each script line in a separate sh -c; the folded
302389
# scalar (>-) plus && keeps everything in one shell invocation.
303390
script: >-
304-
adb install -r integration/templates/expo-native/android/app/build/outputs/apk/release/app-release.apk &&
391+
adb install -r "$RUNNER_TEMP/fixture-app/app-release.apk" &&
305392
cd integration/tests/expo-native &&
306393
MAESTRO_DEBUG_OUTPUT="$RUNNER_TEMP/maestro-debug" ./run-flows.sh adb shell am force-stop com.clerk.exponativebuildfixture
307394
@@ -334,7 +421,7 @@ jobs:
334421
if: always() && (steps.run_e2e_ios.outcome == 'failure' || steps.run_e2e_android.outcome == 'failure')
335422
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
336423
with:
337-
name: maestro-${{ matrix.platform }}-sdk${{ matrix.expo-sdk }}
424+
name: maestro-${{ matrix.platform }}
338425
path: ${{ runner.temp }}/maestro-debug
339426
retention-days: 7
340427

@@ -346,7 +433,7 @@ jobs:
346433
run: |
347434
outcome="$IOS_OUTCOME"
348435
[ "$outcome" = "skipped" ] && outcome="$ANDROID_OUTCOME"
349-
echo "## Maestro e2e (${{ matrix.platform }}, SDK ${{ matrix.expo-sdk }}): $outcome" >> "$GITHUB_STEP_SUMMARY"
436+
echo "## Maestro e2e (${{ matrix.platform }}): $outcome" >> "$GITHUB_STEP_SUMMARY"
350437
if [ "$outcome" = "failure" ]; then
351-
echo "::warning::Maestro e2e failed (burn-in mode: not failing the job). See the maestro-${{ matrix.platform }}-sdk${{ matrix.expo-sdk }} artifact."
438+
echo "::warning::Maestro e2e failed (burn-in mode: not failing the job). See the maestro-${{ matrix.platform }} artifact."
352439
fi
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { http, HttpResponse } from 'msw';
2+
import { describe, expect, it } from 'vitest';
3+
4+
import { server, validateHeaders } from '../../mock-server';
5+
import { createBackendApiClient } from '../factory';
6+
7+
describe('SignInTokenAPI', () => {
8+
const apiClient = createBackendApiClient({
9+
apiUrl: 'https://api.clerk.test',
10+
secretKey: 'deadbeef',
11+
});
12+
13+
it('sends the organization ID as org_id', async () => {
14+
server.use(
15+
http.post(
16+
'https://api.clerk.test/v1/sign_in_tokens',
17+
validateHeaders(async ({ request }) => {
18+
const body = await request.json();
19+
expect(body).toEqual({
20+
user_id: 'user_123',
21+
org_id: 'org_123',
22+
expires_in_seconds: 3600,
23+
});
24+
25+
return HttpResponse.json({
26+
id: 'sign_in_token_123',
27+
user_id: 'user_123',
28+
token: 'token_123',
29+
status: 'pending',
30+
url: 'https://accounts.example.com/tickets/token_123',
31+
created_at: 1,
32+
updated_at: 1,
33+
});
34+
}),
35+
),
36+
);
37+
38+
const response = await apiClient.signInTokens.createSignInToken({
39+
userId: 'user_123',
40+
orgId: 'org_123',
41+
expiresInSeconds: 3600,
42+
});
43+
44+
expect(response.id).toBe('sign_in_token_123');
45+
});
46+
});

packages/backend/src/api/endpoints/SignInTokenApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { AbstractAPI } from './AbstractApi';
66
export type CreateSignInTokensParams = {
77
/** The ID of the user to create the sign-in token for. */
88
userId: string;
9+
/** The ID of the organization to activate when the user signs in. Organizations must be enabled for the instance, and the user must be a member of the organization. */
10+
orgId?: string;
911
/** The number of seconds until the sign-in token expires. By default, the duration is `2592000` (30 days). */
1012
expiresInSeconds: number;
1113
};

0 commit comments

Comments
 (0)