Skip to content

Commit 80eb788

Browse files
committed
Merge branch 'vincent-and-the-doctor' into nikos/ui-versioning-p1
2 parents fc21a09 + ba99fed commit 80eb788

File tree

130 files changed

+2561
-617
lines changed

Some content is hidden

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

130 files changed

+2561
-617
lines changed

.changeset/bright-papayas-accept.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

.changeset/fuzzy-keys-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/shared': patch
3+
---
4+
5+
Build internal variants of all paginated hooks that use React Query instead of SWR.

.changeset/moody-parks-scream.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

.changeset/ripe-ants-carry.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

.changeset/ripe-banks-pay.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

.changeset/shaggy-numbers-attack.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.cursor/rules/monorepo.mdc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ Localization Support
9090
Package Interdependency Rules
9191

9292
- `@clerk/shared` is a common dependency for most packages
93-
- `@clerk/types` provides TypeScript definitions used across packages
93+
- `@clerk/shared/types` provides TypeScript definitions used across packages
94+
- `@clerk/types` is an alias for `@clerk/shared/types`, but its usage is deprecated. Prefer using `@clerk/shared/types`.
95+
- If a TypeScript error comes from a type imported from `@clerk/shared/types`, run `turbo build --filter=@clerk/shared --filter=@clerk/types` to make sure the latest version of the packages are being used.
9496
- `@clerk/backend` is independent and used for server-side operations
9597
- Framework packages depend on `@clerk/clerk-js` for core functionality
9698
- Integration packages build upon framework-specific packages

.github/workflows/ci.yml

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ jobs:
191191
unit-tests:
192192
needs: [check-permissions, build-packages]
193193
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
194-
name: Unit Tests
194+
name: Unit Tests (${{ matrix.filter-label }}${{ matrix.clerk-use-rq == 'true' && ', RQ' || '' }})
195195
permissions:
196196
contents: read
197197
actions: write # needed for actions/upload-artifact
@@ -205,11 +205,17 @@ jobs:
205205
TURBO_SUMMARIZE: false
206206

207207
strategy:
208-
fail-fast: true
208+
fail-fast: false
209209
matrix:
210210
include:
211211
- node-version: 22
212212
test-filter: "**"
213+
clerk-use-rq: "false"
214+
filter-label: "**"
215+
- node-version: 22
216+
test-filter: "--filter=@clerk/shared --filter=@clerk/clerk-js"
217+
clerk-use-rq: "true"
218+
filter-label: "shared, clerk-js"
213219

214220
steps:
215221
- name: Checkout Repo
@@ -229,22 +235,35 @@ jobs:
229235
turbo-team: ${{ vars.TURBO_TEAM }}
230236
turbo-token: ${{ secrets.TURBO_TOKEN }}
231237

238+
- name: Rebuild @clerk/shared with CLERK_USE_RQ=true
239+
if: ${{ matrix.clerk-use-rq == 'true' }}
240+
run: pnpm turbo build $TURBO_ARGS --filter=@clerk/shared --force
241+
env:
242+
CLERK_USE_RQ: true
243+
244+
- name: Rebuild dependent packages with CLERK_USE_RQ=true
245+
if: ${{ matrix.clerk-use-rq == 'true' }}
246+
run: pnpm turbo build $TURBO_ARGS --filter=@clerk/shared^... --force
247+
env:
248+
CLERK_USE_RQ: true
249+
232250
- name: Run tests in packages
233251
run: |
234252
if [ "${{ matrix.test-filter }}" = "**" ]; then
235-
echo "Running full test suite on Node ${{ matrix.node-version }}."
253+
echo "Running full test suite on Node ${{ matrix.node-version }}"
236254
pnpm turbo test $TURBO_ARGS
237255
else
238-
echo "Running LTS subset on Node ${{ matrix.node-version }}."
256+
echo "Running tests: ${{ matrix.filter-label }}"
239257
pnpm turbo test $TURBO_ARGS ${{ matrix.test-filter }}
240258
fi
241259
env:
242260
NODE_VERSION: ${{ matrix.node-version }}
261+
CLERK_USE_RQ: ${{ matrix.clerk-use-rq }}
243262

