UNPKG

fumadocs-docgen

Version:

Useful remark utilities and plugins

204 lines (203 loc) 5.55 kB
import { visit } from "unist-util-visit"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import { z } from "zod"; import convert from "npm-to-yarn"; //#region src/remark-docgen.ts const metaRegex = /doc-gen:(?<name>.+)/; function remarkDocGen({ generators = [] }) { return async (tree, file) => { generators.forEach((gen) => gen.onFile?.(tree, file)); const queue = []; visit(tree, "code", (code, _, parent) => { if (code.lang !== "json" || !code.meta || !parent) return; const matches = metaRegex.exec(code.meta); if (!matches) return; const name = matches[1]; const gen = generators.find((g) => g.name === name); const run = async () => { const result = await gen?.run(JSON.parse(code.value), { cwd: file.cwd, path: file.path, node: code }); const index = parent.children.findIndex((c) => c === code); if (result && index !== -1) { const items = Array.isArray(result) ? result : [result]; parent.children.splice(index, 1, ...items); } }; queue.push(run()); }); await Promise.all(queue); }; } //#endregion //#region src/utils.ts function createElement(name, attributes, children) { const element = { type: "mdxJsxFlowElement", name, attributes }; if (children) element.children = children; return element; } function expressionToAttribute(key, value) { return { type: "mdxJsxAttribute", name: key, value: { type: "mdxJsxAttributeValueExpression", value: "", data: { estree: { type: "Program", body: [{ type: "ExpressionStatement", expression: value }] } } } }; } //#endregion //#region src/file-generator.ts const fileGeneratorSchema = z.object({ file: z.string(), /** * Turn file content into a code block * * @defaultValue false */ codeblock: z.union([z.object({ lang: z.string().optional(), meta: z.string().optional() }), z.boolean()]).default(false) }); function fileGenerator({ relative = false, trim = true } = {}) { return { name: "file", async run(input, ctx) { const { file, codeblock = false } = fileGeneratorSchema.parse(input); const dest = relative ? path.resolve(ctx.cwd, path.dirname(ctx.path), file) : path.resolve(ctx.cwd, file); let value = await fs.readFile(dest, "utf-8"); if (trim) value = value.trim(); if (codeblock === false) return { type: "paragraph", children: [{ type: "text", value }] }; const codeOptions = codeblock === true ? {} : codeblock; return { type: "code", lang: codeOptions.lang ?? path.extname(dest).slice(1), meta: codeOptions.meta, value }; } }; } //#endregion //#region src/remark-install.ts /** * It generates the following structure from a code block with `package-install` as language * * @example * ```tsx * <Tabs items={["npm", "pnpm", "yarn", "bun"]}> * <Tab value="pnpm">...</Tab> * ... * </Tabs> * ``` * * @deprecated Use `remarkNpm` from `fumadocs-core/mdx-plugins` instead, it's a drop-in replacement. */ function remarkInstall({ Tab = "Tab", Tabs = "Tabs", persist = false, packageManagers = [ { command: (cmd) => convert(cmd, "npm"), name: "npm" }, { command: (cmd) => convert(cmd, "pnpm"), name: "pnpm" }, { command: (cmd) => convert(cmd, "yarn"), name: "yarn" }, { command: (cmd) => convert(cmd, "bun"), name: "bun" } ] } = {}) { return (tree) => { visit(tree, "code", (node) => { if (node.lang !== "package-install") return "skip"; const value = node.value.startsWith("npm") || node.value.startsWith("npx") ? node.value : `npm install ${node.value}`; const insert = createElement(Tabs, [...typeof persist === "object" ? [{ type: "mdxJsxAttribute", name: "groupId", value: persist.id }, { type: "mdxJsxAttribute", name: "persist", value: null }] : [], expressionToAttribute("items", { type: "ArrayExpression", elements: packageManagers.map(({ name }) => ({ type: "Literal", value: name })) })], packageManagers.map(({ command, name }) => ({ type: "mdxJsxFlowElement", name: Tab, attributes: [{ type: "mdxJsxAttribute", name: "value", value: name }], children: [{ type: "code", lang: "bash", meta: node.meta, value: command(value) }] }))); Object.assign(node, insert); }); }; } //#endregion //#region src/remark-show.ts function remarkShow(options) { const variables = options?.variables ?? {}; return async (tree, file) => { const { toJs } = await import("estree-util-to-js"); const tasks = []; visit(tree, "mdxJsxFlowElement", (node) => { if (node.name !== "show") return; for (const attr of node.attributes) { if (attr.type !== "mdxJsxAttribute" || attr.name !== "on") continue; if (!attr.value || typeof attr.value !== "object" || !attr.value.data?.estree) return "skip"; const js = toJs(attr.value.data.estree); const callback = new Function(...Object.keys(variables), `return ${js.value}`)(...Object.values(variables)); tasks.push((async () => { const value = typeof callback === "function" ? await callback(file) : callback; Object.assign(node, { type: "mdxJsxFlowElement", name: null, attributes: [], children: value === true ? node.children : [] }); })()); return "skip"; } }); await Promise.all(tasks); }; } //#endregion export { createElement, expressionToAttribute, fileGenerator, fileGeneratorSchema, remarkDocGen, remarkInstall, remarkShow }; //# sourceMappingURL=index.js.map