@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.
- package/dist/__tests__/fixtures/expo-old-package/package-lock.json +5 -0
- package/dist/__tests__/fixtures/expo-old-package/package.json +10 -0
- package/dist/__tests__/fixtures/expo-old-package/src/App.tsx +14 -0
- package/dist/__tests__/fixtures/nextjs-v6/package.json +9 -0
- package/dist/__tests__/fixtures/nextjs-v6/pnpm-lock.yaml +2 -0
- package/dist/__tests__/fixtures/nextjs-v6/src/app.tsx +17 -0
- package/dist/__tests__/fixtures/nextjs-v7/package.json +9 -0
- package/dist/__tests__/fixtures/nextjs-v7/pnpm-lock.yaml +2 -0
- package/dist/__tests__/fixtures/nextjs-v7/src/app.tsx +16 -0
- package/dist/__tests__/fixtures/no-clerk/package.json +7 -0
- package/dist/__tests__/fixtures/react-v6/package.json +8 -0
- package/dist/__tests__/fixtures/react-v6/src/App.tsx +19 -0
- package/dist/__tests__/fixtures/react-v6/yarn.lock +2 -0
- package/dist/__tests__/helpers/create-fixture.js +56 -0
- package/dist/__tests__/integration/cli.test.js +230 -0
- package/dist/__tests__/integration/config.test.js +76 -0
- package/dist/__tests__/integration/detect-sdk.test.js +100 -0
- package/dist/__tests__/integration/runner.test.js +79 -0
- package/dist/cli.js +159 -45
- package/dist/codemods/__tests__/__fixtures__/transform-align-experimental-unstable-prefixes.fixtures.js +68 -0
- package/dist/codemods/__tests__/__fixtures__/transform-appearance-layout-to-options.fixtures.js +9 -0
- package/dist/codemods/__tests__/__fixtures__/transform-clerk-react-v6.fixtures.js +13 -0
- package/dist/codemods/__tests__/__fixtures__/transform-remove-deprecated-appearance-props.fixtures.js +63 -0
- package/dist/codemods/__tests__/__fixtures__/transform-themes-to-ui-themes.fixtures.js +41 -0
- package/dist/codemods/__tests__/transform-align-experimental-unstable-prefixes.test.js +15 -0
- package/dist/codemods/__tests__/transform-appearance-layout-to-options.test.js +15 -0
- package/dist/codemods/__tests__/transform-remove-deprecated-appearance-props.test.js +15 -0
- package/dist/codemods/__tests__/transform-themes-to-ui-themes.test.js +15 -0
- package/dist/codemods/index.js +67 -13
- package/dist/codemods/transform-align-experimental-unstable-prefixes.cjs +400 -0
- package/dist/codemods/transform-appearance-layout-to-options.cjs +65 -0
- package/dist/codemods/transform-clerk-react-v6.cjs +15 -7
- package/dist/codemods/transform-remove-deprecated-appearance-props.cjs +109 -0
- package/dist/codemods/transform-remove-deprecated-props.cjs +12 -12
- package/dist/codemods/transform-themes-to-ui-themes.cjs +65 -0
- package/dist/config.js +122 -0
- package/dist/render.js +164 -0
- package/dist/runner.js +98 -0
- package/dist/util/detect-sdk.js +125 -0
- package/dist/util/package-manager.js +94 -0
- package/dist/versions/core-3/changes/clerk-expo-package-rename.md +23 -0
- package/dist/versions/core-3/changes/clerk-react-package-rename.md +23 -0
- package/dist/versions/core-3/index.js +40 -0
- package/package.json +2 -8
- package/dist/app.js +0 -177
- package/dist/components/Codemod.js +0 -97
- package/dist/components/Command.js +0 -56
- package/dist/components/Header.js +0 -11
- package/dist/components/SDKWorkflow.js +0 -278
- package/dist/components/Scan.js +0 -180
- package/dist/components/UpgradeSDK.js +0 -116
- package/dist/util/expandable-list.js +0 -173
- package/dist/util/get-clerk-version.js +0 -22
- package/dist/util/guess-framework.js +0 -69
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function detectPackageManager(dir) {
|
|
5
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) {
|
|
6
|
+
return 'pnpm';
|
|
7
|
+
}
|
|
8
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) {
|
|
9
|
+
return 'yarn';
|
|
10
|
+
}
|
|
11
|
+
if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock'))) {
|
|
12
|
+
return 'bun';
|
|
13
|
+
}
|
|
14
|
+
if (fs.existsSync(path.join(dir, 'package-lock.json'))) {
|
|
15
|
+
return 'npm';
|
|
16
|
+
}
|
|
17
|
+
return 'npm';
|
|
18
|
+
}
|
|
19
|
+
export function getInstallCommand(packageManager, packageName, version = 'latest') {
|
|
20
|
+
const pkg = version === 'latest' ? packageName : `${packageName}@${version}`;
|
|
21
|
+
switch (packageManager) {
|
|
22
|
+
case 'pnpm':
|
|
23
|
+
return ['pnpm', ['add', pkg]];
|
|
24
|
+
case 'yarn':
|
|
25
|
+
return ['yarn', ['add', pkg]];
|
|
26
|
+
case 'bun':
|
|
27
|
+
return ['bun', ['add', pkg]];
|
|
28
|
+
case 'npm':
|
|
29
|
+
default:
|
|
30
|
+
return ['npm', ['install', pkg]];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function getUninstallCommand(packageManager, packageName) {
|
|
34
|
+
switch (packageManager) {
|
|
35
|
+
case 'pnpm':
|
|
36
|
+
return ['pnpm', ['remove', packageName]];
|
|
37
|
+
case 'yarn':
|
|
38
|
+
return ['yarn', ['remove', packageName]];
|
|
39
|
+
case 'bun':
|
|
40
|
+
return ['bun', ['remove', packageName]];
|
|
41
|
+
case 'npm':
|
|
42
|
+
default:
|
|
43
|
+
return ['npm', ['uninstall', packageName]];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function runPackageManagerCommand(command, args, cwd) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const child = spawn(command, args, {
|
|
49
|
+
cwd,
|
|
50
|
+
stdio: 'pipe',
|
|
51
|
+
shell: process.platform === 'win32'
|
|
52
|
+
});
|
|
53
|
+
let stdout = '';
|
|
54
|
+
let stderr = '';
|
|
55
|
+
child.stdout?.on('data', data => {
|
|
56
|
+
stdout += data.toString();
|
|
57
|
+
});
|
|
58
|
+
child.stderr?.on('data', data => {
|
|
59
|
+
stderr += data.toString();
|
|
60
|
+
});
|
|
61
|
+
child.on('close', code => {
|
|
62
|
+
if (code === 0) {
|
|
63
|
+
resolve({
|
|
64
|
+
stdout,
|
|
65
|
+
stderr
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`Command failed with code ${code}: ${stderr || stdout}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
child.on('error', reject);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export async function upgradePackage(packageManager, packageName, version, cwd) {
|
|
75
|
+
const [cmd, args] = getInstallCommand(packageManager, packageName, version);
|
|
76
|
+
return runPackageManagerCommand(cmd, args, cwd);
|
|
77
|
+
}
|
|
78
|
+
export async function removePackage(packageManager, packageName, cwd) {
|
|
79
|
+
const [cmd, args] = getUninstallCommand(packageManager, packageName);
|
|
80
|
+
return runPackageManagerCommand(cmd, args, cwd);
|
|
81
|
+
}
|
|
82
|
+
export function getPackageManagerDisplayName(packageManager) {
|
|
83
|
+
switch (packageManager) {
|
|
84
|
+
case 'pnpm':
|
|
85
|
+
return 'pnpm';
|
|
86
|
+
case 'yarn':
|
|
87
|
+
return 'Yarn';
|
|
88
|
+
case 'bun':
|
|
89
|
+
return 'Bun';
|
|
90
|
+
case 'npm':
|
|
91
|
+
default:
|
|
92
|
+
return 'npm';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: '`@clerk/clerk-expo` renamed to `@clerk/expo`'
|
|
3
|
+
packages: ['expo']
|
|
4
|
+
matcher: '@clerk/clerk-expo'
|
|
5
|
+
category: 'breaking'
|
|
6
|
+
docsAnchor: 'clerk-expo-rename'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
The `@clerk/clerk-expo` package has been renamed to `@clerk/expo`.
|
|
10
|
+
|
|
11
|
+
Update your imports:
|
|
12
|
+
|
|
13
|
+
```diff
|
|
14
|
+
- import { ClerkProvider, useUser } from '@clerk/clerk-expo';
|
|
15
|
+
+ import { ClerkProvider, useUser } from '@clerk/expo';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
And update your package.json:
|
|
19
|
+
|
|
20
|
+
```diff
|
|
21
|
+
- "@clerk/clerk-expo": "^2.0.0",
|
|
22
|
+
+ "@clerk/expo": "^3.0.0",
|
|
23
|
+
```
|