docusaurus-mdx-checker
Version:
Check your Docusaurus content for MDX v2 parsing errors
150 lines (132 loc) • 4.32 kB
JavaScript
import fs from "node:fs/promises";
import path from "node:path";
import { globby } from "globby";
import { compile } from "@mdx-js/mdx";
import chalk from "chalk";
import { getUnknownGlobals } from "./globals.js";
import { preprocess } from "./compat.js";
import {
DefaultRemarkPlugins,
DefaultRehypePlugins,
DefaultInclude,
DefaultExclude,
DefaultGlobals,
} from "./constants.js";
const SuccessPrefix = chalk.green("[SUCCESS]");
const ErrorPrefix = chalk.red("[ERROR]");
////////////////////////////////////////
// Script
export default async function main({
cwd = undefined,
include = DefaultInclude,
exclude = DefaultExclude,
remarkPlugins = DefaultRemarkPlugins,
rehypePlugins = DefaultRehypePlugins,
format = "mdx",
verbose = false,
globals = DefaultGlobals,
}) {
const allRelativeFilePaths = await globby(include, {
cwd,
ignore: exclude,
// gitignore: true, // Does not work well with relative paths like ../docs
});
allRelativeFilePaths.sort();
if (allRelativeFilePaths.length === 0) {
throw new Error(`${ErrorPrefix} Couldn't find any file to compile!`);
}
if (verbose) {
console.log(
"Found " + allRelativeFilePaths.length + " files to compile with MDX"
);
console.log(
"List of files:\n",
JSON.stringify(allRelativeFilePaths, null, 2)
);
}
const allResults = await Promise.all(
allRelativeFilePaths.map(processFilePath)
);
const allErrors = allResults.filter((r) => r.status === "error");
const allSuccess = allResults.filter((r) => r.status === "success");
if (verbose) {
console.log(`Errors: ${allErrors.length}`);
console.log(`Success: ${allSuccess.length}`);
}
if (allErrors.length > 0) {
const outputSeparator = `\n${chalk.yellow("---")}\n`;
throw new Error(
`${ErrorPrefix} ${allErrors.length}/${
allRelativeFilePaths.length
} MDX files couldn't compile!${outputSeparator}${allErrors
.map((error) => error.errorMessage)
.join(outputSeparator)}${outputSeparator}`
);
} else {
return `${SuccessPrefix} All ${allRelativeFilePaths.length} MDX files compiled successfully!`;
}
////////////////////////////////////////
// Functions
async function processFilePath(relativeFilePath) {
const filePath = path.resolve(cwd, relativeFilePath);
const fileFormat =
format === "detect" ? (filePath.endsWith(".md") ? "md" : "mdx") : "mdx";
try {
// console.log("filePath", filePath);
const fileContent = await fs.readFile(filePath, "utf8");
// console.log("fileContent", fileContent);
const contentPreprocessed = preprocess(fileContent);
const compilerOptions = {
format: fileFormat,
remarkPlugins,
rehypePlugins,
};
const result = await compile(contentPreprocessed, compilerOptions);
if (globals !== null) {
const unknownGlobals = getUnknownGlobals(result.value, globals);
if (unknownGlobals.length > 0) {
throw new Error(
`These MDX global variables do not seem to be available in scope: ${unknownGlobals.join(
" "
)}`
);
}
}
/*
// const fileToDebug = "docs/introduction.md";
const fileToDebug = undefined;
if (relativeFilePath === fileToDebug) {
console.log({
relativeFilePath,
fileContent,
contentPreprocessed,
result: result.toString(),
compilerOptions,
});
}
*/
// TODO generate warnings for compat options here?
return { relativeFilePath, status: "success", result };
} catch (error) {
const errorMessage = `${chalk.red(
"Error while compiling file"
)} ${chalk.blue(relativeFilePath)}${formatMDXLineColumn(error)}
Details: ${error.message}`;
return { relativeFilePath, status: "error", error, errorMessage };
}
}
function formatMDXLineColumn(error) {
if (error.line || error.column) {
let lineColumn = "";
if (error.line) {
lineColumn = lineColumn + "Line=" + error.line + " ";
}
if (error.column) {
lineColumn = lineColumn + "Column=" + error.column;
}
return ` (${lineColumn})`;
}
return "";
}
}