244263
- name: Run Typedoc tests
245264
run: |
246-
# Only run Typedoc tests for one matrix version
247-
if [ "${{ matrix.node-version }}" == "22" ]; then
265+
# Only run Typedoc tests for one matrix version and main test run
266+
if [ "${{ matrix.node-version }}" == "22" ] && [ "${{ matrix.test-filter }}" = "**" ]; then
248267
pnpm turbo run //#test:typedoc
249268
fi
250269
env:
@@ -255,14 +274,14 @@ jobs:
255274
if: ${{ env.TURBO_SUMMARIZE == 'true' }}
256275
continue-on-error: true
257276
with:
258-
name: turbo-summary-report-unit-${{ github.run_id }}-${{ github.run_attempt }}-node-${{ matrix.node-version }}
277+
name: turbo-summary-report-unit-${{ github.run_id }}-${{ github.run_attempt }}-node-${{ matrix.node-version }}${{ matrix.clerk-use-rq == 'true' && '-rq' || '' }}
259278
path: .turbo/runs
260279
retention-days: 5
261280

262281
integration-tests:
263282
needs: [check-permissions, build-packages]
264283
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
265-
name: Integration Tests
284+
name: Integration Tests (${{ matrix.test-name }}, ${{ matrix.test-project }}${{ matrix.next-version && format(', {0}', matrix.next-version) || '' }}${{ matrix.clerk-use-rq == 'true' && ', RQ' || '' }})
266285
permissions:
267286
contents: read
268287
actions: write # needed for actions/upload-artifact
@@ -290,15 +309,25 @@ jobs:
290309
"vue",
291310
"nuxt",
292311
"react-router",
293-
"billing",
294312
"machine",
295313
"custom",
296314
]
297315
test-project: ["chrome"]
298316
include:
317+
- test-name: "billing"
318+
test-project: "chrome"
319+
clerk-use-rq: "false"
320+
- test-name: "billing"
321+
test-project: "chrome"
322+
clerk-use-rq: "true"
299323
- test-name: "nextjs"
300324
test-project: "chrome"
301325
next-version: "15"
326+
clerk-use-rq: "false"
327+
- test-name: "nextjs"
328+
test-project: "chrome"
329+
next-version: "15"
330+
clerk-use-rq: "true"
302331
- test-name: "nextjs"
303332
test-project: "chrome"
304333
next-version: "16"
@@ -357,12 +386,24 @@ jobs:
357386
echo "affected=${AFFECTED}"
358387
echo "affected=${AFFECTED}" >> $GITHUB_OUTPUT
359388
389+
- name: Rebuild @clerk/shared with CLERK_USE_RQ=true
390+
if: ${{ steps.task-status.outputs.affected == '1' && matrix.clerk-use-rq == 'true' }}
391+
run: pnpm turbo build $TURBO_ARGS --filter=@clerk/shared --force
392+
env:
393+
CLERK_USE_RQ: true
394+
395+
- name: Rebuild dependent packages with CLERK_USE_RQ=true
396+
if: ${{ steps.task-status.outputs.affected == '1' && matrix.clerk-use-rq == 'true' }}
397+
run: pnpm turbo build $TURBO_ARGS --filter=@clerk/shared^... --force
398+
env:
399+
CLERK_USE_RQ: true
400+
360401
- name: Verdaccio
361402
if: ${{ steps.task-status.outputs.affected == '1' }}
362403
uses: ./.github/actions/verdaccio
363404
with:
364405
publish-cmd: |
365-
if [ "$(pnpm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag; fi
406+
if [ "$(pnpm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else CLERK_USE_RQ=${{ matrix.clerk-use-rq }} pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag; fi
366407
367408
- name: Edit .npmrc [link-workspace-packages=false]
368409
run: sed -i -E 's/link-workspace-packages=(deep|true)/link-workspace-packages=false/' .npmrc
@@ -432,6 +473,7 @@ jobs:
432473
E2E_NEXTJS_VERSION: ${{ matrix.next-version }}
433474
E2E_PROJECT: ${{ matrix.test-project }}
434475
E2E_CLERK_ENCRYPTION_KEY: ${{ matrix.clerk-encryption-key }}
476+
CLERK_USE_RQ: ${{ matrix.clerk-use-rq }}
435477
INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }}
436478
MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }}
437479
NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/integration/certs/rootCA.pem
@@ -440,7 +482,7 @@ jobs:
440482
if: ${{ cancelled() || failure() }}
441483
uses: actions/upload-artifact@v4
442484
with:
443-
name: playwright-traces-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.test-name }}
485+
name: playwright-traces-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.test-name }}${{ matrix.next-version && format('-next{0}', matrix.next-version) || '' }}${{ matrix.clerk-use-rq == 'true' && '-rq' || '' }}
444486
path: integration/test-results
445487
retention-days: 1
446488

