@mintlify/astro 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @mintlify/astro
2
+
3
+ Mintlify integration for Astro. This package syncs Mintlify-style docs content into Astro content collections and provides MDX components for rendering docs pages.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @mintlify/components
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### 1) Configure Astro integration
14
+
15
+ ```ts
16
+ // astro.config.mjs
17
+ import { defineConfig } from 'astro/config';
18
+ import react from '@astrojs/react';
19
+ import mdx from '@astrojs/mdx';
20
+ import { mintlify } from '@mintlify/astro';
21
+
22
+ export default defineConfig({
23
+ integrations: [mintlify({ docsDir: './docs' }), react(), mdx()],
24
+ });
25
+ ```
26
+
27
+ ### 2) Use MDX components in docs page
28
+
29
+ ```astro
30
+ ---
31
+ import { render } from 'astro:content';
32
+ import { components } from '@mintlify/astro/components';
33
+ ---
34
+ <Content components={components} />
35
+ ```
36
+
37
+ ## Expected docs structure
38
+
39
+ - `./docs/docs.json`
40
+ - `./docs/**/*.mdx`
41
+ - `./docs/snippets/*` (optional)
42
+ - assets like `./docs/images`, `./docs/logo`, `./docs/favicon.*` (optional)
43
+
44
+ At build setup time, the integration generates:
45
+
46
+ - `src/content/docs/**/*`
47
+ - `src/content/components/**/*`
48
+ - `src/content/generated/favicons.json` (when favicon is configured)
49
+
@@ -0,0 +1 @@
1
+ export { components, useMDXComponents } from './utils/mintlify-components.js';
@@ -0,0 +1 @@
1
+ export { components, useMDXComponents } from './utils/mintlify-components.js';
@@ -0,0 +1,6 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ export type { DocsConfig, NavigationConfig, Favicons } from './types.js';
3
+ export interface MintlifyOptions {
4
+ docsDir?: string;
5
+ }
6
+ export declare function mintlify(options?: MintlifyOptions): AstroIntegration;
package/dist/index.js ADDED
@@ -0,0 +1,70 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { getAllPathsInDocsNav } from '@mintlify/common';
4
+ import { clearContentDirectory } from './utils/clear-content-directory.js';
5
+ import { copyAssets } from './utils/copy-assets.js';
6
+ import { findUserComponents } from './utils/find-user-components.js';
7
+ import { loadAllSnippets } from './utils/load-all-snippets.js';
8
+ import { initLogger } from './utils/logger.js';
9
+ import { readDocsConfig } from './utils/read-docs-config.js';
10
+ import { readPageContent } from './utils/read-page-content.js';
11
+ import { writePageContent } from './utils/write-page-content.js';
12
+ import { writeFaviconsManifest } from './utils/write-favicons-manifest.js';
13
+ export function mintlify(options) {
14
+ return {
15
+ name: '@mintlify/astro',
16
+ hooks: {
17
+ 'astro:config:setup': async ({ config: astroConfig, logger }) => {
18
+ initLogger(logger);
19
+ const rootDir = fileURLToPath(astroConfig.root);
20
+ const srcDir = fileURLToPath(astroConfig.srcDir);
21
+ const publicDir = fileURLToPath(astroConfig.publicDir);
22
+ const contentDir = join(srcDir, 'content', 'docs');
23
+ const componentsDir = join(srcDir, 'content', 'components');
24
+ const docsDir = resolve(rootDir, options?.docsDir ?? './docs');
25
+ await Promise.all([
26
+ clearContentDirectory(contentDir),
27
+ clearContentDirectory(componentsDir),
28
+ ]);
29
+ await copyAssets(docsDir, publicDir);
30
+ const snippets = await loadAllSnippets(docsDir);
31
+ logger.info(`Loaded ${snippets.length} snippet files`);
32
+ const userComponents = await findUserComponents(docsDir);
33
+ const docsConfig = await readDocsConfig(docsDir);
34
+ const pagePaths = getAllPathsInDocsNav(docsConfig.navigation);
35
+ if (docsConfig.favicon) {
36
+ const faviconHref = typeof docsConfig.favicon === 'string'
37
+ ? docsConfig.favicon
38
+ : docsConfig.favicon.light;
39
+ await writeFaviconsManifest(srcDir, {
40
+ icons: [
41
+ {
42
+ rel: 'icon',
43
+ type: 'image/svg+xml',
44
+ href: faviconHref,
45
+ },
46
+ ],
47
+ });
48
+ }
49
+ logger.info(`Found ${pagePaths.length} pages to sync`);
50
+ await Promise.all(pagePaths.map(async (pagePath) => {
51
+ try {
52
+ const { content, slug } = await readPageContent(pagePath, docsDir);
53
+ await writePageContent({
54
+ content,
55
+ slug,
56
+ contentDir,
57
+ componentsDir,
58
+ userComponents,
59
+ snippets,
60
+ });
61
+ }
62
+ catch (error) {
63
+ logger.warn(`Failed to read content for ${pagePath}: ${error}`);
64
+ }
65
+ }));
66
+ logger.info(`Synced ${pagePaths.length} pages`);
67
+ },
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,12 @@
1
+ export type { DocsConfig, NavigationConfig } from '@mintlify/validation';
2
+ export interface FaviconIcon {
3
+ rel: string;
4
+ sizes?: string;
5
+ type: string;
6
+ href: string;
7
+ media?: string;
8
+ }
9
+ export interface Favicons {
10
+ icons: FaviconIcon[];
11
+ browserconfig?: string;
12
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function clearContentDirectory(dirPath: string): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import { mkdir, rm } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ export async function clearContentDirectory(dirPath) {
4
+ if (existsSync(dirPath)) {
5
+ await rm(dirPath, { recursive: true });
6
+ }
7
+ await mkdir(dirPath, { recursive: true });
8
+ }
@@ -0,0 +1 @@
1
+ export declare function copyAssets(docsDir: string, publicDir: string): Promise<void>;
@@ -0,0 +1,36 @@
1
+ import { cp, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ async function exists(path) {
4
+ try {
5
+ await stat(path);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ async function isDirectory(path) {
13
+ try {
14
+ const stats = await stat(path);
15
+ return stats.isDirectory();
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function copyDirectoryIfExists(source, dest) {
22
+ if (!(await isDirectory(source)))
23
+ return;
24
+ await cp(source, dest, { recursive: true });
25
+ }
26
+ async function copyFileIfExists(source, dest) {
27
+ if (!(await exists(source)))
28
+ return;
29
+ await cp(source, dest);
30
+ }
31
+ export async function copyAssets(docsDir, publicDir) {
32
+ await copyDirectoryIfExists(join(docsDir, 'images'), join(publicDir, 'images'));
33
+ await copyDirectoryIfExists(join(docsDir, 'logo'), join(publicDir, 'logo'));
34
+ await copyFileIfExists(join(docsDir, 'favicon.svg'), join(publicDir, 'favicon.svg'));
35
+ await copyFileIfExists(join(docsDir, 'favicon.ico'), join(publicDir, 'favicon.ico'));
36
+ }
@@ -0,0 +1 @@
1
+ export declare function extractReactComponents(mdxContent: string, mdxFilePath: string, componentsDir: string, pageSlug: string, userComponents: Set<string>): Promise<string>;
@@ -0,0 +1,219 @@
1
+ import { compile } from '@mdx-js/mdx';
2
+ import { visit, SKIP } from 'unist-util-visit';
3
+ import { mkdir, writeFile } from 'node:fs/promises';
4
+ import { join, dirname, relative, posix, sep } from 'node:path';
5
+ import { getAST, stringifyTree, findExportedNodes, getExportMapFromTree, isMdxJsxAttribute, createMdxJsxAttribute, allowedComponents, } from '@mintlify/common';
6
+ import { getLogger } from './logger.js';
7
+ const COMPOUND_COMPONENTS = new Set([
8
+ 'Accordion',
9
+ 'AccordionGroup',
10
+ 'Card',
11
+ 'CardGroup',
12
+ 'CodeGroup',
13
+ 'Expandable',
14
+ 'Steps',
15
+ 'Tabs',
16
+ 'Tooltip',
17
+ ]);
18
+ const SUPPORTED_HOOKS = new Set([
19
+ 'useState',
20
+ 'useEffect',
21
+ 'useRef',
22
+ 'useCallback',
23
+ 'useMemo',
24
+ 'useReducer',
25
+ 'useContext',
26
+ 'useLayoutEffect',
27
+ 'useImperativeHandle',
28
+ 'useDebugValue',
29
+ 'useDeferredValue',
30
+ 'useTransition',
31
+ 'useId',
32
+ 'useSyncExternalStore',
33
+ 'useInsertionEffect',
34
+ ]);
35
+ const MDX_COMPONENTS_PATH = '@mintlify/astro/components';
36
+ function isJsxNode(node) {
37
+ const n = node;
38
+ return n?.type === 'mdxJsxFlowElement' || n?.type === 'mdxJsxTextElement';
39
+ }
40
+ function visitJsxNodes(tree, visitor) {
41
+ visit(tree, (node, index, parent) => {
42
+ if (isJsxNode(node)) {
43
+ return visitor(node, index, parent);
44
+ }
45
+ });
46
+ }
47
+ const detectHooksInCode = (code) => [...SUPPORTED_HOOKS].filter((hook) => code.includes(hook));
48
+ function createImportNode(name, path) {
49
+ return {
50
+ type: 'mdxjsEsm',
51
+ value: `import { ${name} } from "${path}";`,
52
+ };
53
+ }
54
+ function addClientDirective(node) {
55
+ node.attributes ??= [];
56
+ const hasClientDirective = node.attributes.some((attr) => isMdxJsxAttribute(attr) &&
57
+ typeof attr.name === 'string' &&
58
+ attr.name.startsWith('client:'));
59
+ if (!hasClientDirective) {
60
+ node.attributes.push(createMdxJsxAttribute('client:load', null));
61
+ }
62
+ }
63
+ function generateComponentFile(code, usedHooks) {
64
+ const validComponents = allowedComponents.filter((c) => !c.includes('.') && !c.includes('-') && /^[A-Z]/.test(c));
65
+ const imports = [
66
+ `import * as Mintlify from "@mintlify/components";`,
67
+ `const { ${validComponents.join(', ')} } = Mintlify;`,
68
+ ];
69
+ if (usedHooks.length > 0) {
70
+ imports.unshift(`import { ${usedHooks.join(', ')} } from "react";`);
71
+ }
72
+ return imports.join('\n') + '\n\n' + code + '\n';
73
+ }
74
+ function findExportedComponents(tree) {
75
+ const exportedNames = findExportedNodes(tree, 'ArrowFunctionExpression', 'FunctionExpression');
76
+ if (exportedNames.length === 0) {
77
+ return { components: [], nodesToRemove: new Set() };
78
+ }
79
+ const exportMap = getExportMapFromTree(tree);
80
+ const nodesToRemove = new Set();
81
+ visit(tree, 'mdxjsEsm', (node) => {
82
+ if (exportedNames.some((name) => node.value?.includes(name))) {
83
+ nodesToRemove.add(node);
84
+ }
85
+ });
86
+ const components = exportedNames
87
+ .filter((name) => exportMap[name])
88
+ .map((name) => ({
89
+ name,
90
+ code: exportMap[name],
91
+ usedHooks: detectHooksInCode(exportMap[name]),
92
+ }));
93
+ return { components, nodesToRemove };
94
+ }
95
+ function processCompoundNode(node, ctx) {
96
+ if (!node.name || !COMPOUND_COMPONENTS.has(node.name))
97
+ return;
98
+ if (!node.position?.start?.offset || !node.position?.end?.offset) {
99
+ getLogger()?.warn(`Skipping ${node.name}: missing position info`);
100
+ return;
101
+ }
102
+ const count = (ctx.counters.get(node.name) || 0) + 1;
103
+ ctx.counters.set(node.name, count);
104
+ const wrapperName = `${node.name}_${count}`;
105
+ ctx.compounds.push({
106
+ wrapperName,
107
+ mdxSource: ctx.mdxContent.slice(node.position.start.offset, node.position.end.offset),
108
+ });
109
+ ctx.nodeReplacements.set(node, wrapperName);
110
+ return SKIP;
111
+ }
112
+ function findCompoundComponents(tree, mdxContent) {
113
+ const ctx = {
114
+ compounds: [],
115
+ nodeReplacements: new Map(),
116
+ counters: new Map(),
117
+ mdxContent,
118
+ };
119
+ visitJsxNodes(tree, (node) => processCompoundNode(node, ctx));
120
+ return { compounds: ctx.compounds, nodeReplacements: ctx.nodeReplacements };
121
+ }
122
+ async function writeComponentFiles(components, componentsDir, pageSlug) {
123
+ const pageComponentsDir = join(componentsDir, pageSlug);
124
+ await mkdir(pageComponentsDir, { recursive: true });
125
+ await Promise.all(components.map(({ name, code, usedHooks }) => writeFile(join(pageComponentsDir, `${name}.jsx`), generateComponentFile(code, usedHooks), 'utf-8')));
126
+ }
127
+ async function compileCompoundComponent(wrapperName, mdxSource) {
128
+ const compiled = await compile(mdxSource, {
129
+ jsx: true,
130
+ outputFormat: 'program',
131
+ development: false,
132
+ providerImportSource: MDX_COMPONENTS_PATH,
133
+ });
134
+ const compiledStr = String(compiled)
135
+ .replace(/export default function MDXContent/g, `export function ${wrapperName}`)
136
+ .replace(/export \{ MDXContent as default \};?/g, '');
137
+ return compiledStr;
138
+ }
139
+ async function writeCompoundComponentFiles(compounds, componentsDir, pageSlug) {
140
+ if (compounds.length === 0)
141
+ return;
142
+ const pageComponentsDir = join(componentsDir, pageSlug);
143
+ await mkdir(pageComponentsDir, { recursive: true });
144
+ await Promise.all(compounds.map(async ({ wrapperName, mdxSource }) => {
145
+ const code = await compileCompoundComponent(wrapperName, mdxSource);
146
+ await writeFile(join(pageComponentsDir, `${wrapperName}.jsx`), code, 'utf-8');
147
+ }));
148
+ }
149
+ function getInsertIndex(tree, skipEsm = false) {
150
+ const skipTypes = skipEsm ? ['yaml', 'mdxjsEsm'] : ['yaml'];
151
+ const index = tree.children.findIndex((c) => !skipTypes.includes(c.type));
152
+ return index === -1 ? tree.children.length : index;
153
+ }
154
+ function transformTree(tree, components, nodesToRemove, relativeComponentsDir) {
155
+ tree.children = tree.children.filter((child) => !nodesToRemove.has(child));
156
+ const importNodes = components.map((c) => createImportNode(c.name, posix.join(relativeComponentsDir, `${c.name}.jsx`)));
157
+ tree.children.splice(getInsertIndex(tree), 0, ...importNodes);
158
+ const componentNames = new Set(components.map((c) => c.name));
159
+ visitJsxNodes(tree, (node) => {
160
+ if (node.name && componentNames.has(node.name)) {
161
+ addClientDirective(node);
162
+ }
163
+ });
164
+ }
165
+ function createReplacementNode(type, wrapperName) {
166
+ return {
167
+ type,
168
+ name: wrapperName,
169
+ attributes: [createMdxJsxAttribute('client:load', null)],
170
+ children: [],
171
+ };
172
+ }
173
+ function transformTreeWithCompounds(tree, compounds, nodeReplacements, relativeComponentsDir) {
174
+ if (compounds.length === 0)
175
+ return;
176
+ const importNodes = compounds.map((c) => createImportNode(c.wrapperName, posix.join(relativeComponentsDir, `${c.wrapperName}.jsx`)));
177
+ tree.children.splice(getInsertIndex(tree, true), 0, ...importNodes);
178
+ visitJsxNodes(tree, (node, index, parent) => {
179
+ const wrapperName = nodeReplacements.get(node);
180
+ if (!wrapperName || index === undefined || !parent)
181
+ return;
182
+ parent.children[index] = createReplacementNode(node.type, wrapperName);
183
+ return SKIP;
184
+ });
185
+ }
186
+ function addClientDirectivesToUserComponents(tree, userComponents) {
187
+ if (userComponents.size === 0)
188
+ return;
189
+ visitJsxNodes(tree, (node) => {
190
+ if (node.name && userComponents.has(node.name)) {
191
+ addClientDirective(node);
192
+ }
193
+ });
194
+ }
195
+ export async function extractReactComponents(mdxContent, mdxFilePath, componentsDir, pageSlug, userComponents) {
196
+ const tree = getAST(mdxContent, mdxFilePath);
197
+ const { components, nodesToRemove } = findExportedComponents(tree);
198
+ const { compounds, nodeReplacements } = findCompoundComponents(tree, mdxContent);
199
+ if (components.length === 0 &&
200
+ compounds.length === 0 &&
201
+ userComponents.size === 0) {
202
+ return mdxContent;
203
+ }
204
+ const mdxDir = dirname(mdxFilePath);
205
+ const pageComponentsDir = join(componentsDir, pageSlug);
206
+ const relativeComponentsDir = relative(mdxDir, pageComponentsDir)
207
+ .split(sep)
208
+ .join(posix.sep);
209
+ if (components.length > 0) {
210
+ await writeComponentFiles(components, componentsDir, pageSlug);
211
+ transformTree(tree, components, nodesToRemove, relativeComponentsDir);
212
+ }
213
+ addClientDirectivesToUserComponents(tree, userComponents);
214
+ if (compounds.length > 0) {
215
+ await writeCompoundComponentFiles(compounds, componentsDir, pageSlug);
216
+ transformTreeWithCompounds(tree, compounds, nodeReplacements, relativeComponentsDir);
217
+ }
218
+ return stringifyTree(tree);
219
+ }
@@ -0,0 +1 @@
1
+ export declare function findUserComponents(docsDir: string): Promise<Set<string>>;
@@ -0,0 +1,40 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { join, extname, relative } from 'node:path';
3
+ import { getAST, findExportedNodes, DEFAULT_MINT_IGNORES, } from '@mintlify/common';
4
+ export async function findUserComponents(docsDir) {
5
+ const components = new Set();
6
+ async function scanDirectory(dir) {
7
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
8
+ for (const entry of entries) {
9
+ const fullPath = join(dir, entry.name);
10
+ if (entry.isDirectory()) {
11
+ if (!DEFAULT_MINT_IGNORES.includes(entry.name)) {
12
+ await scanDirectory(fullPath);
13
+ }
14
+ }
15
+ else {
16
+ const ext = extname(entry.name).toLowerCase();
17
+ if (ext === '.jsx' || ext === '.tsx') {
18
+ const fileComponents = await extractComponentNames(fullPath, docsDir);
19
+ fileComponents.forEach((name) => components.add(name));
20
+ }
21
+ }
22
+ }
23
+ }
24
+ await scanDirectory(docsDir);
25
+ return components;
26
+ }
27
+ async function extractComponentNames(filePath, docsDir) {
28
+ const content = await readFile(filePath, 'utf-8').catch(() => null);
29
+ if (!content)
30
+ return [];
31
+ try {
32
+ const relativePath = '/' + relative(docsDir, filePath);
33
+ const tree = getAST(content, relativePath);
34
+ const exportedNames = findExportedNodes(tree, 'ArrowFunctionExpression', 'FunctionExpression');
35
+ return exportedNames.filter((name) => /^[A-Z]/.test(name));
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
@@ -0,0 +1,7 @@
1
+ import type { Root } from 'mdast';
2
+ export interface SnippetFile {
3
+ filename: string;
4
+ tree: Root;
5
+ content: string;
6
+ }
7
+ export declare function loadAllSnippets(docsDir: string): Promise<SnippetFile[]>;
@@ -0,0 +1,63 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { join, extname, relative } from 'node:path';
3
+ import { getAST, SNIPPET_EXTENSIONS, topologicalSort, extractImportSources, resolveImportPath, DEFAULT_MINT_IGNORES, } from '@mintlify/common';
4
+ import { getLogger } from './logger.js';
5
+ async function scanDirectory(dir, rootDir, files = []) {
6
+ const entries = await readdir(dir, { withFileTypes: true });
7
+ for (const entry of entries) {
8
+ const fullPath = join(dir, entry.name);
9
+ if (entry.isDirectory()) {
10
+ if (DEFAULT_MINT_IGNORES.includes(entry.name)) {
11
+ continue;
12
+ }
13
+ await scanDirectory(fullPath, rootDir, files);
14
+ }
15
+ else {
16
+ const ext = extname(entry.name).toLowerCase();
17
+ if (SNIPPET_EXTENSIONS.includes(ext)) {
18
+ const relativePath = '/' + relative(rootDir, fullPath);
19
+ files.push({ path: fullPath, relativePath });
20
+ }
21
+ }
22
+ }
23
+ return files;
24
+ }
25
+ export async function loadAllSnippets(docsDir) {
26
+ const files = await scanDirectory(docsDir, docsDir);
27
+ const snippets = [];
28
+ for (const file of files) {
29
+ try {
30
+ const content = await readFile(file.path, 'utf-8');
31
+ const tree = getAST(content, file.relativePath);
32
+ snippets.push({
33
+ filename: file.relativePath,
34
+ tree,
35
+ content,
36
+ });
37
+ }
38
+ catch (error) {
39
+ getLogger()?.warn(`Failed to parse ${file.relativePath}: ${error}`);
40
+ }
41
+ }
42
+ const graph = {};
43
+ for (const snippet of snippets) {
44
+ const normalizedPath = snippet.filename.toLowerCase();
45
+ graph[normalizedPath] = [];
46
+ const importSources = extractImportSources(snippet.tree);
47
+ for (const source of importSources) {
48
+ const resolved = resolveImportPath(source, snippet.filename);
49
+ if (resolved) {
50
+ graph[normalizedPath].push(resolved.toLowerCase());
51
+ }
52
+ }
53
+ }
54
+ const sortedPaths = topologicalSort(graph);
55
+ const sortedSnippets = [];
56
+ for (const path of sortedPaths) {
57
+ const snippet = snippets.find((s) => s.filename.toLowerCase() === path.toLowerCase());
58
+ if (snippet) {
59
+ sortedSnippets.push(snippet);
60
+ }
61
+ }
62
+ return sortedSnippets;
63
+ }
@@ -0,0 +1,3 @@
1
+ import type { AstroIntegrationLogger } from 'astro';
2
+ export declare function initLogger(logger: AstroIntegrationLogger): void;
3
+ export declare function getLogger(): AstroIntegrationLogger | undefined;
@@ -0,0 +1,7 @@
1
+ let _logger;
2
+ export function initLogger(logger) {
3
+ _logger = logger;
4
+ }
5
+ export function getLogger() {
6
+ return _logger;
7
+ }
@@ -0,0 +1,121 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { MDXComponents } from 'mdx/types';
3
+ export declare const components: {
4
+ Accordion: (({ title, description, defaultOpen, icon, iconType, children, className, _disabled, trackOpen, trackClose, onMount, topOffset, getInitialOpenFromUrl, onUrlStateChange, _onKeyDownCapture, }: import("@mintlify/components").AccordionProps) => import("react/jsx-runtime").JSX.Element) & {
5
+ Group: ({ children, className }: import("@mintlify/components").AccordionGroupProps) => import("react/jsx-runtime").JSX.Element;
6
+ };
7
+ Badge: ({ children, className, color, shape, variant: variantProp, stroke, disabled, size, leadIcon, tailIcon: tailIconProp, icon, iconType, iconLibrary, onClick, href, }: import("@mintlify/components").BadgeProps) => import("react/jsx-runtime").JSX.Element;
8
+ Callout: ({ children, variant, icon, iconType, iconLibrary, color, className, ariaLabel, }: import("@mintlify/components").CalloutProps) => import("react/jsx-runtime").JSX.Element;
9
+ Card: ({ title, icon, iconType, iconLibrary, color, horizontal, href, img, children, disabled, cta, arrow, as, className, }: import("@mintlify/components").CardComponentProps) => import("react/jsx-runtime").JSX.Element;
10
+ Check: (props: {
11
+ children: ReactNode;
12
+ className?: string | undefined;
13
+ color?: string | undefined;
14
+ icon?: ReactNode;
15
+ iconType?: ("brands" | "duotone" | "light" | "regular" | "sharp-duotone-solid" | "sharp-light" | "sharp-regular" | "sharp-solid" | "sharp-thin" | "solid" | "thin") | undefined;
16
+ iconLibrary?: ("fontawesome" | "lucide") | undefined;
17
+ ariaLabel?: string | undefined;
18
+ }) => import("react/jsx-runtime").JSX.Element;
19
+ CodeBlock: (params: import("@mintlify/components").CodeBlockProps) => import("react/jsx-runtime").JSX.Element;
20
+ CodeGroup: ({ children, isSmallText, className, noMargins, dropdown, feedbackModalOpen, anchorRef, codeBlockTheme, codeBlockThemeObject, initialSelectedTab, onSelectedTabChange, askAiButton, feedbackButton, copyButtonProps, }: import("@mintlify/components").CodeGroupProps) => import("react/jsx-runtime").JSX.Element | null;
21
+ Color: {
22
+ ({ children, variant, className }: import("@mintlify/components").ColorProps): import("react/jsx-runtime").JSX.Element;
23
+ Row: ({ children, title }: import("@mintlify/components").ColorRowProps) => import("react/jsx-runtime").JSX.Element;
24
+ Item: ({ name, value }: import("@mintlify/components").ColorItemProps) => import("react/jsx-runtime").JSX.Element;
25
+ };
26
+ Columns: ({ children, className, cols }: import("@mintlify/components").ColumnsProps) => import("react/jsx-runtime").JSX.Element;
27
+ Danger: (props: {
28
+ children: ReactNode;
29
+ className?: string | undefined;
30
+ color?: string | undefined;
31
+ icon?: ReactNode;
32
+ iconType?: ("brands" | "duotone" | "light" | "regular" | "sharp-duotone-solid" | "sharp-light" | "sharp-regular" | "sharp-solid" | "sharp-thin" | "solid" | "thin") | undefined;
33
+ iconLibrary?: ("fontawesome" | "lucide") | undefined;
34
+ ariaLabel?: string | undefined;
35
+ }) => import("react/jsx-runtime").JSX.Element;
36
+ Expandable: ({ title, defaultOpen, onChange: onChangeProp, lazy, className, children, uniqueParamId, onMount, onOpen, onClose, openedText, closedText, hasScrolledToAnchorRef, anchor, }: import("@mintlify/components").ExpandableProps) => import("react/jsx-runtime").JSX.Element;
37
+ Frame: ({ as: Component, title, description, renderDescription, style, className, children, }: import("@mintlify/components").FrameProps) => import("react/jsx-runtime").JSX.Element;
38
+ Icon: ({ icon, iconType, color, colorLight, colorDark, size, className, iconLibrary, basePath, pageType, overrideColor, overrideSize, }: import("@mintlify/components").IconProps) => import("react/jsx-runtime").JSX.Element | null;
39
+ Info: (props: {
40
+ children: ReactNode;
41
+ className?: string | undefined;
42
+ color?: string | undefined;
43
+ icon?: ReactNode;
44
+ iconType?: ("brands" | "duotone" | "light" | "regular" | "sharp-duotone-solid" | "sharp-light" | "sharp-regular" | "sharp-solid" | "sharp-thin" | "solid" | "thin") | undefined;
45
+ iconLibrary?: ("fontawesome" | "lucide") | undefined;
46
+ ariaLabel?: string | undefined;
47
+ }) => import("react/jsx-runtime").JSX.Element;
48
+ Mermaid: ({ chart, className, ariaLabel, placement, actions, }: import("@mintlify/components").MermaidProps) => import("react/jsx-runtime").JSX.Element;
49
+ Note: (props: {
50
+ children: ReactNode;
51
+ className?: string | undefined;
52
+ color?: string | undefined;
53
+ icon?: ReactNode;
54
+ iconType?: ("brands" | "duotone" | "light" | "regular" | "sharp-duotone-solid" | "sharp-light" | "sharp-regular" | "sharp-solid" | "sharp-thin" | "solid" | "thin") | undefined;
55
+ iconLibrary?: ("fontawesome" | "lucide") | undefined;
56
+ ariaLabel?: string | undefined;
57
+ }) => import("react/jsx-runtime").JSX.Element;
58
+ Panel: ({ children, className, ...props }: import("@mintlify/components").PanelProps) => import("react/jsx-runtime").JSX.Element;
59
+ Property: ({ name, type, location, hidden, default: defaultValue, required, deprecated, children, id, pre, post, className, onMount, navigateToHeaderAriaLabel, defaultLabel, requiredLabel, deprecatedLabel, }: import("@mintlify/components").PropertyProps) => import("react/jsx-runtime").JSX.Element | null;
60
+ Steps: {
61
+ ({ children, titleSize, className }: import("@mintlify/components").StepsProps): import("react/jsx-runtime").JSX.Element;
62
+ Item: ({ stepNumber, icon, iconType, iconLibrary, title, children, titleSize, className, isLast, id, noAnchor, scrollElementIntoView, onRegisterHeading, onUnregisterHeading, _hasContext, onCopyAnchorLink, }: import("@mintlify/components").StepsItemProps) => import("react/jsx-runtime").JSX.Element;
63
+ };
64
+ Tabs: (({ children, defaultTabIndex, onTabChange, className, borderBottom, ariaLabel, panelsRef, }: import("@mintlify/components").TabsProps) => import("react/jsx-runtime").JSX.Element) & {
65
+ Item: ({ children }: import("@mintlify/components").TabsItemProps) => import("react/jsx-runtime").JSX.Element;
66
+ };
67
+ Tile: ({ href, children, title, description, className }: import("@mintlify/components").TileProps) => import("react/jsx-runtime").JSX.Element;
68
+ Tip: (props: {
69
+ children: ReactNode;
70
+ className?: string | undefined;
71
+ color?: string | undefined;
72
+ icon?: ReactNode;
73
+ iconType?: ("brands" | "duotone" | "light" | "regular" | "sharp-duotone-solid" | "sharp-light" | "sharp-regular" | "sharp-solid" | "sharp-thin" | "solid" | "thin") | undefined;
74
+ iconLibrary?: ("fontawesome" | "lucide") | undefined;
75
+ ariaLabel?: string | undefined;
76
+ }) => import("react/jsx-runtime").JSX.Element;
77
+ Tooltip: ({ description, children, title, cta, href, className, side, align, }: import("@mintlify/components").TooltipProps) => import("react/jsx-runtime").JSX.Element | null;
78
+ Tree: (({ className, children }: import("@mintlify/components").TreeProps) => import("react/jsx-runtime").JSX.Element) & {
79
+ File: ({ name }: import("@mintlify/components").TreeFileProps) => import("react/jsx-runtime").JSX.Element;
80
+ Folder: ({ name, defaultOpen, children, openable: _openable, }: import("@mintlify/components").TreeFolderProps) => import("react/jsx-runtime").JSX.Element;
81
+ };
82
+ Update: import("react").ForwardRefExoticComponent<{
83
+ id: string;
84
+ label: string;
85
+ description?: string;
86
+ tags?: string[];
87
+ rss?: {
88
+ title?: string;
89
+ description?: string;
90
+ };
91
+ onRegisterHeading?: (id: string, rect: import("react-use-rect").Rect) => void;
92
+ onUnregisterHeading?: (id: string) => void;
93
+ hasContext?: boolean;
94
+ isVisible: boolean;
95
+ onCopyAnchorLink?: (id: string) => void;
96
+ } & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof {
97
+ id: string;
98
+ label: string;
99
+ description?: string;
100
+ tags?: string[];
101
+ rss?: {
102
+ title?: string;
103
+ description?: string;
104
+ };
105
+ onRegisterHeading?: (id: string, rect: import("react-use-rect").Rect) => void;
106
+ onUnregisterHeading?: (id: string) => void;
107
+ hasContext?: boolean;
108
+ isVisible: boolean;
109
+ onCopyAnchorLink?: (id: string) => void;
110
+ }> & import("react").RefAttributes<HTMLDivElement>>;
111
+ Warning: (props: {
112
+ children: ReactNode;
113
+ className?: string | undefined;
114
+ color?: string | undefined;
115
+ icon?: ReactNode;
116
+ iconType?: ("brands" | "duotone" | "light" | "regular" | "sharp-duotone-solid" | "sharp-light" | "sharp-regular" | "sharp-solid" | "sharp-thin" | "solid" | "thin") | undefined;
117
+ iconLibrary?: ("fontawesome" | "lucide") | undefined;
118
+ ariaLabel?: string | undefined;
119
+ }) => import("react/jsx-runtime").JSX.Element;
120
+ };
121
+ export declare function useMDXComponents(): MDXComponents;
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Accordion, Badge, Callout, Card, Check, CodeBlock, CodeGroup, Color, Columns, Danger, Expandable, Frame, Icon, Info, Mermaid, Note, Panel, Property, Steps, Tabs, Tile, Tip, Tooltip, Tree, Update, Warning, } from '@mintlify/components';
3
+ const Wrapper = ({ children }) => (_jsx("div", { children: children }));
4
+ const MintlifyComponents = {
5
+ Accordion,
6
+ Badge,
7
+ Callout,
8
+ Card,
9
+ Check,
10
+ CodeBlock,
11
+ CodeGroup,
12
+ Color,
13
+ Columns,
14
+ Danger,
15
+ Expandable,
16
+ Frame,
17
+ Icon,
18
+ Info,
19
+ Mermaid,
20
+ Note,
21
+ Panel,
22
+ Property,
23
+ Steps,
24
+ Tabs,
25
+ Tile,
26
+ Tip,
27
+ Tooltip,
28
+ Tree,
29
+ Update,
30
+ Warning,
31
+ };
32
+ const placeholderComponents = {
33
+ AccordionGroup: Wrapper,
34
+ ApiPlayground: Wrapper,
35
+ CardGroup: Wrapper,
36
+ Column: Wrapper,
37
+ CustomCode: Wrapper,
38
+ CustomComponent: Wrapper,
39
+ DynamicCustomComponent: Wrapper,
40
+ Heading: Wrapper,
41
+ Latex: Wrapper,
42
+ Link: Wrapper,
43
+ Loom: Wrapper,
44
+ MDXContentController: Wrapper,
45
+ Param: Wrapper,
46
+ ParamField: Wrapper,
47
+ PreviewButton: Wrapper,
48
+ RequestExample: Wrapper,
49
+ ResponseExample: Wrapper,
50
+ ResponseField: Wrapper,
51
+ Snippet: Wrapper,
52
+ SnippetGroup: Wrapper,
53
+ View: Wrapper,
54
+ };
55
+ export const components = {
56
+ ...MintlifyComponents,
57
+ ...placeholderComponents,
58
+ };
59
+ export function useMDXComponents() {
60
+ return {
61
+ ...components,
62
+ Step: Steps.Item,
63
+ Tab: Tabs.Item,
64
+ };
65
+ }
@@ -0,0 +1,2 @@
1
+ import type { DocsConfig } from '@mintlify/validation';
2
+ export declare function readDocsConfig(docsDir: string): Promise<DocsConfig>;
@@ -0,0 +1,7 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export async function readDocsConfig(docsDir) {
4
+ const configPath = join(docsDir, 'docs.json');
5
+ const content = await readFile(configPath, 'utf-8');
6
+ return JSON.parse(content);
7
+ }
@@ -0,0 +1,5 @@
1
+ export interface PageContent {
2
+ content: string;
3
+ slug: string;
4
+ }
5
+ export declare function readPageContent(pagePath: string, docsDir: string): Promise<PageContent>;
@@ -0,0 +1,10 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export async function readPageContent(pagePath, docsDir) {
4
+ const filePath = join(docsDir, `${pagePath}.mdx`);
5
+ const content = await readFile(filePath, 'utf-8');
6
+ return {
7
+ content,
8
+ slug: pagePath,
9
+ };
10
+ }
@@ -0,0 +1,2 @@
1
+ import type { SnippetFile } from './load-all-snippets.js';
2
+ export declare function resolveSnippets(content: string, filename: string, snippets: SnippetFile[]): Promise<string>;
@@ -0,0 +1,28 @@
1
+ import { getAST, findAndRemoveImports, resolveAllImports, stringifyTree, } from '@mintlify/common';
2
+ import { getLogger } from './logger.js';
3
+ export async function resolveSnippets(content, filename, snippets) {
4
+ try {
5
+ const tree = getAST(content, filename);
6
+ const { importMap, tree: treeWithoutImports } = await findAndRemoveImports(structuredClone(tree));
7
+ if (Object.keys(importMap).length === 0) {
8
+ return content;
9
+ }
10
+ const snippetTrees = snippets.map((s) => ({
11
+ filename: s.filename,
12
+ tree: s.tree,
13
+ }));
14
+ const resolvedTree = await resolveAllImports({
15
+ snippets: snippetTrees,
16
+ fileWithImports: {
17
+ tree: treeWithoutImports,
18
+ filename,
19
+ importMap,
20
+ },
21
+ });
22
+ return stringifyTree(resolvedTree);
23
+ }
24
+ catch (error) {
25
+ getLogger()?.warn(`Failed to resolve snippets in ${filename}: ${error}`);
26
+ return content;
27
+ }
28
+ }
@@ -0,0 +1,2 @@
1
+ import type { Favicons } from '../types';
2
+ export declare function writeFaviconsManifest(srcDir: string, favicons: Favicons): Promise<void>;
@@ -0,0 +1,8 @@
1
+ import { join } from 'node:path';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ export async function writeFaviconsManifest(srcDir, favicons) {
4
+ const generatedDir = join(srcDir, 'content/generated');
5
+ await mkdir(generatedDir, { recursive: true });
6
+ const filePath = join(generatedDir, 'favicons.json');
7
+ await writeFile(filePath, JSON.stringify(favicons, null, 2));
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { SnippetFile } from './load-all-snippets.js';
2
+ export interface WritePageContentOptions {
3
+ content: string;
4
+ slug: string;
5
+ contentDir: string;
6
+ componentsDir: string;
7
+ userComponents: Set<string>;
8
+ snippets: SnippetFile[];
9
+ }
10
+ export declare function writePageContent({ content, slug, contentDir, componentsDir, userComponents, snippets, }: WritePageContentOptions): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { extractReactComponents } from './extract-react-components.js';
4
+ import { resolveSnippets } from './resolve-snippets.js';
5
+ export async function writePageContent({ content, slug, contentDir, componentsDir, userComponents, snippets, }) {
6
+ let filePath;
7
+ if (slug === 'index') {
8
+ filePath = join(contentDir, 'index.mdx');
9
+ }
10
+ else {
11
+ const parts = slug.split('/');
12
+ const fileName = parts.pop() + '.mdx';
13
+ const dirPath = join(contentDir, ...parts);
14
+ if (parts.length > 0) {
15
+ await mkdir(dirPath, { recursive: true });
16
+ }
17
+ filePath = join(dirPath, fileName);
18
+ }
19
+ // Resolve all snippet/MDX imports by inlining their content
20
+ // This happens before React component extraction since snippets may contain React components
21
+ const contentWithResolvedSnippets = await resolveSnippets(content, `/${slug}.mdx`, snippets);
22
+ // Extract compound React components to separate .jsx files
23
+ // This is necessary because Astro compiles MDX with its own JSX runtime,
24
+ // which is incompatible with React hooks. By extracting to .jsx files and
25
+ // re-importing with client:load, we ensure React's JSX runtime handles these components.
26
+ const finalContent = await extractReactComponents(contentWithResolvedSnippets, filePath, componentsDir, slug, userComponents);
27
+ await writeFile(filePath, finalContent, 'utf-8');
28
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@mintlify/astro",
3
+ "version": "0.1.1",
4
+ "description": "Mintlify integration for Astro",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "sideEffects": false,
8
+ "type": "module",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./components": {
18
+ "types": "./dist/components.d.ts",
19
+ "import": "./dist/components.js"
20
+ }
21
+ },
22
+ "scripts": {
23
+ "prepare": "npm run build",
24
+ "build": "tsc",
25
+ "type": "tsc --noEmit"
26
+ },
27
+ "author": "Mintlify, Inc.",
28
+ "license": "ISC",
29
+ "publishConfig": {
30
+ "access": "restricted",
31
+ "registry": "https://registry.npmjs.org/"
32
+ },
33
+ "dependencies": {
34
+ "@mdx-js/mdx": "^3.1.1",
35
+ "@mintlify/common": "^1.0.716",
36
+ "@mintlify/validation": "^0.1.587",
37
+ "unist-util-visit": "^5.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@mintlify/components": "^1.0.4",
41
+ "@types/node": "^25.2.2",
42
+ "@types/react": "^19.2.13",
43
+ "astro": "^5.17.1",
44
+ "mdast": "^2.3.2",
45
+ "mdast-util-mdx": "^3.0.0",
46
+ "react": "^19.2.3",
47
+ "react-dom": "^19.2.3",
48
+ "typescript": "^5.9.3"
49
+ },
50
+ "engines": {
51
+ "node": ">=18"
52
+ },
53
+ "peerDependencies": {
54
+ "@astrojs/mdx": "^4.3.1",
55
+ "@astrojs/react": "^4.4.2",
56
+ "@mintlify/components": "^1.0.4",
57
+ "astro": "^5",
58
+ "react": "^18 || ^19"
59
+ }
60
+ }