Giter Site home page Giter Site logo

juliancataldo / remark-lint-frontmatter-schema Goto Github PK

View Code? Open in Web Editor NEW
53.0 3.0 6.0 2.27 MB

Validate your Markdown frontmatter data against a JSON schema — remark-lint rule plugin

License: ISC License

TypeScript 94.83% JavaScript 5.17%
lint ajv frontmatter json-schema linter linting remark remarkjs rule tooling unified validation vscode yaml markdown astro gatsby

remark-lint-frontmatter-schema's Introduction

remark-lint-frontmatter-schema 📑

VS Code unified
NPM Downloads ISC License GitHub PRs Welcome
TypeScript Prettier EditorConfig ESLint

Validate Markdown frontmatter YAML against an associated JSON schema with this remark-lint rule plugin.

Supports:

  • Types validation, pattern, enumerations,… and all you can get with JSON Schema
  • Code location problems indicator (for IDE to underline)
  • Auto-fixes with suggestions
  • Command Line Interface reports
  • VS Code integration (see below)
  • Global patterns or in-file schemas associations
  • In JS framework MD / MDX pipelines

Demo


🕹  Preview it online!

(w. Astro Content — Editor)


Jump to:


Demo screenshot of frontmatter schema linter 1


Demo screenshot of frontmatter schema linter 2


Demo screenshot of frontmatter schema linter 3


👉  Play with pre-configured ./demo

Quick shallow clone with:

pnpx degit JulianCataldo/remark-lint-frontmatter-schema/demo ./demo

Installation

Base

pnpm install -D \
remark remark-cli \
remark-frontmatter \
remark-lint-frontmatter-schema

Remove -D flag for runtime unified MD / MDX pipeline (custom, Astro, Gatsby, etc.), for production.
Keep it if you just want to lint with CLI or your IDE locally, without any production / CI needs.

VS Code (optional)

code --install-extension unifiedjs.vscode-remark

Configuration

CLI / IDE (VS Code) — Static linting

👉  See ./demo folder to get a working, pre-configured, bare project workspace.
You also get example Markdown files and associated schema to play with.
Supports remark-cli and/or unifiedjs.vscode-remark extension.

📌  Check out the demo/README.md for bootstrapping it.

Workspace

Create the root config. file for remark to source from:
touch ./.remarkrc.mjs

Paste this base configuration:

import remarkFrontmatter from 'remark-frontmatter';
import remarkLintFrontmatterSchema from 'remark-lint-frontmatter-schema';

const remarkConfig = {
	plugins: [remarkFrontmatter, remarkLintFrontmatterSchema],
};
export default remarkConfig;

You can use YAML / JSON / …, too (uses cosmiconfig).

Schema example

./content/creative-work.schema.yaml

type: object
properties:
  title:
    type: string
#
🆕  Add references to external definitions (advanced)

Referencing schema definitions allows re-using bit and piece instead of duplicate them, accross your content schemas.

You can reference an external schema relatively, using $ref. For example we can -kind of- merge an host object with a reference properties:

The host schema, content/articles/index.schema.yaml

allOf:
  - $ref: ../page.schema.yaml

  - properties:
      layout:
        const: src/layouts/Article.astro
      category:
        type: string
        enum:
          - Book
          - Movie
      foo:
        type: string

    required:
      - layout
      - category

A referenced schema, content/page.schema.yaml

properties:
  title:
    type: string
    maxLength: 80
    # ...
  # ...

required:
  - title

The result will be (virtually) the same as this:

properties:
  title:
    type: string
    maxLength: 80
    # ...
  # ...
  layout:
    const: src/layouts/Article.astro
  category:
    type: string
    enum:
      - Book
      - Movie
  foo:
    type: string
  # ...

required:
  - title
  - layout
  - category

Schemas associations

Inspired by VS Code JSON Schema and redhat.vscode-yaml conventions.

Inside frontmatter

See ./demo/content files for examples.

Schema association can be done directly inside the frontmatter of the Markdown file, relative to project root, thanks to the '$schema' key:

---
# From workspace root (`foo/…`, `/foo/…` or `./foo/…` is the same)
'$schema': content/creative-work.schema.yaml

# —Or— relatively, from this current file directory (`./foo/…` or `../foo/…`)
# '$schema': ../creative-work.schema.yaml

layout: src/layouts/Article.astro

title: Hello there
category: Book
#
---

# You're welcome!

🌝  My **Markdown** content…  🌚
…
Globally, with patterns

Note:
Locally defined '$schema' takes precedence over global settings below.

