Skip to content

Commit f2c8a6b

Browse files
authored
ci: lint markdown frontmatter (#2032)
1 parent 1755c9e commit f2c8a6b

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

.github/workflows/lint.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ jobs:
1414
node-version: '12.x'
1515
- run: npm install -g markdownlint-cli@0.25.0
1616
- run: markdownlint '**/*.md' --ignore node_modules
17+
frontmatter:
18+
name: 🍒 Frontmatter
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v3.1.0
22+
- name: 🚀 Use Node.js
23+
uses: actions/setup-node@v3
24+
with:
25+
node-version: '16.x'
26+
- run: yarn install --frozen-lockfile --ignore-scripts
27+
- run: yarn lint:frontmatter
1728
yamllint:
1829
name: 🍏 YAML
1930
runs-on: ubuntu-latest

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"serve:doc": "yarn workspace doc docusaurus serve",
4040
"serve:blog:zh": "yarn workspace blog docusaurus serve zh",
4141
"serve:blog:en": "yarn workspace blog docusaurus serve en",
42+
"lint:frontmatter": "node scripts/lint-frontmatter.js",
4243
"build:website:preview:serve": "yarn build:website:preview && yarn serve:website",
4344
"build:doc:preview:serve": "yarn build:doc:preview && yarn serve:doc",
4445
"build:blog:zh:preview:serve": "yarn build:blog:zh:preview && yarn serve:blog:zh",
@@ -60,6 +61,7 @@
6061
"eslint-plugin-react-hooks": "^4.3.0",
6162
"eslint-plugin-yml": "^0.14.0",
6263
"husky": ">=6",
64+
"js-yaml": "^4.1.0",
6365
"lint-staged": ">=10",
6466
"remark": "^14.0.2",
6567
"remark-cli": "^11.0.0",

scripts/lint-frontmatter.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* eslint-disable no-console */
2+
const fs = require('fs');
3+
const path = require('path');
4+
const yaml = require('js-yaml');
5+
6+
const ROOT = path.resolve(__dirname, '..');
7+
const IGNORED_DIRS = new Set([
8+
'.git',
9+
'build',
10+
'dist',
11+
'node_modules',
12+
]);
13+
const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
14+
15+
function walk(dir, files = []) {
16+
fs.readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
17+
if (entry.isDirectory()) {
18+
if (!IGNORED_DIRS.has(entry.name)) {
19+
walk(path.join(dir, entry.name), files);
20+
}
21+
return;
22+
}
23+
24+
if (entry.isFile() && MARKDOWN_EXTENSIONS.has(path.extname(entry.name))) {
25+
files.push(path.join(dir, entry.name));
26+
}
27+
});
28+
29+
return files;
30+
}
31+
32+
function extractFrontmatter(content) {
33+
if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) {
34+
return null;
35+
}
36+
37+
const lines = content.split(/\r?\n/);
38+
const closingIndex = lines.slice(1).findIndex((line) => line.trim() === '---');
39+
if (closingIndex >= 0) {
40+
return lines.slice(1, closingIndex + 1).join('\n');
41+
}
42+
43+
throw new Error('Missing closing frontmatter delimiter');
44+
}
45+
46+
function main() {
47+
const failures = [];
48+
49+
walk(ROOT).forEach((file) => {
50+
const relativePath = path.relative(ROOT, file);
51+
const content = fs.readFileSync(file, 'utf8');
52+
53+
try {
54+
const frontmatter = extractFrontmatter(content);
55+
if (frontmatter !== null) {
56+
yaml.load(frontmatter);
57+
}
58+
} catch (error) {
59+
failures.push(`${relativePath}: ${error.message}`);
60+
}
61+
});
62+
63+
if (failures.length > 0) {
64+
console.error('Invalid Markdown frontmatter found:\n');
65+
failures.forEach((failure) => console.error(`- ${failure}`));
66+
process.exit(1);
67+
}
68+
}
69+
70+
main();

0 commit comments

Comments
 (0)