@markii/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/corpus.js CHANGED
@@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  /**
5
5
  * The language-agnostic conformance corpus: `*.mk.md` inputs plus
6
- * expected-AST `*.json` siblings, living at the repo root (see DESIGN.md
6
+ * expected-AST `*.json` siblings, living at the repo root (see docs/spec.md
7
7
  * §13). This module is deliberately parser-agnostic — it only
8
8
  * loads/compares corpus data — so it's usable from any package's tests (or
9
9
  * a future non-JS implementation's tooling), not just `@markii/core`'s own
@@ -0,0 +1,50 @@
1
+ import type { Root } from 'mdast';
2
+ /**
3
+ * A document's leading YAML frontmatter block, as far as this package cares
4
+ * about it: the raw text between the `---` fences, plus the one
5
+ * format-defined key (`uses`) when it is written in one of the two simple
6
+ * list forms this module understands.
7
+ *
8
+ * `raw` is the block's exact source text, unparsed — a host that genuinely
9
+ * needs arbitrary YAML metadata (its own `title`, `tags`, ...) brings its own
10
+ * YAML parser and reads this string. `@markii/core` deliberately does not:
11
+ * see `readUses` below.
12
+ */
13
+ export interface Frontmatter {
14
+ /** The raw YAML text between the `---` fences (no fences, no trailing newline). */
15
+ raw: string;
16
+ /**
17
+ * Pack names from a `uses:` list written in one of the two supported
18
+ * forms, or `undefined` when the key is absent, is written in any other
19
+ * shape, or contains anything that is not a plain name. An empty list
20
+ * (`uses: []`) is `[]` — "declared, and it names no packs" — which is
21
+ * distinguishable from `undefined` ("no usable declaration").
22
+ */
23
+ uses?: string[];
24
+ }
25
+ /**
26
+ * The document's leading YAML frontmatter, or `undefined` when it has none.
27
+ *
28
+ * Accepts either the source text or an already-parsed tree, so a caller that
29
+ * has parsed the document already (the common case for a renderer) pays no
30
+ * second parse. Recognition is the PARSER's, not this module's: frontmatter
31
+ * is whatever `parse` produced as a leading `yaml` node, so the accessor and
32
+ * the renderer can never disagree about whether a `---` block at the top of
33
+ * a file was metadata or a thematic break.
34
+ *
35
+ * Never throws — `uses` simply degrades to `undefined` for anything beyond
36
+ * the two list forms `readUses` documents. A tree that did not come from
37
+ * this package's `parse` (a host that transforms the AST, or hand-builds
38
+ * one) is treated the same way: a leading node that merely CLAIMS to be
39
+ * `yaml` without carrying string content is no frontmatter at all, rather
40
+ * than a `raw.split` on a number.
41
+ */
42
+ export declare function extractFrontmatter(source: string | Root): Frontmatter | undefined;
43
+ /**
44
+ * Convenience wrapper over `extractFrontmatter`: just the `uses` list, or
45
+ * `undefined` when the document has no frontmatter, no `uses:` key, or a
46
+ * `uses:` value outside the two supported list forms. `uses` semantics
47
+ * (surfacing "pack not installed" in a host's UI) live with the host — this
48
+ * is only the reader.
49
+ */
50
+ export declare function extractFrontmatterUses(source: string | Root): string[] | undefined;
@@ -0,0 +1,178 @@
1
+ import { parse } from './parse.js';
2
+ /**
3
+ * The one format-defined frontmatter key (docs/format.md, docs/packs.md), as
4
+ * a top-level `uses:` line: the key at column 0 (so a `uses:` nested under
5
+ * some other mapping is deliberately invisible to this reader), optional
6
+ * spacing, then whatever follows the colon on the same line.
7
+ */
8
+ const USES_LINE = /^uses[ \t]*:(.*)$/;
9
+ /** One block-sequence item line: `- name`, capturing its indent and value. */
10
+ const LIST_ITEM_LINE = /^([ \t]*)-[ \t]+(.*)$/;
11
+ /**
12
+ * What a pack name may look like once quotes are stripped. Intentionally
13
+ * narrow: alphanumerics, `-`, `_`, `.` only (docs/packs.md — a namespace is
14
+ * a plain word, joined to a component name with `-` or `_`). Anything else —
15
+ * whitespace, `:`, `#`, brackets, braces, commas, a stray quote — means the
16
+ * value is not the simple scalar this reader claims to understand, so the
17
+ * whole list degrades to `undefined` rather than being half-read.
18
+ *
19
+ * A name like `__proto__` or `constructor` passes, and that is deliberate:
20
+ * this reader returns STRINGS in an array, never object keys, so a
21
+ * prototype-flavored name is ordinary data here. The registry lookups that
22
+ * eventually consume such a name are themselves null-prototype and
23
+ * `Object.hasOwn`-guarded (`@markii/react`'s `registry.ts`), which is where
24
+ * that class of defense belongs — not in a name filter that would have to
25
+ * guess every hostile spelling.
26
+ */
27
+ const PACK_NAME = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
28
+ /**
29
+ * Reads one flow-sequence or block-sequence item: trims it, strips ONE pair
30
+ * of matching surrounding quotes (`'a'` / `"a"` — YAML's two quoting styles,
31
+ * used here only to unwrap a name, never to interpret escapes), and returns
32
+ * it only if what remains is a plain pack name. Returns `undefined` for
33
+ * anything else, including an opening quote with no matching close.
34
+ */
35
+ function readItem(text) {
36
+ const trimmed = text.trim();
37
+ if (trimmed === '')
38
+ return undefined;
39
+ const quote = trimmed[0];
40
+ let value = trimmed;
41
+ if (quote === '"' || quote === "'") {
42
+ if (trimmed.length < 2 || !trimmed.endsWith(quote))
43
+ return undefined;
44
+ value = trimmed.slice(1, -1);
45
+ }
46
+ return PACK_NAME.test(value) ? value : undefined;
47
+ }
48
+ /** Maps `readItem` over every raw item, failing the WHOLE list if any item fails. */
49
+ function readItems(rawItems) {
50
+ const values = [];
51
+ for (const rawItem of rawItems) {
52
+ const value = readItem(rawItem);
53
+ if (value === undefined)
54
+ return undefined;
55
+ values.push(value);
56
+ }
57
+ return values;
58
+ }
59
+ /**
60
+ * Reads the flow form — `uses: [a, b]` — from the text that follows the
61
+ * colon. The text must be exactly a bracketed sequence: a trailing comment
62
+ * (`uses: [a] # note`) or any other suffix is NOT this form and yields
63
+ * `undefined`, since recognizing it would mean tokenizing YAML rather than
64
+ * matching one fixed shape. `[]` yields the empty list.
65
+ */
66
+ function readFlowSequence(text) {
67
+ if (!text.startsWith('[') || !text.endsWith(']'))
68
+ return undefined;
69
+ const inner = text.slice(1, -1).trim();
70
+ if (inner === '')
71
+ return [];
72
+ return readItems(inner.split(','));
73
+ }
74
+ /**
75
+ * Reads the block form — a bare `uses:` followed by `- name` lines — from
76
+ * the lines after the key. Items must be contiguous (the first line that is
77
+ * not an item ends the sequence, blank lines included) and share one indent,
78
+ * which is what keeps a differently-indented continuation of some richer
79
+ * YAML structure from being mistaken for a flat list. No items, or a
80
+ * ragged/uneven sequence, yields `undefined`.
81
+ */
82
+ function readBlockSequence(lines, start) {
83
+ const rawItems = [];
84
+ let indent;
85
+ for (const line of lines.slice(start)) {
86
+ const match = LIST_ITEM_LINE.exec(line);
87
+ const [, lineIndent, item] = match ?? [];
88
+ if (lineIndent === undefined || item === undefined)
89
+ break;
90
+ if (indent === undefined)
91
+ indent = lineIndent;
92
+ else if (lineIndent !== indent)
93
+ return undefined;
94
+ rawItems.push(item);
95
+ }
96
+ return rawItems.length > 0 ? readItems(rawItems) : undefined;
97
+ }
98
+ /**
99
+ * The hand-rolled `uses:` reader — the whole reason `@markii/core` needs no
100
+ * YAML library, exactly as `@markii/bundle`'s `manifest.ts` needs no schema
101
+ * library. It recognizes TWO shapes and nothing else:
102
+ *
103
+ * ```yaml
104
+ * uses: [ana, gh] # flow sequence
105
+ * uses: # block sequence
106
+ * - ana
107
+ * - gh
108
+ * ```
109
+ *
110
+ * quotes and surrounding whitespace tolerated in both. Every other input —
111
+ * a `uses:` holding a scalar or a nested mapping, a duplicated `uses:` key,
112
+ * an indented (non-top-level) `uses:`, a value this reader cannot recognize,
113
+ * or frontmatter that is not valid YAML at all — returns `undefined`. It
114
+ * NEVER throws and never returns a partially-read list: `uses` is an
115
+ * informative hint (docs/packs.md — "this note uses pack `ana`, which is not
116
+ * installed"), so "I could not read this" and "there is nothing here" are
117
+ * the same, harmless answer, while a half-read list would produce a wrong
118
+ * warning about packs the note never named.
119
+ */
120
+ function readUses(raw) {
121
+ const lines = raw.split(/\r?\n/);
122
+ let keyIndex = -1;
123
+ let rest = '';
124
+ for (const [index, line] of lines.entries()) {
125
+ const [, value] = USES_LINE.exec(line) ?? [];
126
+ if (value === undefined)
127
+ continue;
128
+ // A second top-level `uses:` makes the document ambiguous (YAML itself
129
+ // rejects duplicate keys); refuse to guess which one was meant.
130
+ if (keyIndex !== -1)
131
+ return undefined;
132
+ keyIndex = index;
133
+ rest = value;
134
+ }
135
+ if (keyIndex === -1)
136
+ return undefined;
137
+ const inline = rest.trim();
138
+ return inline === ''
139
+ ? readBlockSequence(lines, keyIndex + 1)
140
+ : readFlowSequence(inline);
141
+ }
142
+ /**
143
+ * The document's leading YAML frontmatter, or `undefined` when it has none.
144
+ *
145
+ * Accepts either the source text or an already-parsed tree, so a caller that
146
+ * has parsed the document already (the common case for a renderer) pays no
147
+ * second parse. Recognition is the PARSER's, not this module's: frontmatter
148
+ * is whatever `parse` produced as a leading `yaml` node, so the accessor and
149
+ * the renderer can never disagree about whether a `---` block at the top of
150
+ * a file was metadata or a thematic break.
151
+ *
152
+ * Never throws — `uses` simply degrades to `undefined` for anything beyond
153
+ * the two list forms `readUses` documents. A tree that did not come from
154
+ * this package's `parse` (a host that transforms the AST, or hand-builds
155
+ * one) is treated the same way: a leading node that merely CLAIMS to be
156
+ * `yaml` without carrying string content is no frontmatter at all, rather
157
+ * than a `raw.split` on a number.
158
+ */
159
+ export function extractFrontmatter(source) {
160
+ const tree = typeof source === 'string' ? parse(source) : source;
161
+ const first = tree.children[0];
162
+ if (first?.type !== 'yaml' || typeof first.value !== 'string') {
163
+ return undefined;
164
+ }
165
+ const raw = first.value;
166
+ const uses = readUses(raw);
167
+ return uses === undefined ? { raw } : { raw, uses };
168
+ }
169
+ /**
170
+ * Convenience wrapper over `extractFrontmatter`: just the `uses` list, or
171
+ * `undefined` when the document has no frontmatter, no `uses:` key, or a
172
+ * `uses:` value outside the two supported list forms. `uses` semantics
173
+ * (surfacing "pack not installed" in a host's UI) live with the host — this
174
+ * is only the reader.
175
+ */
176
+ export function extractFrontmatterUses(source) {
177
+ return extractFrontmatter(source)?.uses;
178
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { parse } from './parse.js';
2
+ export { extractFrontmatter, extractFrontmatterUses, type Frontmatter, } from './frontmatter.js';
2
3
  export { toHast, nodeToHast, isSafeUrl, type MarkNode } from './to-hast.js';
3
4
  export { extractScripts, isBareAttribute, isValidScriptName, parseMetaAttributes, type ScriptBlock, } from './scripts.js';
package/dist/index.js CHANGED
@@ -5,5 +5,6 @@
5
5
  // via `@markii/react`) never has to reason about Node built-ins reachable from
6
6
  // its entry point.
7
7
  export { parse } from './parse.js';
8
+ export { extractFrontmatter, extractFrontmatterUses, } from './frontmatter.js';
8
9
  export { toHast, nodeToHast, isSafeUrl } from './to-hast.js';
9
10
  export { extractScripts, isBareAttribute, isValidScriptName, parseMetaAttributes, } from './scripts.js';
package/dist/parse.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Root } from 'mdast';
2
2
  /**
3
3
  * Parses Mark text into an mdast AST. CommonMark plus GitHub-Flavored
4
4
  * Markdown (`remark-gfm`: tables, task lists, strikethrough, autolinks) plus
5
+ * optional leading YAML frontmatter (`remark-frontmatter`) plus
5
6
  * the generic directive grammar (`remark-directive`) — nothing more. GFM and
6
7
  * directives are independent micromark syntax extensions (GFM extends the
7
8
  * table / list-item / inline grammar; directives add their own `:`/`::`/`:::`
@@ -13,5 +14,16 @@ import type { Root } from 'mdast';
13
14
  * and children; interpreting a name is the renderer's job; GFM nodes are
14
15
  * standard mdast — `table`, `listItem.checked`, `delete` — needing no
15
16
  * component/registry coupling at all).
17
+ *
18
+ * Frontmatter is the YAML preset only (`---` fences; the TOML `+++` form is
19
+ * deliberately not enabled) and, per micromark's frontmatter extension, is
20
+ * recognized ONLY as the very first construct of the document. Everywhere
21
+ * else `---` keeps its ordinary CommonMark meaning — a thematic break, or a
22
+ * setext heading underline — and an unterminated opening `---` degrades to
23
+ * exactly that too, never to an error (conformance fixtures 19-23). The
24
+ * resulting `yaml` node carries the raw block text and nothing more: this
25
+ * module does not interpret YAML, and `@markii/core` depends on no YAML
26
+ * library. `frontmatter.ts`'s hand-rolled accessor reads the one
27
+ * format-defined key (`uses`) off that raw text for the simple list forms.
16
28
  */
