@clerk/upgrade 2.0.0-snapshot.v20251204143242 → 2.0.0-snapshot.v20251208202852

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 (54) hide show
  1. package/dist/__tests__/fixtures/expo-old-package/package-lock.json +5 -0
  2. package/dist/__tests__/fixtures/expo-old-package/package.json +10 -0
  3. package/dist/__tests__/fixtures/expo-old-package/src/App.tsx +14 -0
  4. package/dist/__tests__/fixtures/nextjs-v6/package.json +9 -0
  5. package/dist/__tests__/fixtures/nextjs-v6/pnpm-lock.yaml +2 -0
  6. package/dist/__tests__/fixtures/nextjs-v6/src/app.tsx +17 -0
  7. package/dist/__tests__/fixtures/nextjs-v7/package.json +9 -0
  8. package/dist/__tests__/fixtures/nextjs-v7/pnpm-lock.yaml +2 -0
  9. package/dist/__tests__/fixtures/nextjs-v7/src/app.tsx +16 -0
  10. package/dist/__tests__/fixtures/no-clerk/package.json +7 -0
  11. package/dist/__tests__/fixtures/react-v6/package.json +8 -0
  12. package/dist/__tests__/fixtures/react-v6/src/App.tsx +19 -0
  13. package/dist/__tests__/fixtures/react-v6/yarn.lock +2 -0
  14. package/dist/__tests__/helpers/create-fixture.js +56 -0
  15. package/dist/__tests__/integration/cli.test.js +230 -0
  16. package/dist/__tests__/integration/config.test.js +76 -0
  17. package/dist/__tests__/integration/detect-sdk.test.js +100 -0
  18. package/dist/__tests__/integration/runner.test.js +79 -0
  19. package/dist/cli.js +159 -45
  20. package/dist/codemods/__tests__/__fixtures__/transform-align-experimental-unstable-prefixes.fixtures.js +68 -0
  21. package/dist/codemods/__tests__/__fixtures__/transform-appearance-layout-to-options.fixtures.js +9 -0
  22. package/dist/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js +13 -0
  23. package/dist/codemods/__tests__/__fixtures__/transform-remove-deprecated-appearance-props.fixtures.js +63 -0
  24. package/dist/codemods/__tests__/__fixtures__/transform-themes-to-ui-themes.fixtures.js +41 -0
  25. package/dist/codemods/__tests__/transform-align-experimental-unstable-prefixes.test.js +15 -0
  26. package/dist/codemods/__tests__/transform-appearance-layout-to-options.test.js +15 -0
  27. package/dist/codemods/__tests__/transform-remove-deprecated-appearance-props.test.js +15 -0
  28. package/dist/codemods/__tests__/transform-themes-to-ui-themes.test.js +15 -0
  29. package/dist/codemods/index.js +67 -13
  30. package/dist/codemods/transform-align-experimental-unstable-prefixes.cjs +400 -0
  31. package/dist/codemods/transform-appearance-layout-to-options.cjs +65 -0
  32. package/dist/codemods/transform-clerk-react-v6.cjs +15 -7
  33. package/dist/codemods/transform-remove-deprecated-appearance-props.cjs +109 -0
  34. package/dist/codemods/transform-remove-deprecated-props.cjs +12 -12
  35. package/dist/codemods/transform-themes-to-ui-themes.cjs +65 -0
  36. package/dist/config.js +122 -0
  37. package/dist/render.js +164 -0
  38. package/dist/runner.js +98 -0
  39. package/dist/util/detect-sdk.js +125 -0
  40. package/dist/util/package-manager.js +94 -0
  41. package/dist/versions/core-3/changes/clerk-expo-package-rename.md +23 -0
  42. package/dist/versions/core-3/changes/clerk-react-package-rename.md +23 -0
  43. package/dist/versions/core-3/index.js +40 -0
  44. package/package.json +2 -8
  45. package/dist/app.js +0 -177
  46. package/dist/components/Codemod.js +0 -97
  47. package/dist/components/Command.js +0 -56
  48. package/dist/components/Header.js +0 -11
  49. package/dist/components/SDKWorkflow.js +0 -278
  50. package/dist/components/Scan.js +0 -180
  51. package/dist/components/UpgradeSDK.js +0 -116
  52. package/dist/util/expandable-list.js +0 -173
  53. package/dist/util/get-clerk-version.js +0 -22
  54. package/dist/util/guess-framework.js +0 -69
