diff --git a/packages/dev/codemods/src/s1-to-s2/README.md b/packages/dev/codemods/src/s1-to-s2/README.md index 0751b1c1a80..fbc71b472cb 100644 --- a/packages/dev/codemods/src/s1-to-s2/README.md +++ b/packages/dev/codemods/src/s1-to-s2/README.md @@ -9,6 +9,9 @@ Run `npx @react-spectrum/codemods s1-to-s2` from the directory you want to upgra ### Options - `-c, --components `: Comma separated list of components to upgrade (ex: `Button,TableView`). If not specified, all available components will be upgraded. +- `--path `: The path to the directory to run the codemod in. Defaults to the current directory (`.`). +- `-d, --dry`: Run the codemod without writing any changes to disk. Use this to preview migrations before applying. +- `--agent`: Run in non-interactive mode. Skips interactive prompts, package installation, and macro setup. Required when running in CI or from an agent tool. Note: `@react-spectrum/s2` must still be installed and resolvable. ## How it works diff --git a/packages/dev/codemods/src/s1-to-s2/UPGRADE.md b/packages/dev/codemods/src/s1-to-s2/UPGRADE.md index 185ec8886a9..360b97efd7c 100644 --- a/packages/dev/codemods/src/s1-to-s2/UPGRADE.md +++ b/packages/dev/codemods/src/s1-to-s2/UPGRADE.md @@ -265,7 +265,7 @@ Example: ### Border width -Affected style props: `borderWidth`, `borderStartWidth`, `borderEndWidth`, `borderTopWidth`, `orderBottomWidth`, `borderXWidth`, `borderYWidth`. +Affected style props: `borderWidth`, `borderStartWidth`, `borderEndWidth`, `borderTopWidth`, `borderBottomWidth`, `borderXWidth`, `borderYWidth`. Example: diff --git a/packages/dev/s2-docs/migration-references/focused-manual-fixes.md b/packages/dev/s2-docs/migration-references/focused-manual-fixes.md new file mode 100644 index 00000000000..d695eca7183 --- /dev/null +++ b/packages/dev/s2-docs/migration-references/focused-manual-fixes.md @@ -0,0 +1,143 @@ +# Manual fixes after the codemod + +## Icons and illustrations + +- If the codemod leaves `TODO(S2-upgrade)` next to an icon or illustration import, pick the nearest S2 replacement manually. + +## Layout components + +`Flex`, `Grid`, `View`, and `Well` are not part of S2. These should be updated to `div` elements styled with the macro. + +### Flex example + +Before: + +```jsx + +
Item 1
+
Item 2
+
Item 3
+
+``` + +After: + +```jsx +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +
+
Item 1
+
Item 2
+
Item 3
+
+``` + +### Grid example + +Before: + +```jsx + +
Item 1
+
Item 2
+
Item 3
+
+``` + +After: + +```jsx +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +
+
Item 1
+
Item 2
+
Item 3
+
+``` + +### View example + +Before: + +```jsx + + Content + +``` + +After: + +```jsx +
+ Content +
+``` + +### Well example + +Before: + +```jsx + + Content + +``` + +After: + +```jsx +import {style} from '@react-spectrum/s2/style' with {type: 'macro'}; + +
+ Content +
+``` + +## UNSAFE_style and UNSAFE_className + +Move `UNSAFE_style` usage to the S2 style macro when possible. + +Move `UNSAFE_className` usage to the S2 style macro when possible. + +Reference the S2 styling docs to see the supported CSS properties. + +## Dialogs + +- `DialogContainer` and `useDialogContainer` still exist in S2, but the dismiss logic may need to move between `Dialog`, `DialogTrigger`, and `DialogContainer`. See the S2 Dialog documentation for more details. + +## Collections + +- When `Item` survives the codemod, rename it based on its parent component: + + | Parent component | v3 child | S2 child | + |---|---|---| + | Menu / ActionMenu | Item | MenuItem | + | Picker | Item | PickerItem | + | ComboBox | Item | ComboBoxItem | + | Tabs | Item | Tab / TabPanel | + | TagGroup | Item | Tag | + | Breadcrumbs | Item | Breadcrumb | + +- Preserve React `key` when mapping arrays, but ensure collection data items expose `id` when S2 expects it. See the S2 Collections documentation for more details. +- Table and ListView migrations often need manual review for row headers, nested columns, and explicit item ids. + +## Toast migration + +- Move `ToastContainer` and `ToastQueue` imports from `@react-spectrum/toast` to `@react-spectrum/s2`. +- Keep a shared `ToastContainer` mounted near the app root or test harness, then update all queue calls to use the S2 import path. +- S2 supports `ToastQueue.neutral`, `positive`, `negative`, and `info`. +- Re-check options such as `timeout`, `actionLabel`, `onAction`, `shouldCloseOnAction`, and `onClose` after the import move. +- The queue methods still return a close function. Keep programmatic dismissal logic when the existing UX depends on it. +- Search for every `ToastContainer` mount and every `ToastQueue` usage after moving imports. Shared app roots, secondary entrypoints, and test harnesses are easy to miss. diff --git a/packages/dev/s2-docs/migration-references/focused-prerequisites.md b/packages/dev/s2-docs/migration-references/focused-prerequisites.md new file mode 100644 index 00000000000..5a31a1d0200 --- /dev/null +++ b/packages/dev/s2-docs/migration-references/focused-prerequisites.md @@ -0,0 +1,23 @@ +# Inspection checklist + +## Minimum tool versions + +These tools are not all strictly required, but if the project uses them they must be at these minimum versions to avoid issues with the `with {type: 'macro'}` import syntax: + +- **TypeScript 5.3+** — required for the import attributes syntax (`with {type: 'macro'}`). +- **Babel 7.27.0+** or the `@babel/plugin-syntax-import-attributes` plugin — enables Babel to parse import attributes. Alternatively, `@babel/preset-env` with `shippedProposals: true` also enables import attribute parsing. +- **ESLint 9.14.0+** with `@typescript-eslint/parser`. +- **Prettier 3.1.1+** — needed to format `with {type: 'macro'}` import syntax correctly. + +## What to look for + +- Search package manifests and source for `@adobe/react-spectrum`, `@react-spectrum/*`, and `@spectrum-icons/*`. +- In monorepos or mixed-tooling repos, inspect the target package or app first instead of assuming the root manifest represents the runtime target being migrated. +- Determine the package manager from the relevant lockfile or workspace setup. +- Detect the bundler at the migration target level. The workspace root may include Storybook, Vite, or other tooling that does not represent the runtime bundler for the package being migrated. + - **Parcel v2.12.0+** already supports S2 style macros natively. + - **Vite, webpack, Next.js, Rollup, ESBuild** and similar toolchains need `unplugin-parcel-macros`. Keep plugin ordering correct so macros run before the rest of the toolchain. + - If the repo already has a framework-specific S2 or macro setup, preserve it instead of layering a second macro configuration on top. +- Find **all** app entrypoints, including standalone pages, alternate render roots, embedded sub-apps, utility apps, and test-only render targets. Do not assume there is only one entry. +- Locate root providers, shared test wrappers, toast setup, and any direct `defaultTheme` usage. +- Search for `ToastContainer`, `ToastQueue`, `DialogContainer`, `useDialogContainer`, `ClearSlots`, style props, and `UNSAFE_style`. These are common follow-up areas after the codemod. diff --git a/packages/dev/s2-docs/package.json b/packages/dev/s2-docs/package.json index b4b78e88b0b..eb14a2a3041 100644 --- a/packages/dev/s2-docs/package.json +++ b/packages/dev/s2-docs/package.json @@ -49,6 +49,8 @@ "json5": "^2.2.3", "lz-string": "^1.5.0", "markdown-to-jsx": "^6.11.0", + "mdast-util-mdx": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0", "react": "^19.2.0", "react-aria": "^3.40.0", "react-aria-components": "^1.7.1", diff --git a/packages/dev/s2-docs/pages/s2/migrating.mdx b/packages/dev/s2-docs/pages/s2/migrating.mdx index fceb6c4b197..6aa840acef4 100644 --- a/packages/dev/s2-docs/pages/s2/migrating.mdx +++ b/packages/dev/s2-docs/pages/s2/migrating.mdx @@ -2,6 +2,7 @@ import {Layout} from '../../src/Layout'; import {PendingBadge} from '../../src/PendingBadge'; import {InstallCommand} from '../../src/InstallCommand'; import {StaticTable} from '../../src/StaticTable'; +import {Command} from '../../src/Command'; export default Layout; export const section = 'Guides'; @@ -12,6 +13,18 @@ export const description = 'How to migrate from React Spectrum v3 to Spectrum 2. Learn how to migrate from React Spectrum v3 to Spectrum 2. +## AI-assisted migration (recommended) + +If you're using an AI coding tool that supports [Agent Skills](https://agentskills.io/home), React Spectrum now includes a dedicated skill for guiding v3 to S2 upgrades. + +To install the migration skill and the general S2 skill, run: + + + +Then ask your agent to use the `migrate-react-spectrum-v3-to-s2` skill to migrate your project. + +## Command-line tool + An automated upgrade assistant is available by running the following command in the project you want to upgrade: @@ -25,6 +38,7 @@ The following arguments are also available: - `--path` - Path to apply the upgrade changes to. Defaults to the current directory (`.`) - `--dry` - Runs the upgrade assistant without making changes to components - `--ignore-pattern` - Ignore files that match the provided glob expression. Defaults to `'**/node_modules/**'` +- `--agent` - Runs the upgrade assistant non-interactively for AI tools. This skips prompts, package installation, and macro setup, so `@react-spectrum/s2` must already be installed and resolvable For cases that the upgrade assistant doesn't handle automatically or where you'd rather upgrade some components manually, use the guide below. diff --git a/packages/dev/s2-docs/scripts/generateAgentSkills.mjs b/packages/dev/s2-docs/scripts/generateAgentSkills.mjs index e9e019140bf..aa7aecf2303 100644 --- a/packages/dev/s2-docs/scripts/generateAgentSkills.mjs +++ b/packages/dev/s2-docs/scripts/generateAgentSkills.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** - * Generates Agent Skills for React Spectrum (S2) and React Aria. + * Generates Agent Skills for React Spectrum (S2), migration, and React Aria. * * This script creates skills in the Agent Skills format (https://agentskills.io/specification) * @@ -27,6 +27,7 @@ const REPO_ROOT = path.resolve(__dirname, '../../../../'); const MARKDOWN_DOCS_DIST = path.join(REPO_ROOT, 'packages/dev/s2-docs/dist'); const MDX_PAGES_DIR = path.join(REPO_ROOT, 'packages/dev/s2-docs/pages'); const MARKDOWN_DOCS_SCRIPT = path.join(__dirname, 'generateMarkdownDocs.mjs'); +const MIGRATION_REFS_DIR = path.join(REPO_ROOT, 'packages/dev/s2-docs/migration-references'); const WELL_KNOWN_DIR = '.well-known'; const WELL_KNOWN_SKILLS_DIR = 'skills'; @@ -45,6 +46,20 @@ const SKILLS = { website: 'https://react-spectrum.adobe.com/' } }, + 'migrate-react-spectrum-v3-to-s2': { + name: 'migrate-react-spectrum-v3-to-s2', + description: + 'Upgrade React Spectrum v3 (Spectrum 1) codebases to React Spectrum S2. Use when developers mention migrating or upgrading from React Spectrum v3, Spectrum 1, S1, @adobe/react-spectrum, @react-spectrum/* packages, or codemod-assisted upgrades to @react-spectrum/s2.', + kind: 'migration', + license: 'Apache-2.0', + sourceDir: 's2', + compatibility: + 'Requires a React project currently using React Spectrum v3, @react-spectrum/* packages, or related React Spectrum v3 helpers.', + metadata: { + author: 'Adobe', + website: 'https://react-spectrum.adobe.com/' + } + }, 'react-aria': { name: 'react-aria', description: @@ -283,23 +298,25 @@ function categorizeEntries(entries, sourceDir) { return categories; } -/** - * Generate the SKILL.md content - */ -function generateSkillMd(skillConfig, categories, isS2) { - const frontmatter = `--- -name: ${skillConfig.name} -description: ${skillConfig.description} -license: ${skillConfig.license} -compatibility: ${skillConfig.compatibility} +function generateFrontmatter(skillConfig) { + return `--- +name: "${skillConfig.name}" +description: "${skillConfig.description}" +license: "${skillConfig.license}" +compatibility: "${skillConfig.compatibility}" metadata: - author: ${skillConfig.metadata.author} - website: ${skillConfig.metadata.website} + author: "${skillConfig.metadata.author}" + website: "${skillConfig.metadata.website}" --- `; +} - let content = frontmatter; +/** + * Generate the SKILL.md content + */ +function generateDocsSkillMd(skillConfig, categories, isS2) { + let content = generateFrontmatter(skillConfig); if (isS2) { content += `# React Spectrum S2 (Spectrum 2) @@ -386,10 +403,125 @@ The \`references/\` directory contains detailed documentation organized as follo return content.trimEnd() + '\n'; } +function generateMigrationSkillMd(skillConfig) { + return `${generateFrontmatter(skillConfig)}# React Spectrum v3 to S2 migration + +Upgrade React Spectrum v3 codebases to S2 by following these eight steps in order. + +## Scope + +This skill covers only the React Spectrum v3 (S1) to S2 migration. Do **not** perform major dependency upgrades such as React version bumps (e.g. React 16→17, 17→18, 18→19) as part of this migration. If the project needs a major dependency upgrade, note it as a recommended follow-up in the final report (Step 8) rather than attempting it during migration. + +## Step 1: Inspect the codebase + +- Search package manifests for \`@adobe/react-spectrum\`, \`@react-spectrum/*\`, and \`@spectrum-icons/*\`. +- Note the package manager (npm, yarn, pnpm) from the lockfile. +- Identify the bundler used by the migration target (Parcel, Vite, webpack, Next.js, Rollup, ESBuild). +- In monorepos, inspect the specific package or app being migrated rather than the workspace root. +- Find app entrypoints, root providers, shared test wrappers, toast setup, and any \`defaultTheme\` usage. + +See [Prerequisites](references/focused-prerequisites.md) for the full inspection checklist and minimum tool versions. + +## Step 2: Install @react-spectrum/s2 + +Install the S2 package with the project's package manager: + +\`\`\`bash +npm install @react-spectrum/s2 +yarn add @react-spectrum/s2 +pnpm add @react-spectrum/s2 +\`\`\` + +If the bundler is not Parcel v2.12.0+, also install and configure \`unplugin-parcel-macros\` as a dev dependency. See [Getting started](references/docs-getting-started.md) for bundler-specific setup instructions. + +## Step 3: Dry-run the codemod + +Preview what the codemod will change before applying: + +\`\`\`bash +npx @react-spectrum/codemods s1-to-s2 --agent --dry +yarn dlx @react-spectrum/codemods s1-to-s2 --agent --dry +pnpm dlx @react-spectrum/codemods s1-to-s2 --agent --dry +\`\`\` + +Use \`npx\` for npm/Yarn 1, \`yarn dlx\` for Yarn Berry/PnP, \`pnpm dlx\` for pnpm. +Add \`--path \` for monorepos or partial rollouts. +Add \`--components A,B\` only when explicitly requested for incremental migration. + +Review the dry-run output to understand the scope of changes. + +## Step 4: Run the codemod + +Execute the codemod to transform the source files: + +\`\`\`bash +npx @react-spectrum/codemods s1-to-s2 --agent +yarn dlx @react-spectrum/codemods s1-to-s2 --agent +pnpm dlx @react-spectrum/codemods s1-to-s2 --agent +\`\`\` + +Use the same \`--path\` and \`--components\` flags as the dry run if applicable. + +## Step 5: Format with the project's formatter + +If the project has a formatter (Prettier, ESLint, Biome, Oxfmt, etc.), run it on the changed files to remove extraneous formatting changes introduced by the codemod. + +## Step 6: Fix remaining TODO(S2-upgrade) comments + +Search the codebase for \`TODO(S2-upgrade)\` comments left by the codemod. Each one marks a change that requires manual review. + +See [Focused manual fixes](references/focused-manual-fixes.md) for information on how to fix these. + +Also reference the \`react-spectrum-s2\` skill (if available) for full S2 component documentation when needed. + +## Step 7: Validate + +Run the project's own toolchain to verify the migration is complete: + +1. Install dependencies if package manifests changed. +2. Run the typecheck or compile step (e.g. \`tsc --noEmit\`, \`tsc -b\`). +3. Run tests covering the migrated code. Prefer the narrowest test scope that covers the changed files. +4. Run the build to confirm the output is intact. + +In monorepos, validate the affected package first with its own scripts before running workspace-wide checks. Fix any failures before declaring the migration complete. + +## Step 8: Generate final report + +After the migration is complete, produce a final report for the user with the following sections: + +### Summary of changes +- Packages added and removed. +- What the codemod changed (files affected, components migrated). +- Manual fixes applied (layout components, icons, dialogs, collections, toast, etc.). + +### Remaining issues +- Any unresolved \`TODO(S2-upgrade)\` comments. +- Type errors, test failures, or known gaps that still need attention. + +### Recommended follow-ups +- If the project is not on **React 19**, recommend upgrading. React 19 is recommended for S2. Include the relevant upgrade guide links: + - React 17: https://legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html + - React 18: https://react.dev/blog/2022/03/08/react-18-upgrade-guide + - React 19: https://react.dev/blog/2024/04/25/react-19-upgrade-guide +- Any other major upgrades (e.g. React, bundler, etc.) that were out of scope for this migration. +- Any additional cleanup or improvements the user may want to address. + +## Deep reference + +Use these when you need more component-by-component or API-level detail: +- [Migration guide](references/docs-migrating.md): comprehensive component-by-component migration reference. +- [Getting started](references/docs-getting-started.md): framework setup and macro configuration. +- [Provider](references/docs-provider.md): locale, router, color-scheme, and SSR usage. +- [Styling](references/docs-styling.md): style macro overview including runtime conditions, CSS variables, CSS optimization, and CSS resets. +- [Style macro](references/docs-style-macro.md): exact style macro syntax and constraints. +- [Toast](references/docs-toast.md): full S2 toast API and examples. +`.trimEnd() + '\n'; +} + /** * Copy documentation files to the skill's references directory */ -function copyDocumentation(skillConfig, categories, skillDir) { +function copyDocsDocumentation(skillConfig, categories, skillDir) { const refsDir = path.join(skillDir, 'references'); const sourceDir = path.join(MARKDOWN_DOCS_DIST, skillConfig.sourceDir); @@ -463,6 +595,49 @@ function copyDocumentation(skillConfig, categories, skillDir) { } } +function copyFocusedDocs(sourceDir, skillDir, docs) { + for (const [sourceName, outputName] of docs) { + const sourcePath = path.join(MARKDOWN_DOCS_DIST, sourceDir, sourceName); + if (!fs.existsSync(sourcePath)) { + console.warn(`Warning: expected migration reference not found: ${sourcePath}`); + continue; + } + + const outputPath = path.join(skillDir, 'references', outputName); + fs.mkdirSync(path.dirname(outputPath), {recursive: true}); + fs.copyFileSync(sourcePath, outputPath); + } +} + +function writeMigrationReferences(skillDir, sourceDir) { + // Copy focused reference docs from source files + const focusedRefs = [ + 'focused-prerequisites.md', + 'focused-manual-fixes.md' + ]; + + for (const filename of focusedRefs) { + const sourcePath = path.join(MIGRATION_REFS_DIR, filename); + if (!fs.existsSync(sourcePath)) { + console.warn(`Warning: expected migration reference not found: ${sourcePath}`); + continue; + } + + const outputPath = path.join(skillDir, 'references', filename); + fs.mkdirSync(path.dirname(outputPath), {recursive: true}); + fs.copyFileSync(sourcePath, outputPath); + } + + copyFocusedDocs(sourceDir, skillDir, [ + ['migrating.md', 'docs-migrating.md'], + ['getting-started.md', 'docs-getting-started.md'], + ['Provider.md', 'docs-provider.md'], + ['styling.md', 'docs-styling.md'], + ['style-macro.md', 'docs-style-macro.md'], + ['Toast.md', 'docs-toast.md'] + ]); +} + function collectSkillFiles(skillDir) { const files = []; @@ -494,6 +669,37 @@ function collectSkillFiles(skillDir) { }); } +/** + * Validate that all references/ links in SKILL.md resolve to actual files. + * Throws if any broken links are found. + */ +function validateSkillLinks(skillDir) { + const skillMdPath = path.join(skillDir, 'SKILL.md'); + if (!fs.existsSync(skillMdPath)) { + return; + } + + const content = fs.readFileSync(skillMdPath, 'utf8'); + const linkPattern = /\[([^\]]*)\]\((references\/[^)]+)\)/g; + const broken = []; + + let match; + while ((match = linkPattern.exec(content)) !== null) { + const linkText = match[1]; + const linkPath = match[2]; + const resolvedPath = path.join(skillDir, linkPath); + if (!fs.existsSync(resolvedPath)) { + broken.push(`"${linkText}" -> ${linkPath}`); + } + } + + if (broken.length > 0) { + throw new Error( + `Broken references in ${path.relative(REPO_ROOT, skillMdPath)}:\n ${broken.join('\n ')}` + ); + } +} + function writeIndexJson(wellKnownRoot, skills) { const indexPath = path.join(wellKnownRoot, 'index.json'); const payload = {skills}; @@ -506,11 +712,27 @@ function writeIndexJson(wellKnownRoot, skills) { */ function generateSkill(skillConfig, wellKnownRoot) { const skillDir = path.join(wellKnownRoot, skillConfig.name); - const isS2 = skillConfig.name === 'react-spectrum-s2'; // Create skill directory fs.mkdirSync(skillDir, {recursive: true}); + if (skillConfig.kind === 'migration') { + const skillMdContent = generateMigrationSkillMd(skillConfig); + fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMdContent); + console.log( + `Generated ${path.relative(REPO_ROOT, path.join(skillDir, 'SKILL.md'))}` + ); + + writeMigrationReferences(skillDir, skillConfig.sourceDir); + console.log( + `Copied migration references to ${path.relative(REPO_ROOT, path.join(skillDir, 'references'))}` + ); + + return skillDir; + } + + const isS2 = skillConfig.name === 'react-spectrum-s2'; + // Parse documentation entries const llmsTxtPath = path.join( MARKDOWN_DOCS_DIST, @@ -526,14 +748,14 @@ function generateSkill(skillConfig, wellKnownRoot) { const categories = categorizeEntries(entries, skillConfig.sourceDir); // Generate SKILL.md - const skillMdContent = generateSkillMd(skillConfig, categories, isS2); + const skillMdContent = generateDocsSkillMd(skillConfig, categories, isS2); fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMdContent); console.log( `Generated ${path.relative(REPO_ROOT, path.join(skillDir, 'SKILL.md'))}` ); // Copy documentation to references - copyDocumentation(skillConfig, categories, skillDir); + copyDocsDocumentation(skillConfig, categories, skillDir); console.log( `Copied documentation to ${path.relative(REPO_ROOT, path.join(skillDir, 'references'))}` ); @@ -542,7 +764,7 @@ function generateSkill(skillConfig, wellKnownRoot) { } -async function main() { +function main() { console.log( 'Generating Agent Skills for React Spectrum (S2) and React Aria...\n' ); @@ -569,12 +791,17 @@ async function main() { for (const config of skills) { console.log(`\nGenerating skill: ${config.name}`); const skillDir = generateSkill(config, wellKnownRoot); + validateSkillLinks(skillDir); const files = collectSkillFiles(skillDir); - indexEntries.push({ + const entry = { name: config.name, description: config.description, files - }); + }; + if (config.kind) { + entry.kind = config.kind; + } + indexEntries.push(entry); } writeIndexJson(wellKnownRoot, indexEntries); @@ -586,7 +813,9 @@ async function main() { console.log('\nAgent Skills generation complete!'); } -main().catch((err) => { +try { + main(); +} catch (err) { console.error(err); process.exit(1); -}); +} diff --git a/packages/dev/s2-docs/scripts/generateMarkdownDocs.mjs b/packages/dev/s2-docs/scripts/generateMarkdownDocs.mjs index 4ade0b35756..2fcc0a6f679 100644 --- a/packages/dev/s2-docs/scripts/generateMarkdownDocs.mjs +++ b/packages/dev/s2-docs/scripts/generateMarkdownDocs.mjs @@ -4,11 +4,12 @@ import * as babel from '@babel/parser'; import {fileURLToPath} from 'url'; import fs from 'fs'; import glob from 'fast-glob'; +import {mdxToMarkdown} from 'mdast-util-mdx'; import path from 'path'; import {Project} from 'ts-morph'; import remarkMdx from 'remark-mdx'; import remarkParse from 'remark-parse'; -import remarkStringify from 'remark-stringify'; +import {toMarkdown} from 'mdast-util-to-markdown'; import {unified} from 'unified'; import {visit} from 'unist-util-visit'; @@ -3222,14 +3223,17 @@ async function main() { .use(remarkParse) .use(remarkMdx) .use(remarkRemoveImportsExports) - .use(remarkDocsComponentsToMarkdown) - .use(remarkStringify, { - fences: true, - bullets: '-', - listItemIndent: 'one' - }); - - let markdown = String(await processor.process({value: mdContent, path: filePath})); + .use(remarkDocsComponentsToMarkdown); + + const file = {value: mdContent, path: filePath}; + const tree = processor.parse(file); + const transformed = await processor.run(tree, file); + let markdown = toMarkdown(transformed, { + fences: true, + bullet: '-', + listItemIndent: 'one', + extensions: mdxToMarkdown.extensions + }); // Convert markdown links ending in .html to .md (relative links only) markdown = markdown.replace(/\[([^\]]+)\]\(([^)]+\.html)\)/g, (match, text, url) => { diff --git a/yarn.lock b/yarn.lock index fd94d936c89..42216ae15c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7171,6 +7171,8 @@ __metadata: json5: "npm:^2.2.3" lz-string: "npm:^1.5.0" markdown-to-jsx: "npm:^6.11.0" + mdast-util-mdx: "npm:^1.0.0" + mdast-util-to-markdown: "npm:^1.0.0" playwright: "npm:^1.57.0" react: "npm:^19.2.0" react-aria: "npm:^3.40.0"