17
29
  export declare function parse(text: string): Root;
package/dist/parse.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { unified } from 'unified';
2
2
  import remarkParse from 'remark-parse';
3
3
  import remarkDirective from 'remark-directive';
4
+ import remarkFrontmatter from 'remark-frontmatter';
4
5
  import remarkGfm from 'remark-gfm';
5
6
  /**
6
7
  * Parses Mark text into an mdast AST. CommonMark plus GitHub-Flavored
7
8
  * Markdown (`remark-gfm`: tables, task lists, strikethrough, autolinks) plus
9
+ * optional leading YAML frontmatter (`remark-frontmatter`) plus
8
10
  * the generic directive grammar (`remark-directive`) — nothing more. GFM and
9
11
  * directives are independent micromark syntax extensions (GFM extends the
10
12
  * table / list-item / inline grammar; directives add their own `:`/`::`/`:::`
@@ -16,10 +18,22 @@ import remarkGfm from 'remark-gfm';
16
18
  * and children; interpreting a name is the renderer's job; GFM nodes are
17
19
  * standard mdast — `table`, `listItem.checked`, `delete` — needing no
18
20
  * component/registry coupling at all).
21
+ *
22
+ * Frontmatter is the YAML preset only (`---` fences; the TOML `+++` form is
23
+ * deliberately not enabled) and, per micromark's frontmatter extension, is
24
+ * recognized ONLY as the very first construct of the document. Everywhere
25
+ * else `---` keeps its ordinary CommonMark meaning — a thematic break, or a
26
+ * setext heading underline — and an unterminated opening `---` degrades to
27
+ * exactly that too, never to an error (conformance fixtures 19-23). The
28
+ * resulting `yaml` node carries the raw block text and nothing more: this
29
+ * module does not interpret YAML, and `@markii/core` depends on no YAML
30
+ * library. `frontmatter.ts`'s hand-rolled accessor reads the one
31
+ * format-defined key (`uses`) off that raw text for the simple list forms.
19
32
  */
