markdown-flavor-detection
v0.7.3
Published
Shared Markdown flavor detection and Flavor Grenade config resolution
Readme
markdown-flavor-detection
Shared Markdown flavor detection and Flavor Grenade config resolution.
This package contains the reusable, editor-independent logic used by
flavor-grenade-lsp to decide which Markdown dialect applies to a file. It is
intended for tools that need the same behavior without depending on NestJS, LSP
types, VS Code APIs, or the Flavor Grenade server runtime.
Install
npm install markdown-flavor-detectionbun add markdown-flavor-detectionWhat It Does
- exposes the supported Markdown flavor ids and selector labels;
- detects strong syntax evidence for MDX, R Markdown, GitLab Flavored Markdown, Pandoc Markdown, MultiMarkdown, kramdown, Markdown Extra, Reddit Markdown, and Stack Overflow Markdown;
- resolves effective flavor state from explicit selections,
.mdfattributes, Obsidian markers, syntax inference, and CommonMark fallback; - parses and applies
.mdfignore; - parses and applies
.mdfattributesassignments forflavorandstructured_profiles; - infers structured profiles such as Keep a Changelog, Common Changelog, and MADR;
- provides a Node filesystem adapter for cascading
.mdfignoreand.mdfattributeslookup.
Supported Flavors
import {
MARKDOWN_FLAVOR_IDS,
MARKDOWN_FLAVOR_SELECTIONS,
MARKDOWN_FLAVOR_LABELS,
} from 'markdown-flavor-detection';MARKDOWN_FLAVOR_IDS contains the concrete dialect ids:
originalcommonmarkobsidiangfmglfmpandocmultimarkdownmdxkramdownmarkdown-extrar-markdownredditstack-overflow
MARKDOWN_FLAVOR_SELECTIONS includes those ids plus auto.
Resolve a Flavor
import { resolveMarkdownFlavor } from 'markdown-flavor-detection';
const result = resolveMarkdownFlavor({
path: 'docs/example.md',
languageId: 'markdown',
syntaxText: markdownSource,
hasObsidianMarker: false,
});
if (result.kind === 'active') {
console.log(result.effective);
console.log(result.source);
}Resolution order is:
- explicit
flavorSelectionor.mdfattributesflavor; - Obsidian marker;
- strong syntax inference;
- CommonMark fallback.
Non-Markdown language ids and ignored paths return an inactive result.
const result = resolveMarkdownFlavor({
path: 'notes/private.md',
languageId: 'markdown',
ignored: true,
});
// { kind: 'inactive', reason: 'mdfignore' }Parse .mdfattributes
import { applyMdfAttributes, parseMdfAttributes } from 'markdown-flavor-detection';
const rules = parseMdfAttributes(`
docs/**/*.md flavor=obsidian
docs/adr/*.md structured_profiles=madr
CHANGELOG.md structured_profiles=keep-a-changelog
`);
const attributes = applyMdfAttributes(rules, 'docs/adr/0001-example.md');Supported assignment keys:
flavorstructured_profilesstructuredProfiles
Supported reset tokens:
!flavor!structured_profiles!structuredProfiles
structured_profiles accepts auto, none, or a comma-separated list of
structured profile ids.
Parse .mdfignore
import { matchMdfIgnore, parseMdfIgnore } from 'markdown-flavor-detection';
const rules = parseMdfIgnore(`
private/**
!private/public.md
`);
console.log(matchMdfIgnore(rules, 'private/draft.md')); // true
console.log(matchMdfIgnore(rules, 'private/public.md')); // falseIgnore and attribute patterns are path-based and use POSIX-style separators. Callers on Windows can pass native paths to the Node adapter; pure parser APIs expect vault-relative paths.
Resolve Config Files From Disk
Use the ./node export when you want the package to read .mdfignore and
.mdfattributes files from a real directory tree.
import { NodeFlavorConfigResolver, resolveMarkdownFlavor } from 'markdown-flavor-detection/node';
const resolver = new NodeFlavorConfigResolver({ maxConfigBytes: 8192 });
const config = resolver.resolveForFile('/path/to/vault', '/path/to/vault/docs/example.md');
const flavor = resolveMarkdownFlavor({
path: '/path/to/vault/docs/example.md',
languageId: 'markdown',
ignored: config.ignored,
mdfAttributes: config.attributes,
syntaxText: markdownSource,
});The resolver:
- walks from the vault root to the target file's directory;
- applies nested
.mdfignorefiles in order; - applies nested
.mdfattributesfiles in order; - rejects paths outside the configured root;
- ignores config files larger than
maxConfigBytes.
For directory scanning, use shouldPruneDirectory to skip ignored subtrees:
const skip = resolver.shouldPruneDirectory('/path/to/vault', '/path/to/vault/private');Runtime Boundaries
The root export is pure TypeScript/JavaScript logic. It does not read files and does not depend on Node-specific modules.
The markdown-flavor-detection/node export adds Node filesystem access for
config-file resolution. Use that export in CLIs, language servers, and linting
tools that operate on local files.
Out of Scope
This package does not parse Markdown into an AST, validate Markdown syntax, produce LSP diagnostics, or implement editor behavior. It only answers which flavor and structured profile should apply to a Markdown resource.
