| | import fs from "node:fs/promises"; |
| | import os from "node:os"; |
| | import path from "node:path"; |
| |
|
| | import { eachLimit } from "async"; |
| | import cliProgress from "cli-progress"; |
| | import yargs from "yargs"; |
| | import { hideBin } from "yargs/helpers"; |
| |
|
| | import { fdir } from "fdir"; |
| |
|
| | import { getAjvValidator, checkFrontMatter } from "./front-matter_utils.js"; |
| |
|
| | async function resolveDirectory(file) { |
| | const stats = await fs.lstat(file); |
| | if (stats.isDirectory()) { |
| | const api = new fdir() |
| | .withErrors() |
| | .withFullPaths() |
| | .filter((filePath) => filePath.endsWith("index.md")) |
| | .exclude((dirName) => dirName === "conflicting" || dirName === "orphaned") |
| | .crawl(file); |
| | return api.withPromise(); |
| | } else if ( |
| | stats.isFile() && |
| | file.endsWith("index.md") && |
| | !file.includes("/conflicting/") && |
| | !file.includes("/orphaned/") && |
| | !file.includes("scripts/filecheck/fixtures/") && |
| | !file.includes("tests/front-matter_test_files") |
| | ) { |
| | return [file]; |
| | } else { |
| | return []; |
| | } |
| | } |
| |
|
| | |
| | async function lintFrontMatter(filesAndDirectories, options) { |
| | const files = ( |
| | await Promise.all(filesAndDirectories.map(resolveDirectory)) |
| | ).flat(); |
| |
|
| | options.config = JSON.parse(await fs.readFile(options.configFile, "utf-8")); |
| |
|
| | options.validator = getAjvValidator(options.config.schema); |
| |
|
| | const progressBar = new cliProgress.SingleBar({ etaBuffer: 100 }); |
| | progressBar.start(files.length, 0); |
| |
|
| | const errors = []; |
| | const fixableErrors = []; |
| | await eachLimit(files, os.cpus().length, async (file) => { |
| | try { |
| | const [error, fixableError, content] = await checkFrontMatter( |
| | file, |
| | options, |
| | ); |
| | if (content) { |
| | fs.writeFile(file, content); |
| | } |
| | error && errors.push(error); |
| | fixableError && fixableErrors.push(fixableError); |
| | } catch (err) { |
| | errors.push(`${err}\n ${err.stack}`); |
| | } finally { |
| | progressBar.increment(); |
| | } |
| | }); |
| | progressBar.stop(); |
| | console.log(errors.length, fixableErrors.length); |
| | if (errors.length || fixableErrors.length) { |
| | let msg = errors.map((error) => `${error}`).join("\n\n"); |
| |
|
| | if (fixableErrors.length) { |
| | msg += "\n\nFollowing fixable errors can be fixed using '--fix' option\n"; |
| | msg += fixableErrors.map((error) => `${error}`).join("\n"); |
| | } |
| | throw new Error(msg); |
| | } |
| | } |
| |
|
| | yargs(hideBin(process.argv)) |
| | .command( |
| | "$0 [files...]", |
| | "Lint front matter in markdown files", |
| | (yargs) => { |
| | return yargs |
| | .positional("files", { |
| | type: "string", |
| | array: true, |
| | describe: "List of files and/or directories to check", |
| | default: ["./files/en-us"], |
| | }) |
| | .option("fix", { |
| | type: "boolean", |
| | describe: "Save corrected output", |
| | default: false, |
| | }) |
| | .option("config-file", { |
| | type: "string", |
| | describe: "Custom configuration file", |
| | default: "./front-matter-config.json", |
| | }); |
| | }, |
| | async (argv) => { |
| | const cwd = process.cwd(); |
| | const files = (argv.files || []).map((f) => path.resolve(cwd, f)); |
| |
|
| | if (!files.length) { |
| | console.info("No files to lint."); |
| | return; |
| | } |
| |
|
| | |
| | const options = { |
| | fix: argv.fix, |
| | configFile: argv.configFile, |
| | }; |
| |
|
| | await lintFrontMatter(files, options); |
| | }, |
| | ) |
| | .help().argv; |
| |
|