Skip to content

Commit 989c7bd

Browse files
committed
Add custom mdx2vast fork with directive support
Successfully forked and enhanced mdx2vast to support nf-core's custom remark directive syntax like :::tip{title='Examples' collapse}. Changes: - Fork mdx2vast from jdkato/mdx2vast to edmundmiller/mdx2vast - Add micromark-extension-directive and mdast-util-directive dependencies - Implement containerDirective, leafDirective, textDirective handling - Convert directives to HTML divs with proper classes and data attributes - Update Vale workflow to use custom fork instead of original package This resolves the MDX parsing runtime errors that were preventing Vale from processing .mdx files with custom Astro directive syntax. Vale now successfully lints MDX content and provides proper style feedback.
1 parent 013f196 commit 989c7bd

File tree

11 files changed

+7709
-10
lines changed

11 files changed

+7709
-10
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,41 @@
11
Astro
2+
nf-core
3+
Nextflow
4+
Seqera
5+
SeqeraLabs
6+
Bytesize
7+
Conda
8+
Mamba
9+
Bioconda
10+
Singularity
11+
Apptainer
12+
Docker
13+
GitHub
14+
GitLab
15+
Bitbucket
16+
AWS
17+
Azure
18+
GCP
19+
S3
20+
Kubernetes
21+
Slurm
22+
PBS
23+
LSF
24+
SGE
25+
TORQUE
26+
HTCondor
27+
Podman
28+
Charliecloud
29+
Shifter
30+
Spack
31+
OpenEBench
32+
MultiQC
33+
FastQC
34+
nf-test
35+
DSL2
36+
Groovy
37+
CWL
38+
WDL
39+
Snakemake
40+
Prefect
41+
Cromwell

.github/workflows/linting.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
steps:
2424
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
2525
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
26-
- name: Install mdx2vast for MDX support
27-
run: npm install -g mdx2vast
26+
- name: Install custom mdx2vast for MDX directive support
27+
run: npm install -g https://github.com/edmundmiller/mdx2vast.git
2828
- name: Verify Vale setup
2929
run: |
3030
echo "Vale config:" && cat .vale.ini
@@ -33,6 +33,6 @@ jobs:
3333
with:
3434
files: sites/main-site/src/content,sites/docs/src/content,sites/configs/src/content,sites/modules-subworkflows/src/content,sites/pipelines/src/content
3535
reporter: github-pr-check
36-
fail_on_error: false
36+
fail_on_error: true
3737
env:
3838
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ repos:
99
- prettier-plugin-svelte@3.4.0
1010
- prettier-plugin-astro@0.14.1
1111
files: \.(astro|svelte|mdx|md|yml|yaml)$
12+
- repo: https://github.com/errata-ai/vale
13+
rev: v3.7.1
14+
hooks:
15+
- id: vale
16+
args: ["--config=.vale.ini"]
17+
files: \.(md|mdx)$

.vale.ini

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,46 @@
11
StylesPath = .github/styles
2-
MinAlertLevel = suggestion
2+
MinAlertLevel = warning
33

4-
Packages = https://github.com/nf-core/vale/releases/latest/download/nf-core.zip
4+
Packages = MDX, https://github.com/nf-core/vale/releases/latest/download/nf-core.zip
55
Vocab = Docs
66

7-
[*.md]
7+
# Global settings for all Markdown and MDX files
8+
[*.{md,mdx}]
89
BasedOnStyles = nf-core, Vale
910
Vale.Spelling = NO
1011

11-
[src/content/events/**]
12+
# Blog posts - apply standard rules with relaxed personal pronouns
13+
[sites/main-site/src/content/blog/**/*.{md,mdx}]
14+
BasedOnStyles = nf-core, Vale
15+
Vale.Spelling = NO
16+
nf-core.We = NO
17+
18+
# Documentation - stricter rules for formal documentation
19+
[sites/docs/src/content/**/*.{md,mdx}]
20+
BasedOnStyles = nf-core, Vale
21+
Vale.Spelling = NO
22+
23+
# Events - more relaxed rules for community content
24+
[sites/main-site/src/content/events/**/*.md]
25+
BasedOnStyles = nf-core, Vale
26+
Vale.Spelling = NO
1227
nf-core.We = NO
1328
nf-core.SeqeraLabs = NO
1429

15-
[src/content/events/2024]
30+
# 2024 events - granular exception for historical content
31+
[sites/main-site/src/content/events/2024/**/*.md]
1632
nf-core.SeqeraLabs = YES
1733

18-
[src/content/events/*/bytesize_*.md]
34+
# Component and pipeline pages
35+
[sites/modules-subworkflows/src/content/**/*.{md,mdx}]
36+
BasedOnStyles = nf-core, Vale
37+
Vale.Spelling = NO
38+
39+
[sites/pipelines/src/content/**/*.{md,mdx}]
40+
BasedOnStyles = nf-core, Vale
41+
Vale.Spelling = NO
42+
43+
# Config pages
44+
[sites/configs/src/content/**/*.{md,mdx}]
1945
BasedOnStyles = nf-core, Vale
2046
Vale.Spelling = NO