20
33
  export function parse(text) {
21
34
  const processor = unified()
22
35
  .use(remarkParse)
36
+ .use(remarkFrontmatter)
23
37
  .use(remarkDirective)
24
38
  .use(remarkGfm);
25
39
  return processor.parse(text);
package/dist/scripts.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Code, Root } from 'mdast';
2
2
  /**
3
- * Slice 1 of the scripting-usability layer (DESIGN.md §8): this module only
3
+ * Slice 1 of the scripting-usability layer (docs/scripting.md): this module only
4
4
  * *reads* script blocks out of an already-parsed AST — it never executes
5
5
  * anything, imports no Lua/wasmoon, and knows nothing about a value store.
6
6
  * A script block is, at the parse layer, an ordinary fenced code block (see
@@ -16,7 +16,7 @@ import type { Code, Root } from 'mdast';
16
16
  * ` ```lua {src=scripts/etl.lua name=stars}` ` with an empty body"); `code`
17
17
  * is still the fence's own body (empty for a `src=` reference).
18
18
  *
19
- * `publish`, when present, is always `true` — DESIGN.md §8: "**Publishing is
19
+ * `publish`, when present, is always `true` — docs/scripting.md: "**Publishing is
20
20
  * declarative**: the bare `publish` attribute on the script fence, no API to
21
21
  * call." "Bare" is load-bearing: it is set ONLY when the fence spells the
22
22
  * key with no `=value` at all (` ```lua {name=gh publish}` `, per
@@ -47,7 +47,7 @@ export interface ScriptBlock {
47
47
  position?: Code['position'];
48
48
  }
49
49
  /**
50
- * Whether `name` is a legal script `name` (DESIGN.md §8). Dots are
50
+ * Whether `name` is a legal script `name` (docs/scripting.md). Dots are
51
51
  * deliberately excluded from the charset — `data=`/`:value[]` read a dot in
52
52
  * a name as path traversal (`repo.stars` means "field `stars` of value
53
53
  * `repo`"), which would make a dotted *script* name unreachable from either
@@ -76,7 +76,7 @@ export declare function parseMetaAttributes(meta: string | null | undefined): Re
76
76
  * Whether `key` appears in `meta`'s `{...}` attribute group written
77
77
  * genuinely bare (`key` with no `=value` at all — none of the three value
78
78
  * alternatives in `ATTRIBUTE_TOKEN` matched). This is the ONE shared
79
- * mechanism for a bare-only boolean fence-meta attribute (DESIGN.md §8:
79
+ * mechanism for a bare-only boolean fence-meta attribute (docs/scripting.md:
80
80
  * "Boolean fence-meta attributes (`publish`, the reference renderer's
81
81
  * `open`) are bare-only: writing any value — `publish=true` no less than
82
82
  * `publish=false` — is not the boolean form and counts as absent. Fail
@@ -105,7 +105,7 @@ export declare function isBareAttribute(meta: string | null | undefined, key: st
105
105
  * Walks a parsed mdast `Root` and returns every script block, in document
106
106
  * order. A `code` node is a script iff its `meta` carries a `{...}`
107
107
  * attribute group with a non-empty `name` that also matches the script-name
108
- * charset (DESIGN.md §8, `isValidScriptName`); a code block with no meta,
108
+ * charset (docs/scripting.md, `isValidScriptName`); a code block with no meta,
109
109
  * meta with no `name`, or a `name` outside the charset (e.g. a dotted
110
110
  * `repo.stars`) is ordinary code and is skipped — same display-only
111
111
  * degradation either way, never an error and never a console message. Pure
package/dist/scripts.js CHANGED
@@ -33,7 +33,7 @@ function findAttributeGroup(source) {
33
33
  */