@@ -0,0 +1,79 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { loadConfig } from '../../config.js';
3
+ import { runScans } from '../../runner.js';
4
+ import { createTempFixture } from '../helpers/create-fixture.js';
5
+ vi.mock('../../render.js', () => ({
6
+ colors: {
7
+ reset: '',
8
+ bold: '',
9
+ yellow: '',
10
+ gray: ''
11
+ },
12
+ createSpinner: vi.fn(() => ({
13
+ update: vi.fn(),
14
+ stop: vi.fn(),
15
+ success: vi.fn(),
16
+ error: vi.fn()
17
+ })),
18
+ promptText: vi.fn((msg, defaultValue) => defaultValue),
19
+ renderCodemodResults: vi.fn(),
20
+ renderText: vi.fn()
21
+ }));
22
+ describe('runScans', () => {
23
+ let fixture;
24
+ beforeEach(() => {
25
+ fixture = createTempFixture('nextjs-v6');
26
+ });
27
+ afterEach(() => {
28
+ fixture?.cleanup();
29
+ });
30
+ it('finds patterns in fixture files', async () => {
31
+ const config = await loadConfig('nextjs', 6);
32
+ const options = {
33
+ dir: fixture.path,
34
+ ignore: []
35
+ };
36
+ const results = await runScans(config, 'nextjs', options);
37
+ expect(Array.isArray(results)).toBe(true);
38
+ });
39
+ it('returns empty array when no matchers match', async () => {
40
+ const config = await loadConfig('nextjs', 6);
41
+ config.changes = [];
42
+ const options = {
43
+ dir: fixture.path,
44
+ ignore: []
45
+ };
46
+ const results = await runScans(config, 'nextjs', options);
47
+ expect(results).toEqual([]);
48
+ });
49
+ it('respects ignore patterns', async () => {
50
+ const config = await loadConfig('nextjs', 6);
51
+ const options = {
52
+ dir: fixture.path,
53
+ ignore: ['**/src/**']
54
+ };
55
+ const results = await runScans(config, 'nextjs', options);
56
+ expect(Array.isArray(results)).toBe(true);
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
+ }
78
+ });
79
+ });
package/dist/cli.js CHANGED
@@ -1,68 +1,182 @@
1
1
  #!/usr/bin/env node
2
- import { render } from 'ink';
3
2
  import meow from 'meow';
4
- import React from 'react';
5
- import App from './app.js';
6
- import sdks from './constants/sdks.js';
3
+ import { getOldPackageName, getTargetPackageName, loadConfig } from './config.js';
4
+ import { createSpinner, promptConfirm, promptSelect, renderComplete, renderConfig, renderError, renderHeader, renderNewline, renderScanResults, renderSuccess, renderText, renderWarning } from './render.js';
5
+ import { runCodemods, runScans } from './runner.js';
6
+ import { detectSdk, getSdkVersion, getSupportedSdks, normalizeSdkName } from './util/detect-sdk.js';
7
+ import { detectPackageManager, getPackageManagerDisplayName, removePackage, upgradePackage } from './util/package-manager.js';
8
+ const isInteractive = process.stdin.isTTY;
7
9
  const cli = meow(`
8
10
  Usage
9
- $ clerk-upgrade
11
+ $ npx @clerk/upgrade
10
12
 
11
13
  Options
12
- --from Major version number you're upgrading from
13
- --to Major version number you're upgrading to
14
- --sdk Name of the SDK you're upgrading
15
- --dir Directory you'd like to scan for files
16
- --ignore Any files or directories you'd like to ignore
17
- --noWarnings Do not print warnings, only items that must be fixed
18
- --disableTelemetry Do not send anonymous usage telemetry
14
+ --sdk Name of the SDK you're upgrading (e.g., nextjs, react)
15
+ --dir Directory to scan (defaults to current directory)
16
+ --glob Glob pattern for files to transform (defaults to **/*.{js,jsx,ts,tsx,mjs,cjs})
17
+ --ignore Directories/files to ignore (can be used multiple times)
18
+ --dry-run Show what would be done without making changes
19
19
 
20
20
  Examples
21
- $ clerk-upgrade --sdk=nextjs --dir=src/**
22
- $ clerk-upgrade --ignore=**/public/** --ignore=**/dist/**
23
- $ clerk-upgrade --from=core-1 --to=core-2
24
- `, {
21
+ $ npx @clerk/upgrade
22
+ $ npx @clerk/upgrade --sdk=nextjs
23
+ $ npx @clerk/upgrade --dir=./src --ignore=**/test/**
24
+ $ npx @clerk/upgrade --dry-run
25
+
26
+ Non-interactive mode:
27
+ When running in CI or piped environments, --sdk is required if it cannot be auto-detected.
28
+ `, {
25
29
  importMeta: import.meta,
26
30
  flags: {
27
- from: {
28
- type: 'string'
29
- },
30
- to: {
31
+ sdk: {
31
32
  type: 'string'
32
33
  },
33
- sdk: {
34
+ dir: {
34
35
  type: 'string',
35
- choices: sdks.map(i => i.value)
36
+ default: process.cwd()
36
37
  },
37
- dir: {
38
- type: 'string'
38
+ glob: {
39
+ type: 'string',
40
+ default: '**/*.(js|jsx|ts|tsx|mjs|cjs)'
39
41
  },
40
42
  ignore: {
41
43
  type: 'string',
42
44
  isMultiple: true
43
45
  },
44
- yolo: {
45
- type: 'boolean'
46
+ dryRun: {
47
+ type: 'boolean',
48
+ default: false
46
49
  },
47
- noWarnings: {
48
- type: 'boolean'
49
- },
50
- disableTelemetry: {
51
- type: 'boolean'
50
+ skipCodemods: {
51
+ type: 'boolean',
52
+ default: false
52
53
  }
53
54
  }
54
55
  });
