@ox-content/unplugin 2.9.0 → 2.11.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/src.mjs CHANGED
@@ -1,48 +1,824 @@
1
+ import { createRequire } from "node:module";
1
2
  import { createUnplugin } from "unplugin";
2
3
  import { createFilter } from "@rollup/pluginutils";
4
+ import MarkdownIt from "markdown-it";
5
+ import rehypeParse from "rehype-parse";
6
+ import rehypeRemark from "rehype-remark";
7
+ import rehypeStringify from "rehype-stringify";
8
+ import remarkParse from "remark-parse";
9
+ import remarkRehype from "remark-rehype";
10
+ import { unified } from "unified";
11
+ const utf8Decoder = new TextDecoder("utf-8");
12
+ function parseTransferEnvelope(buffer) {
13
+ if (buffer.byteLength < 24) return null;
14
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
15
+ if (view.getUint32(0, true) !== 1381259343) return null;
16
+ if (view.getUint16(4, true) !== 1) throw new Error("[ox-content] Unsupported transfer buffer version.");
17
+ const kind = view.getUint16(6, true);
18
+ const payloadVersion = view.getUint32(8, true);
19
+ const sectionCount = view.getUint32(12, true);
20
+ const rootHandle = view.getUint32(16, true);
21
+ if (24 + sectionCount * 12 > buffer.byteLength) throw new Error("[ox-content] Transfer buffer section table is truncated.");
22
+ const sections = /* @__PURE__ */ new Map();
23
+ for (let index = 0; index < sectionCount; index += 1) {
24
+ const base = 24 + index * 12;
25
+ const id = view.getUint32(base, true);
26
+ const offset = view.getUint32(base + 4, true);
27
+ const len = view.getUint32(base + 8, true);
28
+ if (offset + len > buffer.byteLength) throw new Error("[ox-content] Transfer buffer references an invalid section.");
29
+ sections.set(id, {
30
+ id,
31
+ offset,
32
+ len
33
+ });
34
+ }
35
+ return {
36
+ kind,
37
+ payloadVersion,
38
+ rootHandle,
39
+ sections
40
+ };
41
+ }
42
+ function requireTransferSection(envelope, id, message) {
43
+ const section = envelope.sections.get(id);
44
+ if (!section) throw new Error(message);
45
+ return section;
46
+ }
47
+ function sliceTransferSection(buffer, section) {
48
+ return buffer.subarray(section.offset, section.offset + section.len);
49
+ }
50
+ function decodeTransferSectionUtf8(buffer, envelope, id, message) {
51
+ return utf8Decoder.decode(sliceTransferSection(buffer, requireTransferSection(envelope, id, message)));
52
+ }
53
+ //#endregion
54
+ //#region src/mdast-raw.ts
55
+ const LEGACY_MAGIC = 827475021;
56
+ const LEGACY_VERSION = 1;
57
+ const LEGACY_HEADER_LEN = 28;
58
+ const NODE_RECORD_LEN = 60;
59
+ const NONE_U32 = 4294967295;
60
+ const MDAST_PAYLOAD_VERSION = 1;
61
+ const MDAST_SECTION_NODES = 1;
62
+ const MDAST_SECTION_CHILD_INDICES = 2;
63
+ const MDAST_SECTION_ALIGNS = 3;
64
+ const MDAST_SECTION_STRINGS = 4;
65
+ const MDAST_SECTION_SOURCE_ORIGIN$1 = 7;
66
+ const KIND_ROOT = 0;
67
+ const KIND_PARAGRAPH = 1;
68
+ const KIND_HEADING = 2;
69
+ const KIND_THEMATIC_BREAK = 3;
70
+ const KIND_BLOCKQUOTE = 4;
71
+ const KIND_LIST = 5;
72
+ const KIND_LIST_ITEM = 6;
73
+ const KIND_CODE = 7;
74
+ const KIND_HTML = 8;
75
+ const KIND_TABLE = 9;
76
+ const KIND_TABLE_ROW = 10;
77
+ const KIND_TABLE_CELL = 11;
78
+ const KIND_TEXT = 12;
79
+ const KIND_EMPHASIS = 13;
80
+ const KIND_STRONG = 14;
81
+ const KIND_INLINE_CODE = 15;
82
+ const KIND_BREAK = 16;
83
+ const KIND_LINK = 17;
84
+ const KIND_IMAGE = 18;
85
+ const KIND_DELETE = 19;
86
+ const KIND_FOOTNOTE_REFERENCE = 20;
87
+ const KIND_DEFINITION = 21;
88
+ const KIND_FOOTNOTE_DEFINITION = 22;
89
+ const FLAG_ORDERED = 1;
90
+ const FLAG_SPREAD = 2;
91
+ const FLAG_CHECKED_PRESENT = 4;
92
+ const FLAG_CHECKED_VALUE = 8;
93
+ function deserializeMdastFromRaw(buffer, source) {
94
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
95
+ const decoder = new TextDecoder("utf-8");
96
+ const { nodeCount, childCount, alignCount, rootIndex, nodesOffset, childIndicesOffset, alignsOffset, stringsOffset, stringsEnd } = resolveMdastLayout(buffer, view);
97
+ if (stringsEnd > buffer.byteLength) throw new Error("[ox-content] mdast raw transfer buffer is truncated.");
98
+ const sourceIndex = buildSourceIndex(source);
99
+ const sourceOrigin = readSourceOrigin(buffer, view);
100
+ const readString = (offset, len) => {
101
+ if (offset === NONE_U32) return;
102
+ const start = stringsOffset + offset;
103
+ const end = start + len;
104
+ if (end > stringsEnd) throw new Error("[ox-content] mdast raw transfer buffer references an invalid string.");
105
+ return decoder.decode(buffer.subarray(start, end));
106
+ };
107
+ const readPosition = (startOffset, endOffset) => {
108
+ const start = pointForByteOffset(startOffset, sourceIndex);
109
+ const end = pointForByteOffset(endOffset, sourceIndex);
110
+ if (!sourceOrigin) return {
111
+ start,
112
+ end
113
+ };
114
+ return {
115
+ start: applySourceOrigin(start, sourceOrigin),
116
+ end: applySourceOrigin(end, sourceOrigin)
117
+ };
118
+ };
119
+ const readChildIndex = (index) => {
120
+ if (index >= childCount) throw new Error("[ox-content] mdast raw transfer buffer references an invalid child index.");
121
+ return view.getUint32(childIndicesOffset + index * 4, true);
122
+ };
123
+ const readChildren = (start, len) => {
124
+ if (start + len > childCount) throw new Error("[ox-content] mdast raw transfer buffer has an invalid child range.");
125
+ const children = [];
126
+ for (let index = 0; index < len; index += 1) children.push(readNode(readChildIndex(start + index)));
127
+ return children;
128
+ };
129
+ const readAlign = (start, len) => {
130
+ if (start + len > alignCount) throw new Error("[ox-content] mdast raw transfer buffer has an invalid table alignment range.");
131
+ const align = [];
132
+ for (let index = 0; index < len; index += 1) {
133
+ const value = buffer[alignsOffset + start + index];
134
+ align.push(value === 1 ? "left" : value === 2 ? "center" : value === 3 ? "right" : null);
135
+ }
136
+ return align;
137
+ };
138
+ const readNode = (index) => {
139
+ if (index >= nodeCount) throw new Error("[ox-content] mdast raw transfer buffer references an invalid node.");
140
+ const base = nodesOffset + index * NODE_RECORD_LEN;
141
+ const kind = view.getUint8(base);
142
+ const flags = view.getUint8(base + 1);
143
+ const spanStart = view.getUint32(base + 4, true);
144
+ const spanEnd = view.getUint32(base + 8, true);
145
+ const childStart = view.getUint32(base + 12, true);
146
+ const childLen = view.getUint32(base + 16, true);
147
+ const num0 = view.getUint32(base + 20, true);
148
+ const num1 = view.getUint32(base + 24, true);
149
+ const str0 = readString(view.getUint32(base + 28, true), view.getUint32(base + 32, true));
150
+ const str1 = readString(view.getUint32(base + 36, true), view.getUint32(base + 40, true));
151
+ const str2 = readString(view.getUint32(base + 44, true), view.getUint32(base + 48, true));
152
+ const str3 = readString(view.getUint32(base + 52, true), view.getUint32(base + 56, true));
153
+ const children = childLen > 0 ? readChildren(childStart, childLen) : void 0;
154
+ const position = readPosition(spanStart, spanEnd);
155
+ switch (kind) {
156
+ case KIND_ROOT: return {
157
+ type: "root",
158
+ children: children ?? [],
159
+ position
160
+ };
161
+ case KIND_PARAGRAPH: return {
162
+ type: "paragraph",
163
+ children: children ?? [],
164
+ position
165
+ };
166
+ case KIND_HEADING: return {
167
+ type: "heading",
168
+ depth: num0,
169
+ children: children ?? [],
170
+ position
171
+ };
172
+ case KIND_THEMATIC_BREAK: return {
173
+ type: "thematicBreak",
174
+ position
175
+ };
176
+ case KIND_BLOCKQUOTE: return {
177
+ type: "blockquote",
178
+ children: children ?? [],
179
+ position
180
+ };
181
+ case KIND_LIST: {
182
+ const node = {
183
+ type: "list",
184
+ ordered: (flags & FLAG_ORDERED) !== 0,
185
+ spread: (flags & FLAG_SPREAD) !== 0,
186
+ children: children ?? [],
187
+ position
188
+ };
189
+ if (num0 !== NONE_U32) node.start = num0;
190
+ return node;
191
+ }
192
+ case KIND_LIST_ITEM: {
193
+ const node = {
194
+ type: "listItem",
195
+ spread: (flags & FLAG_SPREAD) !== 0,
196
+ children: children ?? [],
197
+ position
198
+ };
199
+ if ((flags & FLAG_CHECKED_PRESENT) !== 0) node.checked = (flags & FLAG_CHECKED_VALUE) !== 0;
200
+ return node;
201
+ }
202
+ case KIND_CODE: {
203
+ const node = {
204
+ type: "code",
205
+ value: str2 ?? "",
206
+ position
207
+ };
208
+ if (str0 !== void 0) node.lang = str0;
209
+ if (str1 !== void 0) node.meta = str1;
210
+ return node;
211
+ }
212
+ case KIND_HTML: return {
213
+ type: "html",
214
+ value: str0 ?? "",
215
+ position
216
+ };
217
+ case KIND_TABLE: return {
218
+ type: "table",
219
+ align: readAlign(num0, num1),
220
+ children: children ?? [],
221
+ position
222
+ };
223
+ case KIND_TABLE_ROW: return {
224
+ type: "tableRow",
225
+ children: children ?? [],
226
+ position
227
+ };
228
+ case KIND_TABLE_CELL: return {
229
+ type: "tableCell",
230
+ children: children ?? [],
231
+ position
232
+ };
233
+ case KIND_TEXT: return {
234
+ type: "text",
235
+ value: str0 ?? "",
236
+ position
237
+ };
238
+ case KIND_EMPHASIS: return {
239
+ type: "emphasis",
240
+ children: children ?? [],
241
+ position
242
+ };
243
+ case KIND_STRONG: return {
244
+ type: "strong",
245
+ children: children ?? [],
246
+ position
247
+ };
248
+ case KIND_INLINE_CODE: return {
249
+ type: "inlineCode",
250
+ value: str0 ?? "",
251
+ position
252
+ };
253
+ case KIND_BREAK: return {
254
+ type: "break",
255
+ position
256
+ };
257
+ case KIND_LINK: {
258
+ const node = {
259
+ type: "link",
260
+ url: str0 ?? "",
261
+ children: children ?? [],
262
+ position
263
+ };
264
+ if (str1 !== void 0) node.title = str1;
265
+ return node;
266
+ }
267
+ case KIND_IMAGE: {
268
+ const node = {
269
+ type: "image",
270
+ url: str0 ?? "",
271
+ position
272
+ };
273
+ if (str1 !== void 0) node.alt = str1;
274
+ if (str2 !== void 0) node.title = str2;
275
+ return node;
276
+ }
277
+ case KIND_DELETE: return {
278
+ type: "delete",
279
+ children: children ?? [],
280
+ position
281
+ };
282
+ case KIND_FOOTNOTE_REFERENCE: {
283
+ const node = {
284
+ type: "footnoteReference",
285
+ identifier: str0 ?? "",
286
+ position
287
+ };
288
+ if (str1 !== void 0) node.label = str1;
289
+ return node;
290
+ }
291
+ case KIND_DEFINITION: {
292
+ const node = {
293
+ type: "definition",
294
+ identifier: str0 ?? "",
295
+ url: str2 ?? "",
296
+ position
297
+ };
298
+ if (str1 !== void 0) node.label = str1;
299
+ if (str3 !== void 0) node.title = str3;
300
+ return node;
301
+ }
302
+ case KIND_FOOTNOTE_DEFINITION: {
303
+ const node = {
304
+ type: "footnoteDefinition",
305
+ identifier: str0 ?? "",
306
+ children: children ?? [],
307
+ position
308
+ };
309
+ if (str1 !== void 0) node.label = str1;
310
+ return node;
311
+ }
312
+ default: throw new Error(`[ox-content] Unsupported mdast raw node kind: ${kind}`);
313
+ }
314
+ };
315
+ const root = readNode(rootIndex);
316
+ if (root.type !== "root" || !Array.isArray(root.children)) throw new Error("[ox-content] Native parser returned an invalid mdast root.");
317
+ return root;
318
+ }
319
+ function readSourceOrigin(buffer, view) {
320
+ const section = parseTransferEnvelope(buffer)?.sections.get(MDAST_SECTION_SOURCE_ORIGIN$1);
321
+ if (!section) return;
322
+ if (section.len !== 16) throw new Error("[ox-content] mdast transfer source origin section is misaligned.");
323
+ return {
324
+ byteOffset: view.getUint32(section.offset, true),
325
+ offset: view.getUint32(section.offset + 4, true),
326
+ line: view.getUint32(section.offset + 8, true),
327
+ column: view.getUint32(section.offset + 12, true)
328
+ };
329
+ }
330
+ function resolveMdastLayout(buffer, view) {
331
+ const envelope = parseTransferEnvelope(buffer);
332
+ if (envelope) {
333
+ if (envelope.kind !== 1) throw new Error("[ox-content] Transfer buffer does not contain an mdast payload.");
334
+ if (envelope.payloadVersion !== MDAST_PAYLOAD_VERSION) throw new Error("[ox-content] Unsupported mdast transfer payload version.");
335
+ const nodesSection = requireTransferSection(envelope, MDAST_SECTION_NODES, "[ox-content] mdast transfer is missing the nodes section.");
336
+ const childIndicesSection = requireTransferSection(envelope, MDAST_SECTION_CHILD_INDICES, "[ox-content] mdast transfer is missing the child indices section.");
337
+ const alignsSection = requireTransferSection(envelope, MDAST_SECTION_ALIGNS, "[ox-content] mdast transfer is missing the table alignments section.");
338
+ const stringsSection = requireTransferSection(envelope, MDAST_SECTION_STRINGS, "[ox-content] mdast transfer is missing the string table section.");
339
+ if (nodesSection.len % NODE_RECORD_LEN !== 0) throw new Error("[ox-content] mdast transfer nodes section is misaligned.");
340
+ if (childIndicesSection.len % 4 !== 0) throw new Error("[ox-content] mdast transfer child indices section is misaligned.");
341
+ return {
342
+ nodeCount: nodesSection.len / NODE_RECORD_LEN,
343
+ childCount: childIndicesSection.len / 4,
344
+ alignCount: alignsSection.len,
345
+ rootIndex: envelope.rootHandle,
346
+ nodesOffset: nodesSection.offset,
347
+ childIndicesOffset: childIndicesSection.offset,
348
+ alignsOffset: alignsSection.offset,
349
+ stringsOffset: stringsSection.offset,
350
+ stringsEnd: stringsSection.offset + stringsSection.len
351
+ };
352
+ }
353
+ const magic = view.getUint32(0, true);
354
+ const version = view.getUint32(4, true);
355
+ if (magic !== LEGACY_MAGIC || version !== LEGACY_VERSION) throw new Error("[ox-content] Invalid mdast raw transfer buffer.");
356
+ const nodeCount = view.getUint32(8, true);
357
+ const childCount = view.getUint32(12, true);
358
+ const alignCount = view.getUint32(16, true);
359
+ const stringBytesLen = view.getUint32(20, true);
360
+ const rootIndex = view.getUint32(24, true);
361
+ const nodesOffset = LEGACY_HEADER_LEN;
362
+ const childIndicesOffset = nodesOffset + nodeCount * NODE_RECORD_LEN;
363
+ const alignsOffset = childIndicesOffset + childCount * 4;
364
+ const stringsOffset = alignsOffset + alignCount;
365
+ return {
366
+ nodeCount,
367
+ childCount,
368
+ alignCount,
369
+ rootIndex,
370
+ nodesOffset,
371
+ childIndicesOffset,
372
+ alignsOffset,
373
+ stringsOffset,
374
+ stringsEnd: stringsOffset + stringBytesLen
375
+ };
376
+ }
377
+ function buildSourceIndex(source) {
378
+ const points = [{
379
+ byteOffset: 0,
380
+ offset: 0,
381
+ line: 1,
382
+ column: 1
383
+ }];
384
+ let byteOffset = 0;
385
+ let offset = 0;
386
+ let line = 1;
387
+ let column = 1;
388
+ for (const character of source) {
389
+ byteOffset += utf8ByteLength(character);
390
+ offset += character.length;
391
+ if (character === "\n") {
392
+ line += 1;
393
+ column = 1;
394
+ } else column += 1;
395
+ points.push({
396
+ byteOffset,
397
+ offset,
398
+ line,
399
+ column
400
+ });
401
+ }
402
+ return points;
403
+ }
404
+ function pointForByteOffset(byteOffset, points) {
405
+ const maxByteOffset = points[points.length - 1]?.byteOffset ?? 0;
406
+ const clampedOffset = Math.max(0, Math.min(byteOffset, maxByteOffset));
407
+ let low = 0;
408
+ let high = points.length - 1;
409
+ while (low <= high) {
410
+ const middle = low + high >> 1;
411
+ const point = points[middle];
412
+ const nextPoint = points[middle + 1];
413
+ if (clampedOffset < point.byteOffset) high = middle - 1;
414
+ else if (nextPoint && clampedOffset >= nextPoint.byteOffset) low = middle + 1;
415
+ else return {
416
+ line: point.line,
417
+ column: point.column,
418
+ offset: point.offset
419
+ };
420
+ }
421
+ const point = points[points.length - 1] ?? {
422
+ byteOffset: 0,
423
+ offset: 0,
424
+ line: 1,
425
+ column: 1
426
+ };
427
+ return {
428
+ line: point.line,
429
+ column: point.column,
430
+ offset: point.offset
431
+ };
432
+ }
433
+ function applySourceOrigin(point, origin) {
434
+ return {
435
+ line: point.line + origin.line - 1,
436
+ column: point.line === 1 ? point.column + origin.column - 1 : point.column,
437
+ offset: point.offset + origin.offset
438
+ };
439
+ }
440
+ function utf8ByteLength(character) {
441
+ const codePoint = character.codePointAt(0);
442
+ if (codePoint === void 0) return 0;
443
+ if (codePoint <= 127) return 1;
444
+ if (codePoint <= 2047) return 2;
445
+ if (codePoint <= 65535) return 3;
446
+ return 4;
447
+ }
448
+ //#endregion
449
+ //#region src/mdast.ts
450
+ const require$1 = createRequire(import.meta.url);
451
+ let cachedNapiBindings$1;
452
+ const DEFAULT_MDAST_OPTIONS = {
453
+ gfm: true,
454
+ footnotes: true,
455
+ taskLists: true,
456
+ tables: true,
457
+ strikethrough: true,
458
+ autolinks: true
459
+ };
460
+ /**
461
+ * Unified parser plugin backed by Ox Content's native mdast parser.
462
+ *
463
+ * This lets existing remark/unified plugins run on top of Ox Content's parser
464
+ * while keeping a more mdast-native integration point.
465
+ */
466
+ function oxContentMdast(options = {}) {
467
+ this.parser = (document) => parseMarkdownToMdast(document, options);
468
+ }
469
+ /**
470
+ * Defines an Ox Content-native mdast plugin.
471
+ */
472
+ function defineMdastPlugin(name, transform) {
473
+ return {
474
+ name,
475
+ transform
476
+ };
477
+ }
478
+ /**
479
+ * Parses markdown source into an mdast-compatible tree using the native parser.
480
+ */
481
+ function parseMarkdownToMdast(source, options = {}) {
482
+ const napi = loadNapiBindings$1();
483
+ const resolvedOptions = resolveMdastOptions(options);
484
+ const parserOptions = {
485
+ gfm: resolvedOptions.gfm,
486
+ footnotes: resolvedOptions.footnotes,
487
+ taskLists: resolvedOptions.taskLists,
488
+ tables: resolvedOptions.tables,
489
+ strikethrough: resolvedOptions.strikethrough,
490
+ autolinks: resolvedOptions.autolinks
491
+ };
492
+ return deserializeMdastFromRaw(requireNapiMethod$1(napi.parseTransferRaw, "parseTransferRaw")(source, "mdast", parserOptions), source);
493
+ }
494
+ /**
495
+ * Wraps an Ox Content-native mdast plugin as a standard unified transformer.
496
+ */
497
+ function toUnifiedMdastPlugin(plugin, context) {
498
+ return () => {
499
+ return async (tree) => {
500
+ return await plugin.transform(tree, context) ?? tree;
501
+ };
502
+ };
503
+ }
504
+ /**
505
+ * Extracts a nested TOC tree from mdast after mdast-stage plugins have run.
506
+ */
507
+ function extractTocFromMdast(root, maxDepth) {
508
+ const headings = [];
509
+ visitMdast(root, (node) => {
510
+ if (node.type !== "heading") return;
511
+ const depth = typeof node.depth === "number" ? node.depth : 0;
512
+ if (depth < 1 || depth > maxDepth) return;
513
+ const text = collectMdastText(node);
514
+ headings.push({
515
+ depth,
516
+ text,
517
+ slug: slugify$1(text),
518
+ children: []
519
+ });
520
+ });
521
+ return buildTocTree$1(headings);
522
+ }
523
+ /**
524
+ * Creates the context passed to Ox Content-native mdast plugins.
525
+ */
526
+ function createMdastPluginContext(filePath, source, frontmatter, options, sourceOffset) {
527
+ return {
528
+ filePath,
529
+ source,
530
+ frontmatter,
531
+ sourceOffset,
532
+ options
533
+ };
534
+ }
535
+ function loadNapiBindings$1() {
536
+ if (cachedNapiBindings$1) return cachedNapiBindings$1;
537
+ if (cachedNapiBindings$1 === null) throw new Error("[ox-content] Failed to load @ox-content/napi. Please ensure the NAPI module is built. Run: mise run build:napi");
538
+ try {
539
+ cachedNapiBindings$1 = require$1("@ox-content/napi");
540
+ return cachedNapiBindings$1;
541
+ } catch {
542
+ cachedNapiBindings$1 = null;
543
+ throw new Error("[ox-content] Failed to load @ox-content/napi. Please ensure the NAPI module is built. Run: mise run build:napi");
544
+ }
545
+ }
546
+ function requireNapiMethod$1(method, name) {
547
+ if (typeof method === "function") return method;
548
+ throw new Error(`[ox-content] @ox-content/napi is too old for mdast interop: missing ${name}(). Rebuild the NAPI module with \`vp run build:napi\`.`);
549
+ }
550
+ function resolveMdastOptions(options) {
551
+ return {
552
+ gfm: options.gfm ?? DEFAULT_MDAST_OPTIONS.gfm,
553
+ footnotes: options.footnotes ?? DEFAULT_MDAST_OPTIONS.footnotes,
554
+ taskLists: options.taskLists ?? DEFAULT_MDAST_OPTIONS.taskLists,
555
+ tables: options.tables ?? DEFAULT_MDAST_OPTIONS.tables,
556
+ strikethrough: options.strikethrough ?? DEFAULT_MDAST_OPTIONS.strikethrough,
557
+ autolinks: options.autolinks ?? DEFAULT_MDAST_OPTIONS.autolinks
558
+ };
559
+ }
560
+ function buildTocTree$1(entries) {
561
+ const result = [];
562
+ const stack = [];
563
+ for (const entry of entries) {
564
+ const tocEntry = {
565
+ depth: entry.depth,
566
+ text: entry.text,
567
+ slug: entry.slug,
568
+ children: []
569
+ };
570
+ while (stack.length > 0 && stack[stack.length - 1].depth >= entry.depth) stack.pop();
571
+ if (stack.length === 0) result.push(tocEntry);
572
+ else stack[stack.length - 1].children.push(tocEntry);
573
+ stack.push(tocEntry);
574
+ }
575
+ return result;
576
+ }
577
+ function visitMdast(node, visitor) {
578
+ if (!node || typeof node !== "object") return;
579
+ if ("type" in node && typeof node.type === "string") visitor(node);
580
+ if (!Array.isArray(node.children)) return;
581
+ for (const child of node.children) visitMdast(child, visitor);
582
+ }
583
+ function collectMdastText(node) {
584
+ if (!node || typeof node !== "object") return "";
585
+ if (typeof node.value === "string") return node.value;
586
+ if (!Array.isArray(node.children)) return "";
587
+ return node.children.map((child) => collectMdastText(child)).join("");
588
+ }
589
+ function slugify$1(text) {
590
+ return text.toLowerCase().replace(/[^\p{L}\p{N}\s-]+/gu, " ").trim().split(/\s+/).filter(Boolean).join("-");
591
+ }
592
+ //#endregion
3
593
  //#region src/transform.ts
