@dominikcz/greg 0.9.27

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.
Files changed (183) hide show
  1. package/README.md +397 -0
  2. package/bin/greg.js +241 -0
  3. package/bin/init.js +351 -0
  4. package/bin/templates/docs/getting-started.md +47 -0
  5. package/bin/templates/docs/index.md +11 -0
  6. package/bin/templates/greg.config.js +39 -0
  7. package/bin/templates/greg.config.ts +38 -0
  8. package/bin/templates/index.html +16 -0
  9. package/bin/templates/src/App.svelte +5 -0
  10. package/bin/templates/src/app.css +20 -0
  11. package/bin/templates/src/main.js +9 -0
  12. package/bin/templates/svelte.config.js +1 -0
  13. package/bin/templates/tsconfig.json +21 -0
  14. package/bin/templates/vite.config.js +23 -0
  15. package/docs/__partials/markdown/examples/basic.md +4 -0
  16. package/docs/__partials/markdown/examples/diff.md +10 -0
  17. package/docs/__partials/markdown/examples/focus.md +5 -0
  18. package/docs/__partials/markdown/examples/language-title.md +3 -0
  19. package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
  20. package/docs/__partials/markdown/examples/line-numbers.md +5 -0
  21. package/docs/__partials/note.md +4 -0
  22. package/docs/guide/__shared-warning.md +4 -0
  23. package/docs/guide/asset-handling.md +88 -0
  24. package/docs/guide/deploying.md +162 -0
  25. package/docs/guide/getting-started.md +334 -0
  26. package/docs/guide/index.md +23 -0
  27. package/docs/guide/localization.md +290 -0
  28. package/docs/guide/markdown/code.md +95 -0
  29. package/docs/guide/markdown/components-and-mermaid.md +43 -0
  30. package/docs/guide/markdown/containers.md +110 -0
  31. package/docs/guide/markdown/header-anchors.md +34 -0
  32. package/docs/guide/markdown/includes.md +84 -0
  33. package/docs/guide/markdown/index.md +20 -0
  34. package/docs/guide/markdown/inline-attributes.md +21 -0
  35. package/docs/guide/markdown/links-and-toc.md +64 -0
  36. package/docs/guide/markdown/math.md +54 -0
  37. package/docs/guide/markdown/syntax-highlighting.md +75 -0
  38. package/docs/guide/routing.md +150 -0
  39. package/docs/guide/using-svelte.md +88 -0
  40. package/docs/guide/versioning.md +281 -0
  41. package/docs/incompatibilities.md +48 -0
  42. package/docs/index.md +43 -0
  43. package/docs/reference/badge.md +100 -0
  44. package/docs/reference/carbon-ads.md +46 -0
  45. package/docs/reference/code-group.md +126 -0
  46. package/docs/reference/home-page.md +232 -0
  47. package/docs/reference/index.md +18 -0
  48. package/docs/reference/markdowndocs.md +275 -0
  49. package/docs/reference/outline.md +79 -0
  50. package/docs/reference/search.md +263 -0
  51. package/docs/reference/steps.md +200 -0
  52. package/docs/reference/team-page.md +189 -0
  53. package/docs/reference/theme.md +150 -0
  54. package/fakeDocsGenerator/generate_docs.js +310 -0
  55. package/package.json +92 -0
  56. package/scripts/build-versions.js +609 -0
  57. package/scripts/generate-static.js +79 -0
  58. package/scripts/render-markdown.js +420 -0
  59. package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
  60. package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
  61. package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
  62. package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
  63. package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
  64. package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
  65. package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
  66. package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
  67. package/src/lib/MarkdownDocs/Outline.svelte +238 -0
  68. package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
  69. package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
  70. package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
  71. package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
  72. package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
  73. package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
  74. package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
  75. package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
  76. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
  77. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
  78. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
  79. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
  80. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
  81. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
  82. package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
  83. package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
  84. package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
  85. package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
  86. package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
  87. package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
  88. package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
  89. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
  90. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
  91. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
  92. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
  93. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
  94. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
  95. package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
  96. package/src/lib/MarkdownDocs/ai/characters.js +52 -0
  97. package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
  98. package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
  99. package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
  100. package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
  101. package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
  102. package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
  103. package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
  104. package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
  105. package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
  106. package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
  107. package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
  108. package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
  109. package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
  110. package/src/lib/MarkdownDocs/ai/types.ts +71 -0
  111. package/src/lib/MarkdownDocs/aiServer.js +288 -0
  112. package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
  113. package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
  114. package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
  115. package/src/lib/MarkdownDocs/common.ts +47 -0
  116. package/src/lib/MarkdownDocs/docsUtils.js +281 -0
  117. package/src/lib/MarkdownDocs/index.plugins.js +22 -0
  118. package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
  119. package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
  120. package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
  121. package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
  122. package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
  123. package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
  124. package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
  125. package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
  126. package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
  127. package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
  128. package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
  129. package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
  130. package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
  131. package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
  132. package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
  133. package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
  134. package/src/lib/MarkdownDocs/remarkImports.js +461 -0
  135. package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
  136. package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
  137. package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
  138. package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
  139. package/src/lib/MarkdownDocs/searchServer.js +263 -0
  140. package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
  141. package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
  142. package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
  143. package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
  144. package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
  145. package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
  146. package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
  147. package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
  148. package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
  149. package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
  150. package/src/lib/components/Badge.svelte +59 -0
  151. package/src/lib/components/Button.svelte +138 -0
  152. package/src/lib/components/CarbonAds.svelte +99 -0
  153. package/src/lib/components/CodeGroup.svelte +102 -0
  154. package/src/lib/components/Feature.svelte +209 -0
  155. package/src/lib/components/Features.svelte +123 -0
  156. package/src/lib/components/Hero.svelte +399 -0
  157. package/src/lib/components/Image.svelte +128 -0
  158. package/src/lib/components/Link.svelte +105 -0
  159. package/src/lib/components/SocialLink.svelte +84 -0
  160. package/src/lib/components/SocialLinks.svelte +33 -0
  161. package/src/lib/components/Steps.svelte +143 -0
  162. package/src/lib/components/TeamMember.svelte +273 -0
  163. package/src/lib/components/TeamMembers.svelte +81 -0
  164. package/src/lib/components/TeamPage.svelte +65 -0
  165. package/src/lib/components/TeamPageSection.svelte +108 -0
  166. package/src/lib/components/TeamPageTitle.svelte +89 -0
  167. package/src/lib/components/index.js +24 -0
  168. package/src/lib/portal/context.js +12 -0
  169. package/src/lib/portal/index.js +3 -0
  170. package/src/lib/portal/portal.svelte +14 -0
  171. package/src/lib/portal/slot.svelte +8 -0
  172. package/src/lib/scss/__code.scss +128 -0
  173. package/src/lib/scss/__containers.scss +99 -0
  174. package/src/lib/scss/__markdown.scss +447 -0
  175. package/src/lib/scss/__scrollbar.scss +60 -0
  176. package/src/lib/scss/__steps.scss +100 -0
  177. package/src/lib/scss/__theme.scss +238 -0
  178. package/src/lib/scss/__toc.scss +55 -0
  179. package/src/lib/scss/__utilities.scss +7 -0
  180. package/src/lib/scss/greg.scss +9 -0
  181. package/src/lib/spinner/spinner.svelte +42 -0
  182. package/svelte.config.js +146 -0
  183. package/types/index.d.ts +456 -0