const remarkConfig = {
	plugins: [
		remarkFrontmatter,
		[
			remarkLintFrontmatterSchema,
			{
				schemas: {
					/* One schema for many files */
					'./content/creative-work.schema.yaml': [
						/* Per-file association */
						'./content/creative-work/the-shipwreck__global-broken.md',

						/* Support glob patterns ———v */
						// './content/creative-work/*.md',
						// …
						// `./` prefix is optional
						// 'content/creative-work/foobiz.md',
					],

					// './content/ghost.schema.yaml': [
					//   './content/casper.md',
					//   './content/ether.md',
					// ],
				},
			},
		],
	],
};

'./foo', '/foo', 'foo', all will work.
It's always relative to your ./.remarkrc.mjs file, in your workspace root.

CLI usage

Linting whole workspace files (as ./**/*.md) with remark-cli:

pnpm remark .

Yields:

Bonus

Validate your schema with JSON meta schema

First, install the YAML for Visual Studio Code extension:

code --install-extension redhat.vscode-yaml

Then, add this to your .vscode/settings.json:

{
	"yaml.schemas": {
		"http://json-schema.org/draft-07/schema#": ["content/**/*.schema.yaml"]
	}
	/* ... */
}
ESLint MDX plugin setup

Will work with the ESLint VS Code extension and the CLI command.

Install the ESLint MDX plugin, the MDX VS Code extension and the ESLint VS Code extension.

Add this dependencies to your project:

pnpm i -D eslint eslint-plugin-mdx \
eslint-plugin-prettier eslint-config-prettier

Add a .eslintrc.cjs:

/** @type {import("@types/eslint").Linter.Config} */

module.exports = {
	overrides: [
		{
			files: ['*.md', '*.mdx'],
			extends: ['plugin:mdx/recommended'],
		},
	],
};

Add a .remarkrc.yaml (or JSON, etc.), e.g.:

plugins:
  - remark-frontmatter

  - - remark-lint-frontmatter-schema
    - schemas:
        src/schemas/blog-post.schema.yaml:
          - content/blog-posts/*.{md,mdx}

  # - remark-preset-lint-consistent
  # - remark-preset-lint-markdown-style-guide
  # - remark-preset-lint-recommended

Result:



Lint with CLI:

pnpm eslint --ext .mdx .

Efforts has been made to have the best output for both remark and ESLint, for IDE extensions and CLIs.

Known issues
  • Expected enum values suggestions are working with the remark extension, not with the ESLint one.
  • Similarly, ESLint output will give less details (see screenshot above), and a bit different layout for CLI output, too.
  • remark extension seems to load faster, and is more reactive to schema changes.
  • As of eslint-plugin-mdx@2, .remarkrc.mjs (ES Module) is not loaded, JSON and YAML configs are fine.

MD / MDX pipeline — Runtime validation

Use it as usual like any remark plugin inside your framework or your custom unified pipeline.

Custom pipeline

When processing Markdown as single files inside your JS/TS app.
An minimal example is provided in ./demo/pipeline.ts, you can launch it with pnpm pipeline from ./demo.


Schema should be provided programmatically like this:

// …
import remarkFrontmatter from 'remark-frontmatter';
import remarkLintFrontmatterSchema from 'remark-lint-frontmatter-schema';
import type { JSONSchema7 } from 'json-schema';
import { reporter } from 'vfile-reporter';

const mySchema: JSONSchema7 = {
	/* … */
};

const output = await unified()
	// Your pipeline (basic example)
	.use(remarkParse)
	// …
	.use(remarkFrontmatter)

	.use(remarkLintFrontmatterSchema, {
		/* Bring your own schema */
		embed: mySchema,
	})

	// …
	.use(remarkRehype)
	.use(rehypeStringify)
	.use(rehypeFormat)
	.process(theRawMarkdownLiteral);

/* `path` is for debugging purpose here, as MD literal comes from your app. */
output.path = './the-current-processed-md-file.md';

console.error(reporter([output]));

Yields:

./the-current-processed-md-file.md
  1:1  warning  Must have required property 'tag'  frontmatter-schema  remark-lint

⚠ 1 warning
Implementation living example

Checkout Astro Content repository.

Astro Content relies on this library, among others, for providing linting reports.

Important foot-notes for custom pipeline

This is different from static linting, with VS Code extension or CLI.
It will not source .remarkrc (but you can source it by your own means, if you want).
In fact, it's not aware of your file structure, nor it will associate or import any schema / Markdown files.
That way, it will integrate easier with your own business logic and existing pipelines.
I found that static linting (during editing) / and runtime validation are two different uses cases enough to separate them in their setups, but I might converge them partially.

Framework

Warning
WIP. NOT tested yet! It is not a common use case for remark-lint.
Linting data inside frameworks are generally ignored.
AFAIK, messages data isn't forwarded to CLI output.
Feel free to open a PR if you have some uses cases in this area that need special care.
Maybe Astro or Astro Content could leverage these linter warnings in the future.

