@clerk/upgrade 2.0.0-snapshot.v20251204175016 → 2.0.0-snapshot.v20251211120550

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +35 -5
  2. package/dist/__tests__/fixtures/expo-old-package/package-lock.json +5 -0
  3. package/dist/__tests__/fixtures/expo-old-package/package.json +10 -0
  4. package/dist/__tests__/fixtures/expo-old-package/src/App.tsx +14 -0
  5. package/dist/__tests__/fixtures/nextjs-v6/package.json +9 -0
  6. package/dist/__tests__/fixtures/nextjs-v6/pnpm-lock.yaml +2 -0
  7. package/dist/__tests__/fixtures/nextjs-v6/src/app.tsx +17 -0
  8. package/dist/__tests__/fixtures/nextjs-v7/package.json +9 -0
  9. package/dist/__tests__/fixtures/nextjs-v7/pnpm-lock.yaml +2 -0
  10. package/dist/__tests__/fixtures/nextjs-v7/src/app.tsx +16 -0
  11. package/dist/__tests__/fixtures/no-clerk/package.json +7 -0
  12. package/dist/__tests__/fixtures/react-v6/package.json +8 -0
  13. package/dist/__tests__/fixtures/react-v6/src/App.tsx +19 -0
  14. package/dist/__tests__/fixtures/react-v6/yarn.lock +2 -0
  15. package/dist/__tests__/helpers/create-fixture.js +56 -0
  16. package/dist/__tests__/integration/cli.test.js +275 -0
  17. package/dist/__tests__/integration/config.test.js +97 -0
  18. package/dist/__tests__/integration/detect-sdk.test.js +100 -0
  19. package/dist/__tests__/integration/runner.test.js +58 -0
  20. package/dist/cli.js +172 -44
  21. package/dist/codemods/__tests__/__fixtures__/transform-align-experimental-unstable-prefixes.fixtures.js +92 -0
  22. package/dist/codemods/__tests__/__fixtures__/transform-appearance-layout-to-options.fixtures.js +9 -0
  23. package/dist/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js +13 -0
  24. package/dist/codemods/__tests__/__fixtures__/transform-remove-deprecated-appearance-props.fixtures.js +63 -0
  25. package/dist/codemods/__tests__/__fixtures__/transform-themes-to-ui-themes.fixtures.js +41 -0
  26. package/dist/codemods/__tests__/transform-align-experimental-unstable-prefixes.test.js +15 -0
  27. package/dist/codemods/__tests__/transform-appearance-layout-to-options.test.js +15 -0
  28. package/dist/codemods/__tests__/transform-remove-deprecated-appearance-props.test.js +15 -0
  29. package/dist/codemods/__tests__/transform-themes-to-ui-themes.test.js +15 -0
  30. package/dist/codemods/index.js +67 -13
  31. package/dist/codemods/transform-align-experimental-unstable-prefixes.cjs +412 -0
  32. package/dist/codemods/transform-appearance-layout-to-options.cjs +65 -0
  33. package/dist/codemods/transform-clerk-react-v6.cjs +15 -7
  34. package/dist/codemods/transform-remove-deprecated-appearance-props.cjs +109 -0
  35. package/dist/codemods/transform-remove-deprecated-props.cjs +11 -32
  36. package/dist/codemods/transform-themes-to-ui-themes.cjs +65 -0
  37. package/dist/config.js +145 -0
  38. package/dist/render.js +170 -0
  39. package/dist/runner.js +98 -0
  40. package/dist/util/detect-sdk.js +125 -0
  41. package/dist/util/package-manager.js +94 -0
  42. package/dist/versions/core-3/changes/clerk-expo-package-rename.md +23 -0
  43. package/dist/versions/core-3/changes/clerk-react-package-rename.md +22 -0
  44. package/dist/versions/core-3/index.js +40 -0
  45. package/package.json +2 -8
  46. package/dist/app.js +0 -177
  47. package/dist/components/Codemod.js +0 -149
  48. package/dist/components/Command.js +0 -56
  49. package/dist/components/Header.js +0 -11
  50. package/dist/components/SDKWorkflow.js +0 -278
  51. package/dist/components/Scan.js +0 -180
  52. package/dist/components/UpgradeSDK.js +0 -116
  53. package/dist/util/expandable-list.js +0 -173
  54. package/dist/util/get-clerk-version.js +0 -22
  55. package/dist/util/guess-framework.js +0 -69
