File size: 2,986 Bytes
780c9fe |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
import fs from "node:fs/promises";
import path from "node:path";
import YAML from "yaml";
import * as prettier from "prettier";
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(
filePath,
)}\n\t Front matter attributes are not in required order: ${order.join(
"->",
)}`;
}
// 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;
}
}
}
let yml = YAML.stringify(fmOrdered, null, {
lineWidth: options.config.lineWidth,
});
yml = yml.replace(/[\s\n]+$/g, "");
yml = await prettier.format(yml, { parser: "yaml" });
content = `---\n${yml}---\n${document.content}`;
} else {
content = null;
}
return [
errors.length
? `Error: ${getRelativePath(filePath)}\n${errors.join("\n")}`
: null,
fixableError,
content,
];
}
|