See global patterns schemas associations for settings reference.

Astro

In astro.config.mjs

// …
export default defineConfig({
  // …
  remarkPlugins: [
    // …
    'remark-frontmatter',
    ['remark-lint-frontmatter-schema', { schemas }],
    // …
  ];
  // …
});
Gatsby

In gatsby-config.js

{
	// …
	plugins: [
		// …
		{
			resolve: 'gatsby-transformer-remark',
			options: {
				plugins: [
					// …
					'remark-frontmatter',
					['remark-lint-frontmatter-schema', { schemas }],
					// …
				],
			},
		},
		// …
	];
}

Interfaces

export interface Settings {
	/**
	 * Global workspace file associations mapping (for linter extension).
	 *
	 * **Example**: `'schemas/thing.schema.yaml': ['content/things/*.md']`
	 */
	schemas?: Record<string, string[]>;

	/**
	 * Direct schema embedding (for using inside an `unified` transform pipeline).
	 *
	 * Format: JSON Schema - draft-2019-09
	 *
	 * **Documentation**: https://ajv.js.org/json-schema.html#draft-07
	 */
	embed?: JSONSchema7;

	/**
	 * **Documentation**: https://ajv.js.org/options.html
	 */
	ajvOptions?: AjvOptions;
}

export interface FrontmatterSchemaMessage extends VFileMessage {
	schema: AjvErrorObject & { url: JSONSchemaReference };
}

Example of a VFileMessage content you could collect from this lint rule:

[
	// …
	{
		// JS native `Error`
		"name": "Markdown YAML frontmatter error (JSON Schema)",
		"message": "Keyword: type\nType: string\nSchema path: #/properties/title/type",

		// `VFileMessage` (Linter / VS Code…)
		"reason": "/clientType: Must be equal to one of the allowed values",
		"line": 16,
		"column": 13,
		"url": "https://github.com/JulianCataldo/remark-lint-frontmatter-schema",
		"source": "remark-lint",
		"ruleId": "frontmatter-schema",
		"position": {
			"start": {
				"line": 16,
				"column": 13
			},
			"end": {
				"line": 16,
				"column": 24
			}
		},
		"fatal": false,
		"actual": "Individuaaaaaaaal",
		"expected": ["Corporate", "Non-profit", "Individual"],
		// Condensed string, human readable version of AJV error object
		"note": "Keyword: enum\nAllowed values: Corporate, Non-profit, Individual\nSchema path: #/properties/clientType/enum",

		// AJV's `ErrorObject`
		"schema": {
			"url": "https://ajv.js.org/json-schema.html",
			"instancePath": "/clientType",
			"schemaPath": "#/properties/clientType/enum",
			"keyword": "enum",
			"params": {
				"allowedValues": ["Corporate", "Non-profit", "Individual"]
			},
			"message": "must be equal to one of the allowed values"
		}
	}
]

Footnotes

100% ESM, including dependencies.

Environments:

Major dependencies:

ajv, yaml, remark, remark-frontmatter, unified, remark-cli


See CHANGELOG.md for release history.


Other projects 👀

  • retext-case-police: Check popular names casing. Example: ⚠️ github → ✅ GitHub.
  • remark-embed: A remark plugin for embedding remote / local Markdown or code snippets.
  • astro-content: A text based, structured content manager, for edition and consumption.
  • Web garden: Building blocks for making progressive and future-proof websites.

Find this project useful?

GitHub


🔗  JulianCataldo.com

remark-lint-frontmatter-schema's People

Contributors

dependabot[bot] avatar edwardbrowncross avatar juliancataldo avatar renovate[bot] avatar semantic-release-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

remark-lint-frontmatter-schema's Issues

Incompatibility with `remark-mdx-frontmatter`