integration/tests/machine-auth/component.test.ts

Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
1+
import type { Page } from '@playwright/test';
12
import { expect, test } from '@playwright/test';
23

34
import { appConfigs } from '../../presets';
45
import type { FakeOrganization, FakeUser } from '../../testUtils';
56
import { createTestUtils, testAgainstRunningApps } from '../../testUtils';
67

8+
const mockAPIKeysEnvironmentSettings = async (
9+
page: Page,
10+
overrides: Partial<{
11+
user_api_keys_enabled: boolean;
12+
orgs_api_keys_enabled: boolean;
13+
}>,
14+
) => {
15+
await page.route('*/**/v1/environment*', async route => {
16+
const response = await route.fetch();
17+
const json = await response.json();
18+
const newJson = {
19+
...json,
20+
api_keys_settings: {
21+
user_api_keys_enabled: true,
22+
orgs_api_keys_enabled: true,
23+
...overrides,
24+
},
25+
};
26+
await route.fulfill({ response, json: newJson });
27+
});
28+
};
29+
730
testAgainstRunningApps({
831
withEnv: [appConfigs.envs.withAPIKeys],
932
withPattern: ['withMachine.next.appRouter'],
@@ -214,81 +237,111 @@ testAgainstRunningApps({
214237
expect(clipboardText).toBe(secret);
215238
});
216239

217-
test('component does not render for orgs when user does not have permissions', async ({ page, context }) => {
240+
test('UserProfile API keys page visibility', async ({ page, context }) => {
218241
const u = createTestUtils({ app, page, context });
219242

220-
const fakeMember = u.services.users.createFakeUser();
221-
const member = await u.services.users.createBapiUser(fakeMember);
243+
await u.po.signIn.goTo();
244+
await u.po.signIn.waitForMounted();
245+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password });
246+
await u.po.expect.toBeSignedIn();
222247

223-
await u.services.clerk.organizations.createOrganizationMembership({
224-
organizationId: fakeOrganization.organization.id,
225-
role: 'org:member',
226-
userId: member.id,
227-
});
248+
// user_api_keys_enabled: false should hide API keys page
249+
await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: false });
250+
await u.po.page.goToRelative('/user');
251+
await u.po.userProfile.waitForMounted();
252+
await u.po.page.goToRelative('/user#/api-keys');
253+
await expect(u.page.locator('.cl-apiKeys')).toBeHidden({ timeout: 2000 });
254+
255+
// user_api_keys_enabled: true should show API keys page
256+
await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: true });
257+
await page.reload();
258+
await u.po.userProfile.waitForMounted();
259+
await u.po.page.goToRelative('/user#/api-keys');
260+
await expect(u.page.locator('.cl-apiKeys')).toBeVisible({ timeout: 5000 });
261+
262+
await u.page.unrouteAll();
263+
});
264+
265+
test('OrganizationProfile API keys page visibility', async ({ page, context }) => {
266+
const u = createTestUtils({ app, page, context });
267+
268+
await u.po.signIn.goTo();
269+
await u.po.signIn.waitForMounted();
270+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password });
271+
await u.po.expect.toBeSignedIn();
272+
273+
// orgs_api_keys_enabled: false should hide API keys page
274+
await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: false });
275+
await u.po.page.goToRelative('/organization-profile');
276+
await u.po.page.goToRelative('/organization-profile#/organization-api-keys');
277+
await expect(u.page.locator('.cl-apiKeys')).toBeHidden({ timeout: 2000 });
278+
279+
// orgs_api_keys_enabled: true should show API keys page
280+
await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: true });
281+
await page.reload();
282+
await u.po.page.goToRelative('/organization-profile#/organization-api-keys');
283+
await expect(u.page.locator('.cl-apiKeys')).toBeVisible({ timeout: 5000 });
284+
285+
await u.page.unrouteAll();
286+
});
287+
288+
test('standalone API keys component in user context based on user_api_keys_enabled', async ({ page, context }) => {
289+
const u = createTestUtils({ app, page, context });
228290

229291
await u.po.signIn.goTo();
230292
await u.po.signIn.waitForMounted();
231-
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeMember.email, password: fakeMember.password });
293+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password });
232294
await u.po.expect.toBeSignedIn();
233295