34
34
  const ATTRIBUTE_TOKEN = /([A-Za-z_][\w-]*)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
35
35
  /**
36
- * The full charset a script `name` may use (DESIGN.md §8). Deliberately NOT
36
+ * The full charset a script `name` may use (docs/scripting.md). Deliberately NOT
37
37
  * `g`-flagged: a `g`-flagged `RegExp` carries `lastIndex` state across
38
38
  * `.test()` calls (the exec-loop cursor from a previous match survives to
39
39
  * the next call on the same instance), which silently makes alternating
@@ -44,7 +44,7 @@ const ATTRIBUTE_TOKEN = /([A-Za-z_][\w-]*)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
44
44
  */
45
45
  const SCRIPT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
46
46
  /**
47
- * Whether `name` is a legal script `name` (DESIGN.md §8). Dots are
47
+ * Whether `name` is a legal script `name` (docs/scripting.md). Dots are
48
48
  * deliberately excluded from the charset — `data=`/`:value[]` read a dot in
49
49
  * a name as path traversal (`repo.stars` means "field `stars` of value
50
50
  * `repo`"), which would make a dotted *script* name unreachable from either
@@ -91,7 +91,7 @@ export function parseMetaAttributes(meta) {
91
91
  * Whether `key` appears in `meta`'s `{...}` attribute group written
92
92
  * genuinely bare (`key` with no `=value` at all — none of the three value
93
93
  * alternatives in `ATTRIBUTE_TOKEN` matched). This is the ONE shared
94
- * mechanism for a bare-only boolean fence-meta attribute (DESIGN.md §8:
94
+ * mechanism for a bare-only boolean fence-meta attribute (docs/scripting.md:
95
95
  * "Boolean fence-meta attributes (`publish`, the reference renderer's
96
96
  * `open`) are bare-only: writing any value — `publish=true` no less than
97
97
  * `publish=false` — is not the boolean form and counts as absent. Fail
@@ -138,7 +138,7 @@ export function isBareAttribute(meta, key) {
138
138
  * Walks a parsed mdast `Root` and returns every script block, in document
139
139
  * order. A `code` node is a script iff its `meta` carries a `{...}`
140
140
  * attribute group with a non-empty `name` that also matches the script-name
141
- * charset (DESIGN.md §8, `isValidScriptName`); a code block with no meta,
141
+ * charset (docs/scripting.md, `isValidScriptName`); a code block with no meta,
142
142
  * meta with no `name`, or a `name` outside the charset (e.g. a dotted
143
143
  * `repo.stars`) is ordinary code and is skipped — same display-only
144
144
  * degradation either way, never an error and never a console message. Pure
package/dist/to-hast.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { unified } from 'unified';
2
2
  import remarkParse from 'remark-parse';
3
3
  import remarkDirective from 'remark-directive';
4
+ import remarkFrontmatter from 'remark-frontmatter';
4
5
  import remarkGfm from 'remark-gfm';
5
6
  import remarkRehype from 'remark-rehype';
6
7
  import { visit } from 'unist-util-visit';
@@ -156,10 +157,21 @@ function sanitizeUrls(tree) {
156
157
  * immutable-processor model (`.use()` returns a new processor) and keeps
157
158
  * each call's `.parse`/`.runSync` free of any state left over from a
158
159
  * previous call.
160
+ *
161
+ * The plugin list mirrors `parse.ts` exactly (frontmatter, directives, GFM),
162
+ * so what `parse` accepts and what this pipeline renders can never disagree
163
+ * about the grammar. Frontmatter needs no tagging step of its own: a leading
164
+ * `---` block becomes an mdast `yaml` node, and `mdast-util-to-hast`'s
165
+ * default handler for `yaml` is its `ignore` handler, so the block is
166
+ * DROPPED from the hast tree rather than leaking into the rendered document
167
+ * as text (pinned by `to-hast.test.ts`, since that drop is a rendering
168
+ * guarantee of the format, not an incidental upstream default). Metadata is
169
+ * read from the AST via `frontmatter.ts`, never rendered.
159
170
  */
160
171
  function createProcessor() {
161
172
  return unified()
162
173
  .use(remarkParse)
174
+ .use(remarkFrontmatter)
163
175
  .use(remarkDirective)
164
176
  .use(remarkGfm)
165
177
  .use(tagDirectiveNodes)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Framework-agnostic reference parser for Mark (.mk.md): CommonMark + directives to mdast, and mdast to a sanitized hast tree. Zero React dependency.",
5
5
  "keywords": [
6
6
  "markdown",
@@ -55,6 +55,7 @@
55
55
  "@types/mdast": "^4.0.4",
56
56
  "mdast-util-directive": "^3.1.0",
57
57
  "remark-directive": "^4.0.0",
58
+ "remark-frontmatter": "^5.0.0",
58
59
  "remark-gfm": "^4.0.1",
59
60
  "remark-parse": "^11.0.0",
60
61
  "remark-rehype": "^11.1.2",