@clerk/upgrade 2.0.0-snapshot.v20251204175016 → 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 +11 -32
  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 -149
  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
@@ -20,14 +20,13 @@ const COMPONENT_REDIRECT_ATTR = new Map([['ClerkProvider', {
20
20
  const COMPONENTS_WITH_USER_BUTTON_REMOVALS = new Map([['UserButton', ['afterSignOutUrl', 'afterMultiSessionSingleSignOutUrl']]]);
21
21
  const ORGANIZATION_SWITCHER_RENAMES = new Map([['afterSwitchOrganizationUrl', 'afterSelectOrganizationUrl']]);
22
22
  module.exports = function transformDeprecatedProps({
23
- source,
24
- path: filePath
23
+ source
25
24
  }, {
26
- jscodeshift: j
27
- }, options = {}) {
25
+ jscodeshift: j,
26
+ stats
27
+ }) {
28
28
  const root = j(source);
29
29
  let dirty = false;
30
- const stats = options.clerkUpgradeStats;
31
30
  const {
32
31
  namedImports,
33
32
  namespaceImports
@@ -41,29 +40,15 @@ module.exports = function transformDeprecatedProps({
41
40
  if (COMPONENTS_WITH_HIDE_SLUG.has(canonicalName)) {
42
41
  if (removeJsxAttribute(j, jsxNode, 'hideSlug')) {
43
42
  dirty = true;
44
- if (stats) {
45
- stats.hideSlugRemoved = (stats.hideSlugRemoved || 0) + 1;
46
- stats.hideSlugFiles = stats.hideSlugFiles || [];
47
- if (!stats.hideSlugFiles.includes(filePath)) {
48
- stats.hideSlugFiles.push(filePath);
49
- }
50
- }
43
+ stats('hideSlugRemoved');
51
44
  }
52
45
  }
53
46
  if (COMPONENTS_WITH_USER_BUTTON_REMOVALS.has(canonicalName)) {
54
47
  const propsToRemove = COMPONENTS_WITH_USER_BUTTON_REMOVALS.get(canonicalName);
55
- let removedCount = 0;
56
48
  for (const attrName of propsToRemove) {
57
49
  if (removeJsxAttribute(j, jsxNode, attrName)) {
58
50
  dirty = true;
59
- removedCount += 1;
60
- }
61
- }
62
- if (removedCount > 0 && stats) {
63
- stats.userbuttonAfterSignOutPropsRemoved = (stats.userbuttonAfterSignOutPropsRemoved || 0) + removedCount;
64
- stats.userbuttonFilesAffected = stats.userbuttonFilesAffected || [];
65
- if (!stats.userbuttonFilesAffected.includes(filePath)) {
66
- stats.userbuttonFilesAffected.push(filePath);
51
+ stats('userbuttonAfterSignOutPropsRemoved');
67
52
  }
68
53
  }
69
54
  }
@@ -115,7 +100,7 @@ module.exports = function transformDeprecatedProps({
115
100
  if (renameObjectProperties(root, j, 'activeSessions', 'signedInSessions')) {
116
101
  dirty = true;
117
102
  }
118
- if (transformSetActiveBeforeEmit(root, j, stats, filePath)) {
103
+ if (transformSetActiveBeforeEmit(root, j, stats)) {
119
104
  dirty = true;
120
105
  }
121
106
  if (renameTypeReferences(root, j, 'ClerkMiddlewareAuthObject', 'ClerkMiddlewareSessionAuthObject')) {
@@ -338,7 +323,7 @@ function renameTSPropertySignatures(root, j, oldName, newName) {
338
323
  });
339
324
  return changed;
340
325
  }
341
- function transformSetActiveBeforeEmit(root, j, stats, filePath) {
326
+ function transformSetActiveBeforeEmit(root, j, stats) {
342
327
  let changed = false;
343
328
  root.find(j.CallExpression).filter(path => isSetActiveCall(path.node.callee)).forEach(path => {
344
329
  const [args0] = path.node.arguments;
@@ -362,13 +347,7 @@ function transformSetActiveBeforeEmit(root, j, stats, filePath) {
362
347
  const navigateProp = j.objectProperty(j.identifier('navigate'), buildNavigateArrowFunction(j, originalValue));
363
348
  args0.properties.splice(beforeEmitIndex, 1, navigateProp);
364
349
  changed = true;
365
- if (stats) {
366
- stats.beforeEmitTransformed = (stats.beforeEmitTransformed || 0) + 1;
367
- stats.beforeEmitFiles = stats.beforeEmitFiles || [];
368
- if (!stats.beforeEmitFiles.includes(filePath)) {
369
- stats.beforeEmitFiles.push(filePath);
370
- }
371
- }
350
+ stats('beforeEmitTransformed');
372
351
  });
373
352
  return changed;
374
353
  }
@@ -399,8 +378,8 @@ function getPropertyValueExpression(valueNode) {
399
378
  }
400
379
  function buildNavigateArrowFunction(j, originalExpression) {
401
380
  const paramIdentifier = j.identifier('params');
402
- const calleeExpression = clone(originalExpression);
403
- const callExpression = j.callExpression(calleeExpression, [j.memberExpression(paramIdentifier, j.identifier('session'))]);
381
+ // No need to clone - we're moving the expression from beforeEmit to navigate
382
+ const callExpression = j.callExpression(originalExpression, [j.memberExpression(paramIdentifier, j.identifier('session'))]);
404
383
  return j.arrowFunctionExpression([paramIdentifier], callExpression);
405
384
  }
406
385
  function renameTypeReferences(root, j, oldName, newName) {
@@ -0,0 +1,65 @@
1
+ const LEGACY_PACKAGE = '@clerk/themes';
2
+ const TARGET_PACKAGE = '@clerk/ui/themes';
3
+ const isStringLiteral = (j, node) => j.Literal.check(node) && typeof node.value === 'string' || j.StringLiteral && j.StringLiteral.check(node);
4
+ const getReplacement = value => {
5
+ if (typeof value !== 'string' || !value.startsWith(LEGACY_PACKAGE)) {
6
+ return null;
7
+ }
8
+ return `${TARGET_PACKAGE}${value.slice(LEGACY_PACKAGE.length)}`;
9
+ };
10
+ module.exports = function transformThemesToUiThemes({
11
+ source
12
+ }, {
13
+ jscodeshift: j
14
+ }) {
15
+ const root = j(source);
16
+ let dirty = false;
17
+ const replaceSourceLiteral = literal => {
18
+ if (!isStringLiteral(j, literal)) {
19
+ return;
20
+ }
21
+ const nextValue = getReplacement(literal.value);
22
+ if (nextValue && nextValue !== literal.value) {
23
+ literal.value = nextValue;
24
+ dirty = true;
25
+ }
26
+ };
27
+ root.find(j.ImportDeclaration).forEach(path => {
28
+ replaceSourceLiteral(path.node.source);
29
+ });
30
+ root.find(j.ExportNamedDeclaration).forEach(path => {
31
+ if (path.node.source) {
32
+ replaceSourceLiteral(path.node.source);
33
+ }
34
+ });
35
+ root.find(j.ExportAllDeclaration).forEach(path => {
36
+ replaceSourceLiteral(path.node.source);
37
+ });
38
+ root.find(j.CallExpression, {
39
+ callee: {
40
+ name: 'require'
41
+ }
42
+ }).forEach(path => {
43
+ const [arg] = path.node.arguments || [];
44
+ if (arg) {
45
+ replaceSourceLiteral(arg);
46
+ }
47
+ });
48
+ if (j.ImportExpression) {
49
+ root.find(j.ImportExpression).forEach(path => {
50
+ replaceSourceLiteral(path.node.source);
51
+ });
52
+ }
53
+ root.find(j.CallExpression, {
54
+ callee: {
55
+ type: 'Import'
56
+ }
57
+ }).forEach(path => {
58
+ const [arg] = path.node.arguments || [];
59
+ if (arg) {
60
+ replaceSourceLiteral(arg);
61
+ }
62
+ });
63
+ return dirty ? root.toSource() : undefined;
64
+ };
65
+ module.exports.parser = 'tsx';
package/dist/config.js ADDED
@@ -0,0 +1,122 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import matter from 'gray-matter';
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const VERSIONS_DIR = path.join(__dirname, 'versions');
7
+ export async function loadConfig(sdk, currentVersion) {
8
+ const versionDirs = fs.readdirSync(VERSIONS_DIR, {
9
+ withFileTypes: true
10
+ }).filter(d => d.isDirectory()).map(d => d.name);
11
+ let applicableConfig = null;
12
+ for (const versionDir of versionDirs) {
13
+ const configPath = path.join(VERSIONS_DIR, versionDir, 'index.js');
14
+ if (!fs.existsSync(configPath)) {
15
+ continue;
16
+ }
17
+ const moduleUrl = pathToFileURL(configPath).href;
18
+ const mod = await import(moduleUrl);
19
+ const config = mod.default ?? mod;
20
+ if (!config.sdkVersions) {
21
+ continue;
22
+ }
23
+ const versionStatus = getVersionStatus(config, sdk, currentVersion);
24
+ if (versionStatus === 'unsupported' || versionStatus === 'unknown') {
25
+ continue;
26
+ }
27
+ if (versionStatus === 'needs-upgrade') {
28
+ const changes = loadChanges(versionDir, sdk);
29
+ return {
30
+ ...config,
31
+ changes,
32
+ versionStatus,
33
+ needsUpgrade: true,
34
+ alreadyUpgraded: false
35
+ };
36
+ }
37
+ if (versionStatus === 'already-upgraded' && !applicableConfig) {
38
+ applicableConfig = {
39
+ config,
40
+ versionDir
41
+ };
42
+ }
43
+ }
44
+ if (applicableConfig) {
45
+ const changes = loadChanges(applicableConfig.versionDir, sdk);
46
+ return {
47
+ ...applicableConfig.config,
48
+ changes,
49
+ versionStatus: 'already-upgraded',
50
+ needsUpgrade: false,
51
+ alreadyUpgraded: true
52
+ };
53
+ }
54
+ return null;
55
+ }
56
+ function getVersionStatus(config, sdk, currentVersion) {
57
+ if (!config?.sdkVersions) {
58
+ return 'unknown';
59
+ }
60
+ const range = config.sdkVersions[sdk];
61
+ if (!range) {
62
+ return 'unknown';
63
+ }
64
+ if (typeof currentVersion !== 'number') {
65
+ return 'unknown';
66
+ }
67
+ if (typeof range.from === 'number' && currentVersion < range.from) {
68
+ return 'unsupported';
69
+ }
70
+ if (typeof range.to === 'number' && currentVersion >= range.to) {
71
+ return 'already-upgraded';
72
+ }
73
+ return 'needs-upgrade';
74
+ }
75
+ export function getTargetPackageName(sdk) {
76
+ if (sdk === 'clerk-react' || sdk === 'react') {
77
+ return '@clerk/react';
78
+ }
79
+ if (sdk === 'clerk-expo' || sdk === 'expo') {
80
+ return '@clerk/expo';
81
+ }
82
+ return `@clerk/${sdk}`;
83
+ }
84
+ export function getOldPackageName(sdk) {
85
+ if (sdk === 'clerk-react' || sdk === 'react') {
86
+ return '@clerk/clerk-react';
87
+ }
88
+ if (sdk === 'clerk-expo' || sdk === 'expo') {
89
+ return '@clerk/clerk-expo';
90
+ }
91
+ return null;
92
+ }
93
+ function loadChanges(versionDir, sdk) {
94
+ const changesDir = path.join(VERSIONS_DIR, versionDir, 'changes');
95
+ if (!fs.existsSync(changesDir)) {
96
+ return [];
97
+ }
98
+ const files = fs.readdirSync(changesDir).filter(f => f.endsWith('.md'));
99
+ const changes = [];
100
+ for (const file of files) {
101
+ const filePath = path.join(changesDir, file);
102
+ const content = fs.readFileSync(filePath, 'utf8');
103
+ const parsed = matter(content);
104
+ const fm = parsed.data;
105
+ const packages = fm.packages || ['*'];
106
+ const appliesToSdk = packages.includes('*') || packages.includes(sdk);
107
+ if (!appliesToSdk) {
108
+ continue;
109
+ }
110
+ const matcher = fm.matcher ? Array.isArray(fm.matcher) ? fm.matcher.map(m => new RegExp(m, `g${fm.matcherFlags || ''}`)) : new RegExp(fm.matcher, `g${fm.matcherFlags || ''}`) : null;
111
+ changes.push({
112
+ title: fm.title,
113
+ matcher,
114
+ packages,
115
+ category: fm.category || 'breaking',
116
+ warning: fm.warning || fm.category === 'warning',
117
+ docsAnchor: fm.docsAnchor || file.replace('.md', ''),
118
+ content: parsed.content
119
+ });
120
+ }
121
+ return changes;
122
+ }
package/dist/render.js ADDED
@@ -0,0 +1,164 @@
1
+ import * as readline from 'node:readline';
2
+ import chalk from 'chalk';
3
+ export function renderHeader() {
4
+ console.log('');
5
+ console.log(chalk.magenta.bold('>> Clerk Upgrade CLI <<'));
6
+ console.log('');
7
+ }
8
+ export function renderText(message, color) {
9
+ const colorFn = chalk[color] || (s => s);
10
+ console.log(colorFn(message));
11
+ }
12
+ export function renderSuccess(message) {
13
+ console.log(chalk.green(`✅ ${message}`));
14
+ }
15
+ export function renderError(message) {
16
+ console.error(chalk.red(`⛔ ${message}`));
17
+ }
18
+ export function renderWarning(message) {
19
+ console.log(chalk.yellow(`⚠️ ${message}`));
20
+ }
21
+ export function renderNewline() {
22
+ console.log('');
23
+ }
24
+ export function renderConfig({
25
+ sdk,
26
+ currentVersion,
27
+ fromVersion,
28
+ toVersion,
29
+ versionName,
30
+ dir,
31
+ packageManager
32
+ }) {
33
+ console.log(`🔧 ${chalk.bold('Upgrade config')}`);
34
+ const versionSuffix = currentVersion ? ` ${chalk.gray(`(v${currentVersion})`)}` : '';
35
+ console.log(`Clerk SDK: ${chalk.green(`@clerk/${sdk}`)}${versionSuffix}`);
36
+ if (fromVersion && toVersion) {
37
+ const versionLabel = versionName ? ` ${chalk.gray(`(${versionName})`)}` : '';
38
+ console.log(`Upgrade: ${chalk.green(`v${fromVersion}`)} → ${chalk.green(`v${toVersion}`)}${versionLabel}`);
39
+ }
40
+ console.log(`Directory: ${chalk.green(dir)}`);
41
+ if (packageManager) {
42
+ console.log(`Package manager: ${chalk.green(packageManager)}`);
43
+ }
44
+ console.log('');
45
+ }
46
+ export async function promptConfirm(message) {
47
+ const rl = readline.createInterface({
48
+ input: process.stdin,
49
+ output: process.stdout
50
+ });
51
+ return new Promise(resolve => {
52
+ rl.question(`${message} (y/n): `, answer => {
53
+ rl.close();
54
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
55
+ });
56
+ });
57
+ }
58
+ export async function promptSelect(message, options) {
59
+ const rl = readline.createInterface({
60
+ input: process.stdin,
61
+ output: process.stdout
62
+ });
63
+ console.log(message);
64
+ options.forEach((opt, i) => {
65
+ console.log(` ${i + 1}) ${opt.label}`);
66
+ });
67
+ return new Promise(resolve => {
68
+ rl.question('Enter number: ', answer => {
69
+ rl.close();
70
+ const index = parseInt(answer, 10) - 1;
71
+ if (index >= 0 && index < options.length) {
72
+ resolve(options[index].value);
73
+ } else {
74
+ resolve(null);
75
+ }
76
+ });
77
+ });
78
+ }
79
+ export async function promptText(message, defaultValue = '') {
80
+ const rl = readline.createInterface({
81
+ input: process.stdin,
82
+ output: process.stdout
83
+ });
84
+ const prompt = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
85
+ return new Promise(resolve => {
86
+ rl.question(prompt, answer => {
87
+ rl.close();
88
+ resolve(answer || defaultValue);
89
+ });
90
+ });
91
+ }
92
+ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
93
+ export function createSpinner(label) {
94
+ let frameIndex = 0;
95
+ let interval = null;
96
+ let currentLabel = label;
97
+ const start = () => {
98
+ interval = setInterval(() => {
99
+ process.stdout.write(`\r${spinnerFrames[frameIndex]} ${currentLabel}`);
100
+ frameIndex = (frameIndex + 1) % spinnerFrames.length;
101
+ }, 80);
102
+ };
103
+ start();
104
+ return {
105
+ update(newLabel) {
106
+ currentLabel = newLabel;
107
+ },
108
+ stop() {
109
+ if (interval) {
110
+ clearInterval(interval);
111
+ interval = null;
112
+ process.stdout.write('\r\x1b[K');
113
+ }
114
+ },
115
+ success(message) {
116
+ if (interval) {
117
+ clearInterval(interval);
118
+ interval = null;
119
+ }
120
+ process.stdout.write(`\r\x1b[K${chalk.green('✓')} ${message}\n`);
121
+ },
122
+ error(message) {
123
+ if (interval) {
124
+ clearInterval(interval);
125
+ interval = null;
126
+ }
127
+ process.stdout.write(`\r\x1b[K${chalk.red('✗')} ${message}\n`);
128
+ }
129
+ };
130
+ }
131
+ export function renderCodemodResults(transform, result) {
132
+ console.log(` ${result.ok ?? 0} file(s) modified, ${chalk.red(` ${result.error ?? 0} errors`)}`);
133
+ console.log('');
134
+ }
135
+ export function renderScanResults(results, docsUrl) {
136
+ if (results.length === 0) {
137
+ console.log(chalk.green('✓ No breaking changes detected!'));
138
+ console.log('');
139
+ return;
140
+ }
141
+ console.log(chalk.yellow.bold(`Found ${results.length} potential issue(s) to review:`));
142
+ console.log('');
143
+ for (const item of results) {
144
+ console.log(chalk.bold(item.title));
145
+ if (item.warning) {
146
+ console.log(chalk.yellow('(warning - may not require action)'));
147
+ }
148
+ console.log(chalk.gray(`Found ${item.instances.length} instance(s):`));
149
+ for (const inst of item.instances) {
150
+ console.log(chalk.gray(` ${inst.file}:${inst.position.line}:${inst.position.column}`));
151
+ }
152
+ const link = docsUrl && item.docsAnchor ? `${docsUrl}#${item.docsAnchor}` : null;
153
+ if (link) {
154
+ console.log(chalk.blue(`→ View in migration guide: ${link}`));
155
+ }
156
+ console.log('');
157
+ }
158
+ }
159
+ export function renderComplete(sdk, docsUrl) {
160
+ console.log(chalk.green.bold(`✅ Upgrade complete for @clerk/${sdk}`));
161
+ console.log('');
162
+ console.log(`Review the changes above and test your application before deployment.`);
163
+ console.log(chalk.gray(`For more information, see the migration guide: ${docsUrl}`));
164
+ }
package/dist/runner.js ADDED
@@ -0,0 +1,98 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { convertPathToPattern, globby } from 'globby';
5
+ import indexToPosition from 'index-to-position';
6
+ import { getCodemodConfig, runCodemod } from './codemods/index.js';
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', '**/*.(png|webp|svg|gif|jpg|jpeg)+', '**/*.(mp4|mkv|wmv|m4v|mov|avi|flv|webm|flac|mka|m4a|aac|ogg)+'];
9
+ export async function runCodemods(config, sdk, options) {
10
+ const codemods = config.codemods || [];
11
+ if (codemods.length === 0) {
12
+ return;
13
+ }
14
+ const glob = typeof options.glob === 'string' ? options.glob.split(/[ ,]/).filter(Boolean) : options.glob;
15
+ for (const transform of codemods) {
16
+ const spinner = createSpinner(`Running codemod: ${transform}`);
17
+ try {
18
+ const result = await runCodemod(transform, glob, options);
19
+ spinner.success(`Codemod applied: ${chalk.dim(transform)}`);
20
+ renderCodemodResults(transform, result);
21
+ const codemodConfig = getCodemodConfig(transform);
22
+ if (codemodConfig?.renderSummary && result.stats) {
23
+ codemodConfig.renderSummary(result.stats);
24
+ }
25
+ } catch (error) {
26
+ spinner.error(`Codemod failed: ${transform}`);
27
+ throw error;
28
+ }
29
+ }
30
+ }
31
+ export async function runScans(config, sdk, options) {
32
+ const matchers = loadMatchers(config, sdk);
33
+ if (matchers.length === 0) {
34
+ return [];
35
+ }
36
+ const spinner = createSpinner('Scanning files for breaking changes...');
37
+ try {
38
+ const pattern = convertPathToPattern(path.resolve(options.dir));
39
+ const files = await globby(pattern, {
40
+ ignore: [...GLOBBY_IGNORE, ...(options.ignore || [])]
41
+ });
42
+ const results = {};
43
+ for (let idx = 0; idx < files.length; idx++) {
44
+ const file = files[idx];
45
+ spinner.update(`Scanning ${path.basename(file)} (${idx + 1}/${files.length})`);
46
+ const content = await fs.readFile(file, 'utf8');
47
+ for (const matcher of matchers) {
48
+ const matches = findMatches(content, matcher.matcher);
49
+ if (matches.length === 0) {
50
+ continue;
51
+ }
52
+ if (!results[matcher.title]) {
53
+ results[matcher.title] = {
54
+ instances: [],
55
+ ...matcher
56
+ };
57
+ }
58
+ for (const match of matches) {
59
+ const position = indexToPosition(content, match.index, {
60
+ oneBased: true
61
+ });
62
+ const fileRelative = path.relative(process.cwd(), file);
63
+ const isDuplicate = results[matcher.title].instances.some(i => i.position.line === position.line && i.position.column === position.column && i.file === fileRelative);
64
+ if (!isDuplicate) {
65
+ results[matcher.title].instances.push({
66
+ sdk,
67
+ position,
68
+ file: fileRelative
69
+ });
70
+ }
71
+ }
72
+ }
73
+ }
74
+ spinner.success(`Scanned ${files.length} files`);
75
+ return Object.values(results);
76
+ } catch (error) {
77
+ spinner.error('Scan failed');
78
+ throw error;
79
+ }
80
+ }
81
+ function loadMatchers(config, sdk) {
82
+ if (!config.changes) {
83
+ return [];
84
+ }
85
+ return config.changes.filter(change => {
86
+ if (!change.matcher) {
87
+ return false;
88
+ }
89
+ const packages = change.packages || ['*'];
90
+ return packages.includes('*') || packages.includes(sdk);
91
+ });
92
+ }
93
+ function findMatches(content, matcher) {
94
+ if (Array.isArray(matcher)) {
95
+ return matcher.flatMap(m => Array.from(content.matchAll(m)));
96
+ }
97
+ return Array.from(content.matchAll(matcher));
98
+ }
@@ -0,0 +1,125 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readPackageSync } from 'read-pkg';
4
+ import semverRegex from 'semver-regex';
5
+ import { getOldPackageName } from '../config.js';
6
+ const SUPPORTED_SDKS = [{
7
+ label: '@clerk/nextjs',
8
+ value: 'nextjs'
9
+ }, {
10
+ label: '@clerk/react',
11
+ value: 'react'
12
+ }, {
13
+ label: '@clerk/clerk-react',
14
+ value: 'react'
15
+ }, {
16
+ label: '@clerk/expo',
17
+ value: 'expo'
18
+ }, {
19
+ label: '@clerk/clerk-expo',
20
+ value: 'expo'
21
+ }, {
22
+ label: '@clerk/react-router',
23
+ value: 'react-router'
24
+ }, {
25
+ label: '@clerk/tanstack-react-start',
26
+ value: 'tanstack-react-start'
27
+ }, {
28
+ label: '@clerk/astro',
29
+ value: 'astro'
30
+ }, {
31
+ label: '@clerk/nuxt',
32
+ value: 'nuxt'
33
+ }, {
34
+ label: '@clerk/vue',
35
+ value: 'vue'
36
+ }, {
37
+ label: '@clerk/express',
38
+ value: 'express'
39
+ }, {
40
+ label: '@clerk/fastify',
41
+ value: 'fastify'
42
+ }, {
43
+ label: '@clerk/clerk-js',
44
+ value: 'js'
45
+ }, {
46
+ label: '@clerk/backend',
47
+ value: 'backend'
48
+ }];
49
+ export function getSupportedSdks() {
50
+ return SUPPORTED_SDKS;
51
+ }
52
+ export function detectSdk(dir) {
53
+ let pkg;
54
+ try {
55
+ const pkgPath = path.join(dir, 'package.json');
56
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
57
+ } catch {
58
+ return null;
59
+ }
60
+ const deps = pkg.dependencies ? Object.keys(pkg.dependencies) : [];
61
+ const devDeps = pkg.devDependencies ? Object.keys(pkg.devDependencies) : [];
62
+ const allDeps = [...deps, ...devDeps];
63
+ for (const {
64
+ label,
65
+ value
66
+ } of SUPPORTED_SDKS) {
67
+ if (allDeps.includes(label)) {
68
+ return value;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ export function getSdkVersion(sdk, dir) {
74
+ let pkg;
75
+ try {
76
+ pkg = readPackageSync({
77
+ cwd: dir
78
+ });
79
+ } catch {
80
+ return null;
81
+ }
82
+ const pkgName = sdk.startsWith('@clerk/') ? sdk : `@clerk/${sdk}`;
83
+ const oldPkgName = getOldPackageName(sdk);
84
+ const version = pkg.dependencies?.[pkgName] || pkg.devDependencies?.[pkgName] || oldPkgName && pkg.dependencies?.[oldPkgName] || oldPkgName && pkg.devDependencies?.[oldPkgName];
85
+ if (!version) {
86
+ return null;
87
+ }
88
+ return getMajorVersion(version);
89
+ }
90
+ export function getMajorVersion(semver) {
91
+ const cleaned = semver.replace(/^[\^~]/, '');
92
+ const match = cleaned.match(semverRegex());
93
+ if (match) {
94
+ const [major] = match[0].split('.');
95
+ return parseInt(major, 10);
96
+ }
97
+ return null;
98
+ }
99
+ export function getInstalledClerkPackages(dir) {
100
+ let pkg;
101
+ try {
102
+ const pkgPath = path.join(dir, 'package.json');
103
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
104
+ } catch {
105
+ return {};
106
+ }
107
+ const deps = pkg.dependencies ? Object.entries(pkg.dependencies) : [];
108
+ const devDeps = pkg.devDependencies ? Object.entries(pkg.devDependencies) : [];
109
+ return Object.fromEntries([...deps, ...devDeps].filter(([name]) => name.startsWith('@clerk/')).map(([name, version]) => [name, getMajorVersion(version)]));
110
+ }
111
+ export function normalizeSdkName(sdk) {
112
+ if (!sdk) {
113
+ return null;
114
+ }
115
+ if (sdk.startsWith('@clerk/')) {
116
+ return sdk.replace('@clerk/', '');
117
+ }
118
+ if (sdk === 'clerk-react') {
119
+ return 'react';
120
+ }
121
+ if (sdk === 'clerk-expo') {
122
+ return 'expo';
123
+ }
124
+ return sdk;
125
+ }