4
594
  /**
595
+ * Markdown transformation logic for @ox-content/unplugin.
596
+ *
597
+ * Uses Rust-based parser via NAPI bindings for optimal performance.
598
+ */
599
+ const require = createRequire(import.meta.url);
600
+ let cachedNapiBindings;
601
+ const MDAST_SECTION_CONTENT = 5;
602
+ const MDAST_SECTION_FRONTMATTER = 6;
603
+ const MDAST_SECTION_SOURCE_ORIGIN = 7;
604
+ const PREPARED_SOURCE_SECTION_CONTENT = 1;
605
+ const PREPARED_SOURCE_SECTION_FRONTMATTER = 2;
606
+ const PREPARED_SOURCE_SECTION_SOURCE_ORIGIN = 3;
607
+ /**
5
608
  * Transforms Markdown content into a JavaScript module.
6
- * Uses Rust-based renderer via NAPI bindings for optimal performance.
609
+ * Uses Rust-based parsing, and switches to a unified/mdast pipeline when
610
+ * JavaScript mdast/remark/rehype plugins are configured.
7
611
  *
8
612
  * Note: This requires the @ox-content/napi package to be built.
9
- * NAPI bindings are loaded dynamically to support both Node.js and build-time execution.
10
613
  */
11
614
  async function transformMarkdown(source, filePath, options) {
12
- let napi;
615
+ const transformed = hasMarkdownItPlugins(options) ? await transformWithMarkdownIt(source, filePath, options) : hasUnifiedPlugins(options) ? await transformWithUnified(source, filePath, options) : transformWithNativePipeline(loadNapiBindings(), source, options);
616
+ let nextHtml = transformed.html;
617
+ for (const plugin of options.plugin.oxContent) nextHtml = await plugin(nextHtml);
618
+ return {
619
+ code: generateModuleCode(nextHtml, transformed.frontmatter, transformed.toc, filePath),
620
+ html: nextHtml,
621
+ frontmatter: transformed.frontmatter,
622
+ toc: transformed.toc
623
+ };
624
+ }
625
+ function loadNapiBindings() {
626
+ if (cachedNapiBindings) return cachedNapiBindings;
627
+ if (cachedNapiBindings === null) throw new Error("[ox-content] Failed to load @ox-content/napi. Please ensure the NAPI module is built. Run: nix develop -c vp run build:napi");
13
628
  try {
14
- napi = await import("@ox-content/napi");
629
+ cachedNapiBindings = require("@ox-content/napi");
630
+ return cachedNapiBindings;
15
631
  } catch {
16
- throw new Error("[ox-content] Failed to load @ox-content/napi. Please ensure the NAPI module is built. Run: nix develop -c vp run build:napi");
632
+ cachedNapiBindings = null;
633
+ throw new Error("[ox-content] Failed to load @ox-content/napi. Please ensure the NAPI module is built. Run: mise run build:napi");
17
634
  }
18
- const result = napi.transform(source, {
635
+ }
636
+ function createNapiTransformOptions(options) {
637
+ const codeAnnotations = options.codeAnnotations ?? {
638
+ enabled: false,
639
+ metaKey: "annotate"
640
+ };
641
+ return {
19
642
  gfm: options.gfm,
20
643
  footnotes: options.footnotes,
21
- task_lists: options.taskLists,
644
+ taskLists: options.taskLists,
22
645
  tables: options.tables,
23
646
  strikethrough: options.strikethrough,
24
- toc_max_depth: options.tocMaxDepth,
25
- code_annotations: options.codeAnnotations.enabled,
26
- code_annotation_meta_key: options.codeAnnotations.metaKey
27
- });
28
- if (result.errors.length > 0) console.warn("[ox-content] Transform warnings:", result.errors);
29
- let frontmatter;
647
+ autolinks: options.gfm,
648
+ frontmatter: options.frontmatter,
649
+ tocMaxDepth: options.tocMaxDepth,
650
+ codeAnnotations: codeAnnotations.enabled,
651
+ codeAnnotationMetaKey: codeAnnotations.metaKey
652
+ };
653
+ }
654
+ function parseFrontmatterJson(json) {
655
+ if (json.length === 0) return {};
30
656
  try {
31
- frontmatter = JSON.parse(result.frontmatter);
657
+ const value = JSON.parse(json);
658
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
32
659
  } catch {
33
- frontmatter = {};
660
+ return {};
34
661
  }
662
+ }
663
+ function prepareSourceWithRust(napi, source, options) {
664
+ return deserializePreparedSource(requireNapiMethod(napi.prepareSourceRaw, "prepareSourceRaw")(source, { frontmatter: options.frontmatter }));
665
+ }
666
+ function hasUnifiedPlugins(options) {
667
+ return options.plugin.mdast.length > 0 || options.plugin.remark.length > 0 || options.plugin.rehype.length > 0;
668
+ }
669
+ function hasMarkdownItPlugins(options) {
670
+ return options.plugin.markdownIt.length > 0;
671
+ }
672
+ function hasMdastOrRemarkPlugins(options) {
673
+ return options.plugin.mdast.length > 0 || options.plugin.remark.length > 0;
674
+ }
675
+ function transformWithNativePipeline(napi, source, options) {
676
+ const result = napi.transform(source, createNapiTransformOptions(options));
677
+ if (result.errors.length > 0) console.warn("[ox-content] Transform warnings:", result.errors);
35
678
  const flatToc = result.toc.map((item) => ({
36
679
  ...item,
37
680
  children: []
38
681
  }));
39
- const toc = options.toc ? buildTocTree(flatToc) : [];
40
- let html = result.html;
41
- for (const plugin of options.plugin.oxContent) html = await plugin(html);
42
682
  return {
43
- code: generateModuleCode(html, frontmatter, toc, filePath),
683
+ html: result.html,
684
+ frontmatter: parseFrontmatterJson(result.frontmatter),
685
+ toc: options.toc ? buildTocTree(flatToc) : []
686
+ };
687
+ }
688
+ function transformWithNativeMdastPipeline(napi, source, options) {
689
+ return deserializeNativeMdastTransform(requireNapiMethod(napi.transformMdastRaw, "transformMdastRaw")(source, createNapiTransformOptions(options)));
690
+ }
691
+ function deserializeNativeMdastTransform(buffer) {
692
+ const envelope = parseTransferEnvelope(buffer);
693
+ if (!envelope) throw new Error("[ox-content] Native mdast transform buffer is not a transfer envelope.");
694
+ const content = decodeTransferSectionUtf8(buffer, envelope, MDAST_SECTION_CONTENT, "[ox-content] mdast transform transfer is missing the content section.");
695
+ const frontmatter = parseFrontmatterJson(decodeTransferSectionUtf8(buffer, envelope, MDAST_SECTION_FRONTMATTER, "[ox-content] mdast transform transfer is missing the frontmatter section."));
696
+ return {
697
+ tree: deserializeMdastFromRaw(buffer, content),
698
+ content,
699
+ frontmatter,
700
+ sourceOffset: readSourceOriginFromEnvelope(buffer, envelope, MDAST_SECTION_SOURCE_ORIGIN)
701
+ };
702
+ }
703
+ function deserializePreparedSource(buffer) {
704
+ const envelope = parseTransferEnvelope(buffer);
705
+ if (!envelope || envelope.kind !== 3) throw new Error("[ox-content] Prepared source buffer is not a transfer envelope.");
706
+ return {
707
+ content: decodeTransferSectionUtf8(buffer, envelope, PREPARED_SOURCE_SECTION_CONTENT, "[ox-content] prepared source transfer is missing the content section."),
708
+ frontmatter: parseFrontmatterJson(decodeTransferSectionUtf8(buffer, envelope, PREPARED_SOURCE_SECTION_FRONTMATTER, "[ox-content] prepared source transfer is missing the frontmatter section.")),
709
+ sourceOffset: readSourceOriginFromEnvelope(buffer, envelope, PREPARED_SOURCE_SECTION_SOURCE_ORIGIN)
710
+ };
711
+ }
712
+ async function transformWithMarkdownIt(fullSource, filePath, options) {
713
+ const { content: markdownContent, frontmatter, sourceOffset } = prepareSourceWithRust(loadNapiBindings(), fullSource, options);
714
+ const markdownIt = createMarkdownItRenderer(options);
715
+ applyMarkdownItPlugins(markdownIt, options.plugin.markdownIt);
716
+ const env = createMarkdownItEnv(filePath, fullSource, markdownContent, frontmatter, sourceOffset);
717
+ const tokens = markdownIt.parse(markdownContent, env);
718
+ const html = markdownIt.renderer.render(tokens, markdownIt.options, env);
719
+ const bridgeData = {
720
+ html,
721
+ tokens,
722
+ env
723
+ };
724
+ if (hasMdastOrRemarkPlugins(options)) return transformMarkdownItWithUnified(bridgeData, fullSource, markdownContent, filePath, frontmatter, options);
725
+ if (options.plugin.rehype.length > 0) return {
726
+ html: await transformHtmlWithRehype(html, filePath, fullSource, markdownContent, frontmatter, options, "markdown-it", bridgeData),
727
+ frontmatter,
728
+ toc: options.toc ? extractTocFromMarkdownItTokens(tokens, options.tocMaxDepth) : []
729
+ };
730
+ return {
44
731
  html,
45
732
  frontmatter,
733
+ toc: options.toc ? extractTocFromMarkdownItTokens(tokens, options.tocMaxDepth) : []
734
+ };
735
+ }
736
+ async function transformMarkdownItWithUnified(markdownIt, fullSource, markdownContent, filePath, frontmatter, options) {
737
+ const mdastContext = createMdastPluginContext(filePath, fullSource, frontmatter, options, markdownIt.env.oxContent.sourceOffset);
738
+ const { plugins: remarkPlugins, options: remarkRehypeOptions } = extractUnifiedPluginWithOptions(options.plugin.remark, remarkRehype);
739
+ const { plugins: rehypePlugins, options: rehypeStringifyOptions } = extractUnifiedPluginWithOptions(options.plugin.rehype, rehypeStringify);
740
+ const processor = unified();
741
+ processor.use(rehypeParse, { fragment: true });
742
+ processor.use(rehypeRemark);
743
+ applyUnifiedPlugins(processor, options.plugin.mdast.map((plugin) => isOxContentMdastPlugin(plugin) ? toUnifiedMdastPlugin(plugin, mdastContext) : plugin));
744
+ applyUnifiedPlugins(processor, remarkPlugins);
745
+ let toc = [];
746
+ processor.use(() => {
747
+ return (tree) => {
748
+ if (options.toc) toc = extractTocFromMdast(tree, options.tocMaxDepth);
749
+ return tree;
750
+ };
751
+ });
752
+ const frozenMdastProcessor = processor.freeze();
753
+ if (resolveUnifiedCompilerStrategy(frozenMdastProcessor) === "custom") {
754
+ const { processor: compiledProcessor, input } = processorFromMarkdownItProcessor(frozenMdastProcessor(), markdownIt, filePath, fullSource, markdownContent, frontmatter, "markdown-it");
755
+ return {
756
+ html: await processUnifiedFile(compiledProcessor, input),
757
+ frontmatter,
758
+ toc
759
+ };
760
+ }
761
+ const hastProcessor = frozenMdastProcessor();
762
+ hastProcessor.use(remarkRehype, {
763
+ allowDangerousHtml: true,
764
+ ...remarkRehypeOptions
765
+ });
766
+ applyUnifiedPlugins(hastProcessor, rehypePlugins);
767
+ const frozenHastProcessor = hastProcessor.freeze();
768
+ const finalProcessor = frozenHastProcessor();
769
+ if (resolveUnifiedCompilerStrategy(frozenHastProcessor) === "default") finalProcessor.use(rehypeStringify, {
770
+ allowDangerousHtml: true,
771
+ ...rehypeStringifyOptions
772
+ });
773
+ const { processor: compiledProcessor, input } = processorFromMarkdownItProcessor(finalProcessor, markdownIt, filePath, fullSource, markdownContent, frontmatter, "markdown-it");
774
+ return {
775
+ html: await processUnifiedFile(compiledProcessor, input),
776
+ frontmatter,
777
+ toc
778
+ };
779
+ }
780
+ async function transformWithUnified(fullSource, filePath, options) {
781
+ const { plugins: remarkPlugins, options: remarkRehypeOptions } = extractUnifiedPluginWithOptions(options.plugin.remark, remarkRehype);
782
+ const { plugins: rehypePlugins, options: rehypeStringifyOptions } = extractUnifiedPluginWithOptions(options.plugin.rehype, rehypeStringify);
783
+ const parserStrategy = detectUnifiedParserStrategy(fullSource, filePath, options, remarkPlugins);
784
+ const nativePayload = parserStrategy === "native" ? transformWithNativeMdastPipeline(loadNapiBindings(), fullSource, options) : null;
785
+ const preparedInput = parserStrategy === "native" ? null : prepareSourceWithRust(loadNapiBindings(), fullSource, options);
786
+ const markdownContent = nativePayload?.content ?? preparedInput?.content ?? fullSource;
787
+ const frontmatter = nativePayload?.frontmatter ?? preparedInput?.frontmatter ?? {};
788
+ const sourceOffset = nativePayload?.sourceOffset ?? preparedInput?.sourceOffset;
789
+ const mdastContext = createMdastPluginContext(filePath, fullSource, frontmatter, options, sourceOffset);
790
+ const processor = unified();
791
+ applyUnifiedPlugins(processor, options.plugin.mdast.map((plugin) => isOxContentMdastPlugin(plugin) ? toUnifiedMdastPlugin(plugin, mdastContext) : plugin));
792
+ applyUnifiedPlugins(processor, remarkPlugins);
793
+ installUnifiedParser(processor, parserStrategy, options, nativePayload?.tree);
794
+ let toc = [];
795
+ processor.use(() => {
796
+ return (tree) => {
797
+ if (options.toc) toc = extractTocFromMdast(tree, options.tocMaxDepth);
798
+ return tree;
799
+ };
800
+ });
801
+ const frozenMdastProcessor = processor.freeze();
802
+ if (resolveUnifiedCompilerStrategy(frozenMdastProcessor) === "custom") return {
803
+ html: await processUnifiedFile(frozenMdastProcessor(), createUnifiedFileInput(parserStrategy, fullSource, markdownContent, filePath, frontmatter, sourceOffset)),
804
+ frontmatter,
805
+ toc
806
+ };
807
+ const hastProcessor = frozenMdastProcessor();
808
+ hastProcessor.use(remarkRehype, {
809
+ allowDangerousHtml: true,
810
+ ...remarkRehypeOptions
811
+ });
812
+ applyUnifiedPlugins(hastProcessor, rehypePlugins);
813
+ const frozenHastProcessor = hastProcessor.freeze();
814
+ const finalProcessor = frozenHastProcessor();
815
+ if (resolveUnifiedCompilerStrategy(frozenHastProcessor) === "default") finalProcessor.use(rehypeStringify, {
816
+ allowDangerousHtml: true,
817
+ ...rehypeStringifyOptions
818
+ });
819
+ return {
820
+ html: await processUnifiedFile(finalProcessor, createUnifiedFileInput(parserStrategy, fullSource, markdownContent, filePath, frontmatter, sourceOffset)),
821
+ frontmatter,
46
822
  toc
47
823
  };
48
824
  }
