0.0.12 • Published 11 months ago

@hashicorp/platform-content-conformance v0.0.12

Weekly downloads
-
License
-
Repository
-
Last release
11 months ago

@hashicorp/platform-content-conformance

A system to execute checks against HashiCorp's various types of content. Very similar to ESLint, but tailored to checking content (MDX) and data (JSON, YAML) instead of JavaScript source.

Installation

$ npm install @hashicorp/platform-content-conformance

Usage

CLI

$ hc-content [options] [path/to/file] [path/to/file]

Options:
  --cwd
  --config ./path/to/conformance.config.mjs

JavaScript

The package exposes its underlying functionality through the ContentConformanceRunner class.

import { ContentConformanceRunner } from '@hashicorp/platform-content-conformance'

async function main() {
  const runner = new ContentConformanceRunner()

  await runner.init()

  await runner.run()

  console.log(runner.report())
}

main()

Options

  • cwd (string) - Specify the working directory where the content checks will be run. Defaults to process.cwd().
  • config (string) - Specify a path to a custom configuration file. If not provided, defaults to {cwd}/content-conformance.config.mjs.
  • files (string[]) - Specify a list of filenames to match against when running checks. Acts as a filter. If not specified, all files matching the patterns provided in your configuration will be checked.
  • reporter (string) - Specify the reporter type to use when calling runner.report(). Can be text, json, or markdown. Defaults to text.

Configuration

On its own, the content conformance system does not know where the files to be checked are located, or what rules should be used. To configure the checker, we create a content-conformance.config.mjs file located in your project's root directory.

export default {
  root: '.',
  contentFileGlobPattern: 'content/**/*.mdx',
  rules: {
    'no-h1': 'error',
  },
}

Configuring rules

Rules can be specified with differing severity to control the check's failure state. The valid values are: error, warn, or off. By default, errors will cause a run to fail, while warnings will not. Rules can also be turned off. Individual rules may also accept a configuration object. This configuration object is made available via the rule context that is passed into the executor functions.

export default {
  root: '.',
  contentFileGlobPattern: 'content/**/*.mdx',
  partialsDirectories: ['content/partials'],
  rules: {
    'with-config': [
      'error',
      { message: 'this will be available as context.config.message' },
    ],
  },
}

Presets

This package includes a number of configuration presets that can be used by specifying a preset configuration property:

export default {
  preset: 'base-mdx',
  rules: {
    'my-rule': 'off',
  },
}

When using a preset, any properties defined in the consuming configuration will take precedence over the preset. Individual rule configuration from a preset is completely overwritten if present in the consuming configuration. In the above case, if base-mdx contains configuration for the my-rule rule, it would be replaced with off.

Available presets can be found in the ./src/configs directory.

Rules

The core value of the conformance checker lies in the rules that you configure. The core package ships with a number of rules and presets that can be used, and custom rules can also be created in your project. A rule is a JavaScript module that exports a specific object format.

// ./rules/my-rule.js

/** @type {import('@hashicorp/platform-content-conformance').Rule} */
export default {
  id: 'my-rule',
  type: 'content',
  description: 'This rule performs an important check.',
  executor: {
    async contentFile(file, context) {},
    async dataFile(file, context) {},
  },
}

Rule Properties

  • id (string) A unique identifier for your rule. Convention is to use the ID as the rule's filename.
  • type (string) The type of rule. Can be content, data, or structure.
  • description (string) A short description of what the rule does.
  • executor (object) An object containing file "executor" functions. These functions contain the logic for the rule.
    • contentFile (function) Called when a ContentFile is visited.
    • dataFile (function) Called when a DataFile is visited.

Writing an executor

A rule executor contains the logic for the conformance rule. Each executor type is run once for each file of the provided type (content, or data), or once for the entire project if the type is structure.

Each executor has access to two arguments:

  • file (ContentFile | DataFile) The file being checked by the rule.
  • context (object) An object containing global information and a report() method (see ConformanceRuleContext in types.ts).

context.report()

An executor should contain logic related to the rule to determine if the given file is in violation. If a violation is detected, the executor should call context.report():

{
  async contentFile(file, context) {
    if (hasViolation) {
      context.report('This rule was broken', file)
    }
  }
}

The context.report() method accepts three arguments:

  • message (string) The message that will be used in generated output.
  • file (object) The file that the report is associated with.
  • node (object) The node that the violation is associated with. Allows positional information to be displayed in the report. (optional)

Note An individual rule does not have a concept of severity in its implementation. That is handled in the configuration file.

ContentFile visitors

When defining a contentFile executor, the file argument exposes a visit() method. This allows the rule to interact with the Abstract Syntax Tree (AST) generated from the source file. It allows checks to be written against specific parts of a content file with minimal boilerplate or extra processing. Usage of file.visit() is very similar to unist-util-visit.

{
  async contentFile(file, context) {
    file.visit(['link'], node => {
      const hasViolation = node.url.includes('example.com')
      if (hasViolation) {
        context.report(`Link to example.com detected: ${node.url}`, file, node)
      }
    })
  }
}

ContentFile frontmatter

The frontmatter of a content file can be accessed using the file.frontmatter() method:

{
  async contentFile(file, context) {
    console.log(file.frontmatter())
  }
}

Internally, the underlying ContentFile class ensures that the source file is only parsed into its AST representation once.

DataFile contents

To access the contents of a data file, use the .contents() method:

{
  async dataFile(file, context) {
    console.log(file.contents())
  }
}

Using remark-lint rules

remark-lint rules are supported by default. To use a remark-lint rule, add it to your configuration and ensure the package is installed as a development dependency in your project.

export default {
  root: '.',
  contentFileGlobPattern: 'content/**/*.mdx',
  rules: {
    'remark-lint-definition-case': 'error',
  },
}

Rule Messages

Writing meaningful, actionable rule messages is important to ensure contributors can address issues on their own. Messages passed to context.report() should be written in an active voice. For example:

❌ 'Expected frontmatter to contain: description'
✅ 'Document does not have a `description` key in its frontmatter. Add a `description` key at the top of the document.'

Internals

At its core, the content conformance checker handles loading a number of different file types and running rules against those files. Rule violations are tracked by file, and after execution rules can be passed to a reporter method to present the results.

Files that are eligible to be checked are represented as VFilehttps://github.com/vfile/vfile instances. VFile has a notion of messages attached to a file, along with a number of custom reporters to format and output a file's messages. We take advantage fo this by logging rule violations as a VFileMessage on the relevant file.

Runner

The runner is responsible for handling options, loading configuration, loading rules, and setting up the environment for the engine to execute. The runner can be called directly from JavaScript, or invoked via the CLI. After calling the engine to execute the checks, the runner is responsible for reporting the results using its configured reporter.

Engine

The engine is responsible for reading files and executing rules against the files.

File

All files that are checked are represented by VFile instances, with some additional information. The ContentFile primitive represents files that contain documentation content. Currently, our content files are authored with MDX, with support for YAML frontmatter, out-of-the-box. The DataFile primitive represents files that contain data used to render our pages. Currently, JSON and YAML files are supported.

Rule

This package ships with a number of rules, and they can also be defined within consuming projects. Rules can define a number of executor functions that will be run on the different file types. See Rules for more information.

0.0.12

11 months ago

0.0.11

1 year ago

0.0.10

1 year ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago