View on GitHub

md-check

Run code blocks from your markdown.

Download this project as a .zip file Download this project as a tar.gz file

Check your docs

md-check

Rush CI

md-check

Compile and execute your markdown documentation.

Why?

  1. Your docs have drifted out of sync with your codebase.
  2. Your docs are incomplete or wrong and won’t compile.
  3. Your users will thank you.

What?

Essentially this is like and borrows a lot from storybook and MDX. But isn’t focused on UI components.

  1. Extract fences from your markdown.
  2. Compile individual blocks if necessary.
  3. Execute each block and optionally capture output.

This verifies that

  1. Your code at the very least is valid.
  2. Your code is complete including imports because each block compiles and executes by itself. No more crawling the web for what was actually imported for that random snippet that was missing pieces.
  3. You code executes without an error. You could even execute tests in you code block. Part of the inspiration for this was Mocha’s doc and markdown reporters.

Install

# Run node and other commands
npm install --save-dev @btilford/md-check

# To compile typescript code blocks
npm install --save-dev @btilford/md-check-compile-typescript

Getting Started

The documentation here was generated from the markdown in examples/src by the md-check.js script .

Configure md-check

const {mdCheck, NodeVmExecutor} = require('@btilford/md-check');

const run = mdCheck({
    include: {
        patterns: [
            'docs/**/*.{md,mdx}'
        ]
    },
    executors: [
        [NodeVmExecutor.supply()]
    ]
});
// Calling run() will trigger execution.

Development

If you don’t have rush installed you’ll need to get that.

npm install -g @microsoft/rush

Clone and initialize

git clone git@github.com:btilford/md-check.git
rush install

Building

rush build

Writing Markdown.

Making code blocks executable

Executors get passed the fence and can either accept it or skip it. The node vm executor will split the fence’s name and check if the last item in the array is node or node-vm.

```javascript node
console.log('This would be executed by the node vm executor');
```


```javascript node-vm
console.log('This would be executed by the node vm executor');
```


```javascript js
console.warn('This would NOT be executed by the node vm executor');
```

File metadata

Metadata is YAML parsed out of the header block of the file. The currently support entries are.

title: Title for the file # Used to generate header and link text
description: Added to link.title 
env: # Provide env parameters for the file
    - NODE_ENV: test
fences:
    - title: Title for code block 0 # Used on links to specific code blocks
      description: Added to link.title
      env: # Provide env parameters specific for this block
        - NODE_ENV: production

Run Mocha

Configure the MochaExecutor

Installation

npm install --save-dev @btilford/md-check
npm install --save-dev @btilford/md-check-exec-mocha

Configuration

const {MochaExecutor} = require('@btilford/md-check-exec-mocha');

MochaExecutor.supply(); // use the defaults
MochaExecutor.supply(
    {ui: 'bdd'}, // Pass in Mocha.MochaOptions object (a custom reporter will be used unless you provide one)
    false // Don't fail on test failure. (default is true)
);

Running Mocha Tests

Inside your markdown file add a code fence ending with mocha, ```javascript mocha.

import {expect} from 'chai';

describe('My test', function() {
    it('Runs this test', function() {
        expect(true).to.eq(true);
        console.log('In test');
    });
});

stdout >

In test

Node Examples

Capture stdout and stderr

console.log('A log message');

stdout >

A log message

Setting up the NodeVMExecutor

This uses VM2 to execute the scripts in a sandbox. All the standard vm2 options can be passed in.

import {NodeVmExecutor} from '@btilford/md-check';

NodeVmExecutor.supply(); // Use defaults
NodeVmExecutor.supply({/* vm2 options */});

Shell Examples

Run code block a bash script

echo "Hello from bash"
echo "Goodbye" 1>&2

stdout >

Hello from bash

stderr >

Goodbye

This requires configuring a WriteSourceCompiler and a ForkExecutor that will create a command resembling bash $COMPILED_FILE which will expand to the path where the code block was written by the WriteSourceCompiler.

import {WriteSourceCompiler, ForkExecutor} from '@btilford/md-check';

// This will generate a temp file with the code block's contents
WriteSourceCompiler.supply(/bash$/)

ForkExecutor.supply(
    /bash$/,
    'bash',
    {},
    '$COMPILED_FILE', // Will insert the path provided by the WriteSourceCompiler
);

Eval a code block

echo "OS Type: $(uname)"

stdout >

OS Type: Linux

This can be setup with a ForkExecutor

import {ForkExecutor} from '@btilford/md-check';

ForkExecutor.supply(
    /eval$/,
    'eval',
    {},
    '"$SOURCE"', // Eval the code block's source.
);

Configuring a ForkExecutor

The fork executor takes the following arguments.

  1. A pattern to match on the fence name.
  2. The command to run.
  3. (optional) Default env properties. These can be overridden in your markdown at file or fence level.
  4. (optional) A var args to pass to the command.

In addition the command will have access to the following env properties

  • COMPILED_FILE will be set if the file goes through compilation
  • SOURCE_FILE the uncompiled file containing the fence’s code (not the original markdown file).
  • SOURCE the raw source of the file.
import {ForkExecutor} from '@btilford/md-check';

ForkExecutor.supply(
    /bash|sh$/, // match fence names ending with bash or sh
    'bash',  // execute bash
    {}, // no env defaults
    '-e', '$SOURCE' // args eval source
);
ForkExecutor.supply(
    /babel$/,
    'babel',
    {NODE_ENV: process.env.NODE_ENV},
    '$SOURCE_FILE', '--out-dir', 'dist/'
);
ForkExecutor.supply(
    /npm$/,
    'npm',
    {},
    'run', 'build'
);

Typescript Examples

Installation

npm install --save-dev @btilford/md-check-compile-typescript

Setup the Typescript compiler

import {TsCompiler} from '@btilford/md-check-compile-typescript';

TsCompiler.supply(); // default configs, uses ${process.cwd()}/tsconfig.json
TsCompiler.supply('tsconfig.json'); // Provide a path to you tsconfig
TsCompiler.supply({/* compiler options */}); // Provide a configuration object

Compile and run

Inside your markdown file add a code fence starting with typescript ```typescript.

import {ConsoleLog} from '@btilford/ts-base';

const log: Log = ConsoleLog.create('example');
log.info('Hello');