@@ -66,6 +842,285 @@ function buildTocTree(entries) {
66
842
  }
67
843
  return result;
68
844
  }
845
+ function applyUnifiedPlugins(processor, plugins) {
846
+ for (const plugin of plugins) {
847
+ if (Array.isArray(plugin)) {
848
+ const [attacher, ...pluginOptions] = plugin;
849
+ if (pluginOptions.length === 0) processor.use(attacher);
850
+ else if (pluginOptions.length === 1) processor.use(attacher, pluginOptions[0]);
851
+ else processor.use(attacher, pluginOptions);
852
+ continue;
853
+ }
854
+ processor.use(plugin);
855
+ }
856
+ }
857
+ function applyMarkdownItPlugins(markdownIt, plugins) {
858
+ for (const plugin of plugins) {
859
+ if (Array.isArray(plugin)) {
860
+ const [fn, ...pluginOptions] = plugin;
861
+ markdownIt.use(fn, ...pluginOptions);
862
+ continue;
863
+ }
864
+ markdownIt.use(plugin);
865
+ }
866
+ }
867
+ function isOxContentMdastPlugin(plugin) {
868
+ return Boolean(plugin) && typeof plugin === "object" && "transform" in plugin;
869
+ }
870
+ function installUnifiedParser(processor, strategy, options, nativeTree) {
871
+ if (strategy === "custom") return;
872
+ if (strategy === "remark") {
873
+ processor.use(remarkParse);
874
+ return;
875
+ }
876
+ if (nativeTree) {
877
+ processor.use(function usePreparsedMdast() {
878
+ this.parser = () => nativeTree;
879
+ });
880
+ return;
881
+ }
882
+ processor.use(oxContentMdast, {
883
+ gfm: options.gfm,
884
+ footnotes: options.footnotes,
885
+ taskLists: options.taskLists,
886
+ tables: options.tables,
887
+ strikethrough: options.strikethrough,
888
+ autolinks: options.gfm
889
+ });
890
+ }
891
+ function resolveUnifiedParserStrategy(processor) {
892
+ if (hasUnifiedCustomParser(processor)) return "custom";
893
+ if (hasRemarkSyntaxExtensions(processor)) return "remark";
894
+ return "native";
895
+ }
896
+ function resolveUnifiedCompilerStrategy(processor) {
897
+ return hasUnifiedCustomCompiler(processor) ? "custom" : "default";
898
+ }
899
+ function detectUnifiedParserStrategy(fullSource, filePath, options, remarkPlugins) {
900
+ const stagedProcessor = unified();
901
+ const detectionContext = createMdastPluginContext(filePath, fullSource, {}, options);
902
+ applyUnifiedPlugins(stagedProcessor, options.plugin.mdast.map((plugin) => isOxContentMdastPlugin(plugin) ? toUnifiedMdastPlugin(plugin, detectionContext) : plugin));
903
+ applyUnifiedPlugins(stagedProcessor, remarkPlugins);
904
+ return resolveUnifiedParserStrategy(stagedProcessor.freeze());
905
+ }
906
+ function selectUnifiedInput(strategy, fullSource, markdownContent) {
907
+ return strategy === "native" ? markdownContent : fullSource;
908
+ }
909
+ function createUnifiedFileInput(strategy, fullSource, markdownContent, filePath, frontmatter, sourceOffset) {
910
+ return {
911
+ path: filePath,
912
+ value: strategy === "markdown-it" ? markdownContent : selectUnifiedInput(strategy, fullSource, markdownContent),
913
+ data: createUnifiedFileData(strategy, fullSource, markdownContent, frontmatter, { sourceOffset })
914
+ };
915
+ }
916
+ function createUnifiedFileData(parser, fullSource, markdownContent, frontmatter, extra) {
917
+ return {
918
+ frontmatter,
919
+ matter: frontmatter,
920
+ ...extra?.sourceOffset ? { sourceOffset: extra.sourceOffset } : {},
921
+ oxContent: {
922
+ parser,
923
+ frontmatter,
924
+ source: fullSource,
925
+ content: markdownContent,
926
+ ...extra?.sourceOffset ? { sourceOffset: extra.sourceOffset } : {},
927
+ ...extra
928
+ }
929
+ };
930
+ }
931
+ function hasUnifiedCustomParser(processor) {
932
+ const candidate = processor;
933
+ return typeof candidate.Parser === "function" || typeof candidate.parser === "function";
934
+ }
935
+ function hasUnifiedCustomCompiler(processor) {
936
+ const candidate = processor;
937
+ return typeof candidate.Compiler === "function" || typeof candidate.compiler === "function";
938
+ }
939
+ async function processUnifiedFile(processor, input) {
940
+ const file = await processor.process(input);
941
+ return String(file);
942
+ }
943
+ function processorFromMarkdownItProcessor(processor, markdownIt, filePath, fullSource, markdownContent, frontmatter, parser) {
944
+ return {
945
+ processor,
946
+ input: {
947
+ path: filePath,
948
+ value: markdownIt.html,
949
+ data: createUnifiedFileData(parser, fullSource, markdownContent, frontmatter, {
950
+ html: markdownIt.html,
951
+ markdownIt: {
952
+ env: markdownIt.env,
953
+ tokens: markdownIt.tokens
954
+ }
955
+ })
956
+ }
957
+ };
958
+ }
959
+ function createMarkdownItRenderer(options) {
960
+ const markdownIt = new MarkdownIt({
961
+ html: true,
962
+ linkify: options.gfm
963
+ });
964
+ if (!options.tables) markdownIt.disable("table");
965
+ if (!options.strikethrough) markdownIt.disable("strikethrough");
966
+ return markdownIt;
967
+ }
968
+ function createMarkdownItEnv(filePath, fullSource, markdownContent, frontmatter, sourceOffset) {
969
+ return {
970
+ filePath,
971
+ frontmatter,
972
+ matter: frontmatter,
973
+ ...sourceOffset ? { sourceOffset } : {},
974
+ oxContent: {
975
+ parser: "markdown-it",
976
+ frontmatter,
977
+ source: fullSource,
978
+ content: markdownContent,
979
+ ...sourceOffset ? { sourceOffset } : {}
980
+ }
981
+ };
982
+ }
983
+ function extractTocFromMarkdownItTokens(tokens, maxDepth) {
984
+ const headings = [];
985
+ for (let index = 0; index < tokens.length; index += 1) {
986
+ const openToken = tokens[index];
987
+ if (openToken?.type !== "heading_open") continue;
988
+ const depth = Number.parseInt(openToken.tag?.replace(/^h/i, "") ?? "", 10);
989
+ if (!Number.isFinite(depth) || depth < 1 || depth > maxDepth) continue;
990
+ const inlineToken = tokens[index + 1];
991
+ if (inlineToken?.type !== "inline") continue;
992
+ const text = collectMarkdownItText(inlineToken);
993
+ if (text.length === 0) continue;
994
+ headings.push({
995
+ depth,
996
+ text,
997
+ slug: getMarkdownItTokenAttr(openToken, "id") ?? slugify(text),
998
+ children: []
999
+ });
1000
+ }
1001
+ return buildTocTree(headings);
1002
+ }
1003
+ function collectMarkdownItText(token) {
1004
+ if (Array.isArray(token.children) && token.children.length > 0) return token.children.map((child) => collectMarkdownItText(child)).join("");
1005
+ return typeof token.content === "string" ? token.content : "";
1006
+ }
1007
+ function getMarkdownItTokenAttr(token, name) {
1008
+ if (!Array.isArray(token.attrs)) return;
1009
+ return token.attrs.find(([key]) => key === name)?.[1];
1010
+ }
1011
+ function readSourceOriginFromEnvelope(buffer, envelope, sectionId) {
1012
+ const section = envelope.sections.get(sectionId);
1013
+ if (!section) return;
1014
+ if (section.len !== 16) throw new Error("[ox-content] source origin transfer section is misaligned.");
1015
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
1016
+ return {
1017
+ byteOffset: view.getUint32(section.offset, true),
1018
+ offset: view.getUint32(section.offset + 4, true),
1019
+ line: view.getUint32(section.offset + 8, true),
1020
+ column: view.getUint32(section.offset + 12, true)
1021
+ };
1022
+ }
1023
+ function slugify(text) {
1024
+ return text.toLowerCase().replace(/[^\p{L}\p{N}\s-]+/gu, " ").trim().split(/\s+/).filter(Boolean).join("-");
1025
+ }
1026
+ function requireNapiMethod(method, name) {
1027
+ if (typeof method === "function") return method;
1028
+ throw new Error(`[ox-content] @ox-content/napi is too old for unified interop: missing ${name}(). Rebuild the NAPI module with \`vp run build:napi\`.`);
1029
+ }
1030
+ async function transformHtmlWithRehype(html, filePath, fullSource, markdownContent, frontmatter, options, parser, extra) {
1031
+ const { plugins: rehypePlugins, options: rehypeStringifyOptions } = extractUnifiedPluginWithOptions(options.plugin.rehype, rehypeStringify);
1032
+ const stagedProcessor = unified();
1033
+ stagedProcessor.use(rehypeParse, { fragment: true });
1034
+ applyUnifiedPlugins(stagedProcessor, rehypePlugins);
1035
+ const frozenProcessor = stagedProcessor.freeze();
1036
+ const processor = frozenProcessor();
1037
+ if (resolveUnifiedCompilerStrategy(frozenProcessor) === "default") processor.use(rehypeStringify, {
1038
+ allowDangerousHtml: true,
1039
+ ...rehypeStringifyOptions
1040
+ });
1041
+ return processUnifiedFile(processor, {
1042
+ path: filePath,
1043
+ value: html,
1044
+ data: createUnifiedFileData(parser, fullSource, markdownContent, frontmatter, {
1045
+ html,
1046
+ ...extra
1047
+ })
1048
+ });
1049
+ }
1050
+ function hasRemarkSyntaxExtensions(processor) {
1051
+ const data = processor.data();
1052
+ return hasOwnDataField(data, "micromarkExtensions") || hasOwnDataField(data, "fromMarkdownExtensions");
1053
+ }
1054
+ function hasOwnDataField(data, key) {
1055
+ return Object.prototype.hasOwnProperty.call(data, key);
1056
+ }
1057
+ function extractUnifiedPluginWithOptions(plugins, targetPlugin) {
1058
+ let extractedOptions;
1059
+ const nextPlugins = [];
1060
+ for (const plugin of plugins) {
1061
+ const result = filterUnifiedPlugin(plugin, targetPlugin);
1062
+ extractedOptions = mergeUnifiedPluginOptions(extractedOptions, result.options);
1063
+ if (result.keep) nextPlugins.push(result.plugin);
1064
+ }
1065
+ return {
1066
+ plugins: nextPlugins,
1067
+ options: extractedOptions
1068
+ };
1069
+ }
1070
+ function filterUnifiedPlugin(plugin, targetPlugin) {
1071
+ if (isUnifiedPreset(plugin)) {
1072
+ const { plugins: _plugins, ...rest } = plugin;
1073
+ let extractedOptions;
1074
+ const nextPlugins = [];
1075
+ for (const nestedPlugin of plugin.plugins ?? []) {
1076
+ const result = filterUnifiedPlugin(nestedPlugin, targetPlugin);
1077
+ extractedOptions = mergeUnifiedPluginOptions(extractedOptions, result.options);
1078
+ if (result.keep) nextPlugins.push(result.plugin);
1079
+ }
1080
+ if (nextPlugins.length === 0 && plugin.settings === void 0) return {
1081
+ keep: false,
1082
+ plugin,
1083
+ options: extractedOptions
1084
+ };
1085
+ return {
1086
+ keep: true,
1087
+ plugin: {
1088
+ ...rest,
1089
+ ...nextPlugins.length > 0 ? { plugins: nextPlugins } : {}
1090
+ },
1091
+ options: extractedOptions
1092
+ };
1093
+ }
1094
+ const { attacher, options } = splitUnifiedPlugin(plugin);
1095
+ if (attacher === targetPlugin) return {
1096
+ keep: false,
1097
+ plugin,
1098
+ options
1099
+ };
1100
+ return {
1101
+ keep: true,
1102
+ plugin
1103
+ };
1104
+ }
1105
+ function mergeUnifiedPluginOptions(left, right) {
1106
+ if (left === void 0) return right;
1107
+ if (right === void 0) return left;
1108
+ return {
1109
+ ...left,
1110
+ ...right
1111
+ };
1112
+ }
1113
+ function isUnifiedPreset(plugin) {
1114
+ return Boolean(plugin) && typeof plugin === "object" && !Array.isArray(plugin) && ("plugins" in plugin || "settings" in plugin);
1115
+ }
1116
+ function splitUnifiedPlugin(plugin) {
1117
+ if (!Array.isArray(plugin)) return { attacher: plugin };
1118
+ const [attacher, firstOption] = plugin;
1119
+ return {
1120
+ attacher,
1121
+ options: firstOption && typeof firstOption === "object" ? firstOption : void 0
1122
+ };
1123
+ }
69
1124
  /**
70
1125
  * Generates the JavaScript module code.
71
1126
  */
