From b6689bd8ddcc9f209b48f8e12f6e9745a0909ec7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Sat, 13 Dec 2025 20:10:20 -0600 Subject: [PATCH 1/4] feat: improve guides --- packages/upgrade/scripts/generate-guide.js | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/upgrade/scripts/generate-guide.js b/packages/upgrade/scripts/generate-guide.js index b434fcdada9..e9e96dea30c 100644 --- a/packages/upgrade/scripts/generate-guide.js +++ b/packages/upgrade/scripts/generate-guide.js @@ -50,7 +50,10 @@ function loadChanges(version, sdk) { return []; } - const files = fs.readdirSync(changesDir).filter(f => f.endsWith('.md')); + const files = fs + .readdirSync(changesDir) + .filter(f => f.endsWith('.md')) + .sort(); const changes = []; for (const file of files) { @@ -94,11 +97,18 @@ function groupByCategory(changes) { function getCategoryHeading(category) { const headings = { + 'behavior-change': 'Behavior Change', breaking: 'Breaking Changes', + deprecation: 'Deprecations', 'deprecation-removal': 'Deprecation Removals', warning: 'Warnings', + version: 'Version', }; - return headings[category] || category; + if (headings[category]) { + return headings[category]; + } + + return category.replace(/[-_]+/g, ' ').replace(/\b\w/g, char => char.toUpperCase()); } function generateMarkdown(sdk, versionConfig, changes) { @@ -114,7 +124,8 @@ function generateMarkdown(sdk, versionConfig, changes) { } const grouped = groupByCategory(changes); - const categoryOrder = ['breaking', 'deprecation-removal', 'warning']; + const categoryOrder = ['breaking', 'deprecation-removal', 'deprecation', 'warning', 'version', 'behavior-change']; + const seenCategories = new Set(); for (const category of categoryOrder) { const categoryChanges = grouped[category]; @@ -122,10 +133,11 @@ function generateMarkdown(sdk, versionConfig, changes) { continue; } + seenCategories.add(category); lines.push(`## ${getCategoryHeading(category)}`); lines.push(''); - for (const change of categoryChanges) { + for (const change of [...categoryChanges].sort((a, b) => a.title.localeCompare(b.title))) { lines.push(`### ${change.title}`); lines.push(''); lines.push(change.content); @@ -134,15 +146,15 @@ function generateMarkdown(sdk, versionConfig, changes) { } // Handle any categories not in the predefined order - for (const [category, categoryChanges] of Object.entries(grouped)) { - if (categoryOrder.includes(category)) { + for (const [category, categoryChanges] of Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))) { + if (seenCategories.has(category)) { continue; } lines.push(`## ${getCategoryHeading(category)}`); lines.push(''); - for (const change of categoryChanges) { + for (const change of [...categoryChanges].sort((a, b) => a.title.localeCompare(b.title))) { lines.push(`### ${change.title}`); lines.push(''); lines.push(change.content); From 784548154316aa2fe32ec2a9c4c89fe588eabf72 Mon Sep 17 00:00:00 2001 From: Jacek Date: Sat, 13 Dec 2025 22:00:46 -0600 Subject: [PATCH 2/4] wip --- packages/upgrade/scripts/generate-guide.js | 53 ++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/upgrade/scripts/generate-guide.js b/packages/upgrade/scripts/generate-guide.js index e9e96dea30c..a222adc484a 100644 --- a/packages/upgrade/scripts/generate-guide.js +++ b/packages/upgrade/scripts/generate-guide.js @@ -23,11 +23,11 @@ const cli = meow( $ 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 }, + sdk: { isRequired: true, type: 'string' }, + version: { isRequired: true, type: 'string' }, }, + importMeta: import.meta, }, ); @@ -101,8 +101,8 @@ function getCategoryHeading(category) { breaking: 'Breaking Changes', deprecation: 'Deprecations', 'deprecation-removal': 'Deprecation Removals', - warning: 'Warnings', version: 'Version', + warning: 'Warnings', }; if (headings[category]) { return headings[category]; @@ -111,6 +111,32 @@ function getCategoryHeading(category) { return category.replace(/[-_]+/g, ' ').replace(/\b\w/g, char => char.toUpperCase()); } +function normalizeSdk(sdk) { + return sdk.replace(/^@clerk\//, ''); +} + +function renderAccordionCategory(lines, category, categoryChanges) { + const sortedChanges = [...categoryChanges].sort((a, b) => a.title.localeCompare(b.title)); + const titles = sortedChanges.map(change => JSON.stringify(change.title)); + + lines.push(`## ${getCategoryHeading(category)}`); + lines.push(''); + lines.push(``); + lines.push(''); + + for (const change of sortedChanges) { + lines.push(' '); + lines.push(''); + lines.push(change.content); + lines.push(''); + lines.push(' '); + lines.push(''); + } + + lines.push(''); + lines.push(''); +} + function generateMarkdown(sdk, versionConfig, changes) { const lines = []; const versionName = versionConfig.name || versionConfig.id; @@ -134,6 +160,11 @@ function generateMarkdown(sdk, versionConfig, changes) { } seenCategories.add(category); + if (category === 'breaking') { + renderAccordionCategory(lines, category, categoryChanges); + continue; + } + lines.push(`## ${getCategoryHeading(category)}`); lines.push(''); @@ -151,6 +182,11 @@ function generateMarkdown(sdk, versionConfig, changes) { continue; } + if (category === 'breaking') { + renderAccordionCategory(lines, category, categoryChanges); + continue; + } + lines.push(`## ${getCategoryHeading(category)}`); lines.push(''); @@ -166,17 +202,18 @@ function generateMarkdown(sdk, versionConfig, changes) { } async function main() { - const { version, sdk } = cli.flags; + const { sdk, version } = cli.flags; + const normalizedSdk = normalizeSdk(sdk); const versionConfig = await loadVersionConfig(version); - const changes = loadChanges(version, sdk); + const changes = loadChanges(version, normalizedSdk); if (changes.length === 0) { - console.error(`No changes found for ${sdk} in ${version}`); + console.error(`No changes found for ${normalizedSdk} in ${version}`); process.exit(1); } - const markdown = generateMarkdown(sdk, versionConfig, changes); + const markdown = generateMarkdown(normalizedSdk, versionConfig, changes); console.log(markdown); } From 2abf1c4c4a8792f478b3190ed1907a6212045810 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 21:26:34 -0600 Subject: [PATCH 3/4] feat(upgrade): enhance generate-guide for clerk-docs MDX format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add SDK display name mapping (nextjs → Next.js, etc.) - Generate proper MDX frontmatter with title/description - Add warning comment for generated files - Properly indent content inside AccordionPanel tags --- packages/upgrade/scripts/generate-guide.js | 49 +++++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/upgrade/scripts/generate-guide.js b/packages/upgrade/scripts/generate-guide.js index a222adc484a..366c8314b1d 100644 --- a/packages/upgrade/scripts/generate-guide.js +++ b/packages/upgrade/scripts/generate-guide.js @@ -9,6 +9,20 @@ import meow from 'meow'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const VERSIONS_DIR = path.join(__dirname, '../src/versions'); +const SDK_DISPLAY_NAMES = { + astro: 'Astro', + 'chrome-extension': 'Chrome Extension', + expo: 'Expo', + express: 'Express', + fastify: 'Fastify', + nextjs: 'Next.js', + nuxt: 'Nuxt', + react: 'React', + 'react-router': 'React Router', + 'tanstack-react-start': 'TanStack Start', + vue: 'Vue', +}; + const cli = meow( ` Usage @@ -115,6 +129,28 @@ function normalizeSdk(sdk) { return sdk.replace(/^@clerk\//, ''); } +function getSdkDisplayName(sdk) { + return SDK_DISPLAY_NAMES[sdk] || sdk; +} + +function indent(text, spaces) { + const padding = ' '.repeat(spaces); + return text + .split('\n') + .map(line => (line.trim() ? padding + line : line)) + .join('\n'); +} + +function generateFrontmatter(sdk, versionName) { + const displayName = getSdkDisplayName(sdk); + return `--- +title: "Upgrading ${displayName} to ${versionName}" +description: "Learn how to upgrade Clerk's ${displayName} SDK to the latest version." +--- + +{/* WARNING: This is a generated file and should not be edited directly. To update its contents, see the "upgrade" package in the clerk/javascript repo. */}`; +} + function renderAccordionCategory(lines, category, categoryChanges) { const sortedChanges = [...categoryChanges].sort((a, b) => a.title.localeCompare(b.title)); const titles = sortedChanges.map(change => JSON.stringify(change.title)); @@ -122,15 +158,11 @@ function renderAccordionCategory(lines, category, categoryChanges) { lines.push(`## ${getCategoryHeading(category)}`); lines.push(''); lines.push(``); - lines.push(''); for (const change of sortedChanges) { lines.push(' '); - lines.push(''); - lines.push(change.content); - lines.push(''); + lines.push(indent(change.content, 4)); lines.push(' '); - lines.push(''); } lines.push(''); @@ -141,14 +173,9 @@ function generateMarkdown(sdk, versionConfig, changes) { const lines = []; const versionName = versionConfig.name || versionConfig.id; - lines.push(`# Upgrading @clerk/${sdk} to ${versionName}`); + lines.push(generateFrontmatter(sdk, 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', 'deprecation', 'warning', 'version', 'behavior-change']; const seenCategories = new Set(); From 9dae6e0faaa9626b63fe19fe3ca4ff6cb6a86f61 Mon Sep 17 00:00:00 2001 From: Jacek Date: Fri, 23 Jan 2026 21:32:29 -0600 Subject: [PATCH 4/4] feat(upgrade): support generating guides for all SDKs - Add --output-dir flag to write files to a directory - Make --sdk optional; when omitted, generates for all SDKs in version config - Output summary of generated files when using --output-dir --- packages/upgrade/scripts/generate-guide.js | 79 +++++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/upgrade/scripts/generate-guide.js b/packages/upgrade/scripts/generate-guide.js index 366c8314b1d..efbd1d0a02f 100644 --- a/packages/upgrade/scripts/generate-guide.js +++ b/packages/upgrade/scripts/generate-guide.js @@ -26,19 +26,24 @@ const SDK_DISPLAY_NAMES = { const cli = meow( ` Usage - $ pnpm run generate-guide --version= --sdk= + $ pnpm run generate-guide --version= [--sdk=] [--output-dir=] Options - --version Version directory to use (e.g., core-3) - --sdk SDK to generate guide for (e.g., nextjs, react, expo) + --version Version directory to use (e.g., core-3) + --sdk SDK to generate guide for (e.g., nextjs, react, expo) + If omitted, generates guides for all SDKs + --output-dir Directory to write generated files to + If omitted, outputs to stdout (single SDK only) Examples $ pnpm run generate-guide --version=core-3 --sdk=nextjs $ pnpm run generate-guide --version=core-3 --sdk=react > react-guide.md + $ pnpm run generate-guide --version=core-3 --output-dir=./guides `, { flags: { - sdk: { isRequired: true, type: 'string' }, + outputDir: { type: 'string' }, + sdk: { type: 'string' }, version: { isRequired: true, type: 'string' }, }, importMeta: import.meta, @@ -228,20 +233,72 @@ function generateMarkdown(sdk, versionConfig, changes) { return lines.join('\n'); } +function generateGuideForSdk(sdk, version, versionConfig) { + const changes = loadChanges(version, sdk); + + if (changes.length === 0) { + return null; + } + + return generateMarkdown(sdk, versionConfig, changes); +} + +function writeGuideToFile(outputDir, sdk, content) { + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + const filePath = path.join(outputDir, `${sdk}.mdx`); + fs.writeFileSync(filePath, content); + return filePath; +} + async function main() { - const { sdk, version } = cli.flags; - const normalizedSdk = normalizeSdk(sdk); + const { outputDir, sdk, version } = cli.flags; const versionConfig = await loadVersionConfig(version); - const changes = loadChanges(version, normalizedSdk); - if (changes.length === 0) { - console.error(`No changes found for ${normalizedSdk} in ${version}`); + // Determine which SDKs to generate + const sdksToGenerate = sdk ? [normalizeSdk(sdk)] : Object.keys(versionConfig.sdkVersions || {}); + + if (sdksToGenerate.length === 0) { + console.error(`No SDKs found in version config for ${version}`); process.exit(1); } - const markdown = generateMarkdown(normalizedSdk, versionConfig, changes); - console.log(markdown); + // If multiple SDKs and no output dir, require output dir + if (sdksToGenerate.length > 1 && !outputDir) { + console.error('--output-dir is required when generating multiple SDK guides'); + console.error(`SDKs to generate: ${sdksToGenerate.join(', ')}`); + process.exit(1); + } + + const results = []; + + for (const currentSdk of sdksToGenerate) { + const markdown = generateGuideForSdk(currentSdk, version, versionConfig); + + if (!markdown) { + console.error(`No changes found for ${currentSdk} in ${version}, skipping...`); + continue; + } + + if (outputDir) { + const filePath = writeGuideToFile(outputDir, currentSdk, markdown); + results.push({ sdk: currentSdk, filePath }); + } else { + // Single SDK, output to stdout + console.log(markdown); + } + } + + if (outputDir && results.length > 0) { + console.log(`\nGenerated ${results.length} guide(s):`); + for (const { sdk: generatedSdk, filePath } of results) { + const displayName = getSdkDisplayName(generatedSdk); + console.log(` ${displayName}: ${filePath}`); + } + } } main().catch(error => {