diff --git a/.changeset/export-appearance-from-ui.md b/.changeset/export-appearance-from-ui.md new file mode 100644 index 00000000000..0c1ec782f28 --- /dev/null +++ b/.changeset/export-appearance-from-ui.md @@ -0,0 +1,6 @@ +--- +'@clerk/ui': patch +'@clerk/upgrade': patch +--- + +Export `Appearance` type from `@clerk/ui` root entry diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index fdfbe0688f6..8e70fb0299e 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -2,6 +2,8 @@ import type { Ui } from './internal'; import { UI_BRAND } from './internal'; import type { Appearance } from './internal/appearance'; +export type { Appearance } from './internal/appearance'; + import { ClerkUI } from './ClerkUI'; declare const PACKAGE_VERSION: string; diff --git a/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-clerk-types-to-shared-types.fixtures.js b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-clerk-types-to-shared-types.fixtures.js new file mode 100644 index 00000000000..0e4921d9c5e --- /dev/null +++ b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-clerk-types-to-shared-types.fixtures.js @@ -0,0 +1,81 @@ +export const fixtures = [ + { + name: 'Rewrites basic type import', + source: ` +import type { UserResource, ClerkResource } from '@clerk/types'; + `, + output: ` +import type { UserResource, ClerkResource } from "@clerk/shared/types"; +`, + }, + { + name: 'Rewrites value import', + source: ` +import { OAUTH_PROVIDERS } from '@clerk/types'; + `, + output: ` +import { OAUTH_PROVIDERS } from "@clerk/shared/types"; +`, + }, + { + name: 'Redirects Appearance to @clerk/ui', + source: ` +import type { Appearance } from '@clerk/types'; + `, + output: ` +import type { Appearance } from "@clerk/ui"; +`, + }, + { + name: 'Splits mixed import with Appearance', + source: ` +import type { Appearance, UserResource, ClerkResource } from '@clerk/types'; + `, + output: ` +import type { UserResource, ClerkResource } from "@clerk/shared/types"; +import type { Appearance } from "@clerk/ui"; +`, + }, + { + name: 'Handles require statements', + source: ` +const { UserResource } = require('@clerk/types'); + `, + output: ` +const { UserResource } = require("@clerk/shared/types"); +`, + }, + { + name: 'Handles require with Appearance only', + source: ` +const { Appearance } = require('@clerk/types'); + `, + output: ` +const { Appearance } = require("@clerk/ui"); +`, + }, + { + name: 'Splits mixed require with Appearance', + source: ` +const { Appearance, UserResource } = require('@clerk/types'); + `, + output: ` +const { + UserResource +} = require("@clerk/shared/types"); + +const { + Appearance +} = require("@clerk/ui"); +`, + }, + { + name: 'Handles namespace import', + source: ` +import * as Types from '@clerk/types'; + `, + output: ` +import * as Types from "@clerk/shared/types"; +`, + }, +]; diff --git a/packages/upgrade/src/codemods/__tests__/transform-clerk-types-to-shared-types.test.js b/packages/upgrade/src/codemods/__tests__/transform-clerk-types-to-shared-types.test.js new file mode 100644 index 00000000000..b1091c452b7 --- /dev/null +++ b/packages/upgrade/src/codemods/__tests__/transform-clerk-types-to-shared-types.test.js @@ -0,0 +1,13 @@ +import { applyTransform } from 'jscodeshift/dist/testUtils'; +import { describe, expect, it } from 'vitest'; + +import transformer from '../transform-clerk-types-to-shared-types.cjs'; +import { fixtures } from './__fixtures__/transform-clerk-types-to-shared-types.fixtures'; + +describe('transform-clerk-types-to-shared-types', () => { + it.each(fixtures)('$name', ({ source, output }) => { + const result = applyTransform(transformer, {}, { source }); + + expect(result).toEqual(output.trim()); + }); +}); diff --git a/packages/upgrade/src/codemods/transform-clerk-types-to-shared-types.cjs b/packages/upgrade/src/codemods/transform-clerk-types-to-shared-types.cjs new file mode 100644 index 00000000000..597ff5e913f --- /dev/null +++ b/packages/upgrade/src/codemods/transform-clerk-types-to-shared-types.cjs @@ -0,0 +1,133 @@ +const SOURCE_PACKAGE = '@clerk/types'; +const TARGET_PACKAGE = '@clerk/shared/types'; +const UI_PACKAGE = '@clerk/ui'; + +/** + * Specifiers that should be redirected to `@clerk/ui` instead of `@clerk/shared/types`. + */ +const UI_SPECIFIERS = new Set(['Appearance']); + +/** + * Transforms imports of `@clerk/types` to `@clerk/shared/types`, splitting out + * `Appearance` to `@clerk/ui`. + * + * @param {import('jscodeshift').FileInfo} fileInfo + * @param {import('jscodeshift').API} api + * @returns {string|undefined} + */ +module.exports = function transformClerkTypesToSharedTypes({ source }, { jscodeshift: j }) { + const root = j(source); + let dirty = false; + + // --- Transform import declarations --- + root.find(j.ImportDeclaration, { source: { value: SOURCE_PACKAGE } }).forEach(path => { + const node = path.node; + const specifiers = node.specifiers || []; + const importKind = node.importKind; + + const uiSpecifiers = []; + const sharedSpecifiers = []; + + for (const spec of specifiers) { + if (j.ImportSpecifier.check(spec) && UI_SPECIFIERS.has(spec.imported.name)) { + uiSpecifiers.push(spec); + } else { + sharedSpecifiers.push(spec); + } + } + + if (uiSpecifiers.length > 0 && sharedSpecifiers.length > 0) { + // Mixed: split into two imports + const sharedImport = j.importDeclaration(sharedSpecifiers, j.stringLiteral(TARGET_PACKAGE)); + if (importKind) { + sharedImport.importKind = importKind; + } + sharedImport.comments = node.comments; + + const uiImport = j.importDeclaration(uiSpecifiers, j.stringLiteral(UI_PACKAGE)); + if (importKind) { + uiImport.importKind = importKind; + } + + j(path).replaceWith([sharedImport, uiImport]); + dirty = true; + return; + } + + if (uiSpecifiers.length > 0) { + // Only UI specifiers + node.source.value = UI_PACKAGE; + dirty = true; + return; + } + + // Only shared specifiers (or namespace/default imports) + node.source.value = TARGET_PACKAGE; + dirty = true; + }); + + // --- Transform require calls --- + root + .find(j.VariableDeclarator, { + init: { + callee: { name: 'require' }, + arguments: [{ value: SOURCE_PACKAGE }], + }, + }) + .forEach(path => { + const node = path.node; + const id = node.id; + + if (id.type === 'ObjectPattern') { + const uiProperties = []; + const sharedProperties = []; + + for (const prop of id.properties) { + if (prop.key && UI_SPECIFIERS.has(prop.key.name)) { + uiProperties.push(prop); + } else { + sharedProperties.push(prop); + } + } + + if (uiProperties.length > 0 && sharedProperties.length > 0) { + // Mixed: keep shared on main, create new require for UI + node.id.properties = sharedProperties; + node.init.arguments[0] = j.literal(TARGET_PACKAGE); + + const variableDeclaration = path.parent.node; + const kind = variableDeclaration.kind || 'const'; + + const uiDeclarator = j.variableDeclarator( + j.objectPattern(uiProperties), + j.callExpression(j.identifier('require'), [j.literal(UI_PACKAGE)]), + ); + const uiDeclaration = j.variableDeclaration(kind, [uiDeclarator]); + + j(path.parent).insertAfter(uiDeclaration); + dirty = true; + return; + } + + if (uiProperties.length > 0) { + node.init.arguments[0] = j.literal(UI_PACKAGE); + dirty = true; + return; + } + } + + // Only shared or not destructured + node.init.arguments[0] = j.literal(TARGET_PACKAGE); + dirty = true; + }); + + if (!dirty) { + return undefined; + } + + let result = root.toSource(); + result = result.replace(/^(['"`][^'"`]+['"`]);;/gm, '$1;'); + return result; +}; + +module.exports.parser = 'tsx'; diff --git a/packages/upgrade/src/versions/core-3/changes/clerk-types-deprecation.md b/packages/upgrade/src/versions/core-3/changes/clerk-types-deprecation.md index 25fad15101a..d5d6a7e588a 100644 --- a/packages/upgrade/src/versions/core-3/changes/clerk-types-deprecation.md +++ b/packages/upgrade/src/versions/core-3/changes/clerk-types-deprecation.md @@ -15,3 +15,10 @@ Update your imports: ``` The `@clerk/types` package will continue to re-export types from `@clerk/shared/types` for backward compatibility, but new types will only be added to `@clerk/shared/types`. + +**Note:** The `Appearance` type should be imported from `@clerk/ui` instead of `@clerk/shared/types`: + +```diff +- import type { Appearance } from '@clerk/types'; ++ import type { Appearance } from '@clerk/ui'; +``` diff --git a/packages/upgrade/src/versions/core-3/index.js b/packages/upgrade/src/versions/core-3/index.js index ab4ba699da5..031fa3aa4d4 100644 --- a/packages/upgrade/src/versions/core-3/index.js +++ b/packages/upgrade/src/versions/core-3/index.js @@ -18,6 +18,7 @@ export default { 'transform-remove-deprecated-appearance-props', 'transform-appearance-layout-to-options', 'transform-themes-to-ui-themes', + 'transform-clerk-types-to-shared-types', 'transform-align-experimental-unstable-prefixes', // React/JSX version of Protect→Show (handles .tsx, .jsx, .ts, .js files) {