-
Notifications
You must be signed in to change notification settings - Fork 417
feat(upgrade): Add migration guide generation, improve output #7397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
brkalow
wants to merge
23
commits into
main
Choose a base branch
from
brk.feat/generate-migration-guide
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+228
−18
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
bac9b4e
Adds codemod to transform clerk/themes to clerk/ui/themes
brkalow 1392ce5
Adds codemod to transform experimental and unstable prefixes
brkalow 2205f80
Adds codemod to transform appearance.layout -> appearance.options
brkalow e6b18d3
Adds codemod to transform appearance prop changes
brkalow a2f1e49
refactor upgrade CLI to not use ink
brkalow 4e06115
Tweaks to codemod output
brkalow 6640fb0
adjust complete output
brkalow edaf412
add change files that line up with current changesets
brkalow fb05d75
updates lockfile
brkalow f3750b4
Adds changeset
brkalow ec24828
don't hardcode release id
brkalow 5a34413
remove unused file
brkalow 7820fed
format
brkalow 6ef9538
Ajdust output and fix tests. Undo fixture changes
brkalow 5520100
undo codemod changes
brkalow e00579e
cleanup codemod output
brkalow 698157f
fix lint
brkalow 68be931
Merge branch 'vincent-and-the-doctor' into brk.feat/upgrade-cli-core-3
brkalow a06f038
use chalk
brkalow bfa2d05
Merge branch 'brk.feat/upgrade-cli-core-3' into brk.feat/core-3-changes
brkalow 936f024
Adds script to generate a migration guide, and update output of the s…
brkalow 0bd9cd4
Merge branch 'main' into brk.feat/generate-migration-guide
jacekradko 794d6b0
changeset
jacekradko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@clerk/upgrade': minor | ||
| --- | ||
|
|
||
| Add a migration guide generator and improve scan output. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| #!/usr/bin/env node | ||
| import fs from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import { fileURLToPath, pathToFileURL } from 'node:url'; | ||
|
|
||
| import matter from 'gray-matter'; | ||
| import meow from 'meow'; | ||
|
|
||
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
| const VERSIONS_DIR = path.join(__dirname, '../src/versions'); | ||
|
|
||
| const cli = meow( | ||
| ` | ||
| Usage | ||
| $ pnpm run generate-guide --version=<version> --sdk=<sdk> | ||
|
|
||
| Options | ||
| --version Version directory to use (e.g., core-3) | ||
| --sdk SDK to generate guide for (e.g., nextjs, react, expo) | ||
|
|
||
| Examples | ||
| $ pnpm run generate-guide --version=core-3 --sdk=nextjs | ||
| $ pnpm run generate-guide --version=core-3 --sdk=react > react-guide.md | ||
| `, | ||
| { | ||
| importMeta: import.meta, | ||
| flags: { | ||
| version: { type: 'string', isRequired: true }, | ||
| sdk: { type: 'string', isRequired: true }, | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| async function loadVersionConfig(version) { | ||
| const configPath = path.join(VERSIONS_DIR, version, 'index.js'); | ||
|
|
||
| if (!fs.existsSync(configPath)) { | ||
| throw new Error(`Version config not found: ${configPath}`); | ||
| } | ||
|
|
||
| const moduleUrl = pathToFileURL(configPath).href; | ||
| const mod = await import(moduleUrl); | ||
| return mod.default ?? mod; | ||
| } | ||
|
|
||
| function loadChanges(version, sdk) { | ||
| const changesDir = path.join(VERSIONS_DIR, version, 'changes'); | ||
|
|
||
| if (!fs.existsSync(changesDir)) { | ||
| return []; | ||
| } | ||
|
|
||
| const files = fs.readdirSync(changesDir).filter(f => f.endsWith('.md')); | ||
| const changes = []; | ||
|
|
||
| for (const file of files) { | ||
| const filePath = path.join(changesDir, file); | ||
| const content = fs.readFileSync(filePath, 'utf8'); | ||
| const parsed = matter(content); | ||
| const fm = parsed.data; | ||
|
|
||
| const packages = fm.packages || ['*']; | ||
| const appliesToSdk = packages.includes('*') || packages.includes(sdk); | ||
|
|
||
| if (!appliesToSdk) { | ||
| continue; | ||
| } | ||
|
|
||
| changes.push({ | ||
| title: fm.title, | ||
| packages, | ||
| category: fm.category || 'breaking', | ||
| content: parsed.content.trim(), | ||
| slug: file.replace('.md', ''), | ||
| }); | ||
| } | ||
|
|
||
| return changes; | ||
| } | ||
|
|
||
| function groupByCategory(changes) { | ||
| const groups = {}; | ||
|
|
||
| for (const change of changes) { | ||
| const category = change.category; | ||
| if (!groups[category]) { | ||
| groups[category] = []; | ||
| } | ||
| groups[category].push(change); | ||
| } | ||
|
|
||
| return groups; | ||
| } | ||
|
|
||
| function getCategoryHeading(category) { | ||
| const headings = { | ||
| breaking: 'Breaking Changes', | ||
| 'deprecation-removal': 'Deprecation Removals', | ||
| warning: 'Warnings', | ||
| }; | ||
| return headings[category] || category; | ||
| } | ||
|
|
||
| function generateMarkdown(sdk, versionConfig, changes) { | ||
| const lines = []; | ||
| const versionName = versionConfig.name || versionConfig.id; | ||
|
|
||
| lines.push(`# Upgrading @clerk/${sdk} to ${versionName}`); | ||
| lines.push(''); | ||
|
|
||
| if (versionConfig.docsUrl) { | ||
| lines.push(`For the full migration guide, see: ${versionConfig.docsUrl}`); | ||
| lines.push(''); | ||
| } | ||
|
|
||
| const grouped = groupByCategory(changes); | ||
| const categoryOrder = ['breaking', 'deprecation-removal', 'warning']; | ||
|
|
||
| for (const category of categoryOrder) { | ||
| const categoryChanges = grouped[category]; | ||
| if (!categoryChanges || categoryChanges.length === 0) { | ||
| continue; | ||
| } | ||
|
|
||
| lines.push(`## ${getCategoryHeading(category)}`); | ||
| lines.push(''); | ||
|
|
||
| for (const change of categoryChanges) { | ||
| lines.push(`### ${change.title}`); | ||
| lines.push(''); | ||
| lines.push(change.content); | ||
| lines.push(''); | ||
| } | ||
| } | ||
|
|
||
| // Handle any categories not in the predefined order | ||
| for (const [category, categoryChanges] of Object.entries(grouped)) { | ||
| if (categoryOrder.includes(category)) { | ||
| continue; | ||
| } | ||
|
|
||
| lines.push(`## ${getCategoryHeading(category)}`); | ||
| lines.push(''); | ||
|
|
||
| for (const change of categoryChanges) { | ||
| lines.push(`### ${change.title}`); | ||
| lines.push(''); | ||
| lines.push(change.content); | ||
| lines.push(''); | ||
| } | ||
| } | ||
|
|
||
| return lines.join('\n'); | ||
| } | ||
|
|
||
| async function main() { | ||
| const { version, sdk } = cli.flags; | ||
|
|
||
| const versionConfig = await loadVersionConfig(version); | ||
| const changes = loadChanges(version, sdk); | ||
|
|
||
| if (changes.length === 0) { | ||
| console.error(`No changes found for ${sdk} in ${version}`); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const markdown = generateMarkdown(sdk, versionConfig, changes); | ||
| console.log(markdown); | ||
| } | ||
|
|
||
| main().catch(error => { | ||
| console.error(error.message); | ||
| process.exit(1); | ||
| }); | ||
9 changes: 9 additions & 0 deletions
9
packages/upgrade/src/__tests__/fixtures/nextjs-v6-scan-issues/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "name": "test-nextjs-v6-scan-issues", | ||
| "version": "1.0.0", | ||
| "dependencies": { | ||
| "@clerk/nextjs": "^6.0.0", | ||
| "next": "^14.0.0", | ||
| "react": "^18.0.0" | ||
| } | ||
| } |
2 changes: 2 additions & 0 deletions
2
packages/upgrade/src/__tests__/fixtures/nextjs-v6-scan-issues/pnpm-lock.yaml
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
30 changes: 30 additions & 0 deletions
30
packages/upgrade/src/__tests__/fixtures/nextjs-v6-scan-issues/src/app.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { ClerkProvider, SignIn, useAuth } from '@clerk/nextjs'; | ||
|
|
||
| export default function App({ children }) { | ||
| return ( | ||
| <ClerkProvider | ||
| appearance={{ | ||
| layout: { | ||
| socialButtonsPlacement: 'bottom', | ||
| }, | ||
| }} | ||
| > | ||
| {children} | ||
| </ClerkProvider> | ||
| ); | ||
| } | ||
|
|
||
| export function SignInPage() { | ||
| return ( | ||
| <SignIn | ||
| afterSignInUrl='/dashboard' | ||
| afterSignUpUrl='/onboarding' | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| export function SamlCallback() { | ||
| const { isSignedIn } = useAuth(); | ||
| // Handle saml callback | ||
| return <div>SAML SSO Callback</div>; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate required frontmatter fields.
The code doesn't validate that
fm.titleexists before using it. If a markdown file is missing thetitlefield in its frontmatter, the generated output will contain "### undefined" (see line 129 wherechange.titleis used).Apply this fix to validate required fields:
for (const file of files) { const filePath = path.join(changesDir, file); const content = fs.readFileSync(filePath, 'utf8'); const parsed = matter(content); const fm = parsed.data; + + if (!fm.title) { + console.warn(`Warning: ${file} is missing a title field, skipping`); + continue; + } const packages = fm.packages || ['*']; const appliesToSdk = packages.includes('*') || packages.includes(sdk);🤖 Prompt for AI Agents