55
- render(/*#__PURE__*/React.createElement(App, {
56
- dir: cli.flags.dir,
57
- disableTelemetry: cli.flags.disableTelemetry,
58
- fromVersion: cli.flags.from,
59
- ignore: cli.flags.ignore,
60
- noWarnings: cli.flags.noWarnings,
61
- packageManager: cli.flags.packageManager,
62
- sdk: cli.flags.sdk,
63
- toVersion: cli.flags.to,
64
- yolo: cli.flags.yolo
65
- })
66
- // if having issues with errors being swallowed, uncomment this
67
- // { debug: true },
68
- );
56
+ async function main() {
57
+ renderHeader();
58
+ const options = {
59
+ dir: cli.flags.dir,
60
+ glob: cli.flags.glob,
61
+ ignore: cli.flags.ignore,
62
+ dryRun: cli.flags.dryRun,
63
+ skipCodemods: cli.flags.skipCodemods
64
+ };
65
+ if (options.dryRun) {
66
+ renderWarning(' Upgrade running in dry run mode - no changes will be made');
67
+ renderNewline();
68
+ }
69
+
70
+ // Step 1: Detect or prompt for SDK
71
+ let sdk = normalizeSdkName(cli.flags.sdk);
72
+ if (!sdk) {
73
+ sdk = detectSdk(options.dir);
74
+ }
75
+ if (!sdk) {
76
+ if (!isInteractive) {
77
+ renderError('Could not detect Clerk SDK. Please provide --sdk flag in non-interactive mode.');
78
+ renderText('Supported SDKs: ' + getSupportedSdks().map(s => s.value).join(', '));
79
+ process.exit(1);
80
+ }
81
+ const sdkOptions = getSupportedSdks().map(s => ({
82
+ label: s.label,
83
+ value: s.value
84
+ }));
85
+ sdk = await promptSelect('Could not detect Clerk SDK. Please select which SDK you are upgrading:', sdkOptions);
86
+ }
87
+ if (!sdk) {
88
+ renderError('No SDK selected. Exiting.');
89
+ process.exit(1);
90
+ }
91
+
92
+ // Step 2: Get current version and detect package manager
93
+ const currentVersion = getSdkVersion(sdk, options.dir);
94
+ const packageManager = detectPackageManager(options.dir);
95
+
96
+ // Step 3: Load version config
97
+ const config = await loadConfig(sdk, currentVersion);
98
+ if (!config) {
99
+ renderError(`No upgrade path found for @clerk/${sdk}. Your version may be too old for this upgrade tool.`);
100
+ process.exit(1);
101
+ }
102
+
103
+ // Step 4: Display configuration
104
+ renderConfig({
105
+ sdk,
106
+ currentVersion,
107
+ fromVersion: config.sdkVersions?.[sdk]?.from,
108
+ toVersion: config.sdkVersions?.[sdk]?.to,
109
+ versionName: config.name,
110
+ dir: options.dir,
111
+ packageManager: getPackageManagerDisplayName(packageManager)
112
+ });
113
+ if (isInteractive && !(await promptConfirm('Ready to upgrade?'))) {
114
+ renderError('Upgrade cancelled. Exiting...');
115
+ process.exit(0);
116
+ }
117
+ console.log('');
118
+
119
+ // Step 5: Handle upgrade status
120
+ if (config.alreadyUpgraded) {
121
+ renderSuccess(`You're already on the latest major version of @clerk/${sdk}`);
122
+ } else if (config.needsUpgrade) {
123
+ await performUpgrade(sdk, packageManager, config, options);
124
+ }
125
+
126
+ // Step 6: Run codemods
127
+ if (config.codemods?.length > 0) {
128
+ renderText(`Running ${config.codemods.length} codemod(s)...`, 'blue');
129
+ await runCodemods(config, sdk, options);
130
+ renderSuccess('All codemods applied');
131
+ renderNewline();
132
+ }
133
+
134
+ // Step 7: Run scans
135
+ if (config.changes?.length > 0) {
136
+ renderText('Scanning for additional breaking changes...', 'blue');
137
+ const results = await runScans(config, sdk, options);
138
+ renderScanResults(results, config.docsUrl);
139
+ }
140
+
141
+ // Step 8: Done
142
+ renderComplete(sdk, config.docsUrl);
143
+ }
144
+ async function performUpgrade(sdk, packageManager, config, options) {
145
+ const targetPackage = getTargetPackageName(sdk);
146
+ const oldPackage = getOldPackageName(sdk);
147
+ const targetVersion = config.sdkVersions?.[sdk]?.to;
148
+ if (options.dryRun) {
149
+ renderText(`[dry run] Would upgrade ${targetPackage} to version ${targetVersion}`, 'yellow');
150
+ if (oldPackage) {
151
+ renderText(`[dry run] Would remove old package ${oldPackage}`, 'yellow');
152
+ }
153
+ renderNewline();
154
+ return;
155
+ }
156
+
157
+ // Remove old package if this is a rename (clerk-react -> react, clerk-expo -> expo)
158
+ if (oldPackage) {
159
+ const removeSpinner = createSpinner(`Removing ${oldPackage}...`);
160
+ try {
161
+ await removePackage(packageManager, oldPackage, options.dir);
162
+ removeSpinner.success(`Removed ${oldPackage}`);
163
+ } catch {
164
+ removeSpinner.error(`Failed to remove ${oldPackage}`);
165
+ }
166
+ }
167
+
168
+ // Upgrade to the new version
169
+ const spinner = createSpinner(`Upgrading ${targetPackage} to version ${targetVersion}...`);
170
+ try {
171
+ await upgradePackage(packageManager, targetPackage, targetVersion, options.dir);
172
+ spinner.success(`Upgraded ${targetPackage} to version ${targetVersion}`);
173
+ } catch (error) {
174
+ spinner.error(`Failed to upgrade ${targetPackage}`);
175
+ renderError(error.message);
176
+ process.exit(1);
177
+ }
178
+ }
179
+ main().catch(error => {
180
+ renderError(error.message);
181
+ process.exit(1);
182
+ });
@@ -0,0 +1,68 @@
1
+ export const fixtures = [{
2
+ name: 'Renames unstable hooks and handlers to internal',
3
+ source: `
4
+ const clerk = useClerk();
5
+ clerk.__unstable__updateProps({});
6
+ window.__unstable__onAfterSetActive = () => {};
7
+ const opts = { __unstable_invokeMiddlewareOnAuthStateChange: true };
8
+ const handler = client['__unstable__onAfterResponse'];
9
+ `,
10
+ output: `
11
+ const clerk = useClerk();
12
+ clerk.__internal_updateProps({});
13
+ window.__internal_onAfterSetActive = () => {};
14
+ const opts = { __internal_invokeMiddlewareOnAuthStateChange: true };
15
+ const handler = client["__internal_onAfterResponse"];
16
+ `
17
+ }, {
18
+ name: 'Moves UI theme helpers to experimental path and renames identifiers',
19
+ source: `
20
+ import { __experimental_createTheme, experimental__simple, Button } from '@clerk/ui';
21
+
22
+ const theme = __experimental_createTheme();
23
+ const kind = experimental__simple;
24
+ `,
25
+ output: `
26
+ import { Button } from '@clerk/ui';
27
+
28
+ import { createTheme, simple } from "@clerk/ui/themes/experimental";
29
+
30
+ const theme = createTheme();
31
+ const kind = simple;
32
+ `
33
+ }, {
34
+ name: 'Moves UI theme helpers required from root to experimental path',
35
+ source: `
36
+ const { __experimental_createTheme, experimental__simple, Card } = require('@clerk/ui');
37
+ `,
38
+ output: `
39
+ const {
40
+ Card
41
+ } = require('@clerk/ui');
42
+
43
+ const {
44
+ createTheme,
45
+ simple
46
+ } = require("@clerk/ui/themes/experimental");
47
+ `
48
+ }, {
49
+ name: 'Moves chrome extension client creation to background path',
50
+ source: `
51
+ import { __unstable__createClerkClient } from '@clerk/chrome-extension';
52
+
53
+ __unstable__createClerkClient();
54
+ `,
55
+ output: `
56
+ import { createClerkClient } from "@clerk/chrome-extension/background";
57
+
58
+ createClerkClient();
59
+ `
60
+ }, {
61
+ name: 'Removes deprecated billing props from JSX',
62
+ source: `
63
+ <OrganizationProfile __unstable_manageBillingUrl="url" experimental__forceOauthFirst />;
64
+ `,
65
+ output: `
66
+ <OrganizationProfile />;
67
+ `
68
+ }];
@@ -0,0 +1,9 @@
1
+ export const fixtures = [{
2
+ name: 'Renames layout inside JSX appearance prop',
3
+ source: `
4
+ <SignIn appearance={{ layout: { socialButtonsPlacement: 'top' } }} />
5
+ `,
6
+ output: `
7
+ <SignIn appearance={{ options: { socialButtonsPlacement: 'top' } }} />
8
+ `
9
+ }];
@@ -104,4 +104,17 @@ const clerk = require("@clerk/clerk-react")
104
104
  output: `
105
105
  const clerk = require("@clerk/react")
106
106
  `
