diff --git a/.changeset/eight-groups-poke.md b/.changeset/eight-groups-poke.md
new file mode 100644
index 00000000000..eab224cd900
--- /dev/null
+++ b/.changeset/eight-groups-poke.md
@@ -0,0 +1,5 @@
+---
+'@clerk/upgrade': minor
+---
+
+Add support for @clerk/react
diff --git a/packages/upgrade/src/app.js b/packages/upgrade/src/app.js
index 7799a795735..92f69d593ca 100644
--- a/packages/upgrade/src/app.js
+++ b/packages/upgrade/src/app.js
@@ -59,7 +59,11 @@ export default function App(props) {
}, [fromVersion]);
// Handle the individual SDK upgrade
- if (!fromVersion && !toVersion && sdks[0] === 'nextjs') {
+ if (
+ !fromVersion &&
+ !toVersion &&
+ ['nextjs', 'clerk-react', 'clerk-expo', 'react-router', 'tanstack-react-start'].includes(sdks[0])
+ ) {
return ;
}
diff --git a/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js
new file mode 100644
index 00000000000..d9157d44798
--- /dev/null
+++ b/packages/upgrade/src/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js
@@ -0,0 +1,66 @@
+export const fixtures = [
+ {
+ name: 'Basic import transform',
+ source: `
+import { ClerkProvider } from "@clerk/clerk-react"
+ `,
+ output: `
+import { ClerkProvider } from "@clerk/react"
+`,
+ },
+ {
+ name: 'Basic legacy import',
+ source: `
+import { useSignIn, useSignUp } from "@clerk/clerk-react"
+ `,
+ output: `
+import { useSignIn, useSignUp } from "@clerk/react/legacy"
+`,
+ },
+ {
+ name: 'Basic legacy import (@clerk/clerk-expo)',
+ source: `
+import { useSignIn, useSignUp } from "@clerk/clerk-expo"
+ `,
+ output: `
+import { useSignIn, useSignUp } from "@clerk/expo/legacy"
+`,
+ },
+ {
+ name: 'Basic legacy import (@clerk/nextjs)',
+ source: `
+import { useSignIn, useSignUp } from "@clerk/nextjs"
+ `,
+ output: `
+import { useSignIn, useSignUp } from "@clerk/nextjs/legacy"
+`,
+ },
+ {
+ name: 'Basic legacy import (@clerk/react-router)',
+ source: `
+import { useSignIn, useSignUp } from "@clerk/react-router"
+ `,
+ output: `
+import { useSignIn, useSignUp } from "@clerk/react-router/legacy"
+`,
+ },
+ {
+ name: 'Basic legacy import (@clerk/tanstack-react-start)',
+ source: `
+import { useSignIn, useSignUp } from "@clerk/tanstack-react-start"
+ `,
+ output: `
+import { useSignIn, useSignUp } from "@clerk/tanstack-react-start/legacy"
+`,
+ },
+ {
+ name: 'Mixed legacy imports',
+ source: `
+import { ClerkProvider, useSignIn, useSignUp } from "@clerk/clerk-react"
+ `,
+ output: `
+import { ClerkProvider } from "@clerk/react";
+import { useSignIn, useSignUp } from "@clerk/react/legacy";
+`,
+ },
+];
diff --git a/packages/upgrade/src/codemods/__tests__/transform-clerk-react-v6.test.js b/packages/upgrade/src/codemods/__tests__/transform-clerk-react-v6.test.js
new file mode 100644
index 00000000000..4e82415a9c4
--- /dev/null
+++ b/packages/upgrade/src/codemods/__tests__/transform-clerk-react-v6.test.js
@@ -0,0 +1,13 @@
+import { applyTransform } from 'jscodeshift/dist/testUtils';
+import { describe, expect, it } from 'vitest';
+
+import transformer from '../transform-clerk-react-v6.cjs';
+import { fixtures } from './__fixtures__/transform-clerk-react-v6.fixtures';
+
+describe('transform-clerk-react-v6', () => {
+ it.each(fixtures)(`$name`, ({ source, output }) => {
+ const result = applyTransform(transformer, {}, { source });
+
+ expect(result).toEqual(output.trim());
+ });
+});
diff --git a/packages/upgrade/src/codemods/index.js b/packages/upgrade/src/codemods/index.js
index 5e7245c3d3e..a2b426f00ac 100644
--- a/packages/upgrade/src/codemods/index.js
+++ b/packages/upgrade/src/codemods/index.js
@@ -38,6 +38,6 @@ export async function runCodemod(transform = 'transform-async-request', glob, op
dry: false,
...options,
// we must silence stdout to prevent output from interfering with ink CLI
- silent: true,
+ // silent: true,
});
}
diff --git a/packages/upgrade/src/codemods/transform-clerk-react-v6.cjs b/packages/upgrade/src/codemods/transform-clerk-react-v6.cjs
new file mode 100644
index 00000000000..a2b0db9490c
--- /dev/null
+++ b/packages/upgrade/src/codemods/transform-clerk-react-v6.cjs
@@ -0,0 +1,89 @@
+const PACKAGES = [
+ ['@clerk/clerk-react', '@clerk/react'],
+ ['@clerk/clerk-expo', '@clerk/expo'],
+ ['@clerk/nextjs', '@clerk/nextjs'],
+ ['@clerk/react-router', '@clerk/react-router'],
+ ['@clerk/tanstack-react-start', '@clerk/tanstack-react-start'],
+];
+
+/**
+ * Transforms imports of `@clerk/clerk-react` to `@clerk/react`, in addition to updating imports of `useSignIn` and
+ * `useSignUp` to import from the `/legacy` subpath.
+ *
+ * @param {import('jscodeshift').FileInfo} FileInfo - The parameters object
+ * @param {import('jscodeshift').API} api - The API object provided by jscodeshift
+ * @param {Object} _options - Additional options (unused)
+ * @returns {string|undefined} - The transformed source code if modifications were made, otherwise undefined
+ */
+module.exports = function transformClerkReactV6({ source }, { jscodeshift: j }) {
+ const root = j(source);
+ let dirtyFlag = false;
+
+ PACKAGES.forEach(([sourcePackage, targetPackage]) => {
+ // Transform imports from '@clerk/clerk-react'
+ root.find(j.ImportDeclaration, { source: { value: sourcePackage } }).forEach(path => {
+ const node = path.node;
+ const specifiers = node.specifiers || [];
+ const importKind = node.importKind; // preserve type-only imports
+
+ // If there are no specifiers (side-effect import), simply retarget to '@clerk/react'
+ if (specifiers.length === 0) {
+ node.source.value = targetPackage;
+ dirtyFlag = true;
+ return;
+ }
+
+ /** Split specifiers into legacy and non-legacy groups */
+ const legacySpecifiers = [];
+ const nonLegacySpecifiers = [];
+
+ for (const spec of specifiers) {
+ if (
+ j.ImportSpecifier.check(spec) &&
+ (spec.imported.name === 'useSignIn' || spec.imported.name === 'useSignUp')
+ ) {
+ legacySpecifiers.push(spec);
+ } else {
+ nonLegacySpecifiers.push(spec);
+ }
+ }
+
+ if (legacySpecifiers.length > 0 && nonLegacySpecifiers.length > 0) {
+ // Mixed import: keep non-legacy on '@clerk/react', emit a new import for legacy hooks
+ node.specifiers = nonLegacySpecifiers;
+ node.source = j.literal(targetPackage);
+ if (importKind) {
+ node.importKind = importKind;
+ }
+ const legacyImportDecl = j.importDeclaration(legacySpecifiers, j.literal(`${targetPackage}/legacy`));
+ if (importKind) {
+ legacyImportDecl.importKind = importKind;
+ }
+ j(path).insertAfter(legacyImportDecl);
+ dirtyFlag = true;
+ return;
+ }
+
+ if (legacySpecifiers.length > 0) {
+ // Only legacy hooks present
+ node.source.value = `${targetPackage}/legacy`;
+ if (importKind) {
+ node.importKind = importKind;
+ }
+ dirtyFlag = true;
+ return;
+ }
+
+ // Only non-legacy imports present
+ node.source.value = targetPackage;
+ if (importKind) {
+ node.importKind = importKind;
+ }
+ dirtyFlag = true;
+ });
+ });
+
+ return dirtyFlag ? root.toSource() : undefined;
+};
+
+module.exports.parser = 'tsx';
diff --git a/packages/upgrade/src/components/Codemod.js b/packages/upgrade/src/components/Codemod.js
index 9498dd800f4..99fd107d073 100644
--- a/packages/upgrade/src/components/Codemod.js
+++ b/packages/upgrade/src/components/Codemod.js
@@ -49,7 +49,7 @@ export function Codemod(props) {
{glob.toString()}
) : (
{
setGlob(val.split(/[ ,]/));
}}
diff --git a/packages/upgrade/src/components/SDKWorkflow.js b/packages/upgrade/src/components/SDKWorkflow.js
index f0dd802e640..06acc6076a6 100644
--- a/packages/upgrade/src/components/SDKWorkflow.js
+++ b/packages/upgrade/src/components/SDKWorkflow.js
@@ -9,6 +9,26 @@ import { Command } from './Command.js';
import { Header } from './Header.js';
import { UpgradeSDK } from './UpgradeSDK.js';
+function versionNeedsUpgrade(sdk, version) {
+ if (sdk === 'clerk-react' && version === 5) {
+ return true;
+ }
+
+ if (sdk === 'clerk-expo' && version === 2) {
+ return true;
+ }
+
+ if (sdk === 'react-router' && version === 2) {
+ return true;
+ }
+
+ if (sdk === 'tanstack-react-start' && version === 0) {
+ return true;
+ }
+
+ return false;
+}
+
/**
* SDKWorkflow component handles the upgrade process for a given SDK.
* It checks the current version of the SDK and provides the necessary steps
@@ -29,111 +49,180 @@ export function SDKWorkflow(props) {
const [version] = useState(getClerkSdkVersion(sdk));
- if (sdk !== 'nextjs') {
+ if (!['nextjs', 'clerk-react', 'clerk-expo', 'react-router', 'tanstack-react-start'].includes(sdk)) {
return (
- The SDK upgrade functionality is only available for @clerk/nextjs at the moment.
+ The SDK upgrade functionality is not available for @clerk/{sdk} at the moment.
);
}
- // Right now, we only have one codemod for the `@clerk/nextjs` async request transformation
- return (
- <>
-
-
- Clerk SDK used: @clerk/{sdk}
-
-
- Migrating from version: {version}
-
- {runCodemod ? (
+ if (sdk === 'nextjs') {
+ // Right now, we only have one codemod for the `@clerk/nextjs` async request transformation
+ return (
+ <>
+
+
+ Clerk SDK used: @clerk/{sdk}
+
- Executing codemod: yes
+ Migrating from version: {version}
- ) : null}
-
- {version === 5 && (
- <>
-
- {upgradeComplete ? (
-
+ Executing codemod: yes
+
+ ) : null}
+
+ {version === 5 && (
+ <>
+
- ) : null}
- >
- )}
- {version === 6 && (
- <>
- {runCodemod ? (
-
+ ) : null}
+ >
+ )}
+ {version === 6 && (
+ <>
+
- ) : (
- <>
-
- Looks like you are already on the latest version of @clerk/{sdk}. Would you like to
- run the associated codemod?
-
-