@clerk/upgrade 2.0.0-snapshot.v20251208202852 → 2.0.0-snapshot.v20251215203425
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.
- package/README.md +35 -5
- package/dist/__tests__/fixtures/nextjs-v6/src/app.tsx +3 -3
- package/dist/__tests__/fixtures/nextjs-v6-scan-issues/package.json +9 -0
- package/dist/__tests__/fixtures/nextjs-v6-scan-issues/pnpm-lock.yaml +2 -0
- package/dist/__tests__/fixtures/nextjs-v6-scan-issues/src/app.tsx +30 -0
- package/dist/__tests__/integration/cli.test.js +45 -0
- package/dist/__tests__/integration/config.test.js +21 -0
- package/dist/__tests__/integration/runner.test.js +2 -23
- package/dist/cli.js +24 -10
- package/dist/codemods/__tests__/__fixtures__/transform-align-experimental-unstable-prefixes.fixtures.js +24 -0
- package/dist/codemods/__tests__/transform-align-experimental-unstable-prefixes.test.js +1 -1
- package/dist/codemods/index.js +4 -4
- package/dist/codemods/transform-align-experimental-unstable-prefixes.cjs +12 -12
- package/dist/config.js +24 -1
- package/dist/render.js +40 -4
- package/dist/runner.js +8 -6
- package/dist/versions/core-3/changes/after-switch-organization-url-removed.md +14 -0
- package/dist/versions/core-3/changes/appearance-layout-to-options.md +20 -0
- package/dist/versions/core-3/changes/billing-props-removed.md +14 -0
- package/dist/versions/core-3/changes/checkout-api-changed.md +35 -0
- package/dist/versions/core-3/changes/clerk-react-package-rename.md +0 -1
- package/dist/versions/core-3/changes/clerk-types-deprecation.md +17 -0
- package/dist/versions/core-3/changes/client-active-sessions-removed.md +12 -0
- package/dist/versions/core-3/changes/color-ring-backdrop-opacity.md +21 -0
- package/dist/versions/core-3/changes/deprecated-redirect-props-removed.md +29 -0
- package/dist/versions/core-3/changes/experimental-prefix-aligned.md +17 -0
- package/dist/versions/core-3/changes/expo-min-version.md +24 -0
- package/dist/versions/core-3/changes/hide-slug-removed.md +15 -0
- package/dist/versions/core-3/changes/min-node-version.md +17 -0
- package/dist/versions/core-3/changes/nextjs-encryption-key-required.md +17 -0
- package/dist/versions/core-3/changes/nuxt-getauth-removed.md +21 -0
- package/dist/versions/core-3/changes/nuxt-routing-strategy-default-path.md +28 -0
- package/dist/versions/core-3/changes/saml-account-renamed.md +15 -0
- package/dist/versions/core-3/changes/saml-to-enterprise-sso.md +12 -0
- package/dist/versions/core-3/changes/set-active-before-emit-removed.md +22 -0
- package/dist/versions/core-3/changes/show-optional-fields-default.md +20 -0
- package/dist/versions/core-3/changes/ui-themes-export-path.md +16 -0
- package/dist/versions/core-3/changes/unstable-to-internal.md +25 -0
- package/dist/versions/core-3/changes/user-settings-saml-renamed.md +13 -0
- package/dist/versions/core-3/changes/userbutton-signout-props-removed.md +26 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -27,25 +27,55 @@
|
|
|
27
27
|
|
|
28
28
|
## Getting Started
|
|
29
29
|
|
|
30
|
-
A
|
|
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 `>=
|
|
34
|
+
- Node.js `>=20.9.0`
|
|
35
|
+
- pnpm, npm, or yarn installed in the target project
|
|
35
36
|
|
|
36
37
|
## Usage
|
|
37
38
|
|
|
38
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ClerkProvider, useAuth
|
|
2
|
-
import {
|
|
1
|
+
import { ClerkProvider, useAuth } from '@clerk/nextjs';
|
|
2
|
+
import { useUser } from '@clerk/clerk-react';
|
|
3
3
|
|
|
4
4
|
export default function App({ children }) {
|
|
5
|
-
return <ClerkProvider
|
|
5
|
+
return <ClerkProvider>{children}</ClerkProvider>;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function UserProfile() {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ClerkProvider, SignIn, useAuth } from '@clerk/nextjs';
|
|
2
|
+
|
|
3
|
+
export default function App({ children }) {
|
|
4
|
+
return (
|
|
5
|
+
<ClerkProvider
|
|
6
|
+
appearance={{
|
|
7
|
+
layout: {
|
|
8
|
+
socialButtonsPlacement: 'bottom',
|
|
9
|
+
},
|
|
10
|
+
}}
|
|
11
|
+
>
|
|
12
|
+
{children}
|
|
13
|
+
</ClerkProvider>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function SignInPage() {
|
|
18
|
+
return (
|
|
19
|
+
<SignIn
|
|
20
|
+
afterSignInUrl='/dashboard'
|
|
21
|
+
afterSignUpUrl='/onboarding'
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function SamlCallback() {
|
|
27
|
+
const { isSignedIn } = useAuth();
|
|
28
|
+
// Handle saml callback
|
|
29
|
+
return <div>SAML SSO Callback</div>;
|
|
30
|
+
}
|
|
@@ -74,6 +74,8 @@ describe('CLI Integration', () => {
|
|
|
74
74
|
expect(result.stdout).toContain('--sdk');
|
|
75
75
|
expect(result.stdout).toContain('--dir');
|
|
76
76
|
expect(result.stdout).toContain('--dry-run');
|
|
77
|
+
expect(result.stdout).toContain('--skip-upgrade');
|
|
78
|
+
expect(result.stdout).toContain('--release');
|
|
77
79
|
expect(result.exitCode).toBe(0);
|
|
78
80
|
});
|
|
79
81
|
});
|
|
@@ -227,4 +229,47 @@ describe('CLI Integration', () => {
|
|
|
227
229
|
expect(result.stdout).toContain('codemod');
|
|
228
230
|
});
|
|
229
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
|
+
});
|
|
230
275
|
});
|
|
@@ -45,6 +45,27 @@ describe('loadConfig', () => {
|
|
|
45
45
|
expect(config.docsUrl).toBeDefined();
|
|
46
46
|
expect(config.docsUrl).toContain('clerk.com');
|
|
47
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
|
+
});
|
|
48
69
|
});
|
|
49
70
|
describe('getTargetPackageName', () => {
|
|
50
71
|
it('returns @clerk/react for react sdk', () => {
|
|
@@ -34,7 +34,7 @@ describe('runScans', () => {
|
|
|
34
34
|
ignore: []
|
|
35
35
|
};
|
|
36
36
|
const results = await runScans(config, 'nextjs', options);
|
|
37
|
-
expect(
|
|
37
|
+
expect(results.length).toBeGreaterThan(0);
|
|
38
38
|
});
|
|
39
39
|
it('returns empty array when no matchers match', async () => {
|
|
40
40
|
const config = await loadConfig('nextjs', 6);
|
|
@@ -53,27 +53,6 @@ describe('runScans', () => {
|
|
|
53
53
|
ignore: ['**/src/**']
|
|
54
54
|
};
|
|
55
55
|
const results = await runScans(config, 'nextjs', options);
|
|
56
|
-
expect(
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
describe('runScans with theme imports', () => {
|
|
60
|
-
let fixture;
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
fixture = createTempFixture('nextjs-v6');
|
|
63
|
-
});
|
|
64
|
-
afterEach(() => {
|
|
65
|
-
fixture?.cleanup();
|
|
66
|
-
});
|
|
67
|
-
it('detects theme imports from @clerk/nextjs/themes', async () => {
|
|
68
|
-
const config = await loadConfig('nextjs', 6);
|
|
69
|
-
const options = {
|
|
70
|
-
dir: fixture.path,
|
|
71
|
-
ignore: []
|
|
72
|
-
};
|
|
73
|
-
const results = await runScans(config, 'nextjs', options);
|
|
74
|
-
const themeChange = results.find(r => r.title?.includes('Theme') || r.docsAnchor?.includes('theme'));
|
|
75
|
-
if (themeChange) {
|
|
76
|
-
expect(themeChange.instances.length).toBeGreaterThan(0);
|
|
77
|
-
}
|
|
56
|
+
expect(results).toEqual([]);
|
|
78
57
|
});
|
|
79
58
|
});
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,8 @@ const cli = meow(`
|
|
|
15
15
|
--dir Directory to scan (defaults to current directory)
|
|
16
16
|
--glob Glob pattern for files to transform (defaults to **/*.{js,jsx,ts,tsx,mjs,cjs})
|
|
17
17
|
--ignore Directories/files to ignore (can be used multiple times)
|
|
18
|
+
--skip-upgrade Skip the upgrade step
|
|
19
|
+
--release Name of the release you're upgrading to (e.g. core-3)
|
|
18
20
|
--dry-run Show what would be done without making changes
|
|
19
21
|
|
|
20
22
|
Examples
|
|
@@ -28,13 +30,14 @@ const cli = meow(`
|
|
|
28
30
|
`, {
|
|
29
31
|
importMeta: import.meta,
|
|
30
32
|
flags: {
|
|
31
|
-
sdk: {
|
|
32
|
-
type: 'string'
|
|
33
|
-
},
|
|
34
33
|
dir: {
|
|
35
34
|
type: 'string',
|
|
36
35
|
default: process.cwd()
|
|
37
36
|
},
|
|
37
|
+
dryRun: {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
default: false
|
|
40
|
+
},
|
|
38
41
|
glob: {
|
|
39
42
|
type: 'string',
|
|
40
43
|
default: '**/*.(js|jsx|ts|tsx|mjs|cjs)'
|
|
@@ -43,11 +46,17 @@ const cli = meow(`
|
|
|
43
46
|
type: 'string',
|
|
44
47
|
isMultiple: true
|
|
45
48
|
},
|
|
46
|
-
|
|
49
|
+
release: {
|
|
50
|
+
type: 'string'
|
|
51
|
+
},
|
|
52
|
+
sdk: {
|
|
53
|
+
type: 'string'
|
|
54
|
+
},
|
|
55
|
+
skipCodemods: {
|
|
47
56
|
type: 'boolean',
|
|
48
57
|
default: false
|
|
49
58
|
},
|
|
50
|
-
|
|
59
|
+
skipUpgrade: {
|
|
51
60
|
type: 'boolean',
|
|
52
61
|
default: false
|
|
53
62
|
}
|
|
@@ -57,10 +66,12 @@ async function main() {
|
|
|
57
66
|
renderHeader();
|
|
58
67
|
const options = {
|
|
59
68
|
dir: cli.flags.dir,
|
|
69
|
+
dryRun: cli.flags.dryRun,
|
|
60
70
|
glob: cli.flags.glob,
|
|
61
71
|
ignore: cli.flags.ignore,
|
|
62
|
-
|
|
63
|
-
skipCodemods: cli.flags.skipCodemods
|
|
72
|
+
release: cli.flags.release,
|
|
73
|
+
skipCodemods: cli.flags.skipCodemods,
|
|
74
|
+
skipUpgrade: cli.flags.skipUpgrade
|
|
64
75
|
};
|
|
65
76
|
if (options.dryRun) {
|
|
66
77
|
renderWarning(' Upgrade running in dry run mode - no changes will be made');
|
|
@@ -94,7 +105,7 @@ async function main() {
|
|
|
94
105
|
const packageManager = detectPackageManager(options.dir);
|
|
95
106
|
|
|
96
107
|
// Step 3: Load version config
|
|
97
|
-
const config = await loadConfig(sdk, currentVersion);
|
|
108
|
+
const config = await loadConfig(sdk, currentVersion, options.release);
|
|
98
109
|
if (!config) {
|
|
99
110
|
renderError(`No upgrade path found for @clerk/${sdk}. Your version may be too old for this upgrade tool.`);
|
|
100
111
|
process.exit(1);
|
|
@@ -110,14 +121,17 @@ async function main() {
|
|
|
110
121
|
dir: options.dir,
|
|
111
122
|
packageManager: getPackageManagerDisplayName(packageManager)
|
|
112
123
|
});
|
|
113
|
-
if (isInteractive && !(await promptConfirm('Ready to upgrade?'))) {
|
|
124
|
+
if (isInteractive && !(await promptConfirm('Ready to upgrade?', true))) {
|
|
114
125
|
renderError('Upgrade cancelled. Exiting...');
|
|
115
126
|
process.exit(0);
|
|
116
127
|
}
|
|
117
128
|
console.log('');
|
|
118
129
|
|
|
119
130
|
// Step 5: Handle upgrade status
|
|
120
|
-
if (
|
|
131
|
+
if (options.skipUpgrade) {
|
|
132
|
+
renderText('Skipping package upgrade (--skip-upgrade flag)', 'yellow');
|
|
133
|
+
renderNewline();
|
|
134
|
+
} else if (config.alreadyUpgraded) {
|
|
121
135
|
renderSuccess(`You're already on the latest major version of @clerk/${sdk}`);
|
|
122
136
|
} else if (config.needsUpgrade) {
|
|
123
137
|
await performUpgrade(sdk, packageManager, config, options);
|
|
@@ -65,4 +65,28 @@ createClerkClient();
|
|
|
65
65
|
output: `
|
|
66
66
|
<OrganizationProfile />;
|
|
67
67
|
`
|
|
68
|
+
}, {
|
|
69
|
+
name: 'Does not rename class constructors',
|
|
70
|
+
source: `
|
|
71
|
+
export class AppError extends Error {
|
|
72
|
+
constructor(
|
|
73
|
+
message: string,
|
|
74
|
+
public readonly code: string,
|
|
75
|
+
public readonly statusCode: number = 500
|
|
76
|
+
) {
|
|
77
|
+
super(message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`,
|
|
81
|
+
output: `
|
|
82
|
+
export class AppError extends Error {
|
|
83
|
+
constructor(
|
|
84
|
+
message: string,
|
|
85
|
+
public readonly code: string,
|
|
86
|
+
public readonly statusCode: number = 500
|
|
87
|
+
) {
|
|
88
|
+
super(message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`
|
|
68
92
|
}];
|
package/dist/codemods/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { globby } from 'globby';
|
|
5
4
|
import { run } from 'jscodeshift/src/Runner.js';
|
|
5
|
+
import { glob } from 'tinyglobby';
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const CODEMOD_CONFIG = {
|
|
8
8
|
'transform-remove-deprecated-props': {
|
|
9
9
|
renderSummary: renderDeprecatedPropsSummary
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
|
-
const GLOBBY_IGNORE = ['**/*.md', 'node_modules/**', '**/node_modules/**', '.git/**', '**/*.json', 'package.json', '**/package.json', 'package-lock.json', '**/package-lock.json', 'yarn.lock', '**/yarn.lock', 'pnpm-lock.yaml', '**/pnpm-lock.yaml', 'yalc.lock', '**/*.
|
|
13
|
-
export async function runCodemod(transform = 'transform-async-request',
|
|
12
|
+
const GLOBBY_IGNORE = ['**/*.md', 'node_modules/**', '**/node_modules/**', '.git/**', '**/*.json', 'package.json', '**/package.json', 'package-lock.json', '**/package-lock.json', 'yarn.lock', '**/yarn.lock', 'pnpm-lock.yaml', '**/pnpm-lock.yaml', 'yalc.lock', '**/*.{ico,png,webp,svg,gif,jpg,jpeg}', '**/*.{mp4,mkv,wmv,m4v,mov,avi,flv,webm,flac,mka,m4a,aac,ogg}', '**/*.{css,scss,sass,less,styl}'];
|
|
13
|
+
export async function runCodemod(transform = 'transform-async-request', patterns, options = {}) {
|
|
14
14
|
if (!transform) {
|
|
15
15
|
throw new Error('No transform provided');
|
|
16
16
|
}
|
|
17
17
|
const resolvedPath = resolve(__dirname, `${transform}.cjs`);
|
|
18
|
-
const paths = await
|
|
18
|
+
const paths = await glob(patterns, {
|
|
19
19
|
ignore: GLOBBY_IGNORE
|
|
20
20
|
});
|
|
21
21
|
if (options.skipCodemods) {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
const SPECIFIC_RENAMES = {
|
|
2
|
-
experimental_createTheme: 'createTheme',
|
|
1
|
+
const SPECIFIC_RENAMES = Object.freeze({
|
|
3
2
|
__experimental_createTheme: 'createTheme',
|
|
4
|
-
experimental__simple: 'simple',
|
|
5
3
|
__experimental_simple: 'simple',
|
|
6
4
|
__unstable__createClerkClient: 'createClerkClient',
|
|
7
|
-
__unstable_invokeMiddlewareOnAuthStateChange: '__internal_invokeMiddlewareOnAuthStateChange',
|
|
8
5
|
__unstable__environment: '__internal_environment',
|
|
9
|
-
__unstable__updateProps: '__internal_updateProps',
|
|
10
|
-
__unstable__setEnvironment: '__internal_setEnvironment',
|
|
11
|
-
__unstable__onBeforeRequest: '__internal_onBeforeRequest',
|
|
12
6
|
__unstable__onAfterResponse: '__internal_onAfterResponse',
|
|
7
|
+
__unstable__onAfterSetActive: '__internal_onAfterSetActive',
|
|
8
|
+
__unstable__onBeforeRequest: '__internal_onBeforeRequest',
|
|
13
9
|
__unstable__onBeforeSetActive: '__internal_onBeforeSetActive',
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
__unstable__setEnvironment: '__internal_setEnvironment',
|
|
11
|
+
__unstable__updateProps: '__internal_updateProps',
|
|
12
|
+
__unstable_invokeMiddlewareOnAuthStateChange: '__internal_invokeMiddlewareOnAuthStateChange',
|
|
13
|
+
experimental__simple: 'simple',
|
|
14
|
+
experimental_createTheme: 'createTheme'
|
|
15
|
+
});
|
|
16
16
|
const REMOVED_PROPS = new Set(['__unstable_manageBillingUrl', '__unstable_manageBillingLabel', '__unstable_manageBillingMembersLimit', 'experimental__forceOauthFirst']);
|
|
17
17
|
const UI_THEME_NAMES = new Set(['createTheme', 'simple', 'experimental_createTheme', '__experimental_createTheme', 'experimental__simple', '__experimental_simple']);
|
|
18
18
|
const UI_THEME_SOURCE = '@clerk/ui/themes/experimental';
|
|
@@ -28,10 +28,10 @@ module.exports = function transformAlignExperimentalUnstablePrefixes({
|
|
|
28
28
|
const root = j(source);
|
|
29
29
|
let dirty = false;
|
|
30
30
|
const maybeRename = name => {
|
|
31
|
-
if (!name || REMOVED_PROPS.has(name)) {
|
|
31
|
+
if (!name || REMOVED_PROPS.has(name) || !Object.hasOwn(SPECIFIC_RENAMES, name)) {
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
|
-
return SPECIFIC_RENAMES[name]
|
|
34
|
+
return SPECIFIC_RENAMES[name];
|
|
35
35
|
};
|
|
36
36
|
const renameIdentifier = node => {
|
|
37
37
|
const newName = maybeRename(node.name);
|
|
@@ -164,7 +164,7 @@ module.exports = function transformAlignExperimentalUnstablePrefixes({
|
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
|
-
root.find(j.Identifier).forEach(path => {
|
|
167
|
+
root.find(j.Identifier).filter(path => maybeRename(path.node.name)).forEach(path => {
|
|
168
168
|
renameIdentifier(path.node);
|
|
169
169
|
});
|
|
170
170
|
root.find(j.JSXOpeningElement).forEach(path => {
|
package/dist/config.js
CHANGED
|
@@ -4,10 +4,33 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const VERSIONS_DIR = path.join(__dirname, 'versions');
|
|
7
|
-
export async function loadConfig(sdk, currentVersion) {
|
|
7
|
+
export async function loadConfig(sdk, currentVersion, release) {
|
|
8
8
|
const versionDirs = fs.readdirSync(VERSIONS_DIR, {
|
|
9
9
|
withFileTypes: true
|
|
10
10
|
}).filter(d => d.isDirectory()).map(d => d.name);
|
|
11
|
+
|
|
12
|
+
// If a specific release is requested, load it directly
|
|
13
|
+
if (release) {
|
|
14
|
+
if (!versionDirs.includes(release)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const configPath = path.join(VERSIONS_DIR, release, 'index.js');
|
|
18
|
+
if (!fs.existsSync(configPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const moduleUrl = pathToFileURL(configPath).href;
|
|
22
|
+
const mod = await import(moduleUrl);
|
|
23
|
+
const config = mod.default ?? mod;
|
|
24
|
+
const changes = loadChanges(release, sdk);
|
|
25
|
+
const versionStatus = getVersionStatus(config, sdk, currentVersion);
|
|
26
|
+
return {
|
|
27
|
+
...config,
|
|
28
|
+
changes,
|
|
29
|
+
versionStatus,
|
|
30
|
+
needsUpgrade: versionStatus === 'needs-upgrade',
|
|
31
|
+
alreadyUpgraded: versionStatus === 'already-upgraded'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
11
34
|
let applicableConfig = null;
|
|
12
35
|
for (const versionDir of versionDirs) {
|
|
13
36
|
const configPath = path.join(VERSIONS_DIR, versionDir, 'index.js');
|
package/dist/render.js
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
import * as readline from 'node:readline';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
const gradientColors = ['#5ee7df', '#b490ca'];
|
|
4
|
+
const hexToRgb = hex => {
|
|
5
|
+
const value = hex.replace('#', '');
|
|
6
|
+
const chunk = value.length === 3 ? value.split('').map(char => char + char) : value.match(/.{2}/g);
|
|
7
|
+
return chunk.map(part => parseInt(part, 16));
|
|
8
|
+
};
|
|
9
|
+
const interpolate = (start, end, t) => Math.round(start + (end - start) * t);
|
|
10
|
+
const applyGradient = text => {
|
|
11
|
+
if (!text) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
const rgb = gradientColors.map(hexToRgb);
|
|
15
|
+
const maxIndex = text.length - 1 || 1;
|
|
16
|
+
const segmentCount = rgb.length - 1;
|
|
17
|
+
return text.split('').map((char, index) => {
|
|
18
|
+
const progress = index / maxIndex;
|
|
19
|
+
const segment = Math.min(Math.floor(progress * segmentCount), segmentCount - 1);
|
|
20
|
+
const localT = segmentCount === 0 ? 0 : progress * segmentCount - segment;
|
|
21
|
+
const [r1, g1, b1] = rgb[segment];
|
|
22
|
+
const [r2, g2, b2] = rgb[segment + 1] || rgb[segment];
|
|
23
|
+
const r = interpolate(r1, r2, localT);
|
|
24
|
+
const g = interpolate(g1, g2, localT);
|
|
25
|
+
const b = interpolate(b1, b2, localT);
|
|
26
|
+
return chalk.rgb(r, g, b)(char);
|
|
27
|
+
}).join('');
|
|
28
|
+
};
|
|
3
29
|
export function renderHeader() {
|
|
4
30
|
console.log('');
|
|
5
|
-
|
|
31
|
+
const heading = [' █▀▀ █ █▀▀ █▀█ █▄▀ █ █ █▀█ █▀▀ █▀█ ▄▀█ █▀▄ █▀▀', ' █▄▄ █▄▄ ██▄ █▀▄ █ █ █▄█ █▀▀ █▄█ █▀▄ █▀█ █▄▀ ██▄'];
|
|
32
|
+
heading.forEach(line => console.log(applyGradient(line)));
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log("Hello friend! We're excited to help you upgrade Clerk modules. Before we get started, a couple");
|
|
35
|
+
console.log('questions...');
|
|
6
36
|
console.log('');
|
|
7
37
|
}
|
|
8
38
|
export function renderText(message, color) {
|
|
@@ -43,15 +73,21 @@ export function renderConfig({
|
|
|
43
73
|
}
|
|
44
74
|
console.log('');
|
|
45
75
|
}
|
|
46
|
-
export async function promptConfirm(message) {
|
|
76
|
+
export async function promptConfirm(message, defaultYes = false) {
|
|
47
77
|
const rl = readline.createInterface({
|
|
48
78
|
input: process.stdin,
|
|
49
79
|
output: process.stdout
|
|
50
80
|
});
|
|
51
81
|
return new Promise(resolve => {
|
|
52
|
-
|
|
82
|
+
const prompt = defaultYes ? `${message} (Y/n): ` : `${message} (y/N): `;
|
|
83
|
+
rl.question(prompt, answer => {
|
|
53
84
|
rl.close();
|
|
54
|
-
|
|
85
|
+
const normalized = answer.trim().toLowerCase();
|
|
86
|
+
if (!normalized) {
|
|
87
|
+
resolve(defaultYes);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
resolve(normalized === 'y' || normalized === 'yes');
|
|
55
91
|
});
|
|
56
92
|
});
|
|
57
93
|
}
|
package/dist/runner.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { convertPathToPattern, globby } from 'globby';
|
|
5
4
|
import indexToPosition from 'index-to-position';
|
|
5
|
+
import { glob } from 'tinyglobby';
|
|
6
6
|
import { getCodemodConfig, runCodemod } from './codemods/index.js';
|
|
7
7
|
import { createSpinner, renderCodemodResults } from './render.js';
|
|
8
|
-
const GLOBBY_IGNORE = ['node_modules/**', '**/node_modules/**', '.git/**', 'dist/**', '**/dist/**', 'build/**', '**/build/**', '.next/**', '**/.next/**', 'package.json', '**/package.json', 'package-lock.json', '**/package-lock.json', 'yarn.lock', '**/yarn.lock', 'pnpm-lock.yaml', '**/pnpm-lock.yaml', '**/*.
|
|
8
|
+
const GLOBBY_IGNORE = ['node_modules/**', '**/node_modules/**', '.git/**', 'dist/**', '**/dist/**', 'build/**', '**/build/**', '.next/**', '**/.next/**', 'package.json', '**/package.json', 'package-lock.json', '**/package-lock.json', 'yarn.lock', '**/yarn.lock', 'pnpm-lock.yaml', '**/pnpm-lock.yaml', '**/*.{png,webp,svg,gif,jpg,jpeg}', '**/*.{mp4,mkv,wmv,m4v,mov,avi,flv,webm,flac,mka,m4a,aac,ogg}'];
|
|
9
9
|
export async function runCodemods(config, sdk, options) {
|
|
10
10
|
const codemods = config.codemods || [];
|
|
11
11
|
if (codemods.length === 0) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const patterns = typeof options.glob === 'string' ? options.glob.split(/[ ,]/).filter(Boolean) : options.glob;
|
|
15
15
|
for (const transform of codemods) {
|
|
16
16
|
const spinner = createSpinner(`Running codemod: ${transform}`);
|
|
17
17
|
try {
|
|
18
|
-
const result = await runCodemod(transform,
|
|
18
|
+
const result = await runCodemod(transform, patterns, options);
|
|
19
19
|
spinner.success(`Codemod applied: ${chalk.dim(transform)}`);
|
|
20
20
|
renderCodemodResults(transform, result);
|
|
21
21
|
const codemodConfig = getCodemodConfig(transform);
|
|
@@ -35,8 +35,10 @@ export async function runScans(config, sdk, options) {
|
|
|
35
35
|
}
|
|
36
36
|
const spinner = createSpinner('Scanning files for breaking changes...');
|
|
37
37
|
try {
|
|
38
|
-
const
|
|
39
|
-
const files = await
|
|
38
|
+
const cwd = path.resolve(options.dir);
|
|
39
|
+
const files = await glob('**/*', {
|
|
40
|
+
cwd,
|
|
41
|
+
absolute: true,
|
|
40
42
|
ignore: [...GLOBBY_IGNORE, ...(options.ignore || [])]
|
|
41
43
|
});
|
|
42
44
|
const results = {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`afterSwitchOrganizationUrl` removed from `OrganizationSwitcher`'
|
|
3
|
+
matcher: 'afterSwitchOrganizationUrl'
|
|
4
|
+
category: 'deprecation-removal'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `afterSwitchOrganizationUrl` prop has been removed from `OrganizationSwitcher`. Use `afterSelectOrganizationUrl` instead:
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
<OrganizationSwitcher
|
|
11
|
+
- afterSwitchOrganizationUrl="/org-dashboard"
|
|
12
|
+
+ afterSelectOrganizationUrl="/org-dashboard"
|
|
13
|
+
/>
|
|
14
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`appearance.layout` renamed to `appearance.options`'
|
|
3
|
+
matcher: 'appearance[\\s\\S]*?\\.layout'
|
|
4
|
+
matcherFlags: 'm'
|
|
5
|
+
category: 'breaking'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The `appearance.layout` property has been renamed to `appearance.options`. Update all instances in your codebase:
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
<ClerkProvider
|
|
12
|
+
appearance={{
|
|
13
|
+
- layout: {
|
|
14
|
+
+ options: {
|
|
15
|
+
socialButtonsPlacement: 'bottom',
|
|
16
|
+
socialButtonsVariant: 'iconButton',
|
|
17
|
+
}
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Unstable billing props removed'
|
|
3
|
+
matcher:
|
|
4
|
+
- '__unstable_manageBillingUrl'
|
|
5
|
+
- '__unstable_manageBillingLabel'
|
|
6
|
+
- '__unstable_manageBillingMembersLimit'
|
|
7
|
+
category: 'deprecation-removal'
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
The following unstable properties have been removed. If you were relying on these, please reach out to support.
|
|
11
|
+
|
|
12
|
+
- `__unstable_manageBillingUrl`
|
|
13
|
+
- `__unstable_manageBillingLabel`
|
|
14
|
+
- `__unstable_manageBillingMembersLimit`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`useCheckout` and `Clerk.checkout()` return value changed'
|
|
3
|
+
matcher:
|
|
4
|
+
- 'useCheckout'
|
|
5
|
+
- 'Clerk\\.checkout'
|
|
6
|
+
- '\.checkout\('
|
|
7
|
+
category: 'breaking'
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
The return values of `useCheckout` hook and `Clerk.checkout()` have been updated.
|
|
11
|
+
|
|
12
|
+
### React Hook
|
|
13
|
+
|
|
14
|
+
```diff
|
|
15
|
+
- const { id, plan, status, start, confirm, paymentSource } = useCheckout({ planId: "xxx", planPeriod: "annual" });
|
|
16
|
+
+ const { checkout, errors, fetchStatus } = useCheckout({ planId: "xxx", planPeriod: "annual" });
|
|
17
|
+
+ // Access properties via checkout object
|
|
18
|
+
+ checkout.plan
|
|
19
|
+
+ checkout.status
|
|
20
|
+
+ checkout.start()
|
|
21
|
+
+ checkout.confirm()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Vanilla JS
|
|
25
|
+
|
|
26
|
+
```diff
|
|
27
|
+
- const { getState, subscribe, confirm, start, clear, finalize } = Clerk.checkout({ planId: "xxx", planPeriod: "annual" });
|
|
28
|
+
- getState().isStarting
|
|
29
|
+
- getState().checkout
|
|
30
|
+
+ const { checkout, errors, fetchStatus } = Clerk.checkout({ planId: "xxx", planPeriod: "annual" });
|
|
31
|
+
+ checkout.plan
|
|
32
|
+
+ checkout.status
|
|
33
|
+
+ checkout.start()
|
|
34
|
+
+ checkout.confirm()
|
|
35
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`@clerk/types` deprecated in favor of `@clerk/shared/types`'
|
|
3
|
+
matcher: "from\\s+['\"]@clerk/types['\"]"
|
|
4
|
+
category: 'deprecation'
|
|
5
|
+
warning: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The `@clerk/types` package is deprecated. All type definitions have been consolidated into `@clerk/shared/types`.
|
|
9
|
+
|
|
10
|
+
Update your imports:
|
|
11
|
+
|
|
12
|
+
```diff
|
|
13
|
+
- import type { ClerkResource, UserResource } from '@clerk/types';
|
|
14
|
+
+ import type { ClerkResource, UserResource } from '@clerk/shared/types';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
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`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`Client.activeSessions` removed'
|
|
3
|
+
matcher: '\\.activeSessions'
|
|
4
|
+
category: 'deprecation-removal'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `activeSessions` property has been removed from the `Client` object. Use `sessions` instead:
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
- const sessions = client.activeSessions;
|
|
11
|
+
+ const sessions = client.sessions;
|
|
12
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`colorRing` and `colorModalBackdrop` now render at full opacity'
|
|
3
|
+
matcher:
|
|
4
|
+
- 'colorRing'
|
|
5
|
+
- 'colorModalBackdrop'
|
|
6
|
+
category: 'breaking'
|
|
7
|
+
warning: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
The `colorRing` and `colorModalBackdrop` CSS variables now render at full opacity when modified via the appearance prop or CSS variables. Previously, provided colors were rendered at 15% opacity.
|
|
11
|
+
|
|
12
|
+
If you were relying on the previous behavior, you may need to adjust your color values to include the desired opacity:
|
|
13
|
+
|
|
14
|
+
```diff
|
|
15
|
+
appearance={{
|
|
16
|
+
variables: {
|
|
17
|
+
- colorRing: '#6366f1',
|
|
18
|
+
+ colorRing: 'rgba(99, 102, 241, 0.15)',
|
|
19
|
+
}
|
|
20
|
+
}}
|
|
21
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Legacy redirect props removed'
|
|
3
|
+
matcher:
|
|
4
|
+
- 'afterSignInUrl'
|
|
5
|
+
- 'afterSignUpUrl'
|
|
6
|
+
- 'afterSignOutUrl'
|
|
7
|
+
- 'redirectUrl'
|
|
8
|
+
category: 'deprecation-removal'
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
The legacy redirect props `afterSignInUrl`, `afterSignUpUrl`, and `redirectUrl` have been removed from components. Use the newer redirect options:
|
|
12
|
+
|
|
13
|
+
```diff
|
|
14
|
+
<SignIn
|
|
15
|
+
- afterSignInUrl="/dashboard"
|
|
16
|
+
- afterSignUpUrl="/onboarding"
|
|
17
|
+
+ fallbackRedirectUrl="/dashboard"
|
|
18
|
+
+ signUpFallbackRedirectUrl="/onboarding"
|
|
19
|
+
/>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For forced redirects that ignore the `redirect_url` query parameter:
|
|
23
|
+
|
|
24
|
+
```diff
|
|
25
|
+
<SignIn
|
|
26
|
+
+ forceRedirectUrl="/dashboard"
|
|
27
|
+
+ signUpForceRedirectUrl="/onboarding"
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Experimental method prefix standardized to `__experimental_`'
|
|
3
|
+
matcher:
|
|
4
|
+
- 'experimental__'
|
|
5
|
+
- 'experimental_'
|
|
6
|
+
category: 'breaking'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
All experimental methods now use the `__experimental_` prefix consistently. Update any references:
|
|
10
|
+
|
|
11
|
+
```diff
|
|
12
|
+
- experimental__someMethod
|
|
13
|
+
+ __experimental_someMethod
|
|
14
|
+
|
|
15
|
+
- experimental_someMethod
|
|
16
|
+
+ __experimental_someMethod
|
|
17
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Minimum Expo version increased to 53'
|
|
3
|
+
packages: ['expo']
|
|
4
|
+
matcher: "expo\":\\s*\"(?:^|~|>|=|\\s)*(?:50|51|52)\\..*?"
|
|
5
|
+
matcherFlags: 'm'
|
|
6
|
+
category: 'version'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Support for Expo 50, 51, and 52 has been dropped. Update your project to Expo 53 or later:
|
|
10
|
+
|
|
11
|
+
```diff
|
|
12
|
+
{
|
|
13
|
+
"dependencies": {
|
|
14
|
+
- "expo": "~52.0.0",
|
|
15
|
+
+ "expo": "~53.0.0",
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run the Expo upgrade command:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx expo install expo@latest
|
|
24
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`hideSlug` prop removed'
|
|
3
|
+
matcher: 'hideSlug'
|
|
4
|
+
category: 'deprecation-removal'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `hideSlug` prop has been removed. Organization slugs are now managed through the Clerk Dashboard under Organization Settings.
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
<OrganizationProfile
|
|
11
|
+
- hideSlug={true}
|
|
12
|
+
/>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
To hide organization slugs, update your settings in the Clerk Dashboard → Organization Settings → Slug.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Upgrade Node.js to v20.9.0 or higher'
|
|
3
|
+
category: 'version'
|
|
4
|
+
matcher: "engines\":\\s*{[\\s\\S]*?\"node\":\\s*\"(?:^|~|>|=|\\s)*(?:14|16|18)\\..*?"
|
|
5
|
+
matcherFlags: 'm'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
All Clerk packages now require Node.js 20.9.0 or later. Update your Node.js version and ensure your `package.json` engines field reflects this requirement.
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
{
|
|
12
|
+
"engines": {
|
|
13
|
+
- "node": ">=18.0.0"
|
|
14
|
+
+ "node": ">=20.9.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Encryption key required when passing `secretKey` at runtime'
|
|
3
|
+
packages: ['nextjs']
|
|
4
|
+
matcher: 'clerkMiddleware\([\\s\\S]*?secretKey'
|
|
5
|
+
matcherFlags: 'm'
|
|
6
|
+
category: 'breaking'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
When passing `secretKey` as a runtime option to `clerkMiddleware()`, you must now also provide a `CLERK_ENCRYPTION_KEY` environment variable.
|
|
10
|
+
|
|
11
|
+
Add the encryption key to your environment:
|
|
12
|
+
|
|
13
|
+
```env
|
|
14
|
+
CLERK_ENCRYPTION_KEY=your-encryption-key
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
More information: https://clerk.com/docs/reference/nextjs/clerk-middleware#dynamic-keys
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`getAuth()` helper removed'
|
|
3
|
+
packages: ['nuxt']
|
|
4
|
+
matcher: 'getAuth\\('
|
|
5
|
+
category: 'deprecation-removal'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The deprecated `getAuth()` helper has been removed. Use `event.context.auth()` in your server routes instead:
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
- import { getAuth } from '@clerk/nuxt/server';
|
|
12
|
+
|
|
13
|
+
export default defineEventHandler((event) => {
|
|
14
|
+
- const { userId } = getAuth(event);
|
|
15
|
+
+ const { userId } = event.context.auth();
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
userId,
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Routing strategy now defaults to `path`'
|
|
3
|
+
packages: ['nuxt']
|
|
4
|
+
matcher:
|
|
5
|
+
- '<SignIn'
|
|
6
|
+
- '<SignUp'
|
|
7
|
+
- '<UserProfile'
|
|
8
|
+
- '<OrganizationProfile'
|
|
9
|
+
- '<CreateOrganization'
|
|
10
|
+
- '<OrganizationList'
|
|
11
|
+
category: 'behavior-change'
|
|
12
|
+
warning: true
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
The following components now default to `path` routing strategy instead of `hash`:
|
|
16
|
+
|
|
17
|
+
- `<SignIn />`
|
|
18
|
+
- `<SignUp />`
|
|
19
|
+
- `<UserProfile />`
|
|
20
|
+
- `<OrganizationProfile />`
|
|
21
|
+
- `<CreateOrganization />`
|
|
22
|
+
- `<OrganizationList />`
|
|
23
|
+
|
|
24
|
+
If you were relying on the previous `hash` routing behavior, explicitly set the routing prop:
|
|
25
|
+
|
|
26
|
+
```vue
|
|
27
|
+
<SignIn routing="hash" />
|
|
28
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`samlAccount` renamed to `enterpriseAccount`'
|
|
3
|
+
matcher: 'samlAccount'
|
|
4
|
+
category: 'deprecation-removal'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `samlAccount` property has been renamed to `enterpriseAccount`. Update your code:
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
- user.samlAccounts
|
|
11
|
+
+ user.enterpriseAccounts
|
|
12
|
+
|
|
13
|
+
- verification.samlAccount
|
|
14
|
+
+ verification.enterpriseAccount
|
|
15
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`saml` strategy renamed to `enterprise_sso`'
|
|
3
|
+
matcher: 'saml'
|
|
4
|
+
category: 'deprecation-removal'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `saml` authentication strategy has been renamed to `enterprise_sso`. Update any references in your code:
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
- strategy: 'saml'
|
|
11
|
+
+ strategy: 'enterprise_sso'
|
|
12
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`setActive({ beforeEmit })` changed to `setActive({ navigate })`'
|
|
3
|
+
matcher: 'beforeEmit'
|
|
4
|
+
category: 'deprecation-removal'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The `beforeEmit` callback in `setActive()` has been replaced with `navigate`. The callback signature has also changed:
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
await setActive({
|
|
11
|
+
session: sessionId,
|
|
12
|
+
- beforeEmit: () => {
|
|
13
|
+
- // Called before session is set
|
|
14
|
+
- }
|
|
15
|
+
+ navigate: ({ session }) => {
|
|
16
|
+
+ // Called with the session object
|
|
17
|
+
+ return '/dashboard';
|
|
18
|
+
+ }
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `navigate` callback receives an object with the `session` property and should return the URL to navigate to.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`showOptionalFields` now defaults to `false`'
|
|
3
|
+
matcher: 'showOptionalFields'
|
|
4
|
+
category: 'behavior-change'
|
|
5
|
+
warning: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The default value of `appearance.layout.showOptionalFields` (now `appearance.options.showOptionalFields`) has changed from `true` to `false`. Optional fields are now hidden by default during sign up.
|
|
9
|
+
|
|
10
|
+
To restore the previous behavior, explicitly set the option:
|
|
11
|
+
|
|
12
|
+
```jsx
|
|
13
|
+
<ClerkProvider
|
|
14
|
+
appearance={{
|
|
15
|
+
options: {
|
|
16
|
+
showOptionalFields: true,
|
|
17
|
+
}
|
|
18
|
+
}}
|
|
19
|
+
>
|
|
20
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'UI themes moved to `@clerk/ui/themes/experimental`'
|
|
3
|
+
matcher:
|
|
4
|
+
- '__experimental_createTheme'
|
|
5
|
+
- 'experimental_createTheme'
|
|
6
|
+
category: 'breaking'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
The `createTheme` theme utility has been moved to a new export path. Update your imports:
|
|
10
|
+
|
|
11
|
+
```diff
|
|
12
|
+
- import { __experimental_createTheme } from '@clerk/ui';
|
|
13
|
+
+ import { createTheme } from '@clerk/ui/themes/experimental';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Note: The `__experimental_` prefix has been removed from the method since they're now in the `/themes/experimental` subpath.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`__unstable_*` methods renamed to `__internal_*`'
|
|
3
|
+
matcher: '__unstable_'
|
|
4
|
+
category: 'breaking'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
All `__unstable_*` methods have been renamed to `__internal_*`. These are internal APIs not intended for public use.
|
|
8
|
+
|
|
9
|
+
### @clerk/clerk-js
|
|
10
|
+
|
|
11
|
+
- `__unstable__environment` → `__internal_environment`
|
|
12
|
+
- `__unstable__updateProps` → `__internal_updateProps`
|
|
13
|
+
- `__unstable__setEnvironment` → `__internal_setEnvironment`
|
|
14
|
+
- `__unstable__onBeforeRequest` → `__internal_onBeforeRequest`
|
|
15
|
+
- `__unstable__onAfterResponse` → `__internal_onAfterResponse`
|
|
16
|
+
- `__unstable__onBeforeSetActive` → `__internal_onBeforeSetActive`
|
|
17
|
+
- `__unstable__onAfterSetActive` → `__internal_onAfterSetActive`
|
|
18
|
+
|
|
19
|
+
### @clerk/nextjs
|
|
20
|
+
|
|
21
|
+
- `__unstable_invokeMiddlewareOnAuthStateChange` → `__internal_invokeMiddlewareOnAuthStateChange`
|
|
22
|
+
|
|
23
|
+
### @clerk/chrome-extension
|
|
24
|
+
|
|
25
|
+
- `__unstable__createClerkClient` → `createClerkClient` (exported from `@clerk/chrome-extension/background`)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`UserSettings.saml` renamed to `enterpriseSSO`'
|
|
3
|
+
matcher: 'UserSettings[\\s\\S]*?saml'
|
|
4
|
+
matcherFlags: 'm'
|
|
5
|
+
category: 'deprecation-removal'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The `saml` property on `UserSettings` has been renamed to `enterpriseSSO`:
|
|
9
|
+
|
|
10
|
+
```diff
|
|
11
|
+
- userSettings.saml
|
|
12
|
+
+ userSettings.enterpriseSSO
|
|
13
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`UserButton` sign-out redirect props removed'
|
|
3
|
+
matcher: '<UserButton[\\s\\S]*?(afterSignOutUrl|signOutUrl)[\\s\\S]*?>'
|
|
4
|
+
matcherFlags: 'm'
|
|
5
|
+
category: 'deprecation-removal'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The `UserButton` component no longer accepts sign-out redirect override props. Configure sign-out redirects using one of these methods:
|
|
9
|
+
|
|
10
|
+
**Global configuration:**
|
|
11
|
+
|
|
12
|
+
```jsx
|
|
13
|
+
<ClerkProvider afterSignOutUrl="/signed-out">
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Per-button with SignOutButton:**
|
|
17
|
+
|
|
18
|
+
```jsx
|
|
19
|
+
<SignOutButton redirectUrl='/goodbye'>Sign Out</SignOutButton>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Programmatic:**
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
clerk.signOut({ redirectUrl: '/signed-out' });
|
|
26
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clerk/upgrade",
|
|
3
|
-
"version": "2.0.0-snapshot.
|
|
3
|
+
"version": "2.0.0-snapshot.v20251215203425",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/clerk/javascript.git",
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"chalk": "^5.3.0",
|
|
25
25
|
"ejs": "3.1.10",
|
|
26
26
|
"execa": "9.4.1",
|
|
27
|
-
"globby": "^14.0.1",
|
|
28
27
|
"gray-matter": "^4.0.3",
|
|
29
28
|
"index-to-position": "^0.1.2",
|
|
30
29
|
"jscodeshift": "^17.0.0",
|
|
@@ -32,7 +31,8 @@
|
|
|
32
31
|
"meow": "^11.0.0",
|
|
33
32
|
"read-pkg": "^9.0.1",
|
|
34
33
|
"semver-regex": "^4.0.5",
|
|
35
|
-
"temp-dir": "^3.0.0"
|
|
34
|
+
"temp-dir": "^3.0.0",
|
|
35
|
+
"tinyglobby": "^0.2.15"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@babel/cli": "^7.24.7",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"dev": "babel --keep-file-extension --out-dir=dist --watch src --copy-files",
|
|
52
52
|
"format": "node ../../scripts/format-package.mjs",
|
|
53
53
|
"format:check": "node ../../scripts/format-package.mjs --check",
|
|
54
|
+
"generate-guide": "node scripts/generate-guide.js",
|
|
54
55
|
"lint": "eslint src/",
|
|
55
56
|
"lint:publint": "publint",
|
|
56
57
|
"test": "vitest run",
|