107
+ }, {
108
+ name: 'Handles directives with mixed legacy imports without double semicolons',
109
+ source: `"use client";
110
+
111
+ import { ClerkProvider, useSignIn, useSignUp } from "@clerk/nextjs";
112
+
113
+ export const dynamic = "force-dynamic";
114
+ `,
115
+ output: `"use client";
116
+ import { ClerkProvider } from "@clerk/nextjs";
117
+ import { useSignIn, useSignUp } from "@clerk/nextjs/legacy";
118
+
119
+ export const dynamic = "force-dynamic";`
107
120
  }];
@@ -0,0 +1,63 @@
1
+ export const fixtures = [{
2
+ name: 'Renames baseTheme to theme in JSX appearance',
3
+ source: `
4
+ <SignIn appearance={{ baseTheme: dark }} />
5
+ `,
6
+ output: `
7
+ <SignIn appearance={{ theme: dark }} />
8
+ `
9
+ }, {
10
+ name: 'Renames baseTheme and variable keys when appearance object is referenced',
11
+ source: `
12
+ const appearance = {
13
+ baseTheme: [dark, light],
14
+ variables: {
15
+ colorText: '#000',
16
+ colorTextSecondary: '#111',
17
+ colorInputText: '#222',
18
+ colorInputBackground: '#333',
19
+ colorTextOnPrimaryBackground: '#444',
20
+ spacingUnit: '1rem',
21
+ },
22
+ };
23
+
24
+ <SignUp appearance={appearance} />
25
+ `,
26
+ output: `
27
+ const appearance = {
28
+ theme: [dark, light],
29
+ variables: {
30
+ colorForeground: '#000',
31
+ colorMutedForeground: '#111',
32
+ colorInputForeground: '#222',
33
+ colorInput: '#333',
34
+ colorPrimaryForeground: '#444',
35
+ spacing: '1rem',
36
+ },
37
+ };
38
+
39
+ <SignUp appearance={appearance} />
40
+ `
41
+ }, {
42
+ name: 'Handles string literal keys',
43
+ source: `
44
+ const appearance = {
45
+ 'baseTheme': dark,
46
+ variables: {
47
+ 'colorText': '#000',
48
+ },
49
+ };
50
+
51
+ <SignIn appearance={appearance} />
52
+ `,
53
+ output: `
54
+ const appearance = {
55
+ "theme": dark,
56
+ variables: {
57
+ "colorForeground": '#000',
58
+ },
59
+ };
60
+
61
+ <SignIn appearance={appearance} />
62
+ `
63
+ }];
@@ -0,0 +1,41 @@
1
+ export const fixtures = [{
2
+ name: 'Renames root import',
3
+ source: `
4
+ import { dark, light } from '@clerk/themes';
5
+ `,
6
+ output: `
7
+ import { dark, light } from "@clerk/ui/themes";
8
+ `
9
+ }, {
10
+ name: 'Renames subpath import',
11
+ source: `
12
+ import palette from '@clerk/themes/palette';
13
+ `,
14
+ output: `
15
+ import palette from "@clerk/ui/themes/palette";
16
+ `
17
+ }, {
18
+ name: 'Renames require call',
19
+ source: `
20
+ const themes = require('@clerk/themes');
21
+ `,
22
+ output: `
23
+ const themes = require("@clerk/ui/themes");
24
+ `
25
+ }, {
26
+ name: 'Renames dynamic import',
27
+ source: `
28
+ const mod = await import('@clerk/themes/foo');
29
+ `,
30
+ output: `
31
+ const mod = await import("@clerk/ui/themes/foo");
32
+ `
33
+ }, {
34
+ name: 'Renames export source',
35
+ source: `
36
+ export * from '@clerk/themes';
37
+ `,
38
+ output: `
39
+ export * from "@clerk/ui/themes";
40
+ `
41
+ }];
@@ -0,0 +1,15 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-align-experimental-unstable-prefixes.cjs';
4
+ import { fixtures } from './__fixtures__/transform-align-experimental-unstable-prefixes.fixtures';
5
+ describe('transform-align-experimental-unstable-prefixes', () => {
6
+ it.each(fixtures)('$name', ({
7
+ source,
8
+ output
9
+ }) => {
10
+ const result = applyTransform(transformer, {}, {
11
+ source
12
+ });
13
+ expect(result).toEqual(output.trim());
14
+ });
15
+ });
@@ -0,0 +1,15 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-appearance-layout-to-options.cjs';
4
+ import { fixtures } from './__fixtures__/transform-appearance-layout-to-options.fixtures';
5
+ describe('transform-appearance-layout-to-options', () => {
6
+ it.each(fixtures)('$name', ({
7
+ source,
8
+ output
9
+ }) => {
10
+ const result = applyTransform(transformer, {}, {
11
+ source
12
+ });
13
+ expect(result).toEqual(output.trim());
14
+ });
15
+ });
@@ -0,0 +1,15 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-remove-deprecated-appearance-props.cjs';
4
+ import { fixtures } from './__fixtures__/transform-remove-deprecated-appearance-props.fixtures';
5
+ describe('transform-remove-deprecated-appearance-props', () => {
6
+ it.each(fixtures)('$name', ({
7
+ source,
8
+ output
9
+ }) => {
10
+ const result = applyTransform(transformer, {}, {
11
+ source
12
+ });
13
+ expect(result).toEqual(output.trim());
14
+ });
15
+ });
@@ -0,0 +1,15 @@
1
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import transformer from '../transform-themes-to-ui-themes.cjs';
4
+ import { fixtures } from './__fixtures__/transform-themes-to-ui-themes.fixtures';
5
+ describe('transform-themes-to-ui-themes', () => {
6
+ it.each(fixtures)('$name', ({
7
+ source,
8
+ output
9
+ }) => {
10
+ const result = applyTransform(transformer, {}, {
11
+ source
12
+ });
13
+ expect(result).toEqual(output.trim());
14
+ });
15
+ });
@@ -1,32 +1,86 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
+ import chalk from 'chalk';
3
4
  import { globby } from 'globby';