This plugin assumes that the very first ast child will be the frontmatter. MDX parsers insert an mdxjsEsm block as the first child. (It describes the imports the mdx files has, even if there aren't any imports).

For example:

This markdown file:

---
title: 'Example MDX File'
---

# Example File

This is an mdx file

When run through these plugins

{
  "plugins": [
    "remark-frontmatter",
    "remark-mdx-frontmatter",
    "remark-lint-frontmatter-schema"
  ]
}

Produces sections with these types:

[ 'mdxjsEsm', 'yaml', 'heading', 'paragraph' ]

The result is that the frontmatter section is not linted at all

VSCode extension Marketplace availability

Hey, thanks a lot for sharing this.

Are there any plans to make the vscode extension available on the vscode marketplace? Background is I would like to install this extension in the vscode web version which is used on Github. I think the only way to install extensions there is through the official marketplace, right?

Need to sort out typings issues when updating all core dependencies

After doing a nuclear pnpm up --latest:

It works well with no regressions during runtime.
But sadly a lot of typings broke into havoc for major semver bumps.
I still have to wrap my head around various API changes for unified and friends deps. before publishing an update for this plugin, with 100% correct internal types.

I can't focus on this right now, but it's in my scope. As for now, this plugin is doing its duty, even with latest the remark versions.
In the meantime, any help will be appreciated!

Require that frontmatter exists

Hi, I'm using remark-lint-frontmatter-schema to validate the frontmatter for some markdown files. However, it seems that this plugin doesn't warn you when a file is missing frontmatter entirely, which means required fields don't get enforced at all. I believe it's reasonable behavior for an error to be thrown if the frontmatter schema requires a field to exist but the frontmatter isn't present (since the field therefore must not exist). I think a relatively easy way to solve this would be to feed empty frontmatter into validation if the frontmatter doesn't exist.

Would be ideal to get this fixed, even if this behavior is behind a flag or something to avoid breaking existing projects. Thank you!

ResolverError and .remarkrc file not working on VSCode Windows

I believe I have encountered a path resolving issue with remark-lint-frontmatter-schema on VSCode Windows (v1.84.2).

I am using a VSCode test profile with only vscode-remark extension (v2.1.0) installed.

Here's how the bug is produced:

  1. Clone the demo repo.
  2. Follow the installation step pnpm install remark ...
  3. Follow the installation step code --install-extension ...
  4. Follow the instruction and create the root config ./.remarkrc.mjs
  5. Explore and tinker with the content folder md files to observe the bug behaviour:

behind-the-gare-st-lazare__local-broken.md file gives an error

YAML schema file load/parse: content\creative-work.schema.yaml — ResolverError: Error opening file "D:\test remark\node_modules\.pnpm\@[email protected]\node_modules\@apidevtools\json-schema-ref-parser\dist\content\creative-work.schema.yaml" ENOENT: no such file or directory, open 'D:\test remark\node_modules\.pnpm\@[email protected]\node_modules\@apidevtools\json-schema-ref-parser\dist\content\creative-work.schema.yaml'

however, if the '$schema' parameter is set to ./content/creative-work.schema.yaml, the linting works

image

The the-shipwreck__global-broken.md file does not report any linting error when opened. When '$schema': ./content/creative-work.schema.yaml is pasted, the linting problems show up.

image
image

There is no mentioning of whether the .remarkrc.mjs file was loaded correctly - there was no error message, but when I supply a wrong schema path or target path, it still does not produce any error message.

This is my pnpm-lock file, hope it helps figuring out what went wrong.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): update dependency eslint-config-prettier to v8.6.0
  • chore(deps): update dependency typescript to v4.9.4

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/release.yml
  • actions/checkout v3
  • actions/setup-node v3
  • pnpm/action-setup v2.2.2
npm
demo/package.json
  • remark 14.0.2
  • remark-frontmatter 4.0.1
  • vfile-reporter ^7.0.4
  • tsx ^3.11.0
  • remark-cli 11.0.0
package.json
  • @apidevtools/json-schema-ref-parser ^9.0.9
  • ajv ^8.11.0
  • ajv-formats ^2.1.1
  • find-up ^6.3.0
  • minimatch ^5.1.0
  • unified-lint-rule ^2.1.1
  • yaml ^2.1.3
  • @semantic-release/changelog 6.0.1
  • @semantic-release/git 10.0.1
  • @semantic-release/github 8.0.6
  • @semantic-release/npm 9.0.1
  • @types/eslint 8.4.9
  • @types/json-schema 7.0.11
  • @types/mdast 3.0.10
  • @types/minimatch ^5.1.2
  • @types/node 18.11.9
  • @types/prettier 2.7.1
  • @types/unist 2.0.6
  • @typescript-eslint/eslint-plugin 5.42.0
  • @typescript-eslint/parser 5.42.0
  • eslint 8.26.0
  • eslint-config-airbnb-base 15.0.0
  • eslint-config-airbnb-typescript ^17.0.0
  • eslint-config-prettier 8.5.0
  • eslint-import-resolver-typescript 3.5.2
  • eslint-plugin-import 2.26.0
  • eslint-plugin-prettier 4.2.1
  • eslint-plugin-tsdoc 0.2.17
  • prettier 2.7.1
  • remark 14.0.2
  • remark-cli 11.0.0
  • semantic-release ^19.0.5
  • typescript 4.8.4
  • unified 10.1.2
  • vfile-message ^3.1.2
  • webdev-configs ^1.1.0

  • Check this box to trigger a request for Renovate to run again on this repository

Global schema doesn't work on Windows

On Windows operating system file from the demo broken-creative-work.md is not being checked for global schema. Local schema works.
I've tested on Linux and it worked fine, so I highly suspect the issue is only on Windows.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.