@forge-ts/gen 0.7.2 → 0.8.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/index.js CHANGED
@@ -16,33 +16,185 @@ function getAvailableTargets() {
16
16
  }
17
17
  var DEFAULT_TARGET = "mintlify";
18
18
 
19
- // src/adapters/mintlify.ts
20
- function sanitizeMdx(content) {
21
- const lines = content.split("\n");
22
- const result = [];
23
- let insideFence = false;
24
- for (const line of lines) {
25
- if (insideFence) {
26
- result.push(line);
27
- if (/^```\s*$/.test(line)) {
28
- insideFence = false;
19
+ // src/markdown-utils.ts
20
+ import matter from "gray-matter";
21
+ import remarkFrontmatter from "remark-frontmatter";
22
+ import remarkGfm from "remark-gfm";
23
+ import remarkMdx from "remark-mdx";
24
+ import remarkParse from "remark-parse";
25
+ import { unified } from "unified";
26
+ import { SKIP, visit } from "unist-util-visit";
27
+ function createParser() {
28
+ return unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ["yaml"]);
29
+ }
30
+ function createMdxParser() {
31
+ return unified().use(remarkParse).use(remarkMdx).use(remarkGfm).use(remarkFrontmatter, ["yaml"]);
32
+ }
33
+ function getProtectedRangesSafe(content) {
34
+ try {
35
+ return getProtectedRangesFromTree(createMdxParser().parse(content));
36
+ } catch {
37
+ return getProtectedRangesFromTree(createParser().parse(content));
38
+ }
39
+ }
40
+ function parseFrontmatter(content) {
41
+ const result = matter(content);
42
+ return { body: result.content, data: result.data };
43
+ }
44
+ function stringifyWithFrontmatter(body, data) {
45
+ if (Object.keys(data).length === 0) return body;
46
+ const normalizedBody = body.startsWith("\n") ? body : `
47
+ ${body}`;
48
+ return matter.stringify(normalizedBody, data);
49
+ }
50
+ function stripFrontmatter(content) {
51
+ return matter(content).content;
52
+ }
53
+ var inlineParser = unified().use(remarkParse).use(remarkGfm);
54
+ function parseInline(markdown) {
55
+ if (!markdown) return [];
56
+ const tree = inlineParser.parse(markdown);
57
+ const first = tree.children[0];
58
+ if (first?.type === "paragraph") {
59
+ return first.children;
60
+ }
61
+ return [{ type: "text", value: markdown }];
62
+ }
63
+ function parseBlocks(markdown) {
64
+ if (!markdown) return [];
65
+ const tree = inlineParser.parse(markdown);
66
+ return tree.children;
67
+ }
68
+ function getProtectedRangesFromTree(tree) {
69
+ const ranges = [];
70
+ visit(tree, (node) => {
71
+ const pos = node.position;
72
+ if (!pos?.start.offset || !pos?.end.offset) return;
73
+ if (node.type === "code" || node.type === "inlineCode" || node.type === "yaml") {
74
+ ranges.push({
75
+ start: pos.start.offset,
76
+ end: pos.end.offset
77
+ });
78
+ return SKIP;
79
+ }
80
+ });
81
+ return ranges.sort((a, b) => a.start - b.start);
82
+ }
83
+ function isProtected(offset, ranges) {
84
+ let lo = 0;
85
+ let hi = ranges.length - 1;
86
+ while (lo <= hi) {
87
+ const mid = lo + hi >>> 1;
88
+ const r = ranges[mid];
89
+ if (offset < r.start) {
90
+ hi = mid - 1;
91
+ } else if (offset >= r.end) {
92
+ lo = mid + 1;
93
+ } else {
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ function sanitizeForMdx(content) {
100
+ const tree = createParser().parse(content);
101
+ const transforms = [];
102
+ visit(tree, (node) => {
103
+ const pos = node.position;
104
+ if (!pos?.start.offset || !pos?.end.offset) return;
105
+ const start = pos.start.offset;
106
+ const end = pos.end.offset;
107
+ if (node.type === "html") {
108
+ const original = content.slice(start, end);
109
+ const commentMatch = /^<!--([\s\S]*?)-->$/.exec(original);
110
+ if (commentMatch) {
111
+ transforms.push({
112
+ start,
113
+ end,
114
+ replacement: `{/*${commentMatch[1]}*/}`
115
+ });
116
+ }
117
+ return SKIP;
118
+ }
119
+ if (node.type === "text") {
120
+ const original = content.slice(start, end);
121
+ const escaped = original.replace(/\{(?!\/\*)/g, "\\{").replace(/(?<!\*\/)\}/g, "\\}").replace(/<(\w)/g, "&lt;$1").replace(/(\w)>/g, "$1&gt;");
122
+ if (escaped !== original) {
123
+ transforms.push({ start, end, replacement: escaped });
124
+ }
125
+ return SKIP;
126
+ }
127
+ if (node.type === "code" || node.type === "inlineCode" || node.type === "yaml") {
128
+ return SKIP;
129
+ }
130
+ });
131
+ let result = content;
132
+ for (const t of transforms.sort((a, b) => b.start - a.start)) {
133
+ result = result.slice(0, t.start) + t.replacement + result.slice(t.end);
134
+ }
135
+ return result;
136
+ }
137
+ function findAutoSections(content) {
138
+ const protectedRanges = getProtectedRangesSafe(content);
139
+ const markers = [];
140
+ const patterns = [
141
+ { regex: /<!--\s*FORGE:AUTO-START\s+(\S+)\s*-->/g, type: "start" },
142
+ { regex: /<!--\s*FORGE:AUTO-END\s+(\S+)\s*-->/g, type: "end" },
143
+ { regex: /\{\/\*\s*FORGE:AUTO-START\s+(\S+)\s*\*\/\}/g, type: "start" },
144
+ { regex: /\{\/\*\s*FORGE:AUTO-END\s+(\S+)\s*\*\/\}/g, type: "end" }
145
+ ];
146
+ for (const { regex, type } of patterns) {
147
+ let match;
148
+ while ((match = regex.exec(content)) !== null) {
149
+ if (!isProtected(match.index, protectedRanges)) {
150
+ markers.push({
151
+ type,
152
+ id: match[1],
153
+ offset: match.index,
154
+ length: match[0].length
155
+ });
29
156
  }
30
- continue;
31
157
  }
32
- if (/^```/.test(line)) {
33
- insideFence = true;
34
- result.push(line);
35
- continue;
158
+ }
159
+ markers.sort((a, b) => a.offset - b.offset);
160
+ const sections = /* @__PURE__ */ new Map();
161
+ for (let i = 0; i < markers.length; i++) {
162
+ const m = markers[i];
163
+ if (m.type !== "start") continue;
164
+ for (let j = i + 1; j < markers.length; j++) {
165
+ if (markers[j].type === "end" && markers[j].id === m.id) {
166
+ const sectionEnd = markers[j].offset + markers[j].length;
167
+ sections.set(m.id, {
168
+ id: m.id,
169
+ fullText: content.slice(m.offset, sectionEnd),
170
+ start: m.offset,
171
+ end: sectionEnd
172
+ });
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ return sections;
178
+ }
179
+ function updateAutoSections(existing, generated) {
180
+ const newSections = findAutoSections(generated);
181
+ if (newSections.size === 0) return null;
182
+ const existingSections = findAutoSections(existing);
183
+ if (existingSections.size === 0) return null;
184
+ const sortedExisting = [...existingSections.entries()].sort(([, a], [, b]) => b.start - a.start);
185
+ let result = existing;
186
+ let changed = false;
187
+ for (const [id, existingSection] of sortedExisting) {
188
+ const newSection = newSections.get(id);
189
+ if (newSection) {
190
+ result = result.slice(0, existingSection.start) + newSection.fullText + result.slice(existingSection.end);
191
+ changed = true;
36
192
  }
37
- let safe = line;
38
- safe = safe.replace(/<!--([\s\S]*?)-->/g, "{/* $1 */}");
39
- safe = safe.replace(/\{(?!\/\*)/g, "\\{").replace(/(?<!\*\/)\}/g, "\\}");
40
- safe = safe.replace(/<(\w)/g, "&lt;$1");
41
- safe = safe.replace(/(\w)>/g, "$1&gt;");
42
- result.push(safe);
43
193
  }
44
- return result.join("\n");
194
+ return changed ? result : null;
45
195
  }
196
+
197
+ // src/adapters/mintlify.ts
46
198
  var styleGuide = {
47
199
  pageExtension: "mdx",
48
200
  supportsMdx: true,
@@ -120,13 +272,12 @@ function buildDocsJson(context) {
120
272
  function addMintlifyFrontmatter(page) {
121
273
  const title = String(page.frontmatter.title ?? "");
122
274
  const description = page.frontmatter.description ? String(page.frontmatter.description) : void 0;
123
- const lines = ["---", `title: "${title}"`];
275
+ const fields = { title };
124
276
  if (description) {
125
- lines.push(`description: "${description}"`);
277
+ fields.description = description;
126
278
  }
127
- lines.push("---", "");
128
- const body = page.content.replace(/^---[\s\S]*?---\n+/, "");
129
- return lines.join("\n") + body;
279
+ const body = stripFrontmatter(page.content);
280
+ return stringifyWithFrontmatter(body, fields);
130
281
  }
131
282
  var mintlifyAdapter = {
132
283
  target: "mintlify",
@@ -168,7 +319,7 @@ var mintlifyAdapter = {
168
319
  }
169
320
  return line;
170
321
  }).join("\n");
171
- content = sanitizeMdx(content);
322
+ content = sanitizeForMdx(content);
172
323
  return {
173
324
  path: page.path.replace(/\.md$/, ".mdx"),
174
325
  content,
@@ -319,16 +470,18 @@ function addDocusaurusFrontmatter(page) {
319
470
  const description = page.frontmatter.description ? String(page.frontmatter.description) : void 0;
320
471
  const sidebarLabel = page.frontmatter.sidebar_label ? String(page.frontmatter.sidebar_label) : title;
321
472
  const sidebarPosition = page.frontmatter.sidebar_position;
322
- const lines = ["---", `title: "${title}"`, `sidebar_label: "${sidebarLabel}"`];
473
+ const fields = {
474
+ title,
475
+ sidebar_label: sidebarLabel
476
+ };
323
477
  if (sidebarPosition !== void 0) {
324
- lines.push(`sidebar_position: ${sidebarPosition}`);
478
+ fields.sidebar_position = sidebarPosition;
325
479
  }
326
480
  if (description) {
327
- lines.push(`description: "${description}"`);
481
+ fields.description = description;
328
482
  }
329
- lines.push("---", "");
330
- const body = page.content.replace(/^---[\s\S]*?---\n+/, "");
331
- return lines.join("\n") + body;
483
+ const body = stripFrontmatter(page.content);
484
+ return stringifyWithFrontmatter(body, fields);
332
485
  }
333
486
  var docusaurusAdapter = {
334
487
  target: "docusaurus",
@@ -565,9 +718,8 @@ function addNextraFrontmatter(page) {
565
718
  if (!title) {
566
719
  return page.content;
567
720
  }
568
- const lines = ["---", `title: "${title}"`, "---", ""];
569
- const body = page.content.replace(/^---[\s\S]*?---\n+/, "");
570
- return lines.join("\n") + body;
721
+ const body = stripFrontmatter(page.content);
722
+ return stringifyWithFrontmatter(body, { title });
571
723
  }
572
724
  var nextraAdapter = {
573
725
  target: "nextra",
@@ -720,13 +872,12 @@ function buildPackageJson3(context) {
720
872
  function addVitepressFrontmatter(page) {
721
873
  const title = String(page.frontmatter.title ?? "");
722
874
  const description = page.frontmatter.description ? String(page.frontmatter.description) : void 0;
723
- const lines = ["---", `title: "${title}"`, "outline: deep"];
875
+ const fields = { title, outline: "deep" };
724
876
  if (description) {
725
- lines.push(`description: "${description}"`);
877
+ fields.description = description;
726
878
  }
727
- lines.push("---", "");
728
- const body = page.content.replace(/^---[\s\S]*?---\n+/, "");
729
- return lines.join("\n") + body;
879
+ const body = stripFrontmatter(page.content);
880
+ return stringifyWithFrontmatter(body, fields);
730
881
  }
731
882
  var vitepressAdapter = {
732
883
  target: "vitepress",
@@ -932,6 +1083,96 @@ function generateLlmsFullTxt(symbols, config) {
932
1083
 
933
1084
  // src/markdown.ts
934
1085
  import { relative } from "path";
1086
+
1087
+ // src/mdast-builders.ts
1088
+ import remarkFrontmatter2 from "remark-frontmatter";
1089
+ import remarkGfm2 from "remark-gfm";
1090
+ import remarkStringify from "remark-stringify";
1091
+ import { unified as unified2 } from "unified";
1092
+ var md = {
1093
+ // Inline (phrasing) nodes
1094
+ text: (value) => ({ type: "text", value }),
1095
+ inlineCode: (value) => ({ type: "inlineCode", value }),
1096
+ strong: (...children) => ({ type: "strong", children }),
1097
+ emphasis: (...children) => ({ type: "emphasis", children }),
1098
+ link: (url, ...children) => ({ type: "link", url, children }),
1099
+ // Block nodes
1100
+ heading: (depth, ...children) => ({
1101
+ type: "heading",
1102
+ depth,
1103
+ children
1104
+ }),
1105
+ paragraph: (...children) => ({ type: "paragraph", children }),
1106
+ code: (lang, value) => ({ type: "code", lang, value }),
1107
+ blockquote: (...children) => ({ type: "blockquote", children }),
1108
+ html: (value) => ({ type: "html", value }),
1109
+ thematicBreak: () => ({ type: "thematicBreak" }),
1110
+ // List nodes
1111
+ listItem: (...children) => ({
1112
+ type: "listItem",
1113
+ spread: false,
1114
+ children
1115
+ }),
1116
+ list: (items, ordered = false) => ({
1117
+ type: "list",
1118
+ ordered,
1119
+ spread: false,
1120
+ children: items
1121
+ }),
1122
+ // GFM table nodes
1123
+ tableCell: (...children) => ({ type: "tableCell", children }),
1124
+ tableRow: (...cells) => ({ type: "tableRow", children: cells }),
1125
+ table: (align, ...rows) => ({
1126
+ type: "table",
1127
+ align: align ?? void 0,
1128
+ children: rows
1129
+ }),
1130
+ // Root
1131
+ root: (...children) => ({ type: "root", children })
1132
+ };
1133
+ var serializer = unified2().use(remarkGfm2).use(remarkFrontmatter2).use(remarkStringify, {
1134
+ bullet: "-",
1135
+ emphasis: "*",
1136
+ strong: "*",
1137
+ fences: true,
1138
+ listItemIndent: "one",
1139
+ rule: "-"
1140
+ });
1141
+ function serializeMarkdown(tree) {
1142
+ return String(serializer.stringify(tree));
1143
+ }
1144
+ function textP(value) {
1145
+ return md.paragraph(md.text(value));
1146
+ }
1147
+ function textListItem(value) {
1148
+ return md.listItem(md.paragraph(md.text(value)));
1149
+ }
1150
+ function rawBlock(markdown) {
1151
+ return md.html(markdown);
1152
+ }
1153
+ function truncate(text, maxLen = 80) {
1154
+ if (text.length <= maxLen) return text;
1155
+ let cutPoint = maxLen - 3;
1156
+ const prefix = text.slice(0, cutPoint);
1157
+ const backtickCount = (prefix.match(/`/g) || []).length;
1158
+ if (backtickCount % 2 !== 0) {
1159
+ const lastBacktick = prefix.lastIndexOf("`");
1160
+ if (lastBacktick > 0) {
1161
+ cutPoint = lastBacktick;
1162
+ }
1163
+ }
1164
+ return `${text.slice(0, cutPoint).trimEnd()}...`;
1165
+ }
1166
+ function toAnchor(text) {
1167
+ return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-");
1168
+ }
1169
+ function slugLink(path) {
1170
+ let slug = path.startsWith("./") ? path.slice(2) : path;
1171
+ slug = slug.replace(/\.(mdx?)$/, "");
1172
+ return `/${slug}`;
1173
+ }
1174
+
1175
+ // src/markdown.ts
935
1176
  var KIND_LABELS = {
936
1177
  function: "Functions",
937
1178
  class: "Classes",
@@ -950,133 +1191,131 @@ var KIND_ORDER = [
950
1191
  "enum",
951
1192
  "variable"
952
1193
  ];
953
- function toAnchor(text) {
954
- return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-");
955
- }
956
- function buildFrontmatter(config, mdx) {
1194
+ function buildFrontmatter(config, _mdx) {
957
1195
  const target = config.gen.ssgTarget;
958
1196
  if (!target) return "";
959
- const lines = ["---"];
1197
+ const fields = {};
960
1198
  switch (target) {
961
1199
  case "docusaurus":
962
- lines.push("sidebar_position: 1");
963
- lines.push("title: API Reference");
1200
+ fields.sidebar_position = 1;
1201
+ fields.title = "API Reference";
964
1202
  break;
965
1203
  case "mintlify":
966
- lines.push("title: API Reference");
1204
+ fields.title = "API Reference";
967
1205
  break;
968
1206
  case "nextra":
969
- lines.push("title: API Reference");
970
- lines.push("description: Auto-generated API reference");
1207
+ fields.title = "API Reference";
1208
+ fields.description = "Auto-generated API reference";
971
1209
  break;
972
1210
  case "vitepress":
973
- lines.push("title: API Reference");
974
- lines.push("outline: deep");
1211
+ fields.title = "API Reference";
1212
+ fields.outline = "deep";
975
1213
  break;
976
1214
  }
977
- lines.push("---");
978
- if (mdx) {
979
- lines.push("");
980
- }
981
- return `${lines.join("\n")}
982
- `;
1215
+ return stringifyWithFrontmatter("", fields);
983
1216
  }
984
1217
  function buildMdxImports() {
985
1218
  return 'import { Callout } from "@components/Callout";\n\n';
986
1219
  }
987
- function renderDeprecation(deprecated) {
988
- return `> **Deprecated**: ${deprecated}
989
- `;
990
- }
991
- function renderSourceLink(symbol, rootDir) {
992
- const rel = relative(rootDir, symbol.filePath);
993
- return `_Defined in \`${rel}:${symbol.line}\`_
994
- `;
995
- }
996
- function renderSymbolSection(symbol, rootDir, mdx, depth) {
997
- const lines = [];
998
- const hashes = "#".repeat(depth);
1220
+ function renderSymbolBlocks(symbol, rootDir, depth) {
1221
+ const nodes = [];
999
1222
  const ext = symbol.kind === "function" || symbol.kind === "method" ? "()" : "";
1000
- lines.push(`${hashes} \`${symbol.name}${ext}\``);
1001
- lines.push("");
1223
+ const headingDepth = Math.min(depth, 6);
1224
+ nodes.push(md.heading(headingDepth, md.inlineCode(`${symbol.name}${ext}`)));
1002
1225
  if (symbol.documentation?.deprecated) {
1003
- lines.push(renderDeprecation(symbol.documentation.deprecated));
1226
+ nodes.push(
1227
+ md.blockquote(
1228
+ md.paragraph(
1229
+ md.strong(md.text("Deprecated")),
1230
+ md.text(": "),
1231
+ ...parseInline(symbol.documentation.deprecated)
1232
+ )
1233
+ )
1234
+ );
1004
1235
  }
1005
- lines.push(renderSourceLink(symbol, rootDir));
1236
+ const rel = relative(rootDir, symbol.filePath);
1237
+ nodes.push(
1238
+ md.paragraph(md.emphasis(md.text("Defined in "), md.inlineCode(`${rel}:${symbol.line}`)))
1239
+ );
1006
1240
  if (symbol.signature) {
1007
- lines.push("```typescript");
1008
- lines.push(symbol.signature);
1009
- lines.push("```");
1010
- lines.push("");
1241
+ nodes.push(md.code("typescript", symbol.signature));
1011
1242
  }
1012
1243
  if (symbol.documentation?.summary) {
1013
- lines.push(symbol.documentation.summary);
1014
- lines.push("");
1244
+ nodes.push(md.paragraph(...parseInline(symbol.documentation.summary)));
1015
1245
  }
1016
1246
  const params = symbol.documentation?.params ?? [];
1017
1247
  if (params.length > 0) {
1018
- lines.push("**Parameters**");
1019
- lines.push("");
1248
+ nodes.push(md.paragraph(md.strong(md.text("Parameters"))));
1249
+ const paramItems = [];
1020
1250
  for (const p of params) {
1021
- const typeStr = p.type ? ` (\`${p.type}\`)` : "";
1022
- lines.push(`- \`${p.name}\`${typeStr} \u2014 ${p.description}`);
1251
+ const parts = [md.inlineCode(p.name)];
1252
+ if (p.type) {
1253
+ parts.push(md.text(" ("), md.inlineCode(p.type), md.text(")"));
1254
+ }
1255
+ parts.push(md.text(" \u2014 "), ...parseInline(p.description));
1256
+ paramItems.push(md.listItem(md.paragraph(...parts)));
1023
1257
  }
1024
- lines.push("");
1258
+ nodes.push(md.list(paramItems));
1025
1259
  }
1026
1260
  if (symbol.documentation?.returns) {
1027
- const retType = symbol.documentation.returns.type ? ` (\`${symbol.documentation.returns.type}\`)` : "";
1028
- lines.push(`**Returns**${retType}: ${symbol.documentation.returns.description}`);
1029
- lines.push("");
1261
+ const retParts = [md.strong(md.text("Returns"))];
1262
+ if (symbol.documentation.returns.type) {
1263
+ retParts.push(md.text(" ("), md.inlineCode(symbol.documentation.returns.type), md.text(")"));
1264
+ }
1265
+ retParts.push(md.text(": "), ...parseInline(symbol.documentation.returns.description));
1266
+ nodes.push(md.paragraph(...retParts));
1030
1267
  }
1031
1268
  const throws = symbol.documentation?.throws ?? [];
1032
1269
  if (throws.length > 0) {
1033
- lines.push("**Throws**");
1034
- lines.push("");
1270
+ nodes.push(md.paragraph(md.strong(md.text("Throws"))));
1271
+ const throwItems = [];
1035
1272
  for (const t of throws) {
1036
- const typeStr = t.type ? `\`${t.type}\` \u2014 ` : "";
1037
- lines.push(`- ${typeStr}${t.description}`);
1273
+ const parts = [];
1274
+ if (t.type) {
1275
+ parts.push(md.inlineCode(t.type), md.text(" \u2014 "), ...parseInline(t.description));
1276
+ } else {
1277
+ parts.push(...parseInline(t.description));
1278
+ }
1279
+ throwItems.push(md.listItem(md.paragraph(...parts)));
1038
1280
  }
1039
- lines.push("");
1281
+ nodes.push(md.list(throwItems));
1040
1282
  }
1041
1283
  const examples = symbol.documentation?.examples ?? [];
1042
1284
  if (examples.length > 0) {
1043
- lines.push("**Examples**");
1044
- lines.push("");
1285
+ nodes.push(md.paragraph(md.strong(md.text("Examples"))));
1045
1286
  for (const ex of examples) {
1046
- lines.push(`\`\`\`${ex.language}`);
1047
- lines.push(ex.code.trim());
1048
- lines.push("```");
1049
- lines.push("");
1287
+ nodes.push(md.code(ex.language, ex.code.trim()));
1050
1288
  }
1051
1289
  }
1052
1290
  const children = symbol.children ?? [];
1053
1291
  if (children.length > 0 && depth < 5) {
1054
- const childDepth = depth + 1;
1055
1292
  for (const child of children) {
1056
- lines.push(renderSymbolSection(child, rootDir, mdx, childDepth));
1293
+ nodes.push(...renderSymbolBlocks(child, rootDir, depth + 1));
1057
1294
  }
1058
1295
  }
1059
- return lines.join("\n");
1296
+ return nodes;
1060
1297
  }
1061
- function buildToc(groups) {
1062
- const lines = [];
1063
- lines.push("## Table of Contents");
1064
- lines.push("");
1298
+ function buildTocBlocks(groups) {
1299
+ const tocItems = [];
1065
1300
  for (const kind of KIND_ORDER) {
1066
1301
  const group = groups.get(kind);
1067
1302
  if (!group || group.length === 0) continue;
1068
1303
  const label = KIND_LABELS[kind];
1069
1304
  const anchor = toAnchor(label);
1070
- lines.push(`- [${label}](#${anchor})`);
1305
+ const subItems = [];
1071
1306
  for (const symbol of group) {
1072
1307
  const ext = kind === "function" ? "()" : "";
1073
1308
  const displayName = `${symbol.name}${ext}`;
1074
1309
  const symAnchor = toAnchor(displayName);
1075
- lines.push(` - [\`${displayName}\`](#${symAnchor})`);
1310
+ subItems.push(
1311
+ md.listItem(md.paragraph(md.link(`#${symAnchor}`, md.inlineCode(displayName))))
1312
+ );
1076
1313
  }
1314
+ tocItems.push(
1315
+ md.listItem(md.paragraph(md.link(`#${anchor}`, md.text(label))), md.list(subItems))
1316
+ );
1077
1317
  }
1078
- lines.push("");
1079
- return lines.join("\n");
1318
+ return [md.heading(2, md.text("Table of Contents")), md.list(tocItems)];
1080
1319
  }
1081
1320
  function generateMarkdown(symbols, config, options = {}) {
1082
1321
  const mdx = options.mdx ?? false;
@@ -1088,34 +1327,40 @@ function generateMarkdown(symbols, config, options = {}) {
1088
1327
  list.push(symbol);
1089
1328
  groups.set(symbol.kind, list);
1090
1329
  }
1091
- const parts = [];
1092
- const frontmatter = buildFrontmatter(config, mdx);
1093
- if (frontmatter) {
1094
- parts.push(frontmatter);
1095
- }
1096
- if (mdx) {
1097
- parts.push(buildMdxImports());
1098
- }
1099
- parts.push("# API Reference\n");
1100
- parts.push(
1101
- `Generated by [forge-ts](https://github.com/kryptobaseddev/forge-ts) from \`${config.rootDir}\`.
1102
- `
1330
+ const nodes = [];
1331
+ nodes.push(md.heading(1, md.text("API Reference")));
1332
+ nodes.push(
1333
+ md.paragraph(
1334
+ md.text("Generated by "),
1335
+ md.link("https://github.com/kryptobaseddev/forge-ts", md.text("forge-ts")),
1336
+ md.text(" from "),
1337
+ md.inlineCode(config.rootDir),
1338
+ md.text(".")
1339
+ )
1103
1340
  );
1104
1341
  if (topLevel.length > 0) {
1105
- parts.push(buildToc(groups));
1342
+ nodes.push(...buildTocBlocks(groups));
1106
1343
  }
1107
1344
  for (const kind of KIND_ORDER) {
1108
1345
  const group = groups.get(kind);
1109
1346
  if (!group || group.length === 0) continue;
1110
1347
  const label = KIND_LABELS[kind];
1111
- parts.push(`## ${label}
1112
- `);
1348
+ nodes.push(md.heading(2, md.text(label)));
1113
1349
  for (const symbol of group) {
1114
- parts.push(renderSymbolSection(symbol, config.rootDir, mdx, 3));
1115
- parts.push("");
1350
+ nodes.push(...renderSymbolBlocks(symbol, config.rootDir, 3));
1116
1351
  }
1117
1352
  }
1118
- return `${parts.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}
1353
+ const body = serializeMarkdown(md.root(...nodes));
1354
+ const parts = [];
1355
+ const frontmatter = buildFrontmatter(config, mdx);
1356
+ if (frontmatter) {
1357
+ parts.push(frontmatter);
1358
+ }
1359
+ if (mdx) {
1360
+ parts.push(buildMdxImports());
1361
+ }
1362
+ parts.push(body);
1363
+ return `${parts.join("").trimEnd()}
1119
1364
  `;
1120
1365
  }
1121
1366
 
@@ -1124,59 +1369,67 @@ import { existsSync } from "fs";
1124
1369
  import { readFile, writeFile } from "fs/promises";
1125
1370
  var SECTION_START = "<!-- forge-ts:start -->";
1126
1371
  var SECTION_END = "<!-- forge-ts:end -->";
1127
- function tableSignature(symbol) {
1128
- if (!symbol.signature) {
1129
- const ext = symbol.kind === "function" ? "()" : "";
1130
- return `\`${symbol.name}${ext}\``;
1131
- }
1132
- const sig = symbol.signature.length > 60 ? `${symbol.signature.slice(0, 57)}...` : symbol.signature;
1133
- return `\`${sig}\``;
1134
- }
1135
- function renderFirstExample(symbol) {
1136
- const examples = symbol.documentation?.examples ?? [];
1137
- if (examples.length === 0) return "";
1138
- const ex = examples[0];
1139
- return ["", `\`\`\`${ex.language}`, ex.code.trim(), "```"].join("\n");
1140
- }
1141
- function buildApiTable(symbols, includeExamples) {
1142
- const lines = ["| Symbol | Kind | Description |", "|--------|------|-------------|"];
1372
+ function buildApiBlocks(symbols, includeExamples) {
1373
+ const nodes = [];
1374
+ const headerRow = md.tableRow(
1375
+ md.tableCell(md.text("Symbol")),
1376
+ md.tableCell(md.text("Kind")),
1377
+ md.tableCell(md.text("Description"))
1378
+ );
1379
+ const dataRows = [];
1143
1380
  for (const s of symbols) {
1144
- const sig = tableSignature(s);
1381
+ let sigText;
1382
+ if (!s.signature) {
1383
+ const ext = s.kind === "function" ? "()" : "";
1384
+ sigText = `${s.name}${ext}`;
1385
+ } else {
1386
+ sigText = s.signature.length > 60 ? `${s.signature.slice(0, 57)}...` : s.signature;
1387
+ }
1145
1388
  const summary = s.documentation?.summary ?? "";
1146
- lines.push(`| ${sig} | ${s.kind} | ${summary} |`);
1389
+ dataRows.push(
1390
+ md.tableRow(
1391
+ md.tableCell(md.inlineCode(sigText)),
1392
+ md.tableCell(md.text(s.kind)),
1393
+ md.tableCell(...summary ? parseInline(summary) : [md.text("")])
1394
+ )
1395
+ );
1147
1396
  }
1397
+ nodes.push(md.table(null, headerRow, ...dataRows));
1148
1398
  if (includeExamples) {
1149
1399
  const withExamples = symbols.filter((s) => (s.documentation?.examples ?? []).length > 0);
1150
1400
  if (withExamples.length > 0) {
1151
- lines.push("");
1152
- lines.push("### Examples");
1401
+ nodes.push(md.heading(3, md.text("Examples")));
1153
1402
  for (const s of withExamples) {
1154
- lines.push("");
1155
1403
  const ext = s.kind === "function" ? "()" : "";
1156
- lines.push(`#### \`${s.name}${ext}\``);
1157
- lines.push(renderFirstExample(s));
1404
+ nodes.push(md.heading(4, md.inlineCode(`${s.name}${ext}`)));
1405
+ const examples = s.documentation?.examples ?? [];
1406
+ const ex = examples[0];
1407
+ nodes.push(md.code(ex.language, ex.code.trim()));
1158
1408
  }
1159
1409
  }
1160
1410
  }
1161
- return lines;
1411
+ return nodes;
1162
1412
  }
1163
1413
  async function syncReadme(readmePath, symbols, options = {}) {
1164
1414
  const exported = symbols.filter((s) => s.exported);
1165
1415
  if (exported.length === 0) return false;
1166
1416
  const badge = options.badge ?? false;
1167
1417
  const includeExamples = options.includeExamples ?? false;
1168
- const innerLines = [];
1169
- innerLines.push("## API Overview");
1170
- innerLines.push("");
1418
+ const innerNodes = [];
1419
+ innerNodes.push(md.heading(2, md.text("API Overview")));
1171
1420
  if (badge) {
1172
- innerLines.push(
1173
- "[![Documented with forge-ts](https://img.shields.io/badge/docs-forge--ts-blue)](https://github.com/forge-ts/forge-ts)"
1421
+ innerNodes.push(
1422
+ rawBlock(
1423
+ "[![Documented with forge-ts](https://img.shields.io/badge/docs-forge--ts-blue)](https://github.com/forge-ts/forge-ts)"
1424
+ )
1174
1425
  );
1175
- innerLines.push("");
1176
1426
  }
1177
- innerLines.push(...buildApiTable(exported, includeExamples));
1178
- const summaryLines = [SECTION_START, "", ...innerLines, "", SECTION_END];
1179
- const injection = summaryLines.join("\n");
1427
+ innerNodes.push(...buildApiBlocks(exported, includeExamples));
1428
+ const innerMd = serializeMarkdown(md.root(...innerNodes));
1429
+ const injection = `${SECTION_START}
1430
+
1431
+ ${innerMd}
1432
+ ${SECTION_END}`;
1180
1433
  let existing = existsSync(readmePath) ? await readFile(readmePath, "utf8") : "";
1181
1434
  const startIdx = existing.indexOf(SECTION_START);
1182
1435
  const endIdx = existing.indexOf(SECTION_END);
@@ -1194,25 +1447,12 @@ ${injection}
1194
1447
 
1195
1448
  // src/site-generator.ts
1196
1449
  import { basename, relative as relative2 } from "path";
1197
- function toAnchor2(text) {
1198
- return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").trim().replace(/\s+/g, "-");
1199
- }
1200
- function escapePipe(text) {
1201
- return text.replace(/\|/g, "\\|");
1202
- }
1203
1450
  function escapeMdx(text) {
1204
1451
  return text.replace(/\{/g, "\\{").replace(/\}/g, "\\}").replace(/<(\w)/g, "&lt;$1").replace(/(\w)>/g, "$1&gt;");
1205
1452
  }
1206
1453
  function serializeFrontmatter(fields) {
1207
1454
  if (Object.keys(fields).length === 0) return "";
1208
- const lines = ["---"];
1209
- for (const [key, value] of Object.entries(fields)) {
1210
- lines.push(`${key}: ${value}`);
1211
- }
1212
- lines.push("---");
1213
- return `${lines.join("\n")}
1214
-
1215
- `;
1455
+ return stringifyWithFrontmatter("", fields);
1216
1456
  }
1217
1457
  function buildFrontmatterFields(title, description, ssgTarget, sidebarPosition) {
1218
1458
  if (!ssgTarget) return {};
@@ -1250,15 +1490,6 @@ function buildFrontmatterFields(title, description, ssgTarget, sidebarPosition)
1250
1490
  return {};
1251
1491
  }
1252
1492
  }
1253
- function slugLink(path) {
1254
- let slug = path.startsWith("./") ? path.slice(2) : path;
1255
- slug = slug.replace(/\.(mdx?)$/, "");
1256
- return `/${slug}`;
1257
- }
1258
- function truncate(text, maxLen = 80) {
1259
- if (text.length <= maxLen) return text;
1260
- return `${text.slice(0, maxLen - 3)}...`;
1261
- }
1262
1493
  function groupSymbolsByPackage(symbols, rootDir) {
1263
1494
  const result = /* @__PURE__ */ new Map();
1264
1495
  for (const symbol of symbols) {
@@ -1274,33 +1505,46 @@ function groupSymbolsByPackage(symbols, rootDir) {
1274
1505
  var TYPE_KINDS = /* @__PURE__ */ new Set(["interface", "type", "enum"]);
1275
1506
  var FUNCTION_KINDS = /* @__PURE__ */ new Set(["function", "class"]);
1276
1507
  function renderProjectIndexPage(symbolsByPackage, options) {
1277
- const lines = [];
1508
+ const nodes = [];
1278
1509
  if (options.projectDescription) {
1279
- lines.push(`**${options.projectName}** \u2014 ${options.projectDescription}`);
1510
+ nodes.push(
1511
+ md.paragraph(
1512
+ md.strong(md.text(options.projectName)),
1513
+ md.text(` \u2014 ${options.projectDescription}`)
1514
+ )
1515
+ );
1280
1516
  } else {
1281
- lines.push(
1282
- `**${options.projectName}** is a TypeScript documentation toolkit that performs a single AST traversal of your project and produces API docs, OpenAPI specs, executable doctests, and AI context files in one pass.`
1517
+ nodes.push(
1518
+ md.paragraph(
1519
+ md.strong(md.text(options.projectName)),
1520
+ md.text(
1521
+ " is a TypeScript documentation toolkit that performs a single AST traversal of your project and produces API docs, OpenAPI specs, executable doctests, and AI context files in one pass."
1522
+ )
1523
+ )
1283
1524
  );
1284
1525
  }
1285
- lines.push("");
1286
- lines.push("## Features");
1287
- lines.push("");
1526
+ nodes.push(md.heading(2, md.text("Features")));
1288
1527
  const pkgCount = symbolsByPackage.size;
1528
+ const featureItems = [];
1289
1529
  if (pkgCount > 1) {
1290
- lines.push(`- ${pkgCount} packages with full TypeScript support`);
1530
+ featureItems.push(textListItem(`${pkgCount} packages with full TypeScript support`));
1291
1531
  } else {
1292
- lines.push("- Full TypeScript support with TSDoc extraction");
1293
- }
1294
- lines.push("- Auto-generated API reference from source code");
1295
- lines.push("- Executable `@example` blocks as doctests");
1296
- lines.push("- AI-ready context files from a single build pass");
1297
- lines.push("");
1298
- lines.push("## Installation");
1299
- lines.push("");
1300
- lines.push("```bash");
1301
- lines.push(`npm install -D ${options.packageName ?? "@forge-ts/cli"}`);
1302
- lines.push("```");
1303
- lines.push("");
1532
+ featureItems.push(textListItem("Full TypeScript support with TSDoc extraction"));
1533
+ }
1534
+ featureItems.push(textListItem("Auto-generated API reference from source code"));
1535
+ featureItems.push(
1536
+ md.listItem(
1537
+ md.paragraph(
1538
+ md.text("Executable "),
1539
+ md.inlineCode("@example"),
1540
+ md.text(" blocks as doctests")
1541
+ )
1542
+ )
1543
+ );
1544
+ featureItems.push(textListItem("AI-ready context files from a single build pass"));
1545
+ nodes.push(md.list(featureItems));
1546
+ nodes.push(md.heading(2, md.text("Installation")));
1547
+ nodes.push(md.code("bash", `npm install -D ${options.packageName ?? "@forge-ts/cli"}`));
1304
1548
  let firstExample;
1305
1549
  outer: for (const [, symbols] of symbolsByPackage) {
1306
1550
  for (const s of symbols) {
@@ -1313,36 +1557,53 @@ function renderProjectIndexPage(symbolsByPackage, options) {
1313
1557
  }
1314
1558
  }
1315
1559
  if (firstExample) {
1316
- lines.push("## Quick Example");
1317
- lines.push("");
1318
- lines.push(`\`\`\`${firstExample.language || "typescript"}`);
1319
- lines.push(firstExample.code.trim());
1320
- lines.push("```");
1321
- lines.push("");
1560
+ nodes.push(md.heading(2, md.text("Quick Example")));
1561
+ nodes.push(md.code(firstExample.language || "typescript", firstExample.code.trim()));
1322
1562
  }
1323
1563
  if (symbolsByPackage.size > 0) {
1324
- lines.push("## Packages");
1325
- lines.push("");
1326
- lines.push("| Package | Description |");
1327
- lines.push("|---------|-------------|");
1564
+ nodes.push(md.heading(2, md.text("Packages")));
1565
+ const headerRow = md.tableRow(
1566
+ md.tableCell(md.text("Package")),
1567
+ md.tableCell(md.text("Description"))
1568
+ );
1569
+ const dataRows = [];
1328
1570
  for (const [pkgName, symbols] of symbolsByPackage) {
1329
1571
  const pkgDoc = symbols.map((s) => s.documentation?.tags?.packageDocumentation?.[0]).find(Boolean);
1330
1572
  const exported = symbols.filter(
1331
1573
  (s) => s.exported && s.kind !== "method" && s.kind !== "property"
1332
1574
  );
1333
1575
  const rawDesc = pkgDoc ?? `${exported.length} exported symbol(s).`;
1334
- const desc = escapePipe(truncate(rawDesc));
1335
- const link = `[\`${pkgName}\`](${slugLink(`packages/${pkgName}/index`)})`;
1336
- lines.push(`| ${link} | ${desc} |`);
1576
+ const desc = truncate(rawDesc);
1577
+ dataRows.push(
1578
+ md.tableRow(
1579
+ md.tableCell(md.link(slugLink(`packages/${pkgName}/index`), md.inlineCode(pkgName))),
1580
+ md.tableCell(md.text(desc))
1581
+ )
1582
+ );
1337
1583
  }
1338
- lines.push("");
1339
- }
1340
- lines.push("## Next Steps");
1341
- lines.push("");
1342
- lines.push(`- [Getting Started](/getting-started) \u2014 Step-by-step guide`);
1343
- lines.push(`- [API Reference](/packages) \u2014 Full API documentation`);
1344
- lines.push(`- [Concepts](/concepts) \u2014 How it works`);
1345
- return lines.join("\n");
1584
+ nodes.push(md.table(null, headerRow, ...dataRows));
1585
+ }
1586
+ nodes.push(md.heading(2, md.text("Next Steps")));
1587
+ nodes.push(
1588
+ md.list([
1589
+ md.listItem(
1590
+ md.paragraph(
1591
+ md.link("/getting-started", md.text("Getting Started")),
1592
+ md.text(" \u2014 Step-by-step guide")
1593
+ )
1594
+ ),
1595
+ md.listItem(
1596
+ md.paragraph(
1597
+ md.link("/packages", md.text("API Reference")),
1598
+ md.text(" \u2014 Full API documentation")
1599
+ )
1600
+ ),
1601
+ md.listItem(
1602
+ md.paragraph(md.link("/concepts", md.text("Concepts")), md.text(" \u2014 How it works"))
1603
+ )
1604
+ ])
1605
+ );
1606
+ return serializeMarkdown(md.root(...nodes));
1346
1607
  }
1347
1608
  function renderGettingStartedPage(symbolsByPackage, options) {
1348
1609
  let firstExample;
@@ -1356,126 +1617,143 @@ function renderGettingStartedPage(symbolsByPackage, options) {
1356
1617
  }
1357
1618
  }
1358
1619
  }
1359
- const lines = [];
1360
- lines.push(`Get up and running with **${options.projectName}** in minutes.`);
1620
+ const nodes = [];
1621
+ nodes.push(
1622
+ md.paragraph(
1623
+ md.text("Get up and running with "),
1624
+ md.strong(md.text(options.projectName)),
1625
+ md.text(" in minutes.")
1626
+ )
1627
+ );
1361
1628
  if (options.projectDescription) {
1362
- lines.push("");
1363
- lines.push(options.projectDescription);
1364
- }
1365
- lines.push("");
1366
- lines.push("## Step 1: Install");
1367
- lines.push("");
1368
- lines.push("```bash");
1369
- lines.push(`npm install -D ${options.packageName ?? "@forge-ts/cli"}`);
1370
- lines.push("```");
1371
- lines.push("");
1372
- lines.push("## Step 2: Add TSDoc to your code");
1373
- lines.push("");
1374
- lines.push("Add TSDoc comments to your exported functions and types:");
1375
- lines.push("");
1376
- lines.push("```typescript");
1377
- lines.push("/**");
1378
- lines.push(" * Adds two numbers together.");
1379
- lines.push(" * @param a - First number");
1380
- lines.push(" * @param b - Second number");
1381
- lines.push(" * @returns The sum of a and b");
1382
- lines.push(" * @example");
1383
- lines.push(" * ```typescript");
1384
- lines.push(" * const result = add(1, 2); // => 3");
1385
- lines.push(" * ```");
1386
- lines.push(" */");
1387
- lines.push("export function add(a: number, b: number): number {");
1388
- lines.push(" return a + b;");
1389
- lines.push("}");
1390
- lines.push("```");
1391
- lines.push("");
1392
- lines.push("## Step 3: Run forge-ts check");
1393
- lines.push("");
1394
- lines.push("Lint your TSDoc coverage before generating docs:");
1395
- lines.push("");
1396
- lines.push("```bash");
1397
- lines.push("npx forge-ts check");
1398
- lines.push("```");
1399
- lines.push("");
1400
- lines.push("Expected output:");
1401
- lines.push("");
1402
- lines.push("```");
1403
- lines.push("forge-ts: checking TSDoc coverage...");
1404
- lines.push(" \u2713 All public symbols documented");
1405
- lines.push("```");
1406
- lines.push("");
1407
- lines.push("## Step 4: Generate docs");
1408
- lines.push("");
1409
- lines.push("Build your documentation site:");
1410
- lines.push("");
1411
- lines.push("```bash");
1412
- lines.push("npx forge-ts build");
1413
- lines.push("```");
1414
- lines.push("");
1629
+ nodes.push(md.paragraph(...parseInline(options.projectDescription)));
1630
+ }
1631
+ nodes.push(md.heading(2, md.text("Step 1: Install")));
1632
+ nodes.push(md.code("bash", `npm install -D ${options.packageName ?? "@forge-ts/cli"}`));
1633
+ nodes.push(md.heading(2, md.text("Step 2: Add TSDoc to your code")));
1634
+ nodes.push(textP("Add TSDoc comments to your exported functions and types:"));
1635
+ nodes.push(
1636
+ md.code(
1637
+ "typescript",
1638
+ [
1639
+ "/**",
1640
+ " * Adds two numbers together.",
1641
+ " * @param a - First number",
1642
+ " * @param b - Second number",
1643
+ " * @returns The sum of a and b",
1644
+ " * @example",
1645
+ " * ```typescript",
1646
+ " * const result = add(1, 2); // => 3",
1647
+ " * ```",
1648
+ " */",
1649
+ "export function add(a: number, b: number): number {",
1650
+ " return a + b;",
1651
+ "}"
1652
+ ].join("\n")
1653
+ )
1654
+ );
1655
+ nodes.push(md.heading(2, md.text("Step 3: Run forge-ts check")));
1656
+ nodes.push(textP("Lint your TSDoc coverage before generating docs:"));
1657
+ nodes.push(md.code("bash", "npx forge-ts check"));
1658
+ nodes.push(textP("Expected output:"));
1659
+ nodes.push(
1660
+ md.code(
1661
+ "",
1662
+ ["forge-ts: checking TSDoc coverage...", " \u2713 All public symbols documented"].join("\n")
1663
+ )
1664
+ );
1665
+ nodes.push(md.heading(2, md.text("Step 4: Generate docs")));
1666
+ nodes.push(textP("Build your documentation site:"));
1667
+ nodes.push(md.code("bash", "npx forge-ts build"));
1415
1668
  if (firstExample) {
1416
- lines.push("Your code examples become live documentation:");
1417
- lines.push("");
1418
- lines.push(`\`\`\`${firstExample.language || "typescript"}`);
1419
- lines.push(firstExample.code.trim());
1420
- lines.push("```");
1421
- lines.push("");
1422
- }
1423
- lines.push("## What's Next?");
1424
- lines.push("");
1425
- lines.push("- [Concepts](/concepts) \u2014 Understand how forge-ts works");
1426
- lines.push("- [API Reference](/packages) \u2014 Full API documentation");
1427
- lines.push("- [Guides](/guides) \u2014 Practical how-to guides");
1428
- return lines.join("\n");
1669
+ nodes.push(textP("Your code examples become live documentation:"));
1670
+ nodes.push(md.code(firstExample.language || "typescript", firstExample.code.trim()));
1671
+ }
1672
+ nodes.push(md.heading(2, md.text("What's Next?")));
1673
+ nodes.push(
1674
+ md.list([
1675
+ md.listItem(
1676
+ md.paragraph(
1677
+ md.link("/concepts", md.text("Concepts")),
1678
+ md.text(" \u2014 Understand how forge-ts works")
1679
+ )
1680
+ ),
1681
+ md.listItem(
1682
+ md.paragraph(
1683
+ md.link("/packages", md.text("API Reference")),
1684
+ md.text(" \u2014 Full API documentation")
1685
+ )
1686
+ ),
1687
+ md.listItem(
1688
+ md.paragraph(md.link("/guides", md.text("Guides")), md.text(" \u2014 Practical how-to guides"))
1689
+ )
1690
+ ])
1691
+ );
1692
+ return serializeMarkdown(md.root(...nodes));
1429
1693
  }
1430
1694
  function renderConceptsPage(symbolsByPackage, options) {
1431
- const lines = [];
1432
- lines.push(`This page explains the core concepts behind **${options.projectName}**.`);
1433
- lines.push("");
1434
- lines.push(
1435
- "> This is a stub page. Edit this file to add your project's conceptual documentation."
1695
+ const nodes = [];
1696
+ nodes.push(
1697
+ md.paragraph(
1698
+ md.text("This page explains the core concepts behind "),
1699
+ md.strong(md.text(options.projectName)),
1700
+ md.text(".")
1701
+ )
1702
+ );
1703
+ nodes.push(
1704
+ md.blockquote(
1705
+ textP("This is a stub page. Edit this file to add your project's conceptual documentation."),
1706
+ textP("Auto-generated sections below (inside FORGE:AUTO markers) update on every build.")
1707
+ )
1436
1708
  );
1437
- lines.push("> Auto-generated sections below (inside FORGE:AUTO markers) update on every build.");
1438
- lines.push("");
1439
1709
  const pkgDoc = [...symbolsByPackage.values()].flatMap((syms) => syms.map((s) => s.documentation?.tags?.packageDocumentation?.[0])).find(Boolean);
1440
- lines.push("<!-- FORGE:AUTO-START how-it-works -->");
1441
- lines.push("## How It Works");
1442
- lines.push("");
1710
+ nodes.push(md.html("<!-- FORGE:AUTO-START how-it-works -->"));
1711
+ nodes.push(md.heading(2, md.text("How It Works")));
1443
1712
  if (pkgDoc) {
1444
- lines.push(pkgDoc);
1713
+ nodes.push(md.paragraph(...parseInline(pkgDoc)));
1445
1714
  } else {
1446
- lines.push(
1447
- `${options.projectName} processes your TypeScript source with a single AST traversal, extracting TSDoc comments and type information to generate documentation.`
1715
+ nodes.push(
1716
+ textP(
1717
+ `${options.projectName} processes your TypeScript source with a single AST traversal, extracting TSDoc comments and type information to generate documentation.`
1718
+ )
1448
1719
  );
1449
1720
  }
1450
- lines.push("");
1451
- lines.push("<!-- FORGE:AUTO-END how-it-works -->");
1452
- lines.push("");
1721
+ nodes.push(md.html("<!-- FORGE:AUTO-END how-it-works -->"));
1453
1722
  const allTypeSymbols = [...symbolsByPackage.values()].flat().filter((s) => s.exported && TYPE_KINDS.has(s.kind));
1454
1723
  if (allTypeSymbols.length > 0) {
1455
- lines.push("<!-- FORGE:AUTO-START key-abstractions -->");
1456
- lines.push("## Key Abstractions");
1457
- lines.push("");
1724
+ nodes.push(md.html("<!-- FORGE:AUTO-START key-abstractions -->"));
1725
+ nodes.push(md.heading(2, md.text("Key Abstractions")));
1726
+ const items = [];
1458
1727
  for (const s of allTypeSymbols) {
1459
1728
  const desc = s.documentation?.summary ?? `The \`${s.name}\` ${s.kind}.`;
1460
- lines.push(`- **\`${s.name}\`** \u2014 ${desc}`);
1729
+ items.push(
1730
+ md.listItem(
1731
+ md.paragraph(md.strong(md.inlineCode(s.name)), md.text(" \u2014 "), ...parseInline(desc))
1732
+ )
1733
+ );
1461
1734
  }
1462
- lines.push("");
1463
- lines.push("<!-- FORGE:AUTO-END key-abstractions -->");
1464
- lines.push("");
1735
+ nodes.push(md.list(items));
1736
+ nodes.push(md.html("<!-- FORGE:AUTO-END key-abstractions -->"));
1465
1737
  }
1466
- return lines.join("\n");
1738
+ return serializeMarkdown(md.root(...nodes));
1467
1739
  }
1468
1740
  function renderGuidesIndexPage() {
1469
- return [
1470
- "Practical how-to guides for common tasks.",
1471
- "",
1472
- "> Add your guides to the `guides/` directory. Each `.md` or `.mdx` file will appear here automatically.",
1473
- "",
1474
- "## Getting Things Done",
1475
- "",
1476
- "Guides will appear here as you add them. Start by creating a file like `guides/my-guide.md`.",
1477
- ""
1478
- ].join("\n");
1741
+ const nodes = [];
1742
+ nodes.push(textP("Practical how-to guides for common tasks."));
1743
+ nodes.push(
1744
+ md.blockquote(
1745
+ textP(
1746
+ "Add your guides to the `guides/` directory. Each `.md` or `.mdx` file will appear here automatically."
1747
+ )
1748
+ )
1749
+ );
1750
+ nodes.push(md.heading(2, md.text("Getting Things Done")));
1751
+ nodes.push(
1752
+ textP(
1753
+ "Guides will appear here as you add them. Start by creating a file like `guides/my-guide.md`."
1754
+ )
1755
+ );
1756
+ return serializeMarkdown(md.root(...nodes));
1479
1757
  }
1480
1758
  function renderApiIndexPage(pkgName, symbols) {
1481
1759
  const exported = symbols.filter(
@@ -1484,45 +1762,57 @@ function renderApiIndexPage(pkgName, symbols) {
1484
1762
  const functions = exported.filter((s) => FUNCTION_KINDS.has(s.kind));
1485
1763
  const types = exported.filter((s) => TYPE_KINDS.has(s.kind));
1486
1764
  const others = exported.filter((s) => !FUNCTION_KINDS.has(s.kind) && !TYPE_KINDS.has(s.kind));
1487
- const lines = [];
1765
+ const nodes = [];
1488
1766
  const pkgDoc = symbols.map((s) => s.documentation?.tags?.packageDocumentation?.[0]).find(Boolean);
1489
1767
  if (pkgDoc) {
1490
- lines.push(pkgDoc);
1491
- lines.push("");
1768
+ nodes.push(md.paragraph(...parseInline(pkgDoc)));
1492
1769
  } else {
1493
- lines.push(`API reference for the \`${pkgName}\` package.`);
1494
- lines.push("");
1770
+ nodes.push(
1771
+ md.paragraph(md.text("API reference for the "), md.inlineCode(pkgName), md.text(" package."))
1772
+ );
1495
1773
  }
1496
1774
  const renderGroup = (group, heading, pathSuffix) => {
1497
1775
  if (group.length === 0) return;
1498
- lines.push(`## ${heading}`);
1499
- lines.push("");
1500
- lines.push("| Symbol | Kind | Description |");
1501
- lines.push("|--------|------|-------------|");
1776
+ nodes.push(md.heading(2, md.text(heading)));
1777
+ const headerRow = md.tableRow(
1778
+ md.tableCell(md.text("Symbol")),
1779
+ md.tableCell(md.text("Kind")),
1780
+ md.tableCell(md.text("Description"))
1781
+ );
1782
+ const dataRows = [];
1502
1783
  for (const s of group) {
1503
1784
  const ext = s.kind === "function" ? "()" : "";
1504
- const anchor = toAnchor2(`${s.name}${ext}`);
1505
- const link = `[\`${s.name}${ext}\`](${slugLink(`packages/${pkgName}/${pathSuffix}`)}#${anchor})`;
1785
+ const anchor = toAnchor(`${s.name}${ext}`);
1506
1786
  const rawSummary = s.documentation?.summary ?? "";
1507
- const summary = escapePipe(truncate(rawSummary));
1508
- lines.push(`| ${link} | ${s.kind} | ${summary} |`);
1787
+ const summary = truncate(rawSummary);
1788
+ dataRows.push(
1789
+ md.tableRow(
1790
+ md.tableCell(
1791
+ md.link(
1792
+ `${slugLink(`packages/${pkgName}/${pathSuffix}`)}#${anchor}`,
1793
+ md.inlineCode(`${s.name}${ext}`)
1794
+ )
1795
+ ),
1796
+ md.tableCell(md.text(s.kind)),
1797
+ md.tableCell(...parseInline(summary))
1798
+ )
1799
+ );
1509
1800
  }
1510
- lines.push("");
1801
+ nodes.push(md.table(null, headerRow, ...dataRows));
1511
1802
  };
1512
1803
  renderGroup(functions, "Functions & Classes", "api/functions");
1513
1804
  renderGroup(types, "Types & Interfaces", "api/types");
1514
1805
  renderGroup(others, "Other Exports", "api/functions");
1515
- return lines.join("\n");
1806
+ return serializeMarkdown(md.root(...nodes));
1516
1807
  }
1517
1808
  function renderPackageOverviewPage(packageName, symbols, _options) {
1518
1809
  const exported = symbols.filter(
1519
1810
  (s) => s.exported && s.kind !== "method" && s.kind !== "property"
1520
1811
  );
1521
1812
  const pkgDoc = symbols.map((s) => s.documentation?.tags?.packageDocumentation?.[0]).find(Boolean);
1522
- const lines = [];
1813
+ const nodes = [];
1523
1814
  if (pkgDoc) {
1524
- lines.push(pkgDoc);
1525
- lines.push("");
1815
+ nodes.push(md.paragraph(...parseInline(pkgDoc)));
1526
1816
  }
1527
1817
  if (exported.length > 0) {
1528
1818
  const functions = exported.filter((s) => FUNCTION_KINDS.has(s.kind));
@@ -1530,96 +1820,127 @@ function renderPackageOverviewPage(packageName, symbols, _options) {
1530
1820
  const others = exported.filter((s) => !FUNCTION_KINDS.has(s.kind) && !TYPE_KINDS.has(s.kind));
1531
1821
  const renderGroup = (group, heading) => {
1532
1822
  if (group.length === 0) return;
1533
- lines.push(`## ${heading}`);
1534
- lines.push("");
1535
- lines.push("| Symbol | Kind | Description |");
1536
- lines.push("|--------|------|-------------|");
1823
+ nodes.push(md.heading(2, md.text(heading)));
1824
+ const headerRow = md.tableRow(
1825
+ md.tableCell(md.text("Symbol")),
1826
+ md.tableCell(md.text("Kind")),
1827
+ md.tableCell(md.text("Description"))
1828
+ );
1829
+ const dataRows = [];
1537
1830
  for (const s of group) {
1538
1831
  const ext = s.kind === "function" ? "()" : "";
1539
- const name = `[\`${s.name}${ext}\`](${slugLink(`packages/${packageName}/api/index`)}#${toAnchor2(`${s.name}${ext}`)})`;
1832
+ const anchor = toAnchor(`${s.name}${ext}`);
1540
1833
  const rawSummary = s.documentation?.summary ?? "";
1541
- const summary = escapePipe(truncate(rawSummary));
1542
- lines.push(`| ${name} | ${s.kind} | ${summary} |`);
1834
+ const summary = truncate(rawSummary);
1835
+ dataRows.push(
1836
+ md.tableRow(
1837
+ md.tableCell(
1838
+ md.link(
1839
+ `${slugLink(`packages/${packageName}/api/index`)}#${anchor}`,
1840
+ md.inlineCode(`${s.name}${ext}`)
1841
+ )
1842
+ ),
1843
+ md.tableCell(md.text(s.kind)),
1844
+ md.tableCell(...parseInline(summary))
1845
+ )
1846
+ );
1543
1847
  }
1544
- lines.push("");
1848
+ nodes.push(md.table(null, headerRow, ...dataRows));
1545
1849
  };
1546
1850
  renderGroup(functions, "Functions & Classes");
1547
1851
  renderGroup(types, "Types & Interfaces");
1548
1852
  renderGroup(others, "Other Exports");
1549
1853
  }
1550
- return lines.join("\n");
1854
+ return serializeMarkdown(md.root(...nodes));
1551
1855
  }
1552
1856
  function renderTypesPage(packageName, symbols, _options) {
1553
1857
  const typeSymbols = symbols.filter((s) => s.exported && TYPE_KINDS.has(s.kind));
1554
- const lines = [];
1555
- lines.push("Type contracts exported by this package: interfaces, type aliases, and enums.");
1556
- lines.push("");
1858
+ const nodes = [];
1859
+ nodes.push(
1860
+ textP("Type contracts exported by this package: interfaces, type aliases, and enums.")
1861
+ );
1557
1862
  for (const s of typeSymbols) {
1558
- lines.push(`## ${s.name}`);
1559
- lines.push("");
1863
+ nodes.push(md.heading(2, md.text(s.name)));
1560
1864
  if (s.documentation?.deprecated) {
1561
- lines.push(`> **Deprecated**: ${s.documentation.deprecated}`);
1562
- lines.push("");
1865
+ nodes.push(
1866
+ md.blockquote(
1867
+ md.paragraph(
1868
+ md.strong(md.text("Deprecated")),
1869
+ md.text(": "),
1870
+ ...parseInline(s.documentation.deprecated)
1871
+ )
1872
+ )
1873
+ );
1563
1874
  }
1564
1875
  if (s.documentation?.summary) {
1565
- lines.push(s.documentation.summary);
1566
- lines.push("");
1876
+ nodes.push(md.paragraph(...parseInline(s.documentation.summary)));
1567
1877
  }
1568
1878
  if (s.signature && s.kind !== "interface") {
1569
- lines.push("```typescript");
1570
- lines.push(s.signature);
1571
- lines.push("```");
1572
- lines.push("");
1879
+ nodes.push(md.code("typescript", s.signature));
1573
1880
  }
1574
1881
  const children = (s.children ?? []).filter((c) => c.kind === "property" || c.kind === "method");
1575
1882
  if (children.length > 0) {
1576
- lines.push("| Property | Type | Required | Description |");
1577
- lines.push("|----------|------|----------|-------------|");
1883
+ const headerRow = md.tableRow(
1884
+ md.tableCell(md.text("Property")),
1885
+ md.tableCell(md.text("Type")),
1886
+ md.tableCell(md.text("Required")),
1887
+ md.tableCell(md.text("Description"))
1888
+ );
1889
+ const dataRows = [];
1578
1890
  for (const child of children) {
1579
- const name = `\`${child.name}\``;
1580
- const type = child.signature ? `\`${escapePipe(child.signature)}\`` : "\u2014";
1891
+ const typePhrasing = child.signature ? md.inlineCode(child.signature) : md.text("\u2014");
1581
1892
  const optional = child.signature?.includes("?") || child.signature?.includes("undefined") ? "No" : "Yes";
1582
- const description = escapePipe(child.documentation?.summary || child.name);
1583
- lines.push(`| ${name} | ${type} | ${optional} | ${description} |`);
1893
+ const description = child.documentation?.summary || child.name;
1894
+ dataRows.push(
1895
+ md.tableRow(
1896
+ md.tableCell(md.inlineCode(child.name)),
1897
+ md.tableCell(typePhrasing),
1898
+ md.tableCell(md.text(optional)),
1899
+ md.tableCell(...parseInline(description))
1900
+ )
1901
+ );
1584
1902
  }
1585
- lines.push("");
1903
+ nodes.push(md.table(null, headerRow, ...dataRows));
1586
1904
  }
1587
1905
  }
1588
1906
  void packageName;
1589
- return lines.join("\n");
1907
+ return serializeMarkdown(md.root(...nodes));
1590
1908
  }
1591
1909
  function renderFunctionsPage(packageName, symbols, _options) {
1592
1910
  const fnSymbols = symbols.filter((s) => s.exported && FUNCTION_KINDS.has(s.kind));
1593
- const lines = [];
1594
- lines.push("Functions and classes exported by this package.");
1595
- lines.push("");
1911
+ const nodes = [];
1912
+ nodes.push(textP("Functions and classes exported by this package."));
1596
1913
  for (const s of fnSymbols) {
1597
1914
  const paramSig = s.kind === "function" && s.documentation?.params ? s.documentation.params.map((p) => p.name).join(", ") : "";
1598
1915
  const heading = s.kind === "function" ? `${s.name}(${paramSig})` : s.name;
1599
- lines.push(`## ${heading}`);
1600
- lines.push("");
1916
+ nodes.push(md.heading(2, md.text(heading)));
1601
1917
  if (s.documentation?.deprecated) {
1602
- lines.push(`> **Deprecated**: ${s.documentation.deprecated}`);
1603
- lines.push("");
1918
+ nodes.push(
1919
+ md.blockquote(
1920
+ md.paragraph(
1921
+ md.strong(md.text("Deprecated")),
1922
+ md.text(": "),
1923
+ ...parseInline(s.documentation.deprecated)
1924
+ )
1925
+ )
1926
+ );
1604
1927
  }
1605
1928
  if (s.documentation?.summary) {
1606
- lines.push(s.documentation.summary);
1607
- lines.push("");
1929
+ nodes.push(md.paragraph(...parseInline(s.documentation.summary)));
1608
1930
  }
1609
1931
  if (s.signature) {
1610
- lines.push("**Signature**");
1611
- lines.push("");
1612
- lines.push("```typescript");
1613
- lines.push(s.signature);
1614
- lines.push("```");
1615
- lines.push("");
1932
+ nodes.push(md.paragraph(md.strong(md.text("Signature"))));
1933
+ nodes.push(md.code("typescript", s.signature));
1616
1934
  }
1617
1935
  const params = s.documentation?.params ?? [];
1618
1936
  if (params.length > 0) {
1619
- lines.push("**Parameters**");
1620
- lines.push("");
1621
- lines.push("| Name | Type | Description |");
1622
- lines.push("|------|------|-------------|");
1937
+ nodes.push(md.paragraph(md.strong(md.text("Parameters"))));
1938
+ const headerRow = md.tableRow(
1939
+ md.tableCell(md.text("Name")),
1940
+ md.tableCell(md.text("Type")),
1941
+ md.tableCell(md.text("Description"))
1942
+ );
1943
+ const dataRows = [];
1623
1944
  for (const p of params) {
1624
1945
  let resolvedType = p.type;
1625
1946
  if (!resolvedType && s.signature) {
@@ -1628,183 +1949,254 @@ function renderFunctionsPage(packageName, symbols, _options) {
1628
1949
  resolvedType = typeMatch[1].trim();
1629
1950
  }
1630
1951
  }
1631
- const type = resolvedType ? `\`${escapePipe(resolvedType)}\`` : "\u2014";
1632
- lines.push(`| \`${p.name}\` | ${type} | ${escapePipe(p.description)} |`);
1952
+ const typePhrasing = resolvedType ? md.inlineCode(resolvedType) : md.text("\u2014");
1953
+ dataRows.push(
1954
+ md.tableRow(
1955
+ md.tableCell(md.inlineCode(p.name)),
1956
+ md.tableCell(typePhrasing),
1957
+ md.tableCell(...parseInline(p.description))
1958
+ )
1959
+ );
1633
1960
  }
1634
- lines.push("");
1961
+ nodes.push(md.table(null, headerRow, ...dataRows));
1635
1962
  }
1636
1963
  if (s.documentation?.returns) {
1637
- const retType = s.documentation.returns.type ? ` \`${s.documentation.returns.type}\`` : "";
1638
- lines.push(`**Returns**${retType} \u2014 ${s.documentation.returns.description}`);
1639
- lines.push("");
1964
+ const retParts = [md.strong(md.text("Returns"))];
1965
+ if (s.documentation.returns.type) {
1966
+ retParts.push(md.text(" "));
1967
+ retParts.push(md.inlineCode(s.documentation.returns.type));
1968
+ }
1969
+ retParts.push(md.text(" \u2014 "), ...parseInline(s.documentation.returns.description));
1970
+ nodes.push(md.paragraph(...retParts));
1640
1971
  }
1641
1972
  const throws = s.documentation?.throws ?? [];
1642
1973
  if (throws.length > 0) {
1643
- lines.push("**Throws**");
1644
- lines.push("");
1974
+ nodes.push(md.paragraph(md.strong(md.text("Throws"))));
1975
+ const throwItems = [];
1645
1976
  for (const t of throws) {
1646
- const typeStr = t.type ? `\`${t.type}\` \u2014 ` : "";
1647
- lines.push(`- ${typeStr}${t.description}`);
1977
+ const throwParts = [];
1978
+ if (t.type) {
1979
+ throwParts.push(md.inlineCode(t.type));
1980
+ throwParts.push(md.text(" \u2014 "), ...parseInline(t.description));
1981
+ } else {
1982
+ throwParts.push(...parseInline(t.description));
1983
+ }
1984
+ throwItems.push(md.listItem(md.paragraph(...throwParts)));
1648
1985
  }
1649
- lines.push("");
1986
+ nodes.push(md.list(throwItems));
1650
1987
  }
1651
1988
  const examples = s.documentation?.examples ?? [];
1652
1989
  if (examples.length > 0) {
1653
1990
  const ex = examples[0];
1654
- lines.push("**Example**");
1655
- lines.push("");
1656
- lines.push(`\`\`\`${ex.language || "typescript"}`);
1657
- lines.push(ex.code.trim());
1658
- lines.push("```");
1659
- lines.push("");
1991
+ nodes.push(md.paragraph(md.strong(md.text("Example"))));
1992
+ nodes.push(md.code(ex.language || "typescript", ex.code.trim()));
1660
1993
  }
1661
1994
  const methods = (s.children ?? []).filter((c) => c.kind === "method");
1662
1995
  if (methods.length > 0) {
1663
- lines.push("**Methods**");
1664
- lines.push("");
1996
+ nodes.push(md.paragraph(md.strong(md.text("Methods"))));
1665
1997
  for (const method of methods) {
1666
- lines.push(`### ${method.name}()`);
1667
- lines.push("");
1998
+ nodes.push(md.heading(3, md.text(`${method.name}()`)));
1668
1999
  if (method.documentation?.summary) {
1669
- lines.push(method.documentation.summary);
1670
- lines.push("");
2000
+ nodes.push(md.paragraph(...parseInline(method.documentation.summary)));
1671
2001
  }
1672
2002
  if (method.signature) {
1673
- lines.push("```typescript");
1674
- lines.push(method.signature);
1675
- lines.push("```");
1676
- lines.push("");
2003
+ nodes.push(md.code("typescript", method.signature));
1677
2004
  }
1678
2005
  }
1679
2006
  }
1680
2007
  }
1681
2008
  void packageName;
1682
- return lines.join("\n");
2009
+ return serializeMarkdown(md.root(...nodes));
1683
2010
  }
1684
2011
  function renderExamplesPage(packageName, symbols, _options) {
1685
2012
  const exported = symbols.filter(
1686
2013
  (s) => s.exported && s.kind !== "method" && s.kind !== "property"
1687
2014
  );
1688
- const lines = [];
1689
- lines.push("All usage examples from the package, aggregated for quick reference.");
1690
- lines.push("");
2015
+ const nodes = [];
2016
+ nodes.push(textP("All usage examples from the package, aggregated for quick reference."));
1691
2017
  let hasExamples = false;
1692
2018
  for (const s of exported) {
1693
2019
  const examples = s.documentation?.examples ?? [];
1694
2020
  if (examples.length === 0) continue;
1695
2021
  hasExamples = true;
1696
2022
  const ext = s.kind === "function" ? "()" : "";
1697
- lines.push(`## \`${s.name}${ext}\``);
1698
- lines.push("");
2023
+ nodes.push(md.heading(2, md.inlineCode(`${s.name}${ext}`)));
1699
2024
  if (s.documentation?.summary) {
1700
- lines.push(`_${s.documentation.summary}_`);
1701
- lines.push("");
1702
- }
1703
- lines.push(
1704
- `[View in API reference](${slugLink(`packages/${packageName}/api/functions`)}#${toAnchor2(s.name)})`
2025
+ nodes.push(md.paragraph(md.emphasis(...parseInline(s.documentation.summary))));
2026
+ }
2027
+ nodes.push(
2028
+ md.paragraph(
2029
+ md.link(
2030
+ `${slugLink(`packages/${packageName}/api/functions`)}#${toAnchor(s.name)}`,
2031
+ md.text("View in API reference")
2032
+ )
2033
+ )
1705
2034
  );
1706
- lines.push("");
1707
2035
  for (const ex of examples) {
1708
- lines.push(`\`\`\`${ex.language || "typescript"}`);
1709
- lines.push(ex.code.trim());
1710
- lines.push("```");
1711
- lines.push("");
2036
+ nodes.push(md.code(ex.language || "typescript", ex.code.trim()));
1712
2037
  }
1713
2038
  }
1714
2039
  if (!hasExamples) {
1715
- lines.push("_No examples documented yet._");
1716
- lines.push("");
2040
+ nodes.push(md.paragraph(md.emphasis(md.text("No examples documented yet."))));
1717
2041
  }
1718
- return lines.join("\n");
2042
+ return serializeMarkdown(md.root(...nodes));
1719
2043
  }
1720
2044
  function renderConfigurationPage(symbolsByPackage, options) {
1721
2045
  const configSymbol = [...symbolsByPackage.values()].flat().find((s) => s.exported && TYPE_KINDS.has(s.kind) && /config/i.test(s.name));
1722
- const lines = [];
1723
- lines.push(`Configuration reference for **${options.projectName}**.`);
1724
- lines.push("");
1725
- lines.push("## forge-ts.config.ts");
1726
- lines.push("");
1727
- lines.push("Create a `forge-ts.config.ts` file in your project root:");
1728
- lines.push("");
1729
- lines.push("```typescript");
1730
- lines.push('import { defineConfig } from "@forge-ts/core";');
1731
- lines.push("");
1732
- lines.push("export default defineConfig({");
1733
- lines.push(' rootDir: ".",');
1734
- lines.push(' outDir: "docs/generated",');
1735
- lines.push("});");
1736
- lines.push("```");
1737
- lines.push("");
2046
+ const nodes = [];
2047
+ nodes.push(
2048
+ md.paragraph(
2049
+ md.text("Configuration reference for "),
2050
+ md.strong(md.text(options.projectName)),
2051
+ md.text(".")
2052
+ )
2053
+ );
2054
+ nodes.push(md.heading(2, md.text("forge-ts.config.ts")));
2055
+ nodes.push(
2056
+ md.paragraph(
2057
+ md.text("Create a "),
2058
+ md.inlineCode("forge-ts.config.ts"),
2059
+ md.text(" file in your project root:")
2060
+ )
2061
+ );
2062
+ nodes.push(
2063
+ md.code(
2064
+ "typescript",
2065
+ [
2066
+ 'import { defineConfig } from "@forge-ts/core";',
2067
+ "",
2068
+ "export default defineConfig({",
2069
+ ' rootDir: ".",',
2070
+ ' outDir: "docs/generated",',
2071
+ "});"
2072
+ ].join("\n")
2073
+ )
2074
+ );
1738
2075
  if (configSymbol) {
1739
- lines.push(`## \`${configSymbol.name}\``);
1740
- lines.push("");
2076
+ nodes.push(md.heading(2, md.inlineCode(configSymbol.name)));
1741
2077
  if (configSymbol.documentation?.summary) {
1742
- lines.push(configSymbol.documentation.summary);
1743
- lines.push("");
2078
+ nodes.push(md.paragraph(...parseInline(configSymbol.documentation.summary)));
1744
2079
  }
1745
2080
  const children = (configSymbol.children ?? []).filter(
1746
2081
  (c) => c.kind === "property" || c.kind === "method"
1747
2082
  );
1748
2083
  if (children.length > 0) {
1749
- lines.push("| Property | Type | Required | Description |");
1750
- lines.push("|----------|------|----------|-------------|");
2084
+ const headerRow = md.tableRow(
2085
+ md.tableCell(md.text("Property")),
2086
+ md.tableCell(md.text("Type")),
2087
+ md.tableCell(md.text("Required")),
2088
+ md.tableCell(md.text("Description"))
2089
+ );
2090
+ const dataRows = [];
1751
2091
  for (const child of children) {
1752
- const name = `\`${child.name}\``;
1753
- const type = child.signature ? `\`${escapePipe(child.signature)}\`` : "\u2014";
2092
+ const typePhrasing = child.signature ? md.inlineCode(child.signature) : md.text("\u2014");
1754
2093
  const optional = child.signature?.includes("?") || child.signature?.includes("undefined") ? "No" : "Yes";
1755
- const description = escapePipe(child.documentation?.summary || child.name);
1756
- lines.push(`| ${name} | ${type} | ${optional} | ${description} |`);
2094
+ const description = child.documentation?.summary || child.name;
2095
+ dataRows.push(
2096
+ md.tableRow(
2097
+ md.tableCell(md.inlineCode(child.name)),
2098
+ md.tableCell(typePhrasing),
2099
+ md.tableCell(md.text(optional)),
2100
+ md.tableCell(...parseInline(description))
2101
+ )
2102
+ );
1757
2103
  }
1758
- lines.push("");
2104
+ nodes.push(md.table(null, headerRow, ...dataRows));
1759
2105
  }
1760
2106
  }
1761
- return lines.join("\n");
2107
+ return serializeMarkdown(md.root(...nodes));
1762
2108
  }
1763
2109
  function renderChangelogPage(options) {
1764
2110
  const repoUrl = options.repositoryUrl ?? "";
1765
- const changelogLink = repoUrl ? `See [CHANGELOG.md](${repoUrl}/blob/main/CHANGELOG.md) for the full release history.` : "See your project's `CHANGELOG.md` for the full release history.";
1766
- return [
1767
- `Release history for **${options.projectName}**.`,
1768
- "",
1769
- "> This is a stub page. Link to or embed your `CHANGELOG.md` here.",
1770
- "",
1771
- changelogLink,
1772
- ""
1773
- ].join("\n");
2111
+ const nodes = [];
2112
+ nodes.push(
2113
+ md.paragraph(
2114
+ md.text("Release history for "),
2115
+ md.strong(md.text(options.projectName)),
2116
+ md.text(".")
2117
+ )
2118
+ );
2119
+ nodes.push(
2120
+ md.blockquote(textP("This is a stub page. Link to or embed your `CHANGELOG.md` here."))
2121
+ );
2122
+ if (repoUrl) {
2123
+ nodes.push(
2124
+ md.paragraph(
2125
+ md.text("See "),
2126
+ md.link(`${repoUrl}/blob/main/CHANGELOG.md`, md.text("CHANGELOG.md")),
2127
+ md.text(" for the full release history.")
2128
+ )
2129
+ );
2130
+ } else {
2131
+ nodes.push(
2132
+ md.paragraph(
2133
+ md.text("See your project's "),
2134
+ md.inlineCode("CHANGELOG.md"),
2135
+ md.text(" for the full release history.")
2136
+ )
2137
+ );
2138
+ }
2139
+ return serializeMarkdown(md.root(...nodes));
1774
2140
  }
1775
2141
  function renderFaqPage(options) {
1776
- return [
1777
- `Frequently asked questions about **${options.projectName}**.`,
1778
- "",
1779
- "> This is a stub page. Common questions will be added here as they arise.",
1780
- "",
1781
- "## How do I configure forge-ts?",
1782
- "",
1783
- "Create a `forge-ts.config.ts` file in your project root. See [Configuration](/configuration).",
1784
- "",
1785
- "## What TypeScript version is required?",
1786
- "",
1787
- "forge-ts requires TypeScript 5.0 or later.",
1788
- "",
1789
- "## How do I run @example blocks as tests?",
1790
- "",
1791
- "```bash",
1792
- "npx forge-ts test",
1793
- "```",
1794
- ""
1795
- ].join("\n");
2142
+ const nodes = [];
2143
+ nodes.push(
2144
+ md.paragraph(
2145
+ md.text("Frequently asked questions about "),
2146
+ md.strong(md.text(options.projectName)),
2147
+ md.text(".")
2148
+ )
2149
+ );
2150
+ nodes.push(
2151
+ md.blockquote(textP("This is a stub page. Common questions will be added here as they arise."))
2152
+ );
2153
+ nodes.push(md.heading(2, md.text("How do I configure forge-ts?")));
2154
+ nodes.push(
2155
+ md.paragraph(
2156
+ md.text("Create a "),
2157
+ md.inlineCode("forge-ts.config.ts"),
2158
+ md.text(" file in your project root. See "),
2159
+ md.link("/configuration", md.text("Configuration")),
2160
+ md.text(".")
2161
+ )
2162
+ );
2163
+ nodes.push(md.heading(2, md.text("What TypeScript version is required?")));
2164
+ nodes.push(textP("forge-ts requires TypeScript 5.0 or later."));
2165
+ nodes.push(md.heading(2, md.text("How do I run @example blocks as tests?")));
2166
+ nodes.push(md.code("bash", "npx forge-ts test"));
2167
+ return serializeMarkdown(md.root(...nodes));
1796
2168
  }
1797
2169
  function renderContributingPage(options) {
1798
2170
  const repoUrl = options.repositoryUrl ?? "";
1799
- const contribLink = repoUrl ? `See [CONTRIBUTING.md](${repoUrl}/blob/main/CONTRIBUTING.md) for contribution guidelines.` : "See your project's `CONTRIBUTING.md` for contribution guidelines.";
1800
- return [
1801
- `Contributing to **${options.projectName}**.`,
1802
- "",
1803
- "> This is a stub page. Link to or embed your `CONTRIBUTING.md` here.",
1804
- "",
1805
- contribLink,
1806
- ""
1807
- ].join("\n");
2171
+ const nodes = [];
2172
+ nodes.push(
2173
+ md.paragraph(
2174
+ md.text("Contributing to "),
2175
+ md.strong(md.text(options.projectName)),
2176
+ md.text(".")
2177
+ )
2178
+ );
2179
+ nodes.push(
2180
+ md.blockquote(textP("This is a stub page. Link to or embed your `CONTRIBUTING.md` here."))
2181
+ );
2182
+ if (repoUrl) {
2183
+ nodes.push(
2184
+ md.paragraph(
2185
+ md.text("See "),
2186
+ md.link(`${repoUrl}/blob/main/CONTRIBUTING.md`, md.text("CONTRIBUTING.md")),
2187
+ md.text(" for contribution guidelines.")
2188
+ )
2189
+ );
2190
+ } else {
2191
+ nodes.push(
2192
+ md.paragraph(
2193
+ md.text("See your project's "),
2194
+ md.inlineCode("CONTRIBUTING.md"),
2195
+ md.text(" for contribution guidelines.")
2196
+ )
2197
+ );
2198
+ }
2199
+ return serializeMarkdown(md.root(...nodes));
1808
2200
  }
1809
2201
  function generateDocSite(symbolsByPackage, config, options) {
1810
2202
  const ext = options.format === "mdx" ? "mdx" : "md";
@@ -2679,39 +3071,6 @@ import { existsSync as existsSync2 } from "fs";
2679
3071
  import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
2680
3072
  import { dirname, join } from "path";
2681
3073
  import { createWalker } from "@forge-ts/core";
2682
- function updateAutoSections(existing, generated) {
2683
- const htmlPattern = /<!-- FORGE:AUTO-START (\S+) -->([\s\S]*?)<!-- FORGE:AUTO-END \1 -->/g;
2684
- const mdxGenPattern = /\{\/\*\s*FORGE:AUTO-START (\S+)\s*\*\/\}([\s\S]*?)\{\/\*\s*FORGE:AUTO-END \1\s*\*\/\}/g;
2685
- const newSections = /* @__PURE__ */ new Map();
2686
- let match;
2687
- while ((match = htmlPattern.exec(generated)) !== null) {
2688
- newSections.set(match[1], match[0]);
2689
- }
2690
- while ((match = mdxGenPattern.exec(generated)) !== null) {
2691
- if (!newSections.has(match[1])) {
2692
- newSections.set(match[1], match[0]);
2693
- }
2694
- }
2695
- if (newSections.size === 0) return null;
2696
- let updated = existing;
2697
- let changed = false;
2698
- for (const [id, replacement] of newSections) {
2699
- const htmlSectionPattern = new RegExp(
2700
- `<!-- FORGE:AUTO-START ${id} -->[\\s\\S]*?<!-- FORGE:AUTO-END ${id} -->`
2701
- );
2702
- const mdxSectionPattern = new RegExp(
2703
- `\\{/\\*\\s*FORGE:AUTO-START ${id}\\s*\\*/\\}[\\s\\S]*?\\{/\\*\\s*FORGE:AUTO-END ${id}\\s*\\*/\\}`
2704
- );
2705
- if (htmlSectionPattern.test(updated)) {
2706
- updated = updated.replace(htmlSectionPattern, replacement);
2707
- changed = true;
2708
- } else if (mdxSectionPattern.test(updated)) {
2709
- updated = updated.replace(mdxSectionPattern, replacement);
2710
- changed = true;
2711
- }
2712
- }
2713
- return changed ? updated : null;
2714
- }
2715
3074
  async function generate(config, options) {
2716
3075
  const start = Date.now();
2717
3076
  const forceStubs = options?.forceStubs ?? false;
@@ -2824,7 +3183,18 @@ export {
2824
3183
  getAdapter,
2825
3184
  getAvailableTargets,
2826
3185
  groupSymbolsByPackage,
3186
+ md,
3187
+ parseBlocks,
3188
+ parseFrontmatter,
3189
+ parseInline,
2827
3190
  registerAdapter,
2828
- syncReadme
3191
+ sanitizeForMdx,
3192
+ serializeMarkdown,
3193
+ slugLink,
3194
+ stringifyWithFrontmatter,
3195
+ stripFrontmatter,
3196
+ syncReadme,
3197
+ toAnchor,
3198
+ truncate
2829
3199
  };
2830
3200
  //# sourceMappingURL=index.js.map