package/README.md CHANGED
@@ -27,25 +27,55 @@
27
27
 
28
28
  ## Getting Started
29
29
 
30
- A tool that helps with upgrading major versions of Clerk's SDKs.
30
+ A CLI that detects your installed Clerk SDK, upgrades packages to the next major release, runs codemods, and scans your codebase for breaking changes.
31
31
 
32
32
  ### Prerequisites
33
33
 
34
- - Node.js `>=18.17.0` or later
34
+ - Node.js `>=20.9.0`
35
+ - pnpm, npm, or yarn installed in the target project
35
36
 
36
37
  ## Usage
37
38
 
38
- Navigate to the application you want to upgrade and run the following in your terminal:
39
+ Run the CLI from the root of the project you want to upgrade:
39
40
 
40
41
  ```sh
42
+ # with pnpm
43
+ pnpm dlx @clerk/upgrade
44
+
45
+ # with npm
41
46
  npx @clerk/upgrade
47
+
48
+ # if installed locally
49
+ pnpm clerk-upgrade
42
50
  ```
43
51
 
44
- Fill out the questionnaire and the CLI will show you the required changes.
52
+ Fill out the prompts and the CLI will:
53
+
54
+ - Detect your Clerk SDK and version (or prompt for `--sdk`)
55
+ - Upgrade packages (including renames such as `@clerk/clerk-react` → `@clerk/react`)
56
+ - Run codemods for the selected release
57
+ - Scan your files for breaking changes and print a report
58
+
59
+ ### CLI options
60
+
61
+ - `--sdk` — SDK to upgrade (e.g., `nextjs`, `react`, `expo`); required in non-interactive runs if detection fails
62
+ - `--dir` — directory to scan (default: current working directory)
63
+ - `--glob` — glob of files for codemods (default: `**/*.(js|jsx|ts|tsx|mjs|cjs)`)
64
+ - `--ignore` — extra globs to ignore during scans (repeatable)
65
+ - `--release` — target release (e.g., `core-3`); otherwise auto-selected from installed versions
66
+ - `--skip-upgrade` — skip installing/updating packages
67
+ - `--skip-codemods` — skip codemod execution
68
+ - `--dry-run` — show what would change without writing or installing
69
+
70
+ ### Releases
71
+
72
+ - `core-3`: upgrades Next.js 6 → 7, React 5 → 7, Expo 2 → 3, React Router 2 → 3, TanStack React Start 0 → 1, Astro 2 → 3, Nuxt 2 → 3, Vue 2 → 3; includes package renames for React/Expo
73
+
74
+ The CLI selects the appropriate release automatically based on the installed major version, or you can pin a release with `--release` (currently `core-3`).
45
75
 
46
76
  ### Caveats
47
77
 
48
- This tool uses regular expressions to scan for patterns that match breaking changes. This makes it run substantially faster and makes it more accessible for us at Clerk to author matchers for each breaking change, however it means that _we cannot gurarantee 100% accuracy of the results_. As such, it's important to treat this as a tool that can help you to complete your major version upgrades, rather than an automatic fix to all issues.
78
+ Scans rely on regular expressions, so some patterns (unusual imports, bound methods, indirect calls) can be missed. Codemods cover common upgrades, but you should still review the report, run your test suite, and verify the app before deploying.
49
79
 
50
80
  The main thing that this tool will miss is cases where _unusual import patterns_ are used in your codebase. As an example, if Clerk made a breaking change to the `getAuth` function exported from `@clerk/nextjs`, `@clerk/upgrade` would likely look for something like `import { getAuth } from "@clerk/nextjs"` in order to detect whether you need to make some changes. If you were using your imports like `import * as ClerkNext from "@clerk/nextjs"`, you could use `getAuth` without it detecting it with its matcher.