@@ -0,0 +1,420 @@
1
+ /**
2
+ * scripts/render-markdown.js
3
+ *
4
+ * Build a "human-readable" markdown export by resolving Greg include/snippet
5
+ * directives for every docs page.
6
+ *
7
+ * Supported directives:
8
+ * - <<< ./snippet.js{1,20 js} [Optional title]
9
+ * - <!-- @include: ./other.md -->
10
+ *
11
+ * Usage:
12
+ * node scripts/render-markdown.js
13
+ * node scripts/render-markdown.js --docsDir docs --outDir dist/resolved-markdown
14
+ */
15
+
16
+ import fs from 'node:fs/promises';
17
+ import path from 'node:path';
18
+
19
+ const SNIPPET_RE = /^<<<\s+(.+)$/;
20
+ const INCLUDE_RE = /^<!--\s*@include:\s*([^\s]+)\s*-->$/;
21
+ const TITLE_RE = /\s+\[([^\]]+)\]\s*$/;
22
+ const BRACES_RE = /\{([^}]*)\}\s*$/;
23
+ const REGION_RE = /^(.*?)#([^#{]+)$/;
24
+
25
+ const extToLang = {
26
+ '.js': 'js',
27
+ '.mjs': 'js',
28
+ '.cjs': 'js',
29
+ '.ts': 'ts',
30
+ '.tsx': 'tsx',
31
+ '.jsx': 'jsx',
32
+ '.json': 'json',
33
+ '.css': 'css',
34
+ '.scss': 'scss',
35
+ '.html': 'html',
36
+ '.md': 'md',
37
+ '.sh': 'sh',
38
+ '.bash': 'bash',
39
+ '.yml': 'yaml',
40
+ '.yaml': 'yaml',
41
+ };
42
+
43
+ function parseArgs(argv) {
44
+ const out = {
45
+ docsDir: 'docs',
46
+ outDir: 'dist/resolved-markdown',
47
+ };
48
+
49
+ for (let i = 0; i < argv.length; i++) {
50
+ const a = argv[i];
51
+ if (a === '--docsDir' && argv[i + 1]) out.docsDir = argv[++i];
52
+ if (a === '--outDir' && argv[i + 1]) out.outDir = argv[++i];
53
+ }
54
+ return out;
55
+ }
56
+
57
+ function normalizeSlashes(value) {
58
+ return String(value).replace(/\\/g, '/');
59
+ }
60
+
61
+ function escapeRegExp(value) {
62
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
63
+ }
64
+
65
+ function parseTitle(value) {
66
+ const match = value.match(TITLE_RE);
67
+ if (!match) return { text: value.trim(), title: '' };
68
+ return { text: value.slice(0, match.index).trim(), title: match[1].trim() };
69
+ }
70
+
71
+ function parseBraces(value) {
72
+ const match = value.match(BRACES_RE);
73
+ if (!match) return { text: value.trim(), braces: '' };
74
+ return { text: value.slice(0, match.index).trim(), braces: match[1].trim() };
75
+ }
76
+
77
+ function parseRegion(value) {
78
+ const match = value.match(REGION_RE);
79
+ if (!match) return { filePart: value.trim(), regionOrAnchor: '' };
80
+ return { filePart: match[1].trim(), regionOrAnchor: match[2].trim() };
81
+ }
82
+
83
+ function parseRange(value) {
84
+ const text = String(value ?? '').trim();
85
+ if (!text) return null;
86
+ if (/^\d+$/.test(text)) {
87
+ const n = Number(text);
88
+ return { start: n, end: n };
89
+ }
90
+ const dashMatch = text.match(/^(\d+)\s*-\s*(\d+)$/);
91
+ if (dashMatch) {
92
+ return { start: Number(dashMatch[1]), end: Number(dashMatch[2]) };
93
+ }
94
+ const match = text.match(/^(\d*)\s*,\s*(\d*)$/);
95
+ if (!match) return null;
96
+ const start = match[1] ? Number(match[1]) : null;
97
+ const end = match[2] ? Number(match[2]) : null;
98
+ return { start, end };
99
+ }
100
+
101
+ function selectLines(content, rangeText) {
102
+ const range = parseRange(rangeText);
103
+ if (!range) return content;
104
+ const lines = content.split(/\r?\n/);
105
+ const start = range.start ?? 1;
106
+ const end = range.end ?? lines.length;
107
+ const from = Math.max(1, start);
108
+ const to = Math.max(from, Math.min(end, lines.length));
109
+ return lines.slice(from - 1, to).join('\n');
110
+ }
111
+
112
+ function selectRegion(content, regionName) {
113
+ if (!regionName) return content;
114
+ const lines = content.split(/\r?\n/);
115
+ const escapedName = escapeRegExp(regionName);
116
+ const startRe = new RegExp(`#region\\s+${escapedName}\\s*$`, 'i');
117
+ const endRe = new RegExp(`#endregion\\s+${escapedName}\\s*$`, 'i');
118
+
119
+ let start = -1;
120
+ let end = -1;
121
+ for (let i = 0; i < lines.length; i++) {
122
+ if (start === -1 && startRe.test(lines[i])) {
123
+ start = i;
124
+ continue;
125
+ }
126
+ if (start !== -1 && endRe.test(lines[i])) {
127
+ end = i;
128
+ break;
129
+ }
130
+ }
131
+ if (start === -1 || end === -1 || end <= start) return content;
132
+ return lines.slice(start + 1, end).join('\n');
133
+ }
134
+
135
+ function slugifyHeading(value) {
136
+ return value
137
+ .toLowerCase()
138
+ .replace(/<[^>]+>/g, '')
139
+ .replace(/[^\w\s-]/g, '')
140
+ .trim()
141
+ .replace(/\s+/g, '-');
142
+ }
143
+
144
+ function getHeadingId(rawHeadingText) {
145
+ const customIdMatch = rawHeadingText.match(/\s*\{#([^}]+)\}\s*$/);
146
+ if (customIdMatch?.[1]) return customIdMatch[1].trim().toLowerCase();
147
+ return slugifyHeading(rawHeadingText.replace(/\s*\{#([^}]+)\}\s*$/, ''));
148
+ }
149
+
150
+ function selectMarkdownSectionByAnchor(content, anchor) {
151
+ if (!anchor) return content;
152
+ const lines = content.split(/\r?\n/);
153
+ const headingRe = /^(#{1,6})\s+(.+)$/;
154
+ const wanted = anchor.toLowerCase();
155
+
156
+ let startIndex = -1;
157
+ let startDepth = 0;
158
+ for (let i = 0; i < lines.length; i++) {
159
+ const match = lines[i].match(headingRe);
160
+ if (!match) continue;
161
+ const depth = match[1].length;
162
+ const id = getHeadingId(match[2]);
163
+ if (id === wanted) {
164
+ startIndex = i;
165
+ startDepth = depth;
166
+ break;
167
+ }
168
+ }
169
+ if (startIndex === -1) return content;
170
+
171
+ let endIndex = lines.length;
172
+ for (let i = startIndex + 1; i < lines.length; i++) {
173
+ const match = lines[i].match(headingRe);
174
+ if (!match) continue;
175
+ const depth = match[1].length;
176
+ if (depth <= startDepth) {
177
+ endIndex = i;
178
+ break;
179
+ }
180
+ }
181
+
182
+ return lines.slice(startIndex, endIndex).join('\n');
183
+ }
184
+
185
+ function stripFrontmatter(content) {
186
+ return String(content).replace(/^---[\r\n][\s\S]*?[\r\n]---[\r\n]?/, '');
187
+ }
188
+
189
+ function inferLang(filePath, langOverride) {
190
+ if (langOverride) return langOverride;
191
+ const ext = path.extname(filePath).toLowerCase();
192
+ return extToLang[ext] ?? ext.replace(/^\./, '') ?? 'text';
193
+ }
194
+
195
+ function resolveImportPath(specPath, currentDir, sourceRoot, docsDir) {
196
+ if (specPath === '@') return sourceRoot;
197
+ if (specPath.startsWith('@/')) return path.resolve(sourceRoot, specPath.slice(2));
198
+ if (specPath.startsWith('@')) return path.resolve(sourceRoot, specPath.slice(1));
199
+ if (specPath.startsWith('/')) return path.resolve(sourceRoot, docsDir, specPath.slice(1));
200
+ return path.resolve(currentDir, specPath);
201
+ }
202
+
203
+ function assertInsideRoot(resolvedPath, sourceRoot) {
204
+ const rel = path.relative(sourceRoot, resolvedPath);
205
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
206
+ throw new Error(`Import path "${resolvedPath}" escapes source root "${sourceRoot}"`);
207
+ }
208
+ }
209
+
210
+ function parseSnippetSpec(raw) {
211
+ const withTitle = parseTitle(raw);
212
+ const withBraces = parseBraces(withTitle.text);
213
+ const withRegion = parseRegion(withBraces.text);
214
+
215
+ let rangePart = '';
216
+ let langOverride = '';
217
+ let metaTail = '';
218
+
219
+ if (withBraces.braces) {
220
+ const tokens = withBraces.braces.split(/\s+/).filter(Boolean);
221
+ if (tokens.length > 0 && /^(\d+|\d*\s*,\s*\d*|\d+\s*-\s*\d+)$/.test(tokens[0])) {
222
+ rangePart = tokens.shift() ?? '';
223
+ }
224
+ if (tokens.length > 0 && !tokens[0].includes(':')) {
225
+ langOverride = tokens.shift() ?? '';
226
+ }
227
+ metaTail = tokens.join(' ').trim();
228
+ }
229
+
230
+ return {
231
+ title: withTitle.title,
232
+ filePart: withRegion.filePart,
233
+ regionOrAnchor: withRegion.regionOrAnchor,
234
+ rangePart,
235
+ langOverride,
236
+ metaTail,
237
+ };
238
+ }
239
+
240
+ function buildCodeMeta(title, metaTail) {
241
+ const parts = [];
242
+ if (metaTail) parts.push(metaTail);
243
+ if (title) parts.push(`[${title}]`);
244
+ return parts.join(' ').trim();
245
+ }
246
+
247
+ function parseIncludeSpec(raw) {
248
+ const withBraces = parseBraces(raw);
249
+ const withRegion = parseRegion(withBraces.text);
250
+ return {
251
+ filePart: withRegion.filePart,
252
+ regionOrAnchor: withRegion.regionOrAnchor,
253
+ rangePart: withBraces.braces,
254
+ };
255
+ }
256
+
257
+ function toFence(lang, meta, code) {
258
+ const header = [lang || 'text', meta || ''].filter(Boolean).join(' ').trim();
259
+ return `\`\`\`${header}\n${code}\n\`\`\``;
260
+ }
261
+
262
+ async function readText(filePath) {
263
+ try {
264
+ return await fs.readFile(filePath, 'utf8');
265
+ } catch (err) {
266
+ if (err?.code === 'ENOENT') return null;
267
+ throw err;
268
+ }
269
+ }
270
+
271
+ async function resolveSnippet(rawSpec, currentDir, context) {
272
+ const parsed = parseSnippetSpec(rawSpec);
273
+ const abs = resolveImportPath(parsed.filePart, currentDir, context.sourceRoot, context.docsDirRel);
274
+ assertInsideRoot(abs, context.sourceRoot);
275
+
276
+ let content = await readText(abs);
277
+ if (content == null) {
278
+ return toFence('text', '', `[render-markdown] File not found: ${normalizeSlashes(abs)}`);
279
+ }
280
+
281
+ if (parsed.regionOrAnchor) content = selectRegion(content, parsed.regionOrAnchor);
282
+ if (parsed.rangePart) content = selectLines(content, parsed.rangePart);
283
+
284
+ const meta = buildCodeMeta(parsed.title, parsed.metaTail);
285
+ return toFence(inferLang(abs, parsed.langOverride), meta, content);
286
+ }
287
+
288
+ async function resolveInclude(rawSpec, currentDir, context, stack) {
289
+ const parsed = parseIncludeSpec(rawSpec);
290
+ const abs = resolveImportPath(parsed.filePart, currentDir, context.sourceRoot, context.docsDirRel);
291
+ assertInsideRoot(abs, context.sourceRoot);
292
+
293
+ if (stack.includes(abs)) {
294
+ return `\n> [render-markdown] Circular include skipped: ${normalizeSlashes(abs)}\n`;
295
+ }
296
+
297
+ let content = await readText(abs);
298
+ if (content == null) {
299
+ return `\n> [render-markdown] Include file not found: ${normalizeSlashes(abs)}\n`;
300
+ }
301
+
302
+ if (parsed.regionOrAnchor) {
303
+ const byRegion = selectRegion(content, parsed.regionOrAnchor);
304
+ content = byRegion === content ? selectMarkdownSectionByAnchor(content, parsed.regionOrAnchor) : byRegion;
305
+ }
306
+ if (parsed.rangePart) content = selectLines(content, parsed.rangePart);
307
+
308
+ const ext = path.extname(abs).toLowerCase();
309
+ if (ext === '.md') {
310
+ content = stripFrontmatter(content);
311
+ return resolveMarkdownContent(content, path.dirname(abs), context, [...stack, abs]);
312
+ }
313
+
314
+ return content;
315
+ }
316
+
317
+ async function resolveMarkdownContent(content, currentDir, context, stack) {
318
+ const lines = String(content).split(/\r?\n/);
319
+ const out = [];
320
+ let activeFence = null;
321
+
322
+ for (const line of lines) {
323
+ const trimmed = line.trim();
324
+
325
+ const fenceMatch = trimmed.match(/^(```+|~~~+)/);
326
+ if (fenceMatch) {
327
+ const fence = fenceMatch[1][0];
328
+ if (!activeFence) {
329
+ activeFence = fence;
330
+ } else if (activeFence === fence) {
331
+ activeFence = null;
332
+ }
333
+ out.push(line);
334
+ continue;
335
+ }
336
+
337
+ if (activeFence) {
338
+ out.push(line);
339
+ continue;
340
+ }
341
+
342
+ const snippetMatch = trimmed.match(SNIPPET_RE);
343
+ if (snippetMatch?.[1]) {
344
+ out.push(await resolveSnippet(snippetMatch[1].trim(), currentDir, context));
345
+ continue;
346
+ }
347
+
348
+ const includeMatch = trimmed.match(INCLUDE_RE);
349
+ if (includeMatch?.[1]) {
350
+ out.push(await resolveInclude(includeMatch[1].trim(), currentDir, context, stack));
351
+ continue;
352
+ }
353
+
354
+ out.push(line);
355
+ }
356
+
357
+ return out.join('\n');
358
+ }
359
+
360
+ async function walkMarkdownFiles(dir, root = dir, acc = []) {
361
+ const entries = await fs.readdir(dir, { withFileTypes: true });
362
+ for (const entry of entries) {
363
+ if (entry.name.startsWith('__')) continue;
364
+ const abs = path.join(dir, entry.name);
365
+ if (entry.isDirectory()) {
366
+ await walkMarkdownFiles(abs, root, acc);
367
+ continue;
368
+ }
369
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
370
+ acc.push({ abs, rel: normalizeSlashes(path.relative(root, abs)) });
371
+ }
372
+ return acc;
373
+ }
374
+
375
+ async function main() {
376
+ const args = parseArgs(process.argv.slice(2));
377
+ const sourceRoot = process.cwd();
378
+ const docsDirRel = String(args.docsDir || 'docs').replace(/^\/+|\/+$/g, '') || 'docs';
379
+ const docsDirAbs = path.resolve(sourceRoot, docsDirRel);
380
+ const outDirAbs = path.resolve(sourceRoot, args.outDir);
381
+
382
+ const files = await walkMarkdownFiles(docsDirAbs);
383
+ await fs.mkdir(outDirAbs, { recursive: true });
384
+
385
+ const context = { sourceRoot, docsDirRel };
386
+ let written = 0;
387
+
388
+ for (const file of files) {
389
+ const src = await fs.readFile(file.abs, 'utf8');
390
+ const resolved = await resolveMarkdownContent(src, path.dirname(file.abs), context, [file.abs]);
391
+
392
+ const outPath = path.join(outDirAbs, file.rel);
393
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
394
+ await fs.writeFile(outPath, resolved, 'utf8');
395
+ written++;
396
+ }
397
+
398
+ const summaryPath = path.join(outDirAbs, '_summary.json');
399
+ await fs.writeFile(
400
+ summaryPath,
401
+ JSON.stringify(
402
+ {
403
+ generatedAt: new Date().toISOString(),
404
+ docsDir: normalizeSlashes(path.relative(sourceRoot, docsDirAbs)),
405
+ outDir: normalizeSlashes(path.relative(sourceRoot, outDirAbs)),
406
+ pages: written,
407
+ },
408
+ null,
409
+ 2,
410
+ ) + '\n',
411
+ 'utf8',
412
+ );
413
+
414
+ console.log(`Resolved markdown export complete: ${written} files -> ${normalizeSlashes(path.relative(sourceRoot, outDirAbs))}`);
415
+ }
416
+
417
+ main().catch((err) => {
418
+ console.error('[render-markdown] Failed:', err);
419
+ process.exit(1);
420
+ });