mdx2vast/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# mdx2vast
2+
3+
`mdx2vast` is a CLI tool that converts MDX files to HTML while preserving
4+
JSX syntax and components. This format (HTML that retains the structure of
5+
the original file) is used by [Vale][1] to provide markup-aware linting.
6+
7+
## Installation
8+
9+
Ensure you have [Node.js](https://nodejs.org/) installed on your system.
10+
11+
### Install via `npm`:
12+
13+
```sh
14+
npm install -g mdx2vast
15+
```
16+
17+
## Usage
18+
19+
Run the CLI tool with the following command:
20+
21+
```sh
22+
mdx2vast input.mdx
23+
```
24+
25+
[1]: https://vale.sh/

mdx2vast/bin/cli.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env node
2+
import { Command } from 'commander';
3+
import fs from 'fs';
4+
import { fileURLToPath } from 'url';
5+
import path from 'path';
6+
import getStdin from 'get-stdin';
7+
8+
import { toValeAST } from './lib.js';
9+
10+
// Get the directory name of the current module
11+
const __filename = fileURLToPath(import.meta.url);
12+
const __dirname = path.dirname(__filename);
13+
14+
// Read the version from package.json
15+
const packageJsonPath = path.join(__dirname, '../package.json');
16+
const { version } = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
17+
18+
const program = new Command();
19+
20+
program
21+
.version(version)
22+
.description('CLI to convert MDX to HTML while preserving JSX and expressions.')
23+
.argument('[file]', 'path to the MDX file to read')
24+
.action(async (file) => {
25+
if (file) {
26+
if (fs.existsSync(file) && fs.statSync(file).isFile()) {
27+
fs.readFile(file, 'utf8', (err, doc) => {
28+
if (err) {
29+
console.error("Error reading the file:", err.message);
30+
process.exit(1);
31+
}
32+
console.log(toValeAST(doc));
33+
});
34+
} else {
35+
console.error('File does not exist or the path is incorrect.');
36+
process.exit(1);
37+
}
38+
} else {
39+
const input = await getStdin();
40+
if (input.trim() === '') {
41+
console.error('No input provided.');
42+
process.exit(1);
43+
}
44+
console.log(toValeAST(input));
45+
}
46+
});
47+
48+
program.parse(process.argv);

mdx2vast/bin/lib.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// TODO: Should we use `micromark-extension-mdx` instead? It's "non-JS aware,"
2+
// which is what we want, but it doesn't seem to recognize non-expression
3+
// syntax (such as import statements).
4+
import { mdxjs } from "micromark-extension-mdxjs";
5+
import { fromMarkdown } from "mdast-util-from-markdown";
6+
import { mathFromMarkdown } from "mdast-util-math";
7+
import { math } from "micromark-extension-math";
8+
import { mdxFromMarkdown } from "mdast-util-mdx";
9+
import { directive } from "micromark-extension-directive";
10+
import { directiveFromMarkdown } from "mdast-util-directive";
11+
import { toHast } from "mdast-util-to-hast";
12+
import { h } from "hastscript";
13+
import { toHtml } from "hast-util-to-html";
14+
15+
const mdxNodes = [
16+
"mdxjsEsm",
17+
"mdxFlowExpression",
18+
"mdxJsxFlowElement",
19+
"mdxJsxTextElement",
20+
"mdxTextExpression",
21+
];
22+
23+
const directiveNodes = [
24+
"containerDirective",
25+
"leafDirective",
26+
"textDirective",
27+
];
28+
29+
function isComment(source) {
30+
return source.startsWith("{/*") && source.endsWith("*/}");
31+
}
32+
33+
function createCustomHandler(doc) {
34+
return function customHandler(state, node, parent) {
35+
const start = node.position.start.offset;
36+
const end = node.position.end.offset;
37+
38+
const source = doc.slice(start, end);
39+
if (node.type === "mdxFlowExpression" && isComment(source)) {
40+
return { type: "comment", value: source.slice(3, -3) };
41+
} else if (mdxNodes.includes(node.type)) {
42+
const className = `mdxNode ${node.type}`;
43+
if (source.includes("\n")) {
44+
return h("pre", h("code", { className }, source));
45+
} else {
46+
return h("code", { className }, source);
47+
}
48+
} else if (directiveNodes.includes(node.type)) {
49+
// Handle directives like :::tip{title="Examples" collapse}
50+
// Convert them to div elements with appropriate classes and content
51+
const directiveType = node.name || 'directive';
52+
const attributes = node.attributes || {};
53+
54+
// Convert directive to a div with class
55+
const divProps = { class: `directive directive-${directiveType}` };
56+
57+
// Add data attributes for directive attributes
58+
for (const [key, value] of Object.entries(attributes)) {
59+
divProps[`data-${key}`] = value;
60+
}
61+
62+
// Process children if they exist
63+
const children = node.children ?
64+
node.children.map(child => state.one(child, node)) :
65+
[];
66+
67+
return h('div', divProps, children);
68+
}
69+
70+
return null;
71+
};
72+
}
73+
74+
function toValeAST(doc) {
75+
const mdast = fromMarkdown(doc, {
76+
extensions: [mdxjs(), math(), directive()],
77+
mdastExtensions: [mdxFromMarkdown(), mathFromMarkdown(), directiveFromMarkdown()],
78+
});
79+
80+
const customHandler = createCustomHandler(doc);
81+
82+
const hast = toHast(mdast, {
83+
allowDangerousHtml: true,
84+
passThrough: [...mdxNodes, ...directiveNodes],
85+
handlers: {
86+
mdxjsEsm: customHandler,
87+
mdxFlowExpression: customHandler,
88+
mdxJsxFlowElement: customHandler,
89+
mdxJsxTextElement: customHandler,
90+
mdxTextExpression: customHandler,
91+
containerDirective: customHandler,
92+
leafDirective: customHandler,
93+
textDirective: customHandler,
94+
},
95+
});
96+
97+
return toHtml(hast);
98+
}
99+
100+
export { toValeAST };

0 commit comments

Comments
 (0)