From a97a830eae82e54d0d4217f74ff046e9e7ad145e Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Tue, 19 Nov 2024 08:26:02 -0800 Subject: [PATCH 01/15] test(nuxt, e2e): Create long-running app for Nuxt (#4600) --- .changeset/wet-dryers-pay.md | 2 + .github/workflows/ci.yml | 2 +- integration/presets/longRunningApps.ts | 2 + integration/presets/nuxt.ts | 18 ++++ integration/templates/index.ts | 1 + integration/templates/nuxt-node/app.vue | 3 + .../nuxt-node/middleware/auth.global.js | 16 +++ .../templates/nuxt-node/nuxt.config.js | 15 +++ integration/templates/nuxt-node/package.json | 17 +++ .../templates/nuxt-node/pages/index.vue | 21 ++++ .../templates/nuxt-node/pages/only-admin.vue | 8 ++ .../templates/nuxt-node/pages/sign-in.vue | 3 + .../templates/nuxt-node/pages/user.vue | 11 ++ .../templates/nuxt-node/server/api/me.js | 16 +++ integration/tests/nuxt/basic.test.ts | 101 ++++++++++++++++++ package.json | 1 + turbo.json | 6 ++ 17 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 .changeset/wet-dryers-pay.md create mode 100644 integration/presets/nuxt.ts create mode 100644 integration/templates/nuxt-node/app.vue create mode 100644 integration/templates/nuxt-node/middleware/auth.global.js create mode 100644 integration/templates/nuxt-node/nuxt.config.js create mode 100644 integration/templates/nuxt-node/package.json create mode 100644 integration/templates/nuxt-node/pages/index.vue create mode 100644 integration/templates/nuxt-node/pages/only-admin.vue create mode 100644 integration/templates/nuxt-node/pages/sign-in.vue create mode 100644 integration/templates/nuxt-node/pages/user.vue create mode 100644 integration/templates/nuxt-node/server/api/me.js create mode 100644 integration/tests/nuxt/basic.test.ts diff --git a/.changeset/wet-dryers-pay.md b/.changeset/wet-dryers-pay.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/wet-dryers-pay.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38c795e363..910af8f0c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,7 @@ jobs: strategy: fail-fast: false matrix: - test-name: [ 'generic', 'express', 'quickstart', 'ap-flows', 'elements', 'sessions', 'astro', 'expo-web', 'tanstack-start', 'tanstack-router', 'vue'] + test-name: [ 'generic', 'express', 'quickstart', 'ap-flows', 'elements', 'sessions', 'astro', 'expo-web', 'tanstack-start', 'tanstack-router', 'vue', 'nuxt'] test-project: ['chrome'] include: - test-name: 'nextjs' diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts index f789bdf0e1..3c310184f4 100644 --- a/integration/presets/longRunningApps.ts +++ b/integration/presets/longRunningApps.ts @@ -6,6 +6,7 @@ import { envs } from './envs'; import { expo } from './expo'; import { express } from './express'; import { next } from './next'; +import { nuxt } from './nuxt'; import { react } from './react'; import { remix } from './remix'; import { tanstack } from './tanstack'; @@ -39,6 +40,7 @@ export const createLongRunningApps = () => { { id: 'tanstack.start', config: tanstack.start, env: envs.withEmailCodes }, { id: 'tanstack.router', config: tanstack.router, env: envs.withEmailCodes }, { id: 'vue.vite', config: vue.vite, env: envs.withCustomRoles }, + { id: 'nuxt.node', config: nuxt.node, env: envs.withCustomRoles }, ] as const; const apps = configs.map(longRunningApplication); diff --git a/integration/presets/nuxt.ts b/integration/presets/nuxt.ts new file mode 100644 index 0000000000..97fe3ef377 --- /dev/null +++ b/integration/presets/nuxt.ts @@ -0,0 +1,18 @@ +import { applicationConfig } from '../models/applicationConfig'; +import { templates } from '../templates'; + +const clerkNuxtLocal = `file:${process.cwd()}/packages/nuxt`; + +const nuxtNode = applicationConfig() + .setName('nuxt-node') + .useTemplate(templates['nuxt-node']) + .setEnvFormatter('public', key => `NUXT_PUBLIC_${key}`) + .addScript('setup', 'pnpm install') + .addScript('dev', 'pnpm dev') + .addScript('build', 'pnpm build') + .addScript('serve', 'pnpm preview') + .addDependency('@clerk/nuxt', clerkNuxtLocal); + +export const nuxt = { + node: nuxtNode, +} as const; diff --git a/integration/templates/index.ts b/integration/templates/index.ts index 9d9b1c84ef..41286f1a00 100644 --- a/integration/templates/index.ts +++ b/integration/templates/index.ts @@ -17,6 +17,7 @@ export const templates = { 'tanstack-start': resolve(__dirname, './tanstack-start'), 'tanstack-router': resolve(__dirname, './tanstack-router'), 'vue-vite': resolve(__dirname, './vue-vite'), + 'nuxt-node': resolve(__dirname, './nuxt-node'), } as const; if (new Set([...Object.values(templates)]).size !== Object.values(templates).length) { diff --git a/integration/templates/nuxt-node/app.vue b/integration/templates/nuxt-node/app.vue new file mode 100644 index 0000000000..8f62b8bf92 --- /dev/null +++ b/integration/templates/nuxt-node/app.vue @@ -0,0 +1,3 @@ + diff --git a/integration/templates/nuxt-node/middleware/auth.global.js b/integration/templates/nuxt-node/middleware/auth.global.js new file mode 100644 index 0000000000..261cb30a3f --- /dev/null +++ b/integration/templates/nuxt-node/middleware/auth.global.js @@ -0,0 +1,16 @@ +export default defineNuxtRouteMiddleware((to, from) => { + const { userId } = useAuth(); + + const isPublicPage = ['/sign-in'].includes(to.path); + const isProtectedPage = ['/user'].includes(to.path); + + // Is authenticated and trying to access a public page + if (userId.value && isPublicPage) { + return navigateTo('/user'); + } + + // Is not authenticated and trying to access a protected page + if (!userId.value && isProtectedPage) { + return navigateTo('/sign-in'); + } +}); diff --git a/integration/templates/nuxt-node/nuxt.config.js b/integration/templates/nuxt-node/nuxt.config.js new file mode 100644 index 0000000000..ea7c01f015 --- /dev/null +++ b/integration/templates/nuxt-node/nuxt.config.js @@ -0,0 +1,15 @@ +export default defineNuxtConfig({ + modules: ['@clerk/nuxt'], + // The code below is only necessary in our test environment. + // Env vars are automatically read by Nuxt. See https://nuxt.com/docs/guide/going-further/runtime-config + runtimeConfig: { + public: { + clerk: { + publishableKey: process.env.NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY, + }, + }, + clerk: { + secretKey: process.env.CLERK_SECRET_KEY, + }, + }, +}); diff --git a/integration/templates/nuxt-node/package.json b/integration/templates/nuxt-node/package.json new file mode 100644 index 0000000000..7414598924 --- /dev/null +++ b/integration/templates/nuxt-node/package.json @@ -0,0 +1,17 @@ +{ + "name": "nuxt-clerk-integration-template", + "private": true, + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev --port $PORT", + "generate": "nuxt generate", + "postinstall": "nuxt prepare", + "preview": "nuxt preview --port $PORT" + }, + "dependencies": { + "nuxt": "^3.14.159", + "vue": "^3.5.12", + "vue-router": "^4.4.5" + } +} diff --git a/integration/templates/nuxt-node/pages/index.vue b/integration/templates/nuxt-node/pages/index.vue new file mode 100644 index 0000000000..cf760d2393 --- /dev/null +++ b/integration/templates/nuxt-node/pages/index.vue @@ -0,0 +1,21 @@ + diff --git a/integration/templates/nuxt-node/pages/only-admin.vue b/integration/templates/nuxt-node/pages/only-admin.vue new file mode 100644 index 0000000000..aa19342a3f --- /dev/null +++ b/integration/templates/nuxt-node/pages/only-admin.vue @@ -0,0 +1,8 @@ + diff --git a/integration/templates/nuxt-node/pages/sign-in.vue b/integration/templates/nuxt-node/pages/sign-in.vue new file mode 100644 index 0000000000..b925853312 --- /dev/null +++ b/integration/templates/nuxt-node/pages/sign-in.vue @@ -0,0 +1,3 @@ + diff --git a/integration/templates/nuxt-node/pages/user.vue b/integration/templates/nuxt-node/pages/user.vue new file mode 100644 index 0000000000..bc5be9d7d6 --- /dev/null +++ b/integration/templates/nuxt-node/pages/user.vue @@ -0,0 +1,11 @@ + + + diff --git a/integration/templates/nuxt-node/server/api/me.js b/integration/templates/nuxt-node/server/api/me.js new file mode 100644 index 0000000000..3534231821 --- /dev/null +++ b/integration/templates/nuxt-node/server/api/me.js @@ -0,0 +1,16 @@ +import { clerkClient } from '@clerk/nuxt/server'; + +export default eventHandler(async event => { + const { userId } = event.context.auth; + + if (!userId) { + throw createError({ + statusCode: 401, + statusMessage: 'Unauthorized', + }); + } + + const user = await clerkClient(event).users.getUser(userId); + + return user; +}); diff --git a/integration/tests/nuxt/basic.test.ts b/integration/tests/nuxt/basic.test.ts new file mode 100644 index 0000000000..12bf61f114 --- /dev/null +++ b/integration/tests/nuxt/basic.test.ts @@ -0,0 +1,101 @@ +import { expect, test } from '@playwright/test'; + +import { appConfigs } from '../../presets'; +import type { FakeOrganization, FakeUser } from '../../testUtils'; +import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; + +testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic tests for @nuxt', ({ app }) => { + test.describe.configure({ mode: 'parallel' }); + + let fakeUser: FakeUser; + let fakeOrganization: FakeOrganization; + + test.beforeAll(async () => { + const u = createTestUtils({ app }); + fakeUser = u.services.users.createFakeUser(); + const user = await u.services.users.createBapiUser(fakeUser); + fakeOrganization = await u.services.users.createFakeOrganization(user.id); + }); + + test.afterAll(async () => { + await fakeOrganization.delete(); + await fakeUser.deleteIfExists(); + + await app.teardown(); + }); + + test.afterEach(async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.page.signOut(); + await u.page.context().clearCookies(); + }); + + test('sign in with hash routing', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + + await u.po.signIn.setIdentifier(fakeUser.email); + await u.po.signIn.continue(); + await u.page.waitForURL(`${app.serverUrl}/sign-in#/factor-one`); + + await u.po.signIn.setPassword(fakeUser.password); + await u.po.signIn.continue(); + await u.po.expect.toBeSignedIn(); + }); + + test('render user profile with SSR data', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + + await u.po.userButton.waitForMounted(); + await u.page.goToRelative('/user'); + await u.po.userProfile.waitForMounted(); + + // Fetched from an API endpoint (/api/me), which is server-rendered. + // This also verifies that the server middleware is working. + await expect(u.page.getByText(`First name: ${fakeUser.firstName}`)).toBeVisible(); + await expect(u.page.getByText(`Email: ${fakeUser.email}`)).toBeVisible(); + }); + + test('redirects to sign-in when unauthenticated', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/user'); + await u.page.waitForURL(`${app.serverUrl}/sign-in`); + await u.po.signIn.waitForMounted(); + }); + + test('renders control components contents in SSR', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToAppHome(); + await expect(u.page.getByText('You are signed out')).toBeVisible(); + + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + await expect(u.page.getByText('You are signed in!')).toBeVisible(); + }); + + test('renders component contents to admin', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + + await u.page.waitForAppUrl('/'); + await u.po.organizationSwitcher.waitForMounted(); + await u.po.organizationSwitcher.waitForAnOrganizationToSelected(); + await u.page.goToRelative('/only-admin'); + await expect(u.page.getByText('I am an admin')).toBeVisible(); + }); +}); diff --git a/package.json b/package.json index 6778850c63..f9ffde2aea 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "test:integration:express": "E2E_APP_ID=express.* pnpm test:integration:base --grep @express", "test:integration:generic": "E2E_APP_ID=react.vite.*,next.appRouter.withEmailCodes* pnpm test:integration:base --grep @generic", "test:integration:nextjs": "E2E_APP_ID=next.appRouter.* pnpm test:integration:base --grep @nextjs", + "test:integration:nuxt": "E2E_APP_ID=nuxt.node npm run test:integration:base -- --grep @nuxt", "test:integration:quickstart": "E2E_APP_ID=quickstart.* pnpm test:integration:base --grep @quickstart", "test:integration:remix": "echo 'placeholder'", "test:integration:sessions": "pnpm test:integration:base --grep @sessions", diff --git a/turbo.json b/turbo.json index 50413a4b4e..2b85a95469 100644 --- a/turbo.json +++ b/turbo.json @@ -218,6 +218,12 @@ "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" + }, + "//#test:integration:nuxt": { + "dependsOn": ["@clerk/clerk-js#build", "@clerk/vue#build", "@clerk/nuxt#build"], + "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], + "inputs": ["integration/**"], + "outputLogs": "new-only" } } } From bf713bcae00ebef88160c3ee57487f0ef012fa0f Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:18:05 -0800 Subject: [PATCH 02/15] feat(clerk-js): Add sandbox support for persistent component props (#4610) --- .changeset/rude-lions-know.md | 2 + packages/clerk-js/sandbox/app.js | 109 ++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 .changeset/rude-lions-know.md diff --git a/.changeset/rude-lions-know.md b/.changeset/rude-lions-know.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/rude-lions-know.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js index e991783d0d..8ed84bfbb9 100644 --- a/packages/clerk-js/sandbox/app.js +++ b/packages/clerk-js/sandbox/app.js @@ -2,9 +2,99 @@ /** @typedef {import('@clerk/clerk-js').Clerk} Clerk */ +/** + * @typedef {object} ComponentPropsControl + * @property {(props: unknown) => void} setProps + * @property {() => (any | null)} getProps + */ + +const AVAILABLE_COMPONENTS = /** @type {const} */ ([ + 'signIn', + 'signUp', + 'userButton', + 'userProfile', + 'createOrganization', + 'organizationList', + 'organizationProfile', + 'organizationSwitcher', + 'waitlist', +]); + +const COMPONENT_PROPS_NAMESPACE = 'clerk-js-sandbox'; + +const urlParams = new URL(window.location.href).searchParams; +for (const [component, encodedProps] of urlParams.entries()) { + if (AVAILABLE_COMPONENTS.includes(/** @type {typeof AVAILABLE_COMPONENTS[number]} */ (component))) { + localStorage.setItem(`${COMPONENT_PROPS_NAMESPACE}-${component}`, encodedProps); + } +} + +/** + * @param {typeof AVAILABLE_COMPONENTS[number]} component + * @param {unknown} props + */ +function setComponentProps(component, props) { + const encodedProps = JSON.stringify(props); + + const url = new URL(window.location.href); + url.searchParams.set(component, encodedProps); + + window.location.href = url.toString(); +} + +/** + * @param {typeof AVAILABLE_COMPONENTS[number]} component + * @returns {unknown | null} + */ +function getComponentProps(component) { + const url = new URL(window.location.href); + const encodedProps = url.searchParams.get(component); + if (encodedProps) { + return JSON.parse(encodedProps); + } + + const localEncodedProps = localStorage.getItem(`${COMPONENT_PROPS_NAMESPACE}-${component}`); + if (localEncodedProps) { + return JSON.parse(localEncodedProps); + } + + return null; +} + +/** + * @param {typeof AVAILABLE_COMPONENTS[number]} component + * @returns {ComponentPropsControl} + */ +function buildComponentControls(component) { + return { + setProps(props) { + setComponentProps(component, props); + }, + getProps() { + return getComponentProps(component); + }, + }; +} + +/** + * @type {Record} + */ +const componentControls = { + signIn: buildComponentControls('signIn'), + signUp: buildComponentControls('signUp'), + userButton: buildComponentControls('userButton'), + userProfile: buildComponentControls('userProfile'), + createOrganization: buildComponentControls('createOrganization'), + organizationList: buildComponentControls('organizationList'), + organizationProfile: buildComponentControls('organizationProfile'), + organizationSwitcher: buildComponentControls('organizationSwitcher'), + waitlist: buildComponentControls('waitlist'), +}; + /** * @typedef {object} CustomWindowObject * @property {Clerk} [Clerk] + * @property {Record} [components] */ /** @@ -13,6 +103,7 @@ /** @type {CustomWindow} */ const windowWithClerk = window; +windowWithClerk.components = componentControls; const Clerk = /** @type {Clerk} **/ (windowWithClerk.Clerk); if (!Clerk) { @@ -36,31 +127,31 @@ const routes = { mountIndex(app); }, '/sign-in': () => { - Clerk.mountSignIn(app, {}); + Clerk.mountSignIn(app, componentControls.signIn.getProps() ?? {}); }, '/sign-up': () => { - Clerk.mountSignUp(app, {}); + Clerk.mountSignUp(app, componentControls.signUp.getProps() ?? {}); }, '/user-button': () => { - Clerk.mountUserButton(app, {}); + Clerk.mountUserButton(app, componentControls.userButton.getProps() ?? {}); }, '/user-profile': () => { - Clerk.mountUserProfile(app, {}); + Clerk.mountUserProfile(app, componentControls.userProfile.getProps() ?? {}); }, '/create-organization': () => { - Clerk.mountCreateOrganization(app, {}); + Clerk.mountCreateOrganization(app, componentControls.createOrganization.getProps() ?? {}); }, '/organization-list': () => { - Clerk.mountOrganizationList(app, {}); + Clerk.mountOrganizationList(app, componentControls.organizationList.getProps() ?? {}); }, '/organization-profile': () => { - Clerk.mountOrganizationProfile(app, {}); + Clerk.mountOrganizationProfile(app, componentControls.organizationProfile.getProps() ?? {}); }, '/organization-switcher': () => { - Clerk.mountOrganizationSwitcher(app, {}); + Clerk.mountOrganizationSwitcher(app, componentControls.organizationSwitcher.getProps() ?? {}); }, '/waitlist': () => { - Clerk.mountWaitlist(app, {}); + Clerk.mountWaitlist(app, componentControls.waitlist.getProps() ?? {}); }, }; From dd237b0f51b3225230dd48547e1fdebffa4f06ce Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Wed, 20 Nov 2024 01:39:19 -0800 Subject: [PATCH 03/15] test(nuxt): Fix incorrect env vars (#4606) --- .changeset/shy-months-invite.md | 2 ++ integration/presets/nuxt.ts | 1 + integration/templates/nuxt-node/nuxt.config.js | 12 ------------ packages/nuxt/README.md | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) create mode 100644 .changeset/shy-months-invite.md diff --git a/.changeset/shy-months-invite.md b/.changeset/shy-months-invite.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/shy-months-invite.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/presets/nuxt.ts b/integration/presets/nuxt.ts index 97fe3ef377..2762d07c9c 100644 --- a/integration/presets/nuxt.ts +++ b/integration/presets/nuxt.ts @@ -7,6 +7,7 @@ const nuxtNode = applicationConfig() .setName('nuxt-node') .useTemplate(templates['nuxt-node']) .setEnvFormatter('public', key => `NUXT_PUBLIC_${key}`) + .setEnvFormatter('private', key => `NUXT_${key}`) .addScript('setup', 'pnpm install') .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') diff --git a/integration/templates/nuxt-node/nuxt.config.js b/integration/templates/nuxt-node/nuxt.config.js index ea7c01f015..e35608d38c 100644 --- a/integration/templates/nuxt-node/nuxt.config.js +++ b/integration/templates/nuxt-node/nuxt.config.js @@ -1,15 +1,3 @@ export default defineNuxtConfig({ modules: ['@clerk/nuxt'], - // The code below is only necessary in our test environment. - // Env vars are automatically read by Nuxt. See https://nuxt.com/docs/guide/going-further/runtime-config - runtimeConfig: { - public: { - clerk: { - publishableKey: process.env.NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY, - }, - }, - clerk: { - secretKey: process.env.CLERK_SECRET_KEY, - }, - }, }); diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md index ed490e3f34..467faa14aa 100644 --- a/packages/nuxt/README.md +++ b/packages/nuxt/README.md @@ -55,7 +55,7 @@ Make sure the following environment variables are set in a `.env` file in your N ``` NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY=[publishable-key] -CLERK_SECRET_KEY=[secret-key] +NUXT_CLERK_SECRET_KEY=[secret-key] ``` Then, add `@clerk/nuxt` to the `modules` section of `nuxt.config.ts`: From 725b88583007cecec62f38237d8c7ffe8a1106cb Mon Sep 17 00:00:00 2001 From: Alexandros Ntitoras <47026269+AlexNti@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:10:53 +0200 Subject: [PATCH 04/15] chore(clerk-expo-passkeys): fix breaking changes (#4601) --- .changeset/fifty-cameras-tease.md | 6 ++++++ packages/expo-passkeys/.eslintrc.js | 8 ++++++++ packages/expo-passkeys/src/utils.ts | 3 +-- packages/expo/package.json | 6 +++++- pnpm-lock.yaml | 6 +++--- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 .changeset/fifty-cameras-tease.md diff --git a/.changeset/fifty-cameras-tease.md b/.changeset/fifty-cameras-tease.md new file mode 100644 index 0000000000..b0c77f8f96 --- /dev/null +++ b/.changeset/fifty-cameras-tease.md @@ -0,0 +1,6 @@ +--- +'@clerk/expo-passkeys': patch +'@clerk/clerk-expo': patch +--- +- Replaced import { Buffer } from 'node:buffer' with import { Buffer } from 'buffer'. +- Moved @clerk/expo-passkeys to a devDependency in @clerk/clerk-expo. diff --git a/packages/expo-passkeys/.eslintrc.js b/packages/expo-passkeys/.eslintrc.js index 9c27d64800..0aab09b0b2 100644 --- a/packages/expo-passkeys/.eslintrc.js +++ b/packages/expo-passkeys/.eslintrc.js @@ -4,4 +4,12 @@ module.exports = { settings: { 'import/ignore': ['node_modules/react-native/index\\.js$'], }, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: ['node:*'], + }, + ], + }, }; diff --git a/packages/expo-passkeys/src/utils.ts b/packages/expo-passkeys/src/utils.ts index 0a7bfb5cee..c9dc88fe9e 100644 --- a/packages/expo-passkeys/src/utils.ts +++ b/packages/expo-passkeys/src/utils.ts @@ -1,6 +1,5 @@ -import { Buffer } from 'node:buffer'; - import { ClerkWebAuthnError } from '@clerk/shared/error'; +import { Buffer } from 'buffer'; export { ClerkWebAuthnError }; export function encodeBase64(data: ArrayLike | ArrayBufferLike) { diff --git a/packages/expo/package.json b/packages/expo/package.json index 782ca86349..2456ad046f 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -62,7 +62,6 @@ "dependencies": { "@clerk/clerk-js": "workspace:*", "@clerk/clerk-react": "workspace:*", - "@clerk/expo-passkeys": "workspace:*", "@clerk/shared": "workspace:*", "@clerk/types": "workspace:*", "base-64": "^1.0.0", @@ -71,6 +70,7 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", + "@clerk/expo-passkeys": "workspace:*", "@types/base-64": "^1.0.2", "@types/node": "^20.11.24", "@types/react": "18.3.12", @@ -83,6 +83,7 @@ "typescript": "*" }, "peerDependencies": { + "@clerk/expo-passkeys": ">=0.0.6", "expo-auth-session": ">=5", "expo-local-authentication": ">=13.5.0", "expo-secure-store": ">=12.4.0", @@ -92,6 +93,9 @@ "react-native": ">=0.73" }, "peerDependenciesMeta": { + "@clerk/expo-passkeys": { + "optional": true + }, "expo-local-authentication": { "optional": true }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e1799e9e37..1272255666 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -567,9 +567,6 @@ importers: '@clerk/clerk-react': specifier: workspace:* version: link:../react - '@clerk/expo-passkeys': - specifier: workspace:* - version: link:../expo-passkeys '@clerk/shared': specifier: workspace:* version: link:../shared @@ -595,6 +592,9 @@ importers: '@clerk/eslint-config-custom': specifier: workspace:* version: link:../eslint-config-custom + '@clerk/expo-passkeys': + specifier: workspace:* + version: link:../expo-passkeys '@types/base-64': specifier: ^1.0.2 version: 1.0.2 From 4f6924823f21eb9c6de8ec9be46f04d64df8f36d Mon Sep 17 00:00:00 2001 From: Danilo Campana Fuchs Date: Wed, 20 Nov 2024 09:56:32 -0300 Subject: [PATCH 05/15] chore(repo): Remove VSCode ZipFS extension recommendation (#4609) Co-authored-by: Lennart --- .vscode/extensions.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index daaa5ee2ec..7efca3f113 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,5 @@ { "recommendations": [ - "arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode" ] From ea9e6b428a976de0f06f1b73444e71c2cb0a1bdb Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Nov 2024 10:35:47 -0500 Subject: [PATCH 06/15] chore(e2e): Enable reverification.test.ts (#4613) --- .changeset/ten-hornets-deny.md | 2 ++ integration/tests/reverification.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .changeset/ten-hornets-deny.md diff --git a/.changeset/ten-hornets-deny.md b/.changeset/ten-hornets-deny.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/ten-hornets-deny.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/tests/reverification.test.ts b/integration/tests/reverification.test.ts index 31f94533b1..62054de82c 100644 --- a/integration/tests/reverification.test.ts +++ b/integration/tests/reverification.test.ts @@ -41,7 +41,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withReverification] })( }); utils.forEach(type => { - test.skip(`reverification error from ${capitalize(type)}`, async ({ page, context }) => { + test(`reverification error from ${capitalize(type)}`, async ({ page, context }) => { test.setTimeout(270_000); const u = createTestUtils({ app, page, context }); @@ -67,12 +67,12 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withReverification] })( await u.page.getByRole('button', { name: /LogUserId/i }).click(); await expect( u.page.getByText( - /\{\s*"clerk_error"\s*:\s*\{\s*"type"\s*:\s*"forbidden"\s*,\s*"reason"\s*:\s*"reverification-mismatch"\s*,\s*"metadata"\s*:\s*\{\s*"reverification"\s*:\s*\{\s*"level"\s*:\s*"secondFactor"\s*,\s*"afterMinutes"\s*:\s*1\s*\}\s*\}\s*\}\s*\}/i, + /\{\s*"clerk_error"\s*:\s*\{\s*"type"\s*:\s*"forbidden"\s*,\s*"reason"\s*:\s*"reverification-error"\s*,\s*"metadata"\s*:\s*\{\s*"reverification"\s*:\s*\{\s*"level"\s*:\s*"second_factor"\s*,\s*"afterMinutes"\s*:\s*1\s*\}\s*\}\s*\}\s*\}/i, ), ).toBeVisible(); }); - test.skip(`reverification recovery from ${capitalize(type)}`, async ({ page, context }) => { + test(`reverification recovery from ${capitalize(type)}`, async ({ page, context }) => { test.setTimeout(270_000); const u = createTestUtils({ app, page, context }); From 273d16cb0665d4d960838cb294dc356f41814745 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Wed, 20 Nov 2024 18:01:11 +0200 Subject: [PATCH 07/15] Fraud protection improvements (#4614) --- .changeset/red-chicken-visit.md | 6 + packages/clerk-js/src/core/fraudProtection.ts | 62 +++++++++ .../src/core/resources/DisplayConfig.ts | 2 + .../clerk-js/src/core/resources/Session.ts | 28 +--- packages/clerk-js/src/core/tokenCache.ts | 2 +- .../src/utils/captcha/getCaptchaToken.ts | 14 +- .../clerk-js/src/utils/captcha/hcaptcha.ts | 120 ------------------ .../src/utils/captcha/retrieveCaptchaInfo.ts | 2 +- packages/types/src/displayConfig.ts | 4 +- 9 files changed, 81 insertions(+), 159 deletions(-) create mode 100644 .changeset/red-chicken-visit.md delete mode 100644 packages/clerk-js/src/utils/captcha/hcaptcha.ts diff --git a/.changeset/red-chicken-visit.md b/.changeset/red-chicken-visit.md new file mode 100644 index 0000000000..4e87d960f1 --- /dev/null +++ b/.changeset/red-chicken-visit.md @@ -0,0 +1,6 @@ +--- +"@clerk/clerk-js": patch +"@clerk/types": patch +--- + +Inject captcha token into every X heartbeats diff --git a/packages/clerk-js/src/core/fraudProtection.ts b/packages/clerk-js/src/core/fraudProtection.ts index 173b72f02c..5ad24208c3 100644 --- a/packages/clerk-js/src/core/fraudProtection.ts +++ b/packages/clerk-js/src/core/fraudProtection.ts @@ -1,8 +1,13 @@ +import { getCaptchaToken, retrieveCaptchaInfo } from '../utils/captcha'; +import type { Clerk } from './resources/internal'; + /** * TODO: @nikos Move captcha and fraud detection logic to this class */ class FraudProtectionService { private inflightRequest: Promise | null = null; + private ticks = 0; + private readonly interval = 6; public async execute Promise>(cb: T): Promise>> { if (this.inflightRequest) { @@ -20,6 +25,63 @@ class FraudProtectionService { public blockUntilReady() { return this.inflightRequest ? this.inflightRequest.then(() => null) : Promise.resolve(); } + + public async challengeHeartbeat(clerk: Clerk) { + if (!clerk.__unstable__environment?.displayConfig.captchaHeartbeat || this.ticks++ % (this.interval - 1)) { + return undefined; + } + return this.invisibleChallenge(clerk); + } + + /** + * Triggers an invisible challenge. + * This will always use the non-interactive variant of the CAPTCHA challenge and will + * always use the fallback key. + */ + public async invisibleChallenge(clerk: Clerk) { + const { captchaSiteKey, canUseCaptcha, captchaURL, captchaPublicKeyInvisible } = retrieveCaptchaInfo(clerk); + + if (canUseCaptcha && captchaSiteKey && captchaURL && captchaPublicKeyInvisible) { + return getCaptchaToken({ + siteKey: captchaPublicKeyInvisible, + invisibleSiteKey: captchaPublicKeyInvisible, + widgetType: 'invisible', + scriptUrl: captchaURL, + captchaProvider: 'turnstile', + }); + } + + return undefined; + } + + /** + * Triggers a smart challenge if the user is required to solve a CAPTCHA. + * Depending on the environment settings, this will either trigger an + * invisible or smart (managed) CAPTCHA challenge. + * Managed challenged start as non-interactive and escalate to interactive if necessary. + * Important: For this to work at the moment, the instance needs to be using SMART protection + * as we need both keys (visible and invisible) to be present. + */ + public async managedChallenge(clerk: Clerk) { + const { captchaSiteKey, canUseCaptcha, captchaURL, captchaWidgetType, captchaProvider, captchaPublicKeyInvisible } = + retrieveCaptchaInfo(clerk); + + if (canUseCaptcha && captchaSiteKey && captchaURL && captchaPublicKeyInvisible) { + return getCaptchaToken({ + siteKey: captchaSiteKey, + widgetType: captchaWidgetType, + invisibleSiteKey: captchaPublicKeyInvisible, + scriptUrl: captchaURL, + captchaProvider, + modalWrapperQuerySelector: '#cl-modal-captcha-wrapper', + modalContainerQuerySelector: '#cl-modal-captcha-container', + openModal: () => clerk.__internal_openBlankCaptchaModal(), + closeModal: () => clerk.__internal_closeBlankCaptchaModal(), + }); + } + + return {}; + } } export const fraudProtection = new FraudProtectionService(); diff --git a/packages/clerk-js/src/core/resources/DisplayConfig.ts b/packages/clerk-js/src/core/resources/DisplayConfig.ts index f6b3390174..62fd568edc 100644 --- a/packages/clerk-js/src/core/resources/DisplayConfig.ts +++ b/packages/clerk-js/src/core/resources/DisplayConfig.ts @@ -26,6 +26,7 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource captchaProvider: CaptchaProvider = 'turnstile'; captchaPublicKeyInvisible: string | null = null; captchaOauthBypass: OAuthStrategy[] = []; + captchaHeartbeat: boolean = false; homeUrl!: string; instanceEnvironmentType!: string; faviconImageUrl!: string; @@ -83,6 +84,7 @@ export class DisplayConfig extends BaseResource implements DisplayConfigResource // These are the OAuth strategies we used to bypass the captcha for by default // before the introduction of the captcha_oauth_bypass field this.captchaOauthBypass = data.captcha_oauth_bypass || ['oauth_google', 'oauth_microsoft', 'oauth_apple']; + this.captchaHeartbeat = data.captcha_heartbeat || false; this.supportEmail = data.support_email || ''; this.clerkJSVersion = data.clerk_js_version; this.organizationProfileUrl = data.organization_profile_url; diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index f003334ea6..c4a30e7600 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -22,7 +22,6 @@ import type { UserResource, } from '@clerk/types'; -import { getCaptchaToken, retrieveCaptchaInfo } from '../../utils/captcha'; import { unixEpochToDate } from '../../utils/date'; import { clerkInvalidStrategy } from '../errors'; import { eventBus, events } from '../events'; @@ -273,13 +272,15 @@ export class Session extends BaseResource implements SessionResource { // TODO: update template endpoint to accept organizationId const params: Record = template ? {} : { organizationId }; + // this handles all getToken invocations with skipCache: true await fraudProtection.blockUntilReady(); const createTokenWithCaptchaProtection = async () => { - return Token.create(path, params).catch(e => { + const heartbeatParams = skipCache ? undefined : await fraudProtection.challengeHeartbeat(Session.clerk); + return Token.create(path, { ...params, ...heartbeatParams }).catch(e => { if (isClerkAPIResponseError(e) && e.errors[0].code === 'requires_captcha') { return fraudProtection.execute(async () => { - const captchaParams = await this.#triggerCaptchaChallenge(); + const captchaParams = await fraudProtection.managedChallenge(Session.clerk); return Token.create(path, { ...params, ...captchaParams }); }); } @@ -298,25 +299,4 @@ export class Session extends BaseResource implements SessionResource { return token.getRawString() || null; }); } - - async #triggerCaptchaChallenge() { - const { captchaSiteKey, canUseCaptcha, captchaURL, captchaWidgetType, captchaProvider, captchaPublicKeyInvisible } = - retrieveCaptchaInfo(Session.clerk); - - if (canUseCaptcha && captchaSiteKey && captchaURL && captchaPublicKeyInvisible) { - return getCaptchaToken({ - siteKey: captchaSiteKey, - widgetType: captchaWidgetType, - invisibleSiteKey: captchaPublicKeyInvisible, - scriptUrl: captchaURL, - captchaProvider, - modalWrapperQuerySelector: '#cl-modal-captcha-wrapper', - modalContainerQuerySelector: '#cl-modal-captcha-container', - openModal: () => Session.clerk.__internal_openBlankCaptchaModal(), - closeModal: () => Session.clerk.__internal_closeBlankCaptchaModal(), - }); - } - - return {}; - } } diff --git a/packages/clerk-js/src/core/tokenCache.ts b/packages/clerk-js/src/core/tokenCache.ts index 102798425e..c5e74be3da 100644 --- a/packages/clerk-js/src/core/tokenCache.ts +++ b/packages/clerk-js/src/core/tokenCache.ts @@ -26,7 +26,7 @@ interface TokenCache { const KEY_PREFIX = 'clerk'; const DELIMITER = '::'; -const LEEWAY = 10; +const LEEWAY = 11; // This value should have the same value as the INTERVAL_IN_MS in SessionCookiePoller const SYNC_LEEWAY = 5; diff --git a/packages/clerk-js/src/utils/captcha/getCaptchaToken.ts b/packages/clerk-js/src/utils/captcha/getCaptchaToken.ts index 9003d1ea5a..68058d407c 100644 --- a/packages/clerk-js/src/utils/captcha/getCaptchaToken.ts +++ b/packages/clerk-js/src/utils/captcha/getCaptchaToken.ts @@ -1,14 +1,4 @@ -import { getHCaptchaToken } from './hcaptcha'; import { getTurnstileToken } from './turnstile'; -import type { CaptchaOptions, GetCaptchaTokenReturn } from './types'; +import type { CaptchaOptions } from './types'; -/* - * This is a temporary solution to test different captcha providers, until we decide on a single one. - */ -export const getCaptchaToken = (opts: CaptchaOptions): Promise => { - if (opts.captchaProvider === 'hcaptcha') { - return getHCaptchaToken(opts); - } else { - return getTurnstileToken(opts); - } -}; +export const getCaptchaToken = (opts: CaptchaOptions) => getTurnstileToken(opts); diff --git a/packages/clerk-js/src/utils/captcha/hcaptcha.ts b/packages/clerk-js/src/utils/captcha/hcaptcha.ts deleted file mode 100644 index 8f74f2d66c..0000000000 --- a/packages/clerk-js/src/utils/captcha/hcaptcha.ts +++ /dev/null @@ -1,120 +0,0 @@ -/// - -import { loadScript } from '@clerk/shared/loadScript'; -import type { CaptchaWidgetType } from '@clerk/types'; - -import { CAPTCHA_ELEMENT_ID, CAPTCHA_INVISIBLE_CLASSNAME } from './constants'; -import type { CaptchaOptions } from './types'; - -async function loadCaptcha(url: string) { - if (!window.hcaptcha) { - try { - await loadScript(url, { defer: true }); - } catch { - // Rethrow with specific message - console.error('Clerk: Failed to load the CAPTCHA script from the URL: ', url); - throw { - captchaError: 'captcha_script_failed_to_load', - }; - } - } - return window.hcaptcha; -} - -export const getHCaptchaToken = async (captchaOptions: CaptchaOptions) => { - const { siteKey, scriptUrl, widgetType, invisibleSiteKey } = captchaOptions; - let captchaToken = '', - id = ''; - let isInvisibleWidget = !widgetType || widgetType === 'invisible'; - let hCaptchaSiteKey = siteKey; - - let widgetDiv: HTMLElement | null = null; - - const createInvisibleDOMElement = () => { - const div = document.createElement('div'); - div.id = CAPTCHA_INVISIBLE_CLASSNAME; - document.body.appendChild(div); - return div; - }; - - const captcha: HCaptcha = await loadCaptcha(scriptUrl); - let retries = 0; - const errorCodes: (string | number)[] = []; - - const handleCaptchaTokenGeneration = (): Promise<[string, string]> => { - return new Promise((resolve, reject) => { - try { - if (isInvisibleWidget) { - widgetDiv = createInvisibleDOMElement(); - } else { - const visibleDiv = document.getElementById(CAPTCHA_ELEMENT_ID); - if (visibleDiv) { - visibleDiv.style.display = 'block'; - widgetDiv = visibleDiv; - } else { - console.error( - 'Cannot initialize Smart CAPTCHA widget because the `clerk-captcha` DOM element was not found; falling back to Invisible CAPTCHA widget. If you are using a custom flow, visit https://clerk.com/docs/custom-flows/bot-sign-up-protection for instructions', - ); - widgetDiv = createInvisibleDOMElement(); - isInvisibleWidget = true; - hCaptchaSiteKey = invisibleSiteKey; - } - } - - const id = captcha.render(isInvisibleWidget ? CAPTCHA_INVISIBLE_CLASSNAME : CAPTCHA_ELEMENT_ID, { - sitekey: hCaptchaSiteKey, - size: isInvisibleWidget ? 'invisible' : 'normal', - callback: function (token: string) { - resolve([token, id]); - }, - 'error-callback': function (errorCode) { - errorCodes.push(errorCode); - if (retries < 2) { - setTimeout(() => { - captcha.reset(id); - retries++; - }, 250); - return; - } - reject([errorCodes.join(','), id]); - }, - }); - - if (isInvisibleWidget) { - captcha.execute(id); - } - } catch (e) { - /** - * There is a case the captcha may fail before the challenge has started. - * In such case the 'error-callback' does not fire. - * We should mark the promise as rejected. - */ - reject([e, undefined]); - } - }); - }; - - try { - [captchaToken, id] = await handleCaptchaTokenGeneration(); - // After a successful challenge remove it - captcha.remove(id); - } catch ([e, id]) { - if (id) { - // After a failed challenge remove it - captcha.remove(id); - } - throw { - captchaError: e, - }; - } finally { - if (widgetDiv) { - if (isInvisibleWidget) { - document.body.removeChild(widgetDiv as HTMLElement); - } else { - (widgetDiv as HTMLElement).style.display = 'none'; - } - } - } - - return { captchaToken, captchaWidgetType: (isInvisibleWidget ? 'invisible' : 'smart') as CaptchaWidgetType }; -}; diff --git a/packages/clerk-js/src/utils/captcha/retrieveCaptchaInfo.ts b/packages/clerk-js/src/utils/captcha/retrieveCaptchaInfo.ts index 2d15bdea6b..2cf78c84db 100644 --- a/packages/clerk-js/src/utils/captcha/retrieveCaptchaInfo.ts +++ b/packages/clerk-js/src/utils/captcha/retrieveCaptchaInfo.ts @@ -18,7 +18,7 @@ export const retrieveCaptchaInfo = (clerk: Clerk) => { : null, captchaURL: fapiClient .buildUrl({ - path: captchaProvider == 'hcaptcha' ? 'hcaptcha/1/api.js' : 'cloudflare/turnstile/v0/api.js', + path: 'cloudflare/turnstile/v0/api.js', pathPrefix: '', search: '?render=explicit', }) diff --git a/packages/types/src/displayConfig.ts b/packages/types/src/displayConfig.ts index f10a8dbb9d..2eab780801 100644 --- a/packages/types/src/displayConfig.ts +++ b/packages/types/src/displayConfig.ts @@ -4,7 +4,7 @@ import type { OAuthStrategy } from './strategies'; export type PreferredSignInStrategy = 'password' | 'otp'; export type CaptchaWidgetType = 'smart' | 'invisible' | null; -export type CaptchaProvider = 'hcaptcha' | 'turnstile'; +export type CaptchaProvider = 'turnstile'; export interface DisplayConfigJSON { object: 'display_config'; @@ -21,6 +21,7 @@ export interface DisplayConfigJSON { captcha_public_key_invisible: string | null; captcha_provider: CaptchaProvider; captcha_oauth_bypass: OAuthStrategy[] | null; + captcha_heartbeat?: boolean; home_url: string; instance_environment_type: string; logo_image_url: string; @@ -64,6 +65,7 @@ export interface DisplayConfigResource extends ClerkResource { * This can also be used to bypass the captcha for a specific OAuth provider on a per-instance basis. */ captchaOauthBypass: OAuthStrategy[]; + captchaHeartbeat: boolean; homeUrl: string; instanceEnvironmentType: string; logoImageUrl: string; From 6b0961765e1f3d09679be4b163fa13ac7dd97191 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Wed, 20 Nov 2024 11:50:00 -0500 Subject: [PATCH 08/15] chore(shared): Update signature of `useReverification` for error handling configuration (#4564) --- .changeset/silent-bears-worry.md | 5 + .../UserProfile/AddAuthenticatorApp.tsx | 7 +- .../UserProfile/ConnectedAccountsMenu.tsx | 6 +- .../UserProfile/ConnectedAccountsSection.tsx | 12 +- .../components/UserProfile/DeleteUserForm.tsx | 8 +- .../ui/components/UserProfile/EmailForm.tsx | 7 +- .../UserProfile/MfaBackupCodeCreateForm.tsx | 7 +- .../components/UserProfile/PasskeySection.tsx | 7 +- .../components/UserProfile/PasswordForm.tsx | 25 ++-- .../ui/components/UserProfile/PhoneForm.tsx | 13 +- .../components/UserProfile/UsernameForm.tsx | 7 +- packages/shared/src/authorization.ts | 12 +- .../react/__tests__/useReverification.test.ts | 61 +++++++++ .../src/react/hooks/useReverification.ts | 119 +++++++++++++----- 14 files changed, 189 insertions(+), 107 deletions(-) create mode 100644 .changeset/silent-bears-worry.md create mode 100644 packages/shared/src/react/__tests__/useReverification.test.ts diff --git a/.changeset/silent-bears-worry.md b/.changeset/silent-bears-worry.md new file mode 100644 index 0000000000..9af5406880 --- /dev/null +++ b/.changeset/silent-bears-worry.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': minor +--- + +Change `useReverification` to handle error in a callback, but still allow an error to be thrown via options. diff --git a/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx b/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx index 997916cee4..3393e31e99 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/AddAuthenticatorApp.tsx @@ -28,12 +28,7 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato const { title, onSuccess, onReset } = props; const { user } = useUser(); const card = useCardState(); - const [createTOTP] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - return user.createTOTP(); - }); + const [createTOTP] = useReverification(() => user?.createTOTP()); const { close } = useActionContext(); const [totp, setTOTP] = React.useState(undefined); const [displayFormat, setDisplayFormat] = React.useState('qr'); diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx index fbb2aab974..9845cf6bf1 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsMenu.tsx @@ -20,17 +20,13 @@ const ConnectMenuButton = (props: { strategy: OAuthStrategy }) => { const isModal = mode === 'modal'; const [createExternalAccount] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - const socialProvider = strategy.replace('oauth_', '') as OAuthProvider; const redirectUrl = isModal ? appendModalState({ url: window.location.href, componentName, socialProvider: socialProvider }) : window.location.href; const additionalScopes = additionalOAuthScopes ? additionalOAuthScopes[socialProvider] : []; - return user.createExternalAccount({ + return user?.createExternalAccount({ strategy, redirectUrl, additionalScopes, diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx index 75335459de..ab61bcd687 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx @@ -98,17 +98,13 @@ const ConnectedAccount = ({ account }: { account: ExternalAccountResource }) => }) : window.location.href; - const [createExternalAccount] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - - return user.createExternalAccount({ + const [createExternalAccount] = useReverification(() => + user?.createExternalAccount({ strategy: account.verification!.strategy as OAuthStrategy, redirectUrl, additionalScopes, - }); - }); + }), + ); if (!user) { return null; diff --git a/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx index 25fc299919..edbe4b8244 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/DeleteUserForm.tsx @@ -16,13 +16,7 @@ export const DeleteUserForm = withCardStateProvider((props: DeleteUserFormProps) const { t } = useLocalizations(); const { otherSessions } = useMultipleSessions({ user }); const { setActive } = useClerk(); - const [deleteUserWithReverification] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - - return user.delete(); - }); + const [deleteUserWithReverification] = useReverification(() => user?.delete()); const confirmationField = useFormControl('deleteConfirmation', '', { type: 'text', diff --git a/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx index deeb86b8da..4c7e0bf44e 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/EmailForm.tsx @@ -22,12 +22,7 @@ export const EmailForm = withCardStateProvider((props: EmailFormProps) => { const environment = useEnvironment(); const preferEmailLinks = emailLinksEnabledForInstance(environment); - const [createEmailAddress] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - return user.createEmailAddress({ email: emailField.value }); - }); + const [createEmailAddress] = useReverification(() => user?.createEmailAddress({ email: emailField.value })); const emailAddressRef = React.useRef(user?.emailAddresses.find(a => a.id === id)); const wizard = useWizard({ diff --git a/packages/clerk-js/src/ui/components/UserProfile/MfaBackupCodeCreateForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/MfaBackupCodeCreateForm.tsx index 0dd61ccb35..4f9cac0c06 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/MfaBackupCodeCreateForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/MfaBackupCodeCreateForm.tsx @@ -19,12 +19,7 @@ export const MfaBackupCodeCreateForm = withCardStateProvider((props: MfaBackupCo const { onSuccess } = props; const { user } = useUser(); const card = useCardState(); - const [createBackupCode] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - return user.createBackupCode(); - }); + const [createBackupCode] = useReverification(() => user?.createBackupCode()); const [backupCode, setBackupCode] = React.useState(undefined); React.useEffect(() => { diff --git a/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx b/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx index a01eb98e0e..4059a396a1 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/PasskeySection.tsx @@ -191,12 +191,7 @@ const AddPasskeyButton = () => { const card = useCardState(); const { isSatellite } = useClerk(); const { user } = useUser(); - const [createPasskey] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - return user.createPasskey(); - }); + const [createPasskey] = useReverification(() => user?.createPasskey()); const handleCreatePasskey = async () => { if (!user) { diff --git a/packages/clerk-js/src/ui/components/UserProfile/PasswordForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/PasswordForm.tsx index 07f6fb7e2a..8b9b22a5fc 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/PasswordForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/PasswordForm.tsx @@ -1,4 +1,5 @@ import { __experimental_useReverification as useReverification, useSession, useUser } from '@clerk/shared/react'; +import type { UserResource } from '@clerk/types'; import { useRef } from 'react'; import { useEnvironment } from '../../contexts'; @@ -36,19 +37,9 @@ type PasswordFormProps = FormProps; export const PasswordForm = withCardStateProvider((props: PasswordFormProps) => { const { onSuccess, onReset } = props; const { user } = useUser(); - const [updatePasswordWithReverification] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - - const opts = { - newPassword: passwordField.value, - signOutOfOtherSessions: sessionsField.checked, - currentPassword: user.passwordEnabled ? currentPasswordField.value : undefined, - } satisfies Parameters[0]; - - return user.updatePassword(opts); - }); + const [updatePasswordWithReverification] = useReverification( + (user: UserResource, opts: Parameters) => user.updatePassword(...opts), + ); if (!user) { return null; @@ -124,7 +115,13 @@ export const PasswordForm = withCardStateProvider((props: PasswordFormProps) => text: generateSuccessPageText(user.passwordEnabled, !!sessionsField.checked), }; - await updatePasswordWithReverification(); + const opts = { + newPassword: passwordField.value, + signOutOfOtherSessions: sessionsField.checked, + currentPassword: user.passwordEnabled ? currentPasswordField.value : undefined, + } satisfies Parameters[0]; + + await updatePasswordWithReverification(user, [opts]); onSuccess(); } catch (e) { handleError(e, [currentPasswordField, passwordField, confirmField], card.setError); diff --git a/packages/clerk-js/src/ui/components/UserProfile/PhoneForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/PhoneForm.tsx index 0627e33daf..6b1baeb9ef 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/PhoneForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/PhoneForm.tsx @@ -1,5 +1,5 @@ import { __experimental_useReverification as useReverification, useUser } from '@clerk/shared/react'; -import type { PhoneNumberResource } from '@clerk/types'; +import type { PhoneNumberResource, UserResource } from '@clerk/types'; import React from 'react'; import { useWizard, Wizard } from '../../common'; @@ -49,12 +49,9 @@ export const AddPhone = (props: AddPhoneProps) => { const { title, onSuccess, onReset, onUseExistingNumberClick, resourceRef } = props; const card = useCardState(); const { user } = useUser(); - const [createPhoneNumber] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - return user.createPhoneNumber({ phoneNumber: phoneField.value }); - }); + const [createPhoneNumber] = useReverification( + (user: UserResource, opt: Parameters[0]) => user.createPhoneNumber(opt), + ); const phoneField = useFormControl('phoneNumber', '', { type: 'tel', @@ -70,7 +67,7 @@ export const AddPhone = (props: AddPhoneProps) => { if (!user) { return; } - return createPhoneNumber() + return createPhoneNumber(user, { phoneNumber: phoneField.value }) .then(res => { resourceRef.current = res; onSuccess(); diff --git a/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx b/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx index 44c0023b46..fc4876ded1 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UsernameForm.tsx @@ -12,12 +12,7 @@ export const UsernameForm = withCardStateProvider((props: UsernameFormProps) => const { onSuccess, onReset } = props; const { user } = useUser(); - const [updateUsername] = useReverification(() => { - if (!user) { - return Promise.resolve(undefined); - } - return user.update({ username: usernameField.value }); - }); + const [updateUsername] = useReverification(() => user?.update({ username: usernameField.value })); const { userSettings } = useEnvironment(); const card = useCardState(); diff --git a/packages/shared/src/authorization.ts b/packages/shared/src/authorization.ts index 888e73c6ae..489067bbfb 100644 --- a/packages/shared/src/authorization.ts +++ b/packages/shared/src/authorization.ts @@ -94,11 +94,11 @@ const validateReverificationConfig = (config: __experimental_ReverificationConfi return config; }; - if (typeof config === 'string' && isValidVerificationType(config)) { - return convertConfigToObject.bind(null, config); - } + const isValidStringValue = typeof config === 'string' && isValidVerificationType(config); + const isValidObjectValue = + typeof config === 'object' && isValidLevel(config.level) && isValidMaxAge(config.afterMinutes); - if (typeof config === 'object' && isValidLevel(config.level) && isValidMaxAge(config.afterMinutes)) { + if (isValidStringValue || isValidObjectValue) { return convertConfigToObject.bind(null, config); } @@ -145,7 +145,7 @@ const checkStepUpAuthorization: CheckStepUpAuthorization = (params, { __experime * The returned function authorizes if both checks pass, or if at least one passes * when the other is indeterminate. Fails if userId is missing. */ -export const createCheckAuthorization = (options: AuthorizationOptions): CheckAuthorizationWithCustomPermissions => { +const createCheckAuthorization = (options: AuthorizationOptions): CheckAuthorizationWithCustomPermissions => { return (params): boolean => { if (!options.userId) { return false; @@ -161,3 +161,5 @@ export const createCheckAuthorization = (options: AuthorizationOptions): CheckAu return [orgAuthorization, stepUpAuthorization].every(a => a === true); }; }; + +export { createCheckAuthorization, validateReverificationConfig }; diff --git a/packages/shared/src/react/__tests__/useReverification.test.ts b/packages/shared/src/react/__tests__/useReverification.test.ts new file mode 100644 index 0000000000..113ea3ee51 --- /dev/null +++ b/packages/shared/src/react/__tests__/useReverification.test.ts @@ -0,0 +1,61 @@ +import { expectTypeOf } from 'expect-type'; + +import { __experimental_reverificationError } from '../../authorization-errors'; +import type { __experimental_useReverification as useReverification } from '../hooks/useReverification'; + +type ExcludeClerkError = T extends { clerk_error: any } ? never : T; + +const fetcher = async (key: string, options: { id: string }) => { + return { + key, + options, + }; +}; + +const fetcherWithHelper = async (key: string, options: { id: string }) => { + if (key == 'a') { + return __experimental_reverificationError(); + } + + return { + key, + options, + }; +}; + +type Fetcher = typeof fetcherWithHelper; + +describe('useReverification type tests', () => { + it('allow pass through types', () => { + type UseReverificationWithFetcher = typeof useReverification; + type VerifiedFetcher = ReturnType[0]; + expectTypeOf(fetcher).toEqualTypeOf(); + }); + + it('returned callback with clerk error excluded and possible null in case of cancelled flow', () => { + type UseReverificationWithFetcherHelper = typeof useReverification; + type VerifiedFetcherHelper = ReturnType[0]; + + expectTypeOf(fetcherWithHelper).not.toEqualTypeOf(); + + expectTypeOf>().toEqualTypeOf>(); + expectTypeOf>().not.toEqualTypeOf>(); + expectTypeOf>> | null>().toEqualTypeOf< + Awaited> + >(); + }); + + it('returned callback with clerk error excluded but without null since we throw', () => { + type UseReverificationWithFetcherHelperThrow = typeof useReverification< + typeof fetcherWithHelper, + { + throwOnCancel: true; + } + >; + type VerifiedFetcherHelperThrow = ReturnType[0]; + expectTypeOf(fetcherWithHelper).not.toEqualTypeOf(); + expectTypeOf>>>().toEqualTypeOf< + Awaited> + >(); + }); +}); diff --git a/packages/shared/src/react/hooks/useReverification.ts b/packages/shared/src/react/hooks/useReverification.ts index 74f3b87a82..7efb0d844e 100644 --- a/packages/shared/src/react/hooks/useReverification.ts +++ b/packages/shared/src/react/hooks/useReverification.ts @@ -1,36 +1,51 @@ import type { Clerk } from '@clerk/types'; import { useMemo, useRef } from 'react'; +import { validateReverificationConfig } from '../../authorization'; import { __experimental_isReverificationHint, __experimental_reverificationError } from '../../authorization-errors'; -import { ClerkRuntimeError, isClerkAPIResponseError } from '../../error'; +import { ClerkRuntimeError, isClerkAPIResponseError, isClerkRuntimeError } from '../../error'; import { createDeferredPromise } from '../../utils/createDeferredPromise'; import { useClerk } from './useClerk'; import { useSafeLayoutEffect } from './useSafeLayoutEffect'; async function resolveResult( - result: Promise, + result: Promise | T, ): Promise> { - return result - .then(r => { - if (r instanceof Response) { - return r.json(); - } - return r; - }) - .catch(e => { - // Treat fapi assurance as an assurance hint - if (isClerkAPIResponseError(e) && e.errors.find(({ code }) => code == 'session_step_up_verification_required')) { - return __experimental_reverificationError(); - } + try { + const r = await result; + if (r instanceof Response) { + return r.json(); + } + return r; + } catch (e) { + // Treat fapi assurance as an assurance hint + if (isClerkAPIResponseError(e) && e.errors.find(({ code }) => code == 'session_step_up_verification_required')) { + return __experimental_reverificationError(); + } - // rethrow - throw e; - }); + // rethrow + throw e; + } } -function createReverificationHandler(params: { onOpenModal: Clerk['__experimental_openUserVerification'] }) { - function assertReverification Promise>(fetcher: Fetcher): Fetcher { - return (async (...args) => { +type ExcludeClerkError = T extends { clerk_error: any } ? (P extends { throwOnCancel: true } ? never : null) : T; + +type UseReverificationOptions = { + onCancel?: () => void; + throwOnCancel?: boolean; +}; + +type CreateReverificationHandlerParams = UseReverificationOptions & { + openUIComponent: Clerk['__experimental_openUserVerification']; +}; + +function createReverificationHandler(params: CreateReverificationHandlerParams) { + function assertReverification Promise | undefined>( + fetcher: Fetcher, + ): ( + ...args: Parameters + ) => Promise>, Parameters[1]>> { + return (async (...args: Parameters) => { let result = await resolveResult(fetcher(...args)); if (__experimental_isReverificationHint(result)) { @@ -39,11 +54,14 @@ function createReverificationHandler(params: { onOpenModal: Clerk['__experimenta */ const resolvers = createDeferredPromise(); + const isValidMetadata = validateReverificationConfig(result.clerk_error.metadata.reverification); + /** * On success resolve the pending promise * On cancel reject the pending promise */ - params.onOpenModal?.({ + params.openUIComponent?.({ + level: isValidMetadata ? isValidMetadata().level : undefined, afterVerification() { resolvers.resolve(true); }, @@ -56,10 +74,22 @@ function createReverificationHandler(params: { onOpenModal: Clerk['__experimenta }, }); - /** - * Wait until the promise from above have been resolved or rejected - */ - await resolvers.promise; + try { + /** + * Wait until the promise from above have been resolved or rejected + */ + await resolvers.promise; + } catch (e) { + if (params.onCancel) { + params.onCancel(); + } + + if (isClerkRuntimeError(e) && e.code === 'reverification_cancelled' && params.throwOnCancel) { + throw e; + } + + return null; + } /** * After the promise resolved successfully try the original request one more time @@ -68,26 +98,55 @@ function createReverificationHandler(params: { onOpenModal: Clerk['__experimenta } return result; - }) as Fetcher; + }) as ExcludeClerkError>, Parameters[1]>; } return assertReverification; } -function __experimental_useReverification Promise>(fetcher: Fetcher): readonly [Fetcher] { +type UseReverificationResult< + Fetcher extends (...args: any[]) => Promise | undefined, + Options extends UseReverificationOptions, +> = readonly [(...args: Parameters) => Promise>, Options>>]; + +/** + * Receives a fetcher async function and returned an enhanced fetcher that automatically handles the reverification flow + * by displaying a prebuilt UI component when the request from the fetcher fails with a reverification error response. + * + * While the UI component is displayed the promise is still pending. + * On success: the original request is retried one more time. + * On error: + * (1) by default the fetcher will return `null` and the `onCancel` callback will be executed. + * (2) when `throwOnCancel: true` instead of returning null, the returned fetcher will throw a `ClerkRuntimeError`. + * + * @example + * A simple example: + * + * function Hello() { + * const [fetchBalance] = useReverification(()=> fetch('/transfer-balance',{method:"POST"})); + * return + * } + */ +function __experimental_useReverification< + Fetcher extends (...args: any[]) => Promise | undefined, + Options extends UseReverificationOptions, +>(fetcher: Fetcher, options?: Options): UseReverificationResult { const { __experimental_openUserVerification } = useClerk(); const fetcherRef = useRef(fetcher); + const optionsRef = useRef(options); const handleReverification = useMemo(() => { const handler = createReverificationHandler({ - onOpenModal: __experimental_openUserVerification, + openUIComponent: __experimental_openUserVerification, + ...optionsRef.current, })(fetcherRef.current); return [handler] as const; - }, [__experimental_openUserVerification, fetcherRef.current]); + }, [__experimental_openUserVerification, fetcherRef.current, optionsRef.current]); - // Keep fetcher ref in sync + // Keep fetcher and options ref in sync useSafeLayoutEffect(() => { fetcherRef.current = fetcher; + optionsRef.current = options; }); return handleReverification; From 6fc5e8ea125f879d86ed42164e155b9dd2d86bd8 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Wed, 20 Nov 2024 21:03:39 +0200 Subject: [PATCH 09/15] fix(clerk-js): Fix tokenCache.test.ts (#4616) --- .changeset/thin-turkeys-turn.md | 2 ++ packages/clerk-js/src/core/__tests__/tokenCache.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 .changeset/thin-turkeys-turn.md diff --git a/.changeset/thin-turkeys-turn.md b/.changeset/thin-turkeys-turn.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/thin-turkeys-turn.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts b/packages/clerk-js/src/core/__tests__/tokenCache.test.ts index da39d29194..75bbf824d9 100644 --- a/packages/clerk-js/src/core/__tests__/tokenCache.test.ts +++ b/packages/clerk-js/src/core/__tests__/tokenCache.test.ts @@ -124,8 +124,8 @@ describe('MemoryTokenCache', () => { expect(cache.get(key)).toMatchObject(key); - // 45s since token created - jest.advanceTimersByTime(45 * 1000); + // 44s since token created + jest.advanceTimersByTime(44 * 1000); expect(cache.get(key)).toMatchObject(key); // 46s since token created From 75bff25e2596637d8747b96e6754e25c7534c642 Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Wed, 20 Nov 2024 13:06:04 -0600 Subject: [PATCH 10/15] fix(astro): Add components to turborepo cache (#4611) --- .changeset/orange-radios-nail.md | 2 ++ packages/astro/turbo.json | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/orange-radios-nail.md create mode 100644 packages/astro/turbo.json diff --git a/.changeset/orange-radios-nail.md b/.changeset/orange-radios-nail.md new file mode 100644 index 0000000000..a49ba48448 --- /dev/null +++ b/.changeset/orange-radios-nail.md @@ -0,0 +1,2 @@ +--- +--- \ No newline at end of file diff --git a/packages/astro/turbo.json b/packages/astro/turbo.json new file mode 100644 index 0000000000..8fc08521e9 --- /dev/null +++ b/packages/astro/turbo.json @@ -0,0 +1,10 @@ +{ + "extends": ["//"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputLogs": "new-only", + "outputs": ["components/**", "dist/**"] + } + } +} From 27526a3f85c12843c920c1175cfd7512da2a2920 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:37:03 -0800 Subject: [PATCH 11/15] feat(clerk-js): Emit sandbox to dist on build (#4608) --- .changeset/large-bulldogs-tap.md | 2 + packages/clerk-js/package.json | 3 +- packages/clerk-js/rspack.config.js | 45 +++++++++++++++---- packages/clerk-js/sandbox/app.js | 2 + .../sandbox/{index.html => template.html} | 4 +- packages/clerk-js/turbo.json | 5 +++ 6 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 .changeset/large-bulldogs-tap.md rename packages/clerk-js/sandbox/{index.html => template.html} (98%) diff --git a/.changeset/large-bulldogs-tap.md b/.changeset/large-bulldogs-tap.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/large-bulldogs-tap.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 6bd50a9401..d6d04b9b8e 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -34,12 +34,13 @@ "build:analyze": "rspack build --config rspack.config.js --env production --env variant=\"clerk.browser\" --env analysis --analyze", "build:bundle": "pnpm clean && rspack build --config rspack.config.js --env production", "build:declarations": "tsc -p tsconfig.declarations.json", + "build:sandbox": "rspack build --config rspack.config.js --env production --env sandbox", "build:stats": "rspack build --config rspack.config.js --env production --json=stats.json --env variant=\"clerk.browser\"", "bundlewatch": "pnpm dlx bundlewatch --config bundlewatch.config.json", "clean": "rimraf ./dist", "dev": "rspack serve --config rspack.config.js", "dev:headless": "rspack serve --config rspack.config.js --env variant=\"clerk.headless.browser\"", - "dev:sandbox": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:4000", + "dev:sandbox": "rspack serve --config rspack.config.js --env devOrigin=http://localhost:4000 --env sandbox=1", "lint": "eslint src/", "lint:attw": "attw --pack .", "lint:publint": "publint || true", diff --git a/packages/clerk-js/rspack.config.js b/packages/clerk-js/rspack.config.js index c1a4c23ce4..2f17f5cd94 100644 --- a/packages/clerk-js/rspack.config.js +++ b/packages/clerk-js/rspack.config.js @@ -308,11 +308,27 @@ const entryForVariant = variant => { * @param {object} config * @param {'development'|'production'} config.mode * @param {boolean} config.analysis + * @param {object} config.env * @returns {(import('@rspack/core').Configuration)[]} */ -const prodConfig = ({ mode, analysis }) => { +const prodConfig = ({ mode, env, analysis }) => { + const isSandbox = !!env.sandbox; + const clerkBrowser = merge( entryForVariant(variants.clerkBrowser), + isSandbox + ? { + entry: { sandbox: './sandbox/app.js' }, + plugins: [ + new rspack.HtmlRspackPlugin({ + minify: false, + template: './sandbox/template.html', + inject: false, + hash: true, + }), + ], + } + : {}, common({ mode }), commonForProd(), commonForProdChunked(), @@ -404,6 +420,7 @@ const devConfig = ({ mode, env }) => { // accept an optional devOrigin environment option to change the origin of the dev server. // By default we use https://js.lclclerk.com which is what our local dev proxy looks for. const devUrl = new URL(env.devOrigin || 'https://js.lclclerk.com'); + const isSandbox = !!env.sandbox; /** @type {() => import('@rspack/core').Configuration} */ const commonForDev = () => { @@ -411,12 +428,20 @@ const devConfig = ({ mode, env }) => { module: { rules: [svgLoader(), ...typescriptLoaderDev(), clerkUICSSLoader()], }, - plugins: [new ReactRefreshPlugin(/** @type {any} **/ ({ overlay: { sockHost: devUrl.host } }))], + plugins: [ + new ReactRefreshPlugin(/** @type {any} **/ ({ overlay: { sockHost: devUrl.host } })), + isSandbox && + new rspack.HtmlRspackPlugin({ + minify: false, + template: './sandbox/template.html', + inject: false, + }), + ].filter(Boolean), devtool: 'eval-cheap-source-map', output: { - publicPath: `${devUrl.origin}/npm`, + publicPath: isSandbox ? `` : `${devUrl.origin}/npm`, crossOriginLoading: 'anonymous', - filename: `${variant}.js`, + filename: `[name].js`, libraryTarget: 'umd', }, optimization: { @@ -430,10 +455,11 @@ const devConfig = ({ mode, env }) => { hot: true, liveReload: false, client: { webSocketURL: `auto://${devUrl.host}/ws` }, - static: './sandbox', - historyApiFallback: { - index: '/index.html', - }, + ...(isSandbox + ? { + historyApiFallback: true, + } + : {}), }, }; }; @@ -448,6 +474,7 @@ const devConfig = ({ mode, env }) => { // prettier-ignore [variants.clerkBrowser]: merge( entryForVariant(variants.clerkBrowser), + isSandbox ? { entry: { sandbox: './sandbox/app.js' } } : {}, common({ mode }), commonForDev(), ), @@ -476,5 +503,5 @@ module.exports = env => { const mode = env.production ? 'production' : 'development'; const analysis = !!env.analysis; - return isProduction(mode) ? prodConfig({ mode, analysis }) : devConfig({ mode, env }); + return isProduction(mode) ? prodConfig({ mode, env, analysis }) : devConfig({ mode, env }); }; diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js index 8ed84bfbb9..4d0afd0c79 100644 --- a/packages/clerk-js/sandbox/app.js +++ b/packages/clerk-js/sandbox/app.js @@ -177,5 +177,7 @@ function addCurrentRouteIndicator(currentRoute) { signUpUrl: '/sign-up', }); renderCurrentRoute(); + } else { + console.error(`Unknown route: "${route}".`); } })(); diff --git a/packages/clerk-js/sandbox/index.html b/packages/clerk-js/sandbox/template.html similarity index 98% rename from packages/clerk-js/sandbox/index.html rename to packages/clerk-js/sandbox/template.html index dba3386c5a..cf497080da 100644 --- a/packages/clerk-js/sandbox/index.html +++ b/packages/clerk-js/sandbox/template.html @@ -200,12 +200,12 @@ diff --git a/packages/clerk-js/turbo.json b/packages/clerk-js/turbo.json index 425069e3e0..ecd642c0f5 100644 --- a/packages/clerk-js/turbo.json +++ b/packages/clerk-js/turbo.json @@ -22,6 +22,11 @@ "!node_modules/**" ] }, + "build:sandbox": { + "dependsOn": ["^build"], + "inputs": ["sandbox/**"], + "outputs": ["dist/**"] + }, "test": { "inputs": [ "*.d.ts", From dd090508f69d69bebcee39d70e19b81bd6ff6d0e Mon Sep 17 00:00:00 2001 From: Jacek Radko Date: Wed, 20 Nov 2024 13:45:31 -0600 Subject: [PATCH 12/15] feat(repo): Use catalog: protocol (#4605) --- .changeset/friendly-mice-pull.md | 2 + .npmrc | 3 +- .vscode/settings.json | 4 +- integration/presets/nuxt.ts | 4 +- package.json | 12 +- packages/astro/package.json | 4 +- packages/backend/package.json | 6 +- packages/chrome-extension/package.json | 8 +- packages/clerk-js/package.json | 6 +- packages/dev-cli/package.json | 2 +- .../elements/examples/nextjs/package.json | 4 +- packages/elements/package.json | 10 +- packages/eslint-config-custom/package.json | 2 +- packages/expo/package.json | 8 +- packages/express/package.json | 6 +- packages/fastify/package.json | 4 +- packages/localizations/package.json | 4 +- packages/nextjs/package.json | 6 +- packages/nuxt/package.json | 10 +- packages/react/package.json | 6 +- packages/remix/package.json | 8 +- packages/sdk-node/package.json | 6 +- packages/shared/package.json | 4 +- packages/tailwindcss-transformer/package.json | 2 +- packages/tanstack-start/package.json | 8 +- packages/testing/package.json | 4 +- packages/themes/package.json | 4 +- packages/types/package.json | 4 +- packages/ui/theme-builder/package.json | 4 +- packages/upgrade/package.json | 2 +- packages/vue/package.json | 2 +- pnpm-lock.yaml | 450 +++++++++--------- pnpm-workspace.yaml | 14 + turbo.json | 4 +- 34 files changed, 309 insertions(+), 318 deletions(-) create mode 100644 .changeset/friendly-mice-pull.md diff --git a/.changeset/friendly-mice-pull.md b/.changeset/friendly-mice-pull.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/friendly-mice-pull.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.npmrc b/.npmrc index 02a6c3e54c..e3d3527dfe 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,2 @@ engine-strict=false -legacy-peer-deps=false -link-workspace-packages=true \ No newline at end of file +legacy-peer-deps=false \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c39422fe0e..0433f8458a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,8 @@ "**/.yalc": true, "**/node_modules": true, ".temp_integration": true, - "packages/*/dist": true + "packages/*/dist": true, + "pnpm-lock.yaml": true, }, "typescript.enablePromptUseWorkspaceTsdk": true, "typescript.tsdk": "node_modules/typescript/lib", @@ -23,4 +24,5 @@ "url": "https://json.schemastore.org/chrome-manifest.json" } ], + "npm.packageManager": "pnpm", } diff --git a/integration/presets/nuxt.ts b/integration/presets/nuxt.ts index 2762d07c9c..8a55270454 100644 --- a/integration/presets/nuxt.ts +++ b/integration/presets/nuxt.ts @@ -1,8 +1,6 @@ import { applicationConfig } from '../models/applicationConfig'; import { templates } from '../templates'; -const clerkNuxtLocal = `file:${process.cwd()}/packages/nuxt`; - const nuxtNode = applicationConfig() .setName('nuxt-node') .useTemplate(templates['nuxt-node']) @@ -12,7 +10,7 @@ const nuxtNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/nuxt', clerkNuxtLocal); + .addDependency('@clerk/nuxt', '*'); export const nuxt = { node: nuxtNode, diff --git a/package.json b/package.json index f9ffde2aea..e503f50bb5 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "@types/cross-spawn": "^6.0.3", "@types/jest": "^29.3.1", "@types/node": "^20.11.24", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "@vitest/coverage-v8": "2.1.4", "citty": "^0.1.4", "conventional-changelog-conventionalcommits": "^4.6.3", @@ -94,16 +94,16 @@ "prettier-plugin-packagejson": "^2.5.3", "prettier-plugin-tailwindcss": "^0.6.3", "publint": "^0.2.4", - "react": "18.3.1", - "react-dom": "18.3.1", + "react": "catalog:react", + "react-dom": "catalog:react", "rimraf": "6.0.1", "statuses": "^1.4.0", "tree-kill": "^1.2.2", "ts-jest": "^29.0.3", - "tsup": "^8.0.1", + "tsup": "catalog:repo", "turbo": "^2.0.14", "turbo-ignore": "^2.0.6", - "typescript": "^5.6.3", + "typescript": "catalog:repo", "verdaccio": "^5.26.3", "vitest": "2.1.4", "zx": "^7.2.3" diff --git a/packages/astro/package.json b/packages/astro/package.json index 1bbe6483af..eebbf490a2 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -89,8 +89,8 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", - "react": "18.3.1", - "typescript": "*" + "react": "catalog:react", + "typescript": "catalog:repo" }, "peerDependencies": { "astro": "^3.2.0 || ^4.0.0" diff --git a/packages/backend/package.json b/packages/backend/package.json index 8c5f3b081c..3a704a9cc9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -96,7 +96,7 @@ "@clerk/types": "workspace:*", "cookie": "0.7.0", "snakecase-keys": "5.4.4", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", @@ -105,8 +105,8 @@ "@types/node": "^18.19.33", "msw": "2.6.4", "npm-run-all": "^4.1.5", - "tsup": "*", - "typescript": "*", + "tsup": "catalog:repo", + "typescript": "catalog:repo", "vitest-environment-miniflare": "2.14.4" }, "engines": { diff --git a/packages/chrome-extension/package.json b/packages/chrome-extension/package.json index 75706b16e2..d668972f10 100644 --- a/packages/chrome-extension/package.json +++ b/packages/chrome-extension/package.json @@ -56,12 +56,12 @@ "@clerk/eslint-config-custom": "workspace:*", "@types/chrome": "*", "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "@types/webextension-polyfill": "^0.10.7", - "tsup": "*", + "tsup": "catalog:repo", "type-fest": "^4.8.3", - "typescript": "*" + "typescript": "catalog:repo" }, "peerDependencies": { "react": ">=18", diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index d6d04b9b8e..aa9ab025b5 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -80,15 +80,15 @@ "@rspack/core": "^1.0.14", "@rspack/plugin-react-refresh": "^1.0.0", "@svgr/webpack": "^6.2.1", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "@types/webpack-dev-server": "^4.7.2", "@types/webpack-env": "^1.16.4", "react-refresh": "^0.14.0", "react-refresh-typescript": "^2.0.5", "terser-webpack-plugin": "^5.3.10", "ts-loader": "^9.3.0", - "typescript": "*", + "typescript": "catalog:repo", "webpack-merge": "^5.9.0" }, "peerDependencies": { diff --git a/packages/dev-cli/package.json b/packages/dev-cli/package.json index 3fec225044..b26b670833 100644 --- a/packages/dev-cli/package.json +++ b/packages/dev-cli/package.json @@ -31,7 +31,7 @@ "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", "@types/node": "^20.14.8", - "typescript": "*" + "typescript": "catalog:repo" }, "engines": { "node": ">=18.17.0" diff --git a/packages/elements/examples/nextjs/package.json b/packages/elements/examples/nextjs/package.json index 4b8e8fbe83..99f67e6f43 100644 --- a/packages/elements/examples/nextjs/package.json +++ b/packages/elements/examples/nextjs/package.json @@ -23,8 +23,8 @@ }, "devDependencies": { "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "autoprefixer": "^10.4.20", "eslint": "^8", "eslint-config-next": "14.2", diff --git a/packages/elements/package.json b/packages/elements/package.json index 2fe1fd8f48..46ace9b975 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -85,14 +85,14 @@ "@clerk/eslint-config-custom": "workspace:*", "@statelyai/inspect": "^0.4.0", "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "concurrently": "^8.2.2", "next": "^14.2.10", - "tslib": "2.4.1", - "tsup": "*", + "tslib": "catalog:repo", + "tsup": "catalog:repo", "type-fest": "^4.9.0", - "typescript": "*" + "typescript": "catalog:repo" }, "peerDependencies": { "next": "^13.5.4 || ^14.0.3 || ^15", diff --git a/packages/eslint-config-custom/package.json b/packages/eslint-config-custom/package.json index 6625aaa882..40442a0082 100644 --- a/packages/eslint-config-custom/package.json +++ b/packages/eslint-config-custom/package.json @@ -25,7 +25,7 @@ "eslint-plugin-unused-imports": "^3.2.0" }, "peerDependencies": { - "typescript": "*" + "typescript": "catalog:repo" }, "publishConfig": { "access": "public" diff --git a/packages/expo/package.json b/packages/expo/package.json index 2456ad046f..416c4bcd51 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -66,21 +66,21 @@ "@clerk/types": "workspace:*", "base-64": "^1.0.0", "react-native-url-polyfill": "2.0.0", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", "@clerk/expo-passkeys": "workspace:*", "@types/base-64": "^1.0.2", "@types/node": "^20.11.24", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "expo-auth-session": "^5.4.0", "expo-local-authentication": "^13.8.0", "expo-secure-store": "^12.8.1", "expo-web-browser": "^12.8.2", "react-native": "^0.73.9", - "typescript": "*" + "typescript": "catalog:repo" }, "peerDependencies": { "@clerk/expo-passkeys": ">=0.0.6", diff --git a/packages/express/package.json b/packages/express/package.json index 64ac3d7314..b55a009453 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -58,7 +58,7 @@ "@clerk/backend": "workspace:^", "@clerk/shared": "workspace:^", "@clerk/types": "workspace:^", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@types/express": "^4.17.21", @@ -66,8 +66,8 @@ "@types/supertest": "^6.0.2", "express": "^4.20.0", "supertest": "^6.3.4", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "peerDependencies": { "express": "^4.17.0 || ^5.0.0" diff --git a/packages/fastify/package.json b/packages/fastify/package.json index b5aa4f864f..3a11c37495 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -50,8 +50,8 @@ "@clerk/eslint-config-custom": "workspace:*", "@types/node": "^20.14.8", "fastify": "^5.0.0", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "peerDependencies": { "fastify": ">=5" diff --git a/packages/localizations/package.json b/packages/localizations/package.json index 09db3251b0..b23db9e261 100644 --- a/packages/localizations/package.json +++ b/packages/localizations/package.json @@ -104,8 +104,8 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "engines": { "node": ">=18.17.0" diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index d43dfdb4ed..9cc237ea0d 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -72,16 +72,14 @@ "crypto-js": "4.2.0", "ezheaders": "0.1.0", "server-only": "0.0.1", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", "@types/crypto-js": "4.2.2", "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", "next": "^14.2.10", - "typescript": "*" + "typescript": "catalog:repo" }, "peerDependencies": { "next": "^13.5.4 || ^14.0.3 || ^15.0.0", diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 4ba668aeed..b0c13f93b0 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -49,17 +49,17 @@ "publish:local": "pnpm dlx yalc push --replace --sig" }, "dependencies": { - "@clerk/backend": "1.17.0", - "@clerk/shared": "2.15.0", - "@clerk/types": "4.34.0", - "@clerk/vue": "0.0.5", + "@clerk/backend": "workspace:*", + "@clerk/shared": "workspace:*", + "@clerk/types": "workspace:*", + "@clerk/vue": "workspace:*", "@nuxt/kit": "^3.14.159", "@nuxt/schema": "^3.14.159", "h3": "^1.13.0" }, "devDependencies": { "nuxt": "^3.14.159", - "typescript": "*" + "typescript": "catalog:repo" }, "engines": { "node": ">=18.17.0" diff --git a/packages/react/package.json b/packages/react/package.json index ff44a80dba..6aac243dfd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -78,17 +78,15 @@ "dependencies": { "@clerk/shared": "workspace:*", "@clerk/types": "workspace:*", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", "@clerk/localizations": "workspace:*", "@clerk/themes": "workspace:*", "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", "@types/semver": "^7.5.8", - "typescript": "*" + "typescript": "catalog:repo" }, "peerDependencies": { "react": "^18 || ^19.0.0-0", diff --git a/packages/remix/package.json b/packages/remix/package.json index 4ede181381..b7f992bd9f 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -78,7 +78,7 @@ "@clerk/shared": "workspace:*", "@clerk/types": "workspace:*", "cookie": "0.7.0", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", @@ -86,9 +86,9 @@ "@remix-run/server-runtime": "^2.0.0", "@types/cookie": "^0.6.0", "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", - "typescript": "*" + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", + "typescript": "catalog:repo" }, "peerDependencies": { "@remix-run/react": "^2.0.0", diff --git a/packages/sdk-node/package.json b/packages/sdk-node/package.json index c8be647372..3cd92e23ca 100644 --- a/packages/sdk-node/package.json +++ b/packages/sdk-node/package.json @@ -56,7 +56,7 @@ "@clerk/backend": "workspace:*", "@clerk/shared": "workspace:*", "@clerk/types": "workspace:*", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", @@ -64,8 +64,8 @@ "@types/node": "^18.19.33", "nock": "^13.0.7", "npm-run-all": "^4.1.5", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "engines": { "node": ">=18.17.0" diff --git a/packages/shared/package.json b/packages/shared/package.json index 456f6f3167..5d54b27597 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -145,8 +145,8 @@ "@types/node": "^18.19.33", "cross-fetch": "^4.0.0", "esbuild": "0.20.2", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "peerDependencies": { "react": "^18 || ^19.0.0-0", diff --git a/packages/tailwindcss-transformer/package.json b/packages/tailwindcss-transformer/package.json index 7c629c5480..bccbeb9dd4 100644 --- a/packages/tailwindcss-transformer/package.json +++ b/packages/tailwindcss-transformer/package.json @@ -35,6 +35,6 @@ "postcss-value-parser": "^4.2.0", "recast": "^0.23.7", "tailwindcss": "^3.4.3", - "tslib": "2.4.1" + "tslib": "catalog:repo" } } diff --git a/packages/tanstack-start/package.json b/packages/tanstack-start/package.json index e99edee43c..72315bfe74 100644 --- a/packages/tanstack-start/package.json +++ b/packages/tanstack-start/package.json @@ -58,17 +58,17 @@ "@clerk/clerk-react": "workspace:*", "@clerk/shared": "workspace:*", "@clerk/types": "workspace:*", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", "@tanstack/react-router": "^1.81.9", "@tanstack/start": "^1.81.9", "@types/node": "^18.19.43", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "esbuild-plugin-file-path-extensions": "^2.1.2", - "typescript": "*", + "typescript": "catalog:repo", "vinxi": "^0.4.1" }, "peerDependencies": { diff --git a/packages/testing/package.json b/packages/testing/package.json index 8cf0b092f5..dab08e8d5c 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -72,8 +72,8 @@ "@playwright/test": "^1.44.0", "@types/node": "^18.19.33", "cypress": "^13.9.0", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "peerDependencies": { "@playwright/test": "^1", diff --git a/packages/themes/package.json b/packages/themes/package.json index 7043b93b1c..b5866bf663 100644 --- a/packages/themes/package.json +++ b/packages/themes/package.json @@ -38,11 +38,11 @@ }, "dependencies": { "@clerk/types": "workspace:*", - "tslib": "2.4.1" + "tslib": "catalog:repo" }, "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", - "typescript": "*" + "typescript": "catalog:repo" }, "engines": { "node": ">=18.17.0" diff --git a/packages/types/package.json b/packages/types/package.json index f96e169fcd..26b9f99cc9 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -41,8 +41,8 @@ "devDependencies": { "@clerk/eslint-config-custom": "workspace:*", "@types/node": "^18.19.33", - "tsup": "*", - "typescript": "*" + "tsup": "catalog:repo", + "typescript": "catalog:repo" }, "engines": { "node": ">=18.17.0" diff --git a/packages/ui/theme-builder/package.json b/packages/ui/theme-builder/package.json index 9ba0f550a5..cc5e14f618 100644 --- a/packages/ui/theme-builder/package.json +++ b/packages/ui/theme-builder/package.json @@ -31,8 +31,8 @@ }, "devDependencies": { "@types/node": "^20.12.12", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", + "@types/react": "catalog:react", + "@types/react-dom": "catalog:react", "eslint": "^8", "eslint-config-next": "14.2.7", "postcss": "^8.4.47", diff --git a/packages/upgrade/package.json b/packages/upgrade/package.json index 060a71f420..f599c66d36 100644 --- a/packages/upgrade/package.json +++ b/packages/upgrade/package.json @@ -43,7 +43,7 @@ "jscodeshift": "^17.0.0", "marked": "^11.1.1", "meow": "^11.0.0", - "react": "18.3.1", + "react": "catalog:react", "read-pkg": "^9.0.1", "semver-regex": "^4.0.5", "temp-dir": "^3.0.0" diff --git a/packages/vue/package.json b/packages/vue/package.json index 19c4a0a54b..f528bb34ce 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -57,7 +57,7 @@ "devDependencies": { "@testing-library/vue": "^8.1.0", "@vue.ts/tsx-auto-props": "^0.6.0", - "typescript": "*", + "typescript": "catalog:repo", "vue": "3.5.12" }, "peerDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1272255666..5caaf6986f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,31 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + react: + '@types/react': + specifier: 18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: 18.3.1 + version: 18.3.1 + react: + specifier: 18.3.1 + version: 18.3.1 + react-dom: + specifier: 18.3.1 + version: 18.3.1 + repo: + tslib: + specifier: 2.4.1 + version: 2.4.1 + tsup: + specifier: 8.3.5 + version: 8.3.5 + typescript: + specifier: 5.6.3 + version: 5.6.3 + overrides: jest: 29.7.0 jest-snapshot-prettier: npm:prettier@^3 @@ -70,10 +95,10 @@ importers: specifier: ^20.11.24 version: 20.17.5 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 '@vitest/coverage-v8': specifier: 2.1.4 @@ -145,10 +170,10 @@ importers: specifier: ^0.2.4 version: 0.2.4 react: - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 react-dom: - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1(react@18.3.1) rimraf: specifier: 6.0.1 @@ -163,8 +188,8 @@ importers: specifier: ^29.0.3 version: 29.0.5(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.5)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3)))(typescript@5.6.3) tsup: - specifier: ^8.0.1 - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) turbo: specifier: ^2.0.14 version: 2.2.3 @@ -172,7 +197,7 @@ importers: specifier: ^2.0.6 version: 2.2.3 typescript: - specifier: ^5.6.3 + specifier: catalog:repo version: 5.6.3 verdaccio: specifier: ^5.26.3 @@ -209,10 +234,10 @@ importers: specifier: workspace:* version: link:../eslint-config-custom react: - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/backend: @@ -230,7 +255,7 @@ importers: specifier: 5.4.4 version: 5.4.4 tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -252,10 +277,10 @@ importers: specifier: ^4.1.5 version: 4.1.5 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 vitest-environment-miniflare: specifier: 2.14.4 @@ -292,22 +317,22 @@ importers: specifier: ^18.19.33 version: 18.19.63 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 '@types/webextension-polyfill': specifier: ^0.10.7 version: 0.10.7 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) type-fest: specifier: ^4.8.3 version: 4.26.1 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/clerk-js: @@ -398,10 +423,10 @@ importers: specifier: ^6.2.1 version: 6.5.1 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 '@types/webpack-dev-server': specifier: ^4.7.2 @@ -422,7 +447,7 @@ importers: specifier: ^9.3.0 version: 9.5.1(typescript@5.6.3)(webpack@5.94.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 webpack-merge: specifier: ^5.9.0 @@ -453,7 +478,7 @@ importers: specifier: ^20.14.8 version: 20.17.5 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/elements: @@ -466,7 +491,7 @@ importers: version: link:../shared '@clerk/types': specifier: ^4.34.0 - version: link:../types + version: 4.34.0 '@radix-ui/primitive': specifier: ^1.1.0 version: 1.1.0 @@ -502,10 +527,10 @@ importers: specifier: ^18.19.33 version: 18.19.63 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 concurrently: specifier: ^8.2.2 @@ -514,16 +539,16 @@ importers: specifier: ^14.2.10 version: 14.2.16(@babel/core@7.26.0)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) type-fest: specifier: ^4.9.0 version: 4.26.1 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/eslint-config-custom: @@ -556,7 +581,7 @@ importers: specifier: ^3.2.0 version: 3.2.0(@typescript-eslint/eslint-plugin@6.9.0(@typescript-eslint/parser@6.21.0(eslint@8.49.0)(typescript@5.6.3))(eslint@8.49.0)(typescript@5.6.3))(eslint@8.49.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/expo: @@ -586,7 +611,7 @@ importers: specifier: 2.0.0 version: 2.0.0(react-native@0.73.9(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(react@18.3.1)) tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -602,10 +627,10 @@ importers: specifier: ^20.11.24 version: 20.17.5 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 expo-auth-session: specifier: ^5.4.0 @@ -623,7 +648,7 @@ importers: specifier: ^0.73.9 version: 0.73.9(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(react@18.3.1) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/expo-passkeys: @@ -659,7 +684,7 @@ importers: specifier: workspace:^ version: link:../types tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@types/express': @@ -678,10 +703,10 @@ importers: specifier: ^6.3.4 version: 6.3.4 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/fastify: @@ -712,10 +737,10 @@ importers: specifier: ^5.0.0 version: 5.0.0 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/localizations: @@ -728,10 +753,10 @@ importers: specifier: workspace:* version: link:../eslint-config-custom tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/nextjs: @@ -764,7 +789,7 @@ importers: specifier: 0.0.1 version: 0.0.1 tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -776,32 +801,26 @@ importers: '@types/node': specifier: ^18.19.33 version: 18.19.63 - '@types/react': - specifier: 18.3.12 - version: 18.3.12 - '@types/react-dom': - specifier: 18.3.1 - version: 18.3.1 next: specifier: ^14.2.10 version: 14.2.16(@babel/core@7.26.0)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/nuxt: dependencies: '@clerk/backend': - specifier: 1.17.0 + specifier: workspace:* version: link:../backend '@clerk/shared': - specifier: 2.15.0 + specifier: workspace:* version: link:../shared '@clerk/types': - specifier: 4.34.0 + specifier: workspace:* version: link:../types '@clerk/vue': - specifier: 0.0.5 + specifier: workspace:* version: link:../vue '@nuxt/kit': specifier: ^3.14.159 @@ -817,7 +836,7 @@ importers: specifier: ^3.14.159 version: 3.14.159(@parcel/watcher@2.4.1)(@types/node@22.9.0)(eslint@8.49.0)(ioredis@5.4.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.26.0)(terser@5.31.1)(typescript@5.6.3)(vite@5.4.10(@types/node@22.9.0)(terser@5.31.1))(webpack-sources@3.2.3) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/react: @@ -835,7 +854,7 @@ importers: specifier: ^18 || ^19.0.0-0 version: 18.3.1(react@18.3.1) tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -850,17 +869,11 @@ importers: '@types/node': specifier: ^18.19.33 version: 18.19.63 - '@types/react': - specifier: 18.3.12 - version: 18.3.12 - '@types/react-dom': - specifier: 18.3.1 - version: 18.3.1 '@types/semver': specifier: ^7.5.8 version: 7.5.8 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/remix: @@ -890,7 +903,7 @@ importers: specifier: ^6.0.0 version: 6.16.0(react@18.3.1) tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -909,13 +922,13 @@ importers: specifier: ^18.19.33 version: 18.19.63 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/sdk-node: @@ -930,7 +943,7 @@ importers: specifier: workspace:* version: link:../types tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -949,10 +962,10 @@ importers: specifier: ^4.1.5 version: 4.1.5 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/shared: @@ -1001,10 +1014,10 @@ importers: specifier: 0.20.2 version: 0.20.2 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/tailwindcss-transformer: @@ -1028,7 +1041,7 @@ importers: specifier: ^3.4.3 version: 3.4.4(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3)) tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 packages/tanstack-start: @@ -1052,7 +1065,7 @@ importers: specifier: '>=18 || >=19.0.0-beta' version: 18.3.1(react@18.3.1) tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': @@ -1068,16 +1081,16 @@ importers: specifier: ^18.19.43 version: 18.19.63 '@types/react': - specifier: 18.3.12 + specifier: catalog:react version: 18.3.12 '@types/react-dom': - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 esbuild-plugin-file-path-extensions: specifier: ^2.1.2 version: 2.1.2 typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 vinxi: specifier: ^0.4.1 @@ -1111,10 +1124,10 @@ importers: specifier: ^13.9.0 version: 13.9.0 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/themes: @@ -1123,14 +1136,14 @@ importers: specifier: workspace:* version: link:../types tslib: - specifier: 2.4.1 + specifier: catalog:repo version: 2.4.1 devDependencies: '@clerk/eslint-config-custom': specifier: workspace:* version: link:../eslint-config-custom typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/types: @@ -1146,10 +1159,10 @@ importers: specifier: ^18.19.33 version: 18.19.63 tsup: - specifier: '*' - version: 8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3) + specifier: catalog:repo + version: 8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 packages/ui: @@ -1255,7 +1268,7 @@ importers: specifier: ^11.0.0 version: 11.0.0 react: - specifier: 18.3.1 + specifier: catalog:react version: 18.3.1 read-pkg: specifier: ^9.0.1 @@ -1296,7 +1309,7 @@ importers: specifier: ^0.6.0 version: 0.6.0(rollup@4.26.0)(typescript@5.6.3)(vue@3.5.12(typescript@5.6.3))(webpack-sources@3.2.3) typescript: - specifier: '*' + specifier: catalog:repo version: 5.6.3 vue: specifier: 3.5.12 @@ -2234,6 +2247,10 @@ packages: '@changesets/write@0.3.2': resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} + '@clerk/types@4.34.0': + resolution: {integrity: sha512-4ghDvf80/sFlpx5HnmIl3vW7SOqEaTDwyKAw64H/E2ahgGUMk+qLVpxnBumTpowq+bGjfMVwbneDQOhtmYidoQ==} + engines: {node: '>=18.17.0'} + '@cloudflare/kv-asset-handler@0.3.4': resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} engines: {node: '>=16.13'} @@ -6811,11 +6828,11 @@ packages: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} - bundle-require@4.2.1: - resolution: {integrity: sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==} + bundle-require@5.0.0: + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - esbuild: '>=0.17' + esbuild: '>=0.18' busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -12069,6 +12086,24 @@ packages: ts-node: optional: true + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss-merge-longhand@7.0.4: resolution: {integrity: sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -13566,6 +13601,11 @@ packages: engines: {node: '>=8'} hasBin: true + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + sudo-prompt@8.2.5: resolution: {integrity: sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw==} @@ -13984,8 +14024,8 @@ packages: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} - tsup@8.1.0: - resolution: {integrity: sha512-UFdfCAXukax+U6KzeTNO2kAARHcWxmKsnvSPXUcfA1D+kU05XDccCrkffCQpFaWDsZfV0jMyTsxU39VfCp6EOg==} + tsup@8.3.5: + resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -16456,6 +16496,10 @@ snapshots: human-id: 1.0.2 prettier: 2.8.8 + '@clerk/types@4.34.0': + dependencies: + csstype: 3.1.1 + '@cloudflare/kv-asset-handler@0.3.4': dependencies: mime: 3.0.0 @@ -16635,7 +16679,7 @@ snapshots: '@emnapi/runtime@1.3.1': dependencies: - tslib: 2.4.1 + tslib: 2.8.1 optional: true '@emotion/babel-plugin@11.11.0': @@ -17208,7 +17252,7 @@ snapshots: password-prompt: 1.1.3 sudo-prompt: 8.2.5 tmp: 0.0.33 - tslib: 2.4.1 + tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -17417,26 +17461,26 @@ snapshots: '@formatjs/ecma402-abstract@2.0.0': dependencies: '@formatjs/intl-localematcher': 0.5.4 - tslib: 2.4.1 + tslib: 2.8.1 '@formatjs/fast-memoize@2.2.0': dependencies: - tslib: 2.4.1 + tslib: 2.8.1 '@formatjs/icu-messageformat-parser@2.7.8': dependencies: '@formatjs/ecma402-abstract': 2.0.0 '@formatjs/icu-skeleton-parser': 1.8.2 - tslib: 2.4.1 + tslib: 2.8.1 '@formatjs/icu-skeleton-parser@1.8.2': dependencies: '@formatjs/ecma402-abstract': 2.0.0 - tslib: 2.4.1 + tslib: 2.8.1 '@formatjs/intl-localematcher@0.5.4': dependencies: - tslib: 2.4.1 + tslib: 2.8.1 '@formkit/auto-animate@0.8.2': {} @@ -17896,21 +17940,21 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.0 optional: true - '@jsonjoy.com/base64@1.1.2(tslib@2.4.1)': + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: - tslib: 2.4.1 + tslib: 2.8.1 - '@jsonjoy.com/json-pack@1.1.0(tslib@2.4.1)': + '@jsonjoy.com/json-pack@1.1.0(tslib@2.8.1)': dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.4.1) - '@jsonjoy.com/util': 1.5.0(tslib@2.4.1) + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 1.21.0(tslib@2.4.1) - tslib: 2.4.1 + thingies: 1.21.0(tslib@2.8.1) + tslib: 2.8.1 - '@jsonjoy.com/util@1.5.0(tslib@2.4.1)': + '@jsonjoy.com/util@1.5.0(tslib@2.8.1)': dependencies: - tslib: 2.4.1 + tslib: 2.8.1 '@kwsites/file-exists@1.1.1': dependencies: @@ -20607,12 +20651,12 @@ snapshots: '@swc/helpers@0.5.13': dependencies: - tslib: 2.4.1 + tslib: 2.8.1 '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.4.1 + tslib: 2.8.1 '@tanstack/history@1.81.9': {} @@ -22259,7 +22303,7 @@ snapshots: aria-hidden@1.2.4: dependencies: - tslib: 2.4.1 + tslib: 2.8.1 aria-query@5.1.3: dependencies: @@ -22366,15 +22410,15 @@ snapshots: ast-types@0.14.2: dependencies: - tslib: 2.4.1 + tslib: 2.8.1 ast-types@0.15.2: dependencies: - tslib: 2.4.1 + tslib: 2.8.1 ast-types@0.16.1: dependencies: - tslib: 2.4.1 + tslib: 2.8.1 ast-walker-scope@0.6.2: dependencies: @@ -22866,9 +22910,9 @@ snapshots: dependencies: run-applescript: 7.0.0 - bundle-require@4.2.1(esbuild@0.21.5): + bundle-require@5.0.0(esbuild@0.24.0): dependencies: - esbuild: 0.21.5 + esbuild: 0.24.0 load-tsconfig: 0.2.5 busboy@1.6.0: @@ -24085,7 +24129,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.8.1 dot-prop@5.3.0: dependencies: @@ -25765,7 +25809,7 @@ snapshots: graphql-tag@2.12.6(graphql@15.8.0): dependencies: graphql: 15.8.0 - tslib: 2.4.1 + tslib: 2.8.1 graphql@15.8.0: {} @@ -26310,7 +26354,7 @@ snapshots: '@formatjs/ecma402-abstract': 2.0.0 '@formatjs/fast-memoize': 2.2.0 '@formatjs/icu-messageformat-parser': 2.7.8 - tslib: 2.4.1 + tslib: 2.8.1 invariant@2.2.4: dependencies: @@ -27731,7 +27775,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.4.1 + tslib: 2.8.1 lru-cache@10.4.3: {} @@ -27975,10 +28019,10 @@ snapshots: memfs@4.14.0: dependencies: - '@jsonjoy.com/json-pack': 1.1.0(tslib@2.4.1) - '@jsonjoy.com/util': 1.5.0(tslib@2.4.1) - tree-dump: 1.0.2(tslib@2.4.1) - tslib: 2.4.1 + '@jsonjoy.com/json-pack': 1.1.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) + tree-dump: 1.0.2(tslib@2.8.1) + tslib: 2.8.1 memoize-one@5.2.1: {} @@ -28719,7 +28763,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.4.1 + tslib: 2.8.1 nocache@3.0.4: {} @@ -29632,29 +29676,14 @@ snapshots: postcss: 8.4.47 ts-node: 10.9.2(@types/node@22.9.0)(typescript@5.6.3) - postcss-load-config@4.0.1(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3)): - dependencies: - lilconfig: 2.1.0 - yaml: 2.6.0 - optionalDependencies: - postcss: 8.4.49 - ts-node: 10.9.2(@types/node@18.19.63)(typescript@5.6.3) - - postcss-load-config@4.0.1(postcss@8.4.49)(ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3)): + postcss-load-config@6.0.1(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(yaml@2.6.0): dependencies: - lilconfig: 2.1.0 - yaml: 2.6.0 + lilconfig: 3.1.2 optionalDependencies: + jiti: 2.4.0 postcss: 8.4.49 - ts-node: 10.9.2(@types/node@20.17.5)(typescript@5.6.3) - - postcss-load-config@4.0.1(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3)): - dependencies: - lilconfig: 2.1.0 + tsx: 4.19.2 yaml: 2.6.0 - optionalDependencies: - postcss: 8.4.49 - ts-node: 10.9.2(@types/node@22.9.0)(typescript@5.6.3) postcss-merge-longhand@7.0.4(postcss@8.4.47): dependencies: @@ -30180,7 +30209,7 @@ snapshots: dependencies: react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) - tslib: 2.4.1 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 @@ -30189,7 +30218,7 @@ snapshots: react: 18.3.1 react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) - tslib: 2.4.1 + tslib: 2.8.1 use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) optionalDependencies: @@ -30245,7 +30274,7 @@ snapshots: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 - tslib: 2.4.1 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 @@ -30349,14 +30378,14 @@ snapshots: ast-types: 0.14.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.4.1 + tslib: 2.8.1 recast@0.21.5: dependencies: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.4.1 + tslib: 2.8.1 recast@0.23.9: dependencies: @@ -30364,7 +30393,7 @@ snapshots: esprima: 4.0.1 source-map: 0.6.1 tiny-invariant: 1.3.3 - tslib: 2.4.1 + tslib: 2.8.1 rechoir@0.8.0: dependencies: @@ -30689,7 +30718,7 @@ snapshots: rxjs@7.8.1: dependencies: - tslib: 2.4.1 + tslib: 2.8.1 sade@1.8.1: dependencies: @@ -31039,7 +31068,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.8.1 snakecase-keys@5.4.4: dependencies: @@ -31420,6 +31449,16 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + sudo-prompt@8.2.5: {} sudo-prompt@9.1.1: {} @@ -31652,9 +31691,9 @@ snapshots: dependencies: any-promise: 1.3.0 - thingies@1.21.0(tslib@2.4.1): + thingies@1.21.0(tslib@2.8.1): dependencies: - tslib: 2.4.1 + tslib: 2.8.1 thread-stream@0.15.2: dependencies: @@ -31758,9 +31797,9 @@ snapshots: typedarray.prototype.slice: 1.0.3 which-typed-array: 1.1.15 - tree-dump@1.0.2(tslib@2.4.1): + tree-dump@1.0.2(tslib@2.8.1): dependencies: - tslib: 2.4.1 + tslib: 2.8.1 tree-kill@1.2.2: {} @@ -31807,25 +31846,6 @@ snapshots: typescript: 5.6.3 webpack: 5.94.0 - ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.19.63 - acorn: 8.14.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.6.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true - ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -31883,74 +31903,32 @@ snapshots: tsscmp@1.0.6: {} - tsup@8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3))(typescript@5.6.3): - dependencies: - bundle-require: 4.2.1(esbuild@0.21.5) - cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.7(supports-color@8.1.1) - esbuild: 0.21.5 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.1(postcss@8.4.49)(ts-node@10.9.2(@types/node@18.19.63)(typescript@5.6.3)) - resolve-from: 5.0.0 - rollup: 4.26.0 - source-map: 0.8.0-beta.0 - sucrase: 3.34.0 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.4.49 - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - ts-node - - tsup@8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3))(typescript@5.6.3): + tsup@8.3.5(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.6.3)(yaml@2.6.0): dependencies: - bundle-require: 4.2.1(esbuild@0.21.5) + bundle-require: 5.0.0(esbuild@0.24.0) cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.7(supports-color@8.1.1) - esbuild: 0.21.5 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.1(postcss@8.4.49)(ts-node@10.9.2(@types/node@20.17.5)(typescript@5.6.3)) - resolve-from: 5.0.0 - rollup: 4.26.0 - source-map: 0.8.0-beta.0 - sucrase: 3.34.0 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.4.49 - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - ts-node - - tsup@8.1.0(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3))(typescript@5.6.3): - dependencies: - bundle-require: 4.2.1(esbuild@0.21.5) - cac: 6.7.14 - chokidar: 3.6.0 + chokidar: 4.0.1 + consola: 3.2.3 debug: 4.3.7(supports-color@8.1.1) - esbuild: 0.21.5 - execa: 5.1.1 - globby: 11.1.0 + esbuild: 0.24.0 joycon: 3.1.1 - postcss-load-config: 4.0.1(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3)) + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.4.0)(postcss@8.4.49)(tsx@4.19.2)(yaml@2.6.0) resolve-from: 5.0.0 rollup: 4.26.0 source-map: 0.8.0-beta.0 - sucrase: 3.34.0 + sucrase: 3.35.0 + tinyexec: 0.3.1 + tinyglobby: 0.2.10 tree-kill: 1.2.2 optionalDependencies: postcss: 8.4.49 typescript: 5.6.3 transitivePeerDependencies: + - jiti - supports-color - - ts-node + - tsx + - yaml tsutils@3.21.0(typescript@5.6.3): dependencies: @@ -32370,7 +32348,7 @@ snapshots: use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.4.1 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 @@ -32384,7 +32362,7 @@ snapshots: dependencies: detect-node-es: 1.1.0 react: 18.3.1 - tslib: 2.4.1 + tslib: 2.8.1 optionalDependencies: '@types/react': 18.3.12 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 18ec407efc..96aba05abc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,16 @@ packages: - 'packages/*' + +catalogs: + # Can be referenced through "catalog:react" + react: + react: 18.3.1 + react-dom: 18.3.1 + '@types/react': 18.3.12 + '@types/react-dom': 18.3.1 + + # Can be referenced through "catalog:repo" + repo: + tslib: 2.4.1 + tsup: 8.3.5 + typescript: 5.6.3 diff --git a/turbo.json b/turbo.json index 2b85a95469..d6503add92 100644 --- a/turbo.json +++ b/turbo.json @@ -6,9 +6,11 @@ "ui": "tui", "globalDependencies": [ ".github/.cache-version", + ".npmrc", "jest.*.ts", "package.json", - "package-lock.json", + "pnpm-lock.yaml", + "pnpm-workspace.yaml", "tsconfig.json", "tsconfig.*.json", "scripts/subpath-workaround.mjs" From 7c63110e774bc35ea9a51d1e5547308f5860ec21 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 20 Nov 2024 14:05:19 -0600 Subject: [PATCH 13/15] fix(dev-cli): Fix `clerk-dev` Next.js detection (#4612) --- .changeset/shaggy-donuts-cry.md | 5 +++++ packages/dev-cli/src/commands/setup.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/shaggy-donuts-cry.md diff --git a/.changeset/shaggy-donuts-cry.md b/.changeset/shaggy-donuts-cry.md new file mode 100644 index 0000000000..555d224ee0 --- /dev/null +++ b/.changeset/shaggy-donuts-cry.md @@ -0,0 +1,5 @@ +--- +'@clerk/dev-cli': patch +--- + +Fix framework detection for Next.js. `clerk-dev` will now check for `next` as a dependency. diff --git a/packages/dev-cli/src/commands/setup.js b/packages/dev-cli/src/commands/setup.js index 764e64be2e..c739e4ed6a 100644 --- a/packages/dev-cli/src/commands/setup.js +++ b/packages/dev-cli/src/commands/setup.js @@ -40,7 +40,7 @@ function hasPackage(packages, dependency) { async function detectFramework() { const { dependencies, devDependencies } = await getDependencies(join(process.cwd(), 'package.json')); - const IS_NEXT = hasFile('next.config.js') || hasFile('next.config.mjs'); + const IS_NEXT = hasPackage(dependencies, 'next'); if (IS_NEXT) { return 'nextjs'; } From 860edfd9acd822e9f54440e264978beaeab46c8c Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Wed, 20 Nov 2024 15:10:33 -0500 Subject: [PATCH 14/15] feat(clerk-js): Move sandbox nav to a sidebar (#4617) --- .changeset/unlucky-teachers-joke.md | 2 + packages/clerk-js/sandbox/app.js | 4 +- packages/clerk-js/sandbox/template.html | 185 +++++++++++++++--------- packages/clerk-js/vercel.json | 3 + 4 files changed, 123 insertions(+), 71 deletions(-) create mode 100644 .changeset/unlucky-teachers-joke.md create mode 100644 packages/clerk-js/vercel.json diff --git a/.changeset/unlucky-teachers-joke.md b/.changeset/unlucky-teachers-joke.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/unlucky-teachers-joke.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js index 4d0afd0c79..6f7debdec9 100644 --- a/packages/clerk-js/sandbox/app.js +++ b/packages/clerk-js/sandbox/app.js @@ -163,8 +163,8 @@ function addCurrentRouteIndicator(currentRoute) { if (!link) { return; } - link.classList.remove('text-gray-400', 'hover:bg-[#1D1D21]', 'hover:text-white'); - link.classList.add('bg-[#1D1D21]', 'text-white'); + link.removeAttribute('aria-current'); + link.setAttribute('aria-current', 'page'); } (async () => { diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index cf497080da..3d1c1eea4c 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -1,5 +1,5 @@ - + clerk-js Sandbox @@ -126,77 +126,124 @@ }; - - - - +
+
+
+