From d3f7db89399084a0e0088ddbb3e3ba5ce02786d3 Mon Sep 17 00:00:00 2001 From: brkalow Date: Wed, 14 Jan 2026 10:56:10 -0600 Subject: [PATCH 01/16] feat(ui,react): Add shared React variant to reduce bundle size Introduces a "shared" variant of @clerk/ui that externalizes React dependencies, allowing the host application's React to be reused instead of bundling a separate copy. Changes: - Add @clerk/ui/register module to register React on globalThis - Add ui.shared.browser.js build variant with externalized React - Add React version compatibility checking in @clerk/react - Add clerkUiVariant option to load the appropriate variant - Make dev server React externalization conditional via --env shared Co-Authored-By: Claude Opus 4.5 --- packages/clerk-js/package.json | 25 +- packages/react/package.json | 4 +- .../src/contexts/ClerkContextProvider.tsx | 59 +++- packages/react/src/index.ts | 5 + packages/react/src/isomorphicClerk.ts | 1 + packages/react/tsup.config.ts | 54 +++ packages/shared/src/loadClerkJsScript.ts | 6 +- packages/shared/src/types/clerk.ts | 5 + packages/ui/package.json | 16 +- packages/ui/register/index.d.ts | 15 + packages/ui/register/index.js | 28 ++ packages/ui/register/index.mjs | 27 ++ packages/ui/rspack.config.js | 39 ++- pnpm-lock.yaml | 325 ++++++++++++++++-- 14 files changed, 580 insertions(+), 29 deletions(-) create mode 100644 packages/ui/register/index.d.ts create mode 100644 packages/ui/register/index.js create mode 100644 packages/ui/register/index.mjs diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 478c802b1ab..5d041cb2d14 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -21,6 +21,28 @@ }, "license": "MIT", "author": "Clerk", + "exports": { + ".": { + "import": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.mjs" + }, + "require": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.js" + } + }, + "./headless": { + "import": { + "types": "./dist/types/index.headless.d.ts", + "default": "./dist/clerk.headless.mjs" + }, + "require": { + "types": "./dist/types/index.headless.d.ts", + "default": "./dist/clerk.headless.js" + } + } + }, "main": "dist/clerk.js", "jsdelivr": "dist/clerk.browser.js", "module": "dist/clerk.mjs", @@ -89,7 +111,8 @@ "bundlewatch": "^0.4.1", "jsdom": "26.1.0", "minimatch": "^10.0.3", - "webpack-merge": "^5.10.0" + "webpack-merge": "^5.10.0", + "yaml": "^2.7.0" }, "engines": { "node": ">=20.9.0" diff --git a/packages/react/package.json b/packages/react/package.json index 3968872e86b..65ff93c930e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -101,7 +101,9 @@ "devDependencies": { "@clerk/localizations": "workspace:*", "@clerk/ui": "workspace:*", - "@types/semver": "^7.7.1" + "@types/semver": "^7.7.1", + "semver": "^7.7.1", + "yaml": "^2.8.0" }, "peerDependencies": { "react": "catalog:peer-react", diff --git a/packages/react/src/contexts/ClerkContextProvider.tsx b/packages/react/src/contexts/ClerkContextProvider.tsx index 6d507da4b6c..9df38f3f6c9 100644 --- a/packages/react/src/contexts/ClerkContextProvider.tsx +++ b/packages/react/src/contexts/ClerkContextProvider.tsx @@ -14,6 +14,47 @@ import type { IsomorphicClerkOptions } from '../types'; import { AuthContext } from './AuthContext'; import { IsomorphicClerkContext } from './IsomorphicClerkContext'; +// Version bounds format: [major, minMinor, maxMinor, minPatch] +// - maxMinor === -1 means "any minor" (caret range, e.g., ^18.0.0) +// - maxMinor === minMinor means "same minor only" (tilde range, e.g., ~19.0.3) +declare const __CLERK_UI_SUPPORTED_REACT_BOUNDS__: Array< + [major: number, minMinor: number, maxMinor: number, minPatch: number] +>; + +/** + * Checks if the host application's React version is compatible with @clerk/ui's shared variant. + * The shared variant expects React to be provided via globalThis.__clerkSharedModules, + * so we need to ensure the host's React version matches what @clerk/ui was built against. + */ +function isReactVersionCompatibleWithSharedVariant(): boolean { + try { + // Parse version string (e.g., "18.3.1" or "19.0.0-rc.1") + const match = React.version.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match) return false; + + const [, majorStr, minorStr, patchStr] = match; + const major = parseInt(majorStr, 10); + const minor = parseInt(minorStr, 10); + const patch = parseInt(patchStr, 10); + + // Check against pre-computed bounds + return __CLERK_UI_SUPPORTED_REACT_BOUNDS__.some(([bMajor, minMinor, maxMinor, minPatch]) => { + if (major !== bMajor) return false; + + if (maxMinor === -1) { + // Caret range: any minor >= minMinor, with patch check for minMinor + return minor > minMinor || (minor === minMinor && patch >= minPatch); + } else { + // Tilde range: specific minor only + return minor === maxMinor && patch >= minPatch; + } + }); + } catch { + // If we can't determine compatibility, fall back to non-shared variant + return false; + } +} + type ClerkContextProvider = { isomorphicClerkOptions: IsomorphicClerkOptions; initialState: InitialState | undefined; @@ -112,7 +153,23 @@ export function ClerkContextProvider(props: ClerkContextProvider) { } const useLoadedIsomorphicClerk = (options: IsomorphicClerkOptions) => { - const isomorphicClerkRef = React.useRef(IsomorphicClerk.getOrCreateInstance(options)); + // Default to 'shared' variant for @clerk/ui if the host's React version is compatible. + // The shared variant expects React to be provided via globalThis.__clerkSharedModules + // (set up by @clerk/ui/register import), which reduces bundle size. + const defaultClerkUiVariant = React.useMemo( + () => (isReactVersionCompatibleWithSharedVariant() ? ('shared' as const) : ('' as const)), + [], + ); + // Merge default clerkUiVariant with user options. + // User-provided options spread last to allow explicit overrides. + const optionsWithDefaults = React.useMemo( + () => ({ + clerkUiVariant: defaultClerkUiVariant, + ...options, + }), + [defaultClerkUiVariant, options], + ); + const isomorphicClerkRef = React.useRef(IsomorphicClerk.getOrCreateInstance(optionsWithDefaults)); const [clerkStatus, setClerkStatus] = React.useState(isomorphicClerkRef.current.status); React.useEffect(() => { diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 97d841eaf1c..568b14fa746 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,6 +1,11 @@ import './polyfills'; import './types/appearance'; +// Register React on the global shared modules registry. +// This enables @clerk/ui's shared variant to use the host app's React +// instead of bundling its own copy, reducing overall bundle size. +import '@clerk/ui/register'; + import { setClerkJsLoadingErrorPackageName } from '@clerk/shared/loadClerkJsScript'; import { setErrorThrowerOptions } from './errors/errorThrower'; diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index b39bf352b8c..7549cd17d5e 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -515,6 +515,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { await loadClerkUiScript({ ...this.options, + clerkUiVariant: this.options.clerkUiVariant, clerkUiVersion: this.options.ui?.version, clerkUiUrl: this.options.ui?.url || this.options.clerkUiUrl, publishableKey: this.#publishableKey, diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index b529ea7ff08..490d7e206dd 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -1,11 +1,64 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { coerce } from 'semver'; +import { parse as parseYaml } from 'yaml'; import { defineConfig } from 'tsup'; import { version as clerkJsVersion } from '../clerk-js/package.json'; import { name, version } from './package.json'; +// Version bounds format: [major, minMinor, maxMinor, minPatch] +// - maxMinor === -1 means "any minor" (caret range, e.g., ^18.0.0) +// - maxMinor === minMinor means "same minor only" (tilde range, e.g., ~19.0.3) +type VersionBounds = [major: number, minMinor: number, maxMinor: number, minPatch: number]; + +// Read supported React range from pnpm-workspace.yaml catalogs and parse into bounds +function getClerkUiSupportedReactBounds(): VersionBounds[] { + let rangeStr = '^18.0.0 || ^19.0.0'; + + try { + const workspaceYamlPath = resolve(__dirname, '../../pnpm-workspace.yaml'); + const workspaceYaml = readFileSync(workspaceYamlPath, 'utf-8'); + const workspace = parseYaml(workspaceYaml); + rangeStr = workspace?.catalogs?.['peer-react']?.react || rangeStr; + } catch { + // Use fallback range + } + + // Parse the range string into bounds + const bounds: VersionBounds[] = []; + const parts = rangeStr.split('||').map(s => s.trim()); + + for (const part of parts) { + if (part.startsWith('^')) { + // Caret range: ^X.Y.Z means >= X.Y.Z and < (X+1).0.0 + const ver = coerce(part.slice(1)); + if (ver) { + bounds.push([ver.major, ver.minor, -1, ver.patch]); + } + } else if (part.startsWith('~')) { + // Tilde range: ~X.Y.Z means >= X.Y.Z and < X.(Y+1).0 + const ver = coerce(part.slice(1)); + if (ver) { + bounds.push([ver.major, ver.minor, ver.minor, ver.patch]); + } + } else { + // Exact version or other format - try to parse as caret + const ver = coerce(part); + if (ver) { + bounds.push([ver.major, ver.minor, -1, ver.patch]); + } + } + } + + return bounds; +} + export default defineConfig(overrideOptions => { const isWatch = !!overrideOptions.watch; const shouldPublish = !!overrideOptions.env?.publish; + const clerkUiSupportedReactBounds = getClerkUiSupportedReactBounds(); return { entry: { @@ -28,6 +81,7 @@ export default defineConfig(overrideOptions => { PACKAGE_VERSION: `"${version}"`, JS_PACKAGE_VERSION: `"${clerkJsVersion}"`, __DEV__: `${isWatch}`, + __CLERK_UI_SUPPORTED_REACT_BOUNDS__: JSON.stringify(clerkUiSupportedReactBounds), }, }; }); diff --git a/packages/shared/src/loadClerkJsScript.ts b/packages/shared/src/loadClerkJsScript.ts index 2e3a6012dbe..6f48971f1d6 100644 --- a/packages/shared/src/loadClerkJsScript.ts +++ b/packages/shared/src/loadClerkJsScript.ts @@ -30,6 +30,7 @@ export type LoadClerkJsScriptOptions = { export type LoadClerkUiScriptOptions = { publishableKey: string; clerkUiUrl?: string; + clerkUiVariant?: 'shared' | ''; clerkUiVersion?: string; proxyUrl?: string; domain?: string; @@ -229,15 +230,16 @@ export const clerkJsScriptUrl = (opts: LoadClerkJsScriptOptions) => { }; export const clerkUiScriptUrl = (opts: LoadClerkUiScriptOptions) => { - const { clerkUiUrl, clerkUiVersion, proxyUrl, domain, publishableKey } = opts; + const { clerkUiUrl, clerkUiVariant, clerkUiVersion, proxyUrl, domain, publishableKey } = opts; if (clerkUiUrl) { return clerkUiUrl; } const scriptHost = buildScriptHost({ publishableKey, proxyUrl, domain }); + const variant = clerkUiVariant ? `${clerkUiVariant}.` : ''; const version = versionSelector(clerkUiVersion, UI_PACKAGE_VERSION); - return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.browser.js`; + return `https://${scriptHost}/npm/@clerk/ui@${version}/dist/ui.${variant}browser.js`; }; export const buildClerkJsScriptAttributes = (options: LoadClerkJsScriptOptions) => { diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 33b15d3bfe8..e4eb32eb5ee 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -2361,6 +2361,11 @@ export type IsomorphicClerkOptions = Without & { * The URL that `@clerk/ui` should be hot-loaded from. */ clerkUiUrl?: string; + /** + * If set to `'shared'`, loads a variant of `@clerk/ui` that expects React to be provided by the host application via `globalThis.__clerkSharedModules`. + * This reduces bundle size when using framework packages like `@clerk/react`. + */ + clerkUiVariant?: 'shared' | ''; /** * 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/ui/package.json b/packages/ui/package.json index d65256cf3e8..86f7bd10750 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -10,7 +10,8 @@ "license": "MIT", "author": "Clerk", "sideEffects": [ - "./src/utils/setWebpackChunkPublicPath.ts" + "./src/utils/setWebpackChunkPublicPath.ts", + "./register/*" ], "type": "module", "exports": { @@ -40,11 +41,22 @@ "default": "./dist/themes/experimental.js" }, "./themes/shadcn.css": "./dist/themes/shadcn.css", + "./register": { + "import": { + "types": "./register/index.d.ts", + "default": "./register/index.mjs" + }, + "require": { + "types": "./register/index.d.ts", + "default": "./register/index.js" + } + }, "./package.json": "./package.json" }, "types": "dist/index.d.ts", "files": [ - "dist" + "dist", + "register" ], "scripts": { "build": "pnpm build:umd && pnpm build:esm", diff --git a/packages/ui/register/index.d.ts b/packages/ui/register/index.d.ts new file mode 100644 index 00000000000..ba198b039a6 --- /dev/null +++ b/packages/ui/register/index.d.ts @@ -0,0 +1,15 @@ +/** + * Registers React and ReactDOM on the global shared modules registry. + * Import this before loading @clerk/ui's shared variant to enable dependency sharing. + */ +export {}; + +declare global { + var __clerkSharedModules: + | { + react: typeof import('react'); + 'react-dom': typeof import('react-dom'); + 'react/jsx-runtime': typeof import('react/jsx-runtime'); + } + | undefined; +} diff --git a/packages/ui/register/index.js b/packages/ui/register/index.js new file mode 100644 index 00000000000..83e86842f8f --- /dev/null +++ b/packages/ui/register/index.js @@ -0,0 +1,28 @@ +/** + * Register React dependencies for sharing with @clerk/ui's shared variant. + * + * Import this module BEFORE loading the ui.shared.browser.js bundle: + * + * ```js + * require('@clerk/ui/register'); + * // Now load clerk-js which will load ui.shared.browser.js + * ``` + * + * This enables @clerk/ui to use the host app's React instead of bundling its own, + * reducing the overall bundle size. + */ +'use strict'; + +const react = require('react'); +const reactDom = require('react-dom'); +const jsxRuntime = require('react/jsx-runtime'); + +// Only register if not already registered to avoid overwriting with potentially +// different React versions in complex module resolution scenarios. +if (!globalThis.__clerkSharedModules) { + globalThis.__clerkSharedModules = { + react, + 'react-dom': reactDom, + 'react/jsx-runtime': jsxRuntime, + }; +} diff --git a/packages/ui/register/index.mjs b/packages/ui/register/index.mjs new file mode 100644 index 00000000000..f3d313b6772 --- /dev/null +++ b/packages/ui/register/index.mjs @@ -0,0 +1,27 @@ +/** + * Register React dependencies for sharing with @clerk/ui's shared variant. + * + * Import this module BEFORE loading the ui.shared.browser.js bundle: + * + * ```js + * import '@clerk/ui/register'; + * // Now load clerk-js which will load ui.shared.browser.js + * ``` + * + * This enables @clerk/ui to use the host app's React instead of bundling its own, + * reducing the overall bundle size. + */ + +import * as react from 'react'; +import * as reactDom from 'react-dom'; +import * as jsxRuntime from 'react/jsx-runtime'; + +// Only register if not already registered to avoid overwriting with potentially +// different React versions in complex module resolution scenarios. +if (!globalThis.__clerkSharedModules) { + globalThis.__clerkSharedModules = { + react, + 'react-dom': reactDom, + 'react/jsx-runtime': jsxRuntime, + }; +} diff --git a/packages/ui/rspack.config.js b/packages/ui/rspack.config.js index a725da7c340..9d731350367 100644 --- a/packages/ui/rspack.config.js +++ b/packages/ui/rspack.config.js @@ -16,14 +16,34 @@ const __dirname = path.dirname(__filename); const isProduction = mode => mode === 'production'; const isDevelopment = mode => !isProduction(mode); +/** + * Externals handler for the shared variant that reads React from globalThis.__clerkSharedModules. + * This allows the host application's React to be shared with @clerk/ui. + * @type {import('@rspack/core').ExternalItemFunctionData} + */ +const sharedReactExternalsHandler = ({ request }, callback) => { + if (request === 'react') { + return callback(null, ['__clerkSharedModules', 'react'], 'root'); + } + if (request === 'react-dom') { + return callback(null, ['__clerkSharedModules', 'react-dom'], 'root'); + } + if (request === 'react/jsx-runtime') { + return callback(null, ['__clerkSharedModules', 'react/jsx-runtime'], 'root'); + } + callback(); +}; + const variants = { uiBrowser: 'ui.browser', uiLegacyBrowser: 'ui.legacy.browser', + uiSharedBrowser: 'ui.shared.browser', }; const variantToSourceFile = { [variants.uiBrowser]: './src/index.browser.ts', [variants.uiLegacyBrowser]: './src/index.legacy.browser.ts', + [variants.uiSharedBrowser]: './src/index.browser.ts', // Same entry, different externals }; /** @@ -197,18 +217,32 @@ const prodConfig = mode => { commonForProdBrowser({ targets: packageJSON.browserslistLegacy, useCoreJs: true }), ); - return [uiBrowser, uiLegacyBrowser]; + // Shared variant - externalizes react/react-dom to use host app's versions + // Expects host to provide these via globalThis.__clerkSharedModules + const uiSharedBrowser = merge( + entryForVariant(variants.uiSharedBrowser), + common({ mode, variant: variants.uiSharedBrowser }), + commonForProdBrowser(), + { + externals: [sharedReactExternalsHandler], + }, + ); + + return [uiBrowser, uiLegacyBrowser, uiSharedBrowser]; }; /** * Development configuration - only builds browser bundle with dev server * @param {'development'|'production'} mode * @param {object} env + * @param {boolean} [env.shared] - If true, externalize React to globalThis.__clerkSharedModules (for use with @clerk/react). + * If false/unset, bundle React normally (for standalone or non-React framework usage). * @returns {import('@rspack/core').Configuration} */ const devConfig = (mode, env) => { const devUrl = new URL(env.devOrigin || 'https://ui.lclclerk.com'); const port = Number(new URL(env.devOrigin ?? 'http://localhost:4011').port || 4011); + const useSharedReact = Boolean(env.shared); return merge(entryForVariant(variants.uiBrowser), common({ mode, variant: variants.uiBrowser }), { module: { @@ -242,6 +276,9 @@ const devConfig = (mode, env) => { type: 'memory', }, }, + // Only externalize React when using the shared variant (e.g., with @clerk/react). + // For standalone usage or non-React frameworks, bundle React normally. + ...(useSharedReact ? { externals: [sharedReactExternalsHandler] } : {}), }); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57d54b6b39e..bd54bc8fd0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,7 +154,7 @@ importers: version: 4.7.0(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) + version: 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)) chalk: specifier: 4.1.2 version: 4.1.2 @@ -331,7 +331,7 @@ importers: version: 6.1.6(typanion@3.14.0) vitest: specifier: 3.2.4 - version: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + version: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1) yalc: specifier: 1.0.0-pre.53 version: 1.0.0-pre.53 @@ -418,7 +418,7 @@ importers: version: 9.0.2 vitest-environment-miniflare: specifier: 2.14.4 - version: 2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) + version: 2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)) packages/chrome-extension: dependencies: @@ -544,6 +544,9 @@ importers: webpack-merge: specifier: ^5.10.0 version: 5.10.0 + yaml: + specifier: ^2.7.0 + version: 2.8.1 packages/dev-cli: dependencies: @@ -797,6 +800,12 @@ importers: '@types/semver': specifier: ^7.7.1 version: 7.7.1 + semver: + specifier: ^7.7.1 + version: 7.7.3 + yaml: + specifier: ^2.8.0 + version: 2.8.1 packages/react-router: dependencies: @@ -2264,102 +2273,204 @@ packages: resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -2372,6 +2483,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -2384,6 +2501,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -2396,24 +2519,48 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -8017,6 +8164,11 @@ packages: resolution: {integrity: sha512-lNjylaAsJMprYg28zjUyBivP3y0ms9b7RJZ5tdhDUFLa3sCbqZw4wDnbFUSmnyZYWhCYDPxxp7KkXM2TXGw3PQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -14640,6 +14792,37 @@ packages: vite: ^6.0.0 || ^7.0.0 vue: ^3.5.0 + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vite@6.4.1: resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -16857,81 +17040,150 @@ snapshots: esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true @@ -21439,7 +21691,7 @@ snapshots: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) vue: 3.5.24(typescript@5.8.3) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -21454,7 +21706,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1) transitivePeerDependencies: - supports-color @@ -21466,14 +21718,14 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.11.6(@types/node@22.19.0)(typescript@5.8.3) - vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + vite: 5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -24229,6 +24481,32 @@ snapshots: esbuild-plugin-file-path-extensions@2.1.4: {} + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -32431,16 +32709,15 @@ snapshots: dependencies: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) - vite-node@3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): + vite-node@3.2.4(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + vite: 5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) transitivePeerDependencies: - '@types/node' - - jiti - less - lightningcss - sass @@ -32449,8 +32726,6 @@ snapshots: - sugarss - supports-color - terser - - tsx - - yaml vite-node@5.0.0(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: @@ -32517,6 +32792,17 @@ snapshots: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) vue: 3.5.24(typescript@5.8.3) + vite@5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.53.1 + optionalDependencies: + '@types/node': 22.19.0 + fsevents: 2.3.3 + lightningcss: 1.30.2 + terser: 5.44.1 + vite@6.4.1(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.12 @@ -32559,23 +32845,23 @@ snapshots: optionalDependencies: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) - vitest-environment-miniflare@2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)): + vitest-environment-miniflare@2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)): dependencies: '@miniflare/queues': 2.14.4 '@miniflare/runner-vm': 2.14.4 '@miniflare/shared': 2.14.4 '@miniflare/shared-test-environment': 2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) undici: 5.28.4 - vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1) transitivePeerDependencies: - bufferutil - utf-8-validate - vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): + vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -32593,8 +32879,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + vite: 5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) + vite-node: 3.2.4(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 @@ -32602,7 +32888,6 @@ snapshots: '@types/node': 22.19.0 jsdom: 27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10) transitivePeerDependencies: - - jiti - less - lightningcss - msw @@ -32612,8 +32897,6 @@ snapshots: - sugarss - supports-color - terser - - tsx - - yaml vlq@1.0.1: {} From 41e808c7ce2275014f4a1935f8fe89f88eb0b6dd Mon Sep 17 00:00:00 2001 From: brkalow Date: Wed, 14 Jan 2026 10:59:43 -0600 Subject: [PATCH 02/16] docs: Add changeset for shared React variant feature Co-Authored-By: Claude Opus 4.5 --- .changeset/shared-react-variant.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/shared-react-variant.md diff --git a/.changeset/shared-react-variant.md b/.changeset/shared-react-variant.md new file mode 100644 index 00000000000..a3104241a0b --- /dev/null +++ b/.changeset/shared-react-variant.md @@ -0,0 +1,17 @@ +--- +"@clerk/ui": minor +"@clerk/react": minor +"@clerk/shared": patch +--- + +Add shared React variant to reduce bundle size when using `@clerk/react`. + +Introduces a new `ui.shared.browser.js` build variant that externalizes React dependencies, allowing the host application's React to be reused instead of bundling a separate copy. This can significantly reduce bundle size for applications using `@clerk/react`. + +**New features:** +- `@clerk/ui/register` module: Import this to register React on `globalThis.__clerkSharedModules` for sharing with `@clerk/ui` +- `clerkUiVariant` option: Set to `'shared'` to use the shared variant (automatically detected and enabled for compatible React versions in `@clerk/react`) + +**For `@clerk/react` users:** No action required. The shared variant is automatically used when your React version is compatible. + +**For custom integrations:** Import `@clerk/ui/register` before loading the UI bundle, then set `clerkUiVariant: 'shared'` in your configuration. From b49c8a109e9a6836b8bc2846eead05d9bd14b53b Mon Sep 17 00:00:00 2001 From: brkalow Date: Wed, 14 Jan 2026 21:51:24 -0600 Subject: [PATCH 03/16] refactor(react): Improve shared React variant implementation - Extract version checking logic into testable utility (versionCheck.ts) - Add comprehensive unit tests for version parsing and bounds checking - Move version compatibility check to module level for better performance - Add dev warning when pnpm-workspace.yaml fallback is used - Add warning for React version mismatch in register modules - Remove redundant clerkUiVariant assignment in isomorphicClerk.ts Co-Authored-By: Claude Opus 4.5 --- .../src/contexts/ClerkContextProvider.tsx | 59 ++----- packages/react/src/isomorphicClerk.ts | 1 - .../src/utils/__tests__/versionCheck.test.ts | 144 ++++++++++++++++++ packages/react/src/utils/versionCheck.ts | 91 +++++++++++ packages/react/tsup.config.ts | 19 ++- packages/ui/register/index.js | 17 ++- packages/ui/register/index.mjs | 13 +- 7 files changed, 288 insertions(+), 56 deletions(-) create mode 100644 packages/react/src/utils/__tests__/versionCheck.test.ts create mode 100644 packages/react/src/utils/versionCheck.ts diff --git a/packages/react/src/contexts/ClerkContextProvider.tsx b/packages/react/src/contexts/ClerkContextProvider.tsx index 9df38f3f6c9..7013213c17e 100644 --- a/packages/react/src/contexts/ClerkContextProvider.tsx +++ b/packages/react/src/contexts/ClerkContextProvider.tsx @@ -11,50 +11,10 @@ import React from 'react'; import { IsomorphicClerk } from '../isomorphicClerk'; import type { IsomorphicClerkOptions } from '../types'; +import { IS_REACT_SHARED_VARIANT_COMPATIBLE } from '../utils/versionCheck'; import { AuthContext } from './AuthContext'; import { IsomorphicClerkContext } from './IsomorphicClerkContext'; -// Version bounds format: [major, minMinor, maxMinor, minPatch] -// - maxMinor === -1 means "any minor" (caret range, e.g., ^18.0.0) -// - maxMinor === minMinor means "same minor only" (tilde range, e.g., ~19.0.3) -declare const __CLERK_UI_SUPPORTED_REACT_BOUNDS__: Array< - [major: number, minMinor: number, maxMinor: number, minPatch: number] ->; - -/** - * Checks if the host application's React version is compatible with @clerk/ui's shared variant. - * The shared variant expects React to be provided via globalThis.__clerkSharedModules, - * so we need to ensure the host's React version matches what @clerk/ui was built against. - */ -function isReactVersionCompatibleWithSharedVariant(): boolean { - try { - // Parse version string (e.g., "18.3.1" or "19.0.0-rc.1") - const match = React.version.match(/^(\d+)\.(\d+)\.(\d+)/); - if (!match) return false; - - const [, majorStr, minorStr, patchStr] = match; - const major = parseInt(majorStr, 10); - const minor = parseInt(minorStr, 10); - const patch = parseInt(patchStr, 10); - - // Check against pre-computed bounds - return __CLERK_UI_SUPPORTED_REACT_BOUNDS__.some(([bMajor, minMinor, maxMinor, minPatch]) => { - if (major !== bMajor) return false; - - if (maxMinor === -1) { - // Caret range: any minor >= minMinor, with patch check for minMinor - return minor > minMinor || (minor === minMinor && patch >= minPatch); - } else { - // Tilde range: specific minor only - return minor === maxMinor && patch >= minPatch; - } - }); - } catch { - // If we can't determine compatibility, fall back to non-shared variant - return false; - } -} - type ClerkContextProvider = { isomorphicClerkOptions: IsomorphicClerkOptions; initialState: InitialState | undefined; @@ -152,22 +112,21 @@ export function ClerkContextProvider(props: ClerkContextProvider) { ); } +// Default clerkUiVariant based on React version compatibility. +// Computed once at module level for optimal performance. +const DEFAULT_CLERK_UI_VARIANT = IS_REACT_SHARED_VARIANT_COMPATIBLE ? ('shared' as const) : ('' as const); + const useLoadedIsomorphicClerk = (options: IsomorphicClerkOptions) => { - // Default to 'shared' variant for @clerk/ui if the host's React version is compatible. - // The shared variant expects React to be provided via globalThis.__clerkSharedModules - // (set up by @clerk/ui/register import), which reduces bundle size. - const defaultClerkUiVariant = React.useMemo( - () => (isReactVersionCompatibleWithSharedVariant() ? ('shared' as const) : ('' as const)), - [], - ); // Merge default clerkUiVariant with user options. // User-provided options spread last to allow explicit overrides. + // The shared variant expects React to be provided via globalThis.__clerkSharedModules + // (set up by @clerk/ui/register import), which reduces bundle size. const optionsWithDefaults = React.useMemo( () => ({ - clerkUiVariant: defaultClerkUiVariant, + clerkUiVariant: DEFAULT_CLERK_UI_VARIANT, ...options, }), - [defaultClerkUiVariant, options], + [options], ); const isomorphicClerkRef = React.useRef(IsomorphicClerk.getOrCreateInstance(optionsWithDefaults)); const [clerkStatus, setClerkStatus] = React.useState(isomorphicClerkRef.current.status); diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 7549cd17d5e..b39bf352b8c 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -515,7 +515,6 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { await loadClerkUiScript({ ...this.options, - clerkUiVariant: this.options.clerkUiVariant, clerkUiVersion: this.options.ui?.version, clerkUiUrl: this.options.ui?.url || this.options.clerkUiUrl, publishableKey: this.#publishableKey, diff --git a/packages/react/src/utils/__tests__/versionCheck.test.ts b/packages/react/src/utils/__tests__/versionCheck.test.ts new file mode 100644 index 00000000000..9a36f1640c1 --- /dev/null +++ b/packages/react/src/utils/__tests__/versionCheck.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, it } from 'vitest'; + +import { checkVersionAgainstBounds, isVersionCompatible, parseVersion, type VersionBounds } from '../versionCheck'; + +describe('parseVersion', () => { + it('parses standard semver versions', () => { + expect(parseVersion('18.3.1')).toEqual({ major: 18, minor: 3, patch: 1 }); + expect(parseVersion('19.0.0')).toEqual({ major: 19, minor: 0, patch: 0 }); + expect(parseVersion('0.0.1')).toEqual({ major: 0, minor: 0, patch: 1 }); + }); + + it('parses versions with pre-release suffixes', () => { + expect(parseVersion('19.0.0-rc.1')).toEqual({ major: 19, minor: 0, patch: 0 }); + expect(parseVersion('18.3.0-alpha.1')).toEqual({ major: 18, minor: 3, patch: 0 }); + expect(parseVersion('19.0.0-beta.2+build.123')).toEqual({ major: 19, minor: 0, patch: 0 }); + }); + + it('returns null for invalid versions', () => { + expect(parseVersion('')).toBeNull(); + expect(parseVersion('invalid')).toBeNull(); + expect(parseVersion('18')).toBeNull(); + expect(parseVersion('18.3')).toBeNull(); + expect(parseVersion('v18.3.1')).toBeNull(); + expect(parseVersion('18.3.x')).toBeNull(); + }); +}); + +describe('checkVersionAgainstBounds', () => { + describe('caret ranges (maxMinor === -1)', () => { + // ^18.0.0 means >= 18.0.0 and < 19.0.0 + const caretBounds: VersionBounds[] = [[18, 0, -1, 0]]; + + it('matches versions at the minimum', () => { + expect(checkVersionAgainstBounds({ major: 18, minor: 0, patch: 0 }, caretBounds)).toBe(true); + }); + + it('matches versions with higher minor', () => { + expect(checkVersionAgainstBounds({ major: 18, minor: 1, patch: 0 }, caretBounds)).toBe(true); + expect(checkVersionAgainstBounds({ major: 18, minor: 99, patch: 99 }, caretBounds)).toBe(true); + }); + + it('matches versions with higher patch on same minor', () => { + expect(checkVersionAgainstBounds({ major: 18, minor: 0, patch: 1 }, caretBounds)).toBe(true); + expect(checkVersionAgainstBounds({ major: 18, minor: 0, patch: 99 }, caretBounds)).toBe(true); + }); + + it('does not match versions with lower major', () => { + expect(checkVersionAgainstBounds({ major: 17, minor: 99, patch: 99 }, caretBounds)).toBe(false); + }); + + it('does not match versions with higher major', () => { + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 0 }, caretBounds)).toBe(false); + }); + + it('does not match versions below the minimum patch', () => { + // ^18.2.5 means >= 18.2.5 + const boundsWithPatch: VersionBounds[] = [[18, 2, -1, 5]]; + expect(checkVersionAgainstBounds({ major: 18, minor: 2, patch: 4 }, boundsWithPatch)).toBe(false); + expect(checkVersionAgainstBounds({ major: 18, minor: 2, patch: 5 }, boundsWithPatch)).toBe(true); + expect(checkVersionAgainstBounds({ major: 18, minor: 2, patch: 6 }, boundsWithPatch)).toBe(true); + // Higher minor still works + expect(checkVersionAgainstBounds({ major: 18, minor: 3, patch: 0 }, boundsWithPatch)).toBe(true); + }); + }); + + describe('tilde ranges (maxMinor === minMinor)', () => { + // ~19.0.0 means >= 19.0.0 and < 19.1.0 + const tildeBounds: VersionBounds[] = [[19, 0, 0, 0]]; + + it('matches versions at the minimum', () => { + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 0 }, tildeBounds)).toBe(true); + }); + + it('matches versions with higher patch on same minor', () => { + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 1 }, tildeBounds)).toBe(true); + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 99 }, tildeBounds)).toBe(true); + }); + + it('does not match versions with different minor', () => { + expect(checkVersionAgainstBounds({ major: 19, minor: 1, patch: 0 }, tildeBounds)).toBe(false); + expect(checkVersionAgainstBounds({ major: 19, minor: 2, patch: 0 }, tildeBounds)).toBe(false); + }); + + it('does not match versions with different major', () => { + expect(checkVersionAgainstBounds({ major: 18, minor: 0, patch: 0 }, tildeBounds)).toBe(false); + expect(checkVersionAgainstBounds({ major: 20, minor: 0, patch: 0 }, tildeBounds)).toBe(false); + }); + + it('does not match versions below the minimum patch', () => { + // ~19.0.3 means >= 19.0.3 and < 19.1.0 + const boundsWithPatch: VersionBounds[] = [[19, 0, 0, 3]]; + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 2 }, boundsWithPatch)).toBe(false); + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 3 }, boundsWithPatch)).toBe(true); + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 4 }, boundsWithPatch)).toBe(true); + }); + }); + + describe('multiple bounds', () => { + // ^18.0.0 || ^19.0.0 + const multipleBounds: VersionBounds[] = [ + [18, 0, -1, 0], + [19, 0, -1, 0], + ]; + + it('matches versions satisfying any bound', () => { + expect(checkVersionAgainstBounds({ major: 18, minor: 3, patch: 1 }, multipleBounds)).toBe(true); + expect(checkVersionAgainstBounds({ major: 19, minor: 0, patch: 0 }, multipleBounds)).toBe(true); + }); + + it('does not match versions outside all bounds', () => { + expect(checkVersionAgainstBounds({ major: 17, minor: 0, patch: 0 }, multipleBounds)).toBe(false); + expect(checkVersionAgainstBounds({ major: 20, minor: 0, patch: 0 }, multipleBounds)).toBe(false); + }); + }); + + describe('empty bounds', () => { + it('returns false for empty bounds array', () => { + expect(checkVersionAgainstBounds({ major: 18, minor: 0, patch: 0 }, [])).toBe(false); + }); + }); +}); + +describe('isVersionCompatible', () => { + const bounds: VersionBounds[] = [ + [18, 0, -1, 0], // ^18.0.0 + [19, 0, -1, 0], // ^19.0.0 + ]; + + it('returns true for compatible versions', () => { + expect(isVersionCompatible('18.3.1', bounds)).toBe(true); + expect(isVersionCompatible('19.0.0', bounds)).toBe(true); + expect(isVersionCompatible('19.0.0-rc.1', bounds)).toBe(true); + }); + + it('returns false for incompatible versions', () => { + expect(isVersionCompatible('17.0.0', bounds)).toBe(false); + expect(isVersionCompatible('20.0.0', bounds)).toBe(false); + }); + + it('returns false for invalid version strings', () => { + expect(isVersionCompatible('', bounds)).toBe(false); + expect(isVersionCompatible('invalid', bounds)).toBe(false); + }); +}); diff --git a/packages/react/src/utils/versionCheck.ts b/packages/react/src/utils/versionCheck.ts new file mode 100644 index 00000000000..6945d9c4b47 --- /dev/null +++ b/packages/react/src/utils/versionCheck.ts @@ -0,0 +1,91 @@ +import React from 'react'; + +// Version bounds format: [major, minMinor, maxMinor, minPatch] +// - maxMinor === -1 means "any minor" (caret range, e.g., ^18.0.0) +// - maxMinor === minMinor means "same minor only" (tilde range, e.g., ~19.0.3) +export type VersionBounds = [major: number, minMinor: number, maxMinor: number, minPatch: number]; + +declare const __CLERK_UI_SUPPORTED_REACT_BOUNDS__: VersionBounds[]; + +/** + * Parses a version string into major, minor, and patch numbers. + * Returns null if the version string cannot be parsed. + * + * @example + * parseVersion("18.3.1") // { major: 18, minor: 3, patch: 1 } + * parseVersion("19.0.0-rc.1") // { major: 19, minor: 0, patch: 0 } + * parseVersion("invalid") // null + */ +export function parseVersion(version: string): { major: number; minor: number; patch: number } | null { + const match = version.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match) return null; + + const [, majorStr, minorStr, patchStr] = match; + return { + major: parseInt(majorStr, 10), + minor: parseInt(minorStr, 10), + patch: parseInt(patchStr, 10), + }; +} + +/** + * Checks if a parsed version satisfies the given version bounds. + * + * @param version - The parsed version to check + * @param bounds - Array of version bounds to check against + * @returns true if the version satisfies any of the bounds + */ +export function checkVersionAgainstBounds( + version: { major: number; minor: number; patch: number }, + bounds: VersionBounds[], +): boolean { + const { major, minor, patch } = version; + + return bounds.some(([bMajor, minMinor, maxMinor, minPatch]) => { + if (major !== bMajor) return false; + + if (maxMinor === -1) { + // Caret range: any minor >= minMinor, with patch check for minMinor + return minor > minMinor || (minor === minMinor && patch >= minPatch); + } else { + // Tilde range: specific minor only + return minor === maxMinor && patch >= minPatch; + } + }); +} + +/** + * Checks if a version string is compatible with the given bounds. + * This is a convenience function that combines parsing and checking. + * + * @param version - The version string to check (e.g., "18.3.1") + * @param bounds - Array of version bounds to check against + * @returns true if the version is compatible, false otherwise + */ +export function isVersionCompatible(version: string, bounds: VersionBounds[]): boolean { + const parsed = parseVersion(version); + if (!parsed) return false; + return checkVersionAgainstBounds(parsed, bounds); +} + +/** + * Checks if the host application's React version is compatible with @clerk/ui's shared variant. + * The shared variant expects React to be provided via globalThis.__clerkSharedModules, + * so we need to ensure the host's React version matches what @clerk/ui was built against. + * + * This function is evaluated once at module load time. + */ +function computeReactVersionCompatibility(): boolean { + try { + return isVersionCompatible(React.version, __CLERK_UI_SUPPORTED_REACT_BOUNDS__); + } catch { + // If we can't determine compatibility, fall back to non-shared variant + return false; + } +} + +/** + * Whether the host React version is compatible with the shared @clerk/ui variant. + * This is computed once at module load time for optimal performance. + */ +export const IS_REACT_SHARED_VARIANT_COMPATIBLE = computeReactVersionCompatibility(); diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index 490d7e206dd..c0880062737 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -15,15 +15,28 @@ type VersionBounds = [major: number, minMinor: number, maxMinor: number, minPatc // Read supported React range from pnpm-workspace.yaml catalogs and parse into bounds function getClerkUiSupportedReactBounds(): VersionBounds[] { - let rangeStr = '^18.0.0 || ^19.0.0'; + const fallbackRange = '^18.0.0 || ^19.0.0'; + let rangeStr = fallbackRange; + let usedFallback = false; try { const workspaceYamlPath = resolve(__dirname, '../../pnpm-workspace.yaml'); const workspaceYaml = readFileSync(workspaceYamlPath, 'utf-8'); const workspace = parseYaml(workspaceYaml); - rangeStr = workspace?.catalogs?.['peer-react']?.react || rangeStr; + const catalogRange = workspace?.catalogs?.['peer-react']?.react; + if (catalogRange) { + rangeStr = catalogRange; + } else { + usedFallback = true; + } } catch { - // Use fallback range + usedFallback = true; + } + + if (usedFallback) { + console.warn( + `[@clerk/react] Could not read React peer dependency range from pnpm-workspace.yaml, using fallback: ${fallbackRange}`, + ); } // Parse the range string into bounds diff --git a/packages/ui/register/index.js b/packages/ui/register/index.js index 83e86842f8f..48d3ac8f0e9 100644 --- a/packages/ui/register/index.js +++ b/packages/ui/register/index.js @@ -19,7 +19,22 @@ const jsxRuntime = require('react/jsx-runtime'); // Only register if not already registered to avoid overwriting with potentially // different React versions in complex module resolution scenarios. -if (!globalThis.__clerkSharedModules) { +if (globalThis.__clerkSharedModules) { + // Warn if the already-registered React version differs from this import. + // This could indicate multiple React versions in the bundle, which may cause issues. + const existingVersion = globalThis.__clerkSharedModules.react?.version; + if (existingVersion && existingVersion !== react.version) { + console.warn( + '[@clerk/ui/register] React version mismatch detected. ' + + 'Already registered: ' + + existingVersion + + ', current import: ' + + react.version + + '. ' + + 'This may cause issues with the shared @clerk/ui variant.', + ); + } +} else { globalThis.__clerkSharedModules = { react, 'react-dom': reactDom, diff --git a/packages/ui/register/index.mjs b/packages/ui/register/index.mjs index f3d313b6772..086d9cf869b 100644 --- a/packages/ui/register/index.mjs +++ b/packages/ui/register/index.mjs @@ -18,7 +18,18 @@ import * as jsxRuntime from 'react/jsx-runtime'; // Only register if not already registered to avoid overwriting with potentially // different React versions in complex module resolution scenarios. -if (!globalThis.__clerkSharedModules) { +if (globalThis.__clerkSharedModules) { + // Warn if the already-registered React version differs from this import. + // This could indicate multiple React versions in the bundle, which may cause issues. + const existingVersion = globalThis.__clerkSharedModules.react?.version; + if (existingVersion && existingVersion !== react.version) { + console.warn( + `[@clerk/ui/register] React version mismatch detected. ` + + `Already registered: ${existingVersion}, current import: ${react.version}. ` + + `This may cause issues with the shared @clerk/ui variant.`, + ); + } +} else { globalThis.__clerkSharedModules = { react, 'react-dom': reactDom, From 51b34ec5a716f1ebf129efcc9bf553d08ed9a343 Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 10:00:38 -0600 Subject: [PATCH 04/16] fix(clerk-js): Add missing exports for internal types and no-rhc variant The new exports field in clerk-js/package.json blocked deep imports that expo and chrome-extension packages depended on. This adds: - ./internal/fapi export for FapiRequestInit/FapiResponse types (expo) - ./no-rhc export for the no-RHC variant (chrome-extension) Also updates expo to use the new clean import path. Co-Authored-By: Claude Opus 4.5 --- packages/clerk-js/package.json | 13 +++++++++++++ .../src/provider/singleton/createClerkInstance.ts | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 5d041cb2d14..474c317aef3 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -41,6 +41,19 @@ "types": "./dist/types/index.headless.d.ts", "default": "./dist/clerk.headless.js" } + }, + "./internal/fapi": { + "types": "./dist/types/core/fapiClient.d.ts" + }, + "./no-rhc": { + "import": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.no-rhc.mjs" + }, + "require": { + "types": "./dist/types/index.d.ts", + "default": "./dist/clerk.no-rhc.js" + } } }, "main": "dist/clerk.js", diff --git a/packages/expo/src/provider/singleton/createClerkInstance.ts b/packages/expo/src/provider/singleton/createClerkInstance.ts index f80234590d6..dcf8fc43926 100644 --- a/packages/expo/src/provider/singleton/createClerkInstance.ts +++ b/packages/expo/src/provider/singleton/createClerkInstance.ts @@ -1,4 +1,4 @@ -import type { FapiRequestInit, FapiResponse } from '@clerk/clerk-js/dist/types/core/fapiClient'; +import type { FapiRequestInit, FapiResponse } from '@clerk/clerk-js/internal/fapi'; import { type Clerk, isClerkRuntimeError } from '@clerk/clerk-js/headless'; import type { BrowserClerk, HeadlessBrowserClerk } from '@clerk/react'; import { is4xxError } from '@clerk/shared/error'; From 48c4d234f4f55ac749c19ca20cb3c808d79801a0 Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 12:19:27 -0600 Subject: [PATCH 05/16] fix(ui): Fix attw check for register entry point and dedupe lockfile - Rename register/index.js to register/index.cjs to fix module syntax mismatch (package uses "type": "module" but file was CommonJS) - Add register/index.d.cts for CJS type declarations to fix "Masquerading as ESM" attw error - Update exports to point to correct file extensions - Run pnpm dedupe to clean up lockfile Co-Authored-By: Claude Opus 4.5 --- packages/ui/package.json | 4 +- packages/ui/register/{index.js => index.cjs} | 0 packages/ui/register/index.d.cts | 16 + pnpm-lock.yaml | 320 ++----------------- 4 files changed, 42 insertions(+), 298 deletions(-) rename packages/ui/register/{index.js => index.cjs} (100%) create mode 100644 packages/ui/register/index.d.cts diff --git a/packages/ui/package.json b/packages/ui/package.json index 86f7bd10750..7f176913b49 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -47,8 +47,8 @@ "default": "./register/index.mjs" }, "require": { - "types": "./register/index.d.ts", - "default": "./register/index.js" + "types": "./register/index.d.cts", + "default": "./register/index.cjs" } }, "./package.json": "./package.json" diff --git a/packages/ui/register/index.js b/packages/ui/register/index.cjs similarity index 100% rename from packages/ui/register/index.js rename to packages/ui/register/index.cjs diff --git a/packages/ui/register/index.d.cts b/packages/ui/register/index.d.cts new file mode 100644 index 00000000000..5bc2051b0a1 --- /dev/null +++ b/packages/ui/register/index.d.cts @@ -0,0 +1,16 @@ +/** + * Registers React and ReactDOM on the global shared modules registry. + * Import this before loading @clerk/ui's shared variant to enable dependency sharing. + */ + +declare global { + var __clerkSharedModules: + | { + react: typeof import('react'); + 'react-dom': typeof import('react-dom'); + 'react/jsx-runtime': typeof import('react/jsx-runtime'); + } + | undefined; +} + +export {}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd54bc8fd0e..338b9795994 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -154,7 +154,7 @@ importers: version: 4.7.0(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/coverage-v8': specifier: 3.2.4 - version: 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)) + version: 3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) chalk: specifier: 4.1.2 version: 4.1.2 @@ -331,7 +331,7 @@ importers: version: 6.1.6(typanion@3.14.0) vitest: specifier: 3.2.4 - version: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1) + version: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) yalc: specifier: 1.0.0-pre.53 version: 1.0.0-pre.53 @@ -418,7 +418,7 @@ importers: version: 9.0.2 vitest-environment-miniflare: specifier: 2.14.4 - version: 2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)) + version: 2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) packages/chrome-extension: dependencies: @@ -2273,204 +2273,102 @@ packages: resolution: {integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==} engines: {node: '>=18'} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} @@ -2483,12 +2381,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} @@ -2501,12 +2393,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} @@ -2519,48 +2405,24 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} @@ -2607,7 +2469,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {'0': node >=0.10.0} + engines: {node: '>=0.10.0'} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -8164,11 +8026,6 @@ packages: resolution: {integrity: sha512-lNjylaAsJMprYg28zjUyBivP3y0ms9b7RJZ5tdhDUFLa3sCbqZw4wDnbFUSmnyZYWhCYDPxxp7KkXM2TXGw3PQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -14792,37 +14649,6 @@ packages: vite: ^6.0.0 || ^7.0.0 vue: ^3.5.0 - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - vite@6.4.1: resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -15091,10 +14917,12 @@ packages: whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} @@ -17040,150 +16868,81 @@ snapshots: esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 - '@esbuild/aix-ppc64@0.21.5': - optional: true - '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm64@0.21.5': - optional: true - '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm@0.21.5': - optional: true - '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-x64@0.21.5': - optional: true - '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.21.5': - optional: true - '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.21.5': - optional: true - '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.21.5': - optional: true - '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.21.5': - optional: true - '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm64@0.21.5': - optional: true - '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm@0.21.5': - optional: true - '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-ia32@0.21.5': - optional: true - '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-loong64@0.21.5': - optional: true - '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-mips64el@0.21.5': - optional: true - '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.21.5': - optional: true - '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.21.5': - optional: true - '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-s390x@0.21.5': - optional: true - '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-x64@0.21.5': - optional: true - '@esbuild/linux-x64@0.25.12': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.21.5': - optional: true - '@esbuild/netbsd-x64@0.25.12': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.21.5': - optional: true - '@esbuild/openbsd-x64@0.25.12': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.21.5': - optional: true - '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.21.5': - optional: true - '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.21.5': - optional: true - '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-x64@0.21.5': - optional: true - '@esbuild/win32-x64@0.25.12': optional: true @@ -21691,7 +21450,7 @@ snapshots: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) vue: 3.5.24(typescript@5.8.3) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -21706,7 +21465,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1) + vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -21718,14 +21477,14 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1))': + '@vitest/mocker@3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.11.6(@types/node@22.19.0)(typescript@5.8.3) - vite: 5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) + vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -24481,32 +24240,6 @@ snapshots: esbuild-plugin-file-path-extensions@2.1.4: {} - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -32709,15 +32442,16 @@ snapshots: dependencies: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) - vite-node@3.2.4(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1): + vite-node@3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) + vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' + - jiti - less - lightningcss - sass @@ -32726,6 +32460,8 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml vite-node@5.0.0(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: @@ -32792,17 +32528,6 @@ snapshots: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) vue: 3.5.24(typescript@5.8.3) - vite@5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.6 - rollup: 4.53.1 - optionalDependencies: - '@types/node': 22.19.0 - fsevents: 2.3.3 - lightningcss: 1.30.2 - terser: 5.44.1 - vite@6.4.1(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.12 @@ -32845,23 +32570,23 @@ snapshots: optionalDependencies: vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) - vitest-environment-miniflare@2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)): + vitest-environment-miniflare@2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)): dependencies: '@miniflare/queues': 2.14.4 '@miniflare/runner-vm': 2.14.4 '@miniflare/shared': 2.14.4 '@miniflare/shared-test-environment': 2.14.4(bufferutil@4.0.9)(utf-8-validate@5.0.10) undici: 5.28.4 - vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1) + vitest: 3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - bufferutil - utf-8-validate - vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1): + vitest@3.2.4(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.19.0)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10))(lightningcss@1.30.2)(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1)) + '@vitest/mocker': 3.2.4(msw@2.11.6(@types/node@22.19.0)(typescript@5.8.3))(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -32879,8 +32604,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 5.4.21(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) - vite-node: 3.2.4(@types/node@22.19.0)(lightningcss@1.30.2)(terser@5.44.1) + vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@edge-runtime/vm': 5.0.0 @@ -32888,6 +32613,7 @@ snapshots: '@types/node': 22.19.0 jsdom: 27.0.0(bufferutil@4.0.9)(postcss@8.5.6)(utf-8-validate@5.0.10) transitivePeerDependencies: + - jiti - less - lightningcss - msw @@ -32897,6 +32623,8 @@ snapshots: - sugarss - supports-color - terser + - tsx + - yaml vlq@1.0.1: {} From 7b07cc1099c154277299800ab9be286bb02cc2c1 Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 12:54:34 -0600 Subject: [PATCH 06/16] fix(clerk-js): Add false-cjs to attw ignore rules The new exports field introduces ESM entry points (.mjs) with CJS type declarations (.d.ts), which triggers attw's "Masquerading as CJS" warning. This is expected behavior for this package's build setup, so we ignore the false-cjs rule. Co-Authored-By: Claude Opus 4.5 --- packages/clerk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 474c317aef3..0984c52b635 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -83,7 +83,7 @@ "format": "node ../../scripts/format-package.mjs", "format:check": "node ../../scripts/format-package.mjs --check", "lint": "eslint src", - "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports", + "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports false-cjs", "lint:publint": "publint || true", "postbuild:disabled": "node ../../scripts/search-for-rhc.mjs file dist/clerk.no-rhc.mjs", "test:disabled": "vitest --watch=false", From c083f347ca0545b8667f258d879087043012c87d Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 12:57:28 -0600 Subject: [PATCH 07/16] Revert "fix(clerk-js): Add false-cjs to attw ignore rules" This reverts commit 74be85099db34de80fcfec8f03dd6ed7964b8f6d. --- packages/clerk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 0984c52b635..474c317aef3 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -83,7 +83,7 @@ "format": "node ../../scripts/format-package.mjs", "format:check": "node ../../scripts/format-package.mjs --check", "lint": "eslint src", - "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports false-cjs", + "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports", "lint:publint": "publint || true", "postbuild:disabled": "node ../../scripts/search-for-rhc.mjs file dist/clerk.no-rhc.mjs", "test:disabled": "vitest --watch=false", From 6755c82e0afb4f5428f3e7a3505683591ed28e63 Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 12:57:28 -0600 Subject: [PATCH 08/16] Revert "fix(clerk-js): Add missing exports for internal types and no-rhc variant" This reverts commit 364059b6eb7a73895ec69ccbd243d0fba4e1e114. --- packages/clerk-js/package.json | 13 ------------- .../src/provider/singleton/createClerkInstance.ts | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 474c317aef3..5d041cb2d14 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -41,19 +41,6 @@ "types": "./dist/types/index.headless.d.ts", "default": "./dist/clerk.headless.js" } - }, - "./internal/fapi": { - "types": "./dist/types/core/fapiClient.d.ts" - }, - "./no-rhc": { - "import": { - "types": "./dist/types/index.d.ts", - "default": "./dist/clerk.no-rhc.mjs" - }, - "require": { - "types": "./dist/types/index.d.ts", - "default": "./dist/clerk.no-rhc.js" - } } }, "main": "dist/clerk.js", diff --git a/packages/expo/src/provider/singleton/createClerkInstance.ts b/packages/expo/src/provider/singleton/createClerkInstance.ts index dcf8fc43926..f80234590d6 100644 --- a/packages/expo/src/provider/singleton/createClerkInstance.ts +++ b/packages/expo/src/provider/singleton/createClerkInstance.ts @@ -1,4 +1,4 @@ -import type { FapiRequestInit, FapiResponse } from '@clerk/clerk-js/internal/fapi'; +import type { FapiRequestInit, FapiResponse } from '@clerk/clerk-js/dist/types/core/fapiClient'; import { type Clerk, isClerkRuntimeError } from '@clerk/clerk-js/headless'; import type { BrowserClerk, HeadlessBrowserClerk } from '@clerk/react'; import { is4xxError } from '@clerk/shared/error'; From 55f725b98f0d0dac6f2935375942e8b991fb8156 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 15 Jan 2026 13:06:26 -0600 Subject: [PATCH 09/16] Apply suggestion from @brkalow --- packages/clerk-js/package.json | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 5d041cb2d14..8983aa0a4e8 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -21,28 +21,6 @@ }, "license": "MIT", "author": "Clerk", - "exports": { - ".": { - "import": { - "types": "./dist/types/index.d.ts", - "default": "./dist/clerk.mjs" - }, - "require": { - "types": "./dist/types/index.d.ts", - "default": "./dist/clerk.js" - } - }, - "./headless": { - "import": { - "types": "./dist/types/index.headless.d.ts", - "default": "./dist/clerk.headless.mjs" - }, - "require": { - "types": "./dist/types/index.headless.d.ts", - "default": "./dist/clerk.headless.js" - } - } - }, "main": "dist/clerk.js", "jsdelivr": "dist/clerk.browser.js", "module": "dist/clerk.mjs", From 0c4718f65e140c02ddbb95dc1fb42cc858d82ee5 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 15 Jan 2026 13:07:06 -0600 Subject: [PATCH 10/16] Apply suggestion from @brkalow --- packages/clerk-js/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 8983aa0a4e8..0a1d4600daf 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -90,7 +90,6 @@ "jsdom": "26.1.0", "minimatch": "^10.0.3", "webpack-merge": "^5.10.0", - "yaml": "^2.7.0" }, "engines": { "node": ">=20.9.0" From 3a7d6b39aafd6da30e9f305b8d8fcf689cc5012c Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Thu, 15 Jan 2026 13:07:34 -0600 Subject: [PATCH 11/16] Apply suggestion from @brkalow --- packages/clerk-js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 0a1d4600daf..478c802b1ab 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -89,7 +89,7 @@ "bundlewatch": "^0.4.1", "jsdom": "26.1.0", "minimatch": "^10.0.3", - "webpack-merge": "^5.10.0", + "webpack-merge": "^5.10.0" }, "engines": { "node": ">=20.9.0" From d7c670c31768b2900f6b0d37048ae958c8bcafc4 Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 14:59:31 -0600 Subject: [PATCH 12/16] updates deps --- pnpm-lock.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 338b9795994..d3e668fc972 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -544,9 +544,6 @@ importers: webpack-merge: specifier: ^5.10.0 version: 5.10.0 - yaml: - specifier: ^2.7.0 - version: 2.8.1 packages/dev-cli: dependencies: @@ -2469,7 +2466,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} From 6818b53f8074ff624260d47627bb4182d6f0b7f7 Mon Sep 17 00:00:00 2001 From: brkalow Date: Thu, 15 Jan 2026 22:59:24 -0600 Subject: [PATCH 13/16] fix(react): Fix ESLint curly brace and import sort errors Add required curly braces after if conditions in versionCheck.ts and fix import sorting in index.ts. Co-Authored-By: Claude Opus 4.5 --- packages/react/src/index.ts | 1 - packages/react/src/utils/versionCheck.ts | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 568b14fa746..903cb6ef2bd 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,6 +1,5 @@ import './polyfills'; import './types/appearance'; - // Register React on the global shared modules registry. // This enables @clerk/ui's shared variant to use the host app's React // instead of bundling its own copy, reducing overall bundle size. diff --git a/packages/react/src/utils/versionCheck.ts b/packages/react/src/utils/versionCheck.ts index 6945d9c4b47..2e192a3c7c2 100644 --- a/packages/react/src/utils/versionCheck.ts +++ b/packages/react/src/utils/versionCheck.ts @@ -18,7 +18,9 @@ declare const __CLERK_UI_SUPPORTED_REACT_BOUNDS__: VersionBounds[]; */ export function parseVersion(version: string): { major: number; minor: number; patch: number } | null { const match = version.match(/^(\d+)\.(\d+)\.(\d+)/); - if (!match) return null; + if (!match) { + return null; + } const [, majorStr, minorStr, patchStr] = match; return { @@ -42,7 +44,9 @@ export function checkVersionAgainstBounds( const { major, minor, patch } = version; return bounds.some(([bMajor, minMinor, maxMinor, minPatch]) => { - if (major !== bMajor) return false; + if (major !== bMajor) { + return false; + } if (maxMinor === -1) { // Caret range: any minor >= minMinor, with patch check for minMinor @@ -64,7 +68,9 @@ export function checkVersionAgainstBounds( */ export function isVersionCompatible(version: string, bounds: VersionBounds[]): boolean { const parsed = parseVersion(version); - if (!parsed) return false; + if (!parsed) { + return false; + } return checkVersionAgainstBounds(parsed, bounds); } From 83fc89fa6284edce6bf9d26f79ee667fcb465935 Mon Sep 17 00:00:00 2001 From: brkalow Date: Tue, 20 Jan 2026 22:43:59 -0600 Subject: [PATCH 14/16] feat(ui): Add react-dom/client to shared modules registry Externalize react-dom/client in the shared variant and register it on globalThis.__clerkSharedModules so host apps can share it with @clerk/ui. Co-Authored-By: Claude Opus 4.5 --- packages/ui/register/index.cjs | 2 ++ packages/ui/register/index.d.cts | 1 + packages/ui/register/index.d.ts | 1 + packages/ui/register/index.mjs | 2 ++ packages/ui/rspack.config.js | 5 ++++- 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ui/register/index.cjs b/packages/ui/register/index.cjs index 48d3ac8f0e9..177fb003315 100644 --- a/packages/ui/register/index.cjs +++ b/packages/ui/register/index.cjs @@ -15,6 +15,7 @@ const react = require('react'); const reactDom = require('react-dom'); +const reactDomClient = require('react-dom/client'); const jsxRuntime = require('react/jsx-runtime'); // Only register if not already registered to avoid overwriting with potentially @@ -38,6 +39,7 @@ if (globalThis.__clerkSharedModules) { globalThis.__clerkSharedModules = { react, 'react-dom': reactDom, + 'react-dom/client': reactDomClient, 'react/jsx-runtime': jsxRuntime, }; } diff --git a/packages/ui/register/index.d.cts b/packages/ui/register/index.d.cts index 5bc2051b0a1..23ccddf5e24 100644 --- a/packages/ui/register/index.d.cts +++ b/packages/ui/register/index.d.cts @@ -8,6 +8,7 @@ declare global { | { react: typeof import('react'); 'react-dom': typeof import('react-dom'); + 'react-dom/client': typeof import('react-dom/client'); 'react/jsx-runtime': typeof import('react/jsx-runtime'); } | undefined; diff --git a/packages/ui/register/index.d.ts b/packages/ui/register/index.d.ts index ba198b039a6..63ecbe7091f 100644 --- a/packages/ui/register/index.d.ts +++ b/packages/ui/register/index.d.ts @@ -9,6 +9,7 @@ declare global { | { react: typeof import('react'); 'react-dom': typeof import('react-dom'); + 'react-dom/client': typeof import('react-dom/client'); 'react/jsx-runtime': typeof import('react/jsx-runtime'); } | undefined; diff --git a/packages/ui/register/index.mjs b/packages/ui/register/index.mjs index 086d9cf869b..fe14f70baec 100644 --- a/packages/ui/register/index.mjs +++ b/packages/ui/register/index.mjs @@ -14,6 +14,7 @@ import * as react from 'react'; import * as reactDom from 'react-dom'; +import * as reactDomClient from 'react-dom/client'; import * as jsxRuntime from 'react/jsx-runtime'; // Only register if not already registered to avoid overwriting with potentially @@ -33,6 +34,7 @@ if (globalThis.__clerkSharedModules) { globalThis.__clerkSharedModules = { react, 'react-dom': reactDom, + 'react-dom/client': reactDomClient, 'react/jsx-runtime': jsxRuntime, }; } diff --git a/packages/ui/rspack.config.js b/packages/ui/rspack.config.js index 63907a4d928..27c11311477 100644 --- a/packages/ui/rspack.config.js +++ b/packages/ui/rspack.config.js @@ -29,6 +29,9 @@ const sharedReactExternalsHandler = ({ request }, callback) => { if (request === 'react-dom') { return callback(null, ['__clerkSharedModules', 'react-dom'], 'root'); } + if (request === 'react-dom/client') { + return callback(null, ['__clerkSharedModules', 'react-dom/client'], 'root'); + } if (request === 'react/jsx-runtime') { return callback(null, ['__clerkSharedModules', 'react/jsx-runtime'], 'root'); } @@ -238,7 +241,7 @@ const prodConfig = ({ mode, analysis }) => { externals: [sharedReactExternalsHandler], }, ); - + // webpack-bundle-analyzer only supports a single build, use uiBrowser as that's the default build we serve if (analysis) { return [uiBrowser]; From c84b0ef888f16ef216c3d38262528881fdc054e4 Mon Sep 17 00:00:00 2001 From: brkalow Date: Wed, 21 Jan 2026 11:00:35 -0600 Subject: [PATCH 15/16] refactor(react): Derive React version bounds from package.json peerDependencies - Remove hardcoded fallback version range in tsup.config.ts - Start from package.json peerDependencies.react to determine version range - If it's a catalog reference (catalog:XXX), resolve it from pnpm-workspace.yaml - If it's already a version range, parse it directly - Extract parseRangeToBounds to build-utils/ for testability - Add comprehensive tests for version range parsing Co-Authored-By: Claude Opus 4.5 --- packages/react/.gitignore | 1 + .../__tests__/parseVersionRange.test.ts | 101 ++++++++++++++++++ .../react/build-utils/parseVersionRange.ts | 47 ++++++++ packages/react/tsup.config.ts | 82 ++++++-------- 4 files changed, 182 insertions(+), 49 deletions(-) create mode 100644 packages/react/build-utils/__tests__/parseVersionRange.test.ts create mode 100644 packages/react/build-utils/parseVersionRange.ts diff --git a/packages/react/.gitignore b/packages/react/.gitignore index 7107e6a4038..99cd8e73638 100644 --- a/packages/react/.gitignore +++ b/packages/react/.gitignore @@ -1,3 +1,4 @@ /*/ !/src/ !/docs/ +!/build-utils/ diff --git a/packages/react/build-utils/__tests__/parseVersionRange.test.ts b/packages/react/build-utils/__tests__/parseVersionRange.test.ts new file mode 100644 index 00000000000..0fd37544d49 --- /dev/null +++ b/packages/react/build-utils/__tests__/parseVersionRange.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from 'vitest'; + +import { parseRangeToBounds, type VersionBounds } from '../parseVersionRange'; + +describe('parseRangeToBounds', () => { + describe('caret ranges', () => { + it('parses simple caret range', () => { + expect(parseRangeToBounds('^18.0.0')).toEqual([[18, 0, -1, 0]]); + }); + + it('parses caret range with non-zero minor', () => { + expect(parseRangeToBounds('^18.2.0')).toEqual([[18, 2, -1, 0]]); + }); + + it('parses caret range with non-zero patch', () => { + expect(parseRangeToBounds('^18.2.5')).toEqual([[18, 2, -1, 5]]); + }); + }); + + describe('tilde ranges', () => { + it('parses simple tilde range', () => { + expect(parseRangeToBounds('~19.0.0')).toEqual([[19, 0, 0, 0]]); + }); + + it('parses tilde range with non-zero minor', () => { + expect(parseRangeToBounds('~19.1.0')).toEqual([[19, 1, 1, 0]]); + }); + + it('parses tilde range with non-zero patch', () => { + expect(parseRangeToBounds('~19.0.3')).toEqual([[19, 0, 0, 3]]); + }); + }); + + describe('exact versions', () => { + it('treats exact version as caret range', () => { + expect(parseRangeToBounds('18.3.1')).toEqual([[18, 3, -1, 1]]); + }); + }); + + describe('OR combinations', () => { + it('parses two caret ranges', () => { + expect(parseRangeToBounds('^18.0.0 || ^19.0.0')).toEqual([ + [18, 0, -1, 0], + [19, 0, -1, 0], + ]); + }); + + it('parses mixed caret and tilde ranges', () => { + expect(parseRangeToBounds('^18.0.0 || ~19.0.3')).toEqual([ + [18, 0, -1, 0], + [19, 0, 0, 3], + ]); + }); + + it('parses multiple tilde ranges', () => { + expect(parseRangeToBounds('~19.0.3 || ~19.1.4 || ~19.2.3')).toEqual([ + [19, 0, 0, 3], + [19, 1, 1, 4], + [19, 2, 2, 3], + ]); + }); + + it('parses complex real-world range', () => { + // This is the actual range from pnpm-workspace.yaml + expect(parseRangeToBounds('^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0')).toEqual([ + [18, 0, -1, 0], + [19, 0, 0, 3], + [19, 1, 1, 4], + [19, 2, 2, 3], + [19, 3, 3, 0], + ]); + }); + }); + + describe('edge cases', () => { + it('handles extra whitespace', () => { + expect(parseRangeToBounds(' ^18.0.0 || ^19.0.0 ')).toEqual([ + [18, 0, -1, 0], + [19, 0, -1, 0], + ]); + }); + + it('returns empty array for invalid input', () => { + expect(parseRangeToBounds('invalid')).toEqual([]); + expect(parseRangeToBounds('')).toEqual([]); + }); + + it('skips invalid parts in OR combinations', () => { + expect(parseRangeToBounds('^18.0.0 || invalid || ^19.0.0')).toEqual([ + [18, 0, -1, 0], + [19, 0, -1, 0], + ]); + }); + + it('handles prerelease versions', () => { + // semver.coerce strips prerelease info + expect(parseRangeToBounds('~19.3.0-0')).toEqual([[19, 3, 3, 0]]); + expect(parseRangeToBounds('^19.0.0-rc.1')).toEqual([[19, 0, -1, 0]]); + }); + }); +}); diff --git a/packages/react/build-utils/parseVersionRange.ts b/packages/react/build-utils/parseVersionRange.ts new file mode 100644 index 00000000000..447b85e11d0 --- /dev/null +++ b/packages/react/build-utils/parseVersionRange.ts @@ -0,0 +1,47 @@ +import { coerce } from 'semver'; + +// Version bounds format: [major, minMinor, maxMinor, minPatch] +// - maxMinor === -1 means "any minor" (caret range, e.g., ^18.0.0) +// - maxMinor === minMinor means "same minor only" (tilde range, e.g., ~19.0.3) +export type VersionBounds = [major: number, minMinor: number, maxMinor: number, minPatch: number]; + +/** + * Parses a semver range string (e.g., "^18.0.0 || ~19.0.3") into version bounds. + * + * Supported formats: + * - Caret ranges: ^X.Y.Z - allows any version >= X.Y.Z and < (X+1).0.0 + * - Tilde ranges: ~X.Y.Z - allows any version >= X.Y.Z and < X.(Y+1).0 + * - Exact versions: X.Y.Z - treated as caret range + * - OR combinations: "^18.0.0 || ~19.0.3" - multiple ranges separated by || + * + * @param rangeStr - The semver range string to parse + * @returns Array of version bounds, one per range component + */ +export function parseRangeToBounds(rangeStr: string): VersionBounds[] { + const bounds: VersionBounds[] = []; + const parts = rangeStr.split('||').map(s => s.trim()); + + for (const part of parts) { + if (part.startsWith('^')) { + // Caret range: ^X.Y.Z means >= X.Y.Z and < (X+1).0.0 + const ver = coerce(part.slice(1)); + if (ver) { + bounds.push([ver.major, ver.minor, -1, ver.patch]); + } + } else if (part.startsWith('~')) { + // Tilde range: ~X.Y.Z means >= X.Y.Z and < X.(Y+1).0 + const ver = coerce(part.slice(1)); + if (ver) { + bounds.push([ver.major, ver.minor, ver.minor, ver.patch]); + } + } else { + // Exact version or other format - try to parse as caret + const ver = coerce(part); + if (ver) { + bounds.push([ver.major, ver.minor, -1, ver.patch]); + } + } + } + + return bounds; +} diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index c0880062737..eb3ac06e9a7 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -1,68 +1,52 @@ import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; -import { coerce } from 'semver'; import { parse as parseYaml } from 'yaml'; import { defineConfig } from 'tsup'; import { version as clerkJsVersion } from '../clerk-js/package.json'; -import { name, version } from './package.json'; +import { name, version, peerDependencies } from './package.json'; +import { parseRangeToBounds } from './build-utils/parseVersionRange'; -// Version bounds format: [major, minMinor, maxMinor, minPatch] -// - maxMinor === -1 means "any minor" (caret range, e.g., ^18.0.0) -// - maxMinor === minMinor means "same minor only" (tilde range, e.g., ~19.0.3) -type VersionBounds = [major: number, minMinor: number, maxMinor: number, minPatch: number]; - -// Read supported React range from pnpm-workspace.yaml catalogs and parse into bounds +/** + * Resolves the React peer dependency range from package.json. + * If it's a catalog reference (catalog:XXX), looks it up in pnpm-workspace.yaml. + * Otherwise, parses the range string directly. + */ function getClerkUiSupportedReactBounds(): VersionBounds[] { - const fallbackRange = '^18.0.0 || ^19.0.0'; - let rangeStr = fallbackRange; - let usedFallback = false; + const reactPeerDep = peerDependencies.react; + + let rangeStr: string; - try { + // Check if it's a catalog reference (e.g., "catalog:peer-react") + const catalogMatch = reactPeerDep.match(/^catalog:(.+)$/); + if (catalogMatch) { + const catalogName = catalogMatch[1]; + + // Read the version range from pnpm-workspace.yaml const workspaceYamlPath = resolve(__dirname, '../../pnpm-workspace.yaml'); - const workspaceYaml = readFileSync(workspaceYamlPath, 'utf-8'); - const workspace = parseYaml(workspaceYaml); - const catalogRange = workspace?.catalogs?.['peer-react']?.react; - if (catalogRange) { - rangeStr = catalogRange; - } else { - usedFallback = true; + let workspaceYaml: string; + try { + workspaceYaml = readFileSync(workspaceYamlPath, 'utf-8'); + } catch (err) { + throw new Error(`[@clerk/react] Failed to read pnpm-workspace.yaml: ${err}`); } - } catch { - usedFallback = true; - } - if (usedFallback) { - console.warn( - `[@clerk/react] Could not read React peer dependency range from pnpm-workspace.yaml, using fallback: ${fallbackRange}`, - ); + const workspace = parseYaml(workspaceYaml); + const catalogRange = workspace?.catalogs?.[catalogName]?.react; + if (!catalogRange) { + throw new Error(`[@clerk/react] Could not find react version in catalog "${catalogName}" in pnpm-workspace.yaml`); + } + rangeStr = catalogRange; + } else { + // Not a catalog reference - use the value directly as a version range + rangeStr = reactPeerDep; } - // Parse the range string into bounds - const bounds: VersionBounds[] = []; - const parts = rangeStr.split('||').map(s => s.trim()); + const bounds = parseRangeToBounds(rangeStr); - for (const part of parts) { - if (part.startsWith('^')) { - // Caret range: ^X.Y.Z means >= X.Y.Z and < (X+1).0.0 - const ver = coerce(part.slice(1)); - if (ver) { - bounds.push([ver.major, ver.minor, -1, ver.patch]); - } - } else if (part.startsWith('~')) { - // Tilde range: ~X.Y.Z means >= X.Y.Z and < X.(Y+1).0 - const ver = coerce(part.slice(1)); - if (ver) { - bounds.push([ver.major, ver.minor, ver.minor, ver.patch]); - } - } else { - // Exact version or other format - try to parse as caret - const ver = coerce(part); - if (ver) { - bounds.push([ver.major, ver.minor, -1, ver.patch]); - } - } + if (bounds.length === 0) { + throw new Error(`[@clerk/react] Failed to parse any version bounds from range: ${rangeStr}`); } return bounds; From 354950ddecca73e649759928505dcd29b9e00eae Mon Sep 17 00:00:00 2001 From: brkalow Date: Wed, 21 Jan 2026 21:48:23 -0600 Subject: [PATCH 16/16] fix(nextjs): Pass clerkUiVariant to script URL for shared React variant The ClerkScripts component was not passing clerkUiVariant to clerkUiScriptUrl(), causing ui.browser.js to load instead of ui.shared.browser.js in Next.js apps. - Export IS_REACT_SHARED_VARIANT_COMPATIBLE from @clerk/react/internal - Use React version compatibility to determine default UI variant in Next.js - Pass clerkUiVariant to opts, allowing user override via provider props Co-Authored-By: Claude Opus 4.5 --- packages/nextjs/src/utils/clerk-script.tsx | 7 ++++++- packages/react/src/internal.ts | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/src/utils/clerk-script.tsx b/packages/nextjs/src/utils/clerk-script.tsx index aceb76c4d9d..8d65d10ab81 100644 --- a/packages/nextjs/src/utils/clerk-script.tsx +++ b/packages/nextjs/src/utils/clerk-script.tsx @@ -4,12 +4,15 @@ import { buildClerkUiScriptAttributes, clerkJsScriptUrl, clerkUiScriptUrl, + IS_REACT_SHARED_VARIANT_COMPATIBLE, } from '@clerk/react/internal'; import NextScript from 'next/script'; import React from 'react'; import { useClerkNextOptions } from '../client-boundary/NextOptionsContext'; +const DEFAULT_CLERK_UI_VARIANT = IS_REACT_SHARED_VARIANT_COMPATIBLE ? ('shared' as const) : ('' as const); + type ClerkScriptProps = { scriptUrl: string; attributes: Record; @@ -43,7 +46,8 @@ function ClerkScript(props: ClerkScriptProps) { } export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) { - const { publishableKey, clerkJSUrl, clerkJSVersion, clerkJSVariant, nonce, clerkUiUrl, ui } = useClerkNextOptions(); + const { publishableKey, clerkJSUrl, clerkJSVersion, clerkJSVariant, nonce, clerkUiUrl, clerkUiVariant, ui } = + useClerkNextOptions(); const { domain, proxyUrl } = useClerk(); if (!publishableKey) { @@ -60,6 +64,7 @@ export function ClerkScripts({ router }: { router: ClerkScriptProps['router'] }) proxyUrl, clerkUiVersion: ui?.version, clerkUiUrl: ui?.url || clerkUiUrl, + clerkUiVariant: clerkUiVariant ?? DEFAULT_CLERK_UI_VARIANT, }; return ( diff --git a/packages/react/src/internal.ts b/packages/react/src/internal.ts index 26e71d2e998..5496de80277 100644 --- a/packages/react/src/internal.ts +++ b/packages/react/src/internal.ts @@ -2,6 +2,7 @@ export { setErrorThrowerOptions } from './errors/errorThrower'; export { MultisessionAppSupport } from './components/controlComponents'; export { useRoutingProps } from './hooks/useRoutingProps'; export { useDerivedAuth } from './hooks/useAuth'; +export { IS_REACT_SHARED_VARIANT_COMPATIBLE } from './utils/versionCheck'; export { clerkJsScriptUrl,