This is an automated email from the ASF dual-hosted git repository.

guoqi pushed a commit to branch add-frontmatter-lint
in repository https://gitbox.apache.org/repos/asf/apisix-website.git

commit 4ce5f567a0b01f8ebbdbde78b0f8621c5fced387
Author: Qi Guo <[email protected]>
AuthorDate: Thu Apr 16 15:26:11 2026 +0800

    ci: lint markdown frontmatter
---
 .github/workflows/lint.yml  | 11 +++++++
 package.json                |  2 ++
 scripts/lint-frontmatter.js | 70 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+)

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e9ea7577503..a89cf69beef 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -14,6 +14,17 @@ jobs:
           node-version: '12.x'
       - run: npm install -g [email protected]
       - run: markdownlint '**/*.md' --ignore node_modules
+  frontmatter:
+    name: 🍒 Frontmatter
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/[email protected]
+      - name: 🚀 Use Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: '16.x'
+      - run: yarn install --frozen-lockfile --ignore-scripts
+      - run: yarn lint:frontmatter
   yamllint:
     name: 🍏 YAML
     runs-on: ubuntu-latest
diff --git a/package.json b/package.json
index 24681a8f15a..cd5e20fe3c8 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
     "serve:doc": "yarn workspace doc docusaurus serve",
     "serve:blog:zh": "yarn workspace blog docusaurus serve zh",
     "serve:blog:en": "yarn workspace blog docusaurus serve en",
+    "lint:frontmatter": "node scripts/lint-frontmatter.js",
     "build:website:preview:serve": "yarn build:website:preview && yarn 
serve:website",
     "build:doc:preview:serve": "yarn build:doc:preview && yarn serve:doc",
     "build:blog:zh:preview:serve": "yarn build:blog:zh:preview && yarn 
serve:blog:zh",
@@ -60,6 +61,7 @@
     "eslint-plugin-react-hooks": "^4.3.0",
     "eslint-plugin-yml": "^0.14.0",
     "husky": ">=6",
+    "js-yaml": "^4.1.0",
     "lint-staged": ">=10",
     "remark": "^14.0.2",
     "remark-cli": "^11.0.0",
diff --git a/scripts/lint-frontmatter.js b/scripts/lint-frontmatter.js
new file mode 100644
index 00000000000..9464beeda01
--- /dev/null
+++ b/scripts/lint-frontmatter.js
@@ -0,0 +1,70 @@
+/* eslint-disable no-console */
+const fs = require('fs');
+const path = require('path');
+const yaml = require('js-yaml');
+
+const ROOT = path.resolve(__dirname, '..');
+const IGNORED_DIRS = new Set([
+  '.git',
+  'build',
+  'dist',
+  'node_modules',
+]);
+const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
+
+function walk(dir, files = []) {
+  fs.readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
+    if (entry.isDirectory()) {
+      if (!IGNORED_DIRS.has(entry.name)) {
+        walk(path.join(dir, entry.name), files);
+      }
+      return;
+    }
+
+    if (entry.isFile() && MARKDOWN_EXTENSIONS.has(path.extname(entry.name))) {
+      files.push(path.join(dir, entry.name));
+    }
+  });
+
+  return files;
+}
+
+function extractFrontmatter(content) {
+  if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) {
+    return null;
+  }
+
+  const lines = content.split(/\r?\n/);
+  const closingIndex = lines.slice(1).findIndex((line) => line.trim() === 
'---');
+  if (closingIndex >= 0) {
+    return lines.slice(1, closingIndex + 1).join('\n');
+  }
+
+  throw new Error('Missing closing frontmatter delimiter');
+}
+
+function main() {
+  const failures = [];
+
+  walk(ROOT).forEach((file) => {
+    const relativePath = path.relative(ROOT, file);
+    const content = fs.readFileSync(file, 'utf8');
+
+    try {
+      const frontmatter = extractFrontmatter(content);
+      if (frontmatter !== null) {
+        yaml.load(frontmatter);
+      }
+    } catch (error) {
+      failures.push(`${relativePath}: ${error.message}`);
+    }
+  });
+
+  if (failures.length > 0) {
+    console.error('Invalid Markdown frontmatter found:\n');
+    failures.forEach((failure) => console.error(`- ${failure}`));
+    process.exit(1);
+  }
+}
+
+main();

Reply via email to