Skip to content

Commit 0d4cda7

Browse files
authored
feat(upgrade): Add migration guide generation, improve output (#7397)
1 parent 1eaef8e commit 0d4cda7

File tree

9 files changed

+228
-18
lines changed

9 files changed

+228
-18
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/upgrade': minor
3+
---
4+
5+
Add a migration guide generator and improve scan output.

packages/upgrade/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"dev": "babel --keep-file-extension --out-dir=dist --watch src --copy-files",
2222
"format": "node ../../scripts/format-package.mjs",
2323
"format:check": "node ../../scripts/format-package.mjs --check",
24+
"generate-guide": "node scripts/generate-guide.js",
2425
"lint": "eslint src/",
2526
"lint:publint": "publint",
2627
"test": "vitest run",
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/usr/bin/env node
2+
import fs from 'node:fs';
3+
import path from 'node:path';
4+
import { fileURLToPath, pathToFileURL } from 'node:url';
5+
6+
import matter from 'gray-matter';
7+
import meow from 'meow';
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
10+
const VERSIONS_DIR = path.join(__dirname, '../src/versions');
11+
12+
const cli = meow(
13+
`
14+
Usage
15+
$ pnpm run generate-guide --version=<version> --sdk=<sdk>
16+
17+
Options
18+
--version Version directory to use (e.g., core-3)
19+
--sdk SDK to generate guide for (e.g., nextjs, react, expo)
20+
21+
Examples
22+
$ pnpm run generate-guide --version=core-3 --sdk=nextjs
23+
$ pnpm run generate-guide --version=core-3 --sdk=react > react-guide.md
24+
`,
25+
{
26+
importMeta: import.meta,
27+
flags: {
28+
version: { type: 'string', isRequired: true },
29+
sdk: { type: 'string', isRequired: true },
30+
},
31+
},
32+
);
33+
34+
async function loadVersionConfig(version) {
35+
const configPath = path.join(VERSIONS_DIR, version, 'index.js');
36+
37+
if (!fs.existsSync(configPath)) {
38+
throw new Error(`Version config not found: ${configPath}`);
39+
}
40+
41+
const moduleUrl = pathToFileURL(configPath).href;
42+
const mod = await import(moduleUrl);
43+
return mod.default ?? mod;
44+
}
45+
46+
function loadChanges(version, sdk) {
47+
const changesDir = path.join(VERSIONS_DIR, version, 'changes');
48+
49+
if (!fs.existsSync(changesDir)) {
50+
return [];
51+
}
52+
53+
const files = fs.readdirSync(changesDir).filter(f => f.endsWith('.md'));
54+
const changes = [];
55+
56+
for (const file of files) {
57+
const filePath = path.join(changesDir, file);
58+
const content = fs.readFileSync(filePath, 'utf8');
59+
const parsed = matter(content);
60+
const fm = parsed.data;
61+
62+
const packages = fm.packages || ['*'];
63+
const appliesToSdk = packages.includes('*') || packages.includes(sdk);
64+
65+
if (!appliesToSdk) {
66+
continue;
67+
}
68+
69+
changes.push({
70+
title: fm.title,
71+
packages,
72+
category: fm.category || 'breaking',
73+
content: parsed.content.trim(),
74+
slug: file.replace('.md', ''),
75+
});
76+
}
77+
78+
return changes;
79+
}
80+
81+
function groupByCategory(changes) {
82+
const groups = {};
83+
84+
for (const change of changes) {
85+
const category = change.category;
86+
if (!groups[category]) {
87+
groups[category] = [];
88+
}
89+
groups[category].push(change);
90+
}
91+
92+
return groups;
93+
}
94+
95+
function getCategoryHeading(category) {
96+
const headings = {
97+
breaking: 'Breaking Changes',
98+
'deprecation-removal': 'Deprecation Removals',
99+
warning: 'Warnings',
100+
};
101+
return headings[category] || category;
102+
}
103+
104+
function generateMarkdown(sdk, versionConfig, changes) {
105+
const lines = [];
106+
const versionName = versionConfig.name || versionConfig.id;
107+
108+
lines.push(`# Upgrading @clerk/${sdk} to ${versionName}`);
109+
lines.push('');
110+
111+
if (versionConfig.docsUrl) {
112+
lines.push(`For the full migration guide, see: ${versionConfig.docsUrl}`);
113+
lines.push('');
114+
}
115+
116+
const grouped = groupByCategory(changes);
117+
const categoryOrder = ['breaking', 'deprecation-removal', 'warning'];
118+
119+
for (const category of categoryOrder) {
120+
const categoryChanges = grouped[category];
121+
if (!categoryChanges || categoryChanges.length === 0) {
122+
continue;
123+
}
124+
125+
lines.push(`## ${getCategoryHeading(category)}`);
126+
lines.push('');
127+
128+
for (const change of categoryChanges) {
129+
lines.push(`### ${change.title}`);
130+
lines.push('');
131+
lines.push(change.content);
132+
lines.push('');
133+
}
134+
}
135+
136+
// Handle any categories not in the predefined order
137+
for (const [category, categoryChanges] of Object.entries(grouped)) {
138+
if (categoryOrder.includes(category)) {
139+
continue;
140+
}
141+
142+
lines.push(`## ${getCategoryHeading(category)}`);
143+
lines.push('');
144+
145+
for (const change of categoryChanges) {
146+
lines.push(`### ${change.title}`);
147+
lines.push('');
148+
lines.push(change.content);
149+
lines.push('');
150+
}
151+
}
152+
153+
return lines.join('\n');
154+
}
155+
156+
async function main() {
157+
const { version, sdk } = cli.flags;
158+
159+
const versionConfig = await loadVersionConfig(version);
160+
const changes = loadChanges(version, sdk);
161+
162+
if (changes.length === 0) {
163+
console.error(`No changes found for ${sdk} in ${version}`);
164+
process.exit(1);
165+
}
166+
167+
const markdown = generateMarkdown(sdk, versionConfig, changes);
168+
console.log(markdown);
169+
}
170+
171+
main().catch(error => {
172+
console.error(error.message);
173+
process.exit(1);
174+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "test-nextjs-v6-scan-issues",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"@clerk/nextjs": "^6.0.0",
6+
"next": "^14.0.0",
7+
"react": "^18.0.0"
8+
}
9+
}

packages/upgrade/src/__tests__/fixtures/nextjs-v6-scan-issues/pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

packages/upgrade/src/cli.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ const cli = meow(
5353
{
5454
importMeta: import.meta,
5555
flags: {
56-
sdk: { type: 'string' },
5756
dir: { type: 'string', default: process.cwd() },
57+
dryRun: { type: 'boolean', default: false },
5858
glob: { type: 'string', default: '**/*.(js|jsx|ts|tsx|mjs|cjs)' },
5959
ignore: { type: 'string', isMultiple: true },
60-
skipUpgrade: { type: 'boolean', default: false },
6160
release: { type: 'string' },
62-
dryRun: { type: 'boolean', default: false },
61+
sdk: { type: 'string' },
6362
skipCodemods: { type: 'boolean', default: false },
63+
skipUpgrade: { type: 'boolean', default: false },
6464
},
6565
},
6666
);
@@ -70,12 +70,12 @@ async function main() {
7070

7171
const options = {
7272
dir: cli.flags.dir,
73+
dryRun: cli.flags.dryRun,
7374
glob: cli.flags.glob,
7475
ignore: cli.flags.ignore,
75-
skipUpgrade: cli.flags.skipUpgrade,
7676
release: cli.flags.release,
77-
dryRun: cli.flags.dryRun,
7877
skipCodemods: cli.flags.skipCodemods,
78+
skipUpgrade: cli.flags.skipUpgrade,
7979
};
8080

8181
if (options.dryRun) {

packages/upgrade/src/codemods/transform-align-experimental-unstable-prefixes.cjs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,6 @@ const CHROME_CLIENT_NAMES = new Set(['__unstable__createClerkClient', 'createCle
3636
const CHROME_BACKGROUND_SOURCE = '@clerk/chrome-extension/background';
3737
const CHROME_LEGACY_SOURCE = '@clerk/chrome-extension';
3838

39-
/**
40-
* Transforms experimental and unstable prefixed identifiers to their stable or internal equivalents.
41-
* Also moves theme-related imports to @clerk/ui/themes/experimental and Chrome extension imports
42-
* to @clerk/chrome-extension/background. Removes deprecated billing-related props.
43-
*
44-
* @param {Object} file - The file object containing the source code
45-
* @param {string} file.source - The source code to transform
46-
* @param {Object} api - The jscodeshift API
47-
* @param {Function} api.jscodeshift - The jscodeshift function
48-
* @returns {string|undefined} The transformed source code, or undefined if no changes were made
49-
*/
5039
module.exports = function transformAlignExperimentalUnstablePrefixes({ source }, { jscodeshift: j }) {
5140
const root = j(source);
5241
let dirty = false;

packages/upgrade/src/runner.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ export async function runCodemods(config, sdk, options) {
3737
return;
3838
}
3939

40-
const glob = typeof options.glob === 'string' ? options.glob.split(/[ ,]/).filter(Boolean) : options.glob;
40+
const patterns = typeof options.glob === 'string' ? options.glob.split(/[ ,]/).filter(Boolean) : options.glob;
4141

4242
for (const transform of codemods) {
4343
const spinner = createSpinner(`Running codemod: ${transform}`);
4444

4545
try {
46-
const result = await runCodemod(transform, glob, options);
46+
const result = await runCodemod(transform, patterns, options);
4747
spinner.success(`Codemod applied: ${chalk.dim(transform)}`);
4848
renderCodemodResults(transform, result);
4949

0 commit comments

Comments
 (0)