| | 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"]; |
| |
|
| | |
| | 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 (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, |
| | ]; |
| | } |
| |
|