4
5
  import { run } from 'jscodeshift/src/Runner.js';
5
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const CODEMOD_CONFIG = {
8
+ 'transform-remove-deprecated-props': {
9
+ renderSummary: renderDeprecatedPropsSummary
10
+ }
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', '**/*.(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)+'];
6
13
  export async function runCodemod(transform = 'transform-async-request', glob, options = {}) {
7
14
  if (!transform) {
8
15
  throw new Error('No transform provided');
9
16
  }
10
17
  const resolvedPath = resolve(__dirname, `${transform}.cjs`);
11
18
  const paths = await globby(glob, {
12
- 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)+',
13
- // common image files
14
- '**/*.(mp4|mkv|wmv|m4v|mov|avi|flv|webm|flac|mka|m4a|aac|ogg)+',
15
- // common video files] }).then(files => {
16
- '**/*.(css|scss|sass|less|styl)+' // common style files
17
- ]
19
+ ignore: GLOBBY_IGNORE
18
20
  });
19
- const clerkUpgradeStats = options.clerkUpgradeStats ?? {};
20
- const result = await run(resolvedPath, paths ?? [], {
21
- dry: false,
21
+ if (options.skipCodemods) {
22
+ return {
23
+ stats: {
24
+ total: 0,
25
+ modified: 0,
26
+ deleted: 0
27
+ }
28
+ };
29
+ }
30
+
31
+ // First pass: dry run to collect stats (jscodeshift only reports stats in dry mode)
32
+ const dryResult = await run(resolvedPath, paths ?? [], {
22
33
  ...options,
23
- // expose a mutable stats bag so individual transforms can record structured information
24
- clerkUpgradeStats,
25
- // we must silence stdout to prevent output from interfering with ink CLI
34
+ dry: true,
26
35
  silent: true
27
36
  });
37
+ let result = {};
38
+ if (!options.dryRun) {
39
+ // Second pass: apply the changes
40
+ result = await run(resolvedPath, paths ?? [], {
41
+ ...options,
42
+ dry: false,
43
+ silent: true
44
+ });
45
+ }
46
+ if (options.dry) {
47
+ return dryResult;
48
+ }
49
+
50
+ // Merge stats from dry run into final result
28
51
  return {
29
52
  ...result,
30
- clerkUpgradeStats
53
+ stats: dryResult.stats
31
54
  };
55
+ }
56
+ export function getCodemodConfig(transform) {
57
+ return CODEMOD_CONFIG[transform] || null;
58
+ }
59
+ function renderDeprecatedPropsSummary(stats) {
60
+ if (!stats) {
61
+ return;
62
+ }
63
+ const userButtonCount = stats.userbuttonAfterSignOutPropsRemoved || 0;
64
+ const hideSlugCount = stats.hideSlugRemoved || 0;
65
+ const beforeEmitCount = stats.beforeEmitTransformed || 0;
66
+ if (!userButtonCount && !hideSlugCount && !beforeEmitCount) {
67
+ return;
68
+ }
69
+ console.log(chalk.yellow.bold('Manual intervention may be required:'));
70
+ if (userButtonCount > 0) {
71
+ console.log(chalk.yellow(`• Removed ${userButtonCount} UserButton sign-out redirect prop(s)`));
72
+ console.log(chalk.gray(' To configure sign-out redirects:'));
73
+ console.log(chalk.gray(' - Global: Add afterSignOutUrl to <ClerkProvider>'));
74
+ console.log(chalk.gray(' - Per-button: Use <SignOutButton redirectUrl="...">'));
75
+ console.log(chalk.gray(' - Programmatic: clerk.signOut({ redirectUrl: "..." })'));
76
+ }
77
+ if (hideSlugCount > 0) {
78
+ console.log(chalk.yellow(`• Removed ${hideSlugCount} hideSlug prop(s)`));
79
+ console.log(chalk.gray(' Slugs are now managed in the Clerk Dashboard.'));
80
+ }
81
+ if (beforeEmitCount > 0) {
82
+ console.log(chalk.yellow(`• Transformed ${beforeEmitCount} setActive({ beforeEmit }) → setActive({ navigate })`));
83
+ console.log(chalk.gray(' The callback now receives an object with session property.'));
84
+ }
85
+ console.log('');
32
86
  }