51
81
 
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "test-expo-old",
3
+ "lockfileVersion": 3
4
+ }
5
+
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "test-expo-old",
3
+ "version": "1.0.0",
4
+ "dependencies": {
5
+ "@clerk/clerk-expo": "^2.0.0",
6
+ "expo": "^50.0.0",
7
+ "react": "^18.0.0",
8
+ "react-native": "^0.73.0"
9
+ }
10
+ }
@@ -0,0 +1,14 @@
1
+ import { ClerkProvider, useAuth } from '@clerk/clerk-expo';
2
+
3
+ export default function App() {
4
+ return (
5
+ <ClerkProvider publishableKey='pk_test_xxx'>
6
+ <AuthStatus />
7
+ </ClerkProvider>
8
+ );
9
+ }
10
+
11
+ function AuthStatus() {
12
+ const { isSignedIn } = useAuth();
13
+ return <Text>{isSignedIn ? 'Signed in' : 'Signed out'}</Text>;
14
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "test-nextjs-v6",
3
+ "version": "1.0.0",
4
+ "dependencies": {
5
+ "@clerk/nextjs": "^6.0.0",
6
+ "next": "^14.0.0",
7
+ "react": "^18.0.0"
8
+ }
9
+ }
@@ -0,0 +1,2 @@
1
+ lockfileVersion: '9.0'
2
+
@@ -0,0 +1,17 @@
1
+ import { ClerkProvider, useAuth } from '@clerk/nextjs';
2
+ import { useUser } from '@clerk/clerk-react';
3
+
4
+ export default function App({ children }) {
5
+ return <ClerkProvider>{children}</ClerkProvider>;
6
+ }
7
+
8
+ export function UserProfile() {
9
+ const { isSignedIn } = useAuth();
10
+ const { user } = useUser();
11
+
12
+ if (!isSignedIn) {
13
+ return <div>Not signed in</div>;
14
+ }
15
+
16
+ return <div>Hello, {user?.firstName}</div>;
17
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "test-nextjs-v7",
3
+ "version": "1.0.0",
4
+ "dependencies": {
5
+ "@clerk/nextjs": "^7.0.0",
6
+ "next": "^14.0.0",
7
+ "react": "^18.0.0"
8
+ }
9
+ }
@@ -0,0 +1,2 @@
1
+ lockfileVersion: '9.0'
2
+
@@ -0,0 +1,16 @@
1
+ import { ClerkProvider, useAuth, useUser } from '@clerk/nextjs';
2
+
3
+ export default function App({ children }) {
4
+ return <ClerkProvider>{children}</ClerkProvider>;
5
+ }
6
+
7
+ export function UserProfile() {
8
+ const { isSignedIn } = useAuth();
9
+ const { user } = useUser();
10
+
11
+ if (!isSignedIn) {
12
+ return <div>Not signed in</div>;
13
+ }
14
+
15
+ return <div>Hello, {user?.firstName}</div>;
16
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "test-no-clerk",
3
+ "version": "1.0.0",
4
+ "dependencies": {
5
+ "react": "^18.0.0"
6
+ }
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "test-react-v6",
3
+ "version": "1.0.0",
4
+ "dependencies": {
5
+ "@clerk/clerk-react": "^5.0.0",
6
+ "react": "^18.0.0"
7
+ }
8
+ }
@@ -0,0 +1,19 @@
1
+ import { ClerkProvider, useUser } from '@clerk/react';
2
+
3
+ export default function App() {
4
+ return (
5
+ <ClerkProvider publishableKey='pk_test_xxx'>
6
+ <UserInfo />
7
+ </ClerkProvider>
8
+ );
9
+ }
10
+
11
+ function UserInfo() {
12
+ const { user, isSignedIn } = useUser();
13
+
14
+ if (!isSignedIn) {
15
+ return <div>Please sign in</div>;
16
+ }
17
+
18
+ return <div>Welcome, {user?.firstName}</div>;
19
+ }
@@ -0,0 +1,2 @@
1
+ # yarn lockfile v1
2
+
@@ -0,0 +1,56 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const FIXTURES_DIR = path.join(__dirname, '..', 'fixtures');
7
+ export function getFixturePath(fixtureName) {
8
+ return path.join(FIXTURES_DIR, fixtureName);
9
+ }
10
+ export function createTempFixture(fixtureName) {
11
+ const sourcePath = getFixturePath(fixtureName);
12
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `clerk-upgrade-test-${fixtureName}-`));
13
+ copyDirSync(sourcePath, tempDir);
14
+ return {
15
+ path: tempDir,
16
+ cleanup() {
17
+ fs.rmSync(tempDir, {
18
+ recursive: true,
19
+ force: true
20
+ });
21
+ }
22
+ };
23
+ }
24
+ function copyDirSync(src, dest) {
25
+ fs.mkdirSync(dest, {
26
+ recursive: true
27
+ });
28
+ const entries = fs.readdirSync(src, {
29
+ withFileTypes: true
30
+ });
31
+ for (const entry of entries) {
32
+ const srcPath = path.join(src, entry.name);
33
+ const destPath = path.join(dest, entry.name);
34
+ if (entry.isDirectory()) {
35
+ copyDirSync(srcPath, destPath);
36
+ } else {
37
+ fs.copyFileSync(srcPath, destPath);
38
+ }
39
+ }
40
+ }
41
+ export function readFixtureFile(fixtureName, filePath) {
42
+ return fs.readFileSync(path.join(getFixturePath(fixtureName), filePath), 'utf8');
43
+ }
44
+ export function writeFixtureFile(tempPath, filePath, content) {
45
+ const fullPath = path.join(tempPath, filePath);
46
+ fs.mkdirSync(path.dirname(fullPath), {
47
+ recursive: true
48
+ });
49
+ fs.writeFileSync(fullPath, content, 'utf8');
50
+ }
51
+ export function readTempFile(tempPath, filePath) {
52
+ return fs.readFileSync(path.join(tempPath, filePath), 'utf8');
53
+ }
54
+ export function fileExists(tempPath, filePath) {
55
+ return fs.existsSync(path.join(tempPath, filePath));
56
+ }
@@ -0,0 +1,275 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { createTempFixture, getFixturePath } from '../helpers/create-fixture.js';
6
+
7
+ // Toggle this to true to debug the CLI output during the test run
8
+ const DEBUG_OUTPUT = false;
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const CLI_PATH = path.resolve(__dirname, '../../cli.js');
11
+ function runCli(args = [], options = {}) {
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn('node', [CLI_PATH, ...args], {
14
+ cwd: options.cwd || process.cwd(),
15
+ env: {
16
+ ...process.env,
17
+ FORCE_COLOR: '0',
18
+ NO_COLOR: '1'
19
+ },
20
+ stdio: ['pipe', 'pipe', 'pipe']
21
+ });
22
+ let stdout = '';
23
+ let stderr = '';
24
+ child.stdout.on('data', data => {
25
+ stdout += data.toString();
26
+ if (DEBUG_OUTPUT) {
27
+ console.log(data.toString());
28
+ }
29
+ });
30
+ child.stderr.on('data', data => {
31
+ stderr += data.toString();
32
+ if (DEBUG_OUTPUT) {
33
+ console.error(data.toString());
34
+ }
35
+ });
36
+
37
+ // Send input if provided (for interactive prompts)
38
+ if (options.input) {
39
+ child.stdin.write(options.input);
40
+ child.stdin.end();
41
+ }
42
+
43
+ // Set timeout to kill the process
44
+ const timeout = setTimeout(() => {
45
+ child.kill('SIGTERM');
46
+ resolve({
47
+ stdout,
48
+ stderr,
49
+ exitCode: null,
50
+ timedOut: true
51
+ });
52
+ }, options.timeout || 10000);
53
+ child.on('close', exitCode => {
54
+ clearTimeout(timeout);
55
+ resolve({
56
+ stdout,
57
+ stderr,
58
+ exitCode,
59
+ timedOut: false
60
+ });
61
+ });
62
+ child.on('error', err => {
63
+ clearTimeout(timeout);
64
+ reject(err);
65
+ });
66
+ });
67
+ }
68
+ describe('CLI Integration', () => {
69
+ describe('--help flag', () => {
70
+ it('displays help text', async () => {
71
+ const result = await runCli(['--help']);
72
+ expect(result.stdout).toContain('Usage');
73
+ expect(result.stdout).toContain('npx @clerk/upgrade');
74
+ expect(result.stdout).toContain('--sdk');
75
+ expect(result.stdout).toContain('--dir');
76
+ expect(result.stdout).toContain('--dry-run');
77
+ expect(result.stdout).toContain('--skip-upgrade');
78
+ expect(result.stdout).toContain('--release');
79
+ expect(result.exitCode).toBe(0);
80
+ });
81
+ });
82
+ describe('--version flag', () => {
83
+ it('displays version', async () => {
84
+ const result = await runCli(['--version']);
85
+ expect(result.stdout).toMatch(/\d+\.\d+\.\d+/);
86
+ expect(result.exitCode).toBe(0);
87
+ });
88
+ });
89
+ describe('SDK Detection', () => {
90
+ it('detects nextjs SDK from project directory', async () => {
91
+ const dir = getFixturePath('nextjs-v6');
92
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
93
+ timeout: 15000
94
+ });
95
+
96
+ // Combine stdout and stderr for full output
97
+ const output = result.stdout + result.stderr;
98
+ expect(output).toContain('@clerk/nextjs');
99
+ expect(output).toContain('dry run');
100
+ });
101
+ it('detects nextjs v7 as already upgraded', async () => {
102
+ const dir = getFixturePath('nextjs-v7');
103
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
104
+ timeout: 15000
105
+ });
106
+ expect(result.stdout).toContain('@clerk/nextjs');
107
+ expect(result.stdout).toContain('already on the latest');
108
+ });
109
+ it('errors when SDK not detected and not provided in non-interactive mode', async () => {
110
+ const dir = getFixturePath('no-clerk');
111
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
112
+ timeout: 5000
113
+ });
114
+
115
+ // Error messages go to stderr via console.error
116
+ const output = result.stdout + result.stderr;
117
+ expect(output).toContain('Could not detect Clerk SDK');
118
+ expect(output).toContain('--sdk');
119
+ expect(result.exitCode).toBe(1);
120
+ });
121
+ });
122
+ describe('--sdk flag', () => {
123
+ it('accepts explicit SDK specification', async () => {
124
+ const dir = getFixturePath('nextjs-v6');
125
+ const result = await runCli(['--dir', dir, '--sdk', 'nextjs', '--dry-run', '--skip-codemods'], {
126
+ timeout: 15000
127
+ });
128
+ expect(result.stdout).toContain('@clerk/nextjs');
129
+ });
130
+ it('accepts @clerk/ prefixed SDK name', async () => {
131
+ const dir = getFixturePath('nextjs-v6');
132
+ const result = await runCli(['--dir', dir, '--sdk', '@clerk/nextjs', '--dry-run', '--skip-codemods'], {
133
+ timeout: 15000
134
+ });
135
+ expect(result.stdout).toContain('@clerk/nextjs');
136
+ });
137
+ });
138
+ describe('--dry-run flag', () => {
139
+ let fixture;
140
+ beforeEach(() => {
141
+ fixture = createTempFixture('nextjs-v6');
142
+ });
143
+ afterEach(() => {
144
+ fixture?.cleanup();
145
+ });
146
+ it('shows what would be done without making changes', async () => {
147
+ const result = await runCli(['--dir', fixture.path, '--dry-run', '--skip-codemods'], {
148
+ timeout: 15000
149
+ });
150
+ expect(result.stdout).toContain('[dry run]');
151
+ });
152
+ it('does not modify package.json in dry-run mode', async () => {
153
+ const fs = await import('node:fs');
154
+ const pkgBefore = fs.readFileSync(path.join(fixture.path, 'package.json'), 'utf8');
155
+ await runCli(['--dir', fixture.path, '--dry-run', '--skip-codemods'], {
156
+ timeout: 15000
157
+ });
158
+ const pkgAfter = fs.readFileSync(path.join(fixture.path, 'package.json'), 'utf8');
159
+ expect(pkgAfter).toBe(pkgBefore);
160
+ });
161
+ });
162
+ describe('Version Display', () => {
163
+ it('shows current version in output', async () => {
164
+ const dir = getFixturePath('nextjs-v6');
165
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
166
+ timeout: 15000
167
+ });
168
+ expect(result.stdout).toContain('v6');
169
+ });
170
+ it('shows upgrade path in output', async () => {
171
+ const dir = getFixturePath('nextjs-v6');
172
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
173
+ timeout: 15000
174
+ });
175
+ expect(result.stdout).toContain('v6 → v7');
176
+ });
177
+ });
178
+ describe('Package Manager Detection', () => {
179
+ it('detects pnpm from fixture', async () => {
180
+ const dir = getFixturePath('nextjs-v6');
181
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
182
+ timeout: 15000
183
+ });
184
+ expect(result.stdout).toContain('pnpm');
185
+ });
186
+ it('detects yarn from fixture', async () => {
187
+ const dir = getFixturePath('react-v6');
188
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
189
+ timeout: 15000
190
+ });
191
+ expect(result.stdout).toMatch(/yarn/i);
192
+ });
193
+ it('detects npm from fixture', async () => {
194
+ const dir = getFixturePath('expo-old-package');
195
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
196
+ timeout: 15000
197
+ });
198
+ expect(result.stdout).toMatch(/npm/i);
199
+ });
200
+ });
201
+ describe('Legacy Package Names', () => {
202
+ it('handles @clerk/clerk-react legacy package', async () => {
203
+ const dir = getFixturePath('react-v6');
204
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
205
+ timeout: 15000
206
+ });
207
+ expect(result.stdout).toContain('@clerk/react');
208
+ });
209
+ it('handles @clerk/clerk-expo legacy package', async () => {
210
+ const dir = getFixturePath('expo-old-package');
211
+ const result = await runCli(['--dir', dir, '--dry-run', '--skip-codemods'], {
212
+ timeout: 15000
213
+ });
214
+ expect(result.stdout).toContain('@clerk/expo');
215
+ });
216
+ });
217
+ describe('Codemods', () => {
218
+ let fixture;
219
+ beforeEach(() => {
220
+ fixture = createTempFixture('nextjs-v6');
221
+ });
222
+ afterEach(() => {
223
+ fixture?.cleanup();
224
+ });
225
+ it('lists codemods that would run in dry-run mode', async () => {
226
+ const result = await runCli(['--dir', fixture.path, '--dry-run', '--skip-codemods'], {
227
+ timeout: 15000
228
+ });
229
+ expect(result.stdout).toContain('codemod');
230
+ });
231
+ });
232
+ describe('--skip-upgrade flag', () => {
233
+ let fixture;
234
+ beforeEach(() => {
235
+ fixture = createTempFixture('nextjs-v6');
236
+ });
237
+ afterEach(() => {
238
+ fixture?.cleanup();
239
+ });
240
+ it('skips the package upgrade step', async () => {
241
+ const result = await runCli(['--dir', fixture.path, '--skip-upgrade', '--skip-codemods'], {
242
+ timeout: 15000
243
+ });
244
+ expect(result.stdout).toContain('Skipping package upgrade');
245
+ expect(result.stdout).toContain('--skip-upgrade');
246
+ });
247
+ it('does not modify package.json when skipping upgrade', async () => {
248
+ const fs = await import('node:fs');
249
+ const pkgBefore = fs.readFileSync(path.join(fixture.path, 'package.json'), 'utf8');
250
+ await runCli(['--dir', fixture.path, '--skip-upgrade', '--skip-codemods'], {
251
+ timeout: 15000
252
+ });
253
+ const pkgAfter = fs.readFileSync(path.join(fixture.path, 'package.json'), 'utf8');
254
+ expect(pkgAfter).toBe(pkgBefore);
255
+ });
256
+ });
257
+ describe('--release flag', () => {
258
+ it('loads specific release configuration', async () => {
259
+ const dir = getFixturePath('nextjs-v7');
260
+ const result = await runCli(['--dir', dir, '--release', 'core-3', '--dry-run', '--skip-codemods'], {
261
+ timeout: 15000
262
+ });
263
+ expect(result.stdout).toContain('core-3');
264
+ });
265
+ it('errors when release does not exist', async () => {
266
+ const dir = getFixturePath('nextjs-v6');
267
+ const result = await runCli(['--dir', dir, '--release', 'nonexistent-release', '--dry-run', '--skip-codemods'], {
268
+ timeout: 15000
269
+ });
270
+ const output = result.stdout + result.stderr;
271
+ expect(output).toContain('No upgrade path found');
272
+ expect(result.exitCode).toBe(1);
273
+ });
274
+ });
275
+ });
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getOldPackageName, getTargetPackageName, loadConfig } from '../../config.js';
3
+ describe('loadConfig', () => {
4
+ it('returns config with needsUpgrade: true for nextjs v6', async () => {
5
+ const config = await loadConfig('nextjs', 6);
6
+ expect(config).not.toBeNull();
7
+ expect(config.id).toBe('core-3');
8
+ expect(config.needsUpgrade).toBe(true);
9
+ expect(config.alreadyUpgraded).toBe(false);
10
+ });
11
+ it('returns config with alreadyUpgraded: true for nextjs v7', async () => {
12
+ const config = await loadConfig('nextjs', 7);
13
+ expect(config).not.toBeNull();
14
+ expect(config.id).toBe('core-3');
15
+ expect(config.needsUpgrade).toBe(false);
16
+ expect(config.alreadyUpgraded).toBe(true);
17
+ });
18
+ it('returns config with needsUpgrade: true for react v6', async () => {
19
+ const config = await loadConfig('react', 6);
20
+ expect(config).not.toBeNull();
21
+ expect(config.needsUpgrade).toBe(true);
22
+ });
23
+ it('returns config with needsUpgrade: true for expo v2', async () => {
24
+ const config = await loadConfig('expo', 2);
25
+ expect(config).not.toBeNull();
26
+ expect(config.needsUpgrade).toBe(true);
27
+ });
28
+ it('returns null for unsupported SDK version (too old)', async () => {
29
+ const config = await loadConfig('nextjs', 4);
30
+ expect(config).toBeNull();
31
+ });
32
+ it('loads codemods array from config', async () => {
33
+ const config = await loadConfig('nextjs', 6);
34
+ expect(config.codemods).toBeDefined();
35
+ expect(Array.isArray(config.codemods)).toBe(true);
36
+ expect(config.codemods.length).toBeGreaterThan(0);
37
+ });
38
+ it('loads changes array from config', async () => {
39
+ const config = await loadConfig('nextjs', 6);
40
+ expect(config.changes).toBeDefined();
41
+ expect(Array.isArray(config.changes)).toBe(true);
42
+ });
43
+ it('includes docsUrl in config', async () => {
44
+ const config = await loadConfig('nextjs', 6);
45
+ expect(config.docsUrl).toBeDefined();
46
+ expect(config.docsUrl).toContain('clerk.com');
47
+ });
48
+ describe('release parameter', () => {
49
+ it('loads specific release when provided', async () => {
50
+ const config = await loadConfig('nextjs', 7, 'core-3');
51
+ expect(config).not.toBeNull();
52
+ expect(config.id).toBe('core-3');
53
+ });
54
+ it('returns null for non-existent release', async () => {
55
+ const config = await loadConfig('nextjs', 6, 'nonexistent-release');
56
+ expect(config).toBeNull();
57
+ });
58
+ it('ignores version status when release is specified', async () => {
59
+ const config = await loadConfig('nextjs', 7, 'core-3');
60
+ expect(config).not.toBeNull();
61
+ expect(config.alreadyUpgraded).toBe(true);
62
+ });
63
+ it('loads changes for specific release', async () => {
64
+ const config = await loadConfig('nextjs', 6, 'core-3');
65
+ expect(config.changes).toBeDefined();
66
+ expect(Array.isArray(config.changes)).toBe(true);
67
+ });
68
+ });
69
+ });
70
+ describe('getTargetPackageName', () => {
71
+ it('returns @clerk/react for react sdk', () => {
72
+ expect(getTargetPackageName('react')).toBe('@clerk/react');
73
+ });
74
+ it('returns @clerk/react for clerk-react sdk', () => {
75
+ expect(getTargetPackageName('clerk-react')).toBe('@clerk/react');
76
+ });
77
+ it('returns @clerk/expo for expo sdk', () => {
78
+ expect(getTargetPackageName('expo')).toBe('@clerk/expo');
79
+ });
80
+ it('returns @clerk/expo for clerk-expo sdk', () => {
81
+ expect(getTargetPackageName('clerk-expo')).toBe('@clerk/expo');
82
+ });
83
+ it('returns @clerk/nextjs for nextjs sdk', () => {
84
+ expect(getTargetPackageName('nextjs')).toBe('@clerk/nextjs');
85
+ });
86
+ });
87
+ describe('getOldPackageName', () => {
88
+ it('returns @clerk/clerk-react for react sdk', () => {
89
+ expect(getOldPackageName('react')).toBe('@clerk/clerk-react');
90
+ });
91
+ it('returns @clerk/clerk-expo for expo sdk', () => {
92
+ expect(getOldPackageName('expo')).toBe('@clerk/clerk-expo');
93
+ });
94
+ it('returns null for nextjs sdk (no rename)', () => {
95
+ expect(getOldPackageName('nextjs')).toBeNull();
96
+ });
97
+ });