296+
// user_api_keys_enabled: false should prevent standalone component from rendering
297+
await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: false });
298+
234299
let apiKeysRequestWasMade = false;
235-
u.page.on('request', request => {
236-
if (request.url().includes('/api_keys')) {
237-
apiKeysRequestWasMade = true;
238-
}
300+
await u.page.route('**/api_keys*', async route => {
301+
apiKeysRequestWasMade = true;
302+
await route.abort();
239303
});
240304

241-
// Check that standalone component is not rendered
242305
await u.po.page.goToRelative('/api-keys');
243306
await expect(u.page.locator('.cl-apiKeys-root')).toBeHidden({ timeout: 1000 });
244-
245-
// Check that page is not rendered in OrganizationProfile
246-
await u.po.page.goToRelative('/organization-profile#/organization-api-keys');
247-
await expect(u.page.locator('.cl-apiKeys-root')).toBeHidden({ timeout: 1000 });
248-
249307
expect(apiKeysRequestWasMade).toBe(false);
250308

251-
await fakeMember.deleteIfExists();
309+
// user_api_keys_enabled: true should allow standalone component to render
310+
await mockAPIKeysEnvironmentSettings(u.page, { user_api_keys_enabled: true });
311+
await page.reload();
312+
await u.po.apiKeys.waitForMounted();
313+
await expect(u.page.locator('.cl-apiKeys-root')).toBeVisible();
314+
315+
await u.page.unrouteAll();
252316
});
253317

254-
test('user with read permission can view API keys but not manage them', async ({ page, context }) => {
318+
test('standalone API keys component in org context based on orgs_api_keys_enabled', async ({ page, context }) => {
255319
const u = createTestUtils({ app, page, context });
256320

257-
const fakeViewer = u.services.users.createFakeUser();
258-
const viewer = await u.services.users.createBapiUser(fakeViewer);
259-
260-
await u.services.clerk.organizations.createOrganizationMembership({
261-
organizationId: fakeOrganization.organization.id,
262-
role: 'org:viewer',
263-
userId: viewer.id,
264-
});
265-
266321
await u.po.signIn.goTo();
267322
await u.po.signIn.waitForMounted();
268-
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeViewer.email, password: fakeViewer.password });
323+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeAdmin.email, password: fakeAdmin.password });
269324
await u.po.expect.toBeSignedIn();
270325

326+
// orgs_api_keys_enabled: false should prevent standalone component from rendering in org context
327+
await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: false });
328+
271329
let apiKeysRequestWasMade = false;
272-
u.page.on('request', request => {
273-
if (request.url().includes('/api_keys')) {
274-
apiKeysRequestWasMade = true;
275-
}
330+
await u.page.route('**/api_keys*', async route => {
331+
apiKeysRequestWasMade = true;
332+
await route.abort();
276333
});
277334

278-
// Check that standalone component is rendered and user can read API keys
279335
await u.po.page.goToRelative('/api-keys');
280-
await u.po.apiKeys.waitForMounted();
281-
await expect(u.page.getByRole('button', { name: /Add new key/i })).toBeHidden();
282-
await expect(u.page.getByRole('columnheader', { name: /Actions/i })).toBeHidden();
283-
284-
// Check that page is rendered in OrganizationProfile and user can read API keys
285-
await u.po.page.goToRelative('/organization-profile#/organization-api-keys');
286-
await expect(u.page.locator('.cl-apiKeys')).toBeVisible();
287-
await expect(u.page.getByRole('button', { name: /Add new key/i })).toBeHidden();
288-
await expect(u.page.getByRole('columnheader', { name: /Actions/i })).toBeHidden();
336+
await expect(u.page.locator('.cl-apiKeys-root')).toBeHidden({ timeout: 1000 });
337+
expect(apiKeysRequestWasMade).toBe(false);
289338

290-
expect(apiKeysRequestWasMade).toBe(true);
339+
// orgs_api_keys_enabled: true should allow standalone component to render in org context
340+
await mockAPIKeysEnvironmentSettings(u.page, { orgs_api_keys_enabled: true });
341+
await page.reload();
342+
await u.po.apiKeys.waitForMounted();
343+
await expect(u.page.locator('.cl-apiKeys-root')).toBeVisible();
291344

292-
await fakeViewer.deleteIfExists();
345+
await u.page.unrouteAll();
293346
});
294347
});

0 commit comments

Comments
 (0)