diff --git a/.circleci/config.yml b/.circleci/config.yml
index 784758beed8be8..5677ba8b5de42f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -219,11 +219,6 @@ jobs:
command: |
pnpm extract-error-codes
git add -A && git diff --exit-code --staged
- - run:
- name: '`pnpm docs:link-check` changes committed?'
- command: |
- pnpm docs:link-check
- git add -A && git diff --exit-code --staged
test_types:
<<: *default-job
resource_class: 'medium+'
diff --git a/docs/.link-check-errors.txt b/docs/.link-check-errors.txt
deleted file mode 100644
index 0b8d083f4a0077..00000000000000
--- a/docs/.link-check-errors.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Broken links found by `pnpm docs:link-check` that exist:
-
diff --git a/docs/package.json b/docs/package.json
index fcbd0fb6766c3c..145730b0ae0fca 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -3,7 +3,7 @@
"private": true,
"license": "MIT",
"scripts": {
- "build": "rimraf ./export && cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=8192 next build && pnpm build-sw",
+ "build": "rimraf ./export && cross-env NODE_ENV=production NODE_OPTIONS=--max_old_space_size=8192 next build && pnpm build-sw && pnpm link-check",
"build:clean": "rimraf .next && pnpm build",
"build-sw": "node ./scripts/buildServiceWorker.js",
"dev": "next dev",
@@ -14,7 +14,7 @@
"typescript": "tsc -p tsconfig.json && tsc -p scripts/tsconfig.json",
"typescript:transpile": "echo 'Use `pnpm docs:typescript:formatted'` instead && exit 1",
"typescript:transpile:dev": "echo 'Use `pnpm docs:typescript'` instead && exit 1",
- "link-check": "tsx ./scripts/reportBrokenLinks.ts"
+ "link-check": "tsx ./scripts/reportBrokenLinks.mts"
},
"dependencies": {
"@babel/core": "^7.28.5",
@@ -130,7 +130,6 @@
"chai": "^6.0.1",
"cross-fetch": "^4.1.0",
"gm": "^1.25.1",
- "marked": "^16.2.0",
"prettier": "^3.6.2",
"tailwindcss": "^4.1.17",
"yargs": "^18.0.0"
diff --git a/docs/pages/blog/2020-q3-update.md b/docs/pages/blog/2020-q3-update.md
index 55825b5eca1382..e337e77594b511 100644
--- a/docs/pages/blog/2020-q3-update.md
+++ b/docs/pages/blog/2020-q3-update.md
@@ -92,7 +92,7 @@ Here are the most significant improvements since June 2020. This was a dense qua
-- 💅 We have completed the first iteration of the unstyled components for v5.
You can find a [new version](/material-ui/react-slider/#unstyled) of the slider in the lab without any styles.
+- 💅 We have completed the first iteration of the unstyled components for v5.
You can find a [new version](https://v5.mui.com/material-ui/react-slider/#unstyled) of the slider in the lab without any styles.
The unstyled component weighs in at [5.2 kB gzipped](https://bundlephobia.com/package/@mui/lab@5.0.0-alpha.12), compared with 26 kB for the styled version (when used standalone). The component is best suited for use when you want to fully customize the look, without reimplementing the JavaScript and accessibility logic.
We're also pushing in this direction to address a concern we hear from large enterprises
that want to be able to go one layer down in the abstraction, in order to gain more control.
@@ -101,10 +101,10 @@ Here are the most significant improvements since June 2020. This was a dense qua
import SliderUnstyled from '@mui/lab/SliderUnstyled';
```
- Note that we have experimented with headless components (hooks only) in the past. For instance, you can leverage the [useAutocomplete](/material-ui/react-autocomplete/#useautocomplete), and [usePagination](/material-ui/react-pagination/#usepagination) hooks. However, we are pushing with unstyled first as a required step for the next item: ⬇️.
+ Note that we have experimented with headless components (hooks only) in the past. For instance, you can leverage the [useAutocomplete](https://v5.mui.com/material-ui/react-autocomplete/#useautocomplete), and [usePagination](https://v5.mui.com/material-ui/react-pagination/#usepagination) hooks. However, we are pushing with unstyled first as a required step for the next item: ⬇️.
- 👩🎨 We have completed the first iteration of the new styling solution of v5.
- You can find a [new version](/material-ui/react-slider/) of the slider in the lab powered by [Emotion](https://emotion.sh/docs/introduction).
+ You can find a [new version](https://v5.mui.com/material-ui/react-slider/) of the slider in the lab powered by [Emotion](https://emotion.sh/docs/introduction).
If you are already using styled-components in your application, you can swap Emotion for styled-components 💅. Check this [CodeSandbox](https://codesandbox.io/p/sandbox/sliderstyled-with-styled-components-forked-olc27?file=/package.json) or [CRA](https://github.com/mui/material-ui/tree/HEAD/examples/material-ui-cra-styled-components/) for a demo. It relies on aliases to prevent any bundle size overhead.
The new styling solution saves 2kB+ gzipped in the bundle compared to JSS, and about 14 kB gzipped if you were already using styled-components or Emotion.
Last but not least, this change allows us to take advantage of dynamic style props. We will use them for dynamic color props, variant props, and new style props available in the core components.
diff --git a/docs/pages/blog/2021-q3-update.md b/docs/pages/blog/2021-q3-update.md
index 9cc5e786ba25b3..d28483c54297c9 100644
--- a/docs/pages/blog/2021-q3-update.md
+++ b/docs/pages/blog/2021-q3-update.md
@@ -44,43 +44,43 @@ Here are the most significant improvements since early July 2021.
After
-- [Masonry](/material-ui/react-masonry/). We introduced a new component for use when the `Grid` component leads to wasted space. It's frequently used in dashboards.
+- [Masonry](https://v5.mui.com/material-ui/react-masonry/). We introduced a new component for use when the `Grid` component leads to wasted space. It's frequently used in dashboards.
- We introduced a new [package of components without styles](https://www.npmjs.com/package/@mui/base), laying the foundations for supporting multiple design systems with headless components.
While it was tough to balance the time between working on v5 stable and developing the unstyled components, we still managed to introduce the first few:
- - [Autocomplete](/material-ui/react-autocomplete/#useautocomplete)
+ - [Autocomplete](https://v5.mui.com/material-ui/react-autocomplete/#useautocomplete)
```jsx
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
```
- - [Button](/material-ui/react-button/#unstyled)
+ - [Button](https://v5.mui.com/material-ui/react-button/#unstyled)
```jsx
import { useButton } from '@mui/base/ButtonUnstyled';
```
- - [Modal](/material-ui/react-modal/#unstyled)
+ - [Modal](https://v5.mui.com/material-ui/react-modal/#unstyled)
```jsx
import { ModalUnstyled } from '@mui/base/ModalUnstyled';
```
- - [Slider](/material-ui/react-slider/#unstyled)
+ - [Slider](https://v5.mui.com/material-ui/react-slider/#unstyled)
```jsx
import { SliderUnstyled } from '@mui/base/SliderUnstyled';
```
- - [Switch](/material-ui/react-switch/#unstyled)
+ - [Switch](https://v5.mui.com/material-ui/react-switch/#unstyled)
```jsx
import { useSwitch } from '@mui/base/SwitchUnstyled';
```
- - [Portal](/material-ui/react-portal/#unstyled)
+ - [Portal](https://v5.mui.com/material-ui/react-portal/#unstyled)
```jsx
import { Portal } from '@mui/base/Portal';
diff --git a/docs/pages/blog/2022-tenerife-retreat.md b/docs/pages/blog/2022-tenerife-retreat.md
index b93b9082451f56..e861b6af19dec6 100644
--- a/docs/pages/blog/2022-tenerife-retreat.md
+++ b/docs/pages/blog/2022-tenerife-retreat.md
@@ -172,7 +172,7 @@ We're hiring!
MUI is on track to grow from roughly 20 people to 40 or more by the time we get to our next company retreat in late Q1 2023.
-We are constantly interviewing React developers from all around the world, and we are also increasingly looking to hire for non-technical roles such as [People Operations Manager](/careers/people-operations-manager/) in which you could have a huge impact steering the direction of the company's nascent HR department.
+We are constantly interviewing React developers from all around the world, and we are also increasingly looking to hire for non-technical roles such as People Operations Manager in which you could have a huge impact steering the direction of the company's nascent HR department.
Check out [our open roles](/careers/) so you can join us before the next retreat!
diff --git a/docs/scripts/reportBrokenLinks.mts b/docs/scripts/reportBrokenLinks.mts
new file mode 100644
index 00000000000000..2f510dc71d6d1c
--- /dev/null
+++ b/docs/scripts/reportBrokenLinks.mts
@@ -0,0 +1,41 @@
+import * as path from 'path';
+import { crawl } from '@mui/internal-code-infra/brokenLinksChecker';
+import { globby } from 'globby';
+
+async function main() {
+ // The /blog/ page has pagination that's not persisted in the url.
+ const blogPages = await globby('blog/**/*.js', {
+ cwd: path.resolve(import.meta.dirname, '../pages'),
+ });
+ const blogSeedUrls = blogPages.map((page) => {
+ const pathname = page.replace(/(?:\/index)?\.js$/, '');
+ return `/${pathname}`;
+ });
+
+ const { issues } = await crawl({
+ startCommand: 'pnpm start --no-request-logging -p 3001',
+ host: 'http://localhost:3001/',
+ seedUrls: ['/', ...blogSeedUrls],
+ outPath: path.resolve(import.meta.dirname, '../export/material-ui/link-structure.json'),
+ // Target paths to ignore during link checking
+ ignoredPaths: [
+ // Internal links not on this server
+ // TODO: Seed crawler with stored links from e.g. mui.com/x/link-structure.json
+ /^\/(x|base-ui|joy-ui|store|toolpad)(\/|$)/,
+ ],
+ // CSS selectors for content to ignore during link checking
+ ignoredContent: [
+ // Links used in demos under MemoryRouter
+ // TODO: Create an easier way to identify content under MemoryRouter
+ // (e.g. a class or an option on the demo)
+ '[id^="demo-"] a[href^="/inbox"]',
+ '[id^="demo-"] a[href^="/trash"]',
+ '[id^="demo-"] a[href^="/spam"]',
+ '[id^="demo-"] a[href^="/drafts"]',
+ ],
+ });
+
+ process.exit(issues.length);
+}
+
+main();
diff --git a/docs/scripts/reportBrokenLinks.ts b/docs/scripts/reportBrokenLinks.ts
deleted file mode 100644
index 9484d610c22a37..00000000000000
--- a/docs/scripts/reportBrokenLinks.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/* eslint-disable no-console */
-import path from 'path';
-import fs from 'node:fs';
-import { parseDocFolder, getAnchor } from './reportBrokenLinksLib';
-
-/**
- * The remaining path to the code is specific to this repository
- */
-const EXTERNAL_PATHS = [
- '/api/',
- // Not defined in .md file
- '/careers/',
- // Same domain, different project
- '/store/',
- // Same domain, different project
- '/x/',
- // these url segments are specific to Base UI and added by scripts (can not be found in markdown)
- 'components-api',
- 'hooks-api',
- '#unstyled',
-];
-
-const docsSpaceRoot = path.join(path.dirname(new URL(import.meta.url).pathname), '../');
-
-const buffer: string[] = [];
-
-function write(text: string) {
- buffer.push(text);
-}
-
-function save(lines: string[]) {
- const fileContents = [...lines, ''].join('\n');
- fs.writeFileSync(path.join(docsSpaceRoot, '.link-check-errors.txt'), fileContents);
-}
-
-/**
- *
- * @param {string} link
- * @returns {string}
- */
-function getPageUrlFromLink(link: string): string {
- const [rep] = link.split('/#');
- return rep;
-}
-
-const availableLinks: Record = {};
-
-// Per link a list a of files where it is used
-const usedLinks: Record = {};
-
-parseDocFolder(path.join(docsSpaceRoot, './pages/'), availableLinks, usedLinks);
-
-write('Broken links found by `pnpm docs:link-check` that exist:\n');
-Object.keys(usedLinks)
- .filter((link) => link.startsWith('/'))
- .filter((link) => !availableLinks[link])
- .filter((link) => EXTERNAL_PATHS.every((externalPath) => !link.includes(externalPath)))
- .sort()
- .forEach((linkKey) => {
- //
- //
- //
- write(`- https://mui.com${linkKey}`);
- console.log(`https://mui.com${linkKey}`);
-
- console.log(`used in`);
- usedLinks[linkKey].forEach((f) => console.log(`- ${path.relative(docsSpaceRoot, f)}`));
- console.log('available anchors on the same page:');
- console.log(
- Object.keys(availableLinks)
- .filter((link) => getPageUrlFromLink(link) === getPageUrlFromLink(linkKey))
- .sort()
- .map(getAnchor)
- .join('\n'),
- );
- console.log('\n\n');
- });
-save(buffer);
diff --git a/docs/scripts/reportBrokenLinksLib.ts b/docs/scripts/reportBrokenLinksLib.ts
deleted file mode 100644
index 69a354589e4ae2..00000000000000
--- a/docs/scripts/reportBrokenLinksLib.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import path from 'path';
-import fs from 'node:fs';
-import { createRender } from '@mui/internal-markdown';
-import { marked } from 'marked';
-import { LANGUAGES_IGNORE_PAGES } from '../config';
-
-/**
- * Use renderer to extract all links into a markdown document
- */
-function getPageLinks(markdown: string): string[] {
- const hrefs: string[] = [];
-
- const renderer = new marked.Renderer();
- renderer.link = ({ href }) => {
- if (href.startsWith('/')) {
- hrefs.push(href);
- }
- return '';
- };
- marked(markdown, { renderer });
- return hrefs;
-}
-
-/**
- * List all .js files in a folder
- */
-function getJsFilesInFolder(folderPath: string): string[] {
- const files = fs.readdirSync(folderPath, { withFileTypes: true });
- return files.reduce((acc, file) => {
- if (file.isDirectory()) {
- const filesInFolder = getJsFilesInFolder(path.join(folderPath, file.name));
- return [...acc, ...filesInFolder];
- }
- if (file.name.endsWith('.js') || file.name.endsWith('.tsx')) {
- return [...acc, path.join(folderPath, file.name).replace(/\\/g, '/')];
- }
- return acc;
- }, [] as string[]);
-}
-
-/**
- * Returns url assuming it's "./docs/pages/x/..." becomes "mui.com/x/..."
- */
-function jsFilePathToUrl(jsFilePath: string): string {
- const folder = path.dirname(jsFilePath);
- const file = path.basename(jsFilePath);
-
- const root = folder.slice(jsFilePath.indexOf('/pages') + '/pages'.length);
- const suffix = path.extname(file);
- let page = `/${file.slice(0, file.length - suffix.length)}/`;
-
- if (page === '/index/') {
- page = '/';
- }
-
- return `${root}${page}`;
-}
-
-function cleanLink(link: string): string {
- const startQueryIndex = link.indexOf('?');
- const endQueryIndex = link.indexOf('#', startQueryIndex);
-
- if (startQueryIndex === -1) {
- return link;
- }
- if (endQueryIndex === -1) {
- return link.slice(0, startQueryIndex);
- }
- return `${link.slice(0, startQueryIndex)}${link.slice(endQueryIndex)}`;
-}
-
-function getLinksAndAnchors(fileName: string): { hashes: string[]; links: string[] } {
- /** @type {Record} */
- const headingHashes = {};
- const render = createRender({
- headingHashes,
- options: {
- ignoreLanguagePages: LANGUAGES_IGNORE_PAGES,
- env: {
- SOURCE_CODE_REPO: '',
- },
- },
- });
-
- const data = fs.readFileSync(fileName, { encoding: 'utf8' });
- render(data);
-
- const links = getPageLinks(data).map(cleanLink);
-
- return {
- hashes: Object.keys(headingHashes),
- links,
- };
-}
-
-const markdownImportRegExp = /'(.*)\?(muiMarkdown|@mui\/markdown)'/g;
-
-function getMdFilesImported(jsPageFile: string): string[] {
- // For each JS file extract the markdown rendered if it exists
- const fileContent = fs.readFileSync(jsPageFile, 'utf8');
- /**
- * Content files can be represented by either:
- * - 'docsx/data/advanced-components/overview.md?muiMarkdown'; (for mui-x)
- * - 'docs/data/advanced-components/overview.md?muiMarkdown';
- * - './index.md?muiMarkdown';
- */
- const importPaths = fileContent.match(markdownImportRegExp);
-
- if (importPaths === null) {
- return [];
- }
- return importPaths.map((importPath) => {
- let cleanImportPath = importPath.replace(markdownImportRegExp, '$1');
- if (cleanImportPath.startsWith('.')) {
- cleanImportPath = path.join(path.dirname(jsPageFile), cleanImportPath);
- } else {
- /**
- * convert /Users/oliviertassinari/base-ui/docs/pages/base-ui/react-switch/index.js
- * and docs-base/data/base/components/switch/switch.md
- * into /Users/oliviertassinari/base-ui/docs/data/base/components/switch/switch.md
- */
- const cleanImportPathArray = cleanImportPath.split('/');
- // Assume that the first folder is /docs or an alias that starts with /docs
- cleanImportPathArray.shift();
-
- // Truncate jsPageFile at /docs/ and append cleanImportPath
- cleanImportPath = path.join(
- jsPageFile.slice(0, jsPageFile.indexOf('/docs/')),
- 'docs',
- cleanImportPathArray.join('/'),
- );
- }
-
- return cleanImportPath;
- });
-}
-
-function parseDocFolder(
- folderPath: string,
- availableLinks: Record = {},
- usedLinks: Record = {},
-) {
- const jsPageFiles = getJsFilesInFolder(folderPath);
-
- const mdFiles = jsPageFiles.flatMap((jsPageFile) => {
- const pageUrl = jsFilePathToUrl(jsPageFile);
- const importedMds = getMdFilesImported(jsPageFile);
-
- return importedMds.map((fileName) => ({ fileName, url: pageUrl }));
- });
-
- // Mark all the existing page as available
- jsPageFiles.forEach((jsFilePath) => {
- const url = jsFilePathToUrl(jsFilePath);
- availableLinks[url] = true;
- });
-
- // For each markdown file, extract links
- mdFiles.forEach(({ fileName, url }) => {
- const { hashes, links } = getLinksAndAnchors(fileName);
-
- links.forEach((link) => {
- if (usedLinks[link] === undefined) {
- usedLinks[link] = [fileName];
- } else {
- usedLinks[link].push(fileName);
- }
- });
-
- hashes.forEach((hash) => {
- availableLinks[`${url}#${hash}`] = true;
- });
- });
-}
-
-function getAnchor(link: string): string {
- const splittedPath = link.split('/');
- const potentialAnchor = splittedPath[splittedPath.length - 1];
- return potentialAnchor.includes('#') ? potentialAnchor : '';
-}
-
-// Export useful method for doing similar checks in other repositories
-export { parseDocFolder, getAnchor };
diff --git a/package.json b/package.json
index 36762bfabf9f55..13b90855fb3dc5 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"proptypes": "tsx ./scripts/generateProptypes.ts",
"deduplicate": "pnpm dedupe",
"build": "lerna run build --ignore docs",
- "build:ci": "lerna run build --concurrency 8 --skip-nx-cache",
+ "build:ci": "lerna run build --concurrency 8 --ignore docs --stream --skip-nx-cache",
"build:public": "lerna run --no-private build",
"build:public:ci": "lerna run --no-private build --concurrency 8 --skip-nx-cache",
"release:publish": "code-infra publish --github-release",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b1556550337cb2..8b3e75ff86522c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -605,9 +605,6 @@ importers:
gm:
specifier: ^1.25.1
version: 1.25.1
- marked:
- specifier: ^16.2.0
- version: 16.4.2
prettier:
specifier: ^3.6.2
version: 3.6.2
diff --git a/scripts/buildLlmsDocs/index.ts b/scripts/buildLlmsDocs/index.ts
index dd0ccb5fb3f039..afec36e684a8bf 100644
--- a/scripts/buildLlmsDocs/index.ts
+++ b/scripts/buildLlmsDocs/index.ts
@@ -134,50 +134,45 @@ async function findComponentsToProcess(
);
for (const component of projectComponents) {
- try {
- // Get component info using the API docs builder
- const componentInfo = projectSettings.getComponentInfo(component.filename);
-
- // Skip if component should be skipped (internal, etc.)
- const fileInfo = componentInfo.readFile();
- if (fileInfo.shouldSkip) {
- continue;
- }
+ // Get component info using the API docs builder
+ const componentInfo = projectSettings.getComponentInfo(component.filename);
- // Get demos for this component
- const demos = componentInfo.getDemos();
+ // Skip if component should be skipped (internal, etc.)
+ const fileInfo = componentInfo.readFile();
+ if (fileInfo.shouldSkip) {
+ continue;
+ }
- // Skip if no demos found (likely not a public component)
- if (demos.length === 0) {
- continue;
- }
+ // Get demos for this component
+ const demos = componentInfo.getDemos();
- // Get API JSON path
- const apiJsonPath = path.join(
- componentInfo.apiPagesDirectory,
- `${path.basename(componentInfo.apiPathname)}.json`,
- );
-
- const primaryDemo = demos.find(
- (demo) =>
- demo.demoPathname.toLowerCase().includes(`/${kebabCase(componentInfo.name)}/`) ||
- demo.demoPathname.toLowerCase().includes(`/react-${kebabCase(componentInfo.name)}/`),
- );
-
- const demoToUse = primaryDemo || demos[0];
- const markdownPathToUse = demoToUse ? demoToUse.filePath : undefined;
-
- components.push({
- name: componentInfo.name,
- componentInfo,
- demos: primaryDemo ? [primaryDemo] : demos,
- markdownPath: markdownPathToUse,
- apiJsonPath: fs.existsSync(apiJsonPath) ? apiJsonPath : undefined,
- });
- } catch (error) {
- // Skip components that can't be processed
+ // Skip if no demos found (likely not a public component)
+ if (demos.length === 0) {
continue;
}
+
+ // Get API JSON path
+ const apiJsonPath = path.join(
+ componentInfo.apiPagesDirectory,
+ `${path.basename(componentInfo.apiPathname)}.json`,
+ );
+
+ const primaryDemo = demos.find(
+ (demo) =>
+ demo.demoPathname.toLowerCase().includes(`/${kebabCase(componentInfo.name)}/`) ||
+ demo.demoPathname.toLowerCase().includes(`/react-${kebabCase(componentInfo.name)}/`),
+ );
+
+ const demoToUse = primaryDemo || demos[0];
+ const markdownPathToUse = demoToUse ? demoToUse.filePath : undefined;
+
+ components.push({
+ name: componentInfo.name,
+ componentInfo,
+ demos: primaryDemo ? [primaryDemo] : demos,
+ markdownPath: markdownPathToUse,
+ apiJsonPath: fs.existsSync(apiJsonPath) ? apiJsonPath : undefined,
+ });
}
}
@@ -266,7 +261,7 @@ function findNonComponentMarkdownFiles(
) {
// Match by filename basename to avoid pathname collisions when multiple files
// exist in the same directory (e.g., upgrade-to-v7.md and upgrade-to-native-color.md)
- const lastSegment = pathname.split('/').filter(Boolean).pop();
+ const lastSegment = parsedPathname.split('/').filter(Boolean).pop();
const page = allMarkdownFiles.find((p) => {
const fileBasename = path.basename(p.filename).replace(/\.mdx?$/, '');
// p.pathname already has the parent path (from findPagesMarkdown which strips the filename)
@@ -314,25 +309,12 @@ function processComponent(component: ComponentDocInfo): string | null {
`${kebabCase(componentName)}.json`,
);
- if (fs.existsSync(apiJsonPath)) {
- try {
- const apiMarkdown = processApiFile(apiJsonPath, { origin: ORIGIN });
- processedMarkdown += `\n\n${apiMarkdown}`;
- } catch (error) {
- console.error(`Warning: Could not process API for ${componentName}:`, error);
- }
- } else {
- console.warn(`Warning: API JSON file not found for ${componentName}: ${apiJsonPath}`);
- }
- }
- } else if (component.apiJsonPath) {
- // Fallback: Add API section for the primary component if no frontmatter components found
- try {
- const apiMarkdown = processApiFile(component.apiJsonPath, { origin: ORIGIN });
+ const apiMarkdown = processApiFile(apiJsonPath, { origin: ORIGIN });
processedMarkdown += `\n\n${apiMarkdown}`;
- } catch (error) {
- console.error(`Warning: Could not process API for ${component.name}:`, error);
}
+ } else if (component.apiJsonPath) {
+ const apiMarkdown = processApiFile(component.apiJsonPath, { origin: ORIGIN });
+ processedMarkdown += `\n\n${apiMarkdown}`;
}
return processedMarkdown;
@@ -467,66 +449,23 @@ async function buildLlmsDocs(argv: ArgumentsCamelCase): Promise<
// Process each component
let processedCount = 0;
for (const component of components) {
- try {
- const processedMarkdown = processComponent(component);
+ const processedMarkdown = processComponent(component);
- if (!processedMarkdown) {
- continue;
- }
-
- // Use the component's demo pathname to create the output structure
- // e.g., /material-ui/react-accordion/ -> material-ui/react-accordion.md
- const outputFileName = component.demos[0]
- ? `${component.demos[0].demoPathname.replace(/^\//, '').replace(/\/$/, '')}.md`
- : `${component.componentInfo.apiPathname.replace(/^\//, '').replace(/\/$/, '')}.md`;
-
- const outputPath = path.join(outputDir, outputFileName);
-
- // Check if this file has already been generated (avoid duplicates for components that share the same markdown file)
- const existingFile = generatedFiles.find((f) => f.outputPath === outputFileName);
- if (!existingFile) {
- // Ensure the directory exists
- const outputDirPath = path.dirname(outputPath);
- if (!fs.existsSync(outputDirPath)) {
- fs.mkdirSync(outputDirPath, { recursive: true });
- }
-
- fs.writeFileSync(outputPath, processedMarkdown, 'utf-8');
- // ✓ Generated: ${outputFileName}
- processedCount += 1;
-
- // Track this file for llms.txt
- if (component.markdownPath) {
- const { title, description } = extractMarkdownInfo(component.markdownPath);
- generatedFiles.push({
- outputPath: outputFileName,
- title,
- description,
- originalMarkdownPath: component.markdownPath,
- category: 'components',
- });
- generatedComponentRecord[outputFileName] = true;
- }
- }
- } catch (error) {
- console.error(`✗ Error processing ${component.name}:`, error);
+ if (!processedMarkdown) {
+ continue;
}
- }
- // Process non-component markdown files
- for (const file of nonComponentFiles) {
- try {
- if (generatedComponentRecord[file.outputPath]) {
- // Skip files that have already been generated as component docs
- continue;
- }
- // Processing non-component file: ${path.relative(process.cwd(), file.markdownPath)}
+ // Use the component's demo pathname to create the output structure
+ // e.g., /material-ui/react-accordion/ -> material-ui/react-accordion.md
+ const outputFileName = component.demos[0]
+ ? `${component.demos[0].demoPathname.replace(/^\//, '').replace(/\/$/, '')}.md`
+ : `${component.componentInfo.apiPathname.replace(/^\//, '').replace(/\/$/, '')}.md`;
- // Process the markdown file with demo replacement
- const processedMarkdown = processMarkdownFile(file.markdownPath);
-
- const outputPath = path.join(outputDir, file.outputPath);
+ const outputPath = path.join(outputDir, outputFileName);
+ // Check if this file has already been generated (avoid duplicates for components that share the same markdown file)
+ const existingFile = generatedFiles.find((f) => f.outputPath === outputFileName);
+ if (!existingFile) {
// Ensure the directory exists
const outputDirPath = path.dirname(outputPath);
if (!fs.existsSync(outputDirPath)) {
@@ -534,40 +473,75 @@ async function buildLlmsDocs(argv: ArgumentsCamelCase): Promise<
}
fs.writeFileSync(outputPath, processedMarkdown, 'utf-8');
- // ✓ Generated: ${file.outputPath}
+ // ✓ Generated: ${outputFileName}
processedCount += 1;
// Track this file for llms.txt
- const { title, description } = extractMarkdownInfo(file.markdownPath);
-
- // Extract category from the file path
- // e.g., "material-ui/customization/color.md" -> "customization"
- // e.g., "getting-started/installation.md" -> "getting-started"
- const pathParts = file.outputPath.split('/');
- const category = pathParts.reverse()[1];
-
- // Find the order index based on which folder this file belongs to
- let orderIndex = -1;
- if (nonComponentFolders) {
- for (let i = 0; i < nonComponentFolders.length; i += 1) {
- if (file.markdownPath.includes(`/${nonComponentFolders[i]}/`)) {
- orderIndex = i;
- break;
- }
- }
+ if (component.markdownPath) {
+ const { title, description } = extractMarkdownInfo(component.markdownPath);
+ generatedFiles.push({
+ outputPath: outputFileName,
+ title,
+ description,
+ originalMarkdownPath: component.markdownPath,
+ category: 'components',
+ });
+ generatedComponentRecord[outputFileName] = true;
}
+ }
+ }
- generatedFiles.push({
- outputPath: file.outputPath,
- title,
- description,
- originalMarkdownPath: file.markdownPath,
- category,
- orderIndex,
- });
- } catch (error) {
- console.error(`✗ Error processing ${file.markdownPath}:`, error);
+ // Process non-component markdown files
+ for (const file of nonComponentFiles) {
+ if (generatedComponentRecord[file.outputPath]) {
+ // Skip files that have already been generated as component docs
+ continue;
+ }
+ // Processing non-component file: ${path.relative(process.cwd(), file.markdownPath)}
+
+ // Process the markdown file with demo replacement
+ const processedMarkdown = processMarkdownFile(file.markdownPath);
+
+ const outputPath = path.join(outputDir, file.outputPath);
+
+ // Ensure the directory exists
+ const outputDirPath = path.dirname(outputPath);
+ if (!fs.existsSync(outputDirPath)) {
+ fs.mkdirSync(outputDirPath, { recursive: true });
+ }
+
+ fs.writeFileSync(outputPath, processedMarkdown, 'utf-8');
+ // ✓ Generated: ${file.outputPath}
+ processedCount += 1;
+
+ // Track this file for llms.txt
+ const { title, description } = extractMarkdownInfo(file.markdownPath);
+
+ // Extract category from the file path
+ // e.g., "material-ui/customization/color.md" -> "customization"
+ // e.g., "getting-started/installation.md" -> "getting-started"
+ const pathParts = file.outputPath.split('/');
+ const category = pathParts.reverse()[1];
+
+ // Find the order index based on which folder this file belongs to
+ let orderIndex = -1;
+ if (nonComponentFolders) {
+ for (let i = 0; i < nonComponentFolders.length; i += 1) {
+ if (file.markdownPath.includes(`/${nonComponentFolders[i]}/`)) {
+ orderIndex = i;
+ break;
+ }
+ }
}
+
+ generatedFiles.push({
+ outputPath: file.outputPath,
+ title,
+ description,
+ originalMarkdownPath: file.markdownPath,
+ category,
+ orderIndex,
+ });
}
// Generate llms.txt files