├── .editorconfig ├── .eslintrc.cjs ├── .github ├── FUNDING.yaml └── workflows │ └── release.yml ├── .gitignore ├── .prettierrc.cjs ├── .releaserc.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── demo ├── .remarkrc.mjs ├── .vscode │ ├── extensions.json │ └── settings.json ├── README.md ├── content │ ├── creative-work.schema.yaml │ ├── creative-work │ │ ├── behind-the-gare-st-lazare__local-broken.md │ │ ├── guten-nachte__local-correct.md │ │ └── the-shipwreck__global-broken.md │ └── page.schema.yaml ├── package.json └── pipeline.ts ├── docs ├── screenshot-2.png ├── screenshot-3.png └── screenshot.png ├── index.ts ├── package.json ├── pnpm-lock.yaml ├── renovate.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = tab 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = false 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | 17 | [*.json,*.yaml] 18 | indent_style = space 19 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("@types/eslint").Linter.Config} */ 2 | 3 | module.exports = { 4 | /** 5 | * References: 6 | * 7 | * https://github.com/JulianCataldo/web-garden/blob/develop/configs/eslint-js.cjs 8 | * https://github.com/JulianCataldo/web-garden/blob/develop/configs/eslint-ts.cjs 9 | * 10 | * */ 11 | extends: [ 12 | './node_modules/webdev-configs/eslint-js.cjs', 13 | './node_modules/webdev-configs/eslint-ts.cjs', 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yaml: -------------------------------------------------------------------------------- 1 | github: [JulianCataldo] 2 | 3 | custom: ['https://www.buymeacoffee.com/JulianCataldo'] 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 'lts/*' 20 | 21 | - name: Setup PNPM 8.5.1 22 | uses: pnpm/action-setup@v2.4.0 23 | with: 24 | version: 8.5.1 25 | 26 | - name: Install dependencies 27 | run: pnpm install --frozen-lockfile 28 | 29 | - name: Build distributable files 30 | run: pnpm run build 31 | 32 | - name: Release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | run: ls && pnpm run release 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .parcel-cache 3 | .DS_Store 4 | .dev 5 | dist 6 | demo/pnpm-lock.yaml 7 | 8 | **/.vscode/settings.json 9 | 10 | # 11 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Options} */ 2 | 3 | module.exports = { 4 | /** 5 | * Reference: 6 | * 7 | * https://github.com/JulianCataldo/web-garden/blob/develop/configs/prettier-base.cjs 8 | * 9 | * */ 10 | ...require('webdev-configs/prettier-base.cjs'), 11 | 12 | overrides: [ 13 | { 14 | files: ['*.json', '*.yaml'], 15 | options: { 16 | useTabs: false, 17 | }, 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /.releaserc.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - '@semantic-release/commit-analyzer' 3 | - '@semantic-release/release-notes-generator' 4 | - '@semantic-release/changelog' 5 | - '@semantic-release/npm' 6 | - '@semantic-release/git' 7 | - '@semantic-release/github' 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.15.4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.15.3...v3.15.4) (2023-10-15) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * upgrade deps, fix demo ([9dd9dbb](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/9dd9dbb896fbe7a2c63c7651a8cc3899e0b3ec65)) 7 | 8 | ## [3.15.3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.15.2...v3.15.3) (2023-09-15) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * treat first yaml section as frontmatter, even if it is not the first child ([c09c902](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/c09c9021fc274033824be9728ffdcd3023dd3619)) 14 | 15 | ## [3.15.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.15.1...v3.15.2) (2023-04-12) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * trigger release ([11db24b](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/11db24b9fb8851e28417e42e912a43617279cddb)) 21 | 22 | ## [3.15.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.15.0...v3.15.1) (2022-11-19) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * better configuration example (yaml) ([c4629bb](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/c4629bb926d6f8be92bf7a580188874440dd783f)) 28 | 29 | # [3.15.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.14.0...v3.15.0) (2022-11-03) 30 | 31 | 32 | ### Features 33 | 34 | * bump major version ([336f10e](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/336f10e4c37819e6cd1a6ff9e490d72e26e578c1)) 35 | 36 | # [3.14.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.13.0...v3.14.0) (2022-11-03) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * align demo with new feats ([3cbc16a](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/3cbc16a20c3ceae772548f322d949ce978550b8e)) 42 | * use bundle instead of dereference ([6a8824a](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/6a8824a521015ec083408694e2101b914140efb8)) 43 | 44 | 45 | ### Features 46 | 47 | * resolve current remark working directory ([1887ef1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/1887ef1e857b2deaf64fd542ffca1f328d298628)) 48 | * use only json-schema-ref-parser, drop ajv's ([99b66bd](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/99b66bd7fb36d8e17daf001976ab41eb6b133ecd)) 49 | 50 | # [3.13.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.12.0...v3.13.0) (2022-10-30) 51 | 52 | 53 | ### Features 54 | 55 | * support relative path for local `$schema` ([20f53bf](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/20f53bff49512e27a0b04a304cead2cecd0859b8)) 56 | 57 | # [3.12.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.11.0...v3.12.0) (2022-10-30) 58 | 59 | 60 | ### Features 61 | 62 | * support for `const` auto-fix / suggestion ([a133c54](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/a133c54eeb322c03e68582d80036fa39194d161e)) 63 | 64 | # [3.11.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.10.2...v3.11.0) (2022-10-30) 65 | 66 | 67 | ### Features 68 | 69 | * better error output for eslint / remark ([aff95db](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/aff95db261f45eb4dd8f1a947640ec0a312c8772)) 70 | 71 | ## [3.10.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.10.1...v3.10.2) (2022-10-29) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * global schema assoc. w. `eslint-plugin-mdx` ([6849182](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/6849182449db52e7cd1ea76b671466e3c690cd9c)) 77 | 78 | ## [3.10.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.10.0...v3.10.1) (2022-10-29) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * allow empty settings for runtime plugin ([7534ea4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/7534ea4287abfa772779944839311acc7dcdd354)) 84 | 85 | # [3.10.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.9.0...v3.10.0) (2022-10-29) 86 | 87 | 88 | ### Features 89 | 90 | * embedded `$ref` for demo pipeline ([59434d7](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/59434d78470e4e0d5fc0756ddf988f40acd862e5)) 91 | 92 | # [3.9.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.8.0...v3.9.0) (2022-10-29) 93 | 94 | 95 | ### Features 96 | 97 | * convert lint rule to async, for file loading ([4675b25](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/4675b256fcd84a703deeebf3b412d2023c0cac6e)) 98 | * load external schema definition references ([b1b9805](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/b1b98051d03c17b6cc2bc3a29ebb8206172a52c0)) 99 | 100 | # [3.8.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.9...v3.8.0) (2022-10-28) 101 | 102 | 103 | ### Features 104 | 105 | * core meta schema validation docs + example ([1976cc2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/1976cc29f486448fbd72db45f0b9156214e0d430)) 106 | * embed native errors reports in vfile msg ([5a39364](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/5a393647d61f7e2730f07649891c2b0347729ced)) 107 | 108 | ## [3.7.9](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.8...v3.7.9) (2022-10-27) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * new cli harvest image + remove pointing hand ([42c5658](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/42c56581598f919b0060f315196c639b8d18b8b9)) 114 | 115 | ## [3.7.8](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.7...v3.7.8) (2022-10-27) 116 | 117 | 118 | ### Bug Fixes 119 | 120 | * update badge, links,… + pjson ordering ([8ebaca9](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/8ebaca9b11cb3ec97e13e2ee84adaff5cb43df7e)) 121 | 122 | ## [3.7.7](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.6...v3.7.7) (2022-10-20) 123 | 124 | 125 | ### Bug Fixes 126 | 127 | * properties spacing + bold ([e168cab](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e168cabd2d2cbf7619f07da43efd506d54386dae)) 128 | 129 | ## [3.7.6](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.5...v3.7.6) (2022-10-20) 130 | 131 | 132 | ### Bug Fixes 133 | 134 | * remove todo, rename package ([ae1c0b5](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/ae1c0b5d7d6247900540a4e66d4378c6b066dd4c)) 135 | 136 | ## [3.7.5](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.4...v3.7.5) (2022-10-15) 137 | 138 | 139 | ### Bug Fixes 140 | 141 | * update package name ([c14c50a](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/c14c50a47ee2a4c4765a3b41f361f8afcf1a307b)) 142 | 143 | ## [3.7.4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.3...v3.7.4) (2022-09-23) 144 | 145 | 146 | ### Bug Fixes 147 | 148 | * add more helpful dummy content ([b330095](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/b330095ce6ad20b8cf56f459363e6226ef79a62c)), closes [#15](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/issues/15) 149 | * separate schema path and its hash ([f9063f3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/f9063f327b3a935ffd7e6ff0b24e2ee6d6f609d3)), closes [#14](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/issues/14) 150 | 151 | ## [3.7.3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.2...v3.7.3) (2022-09-22) 152 | 153 | 154 | ### Bug Fixes 155 | 156 | * swap custom `glob-to-regexp` w. `minimatch` ([e7022d6](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e7022d61922f41d9a0d177e528ba1d078e0f2bb6)), closes [#12](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/issues/12) [#13](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/issues/13) 157 | 158 | ## [3.7.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.1...v3.7.2) (2022-09-09) 159 | 160 | 161 | ### Bug Fixes 162 | 163 | * add js native error message from pretty note ([e29ea28](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e29ea280b8c2b158b3b72a429cdeb0ee0e65d0fd)) 164 | 165 | ## [3.7.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.7.0...v3.7.1) (2022-09-09) 166 | 167 | 168 | ### Bug Fixes 169 | 170 | * simplify doc headings hierarchy ([25d23c1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/25d23c14ac1667ca8f8147e5c041d4e3f30e773b)) 171 | 172 | # [3.7.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.6.2...v3.7.0) (2022-09-09) 173 | 174 | 175 | ### Bug Fixes 176 | 177 | * docs living example updates ([881d7d3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/881d7d35ed3388c0aa17c4a5172089e7286a8160)) 178 | 179 | 180 | ### Features 181 | 182 | * add user defined ajv settings ([a01f825](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/a01f8256fa35463e8a87abb50082f81438612415)) 183 | * export message typings + more message infos ([e132158](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e1321584fe9dbd1efac73ac470b01cd21de3a79c)) 184 | * js native error message name ([8baa395](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/8baa395161b08a8cb31827daa726b9b9a950628c)) 185 | 186 | ## [3.6.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.6.1...v3.6.2) (2022-08-26) 187 | 188 | 189 | ### Bug Fixes 190 | 191 | * better type guards and typings ([cddf31c](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/cddf31c97c0e41f70b2f22eee25f8a3d2e84e90a)) 192 | 193 | ## [3.6.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.6.0...v3.6.1) (2022-08-25) 194 | 195 | 196 | ### Bug Fixes 197 | 198 | * reduce package size drastically ([d126030](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/d12603011c388bbe01803ba776528178339de26d)) 199 | 200 | # [3.6.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.5.1...v3.6.0) (2022-08-24) 201 | 202 | 203 | ### Features 204 | 205 | * vfile `location` > yaml `lineCounter` [#8](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/issues/8) ([e5ad3ea](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e5ad3eaa1cf8442a76e827b6d8f7509203a90356)) 206 | 207 | ## [3.5.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.5.0...v3.5.1) (2022-08-19) 208 | 209 | 210 | ### Bug Fixes 211 | 212 | * more readable message note construction ([a9328fe](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/a9328fe7c8e320bee0b4b47d7435108d51a10045)) 213 | 214 | # [3.5.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.4.1...v3.5.0) (2022-08-19) 215 | 216 | 217 | ### Bug Fixes 218 | 219 | * rehaul docs titles hierarchy ([03ba93c](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/03ba93c5e245444acfe199ebd5221a736e355141)) 220 | 221 | 222 | ### Features 223 | 224 | * live implementation + frameworks in docs ([8cdb18e](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/8cdb18efb7c65650a67efd7e0cd2be5de4e030f3)) 225 | 226 | ## [3.4.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.4.0...v3.4.1) (2022-08-18) 227 | 228 | 229 | ### Bug Fixes 230 | 231 | * screenshots links in docs ([8544088](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/8544088ebe23ab48bd71b2a5ab655d6a81089df5)) 232 | 233 | # [3.4.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.3.1...v3.4.0) (2022-08-11) 234 | 235 | 236 | ### Bug Fixes 237 | 238 | * crash when no global schemas ([7107487](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/71074876a31d224184a11a234f07c610c3c14380)) 239 | 240 | 241 | ### Features 242 | 243 | * direct schema embedding through settings ([5430797](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/543079764969e78fbe270aa5b5d8d844c7efff16)) 244 | 245 | ## [3.3.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.3.0...v3.3.1) (2022-08-10) 246 | 247 | 248 | ### Bug Fixes 249 | 250 | * 'JSON Schema malformed' when required missing ([0160337](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/01603375de98813351e24ca377b8b71de556837c)) 251 | 252 | # [3.3.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.2.2...v3.3.0) (2022-08-09) 253 | 254 | 255 | ### Features 256 | 257 | * better warning message display ([77542a9](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/77542a9d6101aa39668d915367628e42ff047c89)) 258 | 259 | ## [3.2.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.2.1...v3.2.2) (2022-08-09) 260 | 261 | 262 | ### Bug Fixes 263 | 264 | * add details to features + re-orders [misfire] ([788bf70](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/788bf70ecf6cad3e84523a0e7f6b374ef5ad7559)) 265 | 266 | ## [3.2.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.2.0...v3.2.1) (2022-08-09) 267 | 268 | 269 | ### Bug Fixes 270 | 271 | * add details to features + re-orders ([dbe44e2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/dbe44e219d051c00796101db57d0253ed78cc1b8)) 272 | 273 | # [3.2.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.1.0...v3.2.0) (2022-08-09) 274 | 275 | 276 | ### Bug Fixes 277 | 278 | * add example for global associations + details ([9f7d471](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/9f7d471f40d62d4deafe8b9176b4f8cefe2fecbb)) 279 | 280 | 281 | ### Features 282 | 283 | * update demo to reflect global settings ([0916295](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/091629511ce8d2b1354dc0352de1df529eb2c868)) 284 | 285 | # [3.1.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v3.0.0...v3.1.0) (2022-08-09) 286 | 287 | 288 | ### Features 289 | 290 | * init plugin settings, with glob' schemas 🤯 ([23c06f7](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/23c06f76fbc578474c832957b184ef3d22439e02)) 291 | 292 | # [3.0.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.6...v3.0.0) (2022-08-08) 293 | 294 | 295 | ### Bug Fixes 296 | 297 | * `unified` and `unist` missing, breaking ci ([6b4dbab](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/6b4dbabaa104023b42e2f7dd1be9e23db58db6b2)) 298 | * change screenshot link to raw github ([9d65364](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/9d65364acec56c5f787fd870e2178537abf5c0ff)) 299 | * correct typings + coercition for ast prop ([69766fd](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/69766fd7849e7f3ff6ab53fac17ad1cb382e68ef)) 300 | 301 | 302 | ### Features 303 | 304 | * major rehaul w. type safety, guards + errors ([4dd0dad](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/4dd0dadca808a1affac2605d061447feb8c6ee86)) 305 | 306 | 307 | ### BREAKING CHANGES 308 | 309 | * - `Root` and `YAML from `types/mdast` 310 | - `isNode` from `yaml` 311 | - `vfile.message` instead of pushing to `vfile.messages` 312 | - parse YAML once and use `toJS()` 313 | - `vFile` `cwd` instead of `process.cwd()` 314 | 315 | Guards / Errors for: 316 | 317 | - JSON Schema not found (wrong path) 318 | - JSON Schema malformed 319 | - YAML Schema parsing error 320 | - YAML Frontmatter parsing error (doesn't seems to occur anyway) 321 | 322 | Also, splitting functions a bit, adding some explanations and ideas. 323 | 324 | Thanks a lot @remcohaszing 👏 325 | 326 | ## [2.5.6](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.5...v2.5.6) (2022-08-08) 327 | 328 | 329 | ### Bug Fixes 330 | 331 | * add command details ([b4a648a](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/b4a648aaa252efc101871a777145f606b6c0b480)) 332 | 333 | ## [2.5.5](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.4...v2.5.5) (2022-08-08) 334 | 335 | 336 | ### Bug Fixes 337 | 338 | * clean screenshot unneeded files ([c7026e3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/c7026e3ef29ffefca56661d544945910fe7aa728)) 339 | 340 | ## [2.5.4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.3...v2.5.4) (2022-08-08) 341 | 342 | 343 | ### Bug Fixes 344 | 345 | * update demo content ([5a4c94c](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/5a4c94c55bca3aa7c4c471ff6c6f85b7e89b588b)) 346 | * update docs badge and description ([3a55113](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/3a55113969e12d78f8620c994011e69cb72cf1c8)) 347 | 348 | ## [2.5.3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.2...v2.5.3) (2022-08-07) 349 | 350 | 351 | ### Bug Fixes 352 | 353 | * doc, add remark config example ([e64163e](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e64163e3d38aacb352aa9fa0c98e5d0dce296d78)) 354 | 355 | ## [2.5.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.1...v2.5.2) (2022-08-07) 356 | 357 | 358 | ### Bug Fixes 359 | 360 | * docs, correct full install + more details ([0eb1c9c](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/0eb1c9c692e6a672767a46e05a1ca3e90faa05cd)) 361 | 362 | ## [2.5.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.5.0...v2.5.1) (2022-08-07) 363 | 364 | 365 | ### Bug Fixes 366 | 367 | * check yaml presence + extract function ([d08f5ae](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/d08f5aea005245579321d99af77fa324e9ad3d86)) 368 | 369 | # [2.5.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.7...v2.5.0) (2022-08-07) 370 | 371 | 372 | ### Features 373 | 374 | * use `yaml` instead of `js-yaml` ([95444d3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/95444d3fe1848cd00c646e76ae444d01454cbdd9)) 375 | 376 | ## [2.4.7](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.6...v2.4.7) (2022-08-06) 377 | 378 | 379 | ### Bug Fixes 380 | 381 | * re-order package.json fields + add details ([3e4298e](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/3e4298ef9b15e2ce0413146f1192dd3214a1b635)) 382 | 383 | ## [2.4.6](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.5...v2.4.6) (2022-08-06) 384 | 385 | 386 | ### Bug Fixes 387 | 388 | * add npm package badge link ([14a2ef4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/14a2ef439ce560bfadb5484ef41704a3f67d2dc5)) 389 | * docs, add npm badge + better description ([7e7d614](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/7e7d614560f18b973bbd48c9d8f6aa5f186199ef)) 390 | 391 | ## [2.4.5](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.4...v2.4.5) (2022-08-06) 392 | 393 | 394 | ### Bug Fixes 395 | 396 | * add npm package badge link ([923ad35](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/923ad35e4e3cb91a06fd3af19fc6dec0fc752fec)) 397 | 398 | ## [2.4.4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.3...v2.4.4) (2022-08-06) 399 | 400 | 401 | ### Bug Fixes 402 | 403 | * docs, add npm badge + better description ([e96a79e](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e96a79ecc17fdfb58ef16b7660887917d06b837f)) 404 | 405 | ## [2.4.3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.2...v2.4.3) (2022-08-06) 406 | 407 | 408 | ### Bug Fixes 409 | 410 | * add details to docs + new screenshot ([e6b94b9](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e6b94b9916908a79bc486874632ebda0cd41650b)) 411 | 412 | ## [2.4.2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.1...v2.4.2) (2022-08-06) 413 | 414 | 415 | ### Bug Fixes 416 | 417 | * typo for url in docs ([8d70a9b](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/8d70a9b074f63ca89927d3fabef111133fdc3d96)) 418 | 419 | ## [2.4.1](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.4.0...v2.4.1) (2022-08-05) 420 | 421 | 422 | ### Bug Fixes 423 | 424 | * remove dead code + refactor ([e167e11](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/e167e11945480219318375c8b541c2e749c71bad)) 425 | 426 | # [2.4.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.3.0...v2.4.0) (2022-08-05) 427 | 428 | 429 | ### Features 430 | 431 | * add code examples in docs ([c449814](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/c4498145efb6e5bfc93be49238c04aeb4e5de418)) 432 | 433 | # [2.3.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.2.0...v2.3.0) (2022-08-05) 434 | 435 | 436 | ### Features 437 | 438 | * docs for known limitations + keywords ([a12fdb4](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/a12fdb4da2eaacfc73df4b3a41d2f48951510436)) 439 | 440 | # [2.2.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.1.0...v2.2.0) (2022-08-05) 441 | 442 | 443 | ### Features 444 | 445 | * init root doc + demo screenshot ([3fffa85](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/3fffa858268bd662d613428537fe756feab2d995)) 446 | 447 | # [2.1.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v2.0.0...v2.1.0) (2022-08-05) 448 | 449 | 450 | ### Features 451 | 452 | * demo file package ([216acfd](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/216acfdd6a401b2d7ec7b3f4fdfd94ff44649319)) 453 | 454 | # [2.0.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v1.3.0...v2.0.0) (2022-08-05) 455 | 456 | 457 | ### Features 458 | 459 | * force bump major version (deleted npm) ([7d439b2](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/7d439b28046d6a1624b0baa0ffa11d4f2b48f853)) 460 | 461 | 462 | ### BREAKING CHANGES 463 | 464 | * 465 | 466 | # [1.3.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v1.2.0...v1.3.0) (2022-08-05) 467 | 468 | 469 | ### Features 470 | 471 | * vscode extensions recommendation for demo ([8d4cb7b](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/8d4cb7b9e9b31648b702b109d62d39e4d5c3f50a)) 472 | 473 | # [1.2.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v1.1.0...v1.2.0) (2022-08-05) 474 | 475 | 476 | ### Features 477 | 478 | * remark lint base demo config ([ae67bc3](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/ae67bc3b8190949928cb6bb838db97d73f1b1eb1)) 479 | 480 | # [1.1.0](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/compare/v1.0.0...v1.1.0) (2022-08-05) 481 | 482 | 483 | ### Features 484 | 485 | * add demo content ([f0c1e2e](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/f0c1e2ec5a13ea1edd6134a5628262a035349b6a)) 486 | 487 | # 1.0.0 (2022-08-05) 488 | 489 | 490 | ### Features 491 | 492 | * init plugin script index file ([fd0d451](https://github.com/JulianCataldo/remark-lint-frontmatter-schema/commit/fd0d4511a3e1cb37df4cbd5fe680f248ce71e72d)) 493 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2022 Julian Cataldo — https://www.juliancataldo.com 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `remark-lint-frontmatter-schema` 📑 2 | 3 | 4 | 5 | [![VS Code](https://img.shields.io/badge/Visual_Studio_Code-0078D4?logo=visual%20studio%20code)](https://code.visualstudio.com) 6 | [![unified](https://img.shields.io/badge/uni-fied-0366d6?logo=markdown)](https://unifiedjs.com) 7 | [![NPM](https://img.shields.io/npm/v/remark-lint-frontmatter-schema)](https://www.npmjs.com/package/remark-lint-frontmatter-schema) 8 | ![Downloads](https://img.shields.io/npm/dt/remark-lint-frontmatter-schema) 9 | [![ISC License](https://img.shields.io/npm/l/remark-lint-frontmatter-schema)](./LICENSE) 10 | [![GitHub](https://img.shields.io/badge/Repository-222222?logo=github)](https://github.com/JulianCataldo/remark-lint-frontmatter-schema) 11 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://makeapullrequest.com) 12 | [![TypeScript](https://img.shields.io/badge/TypeScript-333333?logo=typescript)](http://www.typescriptlang.org/) 13 | [![Prettier](https://img.shields.io/badge/Prettier-333333?logo=prettier)](https://prettier.io) 14 | [![EditorConfig](https://img.shields.io/badge/EditorConfig-333333?logo=editorconfig)](https://editorconfig.org) 15 | [![ESLint](https://img.shields.io/badge/ESLint-3A33D1?logo=eslint)](https://eslint.org) 16 | 17 | 18 | 19 | Validate **Markdown** frontmatter **YAML** against an associated **JSON schema** with this **remark-lint** rule plugin. 20 | 21 | Supports: 22 | 23 | - **Types validation**, pattern, enumerations,… and all you can get with JSON Schema 24 | - **Code location** problems indicator (for IDE to underline) 25 | - **Auto-fixes** with suggestions 26 | - **C**ommand **L**ine **I**nterface reports 27 | - **VS Code** integration (see below) 28 | - **Global patterns** or **in-file** schemas associations 29 | - In JS framework **MD / MDX pipelines** 30 | 31 | # Demo 32 | 33 |
34 | 35 | [![](https://res.cloudinary.com/dzfylx93l/image/upload/c_scale,w_1280/demo-rlfmschema_meai5w.png) 36 | **🕹  Preview it online!**](https://astro-content.dev/__content) 37 | 38 | (w. Astro Content — Editor) 39 | 40 |
41 | 42 | --- 43 | 44 | **Jump to**: 45 | 46 | - [👉  **Play with pre-configured ./demo**](#play-with-pre-configured-demo) 47 | - [Base](#base) 48 | - [VS Code (optional)](#vs-code-optional) 49 | - [CLI / IDE (VS Code) — **Static** linting](#cli--ide-vs-code--static-linting) 50 | - [Workspace](#workspace) 51 | - [Schema example](#schema-example) 52 | - [🆕  Add references to external definitions (advanced)](#add-references-to-external-definitions-advanced) 53 | - [Schemas associations](#schemas-associations) 54 | - [Inside frontmatter](#inside-frontmatter) 55 | - [Globally, with patterns](#globally-with-patterns) 56 | - [CLI usage](#cli-usage) 57 | - [Bonus](#bonus) 58 | - [Validate your schema with _JSON meta schema_](#validate-your-schema-with-json-meta-schema) 59 | - [ESLint MDX plugin setup](#eslint-mdx-plugin-setup) 60 | - [Known issues](#known-issues) 61 | - [MD / MDX pipeline — **Runtime** validation](#md--mdx-pipeline--runtime-validation) 62 | - [Custom pipeline](#custom-pipeline) 63 | - [Implementation living example](#implementation-living-example) 64 | - [Important foot-notes for custom pipeline](#important-foot-notes-for-custom-pipeline) 65 | - [Framework](#framework) 66 | - [Astro](#astro) 67 | - [Gatsby](#gatsby) 68 | 69 | --- 70 | 71 | [![Demo screenshot of frontmatter schema linter 1](./docs/screenshot.png)](https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/master/docs/screenshot.png) 72 | 73 | --- 74 | 75 | [![Demo screenshot of frontmatter schema linter 2](./docs/screenshot-2.png)](https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/master/docs/screenshot-2.png) 76 | 77 | --- 78 | 79 | [![Demo screenshot of frontmatter schema linter 3](./docs/screenshot-3.png)](https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/master/docs/screenshot-3.png) 80 | 81 | --- 82 | 83 | ## 👉  **Play with pre-configured [./demo](./demo/)** 84 | 85 | Quick shallow **clone** with: 86 | 87 | ```sh 88 | pnpx degit JulianCataldo/remark-lint-frontmatter-schema/demo ./demo 89 | ``` 90 | 91 | --- 92 | 93 | # Installation 94 | 95 | ### Base 96 | 97 | ```sh 98 | pnpm install -D \ 99 | remark remark-cli \ 100 | remark-frontmatter \ 101 | remark-lint-frontmatter-schema 102 | ``` 103 | 104 | > **Remove** `-D` flag for runtime **`unified`** MD / MDX **pipeline** (custom, Astro, Gatsby, etc.), for production. 105 | > **Keep it** if you just want to lint with **CLI** or your **IDE** locally, without any production / CI needs. 106 | 107 | ### VS Code (optional) 108 | 109 | ```sh 110 | code --install-extension unifiedjs.vscode-remark 111 | ``` 112 | 113 | # Configuration 114 | 115 | ### CLI / IDE (VS Code) — **Static** linting 116 | 117 | 👉  **See [./demo](./demo/)** folder to get a working, pre-configured, bare project workspace. 118 | You also get example Markdown files and associated schema to play with. 119 | Supports `remark-cli` and/or `unifiedjs.vscode-remark` extension. 120 | 121 | 📌  Check out the **[demo/README.md](./demo) for bootstrapping** it. 122 | 123 | #### Workspace 124 | 125 | Create the root config. file for `remark` to source from: 126 | `touch ./.remarkrc.mjs` 127 | 128 | Paste this base configuration: 129 | 130 | ```mjs 131 | import remarkFrontmatter from 'remark-frontmatter'; 132 | import remarkLintFrontmatterSchema from 'remark-lint-frontmatter-schema'; 133 | 134 | const remarkConfig = { 135 | plugins: [remarkFrontmatter, remarkLintFrontmatterSchema], 136 | }; 137 | export default remarkConfig; 138 | ``` 139 | 140 | You can use YAML / JSON / …, too (uses [cosmiconfig](https://github.com/davidtheclark/cosmiconfig)). 141 | 142 | #### Schema example 143 | 144 | `./content/creative-work.schema.yaml` 145 | 146 | ```yaml 147 | type: object 148 | properties: 149 | title: 150 | type: string 151 | # … 152 | ``` 153 | 154 | ##### 🆕  Add references to external definitions (advanced) 155 | 156 | Referencing schema definitions 157 | allows re-using bit and piece instead of duplicate them, 158 | accross your content schemas. 159 | 160 | You can reference an external schema relatively, using `$ref`. 161 | For example we can -_kind of_- merge an host object with a reference properties: 162 | 163 | The host schema, `content/articles/index.schema.yaml` 164 | 165 | ```yaml 166 | allOf: 167 | - $ref: ../page.schema.yaml 168 | 169 | - properties: 170 | layout: 171 | const: src/layouts/Article.astro 172 | category: 173 | type: string 174 | enum: 175 | - Book 176 | - Movie 177 | foo: 178 | type: string 179 | 180 | required: 181 | - layout 182 | - category 183 | ``` 184 | 185 | A referenced schema, `content/page.schema.yaml` 186 | 187 | ```yaml 188 | properties: 189 | title: 190 | type: string 191 | maxLength: 80 192 | # ... 193 | # ... 194 | 195 | required: 196 | - title 197 | ``` 198 | 199 | The result will be _(virtually)_ the same as this: 200 | 201 | ```yaml 202 | properties: 203 | title: 204 | type: string 205 | maxLength: 80 206 | # ... 207 | # ... 208 | layout: 209 | const: src/layouts/Article.astro 210 | category: 211 | type: string 212 | enum: 213 | - Book 214 | - Movie 215 | foo: 216 | type: string 217 | # ... 218 | 219 | required: 220 | - title 221 | - layout 222 | - category 223 | ``` 224 | 225 | #### Schemas associations 226 | 227 | Inspired by [VS Code JSON Schema](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings) 228 | and [`redhat.vscode-yaml`](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) conventions. 229 | 230 | ##### Inside frontmatter 231 | 232 | See **[./demo/content](./demo/content)** files for examples. 233 | 234 | Schema association can be done directly **inside** the **frontmatter** of the **Markdown** file, 235 | relative to project root, thanks to the `'$schema'` key: 236 | 237 | ```markdown 238 | --- 239 | # From workspace root (`foo/…`, `/foo/…` or `./foo/…` is the same) 240 | '$schema': content/creative-work.schema.yaml 241 | 242 | # —Or— relatively, from this current file directory (`./foo/…` or `../foo/…`) 243 | # '$schema': ../creative-work.schema.yaml 244 | 245 | layout: src/layouts/Article.astro 246 | 247 | title: Hello there 248 | category: Book 249 | # … 250 | --- 251 | 252 | # You're welcome! 253 | 254 | 🌝  My **Markdown** content…  🌚 255 | … 256 | ``` 257 | 258 | ##### Globally, with patterns 259 | 260 | > **Note**: 261 | > Locally defined **`'$schema'` takes precedence** over global settings below. 262 | 263 | ```js 264 | const remarkConfig = { 265 | plugins: [ 266 | remarkFrontmatter, 267 | [ 268 | remarkLintFrontmatterSchema, 269 | { 270 | schemas: { 271 | /* One schema for many files */ 272 | './content/creative-work.schema.yaml': [ 273 | /* Per-file association */ 274 | './content/creative-work/the-shipwreck__global-broken.md', 275 | 276 | /* Support glob patterns ———v */ 277 | // './content/creative-work/*.md', 278 | // … 279 | // `./` prefix is optional 280 | // 'content/creative-work/foobiz.md', 281 | ], 282 | 283 | // './content/ghost.schema.yaml': [ 284 | // './content/casper.md', 285 | // './content/ether.md', 286 | // ], 287 | }, 288 | }, 289 | ], 290 | ], 291 | }; 292 | ``` 293 | 294 | `'./foo'`, `'/foo'`, `'foo'`, all will work. 295 | It's always relative to your `./.remarkrc.mjs` file, in your workspace root. 296 | 297 | #### CLI usage 298 | 299 | Linting whole workspace files (as `./**/*.md`) with `remark-cli`: 300 | 301 | ```sh 302 | pnpm remark . 303 | ``` 304 | 305 | Yields: 306 | 307 | ![](https://res.cloudinary.com/dzfylx93l/image/upload/v1666912219/Xnapper-2022-10-28-01.09.11_yh4tnr.png) 308 | 309 | #### Bonus 310 | 311 | ##### Validate your schema with _JSON meta schema_ 312 | 313 | First, install the [YAML for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension: 314 | 315 | ```sh 316 | code --install-extension redhat.vscode-yaml 317 | ``` 318 | 319 | Then, add this to your `.vscode/settings.json`: 320 | 321 | ```jsonc 322 | { 323 | "yaml.schemas": { 324 | "http://json-schema.org/draft-07/schema#": ["content/**/*.schema.yaml"] 325 | } 326 | /* ... */ 327 | } 328 | ``` 329 | 330 | ##### ESLint MDX plugin setup 331 | 332 | Will work with the ESLint VS Code extension and the CLI command. 333 | 334 | Install the [ESLint MDX plugin](https://github.com/mdx-js/eslint-mdx), 335 | the [MDX VS Code extension](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) and the [ESLint VS Code extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint). 336 | 337 | Add this dependencies to your project: 338 | 339 | ```sh 340 | pnpm i -D eslint eslint-plugin-mdx \ 341 | eslint-plugin-prettier eslint-config-prettier 342 | ``` 343 | 344 | Add a `.eslintrc.cjs`: 345 | 346 | ```js 347 | /** @type {import("@types/eslint").Linter.Config} */ 348 | 349 | module.exports = { 350 | overrides: [ 351 | { 352 | files: ['*.md', '*.mdx'], 353 | extends: ['plugin:mdx/recommended'], 354 | }, 355 | ], 356 | }; 357 | ``` 358 | 359 | Add a `.remarkrc.yaml` (or JSON, etc.), e.g.: 360 | 361 | ```yaml 362 | plugins: 363 | - remark-frontmatter 364 | 365 | - - remark-lint-frontmatter-schema 366 | - schemas: 367 | src/schemas/blog-post.schema.yaml: 368 | - content/blog-posts/*.{md,mdx} 369 | 370 | # - remark-preset-lint-consistent 371 | # - remark-preset-lint-markdown-style-guide 372 | # - remark-preset-lint-recommended 373 | ``` 374 | 375 | --- 376 | 377 | Result: 378 | 379 | [![](https://res.cloudinary.com/dzfylx93l/image/upload/c_scale,w_1280/eslint-plugin-mdx-1.png) 380 | ](https://res.cloudinary.com/dzfylx93l/image/upload/eslint-plugin-mdx-1.png) 381 | 382 | --- 383 | 384 | Lint with CLI: 385 | 386 | ```sh 387 | pnpm eslint --ext .mdx . 388 | ``` 389 | 390 | > Efforts has been made to have the best output for both remark and ESLint, 391 | > for IDE extensions and CLIs. 392 | 393 | ###### Known issues 394 | 395 | - Expected `enum` values suggestions are working with the remark extension, not with the ESLint one. 396 | - Similarly, ESLint output will give less details (see screenshot above), and a bit different layout for CLI output, too. 397 | - remark extension seems to load faster, and is more reactive to schema changes. 398 | - As of `eslint-plugin-mdx@2`, `.remarkrc.mjs` (ES Module) is not loaded, JSON and YAML configs are fine. 399 | 400 | ### MD / MDX pipeline — **Runtime** validation 401 | 402 | Use it as usual like any remark plugin inside your framework or your custom `unified` pipeline. 403 | 404 | #### Custom pipeline 405 | 406 | When processing Markdown as single files inside your JS/TS app. 407 | An minimal example is provided in [`./demo/pipeline.ts`](./demo/pipeline.ts), you can launch it with `pnpm pipeline` from `./demo`. 408 | 409 | --- 410 | 411 | Schema should be provided programmatically like this: 412 | 413 | ```ts 414 | // … 415 | import remarkFrontmatter from 'remark-frontmatter'; 416 | import remarkLintFrontmatterSchema from 'remark-lint-frontmatter-schema'; 417 | import type { JSONSchema7 } from 'json-schema'; 418 | import { reporter } from 'vfile-reporter'; 419 | 420 | const mySchema: JSONSchema7 = { 421 | /* … */ 422 | }; 423 | 424 | const output = await unified() 425 | // Your pipeline (basic example) 426 | .use(remarkParse) 427 | // … 428 | .use(remarkFrontmatter) 429 | 430 | .use(remarkLintFrontmatterSchema, { 431 | /* Bring your own schema */ 432 | embed: mySchema, 433 | }) 434 | 435 | // … 436 | .use(remarkRehype) 437 | .use(rehypeStringify) 438 | .use(rehypeFormat) 439 | .process(theRawMarkdownLiteral); 440 | 441 | /* `path` is for debugging purpose here, as MD literal comes from your app. */ 442 | output.path = './the-current-processed-md-file.md'; 443 | 444 | console.error(reporter([output])); 445 | ``` 446 | 447 | Yields: 448 | 449 | ``` 450 | ./the-current-processed-md-file.md 451 | 1:1 warning Must have required property 'tag' frontmatter-schema remark-lint 452 | 453 | ⚠ 1 warning 454 | ``` 455 | 456 | ##### Implementation living example 457 | 458 | Checkout [**Astro Content**](https://github.com/JulianCataldo/astro-content) repository. 459 | 460 | 461 | 462 | 463 | Astro Content relies on this library, among others, for providing linting reports. 464 | 465 | 466 | 467 | ##### Important foot-notes for custom pipeline 468 | 469 | This is **different from static linting**, with VS Code extension or CLI. 470 | It **will not source `.remarkrc`** (but you can source it by your own means, if you want). 471 | In fact, it's not aware of your file structure, 472 | nor it will associate or import any schema / Markdown files. 473 | That way, it will integrate easier with your own business logic and existing pipelines. 474 | I found that **static linting** (during editing) / and **runtime validation** are two different 475 | uses cases enough to separate them in their setups, but I might converge them partially. 476 | 477 | #### Framework 478 | 479 | > **Warning** 480 | > WIP. **NOT tested yet**! It is not a common use case for `remark-lint`. 481 | > Linting data inside frameworks are generally ignored. 482 | > AFAIK, `messages` data isn't forwarded to CLI output. 483 | > Feel free to open a PR if you have some uses cases in this area that need special care. 484 | > Maybe Astro or Astro Content could leverage these linter warnings in the future. 485 | 486 | See [global patterns `schemas` associations](#globally-with-patterns) for settings reference. 487 | 488 | ##### Astro 489 | 490 | In `astro.config.mjs` 491 | 492 | ```ts 493 | // … 494 | export default defineConfig({ 495 | // … 496 | remarkPlugins: [ 497 | // … 498 | 'remark-frontmatter', 499 | ['remark-lint-frontmatter-schema', { schemas }], 500 | // … 501 | ]; 502 | // … 503 | }); 504 | ``` 505 | 506 | ##### Gatsby 507 | 508 | In `gatsby-config.js` 509 | 510 | ```ts 511 | { 512 | // … 513 | plugins: [ 514 | // … 515 | { 516 | resolve: 'gatsby-transformer-remark', 517 | options: { 518 | plugins: [ 519 | // … 520 | 'remark-frontmatter', 521 | ['remark-lint-frontmatter-schema', { schemas }], 522 | // … 523 | ], 524 | }, 525 | }, 526 | // … 527 | ]; 528 | } 529 | ``` 530 | 531 | # Interfaces 532 | 533 | ```ts 534 | export interface Settings { 535 | /** 536 | * Global workspace file associations mapping (for linter extension). 537 | * 538 | * **Example**: `'schemas/thing.schema.yaml': ['content/things/*.md']` 539 | */ 540 | schemas?: Record; 541 | 542 | /** 543 | * Direct schema embedding (for using inside an `unified` transform pipeline). 544 | * 545 | * Format: JSON Schema - draft-2019-09 546 | * 547 | * **Documentation**: https://ajv.js.org/json-schema.html#draft-07 548 | */ 549 | embed?: JSONSchema7; 550 | 551 | /** 552 | * **Documentation**: https://ajv.js.org/options.html 553 | */ 554 | ajvOptions?: AjvOptions; 555 | } 556 | 557 | export interface FrontmatterSchemaMessage extends VFileMessage { 558 | schema: AjvErrorObject & { url: JSONSchemaReference }; 559 | } 560 | ``` 561 | 562 | Example of a `VFileMessage` content you could collect from this lint rule: 563 | 564 | ```jsonc 565 | [ 566 | // … 567 | { 568 | // JS native `Error` 569 | "name": "Markdown YAML frontmatter error (JSON Schema)", 570 | "message": "Keyword: type\nType: string\nSchema path: #/properties/title/type", 571 | 572 | // `VFileMessage` (Linter / VS Code…) 573 | "reason": "/clientType: Must be equal to one of the allowed values", 574 | "line": 16, 575 | "column": 13, 576 | "url": "https://github.com/JulianCataldo/remark-lint-frontmatter-schema", 577 | "source": "remark-lint", 578 | "ruleId": "frontmatter-schema", 579 | "position": { 580 | "start": { 581 | "line": 16, 582 | "column": 13 583 | }, 584 | "end": { 585 | "line": 16, 586 | "column": 24 587 | } 588 | }, 589 | "fatal": false, 590 | "actual": "Individuaaaaaaaal", 591 | "expected": ["Corporate", "Non-profit", "Individual"], 592 | // Condensed string, human readable version of AJV error object 593 | "note": "Keyword: enum\nAllowed values: Corporate, Non-profit, Individual\nSchema path: #/properties/clientType/enum", 594 | 595 | // AJV's `ErrorObject` 596 | "schema": { 597 | "url": "https://ajv.js.org/json-schema.html", 598 | "instancePath": "/clientType", 599 | "schemaPath": "#/properties/clientType/enum", 600 | "keyword": "enum", 601 | "params": { 602 | "allowedValues": ["Corporate", "Non-profit", "Individual"] 603 | }, 604 | "message": "must be equal to one of the allowed values" 605 | } 606 | } 607 | ] 608 | ``` 609 | 610 | --- 611 | 612 | 613 | 614 | # Footnotes 615 | 616 | **100% ESM**, including dependencies. 617 | 618 | Environments: 619 | 620 | - **CLI Tool** 621 | > Remark lint | https://github.com/remarkjs/remark-lint 622 | - **IDE Extension** (optional) 623 | > VS Code `unifiedjs.vscode-remark` 624 | > https://github.com/remarkjs/vscode-remark 625 | 626 | Major dependencies: 627 | 628 | `ajv`, `yaml`, `remark`, `remark-frontmatter`, `unified`, `remark-cli` 629 | 630 | --- 631 | 632 | See [CHANGELOG.md](./CHANGELOG.md) for release history. 633 | 634 | --- 635 | 636 | **Other projects 👀**… 637 | 638 | - [retext-case-police](https://github.com/JulianCataldo/retext-case-police): Check popular names casing. Example: ⚠️ `github` → ✅ `GitHub`. 639 | - [remark-embed](https://github.com/JulianCataldo/remark-embed): A `remark` plugin for embedding remote / local Markdown or code snippets. 640 | - [astro-content](https://github.com/JulianCataldo/astro-content): A text based, structured content manager, for edition and consumption. 641 | - [Web garden](https://github.com/JulianCataldo/web-garden): Building blocks for making progressive and future-proof websites. 642 | 643 | --- 644 | 645 |
646 | 647 | **Find this project useful?** 648 | 649 | [![GitHub](https://img.shields.io/badge/Star_me_on_GitHub-222222?logo=github&style=social)](https://github.com/JulianCataldo/remark-lint-frontmatter-schema) 650 | 651 |
652 | 653 | --- 654 | 655 | 🔗  [JulianCataldo.com](https://www.juliancataldo.com) 656 | -------------------------------------------------------------------------------- /demo/.remarkrc.mjs: -------------------------------------------------------------------------------- 1 | import remarkFrontmatter from 'remark-frontmatter'; 2 | import remarkLintFrontmatterSchema from 'remark-lint-frontmatter-schema'; 3 | /* —————————————————————————————————————————————————————————————————————————— */ 4 | 5 | const remarkConfig = { 6 | plugins: [ 7 | remarkFrontmatter, 8 | /* v————— Use it without settings, with local '$schema' associations only */ 9 | // rlFmSchema 10 | 11 | /* v————— Or with global schemas associations */ 12 | [ 13 | remarkLintFrontmatterSchema, 14 | { 15 | schemas: { 16 | /* One schema for many files */ 17 | './content/creative-work.schema.yaml': [ 18 | './content/creative-work/the-shipwreck__global-broken.md', 19 | 20 | /* Support glob patterns ———v */ 21 | // './content/creative-work/*.md', 22 | // … 23 | // `./` prefix is optional 24 | // 'content/creative-work/foobiz.md', 25 | // './content/elsewhere/does-not-exist-anymore.md', 26 | ], 27 | 28 | // './content/ghost.schema.yaml': [ 29 | // './content/casper.md', 30 | // './content/ether.md', 31 | // ], 32 | }, 33 | }, 34 | ], 35 | ], 36 | }; 37 | 38 | export default remarkConfig; 39 | -------------------------------------------------------------------------------- /demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["unifiedjs.vscode-remark"] 3 | } 4 | -------------------------------------------------------------------------------- /demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "http://json-schema.org/draft-07/schema#": ["content/**/*.schema.yaml"] 4 | }, 5 | "workbench.colorCustomizations": { 6 | "editorRuler.foreground": "#98c2bc1c", 7 | "editorInlayHint.foreground": "#B99", 8 | "editorInlayHint.background": "#222", 9 | "[GitHub Dark]": { 10 | "sideBar.background": "#1b1b1b", 11 | "editor.background": "#1b1b1b", 12 | "panel.background": "#181818" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | ## Install VS Code Remark extension (optional) 4 | 5 | ```sh 6 | code --install-extension unifiedjs.vscode-remark 7 | ``` 8 | 9 | ## Bootstrap current demo project 10 | 11 | ```sh 12 | pnpm i 13 | ``` 14 | 15 | ## Open content 16 | 17 | Let's open [./content](./content/) **markdowns** and **schemas** with VS Code: 18 | 19 | ```sh 20 | (cd ./content && code -r \ 21 | creative-work.schema.yaml \ 22 | correct-creative-work.md \ 23 | broken-creative-work.md) 24 | ``` 25 | 26 | ## Review problems 27 | 28 | ### Continuous linting with VS Code 29 | 30 | Toggle 'Problems' pane view (`⌘ + ⇧ + M` on mac) 31 | 32 | ### One-shot, full project linting with Command Line Interface 33 | 34 | ``` 35 | pnpm remark . 36 | ``` 37 | 38 | ## Edit content 39 | 40 | Play with schema and markdown frontmatter and see what happens in VS Code problem list! 41 | 42 | ## (Alt.) Custom pipeline 43 | 44 | Run `./pipeline.ts`: 45 | 46 | ```sh 47 | pnpm pipeline 48 | ``` 49 | 50 | --- 51 | 52 | 🔗  [JulianCataldo.com](https://www.juliancataldo.com) 53 | -------------------------------------------------------------------------------- /demo/content/creative-work.schema.yaml: -------------------------------------------------------------------------------- 1 | allOf: 2 | # (Optional) You can load an external definition file from relative path 3 | # Docs: https://github.com/JulianCataldo/remark-lint-frontmatter-schema#add-references-to-external-definitions-advanced 4 | - $ref: ./page.schema.yaml 5 | 6 | # "Creative work" props. will be merged with page schema props. 7 | - properties: 8 | category: 9 | type: string 10 | enum: 11 | - Book 12 | - Movie 13 | - Painting 14 | - Photo 15 | - Musical piece 16 | complex: 17 | type: object 18 | required: 19 | - time 20 | properties: 21 | time: 22 | type: number 23 | some: 24 | type: string 25 | 26 | required: 27 | - category 28 | - complex 29 | # # 30 | # # Or simply put your local properties directly: 31 | # properties: 32 | # category: 33 | # type: string 34 | # # ... 35 | -------------------------------------------------------------------------------- /demo/content/creative-work/behind-the-gare-st-lazare__local-broken.md: -------------------------------------------------------------------------------- 1 | --- 2 | # E.g, with a full relative path 3 | '$schema': ../creative-work.schema.yaml 4 | 5 | # title: Behind the Gare St. Lazare, Paris, 1932 6 | category: Video game 7 | --- 8 | 9 | # Markdownish title-ish 10 | 11 | > …Frontmatter **Schema** _validation_… 12 | 13 | 1. ~~Head~~ 14 | 2. ~~Shoulders~~ 15 | 3. ~~Knees~~ 16 | 17 | --- 18 | 19 | [me cleeck](http://perdu.com) 20 | -------------------------------------------------------------------------------- /demo/content/creative-work/guten-nachte__local-correct.md: -------------------------------------------------------------------------------- 1 | --- 2 | # E.g, with a path relative to remark config root folder 3 | '$schema': ./content/creative-work.schema.yaml 4 | 5 | title: Guten Nachte 6 | category: Musical piece 7 | 8 | complex: 9 | time: 8 10 | --- 11 | 12 | # Markdownish title-ish 13 | 14 | > …Frontmatter **Schema** _validation_… 15 | 16 | 1. Head 17 | 2. Shoulders 18 | 3. Knees 19 | 20 | --- 21 | 22 | [WideWebWorld](./link.foo) 23 | -------------------------------------------------------------------------------- /demo/content/creative-work/the-shipwreck__global-broken.md: -------------------------------------------------------------------------------- 1 | --- 2 | # Schema association for this file is defined in: 3 | # 🔗 `file:///./../../.remarkrc.mjs` 4 | 5 | complex: 6 | time: '1859' 7 | 8 | ## v—————— Must have a string title 9 | # title: My Great Title 🤩 10 | 11 | ## v—————— Must be `Book` | `Movie` | … 12 | category: Car 13 | 14 | description: ['not', 'an', 'array'] 15 | --- 16 | 17 | # You're welcome! 18 | 19 | **Associated globally in [.remarkrc.mjs](.remarkrc.mjs)** 20 | 21 | …Frontmatter Schema Validation… 22 | 23 | --- 24 | 25 | © 2222 — ACME incorporated corporation reserved. 26 | -------------------------------------------------------------------------------- /demo/content/page.schema.yaml: -------------------------------------------------------------------------------- 1 | title: Page 2 | 3 | properties: 4 | title: 5 | title: Title 6 | description: Used for SEO and tab title 7 | type: string 8 | maxLength: 80 9 | 10 | description: 11 | title: Description 12 | description: Used for SEO 13 | type: string 14 | maxLength: 300 15 | 16 | image: 17 | description: | 18 | Absolute or relative URL to image file. 19 | Used for hero header and page thumbnail. 20 | type: string 21 | 22 | required: 23 | - title 24 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "pipeline": "tsx pipeline.ts", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "remark-cli": "12.0.0", 16 | "tsx": "^3.13.0" 17 | }, 18 | "dependencies": { 19 | "remark": "15.0.1", 20 | "remark-frontmatter": "5.0.0", 21 | "remark-lint-frontmatter-schema": "latest", 22 | "vfile-reporter": "^8.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/pipeline.ts: -------------------------------------------------------------------------------- 1 | /* Launch with `pnpm pipeline` */ 2 | 3 | import { remark } from 'remark'; 4 | import remarkFrontmatter from 'remark-frontmatter'; 5 | import remarkLintFrontmatterSchema from 'remark-lint-frontmatter-schema'; 6 | import type { JSONSchema7 } from 'json-schema'; 7 | import { reporter } from 'vfile-reporter'; 8 | /* —————————————————————————————————————————————————————————————————————————— */ 9 | 10 | const mySchema: JSONSchema7 = { 11 | allOf: [ 12 | { 13 | /* Works with local / remote, YAML / JSON */ 14 | $ref: './content/page.schema.yaml', 15 | // $ref: 'https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/master/demo/content/page.schema.yaml', 16 | }, 17 | { 18 | properties: { 19 | baz: { 20 | type: 'string', 21 | }, 22 | }, 23 | }, 24 | ], 25 | }; 26 | 27 | /* ·········································································· */ 28 | 29 | const mdContent = `--- 30 | $schema: './content/creative-work.schema.yaml' 31 | title: 1234 32 | baz: ['wrong'] 33 | --- 34 | 35 | # Hey !`; 36 | 37 | /* ·········································································· */ 38 | 39 | console.log('Demo pipeline starting!…\n'); 40 | 41 | const output = await remark() 42 | // Your pipeline (basic example) 43 | // … 44 | .use(remarkFrontmatter) 45 | 46 | .use(remarkLintFrontmatterSchema, { 47 | /* Bring your own schema */ 48 | // embed: mySchema, 49 | // 50 | /* Override default AJV options */ 51 | // ajvOptions: { 52 | // }, 53 | /* —Or— just (local only) */ 54 | // embed: { 55 | // ...mySchema, 56 | // }, 57 | }) 58 | .process(mdContent); 59 | 60 | /* ·········································································· */ 61 | 62 | output.path = import.meta.url; 63 | // Or if you like, for easier referencing: 64 | // output.path = mySchema.$id ?? ''; 65 | 66 | console.error(reporter([output])); 67 | 68 | /** 69 | * Yields: 70 | * 71 | * ```sh 72 | * file:///<...>/content/ 73 | * 2:8-2:12 warning Keyword: type frontmatter-schema remark-lint 74 | * Type: string 75 | * Schema path: · ./content/page.schema.yaml/properties/title/type 76 | * 3:6-3:15 warning Keyword: type frontmatter-schema remark-lint 77 | * Type: string 78 | * Schema path: · #/allOf/1/properties/baz/type 79 | * 80 | * ⚠ 2 warnings 81 | * ``` 82 | * 83 | */ 84 | -------------------------------------------------------------------------------- /docs/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/5e2e246ae4c08d7939b829233f1713970d66e55c/docs/screenshot-2.png -------------------------------------------------------------------------------- /docs/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/5e2e246ae4c08d7939b829233f1713970d66e55c/docs/screenshot-3.png -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JulianCataldo/remark-lint-frontmatter-schema/5e2e246ae4c08d7939b829233f1713970d66e55c/docs/screenshot.png -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | /* ——————————————————————————————————————————————————————————————————————————— * 2 | * © Julian Cataldo — https://www.juliancataldo.com. * 3 | * See LICENSE in the project root. * 4 | /* —————————————————————————————————————————————————————————————————————————— */ 5 | 6 | /* eslint-disable max-lines */ 7 | import path from 'node:path'; 8 | import { existsSync } from 'node:fs'; 9 | import { findUp } from 'find-up'; 10 | // NOTE: minimatch@9 is breaking import. 11 | import { minimatch } from 'minimatch'; 12 | /* ·········································································· */ 13 | import yaml, { type Document, isNode, LineCounter } from 'yaml'; 14 | import Ajv from 'ajv'; 15 | import type { Options as AjvOptions, ErrorObject as AjvErrorObject } from 'ajv'; 16 | import addFormats from 'ajv-formats'; 17 | import $RefParser from '@apidevtools/json-schema-ref-parser'; 18 | import type { JSONSchema7 } from 'json-schema'; 19 | /* ·········································································· */ 20 | import { lintRule } from 'unified-lint-rule'; 21 | import type { VFile } from 'unified-lint-rule/lib'; 22 | import type { Root, YAML } from 'mdast'; 23 | import type { VFileMessage } from 'vfile-message'; 24 | /* —————————————————————————————————————————————————————————————————————————— */ 25 | 26 | const url = 'https://github.com/JulianCataldo/remark-lint-frontmatter-schema'; 27 | const nativeJsErrorName = 'Markdown YAML frontmatter error (JSON Schema)'; 28 | 29 | export interface Settings { 30 | /** 31 | * Global workspace file associations mapping (for linter extension). 32 | * 33 | * **Example**: `'schemas/thing.schema.yaml': ['content/things/*.md']` 34 | */ 35 | schemas?: Record; 36 | 37 | /** 38 | * Direct schema embedding (for using inside an `unified` transform pipeline). 39 | * 40 | * Format: JSON Schema - draft-2019-09 41 | * 42 | * **Documentation**: https://ajv.js.org/json-schema.html#draft-07 43 | */ 44 | embed?: JSONSchema7; 45 | 46 | /** 47 | * **Documentation**: https://ajv.js.org/options.html 48 | */ 49 | ajvOptions?: AjvOptions; 50 | } 51 | 52 | // IDEA: Might be interesting to populate with corresponding error reference 53 | type JSONSchemaReference = 'https://ajv.js.org/json-schema.html'; 54 | 55 | export interface FrontmatterSchemaMessage extends VFileMessage { 56 | schema: AjvErrorObject & { url: JSONSchemaReference }; 57 | } 58 | 59 | interface FrontmatterObject { 60 | $schema: string | undefined; 61 | /* This is the typical Frontmatter object, as treated by common consumers */ 62 | [key: string]: unknown; 63 | } 64 | 65 | /* ·········································································· */ 66 | 67 | /* The vFile cwd isn't the same as the one from IDE extension. 68 | Extension will cascade upward from the current processed file and 69 | take the remarkrc file as its cwd. It's multi-level workspace 70 | friendly. We have to mimick this behavior here, as remark lint rules doesn't 71 | seems to offer an API to hook up on this? */ 72 | async function getRemarkCwd(startDir: string) { 73 | const remarkConfigNames = [ 74 | '.remarkrc', 75 | '.remarkrc.json', 76 | '.remarkrc.yaml', 77 | '.remarkrc.yml', 78 | '.remarkrc.mjs', 79 | '.remarkrc.js', 80 | '.remarkrc.cjs', 81 | ]; 82 | const remarkConfPath = await findUp(remarkConfigNames, { 83 | cwd: path.dirname(startDir), 84 | }); 85 | if (remarkConfPath) { 86 | return path.dirname(remarkConfPath); 87 | } 88 | return process.cwd(); 89 | } 90 | 91 | /* ·········································································· */ 92 | 93 | function pushErrors( 94 | errors: AjvErrorObject[], 95 | yamlDoc: Document, 96 | vFile: VFile, 97 | /** File path from local `$schema` key or from global settings */ 98 | schemaRelPath: string, 99 | /** Used to map character range to line / column tuple */ 100 | lineCounter: LineCounter, 101 | ) { 102 | errors.forEach((error) => { 103 | let reason = ''; 104 | 105 | if (error.message) { 106 | /* Capitalize error message */ 107 | const errMessage = 108 | `${error.message.charAt(0).toUpperCase()}` + 109 | `${error.message.substring(1)}`; 110 | 111 | let expected = ''; 112 | if (Array.isArray(error.params.allowedValues)) { 113 | expected = `: \`${error.params.allowedValues.join('`, `')}\``; 114 | } else if (typeof error.params.allowedValue === 'string') { 115 | expected = `: \`${error.params.allowedValue}\``; 116 | } 117 | const sPath = schemaRelPath ? ` • ${schemaRelPath}` : ''; 118 | 119 | reason = `${errMessage}${expected}${sPath} • ${error.schemaPath}`; 120 | } 121 | 122 | /* Explode AJV error instance path and get corresponding YAML AST node */ 123 | const ajvPath = error.instancePath.substring(1).split('/'); 124 | const node = yamlDoc.getIn(ajvPath, true); 125 | 126 | const message = vFile.message(reason); 127 | 128 | // FIXME: Doesn't seems to be used in custom pipeline? 129 | // Always returning `false` 130 | message.fatal = true; 131 | 132 | /* `name` comes from native JS `Error` object */ 133 | message.name = nativeJsErrorName; 134 | 135 | /* Map YAML characters range to column / line positions, 136 | -OR- squiggling the opening frontmatter fence for **root** path errors */ 137 | if (isNode(node)) { 138 | /* Incriminated token */ 139 | message.actual = node.toString(); 140 | 141 | /* Map AJV Range to VFile Position, via YAML lib. parser */ 142 | if (node.range) { 143 | const OPENING_FENCE_LINE_COUNT = 1; /* Takes the `---` into account */ 144 | const start = lineCounter.linePos(node.range[0]); 145 | const end = lineCounter.linePos(node.range[1]); 146 | message.position = { 147 | start: { 148 | line: start.line + OPENING_FENCE_LINE_COUNT, 149 | column: start.col, 150 | }, 151 | end: { 152 | line: end.line + OPENING_FENCE_LINE_COUNT, 153 | column: end.col, 154 | }, 155 | }; 156 | // NOTE: Seems redundant, but otherwise, it is always set to 1:1 */ 157 | message.line = message.position.start.line; 158 | message.column = message.position.start.column; 159 | } 160 | } 161 | 162 | /* Assemble pretty per-error insights for end-user */ 163 | let note = `Keyword: ${error.keyword}`; 164 | if (Array.isArray(error.params.allowedValues)) { 165 | note += `\nAllowed values: ${error.params.allowedValues.join(', ')}`; 166 | 167 | /* Auto-fix replacement suggestions for `enum` */ 168 | message.expected = error.params.allowedValues; 169 | } else if (typeof error.params.allowedValue === 'string') { 170 | note += `\nAllowed value: ${error.params.allowedValue}`; 171 | 172 | /* Auto-fix replacement suggestion for `const` */ 173 | message.expected = [error.params.allowedValue]; 174 | } 175 | if (typeof error.params.missingProperty === 'string') { 176 | note += `\nMissing property: ${error.params.missingProperty}`; 177 | } 178 | if (typeof error.params.type === 'string') { 179 | note += `\nType: ${error.params.type}`; 180 | } 181 | /* `schemaRelPath` path prefix will show up only when using 182 | file association, not when using pipeline embedded schema */ 183 | note += `\nSchema path: ${schemaRelPath} · ${error.schemaPath}`; 184 | message.note = note; 185 | /* `message` comes from native JS `Error` object */ 186 | message.message = reason; 187 | 188 | /* Adding custom data from AJV */ 189 | /* It’s OK to store custom data directly on the VFileMessage: 190 | https://github.com/vfile/vfile-message#well-known-fields */ 191 | // NOTE: Might be better to type `message` before, instead of asserting here 192 | (message as FrontmatterSchemaMessage).schema = { 193 | url: 'https://ajv.js.org/json-schema.html', 194 | ...error, 195 | }; 196 | }); 197 | } 198 | 199 | /* ·········································································· */ 200 | 201 | async function validateFrontmatter( 202 | sourceYaml: YAML, 203 | vFile: VFile, 204 | settings: Settings, 205 | ) { 206 | const hasPropSchema = typeof settings.embed === 'object'; 207 | const lineCounter = new LineCounter(); 208 | let yamlDoc; 209 | let yamlJS; 210 | let hasLocalAssoc = false; 211 | let schemaPathFromCwd: string | undefined; 212 | const remarkCwd = await getRemarkCwd(vFile.path); 213 | 214 | /* Parse the YAML literal and get the YAML Abstract Syntax Tree, 215 | previously extracted by `remark-frontmatter` */ 216 | try { 217 | yamlDoc = yaml.parseDocument(sourceYaml.value, { lineCounter }); 218 | yamlJS = yamlDoc.toJS() as FrontmatterObject | null; 219 | 220 | /* Local `$schema` association takes precedence over global / prop. */ 221 | if (yamlJS?.$schema && typeof yamlJS.$schema === 'string') { 222 | hasLocalAssoc = true; 223 | /* Fallback if it's an embedded schema (no `path`) */ 224 | const vFilePath = vFile.path || ''; 225 | 226 | /* From current processed file directory (e.g. `./foo…` or `../foo…`) */ 227 | const dirFromCwd = path.isAbsolute(vFilePath) 228 | ? path.relative(process.cwd(), path.dirname(vFilePath)) 229 | : path.dirname(vFilePath); 230 | 231 | const standardPath = path.join(dirFromCwd, yamlJS.$schema); 232 | if (existsSync(standardPath)) { 233 | schemaPathFromCwd = standardPath; 234 | } else { 235 | /* Non standard behavior, like TS / Vite, not JSON Schema resolution. 236 | Resolving `/my/path` or `my/path` from current remark project root */ 237 | schemaPathFromCwd = path.join(remarkCwd, yamlJS.$schema); 238 | } 239 | } 240 | } catch (error) { 241 | if (error instanceof Error) { 242 | const banner = `YAML frontmatter parsing: ${schemaPathFromCwd ?? ''}`; 243 | vFile.message(`${banner} — ${error.name}: ${error.message}`); 244 | } 245 | } 246 | 247 | /* ········································································ */ 248 | 249 | /* Global schemas associations, only if no local schema is set */ 250 | if (yamlDoc && yamlJS && !hasLocalAssoc) { 251 | Object.entries(settings.schemas ?? {}).forEach( 252 | ([globSchemaPath, globSchemaAssocs]) => { 253 | /* Check if current markdown file is associated with this schema */ 254 | globSchemaAssocs.forEach((mdFilePath) => { 255 | if (typeof mdFilePath === 'string') { 256 | const mdPathCleaned = path.normalize(mdFilePath); 257 | 258 | /* With `remark`, `vFile.path` is already relative to project root, 259 | while `eslint-plugin-mdx` gives an absolute path */ 260 | const vFilePathRel = path.relative(remarkCwd, vFile.path); 261 | 262 | if (minimatch(vFilePathRel, mdPathCleaned)) { 263 | schemaPathFromCwd = path.join(remarkCwd, globSchemaPath); 264 | } 265 | } 266 | }); 267 | }, 268 | ); 269 | } 270 | 271 | /* ········································································ */ 272 | 273 | let schema: JSONSchema7 | undefined; 274 | if (hasPropSchema) { 275 | schema = settings.embed; 276 | } else if (schemaPathFromCwd) { 277 | /* Load schema + references */ 278 | schema = await $RefParser 279 | // NOTE: Ext. `$refs` are embedded, not local defs. 280 | // Could be useful to embed ext. refs. in definitions, 281 | // so we could keep the ref. name for debugging? 282 | .bundle(schemaPathFromCwd) 283 | .catch((error) => { 284 | if (error instanceof Error) { 285 | const banner = `YAML schema file load/parse: ${ 286 | schemaPathFromCwd ?? '' 287 | }`; 288 | vFile.message(`${banner} — ${error.name}: ${error.message}`); 289 | } 290 | return undefined; 291 | }) 292 | /* Asserting then using a JSONSchema4 for AJV (JSONSchema7) is OK */ 293 | .then((refSchema) => 294 | refSchema ? (refSchema as JSONSchema7) : undefined, 295 | ); 296 | 297 | /* Schema is now extracted, 298 | remove in-file `$schema` key, so it will not interfere later */ 299 | 300 | if (hasLocalAssoc && yamlJS && typeof yamlJS.$schema === 'string') { 301 | delete yamlJS.$schema; 302 | } 303 | } 304 | 305 | /* ········································································ */ 306 | 307 | /* We got an extracted schema to work with */ 308 | if (schema && yamlDoc) { 309 | /* Setup AJV (Another JSON-Schema Validator) */ 310 | const ajv = new Ajv({ 311 | /* Defaults */ 312 | allErrors: true /* So it doesn't stop at the first found error */, 313 | strict: false /* Prevents warnings for valid, but relaxed schemas */, 314 | 315 | /* User settings / overrides */ 316 | ...settings.ajvOptions, 317 | }); 318 | addFormats(ajv); 319 | 320 | /* JSON Schema compilation + validation with AJV */ 321 | try { 322 | const validate = ajv.compile(schema); 323 | validate(yamlJS); 324 | 325 | /* Push JSON Schema validation failures messages */ 326 | if (validate.errors?.length) { 327 | pushErrors( 328 | validate.errors, 329 | yamlDoc, 330 | vFile, 331 | schemaPathFromCwd ?? '', 332 | lineCounter, 333 | ); 334 | } 335 | } catch (error) { 336 | if (error instanceof Error) { 337 | const banner = `JSON schema malformed: ${schemaPathFromCwd ?? ''}`; 338 | vFile.message(`${banner} — ${error.name}: ${error.message}`); 339 | } 340 | } 341 | } 342 | } 343 | 344 | /* ·········································································· */ 345 | 346 | const remarkFrontmatterSchema = lintRule( 347 | { 348 | url, 349 | origin: 'remark-lint:frontmatter-schema', 350 | }, 351 | async (ast: Root, vFile: VFile, settings: Settings = {}) => { 352 | if (ast.children.length) { 353 | /* Handle only if the processed Markdown file has a frontmatter section */ 354 | const frontmatter = ast.children.find((child) => child.type === 'yaml'); 355 | if (frontmatter?.type === 'yaml') { 356 | await validateFrontmatter(frontmatter, vFile, settings); 357 | } 358 | } 359 | }, 360 | ); 361 | 362 | export default remarkFrontmatterSchema; 363 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-lint-frontmatter-schema", 3 | "version": "3.15.4", 4 | "description": "Validate Markdown frontmatter YAML against an associated JSON schema — remark-lint rule plugin", 5 | "keywords": [ 6 | "lint", 7 | "yaml", 8 | "remark", 9 | "validation", 10 | "linting", 11 | "json-schema", 12 | "linter", 13 | "unified", 14 | "frontmatter", 15 | "remarkjs", 16 | "rule", 17 | "ajv", 18 | "markdown", 19 | "vscode", 20 | "tooling" 21 | ], 22 | "homepage": "https://github.com/JulianCataldo/remark-lint-frontmatter-schema", 23 | "bugs": { 24 | "url": "https://github.com/JulianCataldo/remark-lint-frontmatter-schema/issues", 25 | "email": "contact@juliancataldo.com" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/JulianCataldo/remark-lint-frontmatter-schema" 30 | }, 31 | "license": "ISC", 32 | "author": "Julian Cataldo", 33 | "type": "module", 34 | "main": "./dist/index.js", 35 | "source": "./index.ts", 36 | "types": "./dist/index.d.ts", 37 | "exports": { 38 | ".": "./dist/index.js" 39 | }, 40 | "files": [ 41 | "dist/*", 42 | "index.ts" 43 | ], 44 | "scripts": { 45 | "build": "pnpm tsc", 46 | "dev": "pnpm tsc -w", 47 | "release": "semantic-release" 48 | }, 49 | "dependencies": { 50 | "@apidevtools/json-schema-ref-parser": "^11.2.0", 51 | "ajv": "^8.12.0", 52 | "ajv-formats": "^2.1.1", 53 | "find-up": "^6.3.0", 54 | "minimatch": "^9.0.3", 55 | "unified-lint-rule": "^2.1.2", 56 | "yaml": "^2.3.3" 57 | }, 58 | "devDependencies": { 59 | "@semantic-release/changelog": "6.0.3", 60 | "@semantic-release/git": "10.0.1", 61 | "@semantic-release/github": "9.2.1", 62 | "@semantic-release/npm": "11.0.0", 63 | "@types/eslint": "8.44.4", 64 | "@types/json-schema": "7.0.13", 65 | "@types/mdast": "3.0.13", 66 | "@types/minimatch": "5.1.2", 67 | "@types/node": "20.8.6", 68 | "@types/unist": "2.0.8", 69 | "@typescript-eslint/eslint-plugin": "6.7.5", 70 | "@typescript-eslint/parser": "6.7.5", 71 | "eslint": "8.51.0", 72 | "eslint-config-airbnb-base": "15.0.0", 73 | "eslint-config-airbnb-typescript": "17.1.0", 74 | "eslint-config-prettier": "9.0.0", 75 | "eslint-import-resolver-typescript": "3.6.1", 76 | "eslint-plugin-import": "2.28.1", 77 | "eslint-plugin-prettier": "5.0.1", 78 | "eslint-plugin-tsdoc": "0.2.17", 79 | "prettier": "3.0.3", 80 | "remark": "15.0.1", 81 | "remark-cli": "12.0.0", 82 | "semantic-release": "22.0.5", 83 | "typescript": "5.2.2", 84 | "unified": "10.1.2", 85 | "vfile-message": "3.1.4", 86 | "webdev-configs": "1.5.0" 87 | }, 88 | "packageManager": "pnpm@8.5.1" 89 | } 90 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "schedule": ["before 3am on the first day of the month"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable top-level await, and other modern ESM features. 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | // Enable node-style module resolution, for things like npm package imports. 7 | "moduleResolution": "node", 8 | // Enable JSON imports. 9 | "resolveJsonModule": true, 10 | // Enable stricter transpilation for better output. 11 | "isolatedModules": true, 12 | "types": ["node", "unist", "unified", "minimatch"], 13 | 14 | "allowJs": true, 15 | 16 | "noImplicitAny": true, 17 | 18 | "baseUrl": ".", 19 | "paths": {}, 20 | 21 | "allowSyntheticDefaultImports": true, 22 | 23 | "strictNullChecks": true, 24 | 25 | "declaration": true, 26 | "declarationMap": true, 27 | "outDir": "./dist" 28 | }, 29 | 30 | "include": ["./index.ts"] 31 | } 32 | --------------------------------------------------------------------------------