Repository navigation
Expand file tree
/
Copy pathfront-matter_linter.js
More file actions
126 lines (112 loc) · 3.25 KB
/
Copy pathfront-matter_linter.js
File metadata and controls
126 lines (112 loc) · 3.25 KB
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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 fdir_pkg from "fdir";
const { fdir, PathsOutput } = fdir_pkg;
import caporal from "@caporal/core";
const { program } = caporal;
import {
getAjvValidator,
checkFrontMatter,
getRelativePath,
} 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("tests/front-matter_test_files")
) {
return [file];
} else {
return [];
}
}
// lint front matter
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 true' option\n";
msg += fixableErrors.map((error) => `${error}`).join("\n");
}
throw new Error(msg);
}
}
function tryOrExit(f) {
return async ({ options = {}, ...args }) => {
try {
await f({ options, ...args });
} catch (error) {
if (options.verbose || options.v) {
console.error(error.stack);
}
throw error;
}
};
}
program
.option("--fix", "Save corrected output", {
validator: program.BOOLEAN,
default: false,
})
.option("--config-file", "Custom configuration file", {
validator: program.STRING,
default: "./front-matter-config.json",
})
.argument("[files...]", "list of files and/or directories to check", {
default: ["./files/en-us"],
})
.action(
tryOrExit(({ args, options, logger }) => {
const cwd = process.cwd();
const files = (args.files || []).map((f) => path.resolve(cwd, f));
if (!files.length) {
logger.info("No files to lint.");
return;
}
return lintFrontMatter(files, options);
}),
);
program.run();