@@ -159,7 +1214,11 @@ function resolveDocsConfig(docs) {
159
1214
  * Resolves plugin options with defaults.
160
1215
  */
161
1216
  function resolveOptions(options) {
162
- const extensions = options.extensions ?? [".md", ".markdown"];
1217
+ const extensions = options.extensions ?? [
1218
+ ".md",
1219
+ ".markdown",
1220
+ ".mdx"
1221
+ ];
163
1222
  return {
164
1223
  srcDir: options.srcDir ?? "docs",
165
1224
  gfm: options.gfm ?? true,
@@ -180,6 +1239,7 @@ function resolveOptions(options) {
180
1239
  plugin: {
181
1240
  oxContent: options.plugin?.oxContent ?? [],
182
1241
  markdownIt: options.plugin?.markdownIt ?? [],
1242
+ mdast: options.plugin?.mdast ?? [],
183
1243
  remark: options.plugin?.remark ?? [],
184
1244
  rehype: options.plugin?.rehype ?? []
185
1245
  },
@@ -254,4 +1314,4 @@ const unpluginFactory = (rawOptions = {}) => {
254
1314
  */
255
1315
  const unplugin = /* @__PURE__ */ createUnplugin(unpluginFactory);
256
1316
  //#endregion
257
- export { transformMarkdown as n, unplugin as t };
1317
+ export { extractTocFromMdast as a, toUnifiedMdastPlugin as c, defineMdastPlugin as i, transformMarkdown as n, oxContentMdast as o, createMdastPluginContext as r, parseMarkdownToMdast as s, unplugin as t };