diff --git a/.github/actions/verdaccio/action.yml b/.github/actions/verdaccio/action.yml index 13fee03e20a..6764b719d49 100644 --- a/.github/actions/verdaccio/action.yml +++ b/.github/actions/verdaccio/action.yml @@ -75,7 +75,7 @@ runs: - name: Print published Clerk package versions shell: bash run: | - echo "Published @clerk packages under 'integration' tag:" + echo "Published @clerk packages (snapshot versions):" echo "==================================================" packages=( "@clerk/agent-toolkit" @@ -102,6 +102,6 @@ runs: "@clerk/vue" ) for pkg in "${packages[@]}"; do - version=$(pnpm view "$pkg@integration" version 2>/dev/null || echo "not found") - printf "%-35s %s\n" "$pkg@integration:" "$version" + version=$(pnpm view "$pkg" version 2>/dev/null || echo "not found") + printf "%-35s %s\n" "$pkg:" "$version" done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d65f4832ed1..a1e59f57d0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,10 +64,10 @@ jobs: - name: Setup id: config uses: ./.github/actions/init-blacksmith - with: - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} + # with: + # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + # turbo-team: ${{ vars.TURBO_TEAM }} + # turbo-token: ${{ secrets.TURBO_TOKEN }} - name: Verify lockfile is deduped run: pnpm dedupe --check @@ -110,11 +110,11 @@ jobs: - name: Setup id: config uses: ./.github/actions/init-blacksmith - with: - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-summarize: ${{ env.TURBO_SUMMARIZE }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} + # with: + # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + # turbo-summarize: ${{ env.TURBO_SUMMARIZE }} + # turbo-team: ${{ vars.TURBO_TEAM }} + # turbo-token: ${{ secrets.TURBO_TOKEN }} - name: Turbo Build run: pnpm turbo build $TURBO_ARGS --only @@ -154,11 +154,11 @@ jobs: - name: Setup id: config uses: ./.github/actions/init-blacksmith - with: - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-summarize: ${{ env.TURBO_SUMMARIZE }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} + # with: + # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + # turbo-summarize: ${{ env.TURBO_SUMMARIZE }} + # turbo-team: ${{ vars.TURBO_TEAM }} + # turbo-token: ${{ secrets.TURBO_TOKEN }} - name: Check size using bundlewatch continue-on-error: true @@ -231,10 +231,10 @@ jobs: with: # Ensures that all builds are cached appropriately with a consistent run name `Unit Tests (20)`. node-version: ${{ matrix.node-version }} - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-summarize: ${{ env.TURBO_SUMMARIZE }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} + # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + # turbo-summarize: ${{ env.TURBO_SUMMARIZE }} + # turbo-team: ${{ vars.TURBO_TEAM }} + # turbo-token: ${{ secrets.TURBO_TOKEN }} - name: Rebuild @clerk/shared with CLERK_USE_RQ=true if: ${{ matrix.clerk-use-rq == 'true' }} @@ -280,7 +280,8 @@ jobs: retention-days: 5 integration-tests: - needs: [check-permissions, build-packages] + # needs: [check-permissions, build-packages] + needs: [check-permissions] if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} name: Integration Tests (${{ matrix.test-name }}, ${{ matrix.test-project }}${{ matrix.next-version && format(', {0}', matrix.next-version) || '' }}${{ matrix.clerk-use-rq == 'true' && ', RQ' || '' }}) permissions: @@ -355,9 +356,9 @@ jobs: id: config uses: ./.github/actions/init-blacksmith with: - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} + # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + # turbo-team: ${{ vars.TURBO_TEAM }} + # turbo-token: ${{ secrets.TURBO_TOKEN }} playwright-enabled: true - name: Verify jq is installed @@ -405,12 +406,16 @@ jobs: env: CLERK_USE_RQ: true + - name: Version packages for snapshot + if: ${{ steps.task-status.outputs.affected == '1' }} + run: npm run version-packages:snapshot ci + - name: Verdaccio if: ${{ steps.task-status.outputs.affected == '1' }} uses: ./.github/actions/verdaccio with: publish-cmd: | - if [ "$(pnpm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else CLERK_USE_RQ=${{ matrix.clerk-use-rq }} pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag --tag integration; fi + if [ "$(pnpm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else ${{ matrix.clerk-use-rq == 'true' && 'CLERK_USE_RQ=true' || '' }} pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag --tag latest; fi - name: Edit .npmrc [link-workspace-packages=false] run: sed -i -E 's/link-workspace-packages=(deep|true)/link-workspace-packages=false/' .npmrc @@ -423,6 +428,8 @@ jobs: pnpm config set minimum-release-age-exclude @clerk/* pnpm add @clerk/backend + # Install published packages from Verdaccio to test against real npm install scenarios + # rather than local monorepo builds. Validates package structure, dependencies, and entry points. - name: Install @clerk/clerk-js in os temp if: ${{ steps.task-status.outputs.affected == '1' }} working-directory: ${{runner.temp}} @@ -474,6 +481,7 @@ jobs: timeout-minutes: 25 run: pnpm turbo test:integration:${{ matrix.test-name }} $TURBO_ARGS env: + E2E_CLEANUP: "0" E2E_APP_CLERK_JS_DIR: ${{runner.temp}} E2E_APP_CLERK_UI_DIR: ${{runner.temp}} E2E_CLERK_JS_VERSION: "latest" @@ -483,9 +491,27 @@ jobs: E2E_CLERK_ENCRYPTION_KEY: ${{ matrix.clerk-encryption-key }} CLERK_USE_RQ: ${{ matrix.clerk-use-rq }} INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} - MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} NODE_EXTRA_CA_CERTS: ${{ github.workspace }}/integration/certs/rootCA.pem + - name: List temp directory contents + if: ${{ always() }} + run: | + echo "Contents of /tmp:" + ls -la /tmp/ | grep -E '(temp|integration)' || echo "No temp/integration directories found" + if [ -d "/tmp/.temp_integration" ]; then + echo "Contents of /tmp/.temp_integration:" + ls -la /tmp/.temp_integration/ + echo "Contents of all directories inside /tmp/.temp_integration:" + for dir in /tmp/.temp_integration/*/; do + if [ -d "$dir" ]; then + echo "Directory: $dir" + ls -la "$dir" + fi + done + else + echo "Directory /tmp/.temp_integration does not exist" + fi + - name: Upload test-results if: ${{ cancelled() || failure() }} uses: actions/upload-artifact@v4 @@ -518,10 +544,10 @@ jobs: with: turbo-enabled: true node-version: 22 - turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} - turbo-summarize: ${{ env.TURBO_SUMMARIZE }} - turbo-team: ${{ vars.TURBO_TEAM }} - turbo-token: ${{ secrets.TURBO_TOKEN }} + # turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + # turbo-summarize: ${{ env.TURBO_SUMMARIZE }} + # turbo-team: ${{ vars.TURBO_TEAM }} + # turbo-token: ${{ secrets.TURBO_TOKEN }} - name: Publish with pkg-pr-new run: pnpm run build && pnpx pkg-pr-new@${{ vars.PKG_PR_NEW_VERSION || '0.0.49' }} publish --compact --pnpm './packages/*' diff --git a/.github/workflows/nightly-checks.yml b/.github/workflows/nightly-checks.yml index f74f87a2e0e..79ea94e5fdf 100644 --- a/.github/workflows/nightly-checks.yml +++ b/.github/workflows/nightly-checks.yml @@ -68,10 +68,9 @@ jobs: E2E_CLERK_UI_VERSION: "latest" E2E_NEXTJS_VERSION: "canary" E2E_NPM_FORCE: "true" - E2E_REACT_DOM_VERSION: "19.1.0" - E2E_REACT_VERSION: "19.1.0" + E2E_REACT_DOM_VERSION: "19.2.1" + E2E_REACT_VERSION: "19.2.1" INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} - MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} # Upload test artifacts if tests failed - name: Upload Test Artifacts diff --git a/integration/.env.local.sample b/integration/.env.local.sample index 9505f3baa4c..8fdb1f0151c 100644 --- a/integration/.env.local.sample +++ b/integration/.env.local.sample @@ -1,4 +1,3 @@ -MAILSAC_API_KEY= VERCEL_PROJECT_ID= VERCEL_ORG_ID= VERCEL_TOKEN= diff --git a/integration/README.md b/integration/README.md index 64e26f9ac06..ff565b2cce5 100644 --- a/integration/README.md +++ b/integration/README.md @@ -75,10 +75,10 @@ Below you can find code snippets for running tests in a specific manner, easily #### Keep temporary site -During E2E runs a temporary site is created in which the template is copied into. If you want to keep the site around, pass the `CLEANUP` environment variable: +During E2E runs a temporary site is created in which the template is copied into. If you want to keep the site around, pass the `E2E_CLEANUP` environment variable: ```shell -CLEANUP=0 pnpm test:integration:base +E2E_CLEANUP=0 pnpm test:integration:base ``` For all available environment variables, check the [`constants.ts`](../integration/constants.ts) file. @@ -578,7 +578,6 @@ Before writing tests, it's important to understand how Playwright handles test i > [!NOTE] > The test suite also uses these environment variables to run some tests: > -> - `MAILSAC_API_KEY`: Used for [Mailsac](https://mailsac.com/) to retrieve email codes and magic links from temporary email addresses. > - `VERCEL_PROJECT_ID`: Only required if you plan on running deployment tests locally. This is the Vercel project ID, and it points to an application created via the Vercel dashboard. The easiest way to get access to it is by linking a local app to the Vercel project using the Vercel CLI, and then copying the values from the `.vercel` directory. > - `VERCEL_ORG_ID`: The organization that owns the Vercel project. See above for more details. > - `VERCEL_TOKEN`: A personal access token. This corresponds to a real user running the deployment command. Attention: Be extra careful with this token as it can't be scoped to a single Vercel project, meaning that the token has access to every project in the account it belongs to. diff --git a/integration/constants.ts b/integration/constants.ts index 7195880dba2..227d6e267c3 100644 --- a/integration/constants.ts +++ b/integration/constants.ts @@ -38,10 +38,10 @@ export const constants = { */ E2E_APP_CLERK_UI_DIR: process.env.E2E_APP_CLERK_UI_DIR, /** - * If CLEANUP=0 is used, the .tmp_integration directory will not be deleted. + * If E2E_CLEANUP=0 is used, the .tmp_integration directory will not be deleted. * This is useful for debugging locally. */ - CLEANUP: !(process.env.CLEANUP === '0' || process.env.CLEANUP === 'false'), + E2E_CLEANUP: !(process.env.E2E_CLEANUP === '0' || process.env.E2E_CLEANUP === 'false'), DEBUG: process.env.DEBUG === 'true' || process.env.DEBUG === '1', /** * Used with E2E_APP_URL if the tests need to access BAPI. diff --git a/integration/models/application.ts b/integration/models/application.ts index c8a6b2df2aa..c4b2b059a92 100644 --- a/integration/models/application.ts +++ b/integration/models/application.ts @@ -1,5 +1,8 @@ import * as path from 'node:path'; +import { parsePublishableKey } from '@clerk/shared/keys'; +import { clerkSetup } from '@clerk/testing/playwright'; + import { awaitableTreekill, createLogger, fs, getPort, run, waitForIdleProcess, waitForServer } from '../scripts'; import type { ApplicationConfig } from './applicationConfig.js'; import type { EnvironmentConfig } from './environment.js'; @@ -46,6 +49,10 @@ export const application = ( const log = logger.child({ prefix: 'setup' }).info; await run(scripts.setup, { cwd: appDirPath, log }); state.completedSetup = true; + // Print all Clerk package versions (direct and transitive) + const clerkPackagesLog = logger.child({ prefix: 'clerk-packages' }).info; + clerkPackagesLog('Installed @clerk/* packages:'); + await run('pnpm list @clerk/* --depth 100', { cwd: appDirPath, log: clerkPackagesLog }); } }, dev: async (opts: { port?: number; manualStart?: boolean; detached?: boolean; serverUrl?: string } = {}) => { @@ -82,6 +89,36 @@ export const application = ( log(`Server started at ${runtimeServerUrl}, pid: ${proc.pid}`); cleanupFns.push(() => awaitableTreekill(proc.pid, 'SIGKILL')); state.serverUrl = runtimeServerUrl; + + // Setup Clerk testing tokens after the server is running + if (state.env) { + try { + const publishableKey = state.env.publicVariables.get('CLERK_PUBLISHABLE_KEY'); + const secretKey = state.env.privateVariables.get('CLERK_SECRET_KEY'); + const apiUrl = state.env.privateVariables.get('CLERK_API_URL'); + + if (publishableKey && secretKey) { + const { instanceType, frontendApi: frontendApiUrl } = parsePublishableKey(publishableKey); + + if (instanceType !== 'development') { + log('Skipping clerkSetup for non-development instance'); + } else { + await clerkSetup({ + publishableKey, + frontendApiUrl, + secretKey, + // @ts-expect-error apiUrl is not a typed option for clerkSetup, but it is accepted at runtime. + apiUrl, + dotenv: false, + }); + log('Clerk testing tokens setup complete'); + } + } + } catch (error) { + logger.warn('Failed to setup Clerk testing tokens:', error); + } + } + return { port, serverUrl: runtimeServerUrl, pid: proc.pid }; }, build: async () => { diff --git a/integration/presets/astro.ts b/integration/presets/astro.ts index 65cbebff911..e9e310bf5fc 100644 --- a/integration/presets/astro.ts +++ b/integration/presets/astro.ts @@ -10,9 +10,9 @@ const astroNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/astro', linkPackage('astro', 'integration')) - .addDependency('@clerk/shared', linkPackage('types', 'integration')) - .addDependency('@clerk/localizations', linkPackage('localizations', 'integration')); + .addDependency('@clerk/astro', linkPackage('astro')) + .addDependency('@clerk/shared', linkPackage('shared')) + .addDependency('@clerk/localizations', linkPackage('localizations')); const astroStatic = astroNode.clone().setName('astro-hybrid').useTemplate(templates['astro-hybrid']); diff --git a/integration/presets/custom-flows.ts b/integration/presets/custom-flows.ts index efc01e753a9..236967404b8 100644 --- a/integration/presets/custom-flows.ts +++ b/integration/presets/custom-flows.ts @@ -10,9 +10,9 @@ const reactVite = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/react', linkPackage('react', 'integration')) - .addDependency('@clerk/shared', linkPackage('shared', 'integration')) - .addDependency('@clerk/ui', linkPackage('ui', 'integration')); + .addDependency('@clerk/react', linkPackage('react')) + .addDependency('@clerk/shared', linkPackage('shared')) + .addDependency('@clerk/ui', linkPackage('ui')); export const customFlows = { reactVite, diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts index 6135840636f..43245f0093e 100644 --- a/integration/presets/envs.ts +++ b/integration/presets/envs.ts @@ -65,7 +65,9 @@ const withCustomRoles = base .clone() .setId('withCustomRoles') .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-custom-roles').sk) - .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-custom-roles').pk); + .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-custom-roles').pk) + .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js') + .setEnvVariable('public', 'CLERK_UI_URL', constants.E2E_APP_CLERK_UI || 'http://localhost:18212/ui.browser.js'); const withReverification = base .clone() @@ -165,9 +167,8 @@ const withSessionTasksResetPassword = base const withBillingJwtV2 = base .clone() .setId('withBillingJwtV2') - .setEnvVariable('private', 'CLERK_API_URL', 'https://api.clerkstage.dev') - .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-billing-staging').sk) - .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-billing-staging').pk); + .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-billing').sk) + .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-billing').pk); const withBilling = base .clone() diff --git a/integration/presets/express.ts b/integration/presets/express.ts index f68cf904038..3a0ee82f392 100644 --- a/integration/presets/express.ts +++ b/integration/presets/express.ts @@ -10,9 +10,9 @@ const vite = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm start') - .addDependency('@clerk/express', linkPackage('express', 'integration')) - .addDependency('@clerk/clerk-js', linkPackage('clerk-js', 'integration')) - .addDependency('@clerk/ui', linkPackage('ui', 'integration')); + .addDependency('@clerk/express', linkPackage('express')) + .addDependency('@clerk/clerk-js', linkPackage('clerk-js')) + .addDependency('@clerk/ui', linkPackage('ui')); export const express = { vite, diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts index a14e3c9ef83..6a324675730 100644 --- a/integration/presets/longRunningApps.ts +++ b/integration/presets/longRunningApps.ts @@ -42,15 +42,7 @@ export const createLongRunningApps = () => { * Billing apps */ { id: 'withBillingJwtV2.next.appRouter', config: next.appRouter, env: envs.withBillingJwtV2 }, - { id: 'withBilling.next.appRouter', config: next.appRouter, env: envs.withBilling }, { id: 'withBillingJwtV2.vue.vite', config: vue.vite, env: envs.withBillingJwtV2 }, - { id: 'withBilling.vue.vite', config: vue.vite, env: envs.withBilling }, - - /** - * Machine auth apps - */ - { id: 'withMachine.express.vite', config: express.vite, env: envs.withAPIKeys }, - { id: 'withMachine.next.appRouter', config: next.appRouter, env: envs.withAPIKeys }, /** * Vite apps - basic flows @@ -68,7 +60,6 @@ export const createLongRunningApps = () => { /** * Various apps - basic flows */ - { id: 'withBilling.astro.node', config: astro.node, env: envs.withBilling }, { id: 'astro.node.withCustomRoles', config: astro.node, env: envs.withCustomRoles }, { id: 'astro.static.withCustomRoles', config: astro.static, env: envs.withCustomRoles }, { id: 'expo.expo-web', config: expo.expoWeb, env: envs.withEmailCodes }, diff --git a/integration/presets/react.ts b/integration/presets/react.ts index 1af28ee9b66..863e17b106c 100644 --- a/integration/presets/react.ts +++ b/integration/presets/react.ts @@ -10,9 +10,9 @@ const cra = applicationConfig() .addScript('dev', 'pnpm start') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm start') - .addDependency('@clerk/react', linkPackage('react', 'integration')) - .addDependency('@clerk/shared', linkPackage('shared', 'integration')) - .addDependency('@clerk/ui', linkPackage('ui', 'integration')); + .addDependency('@clerk/react', linkPackage('react')) + .addDependency('@clerk/shared', linkPackage('shared')) + .addDependency('@clerk/ui', linkPackage('ui')); const vite = cra .clone() diff --git a/integration/presets/utils.ts b/integration/presets/utils.ts index d22e39250dd..21672d16b5b 100644 --- a/integration/presets/utils.ts +++ b/integration/presets/utils.ts @@ -3,7 +3,9 @@ import path from 'node:path'; export function linkPackage(pkg: string, tag?: string) { // eslint-disable-next-line turbo/no-undeclared-env-vars if (process.env.CI === 'true') { - return tag || '*'; + // In CI, use '*' to get the latest version from Verdaccio + // which will be the snapshot version we just published + return '*'; } return `link:${path.resolve(process.cwd(), `packages/${pkg}`)}`; diff --git a/integration/templates/astro-hybrid/astro.config.mjs b/integration/templates/astro-hybrid/astro.config.mjs index 30ff739e8a3..8568979754d 100644 --- a/integration/templates/astro-hybrid/astro.config.mjs +++ b/integration/templates/astro-hybrid/astro.config.mjs @@ -4,7 +4,16 @@ import react from '@astrojs/react'; export default defineConfig({ output: 'hybrid', - integrations: [clerk(), react()], + integrations: [ + clerk({ + appearance: { + options: { + showOptionalFields: true, + }, + }, + }), + react(), + ], server: { port: Number(process.env.PORT), }, diff --git a/integration/templates/astro-node/astro.config.mjs b/integration/templates/astro-node/astro.config.mjs index 6b08d1babd3..41e54f926f8 100644 --- a/integration/templates/astro-node/astro.config.mjs +++ b/integration/templates/astro-node/astro.config.mjs @@ -10,7 +10,17 @@ export default defineConfig({ adapter: node({ mode: 'standalone', }), - integrations: [clerk(), react(), tailwind()], + integrations: [ + clerk({ + appearance: { + options: { + showOptionalFields: true, + }, + }, + }), + react(), + tailwind(), + ], server: { port: Number(process.env.PORT), }, diff --git a/integration/templates/custom-flows-react-vite/src/main.tsx b/integration/templates/custom-flows-react-vite/src/main.tsx index 966d034a194..f89e557a8dc 100644 --- a/integration/templates/custom-flows-react-vite/src/main.tsx +++ b/integration/templates/custom-flows-react-vite/src/main.tsx @@ -23,6 +23,11 @@ createRoot(document.getElementById('root')!).render( publishableKey={PUBLISHABLE_KEY} clerkJSUrl={import.meta.env.VITE_CLERK_JS_URL as string} clerkUiUrl={import.meta.env.VITE_CLERK_UI_URL as string} + appearance={{ + options: { + showOptionalFields: true, + }, + }} > diff --git a/integration/templates/expo-web/app/_layout.tsx b/integration/templates/expo-web/app/_layout.tsx index 24295ee033d..60c238a6892 100644 --- a/integration/templates/expo-web/app/_layout.tsx +++ b/integration/templates/expo-web/app/_layout.tsx @@ -10,6 +10,11 @@ export default function RootLayout() { routerReplace={to => router.replace(to)} clerkJSUrl={process.env.EXPO_PUBLIC_CLERK_JS_URL} clerkUiUrl={process.env.EXPO_PUBLIC_CLERK_UI_URL} + appearance={{ + options: { + showOptionalFields: true, + }, + }} > diff --git a/integration/templates/expo-web/metro.config.js b/integration/templates/expo-web/metro.config.js index 045418d1e79..c0f9eee8d78 100644 --- a/integration/templates/expo-web/metro.config.js +++ b/integration/templates/expo-web/metro.config.js @@ -51,8 +51,8 @@ if (clerkMonorepoPath) { // Explicitly map @clerk packages to their source locations // Point to the root of the package so Metro can properly resolve subpath exports config.resolver.extraNodeModules = { - '@clerk/clerk-react': path.resolve(clerkMonorepoPath, 'packages/react'), - '@clerk/clerk-expo': path.resolve(clerkMonorepoPath, 'packages/expo'), + '@clerk/react': path.resolve(clerkMonorepoPath, 'packages/react'), + '@clerk/expo': path.resolve(clerkMonorepoPath, 'packages/expo'), '@clerk/shared': path.resolve(clerkMonorepoPath, 'packages/shared'), '@clerk/types': path.resolve(clerkMonorepoPath, 'packages/types'), }; @@ -87,7 +87,7 @@ if (clerkMonorepoPath) { ]; // Custom resolver to handle package.json subpath exports for @clerk packages - // This enables Metro to resolve imports like '@clerk/clerk-react/internal' + // This enables Metro to resolve imports like '@clerk/react/internal' const originalResolveRequest = config.resolver.resolveRequest; config.resolver.resolveRequest = (context, moduleName, platform) => { // Check if this is a @clerk package with a subpath diff --git a/integration/templates/next-app-router-quickstart/src/app/layout.tsx b/integration/templates/next-app-router-quickstart/src/app/layout.tsx index 29ddd566bdb..411ba883c93 100644 --- a/integration/templates/next-app-router-quickstart/src/app/layout.tsx +++ b/integration/templates/next-app-router-quickstart/src/app/layout.tsx @@ -11,7 +11,13 @@ export const metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + {children} diff --git a/integration/templates/next-app-router/src/app/jwt-v2-organizations/(tests)/has-ssr/page.tsx b/integration/templates/next-app-router/src/app/jwt-v2-organizations/(tests)/has-ssr/page.tsx index a7d7102bb50..9c937cc10fb 100644 --- a/integration/templates/next-app-router/src/app/jwt-v2-organizations/(tests)/has-ssr/page.tsx +++ b/integration/templates/next-app-router/src/app/jwt-v2-organizations/(tests)/has-ssr/page.tsx @@ -3,7 +3,14 @@ import { SSR } from './client'; export default function Page() { return ( - + ); diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx index 2e56184f39d..42341d04adc 100644 --- a/integration/templates/next-app-router/src/app/layout.tsx +++ b/integration/templates/next-app-router/src/app/layout.tsx @@ -13,7 +13,8 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( {children}; + return ( + + {children} + + ); } diff --git a/integration/templates/nuxt-node/nuxt.config.js b/integration/templates/nuxt-node/nuxt.config.js index f60e469817f..68df62e2b2b 100644 --- a/integration/templates/nuxt-node/nuxt.config.js +++ b/integration/templates/nuxt-node/nuxt.config.js @@ -1,5 +1,12 @@ export default defineNuxtConfig({ modules: ['@clerk/nuxt'], + clerk: { + appearance: { + options: { + showOptionalFields: true, + }, + }, + }, devtools: { enabled: false, }, diff --git a/integration/templates/react-cra/src/index.tsx b/integration/templates/react-cra/src/index.tsx index 5a20abd8759..65271a25b7e 100644 --- a/integration/templates/react-cra/src/index.tsx +++ b/integration/templates/react-cra/src/index.tsx @@ -11,6 +11,11 @@ root.render( publishableKey={process.env.REACT_APP_CLERK_PUBLISHABLE_KEY as string} clerkJSUrl={process.env.REACT_APP_CLERK_JS as string} clerkUiUrl={process.env.REACT_APP_CLERK_UI as string} + appearance={{ + options: { + showOptionalFields: true, + }, + }} > diff --git a/integration/templates/react-router-library/src/main.tsx b/integration/templates/react-router-library/src/main.tsx index 46ab36679fd..82c2ee8e68d 100644 --- a/integration/templates/react-router-library/src/main.tsx +++ b/integration/templates/react-router-library/src/main.tsx @@ -6,11 +6,22 @@ import './index.css'; import App from './App.tsx'; const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; +const CLERK_JS_URL = import.meta.env.VITE_CLERK_JS_URL; +const CLERK_UI_URL = import.meta.env.VITE_CLERK_UI_URL; createRoot(document.getElementById('root')!).render( - + +
diff --git a/integration/templates/react-vite/src/main.tsx b/integration/templates/react-vite/src/main.tsx index f35d944287b..de98ba747f7 100644 --- a/integration/templates/react-vite/src/main.tsx +++ b/integration/templates/react-vite/src/main.tsx @@ -34,12 +34,21 @@ const Root = () => { clerkUiUrl={import.meta.env.VITE_CLERK_UI_URL as string} routerPush={(to: string) => navigate(to)} routerReplace={(to: string) => navigate(to, { replace: true })} + appearance={{ + colors: { + colorPrimary: 'red', + }, + options: { + showOptionalFields: true, + }, + }} experimental={{ persistClient: import.meta.env.VITE_EXPERIMENTAL_PERSIST_CLIENT ? import.meta.env.VITE_EXPERIMENTAL_PERSIST_CLIENT === 'true' : undefined, }} > + is this the correct app?
); diff --git a/integration/templates/tanstack-react-start/src/routes/__root.tsx b/integration/templates/tanstack-react-start/src/routes/__root.tsx index ecf10d8fcc2..67915716e14 100644 --- a/integration/templates/tanstack-react-start/src/routes/__root.tsx +++ b/integration/templates/tanstack-react-start/src/routes/__root.tsx @@ -22,7 +22,15 @@ function RootComponent() { function RootDocument({ children }: { children: React.ReactNode }) { return ( - + diff --git a/integration/templates/vue-vite/src/main.ts b/integration/templates/vue-vite/src/main.ts index d8980dd44a8..f3c1a9e4638 100644 --- a/integration/templates/vue-vite/src/main.ts +++ b/integration/templates/vue-vite/src/main.ts @@ -10,6 +10,11 @@ app.use(clerkPlugin, { clerkJSUrl: import.meta.env.VITE_CLERK_JS_URL, clerkUiUrl: import.meta.env.VITE_CLERK_UI_URL, clerkJSVersion: import.meta.env.VITE_CLERK_JS_VERSION, + appearance: { + options: { + showOptionalFields: true, + }, + }, }); app.use(router); app.mount('#app'); diff --git a/integration/testUtils/emailService.ts b/integration/testUtils/emailService.ts index 0de10de747b..c1cb085494d 100644 --- a/integration/testUtils/emailService.ts +++ b/integration/testUtils/emailService.ts @@ -12,8 +12,6 @@ export const createEmailService = () => { const fetcher = async (url: string | URL, init?: RequestInit) => { const headers = new Headers(init?.headers || {}); - // eslint-disable-next-line turbo/no-undeclared-env-vars - headers.set('Mailsac-Key', process.env.MAILSAC_API_KEY); return fetch(url, { ...init, headers }); }; diff --git a/integration/testUtils/usersService.ts b/integration/testUtils/usersService.ts index 52814990c92..eecab30dbf4 100644 --- a/integration/testUtils/usersService.ts +++ b/integration/testUtils/usersService.ts @@ -3,6 +3,19 @@ import { faker } from '@faker-js/faker'; import { hash } from '../models/helpers'; +async function withErrorLogging(operation: string, fn: () => Promise): Promise { + try { + return await fn(); + } catch (e: any) { + console.error(`[usersService] ${operation} failed:`); + console.error(' Status:', e.status); + console.error(' Message:', e.message); + console.error(' Errors:', JSON.stringify(e.errors, null, 2)); + console.error(' Clerk Trace ID:', e.clerkTraceId); + throw e; + } +} + type FakeUserOptions = { /** * A fictional email is an email that contains +clerk_test and can always be verified using 424242 as the OTP. No email will be sent. @@ -126,15 +139,17 @@ export const createUserService = (clerkClient: ClerkClient) => { }; }, createBapiUser: async fakeUser => { - return await clerkClient.users.createUser({ - emailAddress: fakeUser.email !== undefined ? [fakeUser.email] : undefined, - password: fakeUser.password, - firstName: fakeUser.firstName, - lastName: fakeUser.lastName, - phoneNumber: fakeUser.phoneNumber !== undefined ? [fakeUser.phoneNumber] : undefined, - username: fakeUser.username, - skipPasswordRequirement: fakeUser.password === undefined, - }); + return withErrorLogging('createBapiUser', () => + clerkClient.users.createUser({ + emailAddress: fakeUser.email !== undefined ? [fakeUser.email] : undefined, + password: fakeUser.password, + firstName: fakeUser.firstName, + lastName: fakeUser.lastName, + phoneNumber: fakeUser.phoneNumber !== undefined ? [fakeUser.phoneNumber] : undefined, + username: fakeUser.username, + skipPasswordRequirement: fakeUser.password === undefined, + }), + ); }, getOrCreateUser: async fakeUser => { const existingUser = await self.getUser({ email: fakeUser.email }); @@ -147,10 +162,12 @@ export const createUserService = (clerkClient: ClerkClient) => { let id = opts.id; if (!id) { - const { data: users } = await clerkClient.users.getUserList({ - emailAddress: [opts.email], - phoneNumber: [opts.phoneNumber], - }); + const { data: users } = await withErrorLogging('getUserList', () => + clerkClient.users.getUserList({ + emailAddress: [opts.email], + phoneNumber: [opts.phoneNumber], + }), + ); id = users[0]?.id; } @@ -159,12 +176,12 @@ export const createUserService = (clerkClient: ClerkClient) => { return; } - await clerkClient.users.deleteUser(id); + await withErrorLogging('deleteUser', () => clerkClient.users.deleteUser(id)); }, getUser: async (opts: { id?: string; email?: string }) => { if (opts.id) { try { - const user = await clerkClient.users.getUser(opts.id); + const user = await withErrorLogging('getUser', () => clerkClient.users.getUser(opts.id)); return user; } catch (err) { console.log(`Error fetching user "${opts.id}": ${err.message}`); @@ -173,7 +190,9 @@ export const createUserService = (clerkClient: ClerkClient) => { } if (opts.email) { - const { data: users } = await clerkClient.users.getUserList({ emailAddress: [opts.email] }); + const { data: users } = await withErrorLogging('getUserList', () => + clerkClient.users.getUserList({ emailAddress: [opts.email] }), + ); if (users.length > 0) { return users[0]; } else { @@ -186,33 +205,41 @@ export const createUserService = (clerkClient: ClerkClient) => { }, createFakeOrganization: async userId => { const name = faker.animal.dog(); - const organization = await clerkClient.organizations.createOrganization({ - name: faker.animal.dog(), - createdBy: userId, - }); + const organization = await withErrorLogging('createOrganization', () => + clerkClient.organizations.createOrganization({ + name: faker.animal.dog(), + createdBy: userId, + }), + ); return { name, organization, - delete: () => clerkClient.organizations.deleteOrganization(organization.id), + delete: () => + withErrorLogging('deleteOrganization', () => clerkClient.organizations.deleteOrganization(organization.id)), } satisfies FakeOrganization; }, createFakeAPIKey: async (userId: string) => { const TWENTY_MINUTES = 20 * 60; - const apiKey = await clerkClient.apiKeys.create({ - subject: userId, - name: `Integration Test - ${faker.string.uuid()}`, - secondsUntilExpiration: TWENTY_MINUTES, - }); + const apiKey = await withErrorLogging('createAPIKey', () => + clerkClient.apiKeys.create({ + subject: userId, + name: `Integration Test - ${faker.string.uuid()}`, + secondsUntilExpiration: TWENTY_MINUTES, + }), + ); return { apiKey, secret: apiKey.secret ?? '', - revoke: () => clerkClient.apiKeys.revoke({ apiKeyId: apiKey.id, revocationReason: 'For testing purposes' }), + revoke: () => + withErrorLogging('revokeAPIKey', () => + clerkClient.apiKeys.revoke({ apiKeyId: apiKey.id, revocationReason: 'For testing purposes' }), + ), } satisfies FakeAPIKey; }, passwordCompromised: async (userId: string) => { - await clerkClient.users.__experimental_passwordCompromised(userId); + await withErrorLogging('passwordCompromised', () => clerkClient.users.__experimental_passwordCompromised(userId)); }, }; diff --git a/integration/tests/billing-hooks.test.ts b/integration/tests/billing-hooks.test.ts index 48c441e6104..daa474f52a4 100644 --- a/integration/tests/billing-hooks.test.ts +++ b/integration/tests/billing-hooks.test.ts @@ -1,10 +1,9 @@ import { expect, test } from '@playwright/test'; -import { appConfigs } from '../presets'; import type { FakeUser } from '../testUtils'; import { createTestUtils, testAgainstRunningApps } from '../testUtils'; -testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('billing hooks @billing', ({ app }) => { +testAgainstRunningApps({})('billing hooks @billing', ({ app }) => { test.describe.configure({ mode: 'parallel' }); test.skip(!app.name.includes('next'), 'Skipping: Only runs on next'); diff --git a/integration/tests/custom-flows/sign-in.test.ts b/integration/tests/custom-flows/sign-in.test.ts index c8fe5851790..30a34e0d190 100644 --- a/integration/tests/custom-flows/sign-in.test.ts +++ b/integration/tests/custom-flows/sign-in.test.ts @@ -1,5 +1,3 @@ -import { parsePublishableKey } from '@clerk/shared/keys'; -import { clerkSetup } from '@clerk/testing/playwright'; import { expect, test } from '@playwright/test'; import type { Application } from '../../models/application'; @@ -19,20 +17,6 @@ test.describe('Custom Flows Sign In @custom', () => { await app.withEnv(appConfigs.envs.withEmailCodes); await app.dev(); - const publishableKey = appConfigs.envs.withEmailCodes.publicVariables.get('CLERK_PUBLISHABLE_KEY'); - const secretKey = appConfigs.envs.withEmailCodes.privateVariables.get('CLERK_SECRET_KEY'); - const apiUrl = appConfigs.envs.withEmailCodes.privateVariables.get('CLERK_API_URL'); - const { frontendApi: frontendApiUrl } = parsePublishableKey(publishableKey); - - await clerkSetup({ - publishableKey, - frontendApiUrl, - secretKey, - // @ts-expect-error - apiUrl, - dotenv: false, - }); - const u = createTestUtils({ app }); fakeUser = u.services.users.createFakeUser({ fictionalEmail: true, diff --git a/integration/tests/custom-flows/sign-up.test.ts b/integration/tests/custom-flows/sign-up.test.ts index 02e16f02051..f7c16143755 100644 --- a/integration/tests/custom-flows/sign-up.test.ts +++ b/integration/tests/custom-flows/sign-up.test.ts @@ -1,5 +1,3 @@ -import { parsePublishableKey } from '@clerk/shared/keys'; -import { clerkSetup } from '@clerk/testing/playwright'; import { expect, test } from '@playwright/test'; import type { Application } from '../../models/application'; @@ -19,20 +17,6 @@ test.describe('Custom Flows Sign Up @custom', () => { await app.withEnv(appConfigs.envs.withEmailCodes); await app.dev(); - const publishableKey = appConfigs.envs.withEmailCodes.publicVariables.get('CLERK_PUBLISHABLE_KEY'); - const secretKey = appConfigs.envs.withEmailCodes.privateVariables.get('CLERK_SECRET_KEY'); - const apiUrl = appConfigs.envs.withEmailCodes.privateVariables.get('CLERK_API_URL'); - const { frontendApi: frontendApiUrl } = parsePublishableKey(publishableKey); - - await clerkSetup({ - publishableKey, - frontendApiUrl, - secretKey, - // @ts-expect-error - apiUrl, - dotenv: false, - }); - const u = createTestUtils({ app }); fakeUser = u.services.users.createFakeUser({ fictionalEmail: true, diff --git a/integration/tests/global.teardown.ts b/integration/tests/global.teardown.ts index 00e9296dca5..7445ab191c7 100644 --- a/integration/tests/global.teardown.ts +++ b/integration/tests/global.teardown.ts @@ -12,7 +12,7 @@ setup('teardown long running apps', async () => { await killClerkJsHttpServer(); await killClerkUiHttpServer(); - if (appUrl || !constants.CLEANUP) { + if (appUrl || !constants.E2E_CLEANUP) { // if appUrl is provided, it means that the user is running an app manually console.log('Skipping cleanup'); return; diff --git a/integration/tests/machine-auth/component.test.ts b/integration/tests/machine-auth/component.test.ts index b37527e0f36..d9b308cf355 100644 --- a/integration/tests/machine-auth/component.test.ts +++ b/integration/tests/machine-auth/component.test.ts @@ -1,9 +1,10 @@ import type { Page } from '@playwright/test'; import { expect, test } from '@playwright/test'; +import type { Application } from '../../models/application'; import { appConfigs } from '../../presets'; import type { FakeOrganization, FakeUser } from '../../testUtils'; -import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; +import { createTestUtils } from '../../testUtils'; const mockAPIKeysEnvironmentSettings = async ( page: Page, @@ -27,16 +28,21 @@ const mockAPIKeysEnvironmentSettings = async ( }); }; -testAgainstRunningApps({ - withEnv: [appConfigs.envs.withAPIKeys], - withPattern: ['withMachine.next.appRouter'], -})('api keys component @machine', ({ app }) => { +test.describe('api keys component @machine', () => { test.describe.configure({ mode: 'serial' }); + let app: Application; let fakeAdmin: FakeUser; let fakeOrganization: FakeOrganization; test.beforeAll(async () => { + test.setTimeout(90_000); // Wait for app to be ready + app = await appConfigs.next.appRouter.clone().commit(); + + await app.setup(); + await app.withEnv(appConfigs.envs.withAPIKeys); + await app.dev(); + const u = createTestUtils({ app }); fakeAdmin = u.services.users.createFakeUser(); const admin = await u.services.users.createBapiUser(fakeAdmin); diff --git a/integration/tests/pricing-table.test.ts b/integration/tests/pricing-table.test.ts index bdc61418b6c..cc7ea3b5dd4 100644 --- a/integration/tests/pricing-table.test.ts +++ b/integration/tests/pricing-table.test.ts @@ -1,11 +1,10 @@ import type { Locator } from '@playwright/test'; import { expect, test } from '@playwright/test'; -import { appConfigs } from '../presets'; import type { FakeUser } from '../testUtils'; import { createTestUtils, testAgainstRunningApps } from '../testUtils'; -testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing table @billing', ({ app }) => { +testAgainstRunningApps({})('pricing table @billing', ({ app }) => { test.describe.configure({ mode: 'parallel' }); let fakeUser: FakeUser; @@ -236,6 +235,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await u.po.pricingTable.startCheckout({ planSlug: 'plus' }); await u.po.checkout.waitForMounted(); await expect(u.po.page.getByText('Checkout')).toBeVisible(); + await page.pause(); await expect(u.po.page.getByText(/^Add an email address$/i)).toBeVisible(); const newFakeUser = u.services.users.createFakeUser(); @@ -639,7 +639,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await fakeUser.deleteIfExists(); }); - test('displays notice then plan cannot change', async ({ page, context }) => { + test('displays notice the plan cannot change', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); const fakeUser = u.services.users.createFakeUser(); @@ -657,7 +657,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl await u.po.checkout.fillTestCard(); await u.po.checkout.clickPayOrSubscribe(); await expect(u.po.page.getByText('Payment was successful!')).toBeVisible(); - await u.po.checkout.confirmAndContinue(); await u.po.pricingTable.startCheckout({ planSlug: 'pro', shouldSwitch: true, period: 'monthly' }); await u.po.checkout.waitForMounted(); diff --git a/package.json b/package.json index d5ec6d85fb1..c068833cad3 100644 --- a/package.json +++ b/package.json @@ -34,17 +34,17 @@ "test:integration:ap-flows": "pnpm test:integration:base --grep @ap-flows", "test:integration:astro": "E2E_APP_ID=astro.* pnpm test:integration:base --grep @astro", "test:integration:base": "pnpm playwright test --config integration/playwright.config.ts", - "test:integration:billing": "E2E_APP_ID=withBilling.* pnpm test:integration:base --grep @billing", + "test:integration:billing": "E2E_DEBUG=1 E2E_APP_ID=withBillingJwtV2.* pnpm test:integration:base --grep @billing", "test:integration:cleanup": "pnpm playwright test --config integration/playwright.cleanup.config.ts", "test:integration:custom": "pnpm test:integration:base --grep @custom", "test:integration:deployment:nextjs": "pnpm playwright test --config integration/playwright.deployments.config.ts", "test:integration:expo-web:disabled": "E2E_APP_ID=expo.expo-web pnpm test:integration:base --grep @expo-web", - "test:integration:express": "E2E_DEBUG=1 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:express": "E2E_DEBUG=1 E2E_DEBUG=1 E2E_APP_ID=express.* pnpm test:integration:base --grep @express", + "test:integration:generic": "E2E_DEBUG=1 E2E_APP_ID=react.vite.*,next.appRouter.withEmailCodes* pnpm test:integration:base --grep @generic", "test:integration:handshake": "DISABLE_WEB_SECURITY=true E2E_APP_1_ENV_KEY=sessions-prod-1 E2E_SESSIONS_APP_1_HOST=multiple-apps-e2e.clerk.app pnpm test:integration:base --grep @handshake", "test:integration:handshake:staging": "DISABLE_WEB_SECURITY=true E2E_APP_1_ENV_KEY=clerkstage-sessions-prod-1 E2E_SESSIONS_APP_1_HOST=clerkstage-sessions-prod-1-e2e.clerk.app pnpm test:integration:base --grep @handshake", "test:integration:localhost": "pnpm test:integration:base --grep @localhost", - "test:integration:machine": "E2E_APP_ID=withMachine.* pnpm test:integration:base --grep @machine", + "test:integration:machine": "E2E_DEBUG=1 pnpm test:integration:base --grep @machine", "test:integration:nextjs": "E2E_APP_ID=next.appRouter.* pnpm test:integration:base --grep @nextjs", "test:integration:nuxt": "E2E_DEBUG=1 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", diff --git a/packages/astro/src/internal/create-clerk-instance.ts b/packages/astro/src/internal/create-clerk-instance.ts index 73a4473c660..e1cbd520144 100644 --- a/packages/astro/src/internal/create-clerk-instance.ts +++ b/packages/astro/src/internal/create-clerk-instance.ts @@ -36,30 +36,20 @@ function createNavigationHandler( const createClerkInstance = runOnce(createClerkInstanceInternal); async function createClerkInstanceInternal(options?: AstroClerkCreateInstanceParams) { - let clerkJSInstance = window.Clerk; - let clerkUiCtor: Promise | undefined; - - if (!clerkJSInstance) { - // Load both clerk-js and clerk-ui in parallel - const clerkPromise = loadClerkJsScript(options); - clerkUiCtor = options?.clerkUiCtor - ? Promise.resolve(options.clerkUiCtor) - : loadClerkUiScript(options).then(() => { - if (!window.__internal_ClerkUiCtor) { - throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); - } - // After the check, TypeScript knows it's defined - return window.__internal_ClerkUiCtor; - }); - - await clerkPromise; - - if (!window.Clerk) { - throw new Error('Failed to download latest ClerkJS. Contact support@clerk.com.'); - } - clerkJSInstance = window.Clerk; + // Load clerk-js and clerk-ui in parallel. + // Both functions return early if the scripts are already loaded + // (e.g., via middleware-injected script tags in the HTML head). + const clerkJsChunk = getClerkJsEntryChunk(options); + const clerkUiCtor = getClerkUiEntryChunk(options); + + await clerkJsChunk; + + if (!window.Clerk) { + throw new Error('Failed to download latest ClerkJS. Contact support@clerk.com.'); } + const clerkJSInstance = window.Clerk; + if (!$clerk.get()) { $clerk.set(clerkJSInstance); } @@ -110,4 +100,32 @@ function updateClerkOptions(options: AstroClerkUpdateOption void (clerk as any).__internal_updateProps(updateOptions); } +/** + * Loads clerk-js script if not already loaded. + * Returns early if window.Clerk already exists. + */ +async function getClerkJsEntryChunk(options?: AstroClerkCreateInstanceParams): Promise { + await loadClerkJsScript(options); +} + +/** + * Gets the ClerkUI constructor, either from options or by loading the script. + * Returns early if window.__internal_ClerkUiCtor already exists. + */ +async function getClerkUiEntryChunk( + options?: AstroClerkCreateInstanceParams, +): Promise { + if (options?.clerkUiCtor) { + return options.clerkUiCtor; + } + + await loadClerkUiScript(options); + + if (!window.__internal_ClerkUiCtor) { + throw new Error('Failed to download latest Clerk UI. Contact support@clerk.com.'); + } + + return window.__internal_ClerkUiCtor; +} + export { createClerkInstance, updateClerkOptions }; diff --git a/packages/clerk-js/src/core/resources/BillingCheckout.ts b/packages/clerk-js/src/core/resources/BillingCheckout.ts index 0bd0736ebbd..0dbadd220fa 100644 --- a/packages/clerk-js/src/core/resources/BillingCheckout.ts +++ b/packages/clerk-js/src/core/resources/BillingCheckout.ts @@ -109,7 +109,6 @@ export const createSignals = () => { const resource = resourceSignal().resource; const error = errorSignal().error; const fetchStatus = fetchSignal().status; - const errors = errorsToParsedErrors(error, {}); return { errors: errors, fetchStatus, checkout: resource }; }, diff --git a/packages/clerk-js/src/core/resources/DevTools.ts b/packages/clerk-js/src/core/resources/DevTools.ts deleted file mode 100644 index 9a517858604..00000000000 --- a/packages/clerk-js/src/core/resources/DevTools.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ClerkResourceJSON, DevToolsResource, EnableEnvironmentSettingParams } from '@clerk/shared/types'; - -import { BaseResource } from './Base'; - -/** - * @internal - */ -export class DevTools extends BaseResource implements DevToolsResource { - pathRoot = '/dev_tools'; - - protected fromJSON(_data: ClerkResourceJSON | null): this { - return this; - } - - async __internal_enableEnvironmentSetting(params: EnableEnvironmentSettingParams) { - await this._basePatch({ - path: `${this.pathRoot}/enable_environment_setting`, - body: params, - }); - } -} diff --git a/packages/clerk-js/src/core/resources/Environment.ts b/packages/clerk-js/src/core/resources/Environment.ts index f057787a747..5e961e3f354 100644 --- a/packages/clerk-js/src/core/resources/Environment.ts +++ b/packages/clerk-js/src/core/resources/Environment.ts @@ -2,6 +2,7 @@ import type { AuthConfigResource, CommerceSettingsResource, DisplayConfigResource, + EnableEnvironmentSettingParams, EnvironmentJSON, EnvironmentJSONSnapshot, EnvironmentResource, @@ -101,4 +102,11 @@ export class Environment extends BaseResource implements EnvironmentResource { protect_config: this.protectConfig.__internal_toSnapshot(), }; } + + async __internal_enableEnvironmentSetting(params: EnableEnvironmentSettingParams) { + await this._basePatch({ + path: `/dev_tools/enable_environment_setting`, + body: params, + }); + } } diff --git a/packages/clerk-js/src/core/resources/index.ts b/packages/clerk-js/src/core/resources/index.ts index d137d5d588b..b07196edac8 100644 --- a/packages/clerk-js/src/core/resources/index.ts +++ b/packages/clerk-js/src/core/resources/index.ts @@ -1,7 +1,6 @@ export * from './AuthConfig'; export * from './Client'; export * from './DeletedObject'; -export * from './DevTools'; export * from './DisplayConfig'; export * from './EmailAddress'; export * from './Environment'; diff --git a/packages/clerk-js/src/core/signals.ts b/packages/clerk-js/src/core/signals.ts index 2046eca114c..b200154be15 100644 --- a/packages/clerk-js/src/core/signals.ts +++ b/packages/clerk-js/src/core/signals.ts @@ -62,7 +62,6 @@ export function errorsToParsedErrors>( function isFieldError(error: ClerkAPIError): boolean { return 'meta' in error && error.meta && 'paramName' in error.meta && error.meta.paramName !== undefined; } - const hasFieldErrors = error.errors.some(isFieldError); if (hasFieldErrors) { error.errors.forEach(error => { diff --git a/packages/nuxt/src/global.d.ts b/packages/nuxt/src/global.d.ts index 7cde099d9c9..7220a9070ab 100644 --- a/packages/nuxt/src/global.d.ts +++ b/packages/nuxt/src/global.d.ts @@ -16,7 +16,18 @@ declare module 'nuxt/schema' { }; } interface PublicRuntimeConfig { - clerk: PluginOptions; + clerk: Omit & { + /** + * The URL that `@clerk/clerk-js` should be hot-loaded from. + * Supports NUXT_PUBLIC_CLERK_JS_URL env var. + */ + jsUrl?: string; + /** + * The URL that `@clerk/ui` should be hot-loaded from. + * Supports NUXT_PUBLIC_CLERK_UI_URL env var. + */ + uiUrl?: string; + }; } } diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 33dc489a690..c5d42b4b6c3 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -64,8 +64,10 @@ export default defineNuxtModule({ signUpForceRedirectUrl: options.signUpForceRedirectUrl, signUpUrl: options.signUpUrl, domain: options.domain, - clerkJSUrl: options.clerkJSUrl, - clerkUiUrl: options.clerkUiUrl, + // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUiUrl to support + // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. + jsUrl: options.clerkJSUrl, + uiUrl: options.clerkUiUrl, clerkJSVariant: options.clerkJSVariant, clerkJSVersion: options.clerkJSVersion, isSatellite: options.isSatellite, diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index 077a2dc019b..8879860afb6 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -17,9 +17,13 @@ export default defineNuxtPlugin(nuxtApp => { } const runtimeConfig = useRuntimeConfig(); + const clerkConfig = runtimeConfig.public.clerk ?? {}; nuxtApp.vueApp.use(clerkPlugin as any, { - ...(runtimeConfig.public.clerk ?? {}), + ...clerkConfig, + // Map jsUrl/uiUrl to clerkJSUrl/clerkUiUrl as expected by the Vue plugin + clerkJSUrl: clerkConfig.jsUrl, + clerkUiUrl: clerkConfig.uiUrl, sdkMetadata: { name: PACKAGE_NAME, version: PACKAGE_VERSION, diff --git a/packages/shared/package.json b/packages/shared/package.json index 82054683a52..c38c2062c9f 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -20,6 +20,16 @@ "default": "./dist/runtime/index.js" } }, + "./internal/clerk-js/*": { + "import": { + "types": "./dist/runtime/internal/clerk-js/*.d.mts", + "default": "./dist/runtime/internal/clerk-js/*.mjs" + }, + "require": { + "types": "./dist/runtime/internal/clerk-js/*.d.ts", + "default": "./dist/runtime/internal/clerk-js/*.js" + } + }, "./*": { "import": { "types": "./dist/runtime/*.d.mts", @@ -90,16 +100,6 @@ "default": "./dist/types/index.js" } }, - "./internal/clerk-js/*": { - "import": { - "types": "./dist/runtime/internal/clerk-js/*.d.mts", - "default": "./dist/runtime/internal/clerk-js/*.mjs" - }, - "require": { - "types": "./dist/runtime/internal/clerk-js/*.d.ts", - "default": "./dist/runtime/internal/clerk-js/*.js" - } - }, "./package.json": "./package.json" }, "files": [ diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 82ff1de27e8..48634e86809 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -3,3 +3,4 @@ throw new Error( ); export {}; +// Force rebuild for explicit exports (replacing wildcard) diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index e3c096d44f5..7660f5581d9 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -55,6 +55,51 @@ function isClerkGlobalProperlyLoaded(prop: 'Clerk' | '__internal_ClerkUiCtor'): const isClerkProperlyLoaded = () => isClerkGlobalProperlyLoaded('Clerk'); const isClerkUiProperlyLoaded = () => isClerkGlobalProperlyLoaded('__internal_ClerkUiCtor'); +/** + * Checks if an existing script has a request error using Performance API. + * + * @param scriptUrl - The URL of the script to check. + * @returns True if the script has failed to load due to a network/HTTP error. + */ +function hasScriptRequestError(scriptUrl: string): boolean { + if (typeof window === 'undefined' || !window.performance) { + return false; + } + + const entries = performance.getEntriesByName(scriptUrl, 'resource') as PerformanceResourceTiming[]; + + if (entries.length === 0) { + return false; + } + + const scriptEntry = entries[entries.length - 1]; + + // transferSize === 0 with responseEnd === 0 indicates network failure + // transferSize === 0 with responseEnd > 0 might be a 4xx/5xx error or blocked request + if (scriptEntry.transferSize === 0 && scriptEntry.decodedBodySize === 0) { + // If there was no response at all, it's definitely an error + if (scriptEntry.responseEnd === 0) { + return true; + } + // If we got a response but no content, likely an HTTP error (4xx/5xx) + if (scriptEntry.responseEnd > 0 && scriptEntry.responseStart > 0) { + return true; + } + + if ('responseStatus' in scriptEntry) { + const status = (scriptEntry as any).responseStatus; + if (status >= 400) { + return true; + } + if (scriptEntry.responseStatus === 0) { + return true; + } + } + } + + return false; +} + /** * Hotloads the Clerk JS script with robust failure detection. * @@ -88,20 +133,30 @@ export const loadClerkJsScript = async (opts?: LoadClerkJsScriptOptions): Promis return null; } - const existingScript = document.querySelector('script[data-clerk-js-script]'); - - if (existingScript) { - return waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith()); - } - if (!opts?.publishableKey) { errorThrower.throwMissingPublishableKeyError(); return null; } + const scriptUrl = clerkJsScriptUrl(opts); + const existingScript = document.querySelector('script[data-clerk-js-script]'); + + if (existingScript) { + if (hasScriptRequestError(scriptUrl)) { + existingScript.remove(); + } else { + try { + await waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith(), existingScript); + return null; + } catch { + existingScript.remove(); + } + } + } + const loadPromise = waitForPredicateWithTimeout(timeout, isClerkProperlyLoaded, rejectWith()); - loadScript(clerkJsScriptUrl(opts), { + loadScript(scriptUrl, { async: true, crossOrigin: 'anonymous', nonce: opts.nonce, @@ -125,19 +180,30 @@ export const loadClerkUiScript = async (opts?: LoadClerkUiScriptOptions): Promis return null; } - const existingScript = document.querySelector('script[data-clerk-ui-script]'); - - if (existingScript) { - return waitForPredicateWithTimeout(timeout, isClerkUiProperlyLoaded, rejectWith()); - } - if (!opts?.publishableKey) { errorThrower.throwMissingPublishableKeyError(); return null; } + const scriptUrl = clerkUiScriptUrl(opts); + const existingScript = document.querySelector('script[data-clerk-ui-script]'); + + if (existingScript) { + if (hasScriptRequestError(scriptUrl)) { + existingScript.remove(); + } else { + try { + await waitForPredicateWithTimeout(timeout, isClerkUiProperlyLoaded, rejectWith(), existingScript); + return null; + } catch { + existingScript.remove(); + } + } + } + const loadPromise = waitForPredicateWithTimeout(timeout, isClerkUiProperlyLoaded, rejectWith()); - loadScript(clerkUiScriptUrl(opts), { + + loadScript(scriptUrl, { async: true, crossOrigin: 'anonymous', nonce: opts.nonce, @@ -223,6 +289,7 @@ function waitForPredicateWithTimeout( timeoutMs: number, predicate: () => boolean, rejectWith: Error, + existingScript?: HTMLScriptElement, ): Promise { return new Promise((resolve, reject) => { let resolved = false; @@ -232,6 +299,12 @@ function waitForPredicateWithTimeout( clearInterval(pollInterval); }; + // Bail out early if the script fails to load, instead of waiting for the entire timeout + existingScript?.addEventListener('error', () => { + cleanup(timeoutId, pollInterval); + reject(rejectWith); + }); + const checkAndResolve = () => { if (resolved) { return; diff --git a/packages/shared/src/loadScript.ts b/packages/shared/src/loadScript.ts index c9fc4a4536d..54e617ebc54 100644 --- a/packages/shared/src/loadScript.ts +++ b/packages/shared/src/loadScript.ts @@ -36,8 +36,6 @@ export async function loadScript(src = '', opts: LoadScriptOptions): Promise { - console.log('this loaded ', src); - script.remove(); resolve(script); }); @@ -56,7 +54,6 @@ export async function loadScript(src = '', opts: LoadScriptOptions): Promise { - console.log('nikos 3', _, iterations); return iterations <= 5; }, }); diff --git a/packages/shared/src/types/environment.ts b/packages/shared/src/types/environment.ts index 5a81ea75d8f..1176bc03226 100644 --- a/packages/shared/src/types/environment.ts +++ b/packages/shared/src/types/environment.ts @@ -1,6 +1,7 @@ import type { APIKeysSettingsResource } from './apiKeysSettings'; import type { AuthConfigResource } from './authConfig'; import type { CommerceSettingsResource } from './commerceSettings'; +import type { EnableEnvironmentSettingParams } from './devtools'; import type { DisplayConfigResource } from './displayConfig'; import type { OrganizationSettingsResource } from './organizationSettings'; import type { ProtectConfigResource } from './protectConfig'; @@ -23,4 +24,5 @@ export interface EnvironmentResource extends ClerkResource { maintenanceMode: boolean; clientDebugMode: boolean; __internal_toSnapshot: () => EnvironmentJSONSnapshot; + __internal_enableEnvironmentSetting: (params: EnableEnvironmentSettingParams) => Promise; } diff --git a/packages/ui/package.json b/packages/ui/package.json index 44c4fa809ce..c28f8f99767 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -62,7 +62,8 @@ "showerrors": "tsc", "test:ci": "vitest --maxWorkers=70%", "test:coverage": "vitest --collectCoverage && open coverage/lcov-report/index.html", - "test:disabled": "vitest" + "test:disabled": "vitest", + "type-check": "tsc --noEmit" }, "dependencies": { "@clerk/localizations": "workspace:^", diff --git a/packages/ui/rspack.config.js b/packages/ui/rspack.config.js index fa31862617d..b3129302689 100644 --- a/packages/ui/rspack.config.js +++ b/packages/ui/rspack.config.js @@ -44,6 +44,7 @@ const common = ({ mode, variant }) => { PACKAGE_VERSION: JSON.stringify(packageJSON.version), __PKG_VERSION__: JSON.stringify(packageJSON.version), PACKAGE_NAME: JSON.stringify(packageJSON.name), + __BUILD_DISABLE_RHC__: JSON.stringify(false), }), new rspack.EnvironmentPlugin({ NODE_ENV: mode, @@ -166,7 +167,7 @@ const devConfig = (mode, env) => { rules: [svgLoader(), ...typescriptLoaderDev()], }, plugins: [new ReactRefreshPlugin({ overlay: { sockHost: devUrl.host } })], - devtool: 'eval-cheap-source-map', + devtool: 'eval-source-map', output: { publicPath: `${devUrl.origin}/npm/`, crossOriginLoading: 'anonymous', @@ -175,6 +176,8 @@ const devConfig = (mode, env) => { }, optimization: { minimize: false, + usedExports: false, + providedExports: false, }, devServer: { allowedHosts: ['all'], diff --git a/packages/ui/src/Components.tsx b/packages/ui/src/Components.tsx index dcf3447aeb4..a26aac0f459 100644 --- a/packages/ui/src/Components.tsx +++ b/packages/ui/src/Components.tsx @@ -17,14 +17,13 @@ import type { UserProfileProps, WaitlistProps, } from '@clerk/shared/types'; - -import type { Appearance } from './internal/appearance'; import { createDeferredPromise } from '@clerk/shared/utils'; import React, { Suspense } from 'react'; import type { AppearanceCascade } from './customizables/parseAppearance'; // NOTE: Using `./hooks` instead of `./hooks/useClerkModalStateParams` will increase the bundle size import { useClerkModalStateParams } from './hooks/useClerkModalStateParams'; +import type { Appearance } from './internal/appearance'; import type { ClerkComponentName } from './lazyModules/components'; import { BlankCaptchaModal, @@ -293,6 +292,10 @@ const Components = (props: ComponentsProps) => { impersonationFab: false, }); + console.log('props.options.appearance', JSON.stringify(props.options.appearance)); + console.log('props.options', JSON.stringify(props.options)); + console.log('state.appearance', JSON.stringify(state.appearance)); + const { googleOneTapModal, signInModal, diff --git a/packages/ui/src/components/Checkout/CheckoutPage.tsx b/packages/ui/src/components/Checkout/CheckoutPage.tsx index 8fd50eabb86..a159848038b 100644 --- a/packages/ui/src/components/Checkout/CheckoutPage.tsx +++ b/packages/ui/src/components/Checkout/CheckoutPage.tsx @@ -61,7 +61,13 @@ const FetchStatus = ({ const internalFetchStatus = useMemo(() => { if (errors.global) { - const errorCodes = errors.global.map(e => e.code); + const errorCodes = errors.global.flatMap(e => { + if (e.isClerkApiResponseError()) { + return e.errors.map(e => e.code); + } + }); + + console.log({ errorCodes }); if (errorCodes.includes('missing_payer_email')) { return 'missing_payer_email'; diff --git a/packages/ui/src/components/Checkout/parts.tsx b/packages/ui/src/components/Checkout/parts.tsx index fa083985990..8a0a882ce59 100644 --- a/packages/ui/src/components/Checkout/parts.tsx +++ b/packages/ui/src/components/Checkout/parts.tsx @@ -41,12 +41,15 @@ export const InvalidPlanScreen = () => { const { planPeriod } = useCheckoutContext(); const { errors } = useCheckout(); - const InvalidPlanError = errors?.global?.find(e => e.code === 'invalid_plan_change'); + const InvalidPlanError = errors?.global + ?.filter(e => e.isClerkApiResponseError()) + .flatMap(e => e.errors) + .find(e => e.code === 'invalid_plan_change'); + if (!InvalidPlanError) { return null; } - // @ts-expect-error - meta is not a property of FieldError const { plan: planFromError, isPlanUpgradePossible } = InvalidPlanError?.meta || {}; return ( diff --git a/packages/ui/src/components/OrganizationProfile/InviteMembersScreen.tsx b/packages/ui/src/components/OrganizationProfile/InviteMembersScreen.tsx index 5d24d2a3641..f6d493b2812 100644 --- a/packages/ui/src/components/OrganizationProfile/InviteMembersScreen.tsx +++ b/packages/ui/src/components/OrganizationProfile/InviteMembersScreen.tsx @@ -1,5 +1,4 @@ import { useOrganization } from '@clerk/shared/react'; -import { runIfFunctionOrReturn } from '@clerk/shared/utils'; import { useCardState, withCardStateProvider } from '@/ui/elements/contexts'; import { FormContainer } from '@/ui/elements/FormContainer'; @@ -7,12 +6,10 @@ import { IconCircle } from '@/ui/elements/IconCircle'; import { SuccessPage } from '@/ui/elements/SuccessPage'; import { useWizard, Wizard } from '../../common'; -import { useOrganizationProfileContext } from '../../contexts'; import { descriptors, Flex, localizationKeys, Text } from '../../customizables'; import { useActionContext } from '../../elements/Action/ActionRoot'; import { Email } from '../../icons'; import { InviteMembersForm } from './InviteMembersForm'; - type InviteMembersScreenProps = { onReset?: () => void; }; diff --git a/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx b/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx index da882250469..2ce5d4ab645 100644 --- a/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx +++ b/packages/ui/src/components/OrganizationProfile/OrganizationMembers.tsx @@ -8,7 +8,7 @@ import { Header } from '@/ui/elements/Header'; import { Tab, TabPanel, TabPanels, Tabs, TabsList } from '@/ui/elements/Tabs'; import { NotificationCountBadge, useProtect } from '../../common'; -import { useEnvironment, useOrganizationProfileContext } from '../../contexts'; +import { useEnvironment } from '../../contexts'; import { Col, descriptors, Flex, localizationKeys } from '../../customizables'; import { Action } from '../../elements/Action'; import { mqu } from '../../styledSystem'; diff --git a/packages/ui/src/components/SignUp/SignUpStart.tsx b/packages/ui/src/components/SignUp/SignUpStart.tsx index c32e64d23e2..807ea11537e 100644 --- a/packages/ui/src/components/SignUp/SignUpStart.tsx +++ b/packages/ui/src/components/SignUp/SignUpStart.tsx @@ -37,7 +37,10 @@ function SignUpStartInternal(): JSX.Element { const clerk = useClerk(); const status = useLoadingStatus(); const signUp = useCoreSignUp(); - const { showOptionalFields } = useAppearance().parsedOptions; + const appearance = useAppearance(); + const { showOptionalFields } = appearance.parsedOptions; + console.log('[SignUpStart] useAppearance().parsedOptions', JSON.stringify(appearance.parsedOptions)); + console.log('[SignUpStart] showOptionalFields', showOptionalFields); const { userSettings, authConfig } = useEnvironment(); const { navigate } = useRouter(); const { attributes } = userSettings; diff --git a/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx b/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx index aab44a915e0..0be43788a0e 100644 --- a/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx +++ b/packages/ui/src/components/devPrompts/EnableOrganizationsPrompt/index.tsx @@ -46,7 +46,7 @@ const EnableOrganizationsPromptInternal = ({ params.organization_allow_personal_accounts = allowPersonalAccount; } - void new DevTools() + void environment .__internal_enableEnvironmentSetting(params) .then(() => { setIsEnabled(true); diff --git a/packages/ui/src/customizables/AppearanceContext.tsx b/packages/ui/src/customizables/AppearanceContext.tsx index dfedab25078..be354b6b6ac 100644 --- a/packages/ui/src/customizables/AppearanceContext.tsx +++ b/packages/ui/src/customizables/AppearanceContext.tsx @@ -11,11 +11,20 @@ const [AppearanceContext, useAppearance] = createContextAndHook; const AppearanceProvider = (props: AppearanceProviderProps) => { + console.log('[AppearanceProvider] rendering with props:', { + globalAppearance: JSON.stringify(props.globalAppearance), + appearance: JSON.stringify(props.appearance), + appearanceKey: props.appearanceKey, + }); + const ctxValue = useDeepEqualMemo(() => { + console.log('[AppearanceProvider] useDeepEqualMemo factory called'); const value = parseAppearance(props); - + console.log('[AppearanceProvider] parsed value.parsedOptions:', JSON.stringify(value.parsedOptions)); return { value }; - }, [props.appearance, props.globalAppearance]); + }, [props.appearance, props.globalAppearance, props.appearanceKey]); + + console.log('[AppearanceProvider] ctxValue.value.parsedOptions:', JSON.stringify(ctxValue.value.parsedOptions)); return {props.children}; }; diff --git a/packages/ui/src/customizables/parseAppearance.ts b/packages/ui/src/customizables/parseAppearance.ts index ceecf45bc4b..a963cfda619 100644 --- a/packages/ui/src/customizables/parseAppearance.ts +++ b/packages/ui/src/customizables/parseAppearance.ts @@ -22,7 +22,7 @@ export type ParsedCaptcha = Required; type PublicAppearanceTopLevelKey = keyof Omit< Appearance, - 'theme' | 'elements' | 'layout' | 'variables' | 'captcha' | 'cssLayerName' + 'theme' | 'elements' | 'options' | 'variables' | 'captcha' | 'cssLayerName' >; export type AppearanceCascade = { @@ -68,16 +68,26 @@ const defaultCaptchaOptions: ParsedCaptcha = { */ export const parseAppearance = (cascade: AppearanceCascade): ParsedAppearance => { const { globalAppearance, appearance: componentAppearance, appearanceKey } = cascade; - + console.log('[parseAppearance] globalAppearance', JSON.stringify(globalAppearance)); + console.log('[parseAppearance] componentAppearance', JSON.stringify(componentAppearance)); + console.log('[parseAppearance] appearanceKey', appearanceKey); const appearanceList: Appearance[] = []; [globalAppearance, globalAppearance?.[appearanceKey as PublicAppearanceTopLevelKey], componentAppearance].forEach(a => expand(a, appearanceList), ); + console.log('[parseAppearance] appearanceList length', appearanceList.length); + console.log( + '[parseAppearance] appearanceList options', + appearanceList.map(a => JSON.stringify(a?.options)), + ); + const parsedInternalTheme = parseVariables(appearanceList); const parsedOptions = parseOptions(appearanceList); const parsedCaptcha = parseCaptcha(appearanceList); + console.log('[parseAppearance] parsedOptions', JSON.stringify(parsedOptions)); + if ( !appearanceList.find(a => { //@ts-expect-error not public api diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 8c4013283ec..b1cdc8add12 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -29,6 +29,6 @@ "@/ui*": ["./src/*"] } }, - "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx"], + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts", "**/*.spec.tsx", "**/__tests__/**"], "include": ["src", "src/global.d.ts"] } diff --git a/packages/ui/tsdown.config.mts b/packages/ui/tsdown.config.mts index d97ec74fb8e..2a7fdac4473 100644 --- a/packages/ui/tsdown.config.mts +++ b/packages/ui/tsdown.config.mts @@ -20,6 +20,7 @@ export default defineConfig(({ watch }) => { PACKAGE_VERSION: `"${uiPackage.version}"`, __PKG_VERSION__: `"${uiPackage.version}"`, __DEV__: `${watch}`, + __BUILD_DISABLE_RHC__: JSON.stringify(false), }, } satisfies Options; diff --git a/scripts/snapshot.mjs b/scripts/snapshot.mjs index 138c77b7ea3..57eec8287c2 100755 --- a/scripts/snapshot.mjs +++ b/scripts/snapshot.mjs @@ -25,13 +25,14 @@ try { // this will remove the prerelease versions // but will also clear the changeset .md files await $`pnpm changeset version`; - // generate a temp .md file that indicates that all packages have changes - // in order to force a snapshot release - await $`touch .changeset/snap.md && echo ${snapshot} > .changeset/snap.md`; } catch { - // otherwise, do nothing + // not in pre-release mode, continue } +// Always generate a temp .md file that indicates that all packages have changes +// in order to force a snapshot release of all packages +await $`touch .changeset/snap.md && echo ${snapshot} > .changeset/snap.md`; + const res = await $`pnpm changeset version --snapshot ${prefix}`; const success = !res.stderr.includes('No unreleased changesets found'); diff --git a/turbo.json b/turbo.json index f3dfbc66ba8..8ef7b479afc 100644 --- a/turbo.json +++ b/turbo.json @@ -193,240 +193,101 @@ "outputs": [] }, "//#test:integration:ap-flows": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:generic": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/react#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:express": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/express#build", - "@clerk/react#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:nextjs": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:nextjs:canary": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:quickstart": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:astro": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/astro#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:localhost": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:sessions": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:sessions:staging": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:handshake": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:handshake:staging": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build" - ], "env": ["CLEANUP", "DEBUG", "DISABLE_WEB_SECURITY", "E2E_*", "INTEGRATION_INSTANCE_KEYS", "NODE_EXTRA_CA_CERTS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:expo-web": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/expo#build", - "@clerk/react#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:tanstack-react-start": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/tanstack-react-start#build", - "@clerk/react#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:vue": { - "dependsOn": ["@clerk/testing#build", "@clerk/clerk-js#build", "@clerk/ui#build", "@clerk/vue#build"], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:nuxt": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/vue#build", - "@clerk/backend#build", - "@clerk/nuxt#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:react-router": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/react-router#build", - "@clerk/backend#build", - "@clerk/react#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:billing": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build", - "@clerk/vue#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:machine": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/nextjs#build", - "@clerk/express#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only" }, "//#test:integration:custom": { - "dependsOn": [ - "@clerk/testing#build", - "@clerk/clerk-js#build", - "@clerk/ui#build", - "@clerk/backend#build", - "@clerk/react#build" - ], "env": ["CLEANUP", "DEBUG", "E2E_*", "INTEGRATION_INSTANCE_KEYS"], "inputs": ["integration/**"], "outputLogs": "new-only"