diff --git a/.changeset/internal-clerk-js-ui-props.md b/.changeset/internal-clerk-js-ui-props.md new file mode 100644 index 00000000000..9bbc67a3dcc --- /dev/null +++ b/.changeset/internal-clerk-js-ui-props.md @@ -0,0 +1,11 @@ +--- +'@clerk/shared': minor +'@clerk/nextjs': minor +'@clerk/astro': minor +'@clerk/react-router': minor +'@clerk/tanstack-react-start': minor +'@clerk/express': minor +'@clerk/nuxt': minor +--- + +Remove `clerkJSUrl`, `clerkJSVersion`, `clerkUIUrl`, and `clerkUIVersion` props and replace with `__internal_clerkJSUrl`, `__internal_clerkJSVersion`, `__internal_clerkUIUrl`, and `__internal_clerkUIVersion` internal-only options. Use `@clerk/upgrade` to migrate. diff --git a/.changeset/remove-clerk-ui-version.md b/.changeset/remove-clerk-ui-version.md new file mode 100644 index 00000000000..3404d56cb95 --- /dev/null +++ b/.changeset/remove-clerk-ui-version.md @@ -0,0 +1,11 @@ +--- +'@clerk/shared': minor +'@clerk/nextjs': minor +'@clerk/astro': minor +'@clerk/react-router': minor +'@clerk/tanstack-react-start': minor +'@clerk/express': minor +'@clerk/nuxt': minor +--- + +Remove `clerkUIVersion` and `clerkJSVersion` props across all SDKs. Use `@clerk/upgrade` to migrate to the new `__internal_` prefixed props if needed. diff --git a/integration/templates/custom-flows-react-vite/src/main.tsx b/integration/templates/custom-flows-react-vite/src/main.tsx index 225a3ddd20f..33b3d38e758 100644 --- a/integration/templates/custom-flows-react-vite/src/main.tsx +++ b/integration/templates/custom-flows-react-vite/src/main.tsx @@ -14,8 +14,8 @@ createRoot(document.getElementById('root')!).render(
router.push(to)} routerReplace={to => router.replace(to)} - clerkJSUrl={process.env.EXPO_PUBLIC_CLERK_JS_URL} - clerkUIUrl={process.env.EXPO_PUBLIC_CLERK_UI_URL} + __internal_clerkJSUrl={process.env.EXPO_PUBLIC_CLERK_JS_URL} + __internal_clerkUIUrl={process.env.EXPO_PUBLIC_CLERK_UI_URL} appearance={{ options: { showOptionalFields: true, diff --git a/integration/templates/react-cra/src/index.tsx b/integration/templates/react-cra/src/index.tsx index 81039d51a20..a9d172c24f1 100644 --- a/integration/templates/react-cra/src/index.tsx +++ b/integration/templates/react-cra/src/index.tsx @@ -9,8 +9,8 @@ root.render( { const navigate = useNavigate(); return ( navigate(to)} routerReplace={(to: string) => navigate(to, { replace: true })} appearance={{ diff --git a/integration/templates/tanstack-react-start/src/routes/__root.tsx b/integration/templates/tanstack-react-start/src/routes/__root.tsx index b9adc012c75..cdd38e131cd 100644 --- a/integration/templates/tanstack-react-start/src/routes/__root.tsx +++ b/integration/templates/tanstack-react-start/src/routes/__root.tsx @@ -28,8 +28,8 @@ function RootDocument({ children }: { children: React.ReactNode }) { () const { proxyUrl, isSatellite, domain, signInUrl, signUpUrl, enableEnvSchema = true } = params || {}; // These are not provided when the "bundled" integration is used - const clerkJSUrl = (params as any)?.clerkJSUrl as string | undefined; - const clerkJSVersion = (params as any)?.clerkJSVersion as string | undefined; - const clerkUIVersion = (params as any)?.clerkUIVersion as string | undefined; + const clerkJSUrl = (params as any)?.__internal_clerkJSUrl as string | undefined; + const clerkJSVersion = (params as any)?.__internal_clerkJSVersion as string | undefined; + const clerkUIUrl = (params as any)?.__internal_clerkUIUrl as string | undefined; + const clerkUIVersion = (params as any)?.__internal_clerkUIVersion as string | undefined; const prefetchUI = (params as any)?.prefetchUI as boolean | undefined; const hasUI = !!(params as any)?.ui; @@ -60,6 +61,7 @@ function createIntegration() ...buildEnvVarFromOption(domain, 'PUBLIC_CLERK_DOMAIN'), ...buildEnvVarFromOption(clerkJSUrl, 'PUBLIC_CLERK_JS_URL'), ...buildEnvVarFromOption(clerkJSVersion, 'PUBLIC_CLERK_JS_VERSION'), + ...buildEnvVarFromOption(clerkUIUrl, 'PUBLIC_CLERK_UI_URL'), ...buildEnvVarFromOption(clerkUIVersion, 'PUBLIC_CLERK_UI_VERSION'), ...buildEnvVarFromOption( prefetchUI === false || hasUI ? 'false' : undefined, diff --git a/packages/astro/src/internal/__tests__/create-clerk-instance.test.ts b/packages/astro/src/internal/__tests__/create-clerk-instance.test.ts index e9f62f67a99..8811a9427d0 100644 --- a/packages/astro/src/internal/__tests__/create-clerk-instance.test.ts +++ b/packages/astro/src/internal/__tests__/create-clerk-instance.test.ts @@ -42,7 +42,7 @@ describe('getClerkUIEntryChunk', () => { (window as any).Clerk = undefined; }); - it('preserves clerkUIUrl from options', async () => { + it('preserves __internal_clerkUIUrl from options', async () => { mockLoadClerkUIScript.mockImplementation(async () => { (window as any).__internal_ClerkUICtor = mockClerkUICtor; return null; @@ -59,18 +59,18 @@ describe('getClerkUIEntryChunk', () => { // Dynamically import to get fresh module with mocks const { createClerkInstance } = await import('../create-clerk-instance'); - // Call createClerkInstance with clerkUIUrl + // Call createClerkInstance with __internal_clerkUIUrl await createClerkInstance({ publishableKey: 'pk_test_xxx', - clerkUIUrl: 'https://custom.selfhosted.example.com/ui.js', + __internal_clerkUIUrl: 'https://custom.selfhosted.example.com/ui.js', }); expect(mockLoadClerkUIScript).toHaveBeenCalled(); const loadClerkUIScriptCall = mockLoadClerkUIScript.mock.calls[0]?.[0] as Record; - expect(loadClerkUIScriptCall?.clerkUIUrl).toBe('https://custom.selfhosted.example.com/ui.js'); + expect(loadClerkUIScriptCall?.__internal_clerkUIUrl).toBe('https://custom.selfhosted.example.com/ui.js'); }); - it('does not set clerkUIUrl when not provided', async () => { + it('does not set __internal_clerkUIUrl when not provided', async () => { mockLoadClerkUIScript.mockImplementation(async () => { (window as any).__internal_ClerkUICtor = mockClerkUICtor; return null; @@ -92,6 +92,6 @@ describe('getClerkUIEntryChunk', () => { expect(mockLoadClerkUIScript).toHaveBeenCalled(); const loadClerkUIScriptCall = mockLoadClerkUIScript.mock.calls[0]?.[0] as Record; - expect(loadClerkUIScriptCall?.clerkUIUrl).toBeUndefined(); + expect(loadClerkUIScriptCall?.__internal_clerkUIUrl).toBeUndefined(); }); }); diff --git a/packages/astro/src/internal/merge-env-vars-with-params.ts b/packages/astro/src/internal/merge-env-vars-with-params.ts index 39dbfd9820b..65128c255bd 100644 --- a/packages/astro/src/internal/merge-env-vars-with-params.ts +++ b/packages/astro/src/internal/merge-env-vars-with-params.ts @@ -1,3 +1,4 @@ +import type { InternalClerkScriptProps } from '@clerk/shared/types'; import { isTruthy } from '@clerk/shared/underscore'; import type { AstroClerkIntegrationParams, InternalRuntimeOptions } from '../types'; @@ -25,7 +26,9 @@ function mergePrefetchUIConfig(paramPrefetchUI: AstroClerkIntegrationParams['pre /** * @internal */ -const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & InternalRuntimeOptions) => { +const mergeEnvVarsWithParams = ( + params?: AstroClerkIntegrationParams & InternalRuntimeOptions & InternalClerkScriptProps, +) => { const { signInUrl: paramSignIn, signUpUrl: paramSignUp, @@ -34,10 +37,10 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & InternalR domain: paramDomain, publishableKey: paramPublishableKey, telemetry: paramTelemetry, - clerkJSUrl: paramClerkJSUrl, - clerkJSVersion: paramClerkJSVersion, - clerkUIUrl: paramClerkUIUrl, - clerkUIVersion: paramClerkUIVersion, + __internal_clerkJSUrl: paramClerkJSUrl, + __internal_clerkJSVersion: paramClerkJSVersion, + __internal_clerkUIUrl: paramClerkUIUrl, + __internal_clerkUIVersion: paramClerkUIVersion, prefetchUI: paramPrefetchUI, ...rest } = params || {}; @@ -53,10 +56,10 @@ const mergeEnvVarsWithParams = (params?: AstroClerkIntegrationParams & InternalR // In keyless mode, use server-injected publishableKey from params publishableKey: paramPublishableKey || internalOptions?.publishableKey || import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY || '', - clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, - clerkJSVersion: paramClerkJSVersion || import.meta.env.PUBLIC_CLERK_JS_VERSION, - clerkUIUrl: paramClerkUIUrl || import.meta.env.PUBLIC_CLERK_UI_URL, - clerkUIVersion: paramClerkUIVersion || import.meta.env.PUBLIC_CLERK_UI_VERSION, + __internal_clerkJSUrl: paramClerkJSUrl || import.meta.env.PUBLIC_CLERK_JS_URL, + __internal_clerkJSVersion: paramClerkJSVersion || import.meta.env.PUBLIC_CLERK_JS_VERSION, + __internal_clerkUIUrl: paramClerkUIUrl || import.meta.env.PUBLIC_CLERK_UI_URL, + __internal_clerkUIVersion: paramClerkUIVersion || import.meta.env.PUBLIC_CLERK_UI_VERSION, prefetchUI: mergePrefetchUIConfig(paramPrefetchUI), telemetry: paramTelemetry || { disabled: isTruthy(import.meta.env.PUBLIC_CLERK_TELEMETRY_DISABLED), diff --git a/packages/astro/src/server/build-clerk-hotload-script.ts b/packages/astro/src/server/build-clerk-hotload-script.ts index 5510759b387..c97be95cd4e 100644 --- a/packages/astro/src/server/build-clerk-hotload-script.ts +++ b/packages/astro/src/server/build-clerk-hotload-script.ts @@ -13,8 +13,8 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { const domain = env.domain!; const clerkJsScriptSrc = clerkJSScriptUrl({ - clerkJSUrl: env.clerkJsUrl, - clerkJSVersion: env.clerkJsVersion, + __internal_clerkJSUrl: env.clerkJsUrl, + __internal_clerkJSVersion: env.clerkJsVersion, domain, proxyUrl, publishableKey, @@ -35,8 +35,8 @@ function buildClerkHotloadScript(locals: APIContext['locals']) { } const clerkUIScriptSrc = clerkUIScriptUrl({ - clerkUIUrl: env.clerkUIUrl, - clerkUIVersion: env.clerkUIVersion, + __internal_clerkUIUrl: env.clerkUIUrl, + __internal_clerkUIVersion: env.clerkUIVersion, domain, proxyUrl, publishableKey, diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 8e65d322c27..2248d5ce1f3 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -2,6 +2,7 @@ import type { Clerk, ClerkOptions, ClientResource, + InternalClerkScriptProps, MultiDomainAndOrProxyPrimitives, ProtectParams, ShowProps, @@ -29,16 +30,6 @@ type AstroClerkIntegrationParams = Without< > & MultiDomainAndOrProxyPrimitives & { appearance?: Appearance; - clerkJSUrl?: string; - clerkJSVersion?: string; - /** - * The URL that `@clerk/ui` should be hot-loaded from. - */ - clerkUIUrl?: string; - /** - * The npm version for `@clerk/ui`. - */ - clerkUIVersion?: string; /** * Controls prefetching of the `@clerk/ui` script. * - `false` - Skip prefetching the UI (for custom UIs using Control Components) @@ -47,9 +38,10 @@ type AstroClerkIntegrationParams = Without< prefetchUI?: boolean; }; -type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & { - publishableKey: string; -}; +type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & + InternalClerkScriptProps & { + publishableKey: string; + }; /** * @internal diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index db601b22452..15ec1895cfa 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -1,6 +1,6 @@ import type { Clerk } from '@clerk/clerk-js/no-rhc'; import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/react'; -import { ClerkProvider as ClerkReactProvider } from '@clerk/react'; +import { InternalClerkProvider as ClerkReactProvider } from '@clerk/react/internal'; import { ui } from '@clerk/ui'; import React from 'react'; diff --git a/packages/expo/src/provider/ClerkProvider.tsx b/packages/expo/src/provider/ClerkProvider.tsx index 6c81616a1fc..d76e3fa541c 100644 --- a/packages/expo/src/provider/ClerkProvider.tsx +++ b/packages/expo/src/provider/ClerkProvider.tsx @@ -1,7 +1,7 @@ import '../polyfills'; -import { ClerkProvider as ClerkReactProvider } from '@clerk/react'; -import type { Ui } from '@clerk/react/internal'; +import type { ClerkProviderProps as ReactClerkProviderProps } from '@clerk/react'; +import { InternalClerkProvider as ClerkReactProvider, type Ui } from '@clerk/react/internal'; import * as WebBrowser from 'expo-web-browser'; import type { TokenCache } from '../cache/types'; @@ -9,10 +9,7 @@ import { isNative, isWeb } from '../utils/runtime'; import { getClerkInstance } from './singleton'; import type { BuildClerkOptions } from './singleton/types'; -export type ClerkProviderProps = Omit< - React.ComponentProps>, - 'publishableKey' -> & { +export type ClerkProviderProps = Omit, 'publishableKey'> & { /** * Your Clerk publishable key, available in the Clerk Dashboard. * This is required for React Native / Expo apps. Environment variables inside node_modules diff --git a/packages/express/src/utils.ts b/packages/express/src/utils.ts index 73a343842df..bd138ed3dbe 100644 --- a/packages/express/src/utils.ts +++ b/packages/express/src/utils.ts @@ -11,10 +11,10 @@ export const requestHasAuthObject = (req: ExpressRequest): req is ExpressRequest export const loadClientEnv = () => { return { publishableKey: process.env.CLERK_PUBLISHABLE_KEY || '', - clerkJSUrl: process.env.CLERK_JS || process.env.CLERK_JS_URL || '', - clerkJSVersion: process.env.CLERK_JS_VERSION || '', - clerkUIUrl: process.env.CLERK_UI_URL || '', - clerkUIVersion: process.env.CLERK_UI_VERSION || '', + __internal_clerkJSUrl: process.env.CLERK_JS || process.env.CLERK_JS_URL || '', + __internal_clerkJSVersion: process.env.CLERK_JS_VERSION || '', + __internal_clerkUIUrl: process.env.CLERK_UI_URL || '', + __internal_clerkUIVersion: process.env.CLERK_UI_VERSION || '', prefetchUI: process.env.CLERK_PREFETCH_UI === 'false' ? false : undefined, }; }; diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index 77dc12d7cac..109e1a38c87 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -1,6 +1,5 @@ 'use client'; -import { ClerkProvider as ReactClerkProvider } from '@clerk/react'; -import type { Ui } from '@clerk/react/internal'; +import { InternalClerkProvider as ReactClerkProvider, type Ui } from '@clerk/react/internal'; import { InitialStateProvider } from '@clerk/shared/react'; import dynamic from 'next/dynamic'; import { useRouter } from 'next/navigation'; diff --git a/packages/nextjs/src/app-router/client/ClerkScripts.tsx b/packages/nextjs/src/app-router/client/ClerkScripts.tsx index bed102c7c08..66017626683 100644 --- a/packages/nextjs/src/app-router/client/ClerkScripts.tsx +++ b/packages/nextjs/src/app-router/client/ClerkScripts.tsx @@ -5,7 +5,15 @@ import { useClerkNextOptions } from '../../client-boundary/NextOptionsContext'; import { ClerkScriptTags } from '../../utils/clerk-script-tags'; export function ClerkScripts() { - const { publishableKey, clerkJSUrl, clerkJSVersion, clerkUIUrl, nonce, prefetchUI } = useClerkNextOptions(); + const { + publishableKey, + __internal_clerkJSUrl, + __internal_clerkJSVersion, + __internal_clerkUIUrl, + __internal_clerkUIVersion, + nonce, + prefetchUI, + } = useClerkNextOptions(); const { domain, proxyUrl } = useClerk(); if (!publishableKey) { @@ -15,9 +23,10 @@ export function ClerkScripts() { return ( ( { type DynamicClerkScriptsProps = { publishableKey: string; - clerkJSUrl?: string; - clerkJSVersion?: string; - clerkUIUrl?: string; + __internal_clerkJSUrl?: string; + __internal_clerkJSVersion?: string; + __internal_clerkUIUrl?: string; + __internal_clerkUIVersion?: string; domain?: string; proxyUrl?: string; prefetchUI?: boolean; @@ -37,7 +38,16 @@ type DynamicClerkScriptsProps = { * nonce fetching from the rest of the page, allowing static rendering/PPR to work. */ export async function DynamicClerkScripts(props: DynamicClerkScriptsProps) { - const { publishableKey, clerkJSUrl, clerkJSVersion, clerkUIUrl, domain, proxyUrl, prefetchUI } = props; + const { + publishableKey, + __internal_clerkJSUrl, + __internal_clerkJSVersion, + __internal_clerkUIUrl, + __internal_clerkUIVersion, + domain, + proxyUrl, + prefetchUI, + } = props; if (!publishableKey) { return null; @@ -48,9 +58,10 @@ export async function DynamicClerkScripts(props: DynamicClerkScriptsProps) { return ( >; +type ClerkNextContextValue = Partial & InternalClerkScriptProps>; const ClerkNextOptionsCtx = React.createContext<{ value: ClerkNextContextValue } | undefined>(undefined); ClerkNextOptionsCtx.displayName = 'ClerkNextOptionsCtx'; diff --git a/packages/nextjs/src/pages/ClerkProvider.tsx b/packages/nextjs/src/pages/ClerkProvider.tsx index 5bc6c3b76f8..6d47886a470 100644 --- a/packages/nextjs/src/pages/ClerkProvider.tsx +++ b/packages/nextjs/src/pages/ClerkProvider.tsx @@ -1,7 +1,9 @@ -import { ClerkProvider as ReactClerkProvider } from '@clerk/react'; -import type { Ui } from '@clerk/react/internal'; -// Override Clerk React error thrower to show that errors come from @clerk/nextjs -import { setClerkJSLoadingErrorPackageName, setErrorThrowerOptions } from '@clerk/react/internal'; +import { + InternalClerkProvider as ReactClerkProvider, + setClerkJSLoadingErrorPackageName, + setErrorThrowerOptions, + type Ui, +} from '@clerk/react/internal'; import { useRouter } from 'next/router'; import React from 'react'; diff --git a/packages/nextjs/src/pages/ClerkScripts.tsx b/packages/nextjs/src/pages/ClerkScripts.tsx index 508100da3bd..a9f7e89ebb2 100644 --- a/packages/nextjs/src/pages/ClerkScripts.tsx +++ b/packages/nextjs/src/pages/ClerkScripts.tsx @@ -23,8 +23,16 @@ function ClerkScript(props: { scriptUrl: string; attributes: Record { }); }); - describe('clerkUIUrl', () => { - const defaultProps = { children: '' }; - - it('accepts string URL for custom UI location', () => { - expectTypeOf({ ...defaultProps, clerkUIUrl: 'https://custom.com/ui.js' }).toMatchTypeOf(); - }); - - it('accepts undefined', () => { - expectTypeOf({ ...defaultProps, clerkUIUrl: undefined }).toMatchTypeOf(); + describe('internal script props', () => { + it('does not expose internal script props', () => { + expectTypeOf().not.toHaveProperty('__internal_clerkUIUrl'); + expectTypeOf().not.toHaveProperty('__internal_clerkJSUrl'); + expectTypeOf().not.toHaveProperty('__internal_clerkUIVersion'); + expectTypeOf().not.toHaveProperty('__internal_clerkJSVersion'); }); }); diff --git a/packages/nextjs/src/server/constants.ts b/packages/nextjs/src/server/constants.ts index 4bbc3b74fa5..b98542bdc27 100644 --- a/packages/nextjs/src/server/constants.ts +++ b/packages/nextjs/src/server/constants.ts @@ -3,8 +3,8 @@ import { isTruthy } from '@clerk/shared/underscore'; export const CLERK_JS_VERSION = process.env.NEXT_PUBLIC_CLERK_JS_VERSION || ''; export const CLERK_JS_URL = process.env.NEXT_PUBLIC_CLERK_JS_URL || ''; -export const CLERK_UI_VERSION = process.env.NEXT_PUBLIC_CLERK_UI_VERSION || ''; export const CLERK_UI_URL = process.env.NEXT_PUBLIC_CLERK_UI_URL || ''; +export const CLERK_UI_VERSION = process.env.NEXT_PUBLIC_CLERK_UI_VERSION || ''; export const API_VERSION = process.env.CLERK_API_VERSION || 'v1'; export const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; export const MACHINE_SECRET_KEY = process.env.CLERK_MACHINE_SECRET_KEY || ''; diff --git a/packages/nextjs/src/utils/clerk-script-tags.tsx b/packages/nextjs/src/utils/clerk-script-tags.tsx index 588d852e349..b7d67c73cd4 100644 --- a/packages/nextjs/src/utils/clerk-script-tags.tsx +++ b/packages/nextjs/src/utils/clerk-script-tags.tsx @@ -3,9 +3,10 @@ import React from 'react'; type ClerkScriptTagsProps = { publishableKey: string; - clerkJSUrl?: string; - clerkJSVersion?: string; - clerkUIUrl?: string; + __internal_clerkJSUrl?: string; + __internal_clerkJSVersion?: string; + __internal_clerkUIUrl?: string; + __internal_clerkUIVersion?: string; nonce?: string; domain?: string; proxyUrl?: string; @@ -18,13 +19,24 @@ type ClerkScriptTagsProps = { * No hooks or client-only imports — safe for both server and client components. */ export function ClerkScriptTags(props: ClerkScriptTagsProps) { - const { publishableKey, clerkJSUrl, clerkJSVersion, clerkUIUrl, nonce, domain, proxyUrl, prefetchUI } = props; + const { + publishableKey, + __internal_clerkJSUrl, + __internal_clerkJSVersion, + __internal_clerkUIUrl, + __internal_clerkUIVersion, + nonce, + domain, + proxyUrl, + prefetchUI, + } = props; const opts = { publishableKey, - clerkJSUrl, - clerkJSVersion, - clerkUIUrl, + __internal_clerkJSUrl, + __internal_clerkJSVersion, + __internal_clerkUIUrl, + __internal_clerkUIVersion, nonce, domain, proxyUrl, diff --git a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts index e59be2f5715..afb09022061 100644 --- a/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts +++ b/packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts @@ -1,3 +1,4 @@ +import type { InternalClerkScriptProps } from '@clerk/react/internal'; import { isTruthy } from '@clerk/shared/underscore'; import { SDK_METADATA } from '../server/constants'; @@ -18,14 +19,16 @@ function getPrefetchUIFromEnvAndProps(propsPrefetchUI: NextClerkProviderProps['p } // @ts-ignore - https://github.com/microsoft/TypeScript/issues/47663 -export const mergeNextClerkPropsWithEnv = (props: Omit): any => { +export const mergeNextClerkPropsWithEnv = ( + props: Omit & InternalClerkScriptProps, +): any => { return { ...props, publishableKey: props.publishableKey || process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY || '', - clerkJSUrl: props.clerkJSUrl || process.env.NEXT_PUBLIC_CLERK_JS_URL, - clerkJSVersion: props.clerkJSVersion || process.env.NEXT_PUBLIC_CLERK_JS_VERSION, - clerkUIUrl: props.clerkUIUrl || process.env.NEXT_PUBLIC_CLERK_UI_URL, - clerkUIVersion: props.clerkUIVersion || process.env.NEXT_PUBLIC_CLERK_UI_VERSION, + __internal_clerkJSUrl: props.__internal_clerkJSUrl || process.env.NEXT_PUBLIC_CLERK_JS_URL, + __internal_clerkJSVersion: props.__internal_clerkJSVersion || process.env.NEXT_PUBLIC_CLERK_JS_VERSION, + __internal_clerkUIUrl: props.__internal_clerkUIUrl || process.env.NEXT_PUBLIC_CLERK_UI_URL, + __internal_clerkUIVersion: props.__internal_clerkUIVersion || process.env.NEXT_PUBLIC_CLERK_UI_VERSION, prefetchUI: getPrefetchUIFromEnvAndProps(props.prefetchUI), proxyUrl: props.proxyUrl || process.env.NEXT_PUBLIC_CLERK_PROXY_URL || '', domain: props.domain || process.env.NEXT_PUBLIC_CLERK_DOMAIN || '', diff --git a/packages/nuxt/src/global.d.ts b/packages/nuxt/src/global.d.ts index 84eec72d191..6fd1ecef727 100644 --- a/packages/nuxt/src/global.d.ts +++ b/packages/nuxt/src/global.d.ts @@ -16,7 +16,7 @@ declare module 'nuxt/schema' { }; } interface PublicRuntimeConfig { - clerk: Omit & { + clerk: PluginOptions & { /** * The URL that `@clerk/clerk-js` should be hot-loaded from. * Supports NUXT_PUBLIC_CLERK_JS_URL env var. @@ -27,6 +27,14 @@ declare module 'nuxt/schema' { * Supports NUXT_PUBLIC_CLERK_UI_URL env var. */ uiUrl?: string; + /** + * The npm version for `@clerk/clerk-js`. + */ + clerkJSVersion?: string; + /** + * The npm version for `@clerk/ui`. + */ + clerkUIVersion?: string; }; } } diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index c4ee151a95a..49f2505e3ee 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -1,4 +1,4 @@ -import type { Without } from '@clerk/shared/types'; +import type { InternalClerkScriptProps, Without } from '@clerk/shared/types'; import type { PluginOptions } from '@clerk/vue'; import { addComponent, @@ -12,30 +12,28 @@ import { updateRuntimeConfig, } from '@nuxt/kit'; -export type ModuleOptions = Without< - PluginOptions, - 'routerPush' | 'routerReplace' | 'publishableKey' | 'initialState' -> & { - publishableKey?: string; - /** - * Skip the automatic server middleware registration. When enabled, you'll need to - * register the middleware manually in your application. - * - * @default false - * - * @example - * - * ```ts - * // server/middleware/clerk.ts - * import { clerkMiddleware } from '@clerk/nuxt/server' - * - * export default clerkMiddleware((event) => { - * console.log('auth', event.context.auth()) - * }) - * ``` - */ - skipServerMiddleware?: boolean; -}; +export type ModuleOptions = Without & + InternalClerkScriptProps & { + publishableKey?: string; + /** + * Skip the automatic server middleware registration. When enabled, you'll need to + * register the middleware manually in your application. + * + * @default false + * + * @example + * + * ```ts + * // server/middleware/clerk.ts + * import { clerkMiddleware } from '@clerk/nuxt/server' + * + * export default clerkMiddleware((event) => { + * console.log('auth', event.context.auth()) + * }) + * ``` + */ + skipServerMiddleware?: boolean; + }; export default defineNuxtModule({ meta: { @@ -64,12 +62,12 @@ export default defineNuxtModule({ signUpForceRedirectUrl: options.signUpForceRedirectUrl, signUpUrl: options.signUpUrl, domain: options.domain, - // Using jsUrl/uiUrl instead of clerkJSUrl/clerkUIUrl to support + // Using jsUrl/uiUrl instead of __internal_clerkJSUrl/__internal_clerkUIUrl to support // NUXT_PUBLIC_CLERK_JS_URL and NUXT_PUBLIC_CLERK_UI_URL env vars. - jsUrl: options.clerkJSUrl, - uiUrl: options.clerkUIUrl, - clerkJSVersion: options.clerkJSVersion, - clerkUIVersion: options.clerkUIVersion, + jsUrl: options.__internal_clerkJSUrl, + uiUrl: options.__internal_clerkUIUrl, + clerkJSVersion: options.__internal_clerkJSVersion, + clerkUIVersion: options.__internal_clerkUIVersion, // prefetchUI config: can be false or undefined prefetchUI: options.prefetchUI, isSatellite: options.isSatellite, diff --git a/packages/nuxt/src/runtime/plugin.ts b/packages/nuxt/src/runtime/plugin.ts index 03fdeb75d25..650358ef02c 100644 --- a/packages/nuxt/src/runtime/plugin.ts +++ b/packages/nuxt/src/runtime/plugin.ts @@ -21,9 +21,11 @@ export default defineNuxtPlugin(nuxtApp => { nuxtApp.vueApp.use(clerkPlugin as any, { ...clerkConfig, - // Map jsUrl/uiUrl to clerkJSUrl/clerkUIUrl as expected by the Vue plugin - clerkJSUrl: clerkConfig.jsUrl, - clerkUIUrl: clerkConfig.uiUrl, + // Map jsUrl/uiUrl to __internal_clerkJSUrl/__internal_clerkUIUrl as expected by the Vue plugin + __internal_clerkJSUrl: clerkConfig.jsUrl, + __internal_clerkUIUrl: clerkConfig.uiUrl, + __internal_clerkJSVersion: clerkConfig.clerkJSVersion, + __internal_clerkUIVersion: clerkConfig.clerkUIVersion, sdkMetadata: { name: PACKAGE_NAME, version: PACKAGE_VERSION, diff --git a/packages/react-router/src/client/ReactRouterClerkProvider.tsx b/packages/react-router/src/client/ReactRouterClerkProvider.tsx index ce2d7857719..5abefcbb341 100644 --- a/packages/react-router/src/client/ReactRouterClerkProvider.tsx +++ b/packages/react-router/src/client/ReactRouterClerkProvider.tsx @@ -1,5 +1,4 @@ -import { ClerkProvider as ReactClerkProvider } from '@clerk/react'; -import type { Ui } from '@clerk/react/internal'; +import { InternalClerkProvider as ReactClerkProvider, type Ui } from '@clerk/react/internal'; import React from 'react'; import { @@ -93,10 +92,10 @@ function ClerkProviderBase({ children, ...rest }: ClerkProv signUpForceRedirectUrl: __signUpForceRedirectUrl, signInFallbackRedirectUrl: __signInFallbackRedirectUrl, signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl, - clerkJSUrl: __clerkJSUrl, - clerkJSVersion: __clerkJSVersion, - clerkUIUrl: __clerkUIUrl, - clerkUIVersion: __clerkUIVersion, + __internal_clerkJSUrl: __clerkJSUrl, + __internal_clerkJSVersion: __clerkJSVersion, + __internal_clerkUIUrl: __clerkUIUrl, + __internal_clerkUIVersion: __clerkUIVersion, prefetchUI: __prefetchUI, telemetry: { disabled: __telemetryDisabled, diff --git a/packages/react-router/src/client/__tests__/ClerkProvider.test.tsx b/packages/react-router/src/client/__tests__/ClerkProvider.test.tsx index 6164770edb3..1677e93a22a 100644 --- a/packages/react-router/src/client/__tests__/ClerkProvider.test.tsx +++ b/packages/react-router/src/client/__tests__/ClerkProvider.test.tsx @@ -4,10 +4,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const mockClerkProvider = vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
); -vi.mock('@clerk/react', () => ({ - ClerkProvider: (props: any) => mockClerkProvider(props), +vi.mock('@clerk/react/internal', () => ({ + InternalClerkProvider: (props: any) => mockClerkProvider(props), })); +vi.mock('@clerk/react', () => ({})); + vi.mock('react-router', () => ({ useNavigate: () => vi.fn(), useLocation: () => ({ pathname: '/' }), @@ -17,11 +19,11 @@ vi.mock('react-router', () => ({ vi.mock('../../utils/assert', () => ({ assertPublishableKeyInSpaMode: vi.fn(), assertValidClerkState: vi.fn(), - isSpaMode: () => true, + isSpaMode: () => false, warnForSsr: vi.fn(), })); -describe('ClerkProvider clerkUIUrl prop', () => { +describe('ClerkProvider __internal_clerkUIUrl via clerkState', () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -30,26 +32,32 @@ describe('ClerkProvider clerkUIUrl prop', () => { vi.clearAllMocks(); }); - it('passes clerkUIUrl prop to the underlying ClerkProvider', async () => { + it('passes __internal_clerkUIUrl from clerkState to the underlying ClerkProvider', async () => { const { ClerkProvider } = await import('../ReactRouterClerkProvider'); + const clerkState = { + __type: 'clerkState' as const, + __internal_clerk_state: { + __clerk_ssr_state: undefined, + __publishableKey: 'pk_test_xxx', + __clerkUIUrl: 'https://custom.clerk.ui/ui.js', + }, + }; + render( - +
Test
, ); expect(mockClerkProvider).toHaveBeenCalledWith( expect.objectContaining({ - clerkUIUrl: 'https://custom.clerk.ui/ui.js', + __internal_clerkUIUrl: 'https://custom.clerk.ui/ui.js', }), ); }); - it('passes clerkUIUrl as undefined when not provided', async () => { + it('passes __internal_clerkUIUrl as undefined when not in clerkState', async () => { const { ClerkProvider } = await import('../ReactRouterClerkProvider'); render( @@ -60,30 +68,36 @@ describe('ClerkProvider clerkUIUrl prop', () => { expect(mockClerkProvider).toHaveBeenCalledWith( expect.objectContaining({ - clerkUIUrl: undefined, + __internal_clerkUIUrl: undefined, }), ); }); - it('passes clerkUIUrl alongside other props', async () => { + it('passes __internal_clerkUIUrl alongside other props from clerkState', async () => { const { ClerkProvider } = await import('../ReactRouterClerkProvider'); + const clerkState = { + __type: 'clerkState' as const, + __internal_clerk_state: { + __clerk_ssr_state: undefined, + __publishableKey: 'pk_test_xxx', + __clerkUIUrl: 'https://custom.clerk.ui/ui.js', + __clerkJSUrl: 'https://custom.clerk.js/clerk.js', + __signInUrl: '/sign-in', + __signUpUrl: '/sign-up', + }, + }; + render( - +
Test
, ); expect(mockClerkProvider).toHaveBeenCalledWith( expect.objectContaining({ - clerkUIUrl: 'https://custom.clerk.ui/ui.js', - clerkJSUrl: 'https://custom.clerk.js/clerk.js', + __internal_clerkUIUrl: 'https://custom.clerk.ui/ui.js', + __internal_clerkJSUrl: 'https://custom.clerk.js/clerk.js', signInUrl: '/sign-in', signUpUrl: '/sign-up', }), diff --git a/packages/react/src/internal.ts b/packages/react/src/internal.ts index 4f35a1a000f..ff4db48fe49 100644 --- a/packages/react/src/internal.ts +++ b/packages/react/src/internal.ts @@ -1,3 +1,10 @@ +import type { InternalClerkScriptProps } from '@clerk/shared/types'; +import type { Ui } from '@clerk/ui/internal'; +import type React from 'react'; + +import { ClerkProvider } from './contexts/ClerkProvider'; +import type { ClerkProviderProps } from './types'; + export { setErrorThrowerOptions } from './errors/errorThrower'; export { MultisessionAppSupport } from './components/controlComponents'; export { useRoutingProps } from './hooks/useRoutingProps'; @@ -17,3 +24,13 @@ export { } from '@clerk/shared/loadClerkJsScript'; export type { Ui } from '@clerk/ui/internal'; + +export type { InternalClerkScriptProps } from '@clerk/shared/types'; + +/** + * A wider-typed version of ClerkProvider that accepts internal script props. + * Framework SDKs should use this instead of the public ClerkProvider. + */ +export const InternalClerkProvider = ClerkProvider as unknown as (( + props: ClerkProviderProps & InternalClerkScriptProps, +) => React.JSX.Element) & { displayName: string }; diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 99c15fbb0f9..9c38a97c989 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -1,6 +1,7 @@ import type { Clerk, InitialState, + InternalClerkScriptProps, IsomorphicClerkOptions, LoadedClerk, RedirectUrlProp, @@ -36,7 +37,10 @@ declare global { /** * @interface */ -export type ClerkProviderProps = Omit & { +export type ClerkProviderProps = Omit< + IsomorphicClerkOptions, + 'appearance' | keyof InternalClerkScriptProps +> & { children: React.ReactNode; /** * Provide an initial state of the Clerk client during server-side rendering. You don't need to set this value yourself unless you're [developing an SDK](https://clerk.com/docs/guides/development/sdk-development/overview). diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index 4cc384e7893..69eeedf576e 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -147,9 +147,9 @@ describe('clerkJsScriptUrl()', () => { const mockDevPublishableKey = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; const mockProdPublishableKey = 'pk_live_ZXhhbXBsZS5jbGVyay5jb20k'; // example.clerk.com - test('returns clerkJSUrl when provided', () => { + test('returns __internal_clerkJSUrl when provided', () => { const customUrl = 'https://custom.clerk.com/clerk.js'; - const result = clerkJsScriptUrl({ clerkJSUrl: customUrl, publishableKey: mockDevPublishableKey }); + const result = clerkJsScriptUrl({ __internal_clerkJSUrl: customUrl, publishableKey: mockDevPublishableKey }); expect(result).toBe(customUrl); }); @@ -165,8 +165,8 @@ describe('clerkJsScriptUrl()', () => { expect(result).toBe(`https://example.clerk.com/npm/@clerk/clerk-js@${jsPackageMajorVersion}/dist/clerk.browser.js`); }); - test('uses provided clerkJSVersion', () => { - const result = clerkJsScriptUrl({ publishableKey: mockDevPublishableKey, clerkJSVersion: '6' }); + test('uses provided __internal_clerkJSVersion', () => { + const result = clerkJsScriptUrl({ publishableKey: mockDevPublishableKey, __internal_clerkJSVersion: '6' }); expect(result).toContain('/npm/@clerk/clerk-js@6/'); }); }); @@ -386,9 +386,9 @@ describe('clerkUIScriptUrl()', () => { const mockDevPublishableKey = 'pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk'; const mockProdPublishableKey = 'pk_live_ZXhhbXBsZS5jbGVyay5jb20k'; // example.clerk.com - test('returns clerkUIUrl when provided', () => { + test('returns __internal_clerkUIUrl when provided', () => { const customUrl = 'https://custom.clerk.com/ui.js'; - const result = clerkUIScriptUrl({ clerkUIUrl: customUrl, publishableKey: mockDevPublishableKey }); + const result = clerkUIScriptUrl({ __internal_clerkUIUrl: customUrl, publishableKey: mockDevPublishableKey }); expect(result).toBe(customUrl); }); @@ -404,11 +404,6 @@ describe('clerkUIScriptUrl()', () => { expect(result).toBe(`https://example.clerk.com/npm/@clerk/ui@${uiPackageMajorVersion}/dist/ui.browser.js`); }); - test('uses provided clerkUIVersion', () => { - const result = clerkUIScriptUrl({ publishableKey: mockDevPublishableKey, clerkUIVersion: '1' }); - expect(result).toContain('/npm/@clerk/ui@1/'); - }); - test('uses latest as default version when not specified', () => { const result = clerkUIScriptUrl({ publishableKey: mockDevPublishableKey }); // When no version is specified, versionSelector should return the major version diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 5c14b538bee..96171f5c648 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -12,8 +12,10 @@ const errorThrower = buildErrorThrower({ packageName: '@clerk/shared' }); export type LoadClerkJSScriptOptions = { publishableKey: string; - clerkJSUrl?: string; - clerkJSVersion?: string; + /** @internal */ + __internal_clerkJSUrl?: string; + /** @internal */ + __internal_clerkJSVersion?: string; sdkMetadata?: SDKMetadata; proxyUrl?: string; domain?: string; @@ -33,8 +35,10 @@ export type LoadClerkJsScriptOptions = LoadClerkJSScriptOptions; export type LoadClerkUIScriptOptions = { publishableKey: string; - clerkUIUrl?: string; - clerkUIVersion?: string; + /** @internal */ + __internal_clerkUIUrl?: string; + /** @internal */ + __internal_clerkUIVersion?: string; proxyUrl?: string; domain?: string; nonce?: string; @@ -220,26 +224,26 @@ export const loadClerkUIScript = async (opts?: LoadClerkUIScriptOptions): Promis }; export const clerkJSScriptUrl = (opts: LoadClerkJSScriptOptions) => { - const { clerkJSUrl, clerkJSVersion, proxyUrl, domain, publishableKey } = opts; + const { __internal_clerkJSUrl, __internal_clerkJSVersion, proxyUrl, domain, publishableKey } = opts; - if (clerkJSUrl) { - return clerkJSUrl; + if (__internal_clerkJSUrl) { + return __internal_clerkJSUrl; } const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); - const version = versionSelector(clerkJSVersion); + const version = versionSelector(__internal_clerkJSVersion); return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.browser.js`; }; export const clerkUIScriptUrl = (opts: LoadClerkUIScriptOptions) => { - const { clerkUIUrl, clerkUIVersion, proxyUrl, domain, publishableKey } = opts; + const { __internal_clerkUIUrl, __internal_clerkUIVersion, proxyUrl, domain, publishableKey } = opts; - if (clerkUIUrl) { - return clerkUIUrl; + if (__internal_clerkUIUrl) { + return __internal_clerkUIUrl; } const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); - const version = versionSelector(clerkUIVersion, UI_PACKAGE_VERSION); + const version = versionSelector(__internal_clerkUIVersion, UI_PACKAGE_VERSION); return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`; }; diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 6f0182ab318..dfede252d9a 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2487,24 +2487,39 @@ export type ClerkProp = | undefined | null; +/** + * Internal props used by framework SDKs to configure script URLs and versions. + * These are omitted from consumer-facing types like ClerkProviderProps. + */ +export type InternalClerkScriptProps = { + __internal_clerkJSUrl?: string; + __internal_clerkJSVersion?: string; + __internal_clerkUIUrl?: string; + __internal_clerkUIVersion?: string; +}; + export type IsomorphicClerkOptions = Without & { Clerk?: ClerkProp; /** * The URL that `@clerk/clerk-js` should be hot-loaded from. + * @internal */ - clerkJSUrl?: string; + __internal_clerkJSUrl?: string; /** * The npm version for `@clerk/clerk-js`. + * @internal */ - clerkJSVersion?: string; + __internal_clerkJSVersion?: string; /** * The URL that `@clerk/ui` should be hot-loaded from. + * @internal */ - clerkUIUrl?: string; + __internal_clerkUIUrl?: string; /** * The npm version for `@clerk/ui`. + * @internal */ - clerkUIVersion?: string; + __internal_clerkUIVersion?: string; /** * The Clerk Publishable Key for your instance. This can be found on the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard. */ diff --git a/packages/tanstack-react-start/src/client/ClerkProvider.tsx b/packages/tanstack-react-start/src/client/ClerkProvider.tsx index 8247efbac29..23977e0a999 100644 --- a/packages/tanstack-react-start/src/client/ClerkProvider.tsx +++ b/packages/tanstack-react-start/src/client/ClerkProvider.tsx @@ -1,5 +1,4 @@ -import { ClerkProvider as ReactClerkProvider } from '@clerk/react'; -import type { Ui } from '@clerk/react/internal'; +import { InternalClerkProvider as ReactClerkProvider, type Ui } from '@clerk/react/internal'; import { ScriptOnce } from '@tanstack/react-router'; import { getGlobalStartContext } from '@tanstack/react-start'; import { useEffect } from 'react'; diff --git a/packages/tanstack-react-start/src/client/utils.ts b/packages/tanstack-react-start/src/client/utils.ts index f89d19d7ce0..e97c338885f 100644 --- a/packages/tanstack-react-start/src/client/utils.ts +++ b/packages/tanstack-react-start/src/client/utils.ts @@ -1,3 +1,5 @@ +import type { InternalClerkScriptProps } from '@clerk/shared/types'; + import { getPublicEnvVariables } from '../utils/env'; import type { TanstackStartClerkProviderProps } from './types'; @@ -5,11 +7,12 @@ type TanStackProviderAndInitialProps = Omit { +): TanStackProviderAndInitialProps & + InternalClerkScriptProps & { + clerkSsrState: any; + __keylessClaimUrl?: string; + __keylessApiKeysUrl?: string; + } => { const { __clerk_ssr_state, __publishableKey, @@ -41,10 +44,10 @@ export const pickFromClerkInitState = ( isSatellite: !!__isSatellite, signInUrl: __signInUrl, signUpUrl: __signUpUrl, - clerkJSUrl: __clerkJSUrl, - clerkJSVersion: __clerkJSVersion, - clerkUIUrl: __clerkUIUrl, - clerkUIVersion: __clerkUIVersion, + __internal_clerkJSUrl: __clerkJSUrl, + __internal_clerkJSVersion: __clerkJSVersion, + __internal_clerkUIUrl: __clerkUIUrl, + __internal_clerkUIVersion: __clerkUIVersion, prefetchUI: __prefetchUI, telemetry: { disabled: __telemetryDisabled, @@ -68,10 +71,10 @@ export const mergeWithPublicEnvs = (restInitState: any) => { isSatellite: restInitState.isSatellite || envVars.isSatellite, signInUrl: restInitState.signInUrl || envVars.signInUrl, signUpUrl: restInitState.signUpUrl || envVars.signUpUrl, - clerkJSUrl: restInitState.clerkJSUrl || envVars.clerkJsUrl, - clerkJSVersion: restInitState.clerkJSVersion || envVars.clerkJsVersion, - clerkUIUrl: restInitState.clerkUIUrl || envVars.clerkUIUrl, - clerkUIVersion: restInitState.clerkUIVersion || envVars.clerkUIVersion, + __internal_clerkJSUrl: restInitState.__internal_clerkJSUrl || envVars.clerkJsUrl, + __internal_clerkJSVersion: restInitState.__internal_clerkJSVersion || envVars.clerkJsVersion, + __internal_clerkUIUrl: restInitState.__internal_clerkUIUrl || envVars.clerkUIUrl, + __internal_clerkUIVersion: restInitState.__internal_clerkUIVersion || envVars.clerkUIVersion, signInForceRedirectUrl: restInitState.signInForceRedirectUrl, prefetchUI: restInitState.prefetchUI ?? envVars.prefetchUI, }; diff --git a/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-internal-clerk-js-ui-props.fixtures.js b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-internal-clerk-js-ui-props.fixtures.js new file mode 100644 index 00000000000..14efd63a29a --- /dev/null +++ b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-internal-clerk-js-ui-props.fixtures.js @@ -0,0 +1,251 @@ +export const fixtures = [ + { + name: 'JSX: renames clerkJSUrl to __internal_clerkJSUrl', + source: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + output: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + }, + { + name: 'JSX: renames clerkJSVersion to __internal_clerkJSVersion', + source: ` +import { ClerkProvider } from '@clerk/react'; + +export const App = () => ( + +
+ +); + `, + output: ` +import { ClerkProvider } from '@clerk/react'; + +export const App = () => ( + +
+ +); + `, + }, + { + name: 'JSX: renames clerkUIUrl and clerkUIVersion', + source: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + output: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + }, + { + name: 'JSX: renames all four props at once', + source: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + output: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + }, + { + name: 'JSX: does not rename props on non-Clerk components', + source: ` +import { SomeProvider } from 'some-other-package'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + noChange: true, + }, + { + name: 'JSX: handles aliased import', + source: ` +import { ClerkProvider as CP } from '@clerk/react'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + output: ` +import { ClerkProvider as CP } from '@clerk/react'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + }, + { + name: 'JSX: skips if __internal_ prop already exists', + source: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + output: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + }, + { + name: 'Object: renames properties in object literal', + source: ` +const options = { + clerkJSUrl: 'https://js.example.com/clerk.js', + clerkJSVersion: '5.0.0', +}; + `, + output: ` +const options = { + __internal_clerkJSUrl: 'https://js.example.com/clerk.js', + __internal_clerkJSVersion: '5.0.0', +}; + `, + }, + { + name: 'Object: renames all four properties', + source: ` +const options = { + clerkJSUrl: 'https://js.example.com/clerk.js', + clerkJSVersion: '5.0.0', + clerkUIUrl: 'https://ui.example.com/ui.js', + clerkUIVersion: '1.0.0', +}; + `, + output: ` +const options = { + __internal_clerkJSUrl: 'https://js.example.com/clerk.js', + __internal_clerkJSVersion: '5.0.0', + __internal_clerkUIUrl: 'https://ui.example.com/ui.js', + __internal_clerkUIVersion: '1.0.0', +}; + `, + }, + { + name: 'No matching props (no changes)', + source: ` +import { ClerkProvider } from '@clerk/nextjs'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + noChange: true, + }, + { + name: 'JSX: expression container value', + source: ` +import { ClerkProvider } from '@clerk/nextjs'; + +const jsUrl = 'https://js.example.com/clerk.js'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + output: ` +import { ClerkProvider } from '@clerk/nextjs'; + +const jsUrl = 'https://js.example.com/clerk.js'; + +export function App({ children }) { + return ( + + {children} + + ); +} + `, + }, +]; diff --git a/packages/upgrade/src/codemods/__tests__/transform-internal-clerk-js-ui-props.test.js b/packages/upgrade/src/codemods/__tests__/transform-internal-clerk-js-ui-props.test.js new file mode 100644 index 00000000000..a045cd5596d --- /dev/null +++ b/packages/upgrade/src/codemods/__tests__/transform-internal-clerk-js-ui-props.test.js @@ -0,0 +1,17 @@ +import { applyTransform } from 'jscodeshift/dist/testUtils'; +import { describe, expect, it } from 'vitest'; + +import transformer from '../transform-internal-clerk-js-ui-props.cjs'; +import { fixtures } from './__fixtures__/transform-internal-clerk-js-ui-props.fixtures'; + +describe('transform-internal-clerk-js-ui-props', () => { + it.each(fixtures)('$name', ({ source, output, noChange }) => { + const result = applyTransform(transformer, {}, { source }); + + if (noChange) { + expect(result).toEqual(''); + } else { + expect(result).toEqual(output.trim()); + } + }); +}); diff --git a/packages/upgrade/src/codemods/transform-internal-clerk-js-ui-props.cjs b/packages/upgrade/src/codemods/transform-internal-clerk-js-ui-props.cjs new file mode 100644 index 00000000000..2fbac369f63 --- /dev/null +++ b/packages/upgrade/src/codemods/transform-internal-clerk-js-ui-props.cjs @@ -0,0 +1,161 @@ +const CLERK_PACKAGE_PREFIX = '@clerk/'; + +const PROP_RENAMES = new Map([ + ['clerkJSUrl', '__internal_clerkJSUrl'], + ['clerkJSVersion', '__internal_clerkJSVersion'], + ['clerkUIUrl', '__internal_clerkUIUrl'], + ['clerkUIVersion', '__internal_clerkUIVersion'], +]); + +const COMPONENTS_WITH_PROPS = new Set(['ClerkProvider']); + +module.exports = function transformInternalClerkJsUiProps({ source }, { jscodeshift: j, stats }) { + const root = j(source); + let dirty = false; + + const { namedImports, namespaceImports } = collectClerkImports(root, j); + + // Transform JSX attributes + root.find(j.JSXOpeningElement).forEach(path => { + const canonicalName = getCanonicalComponentName(path.node.name, namedImports, namespaceImports); + if (!canonicalName || !COMPONENTS_WITH_PROPS.has(canonicalName)) { + return; + } + + for (const [oldName, newName] of PROP_RENAMES) { + if (renameJsxAttribute(j, path.node, oldName, newName)) { + dirty = true; + stats(oldName + 'Renamed'); + } + } + }); + + // Transform object properties + for (const [oldName, newName] of PROP_RENAMES) { + if (renameObjectProperties(root, j, oldName, newName)) { + dirty = true; + stats(oldName + 'Renamed'); + } + } + + return dirty ? root.toSource() : undefined; +}; + +module.exports.parser = 'tsx'; + +function collectClerkImports(root, j) { + const namedImports = new Map(); + const namespaceImports = new Set(); + + root.find(j.ImportDeclaration).forEach(path => { + const sourceVal = path.node.source.value; + if (typeof sourceVal !== 'string' || !sourceVal.startsWith(CLERK_PACKAGE_PREFIX)) { + return; + } + + for (const specifier of path.node.specifiers || []) { + if (j.ImportSpecifier.check(specifier)) { + const localName = specifier.local ? specifier.local.name : specifier.imported.name; + namedImports.set(localName, specifier.imported.name); + } else if (j.ImportNamespaceSpecifier.check(specifier) || j.ImportDefaultSpecifier.check(specifier)) { + namespaceImports.add(specifier.local.name); + } + } + }); + + return { namedImports, namespaceImports }; +} + +function getCanonicalComponentName(nameNode, namedImports, namespaceImports) { + if (!nameNode) { + return null; + } + + if (nameNode.type === 'JSXIdentifier') { + return namedImports.get(nameNode.name) || nameNode.name; + } + + if (nameNode.type === 'JSXMemberExpression') { + return getNamespaceMemberName(nameNode, namespaceImports); + } + + return null; +} + +function getNamespaceMemberName(memberNode, namespaceImports) { + if (memberNode.object.type === 'JSXIdentifier') { + return namespaceImports.has(memberNode.object.name) ? memberNode.property.name : null; + } + + if (memberNode.object.type === 'JSXMemberExpression') { + const resolved = getNamespaceMemberName(memberNode.object, namespaceImports); + return resolved ? memberNode.property.name : null; + } + + return null; +} + +function renameJsxAttribute(j, jsxNode, oldName, newName) { + if (!jsxNode.attributes) { + return false; + } + + const attrIndex = jsxNode.attributes.findIndex(attr => isJsxAttrNamed(attr, oldName)); + if (attrIndex === -1) { + return false; + } + + // If target already exists, just remove the old one + const targetExists = jsxNode.attributes.some(attr => isJsxAttrNamed(attr, newName)); + if (targetExists) { + jsxNode.attributes.splice(attrIndex, 1); + return true; + } + + jsxNode.attributes[attrIndex].name.name = newName; + return true; +} + +function isJsxAttrNamed(attribute, name) { + return attribute && attribute.type === 'JSXAttribute' && attribute.name && attribute.name.name === name; +} + +function renameObjectProperties(root, j, oldName, newName) { + let changed = false; + + root.find(j.ObjectProperty).forEach(path => { + if (!isPropertyKeyNamed(path.node.key, oldName)) { + return; + } + + if (path.node.shorthand) { + path.node.shorthand = false; + path.node.value = j.identifier(oldName); + } + + if (path.node.key.type === 'Identifier') { + path.node.key.name = newName; + } else if (path.node.key.type === 'StringLiteral') { + path.node.key.value = newName; + } else if (path.node.key.type === 'Literal') { + path.node.key.value = newName; + } + + changed = true; + }); + + return changed; +} + +function isPropertyKeyNamed(keyNode, name) { + if (!keyNode) { + return false; + } + if (keyNode.type === 'Identifier') { + return keyNode.name === name; + } + if (keyNode.type === 'StringLiteral' || keyNode.type === 'Literal') { + return keyNode.value === name; + } + return false; +} diff --git a/packages/upgrade/src/versions/core-3/changes/clerk-js-ui-props-internalized.md b/packages/upgrade/src/versions/core-3/changes/clerk-js-ui-props-internalized.md new file mode 100644 index 00000000000..e9dca2fdcc1 --- /dev/null +++ b/packages/upgrade/src/versions/core-3/changes/clerk-js-ui-props-internalized.md @@ -0,0 +1,28 @@ +--- +title: '`clerkJSUrl`, `clerkJSVersion`, `clerkUIUrl`, and `clerkUIVersion` props removed' +matcher: + - 'clerkJSUrl' + - 'clerkJSVersion' + - 'clerkUIUrl' + - 'clerkUIVersion' +category: 'deprecation-removal' +--- + +The `clerkJSUrl`, `clerkJSVersion`, `clerkUIUrl`, and `clerkUIVersion` props have been removed from `ClerkProvider` and related configuration options. These props were intended for internal use and are no longer part of the public API. + +If you are using these props, prefix them with `__internal_` to continue using them. Note that these are internal APIs and may change without notice. + +A codemod is available to automatically apply this change: + +```diff + +``` diff --git a/packages/upgrade/src/versions/core-3/index.js b/packages/upgrade/src/versions/core-3/index.js index b30b1a8ba32..3b893520fe6 100644 --- a/packages/upgrade/src/versions/core-3/index.js +++ b/packages/upgrade/src/versions/core-3/index.js @@ -31,5 +31,6 @@ export default { // Migrate @clerk/react-router/api.server → @clerk/react-router/server { name: 'transform-react-router-api-server', packages: ['react-router'] }, { name: 'transform-satellite-auto-sync', packages: ['nextjs', 'react', 'expo', 'astro', 'tanstack-react-start'] }, + 'transform-internal-clerk-js-ui-props', ], }; diff --git a/packages/vue/src/plugin.ts b/packages/vue/src/plugin.ts index cb61569ee5e..04573bbaa45 100644 --- a/packages/vue/src/plugin.ts +++ b/packages/vue/src/plugin.ts @@ -6,6 +6,7 @@ import type { ClerkOptions, ClientResource, InitialState, + InternalClerkScriptProps, IsomorphicClerkOptions, MultiDomainAndOrProxy, Resources, @@ -23,7 +24,10 @@ declare global { } } -export type PluginOptions = Without & +export type PluginOptions = Without< + IsomorphicClerkOptions, + 'domain' | 'proxyUrl' | 'appearance' | keyof InternalClerkScriptProps +> & MultiDomainAndOrProxy & { initialState?: InitialState; appearance?: Appearance; diff --git a/playground/app-router/src/app/layout.tsx b/playground/app-router/src/app/layout.tsx index c90b61270fd..89d40d4d778 100644 --- a/playground/app-router/src/app/layout.tsx +++ b/playground/app-router/src/app/layout.tsx @@ -13,7 +13,7 @@ export const metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { // console.log(auth()); return ( - +