2023-04-28 10:56:35 +05:30
|
|
|
import fs from "node:fs/promises";
|
|
|
|
|
import path from "node:path";
|
|
|
|
|
|
2025-02-05 18:09:05 +01:00
|
|
|
import YAML from "yaml";
|
2023-07-07 23:16:12 +05:30
|
|
|
import * as prettier from "prettier";
|
2023-04-28 10:56:35 +05:30
|
|
|
import AJV from "ajv";
|
|
|
|
|
import grayMatter from "gray-matter";
|
|
|
|
|
import addFormats from "ajv-formats";
|
|
|
|
|
import { betterAjvErrors } from "@apideck/better-ajv-errors";
|
|
|
|
|
|
|
|
|
|
export function getRelativePath(filePath) {
|
|
|
|
|
return path.relative(process.cwd(), filePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getAjvValidator(schema) {
|
|
|
|
|
const ajv = new AJV({ allowUnionTypes: true, allErrors: true });
|
|
|
|
|
addFormats.default(ajv);
|
|
|
|
|
return ajv.compile(schema);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function areAttributesInOrder(frontMatter, order) {
|
|
|
|
|
const attrs = Object.keys(frontMatter).filter((item) => order.includes(item));
|
|
|
|
|
const orderedAttrs = order.filter((item) => frontMatter[item]);
|
|
|
|
|
return orderedAttrs.every((item, index) => item === attrs[index]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function checkFrontMatter(filePath, options) {
|
|
|
|
|
let content = await fs.readFile(filePath, "utf-8");
|
|
|
|
|
const document = grayMatter(content);
|
|
|
|
|
const fmObject = document.data;
|
|
|
|
|
const order = options.config["attribute-order"];
|
|
|
|
|
|
|
|
|
|
// validate and collect errors
|
|
|
|
|
const validator = options.validator;
|
|
|
|
|
const valid = validator(fmObject);
|
|
|
|
|
const validationErrors = betterAjvErrors({
|
|
|
|
|
schema: validator.schema,
|
|
|
|
|
data: fmObject,
|
|
|
|
|
errors: validator.errors,
|
|
|
|
|
});
|
|
|
|
|
const errors = [];
|
|
|
|
|
if (!valid) {
|
|
|
|
|
for (const error of validationErrors) {
|
|
|
|
|
let message = error.message.replace("{base}", "Front matter");
|
|
|
|
|
if (error.context.allowedValues) {
|
|
|
|
|
message += `:\n\t${error.context.allowedValues.join(", ")}`;
|
|
|
|
|
}
|
|
|
|
|
errors.push(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isInOrder = areAttributesInOrder(fmObject, order);
|
|
|
|
|
let fixableError = null;
|
|
|
|
|
|
|
|
|
|
if (!options.fix && !isInOrder) {
|
|
|
|
|
fixableError = `${getRelativePath(
|
2023-07-07 23:16:12 +05:30
|
|
|
filePath,
|
2023-04-28 10:56:35 +05:30
|
|
|
)}\n\t Front matter attributes are not in required order: ${order.join(
|
2023-07-07 23:16:12 +05:30
|
|
|
"->",
|
2023-04-28 10:56:35 +05:30
|
|
|
)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if --fix option is true, then fix the order and prettify
|
|
|
|
|
if (options.fix) {
|
|
|
|
|
const fmOrdered = {};
|
|
|
|
|
for (const attr of options.config["attribute-order"]) {
|
|
|
|
|
const value = fmObject[attr];
|
|
|
|
|
if (value) {
|
|
|
|
|
if (attr === "status" && Array.isArray(value) && value.length) {
|
|
|
|
|
fmOrdered[attr] = value.sort();
|
|
|
|
|
} else if (attr === "browser-compat" || attr === "spec-urls") {
|
|
|
|
|
if (Array.isArray(value) && value.length === 1) {
|
|
|
|
|
fmOrdered[attr] = value[0];
|
|
|
|
|
} else {
|
|
|
|
|
fmOrdered[attr] = value;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
fmOrdered[attr] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-05 18:09:05 +01:00
|
|
|
let yml = YAML.stringify(fmOrdered, null, {
|
2023-04-28 10:56:35 +05:30
|
|
|
lineWidth: options.config.lineWidth,
|
|
|
|
|
});
|
|
|
|
|
yml = yml.replace(/[\s\n]+$/g, "");
|
2023-07-07 23:16:12 +05:30
|
|
|
yml = await prettier.format(yml, { parser: "yaml" });
|
|
|
|
|
content = `---\n${yml}---\n${document.content}`;
|
2023-04-28 10:56:35 +05:30
|
|
|
} else {
|
|
|
|
|
content = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
errors.length
|
|
|
|
|
? `Error: ${getRelativePath(filePath)}\n${errors.join("\n")}`
|
|
|
|
|
: null,
|
|
|
|
|
fixableError,
|
|
|
|
|
content,
|
|
|
|
|
];
|
|
|
|
|
}
|