@marlinjai/clearify 1.5.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/bin/clearify.js +2 -0
  4. package/dist/node/chunk-5TD7NQIW.js +25 -0
  5. package/dist/node/chunk-B2Q23JW3.js +55 -0
  6. package/dist/node/chunk-CQ4MNGBE.js +301 -0
  7. package/dist/node/chunk-GFD54GNO.js +223 -0
  8. package/dist/node/chunk-IBK35HZR.js +194 -0
  9. package/dist/node/chunk-L24ILRSX.js +125 -0
  10. package/dist/node/chunk-NXQNNLGC.js +395 -0
  11. package/dist/node/chunk-PRTER35L.js +48 -0
  12. package/dist/node/chunk-SCZZB7OE.js +9 -0
  13. package/dist/node/chunk-V7LLYIRO.js +8 -0
  14. package/dist/node/chunk-WT5W333R.js +136 -0
  15. package/dist/node/cli/index.d.ts +2 -0
  16. package/dist/node/cli/index.js +41 -0
  17. package/dist/node/core/config.d.ts +9 -0
  18. package/dist/node/core/config.js +16 -0
  19. package/dist/node/core/mermaid-renderer.d.ts +22 -0
  20. package/dist/node/core/mermaid-renderer.js +125 -0
  21. package/dist/node/core/mermaid-utils.d.ts +3 -0
  22. package/dist/node/core/mermaid-utils.js +6 -0
  23. package/dist/node/core/navigation.d.ts +2 -0
  24. package/dist/node/core/navigation.js +13 -0
  25. package/dist/node/core/openapi-parser.d.ts +14 -0
  26. package/dist/node/core/openapi-parser.js +6 -0
  27. package/dist/node/core/remark-mermaid.d.ts +10 -0
  28. package/dist/node/core/remark-mermaid.js +11 -0
  29. package/dist/node/core/search.d.ts +31 -0
  30. package/dist/node/core/search.js +6 -0
  31. package/dist/node/node/build.d.ts +3 -0
  32. package/dist/node/node/build.js +14 -0
  33. package/dist/node/node/check.d.ts +3 -0
  34. package/dist/node/node/check.js +10 -0
  35. package/dist/node/node/index.d.ts +11 -0
  36. package/dist/node/node/index.js +108 -0
  37. package/dist/node/node/init.d.ts +6 -0
  38. package/dist/node/node/init.js +6 -0
  39. package/dist/node/presets/nestjs.d.ts +15 -0
  40. package/dist/node/presets/nestjs.js +98 -0
  41. package/dist/node/types/index.d.ts +79 -0
  42. package/dist/node/types/index.js +6 -0
  43. package/dist/node/vite-plugin/index.d.ts +13 -0
  44. package/dist/node/vite-plugin/index.js +11 -0
  45. package/package.json +94 -0
  46. package/src/client/App.tsx +101 -0
  47. package/src/client/Page.tsx +15 -0
  48. package/src/client/entry-server.tsx +79 -0
  49. package/src/client/index.html +18 -0
  50. package/src/client/main.tsx +11 -0
  51. package/src/theme/CodeBlock.tsx +103 -0
  52. package/src/theme/Content.tsx +32 -0
  53. package/src/theme/Footer.tsx +53 -0
  54. package/src/theme/Head.tsx +80 -0
  55. package/src/theme/HeadContext.tsx +32 -0
  56. package/src/theme/Header.tsx +177 -0
  57. package/src/theme/Layout.tsx +44 -0
  58. package/src/theme/MDXComponents.tsx +40 -0
  59. package/src/theme/NotFound.tsx +246 -0
  60. package/src/theme/Search.tsx +359 -0
  61. package/src/theme/Sidebar.tsx +325 -0
  62. package/src/theme/TableOfContents.tsx +153 -0
  63. package/src/theme/ThemeProvider.tsx +77 -0
  64. package/src/theme/components/Accordion.tsx +109 -0
  65. package/src/theme/components/Badge.tsx +72 -0
  66. package/src/theme/components/Breadcrumbs.tsx +88 -0
  67. package/src/theme/components/Callout.tsx +115 -0
  68. package/src/theme/components/Card.tsx +103 -0
  69. package/src/theme/components/CodeGroup.tsx +79 -0
  70. package/src/theme/components/Columns.tsx +42 -0
  71. package/src/theme/components/Frame.tsx +55 -0
  72. package/src/theme/components/Mermaid.tsx +99 -0
  73. package/src/theme/components/MermaidStatic.tsx +32 -0
  74. package/src/theme/components/OpenAPI.tsx +160 -0
  75. package/src/theme/components/OpenAPIPage.tsx +16 -0
  76. package/src/theme/components/Steps.tsx +76 -0
  77. package/src/theme/components/Tabs.tsx +75 -0
  78. package/src/theme/components/Tooltip.tsx +108 -0
  79. package/src/theme/components/index.ts +14 -0
  80. package/src/theme/styles/globals.css +363 -0
@@ -0,0 +1,395 @@
1
+ import {
2
+ parseOpenAPISpec
3
+ } from "./chunk-B2Q23JW3.js";
4
+ import {
5
+ loadUserConfig,
6
+ resolveConfig,
7
+ resolveSections
8
+ } from "./chunk-IBK35HZR.js";
9
+ import {
10
+ buildSectionData
11
+ } from "./chunk-L24ILRSX.js";
12
+
13
+ // src/vite-plugin/index.ts
14
+ import { resolve } from "path";
15
+ import { existsSync, readFileSync } from "fs";
16
+ var VIRTUAL_CONFIG = "virtual:clearify/config";
17
+ var RESOLVED_VIRTUAL_CONFIG = "\0" + VIRTUAL_CONFIG;
18
+ var VIRTUAL_ROUTES = "virtual:clearify/routes";
19
+ var RESOLVED_VIRTUAL_ROUTES = "\0" + VIRTUAL_ROUTES;
20
+ var VIRTUAL_NAVIGATION = "virtual:clearify/navigation";
21
+ var RESOLVED_VIRTUAL_NAVIGATION = "\0" + VIRTUAL_NAVIGATION;
22
+ var VIRTUAL_SEARCH_INDEX = "virtual:clearify/search-index";
23
+ var RESOLVED_VIRTUAL_SEARCH_INDEX = "\0" + VIRTUAL_SEARCH_INDEX;
24
+ var VIRTUAL_MERMAID_SVGS = "virtual:clearify/mermaid-svgs";
25
+ var RESOLVED_VIRTUAL_MERMAID_SVGS = "\0" + VIRTUAL_MERMAID_SVGS;
26
+ var VIRTUAL_OPENAPI_SPEC = "virtual:clearify/openapi-spec";
27
+ var RESOLVED_VIRTUAL_OPENAPI_SPEC = "\0" + VIRTUAL_OPENAPI_SPEC;
28
+ function clearifyPlugin(options = {}) {
29
+ let config;
30
+ let userRoot;
31
+ let resolvedViteConfig;
32
+ let visibleSections;
33
+ let sectionDataList;
34
+ let allRoutes;
35
+ let allSectionNavigations;
36
+ let allSearchEntries;
37
+ let mermaidSvgData = options.mermaidSvgs ?? {};
38
+ function refreshDocs() {
39
+ const changelogPath = resolve(userRoot, "CHANGELOG.md");
40
+ sectionDataList = visibleSections.map(
41
+ (section) => buildSectionData(section, changelogPath, config.navigation)
42
+ );
43
+ allRoutes = sectionDataList.flatMap((sd) => sd.routes);
44
+ allSectionNavigations = sectionDataList.map((sd) => sd.navigation);
45
+ let idCounter = 0;
46
+ allSearchEntries = sectionDataList.flatMap(
47
+ (sd) => sd.searchEntries.map((entry) => ({ ...entry, id: idCounter++ }))
48
+ );
49
+ if (config.openapi?.spec && config.openapi.generatePages !== false) {
50
+ const basePath = config.openapi.basePath ?? "/api";
51
+ const specData = loadOpenAPISpecData();
52
+ if (specData) {
53
+ let opAnchor2 = function(tag, method, opPath) {
54
+ const slug = `${method.toLowerCase()}${opPath.replace(/[{}]/g, "")}`;
55
+ return `${basePath}#tag/${encodeURIComponent(tag)}/${slug}`;
56
+ };
57
+ var opAnchor = opAnchor2;
58
+ const tagGroups = parseOpenAPISpec(specData);
59
+ const METHOD_COLORS = {
60
+ GET: "#22c55e",
61
+ POST: "#3b82f6",
62
+ PUT: "#f59e0b",
63
+ DELETE: "#ef4444",
64
+ PATCH: "#8b5cf6"
65
+ };
66
+ const navItems = tagGroups.map((group) => ({
67
+ label: group.tag,
68
+ children: group.operations.map((op) => ({
69
+ label: `${op.summary}`,
70
+ path: opAnchor2(group.tag, op.method, op.path),
71
+ badge: op.method,
72
+ badgeColor: METHOD_COLORS[op.method] ?? "var(--clearify-primary)"
73
+ }))
74
+ }));
75
+ const apiNavSection = {
76
+ id: "api",
77
+ label: "API Reference",
78
+ basePath,
79
+ navigation: navItems
80
+ };
81
+ allSectionNavigations.push(apiNavSection);
82
+ allRoutes.push({
83
+ path: `${basePath}/*`,
84
+ filePath: "",
85
+ componentPath: "@clearify/theme/components/OpenAPIPage",
86
+ frontmatter: { title: "API Reference", description: "API documentation" },
87
+ sectionId: "api"
88
+ });
89
+ for (const group of tagGroups) {
90
+ for (const op of group.operations) {
91
+ allSearchEntries.push({
92
+ id: idCounter++,
93
+ path: opAnchor2(group.tag, op.method, op.path),
94
+ title: `${op.method} ${op.path}`,
95
+ description: op.summary,
96
+ content: `${op.method} ${op.path} - ${op.summary}`,
97
+ sectionId: "api",
98
+ sectionLabel: "API Reference"
99
+ });
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ function generateRoutesCode() {
106
+ const imports = [];
107
+ const routeEntries = [];
108
+ allRoutes.forEach((route, i) => {
109
+ const varName = `Route${i}`;
110
+ if (route.componentPath) {
111
+ imports.push(`const ${varName} = () => import('${route.componentPath}');`);
112
+ } else {
113
+ imports.push(`const ${varName} = () => import(/* @vite-ignore */ '${route.filePath}');`);
114
+ }
115
+ routeEntries.push(
116
+ ` { path: ${JSON.stringify(route.path)}, component: ${varName}, frontmatter: ${JSON.stringify(route.frontmatter)} }`
117
+ );
118
+ });
119
+ return `${imports.join("\n")}
120
+ export default [
121
+ ${routeEntries.join(",\n")}
122
+ ];`;
123
+ }
124
+ function getAllDocFiles() {
125
+ const files = [];
126
+ for (const route of allRoutes) {
127
+ if (route.filePath.endsWith(".md") || route.filePath.endsWith(".mdx")) {
128
+ files.push(route.filePath);
129
+ }
130
+ }
131
+ return files;
132
+ }
133
+ let cachedSpecData = null;
134
+ let specDataInitialized = false;
135
+ function loadOpenAPISpecData() {
136
+ if (specDataInitialized) return cachedSpecData;
137
+ specDataInitialized = true;
138
+ const specPath = config.openapi?.spec;
139
+ if (!specPath) {
140
+ cachedSpecData = null;
141
+ return null;
142
+ }
143
+ const resolvedPath = resolve(userRoot, specPath);
144
+ if (!existsSync(resolvedPath)) {
145
+ cachedSpecData = null;
146
+ return null;
147
+ }
148
+ try {
149
+ const content = readFileSync(resolvedPath, "utf-8");
150
+ const isYaml = /\.ya?ml$/i.test(resolvedPath);
151
+ if (isYaml) {
152
+ try {
153
+ cachedSpecData = JSON.parse(content);
154
+ return cachedSpecData;
155
+ } catch {
156
+ console.warn(" YAML OpenAPI specs require js-yaml for page generation. Install it or use a JSON spec.");
157
+ cachedSpecData = null;
158
+ return null;
159
+ }
160
+ }
161
+ cachedSpecData = JSON.parse(content);
162
+ return cachedSpecData;
163
+ } catch {
164
+ cachedSpecData = null;
165
+ return null;
166
+ }
167
+ }
168
+ function loadOpenAPISpec() {
169
+ const specPath = config.openapi?.spec;
170
+ if (!specPath) {
171
+ return "export default null;";
172
+ }
173
+ const resolvedPath = resolve(userRoot, specPath);
174
+ if (!existsSync(resolvedPath)) {
175
+ console.warn(` OpenAPI spec not found: ${resolvedPath}`);
176
+ return "export default null;";
177
+ }
178
+ try {
179
+ const content = readFileSync(resolvedPath, "utf-8");
180
+ const isYaml = /\.ya?ml$/i.test(resolvedPath);
181
+ if (isYaml) {
182
+ return `export default ${JSON.stringify(content)};`;
183
+ }
184
+ JSON.parse(content);
185
+ return `export default ${content};`;
186
+ } catch (err) {
187
+ console.warn(` Failed to load OpenAPI spec: ${resolvedPath}`, err instanceof Error ? err.message : err);
188
+ return "export default null;";
189
+ }
190
+ }
191
+ const mainPlugin = {
192
+ name: "clearify",
193
+ enforce: "pre",
194
+ async configResolved(viteConfig) {
195
+ resolvedViteConfig = viteConfig;
196
+ userRoot = options.root ?? process.cwd();
197
+ const userConfig = await loadUserConfig(userRoot);
198
+ config = resolveConfig(userConfig, userRoot);
199
+ const allSections = resolveSections(config, userRoot);
200
+ const isDev = resolvedViteConfig.command === "serve";
201
+ visibleSections = isDev ? allSections : allSections.filter((s) => !s.draft);
202
+ refreshDocs();
203
+ },
204
+ resolveId(id) {
205
+ if (id === VIRTUAL_CONFIG) return RESOLVED_VIRTUAL_CONFIG;
206
+ if (id === VIRTUAL_ROUTES) return RESOLVED_VIRTUAL_ROUTES;
207
+ if (id === VIRTUAL_NAVIGATION) return RESOLVED_VIRTUAL_NAVIGATION;
208
+ if (id === VIRTUAL_SEARCH_INDEX) return RESOLVED_VIRTUAL_SEARCH_INDEX;
209
+ if (id === VIRTUAL_MERMAID_SVGS) return RESOLVED_VIRTUAL_MERMAID_SVGS;
210
+ if (id === VIRTUAL_OPENAPI_SPEC) return RESOLVED_VIRTUAL_OPENAPI_SPEC;
211
+ return null;
212
+ },
213
+ load(id) {
214
+ if (id === RESOLVED_VIRTUAL_CONFIG) {
215
+ return `export default ${JSON.stringify(config)};`;
216
+ }
217
+ if (id === RESOLVED_VIRTUAL_ROUTES) {
218
+ return generateRoutesCode();
219
+ }
220
+ if (id === RESOLVED_VIRTUAL_NAVIGATION) {
221
+ return `export default ${JSON.stringify(allSectionNavigations)};`;
222
+ }
223
+ if (id === RESOLVED_VIRTUAL_SEARCH_INDEX) {
224
+ return `export default ${JSON.stringify(allSearchEntries)};`;
225
+ }
226
+ if (id === RESOLVED_VIRTUAL_MERMAID_SVGS) {
227
+ return `export default ${JSON.stringify(mermaidSvgData)};`;
228
+ }
229
+ if (id === RESOLVED_VIRTUAL_OPENAPI_SPEC) {
230
+ return loadOpenAPISpec();
231
+ }
232
+ return null;
233
+ },
234
+ configureServer(server) {
235
+ for (const section of visibleSections) {
236
+ server.watcher.add(section.docsDir);
237
+ }
238
+ const changelogPath = resolve(userRoot, "CHANGELOG.md");
239
+ if (existsSync(changelogPath)) {
240
+ server.watcher.add(changelogPath);
241
+ }
242
+ if (config.openapi?.spec) {
243
+ const openapiPath = resolve(userRoot, config.openapi.spec);
244
+ if (existsSync(openapiPath)) {
245
+ server.watcher.add(openapiPath);
246
+ }
247
+ server.watcher.on("change", (changedPath) => {
248
+ if (changedPath === openapiPath) {
249
+ specDataInitialized = false;
250
+ cachedSpecData = null;
251
+ refreshDocs();
252
+ const specMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_OPENAPI_SPEC);
253
+ if (specMod) server.moduleGraph.invalidateModule(specMod);
254
+ const routesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES);
255
+ if (routesMod) server.moduleGraph.invalidateModule(routesMod);
256
+ const navMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION);
257
+ if (navMod) server.moduleGraph.invalidateModule(navMod);
258
+ const searchMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SEARCH_INDEX);
259
+ if (searchMod) server.moduleGraph.invalidateModule(searchMod);
260
+ server.ws.send({ type: "full-reload" });
261
+ }
262
+ });
263
+ }
264
+ const mermaidStrategy = options.mermaidStrategy ?? config.mermaid?.strategy ?? "client";
265
+ if (mermaidStrategy === "build") {
266
+ let renderer = null;
267
+ const fileMermaidHashes = /* @__PURE__ */ new Map();
268
+ const warmUp = async () => {
269
+ try {
270
+ const { MermaidRenderer } = await import("./core/mermaid-renderer.js");
271
+ const { mermaidContentHash } = await import("./core/mermaid-utils.js");
272
+ const { setPreRenderedMermaidSvgs } = await import("./core/remark-mermaid.js");
273
+ const cacheDir = resolve(userRoot, "node_modules/.cache/clearify-mermaid");
274
+ renderer = new MermaidRenderer({ cacheDir });
275
+ await renderer.launch();
276
+ const docFiles = getAllDocFiles();
277
+ const definitions = /* @__PURE__ */ new Map();
278
+ const mermaidRegex = /```mermaid\s*\n([\s\S]*?)```/g;
279
+ for (const filePath of docFiles) {
280
+ if (!existsSync(filePath)) continue;
281
+ const content = readFileSync(filePath, "utf-8");
282
+ const hashes = /* @__PURE__ */ new Set();
283
+ let match;
284
+ while ((match = mermaidRegex.exec(content)) !== null) {
285
+ const def = match[1].trim();
286
+ const hash = mermaidContentHash(def);
287
+ definitions.set(hash, def);
288
+ hashes.add(hash);
289
+ }
290
+ fileMermaidHashes.set(filePath, hashes);
291
+ }
292
+ if (definitions.size === 0) {
293
+ console.log(" No mermaid diagrams found.");
294
+ return;
295
+ }
296
+ const rendered = await renderer.renderBatch(definitions);
297
+ const svgRecord = {};
298
+ const svgMap = /* @__PURE__ */ new Map();
299
+ for (const [hash, data] of rendered) {
300
+ svgRecord[hash] = data;
301
+ svgMap.set(hash, data);
302
+ }
303
+ mermaidSvgData = svgRecord;
304
+ setPreRenderedMermaidSvgs(svgMap);
305
+ const mermaidMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MERMAID_SVGS);
306
+ if (mermaidMod) server.moduleGraph.invalidateModule(mermaidMod);
307
+ server.ws.send({ type: "full-reload" });
308
+ console.log(` Mermaid warm-up complete (${rendered.size} diagrams)`);
309
+ } catch (err) {
310
+ console.warn(" Mermaid warm-up failed:", err instanceof Error ? err.message : err);
311
+ }
312
+ };
313
+ setTimeout(warmUp, 100);
314
+ server.httpServer?.on("close", async () => {
315
+ if (renderer) {
316
+ await renderer.close();
317
+ renderer = null;
318
+ }
319
+ });
320
+ server.watcher.on("change", async (filePath) => {
321
+ if (!filePath.endsWith(".md") && !filePath.endsWith(".mdx")) return;
322
+ if (!renderer) return;
323
+ try {
324
+ const { mermaidContentHash } = await import("./core/mermaid-utils.js");
325
+ const { setPreRenderedMermaidSvgs } = await import("./core/remark-mermaid.js");
326
+ const content = readFileSync(filePath, "utf-8");
327
+ const mermaidRegex = /```mermaid\s*\n([\s\S]*?)```/g;
328
+ const currentHashes = /* @__PURE__ */ new Set();
329
+ const newDefinitions = /* @__PURE__ */ new Map();
330
+ let match;
331
+ while ((match = mermaidRegex.exec(content)) !== null) {
332
+ const def = match[1].trim();
333
+ const hash = mermaidContentHash(def);
334
+ currentHashes.add(hash);
335
+ if (!mermaidSvgData[hash]) {
336
+ newDefinitions.set(hash, def);
337
+ }
338
+ }
339
+ fileMermaidHashes.set(filePath, currentHashes);
340
+ if (newDefinitions.size > 0) {
341
+ const rendered = await renderer.renderBatch(newDefinitions);
342
+ for (const [hash, data] of rendered) {
343
+ mermaidSvgData[hash] = data;
344
+ }
345
+ const svgMap = new Map(Object.entries(mermaidSvgData));
346
+ setPreRenderedMermaidSvgs(svgMap);
347
+ const mermaidMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MERMAID_SVGS);
348
+ if (mermaidMod) server.moduleGraph.invalidateModule(mermaidMod);
349
+ }
350
+ } catch (err) {
351
+ console.warn(" Mermaid incremental render failed:", err instanceof Error ? err.message : err);
352
+ }
353
+ });
354
+ }
355
+ server.watcher.on("all", (event, path) => {
356
+ const isChangelog = path === changelogPath;
357
+ const isInSection = visibleSections.some((s) => path.startsWith(s.docsDir));
358
+ if (!isChangelog && !isInSection) return;
359
+ if (!path.endsWith(".md") && !path.endsWith(".mdx")) return;
360
+ if (event === "add" || event === "unlink" || event === "change") {
361
+ refreshDocs();
362
+ const routesMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ROUTES);
363
+ const navMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_NAVIGATION);
364
+ const searchMod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_SEARCH_INDEX);
365
+ if (routesMod) server.moduleGraph.invalidateModule(routesMod);
366
+ if (navMod) server.moduleGraph.invalidateModule(navMod);
367
+ if (searchMod) server.moduleGraph.invalidateModule(searchMod);
368
+ server.ws.send({ type: "full-reload" });
369
+ }
370
+ });
371
+ }
372
+ };
373
+ const headInjectionPlugin = {
374
+ name: "clearify-head-injection",
375
+ transformIndexHtml(html) {
376
+ const tags = [];
377
+ if (config.customCss) {
378
+ const cssPath = resolve(userRoot, config.customCss);
379
+ const href = `/@fs/${cssPath}`;
380
+ tags.push(`<link rel="stylesheet" href="${href}" />`);
381
+ }
382
+ if (config.headTags && config.headTags.length > 0) {
383
+ tags.push(...config.headTags);
384
+ }
385
+ if (tags.length === 0) return html;
386
+ return html.replace("</head>", ` ${tags.join("\n ")}
387
+ </head>`);
388
+ }
389
+ };
390
+ return [mainPlugin, headInjectionPlugin];
391
+ }
392
+
393
+ export {
394
+ clearifyPlugin
395
+ };
@@ -0,0 +1,48 @@
1
+ import {
2
+ mermaidContentHash
3
+ } from "./chunk-SCZZB7OE.js";
4
+
5
+ // src/core/remark-mermaid.ts
6
+ import { visit } from "unist-util-visit";
7
+ var preRenderedSvgs = null;
8
+ function setPreRenderedMermaidSvgs(svgs) {
9
+ preRenderedSvgs = svgs;
10
+ }
11
+ function clearPreRenderedMermaidSvgs() {
12
+ preRenderedSvgs = null;
13
+ }
14
+ function remarkMermaidToComponent() {
15
+ return (tree) => {
16
+ visit(tree, "code", (node, index, parent) => {
17
+ if (node.lang !== "mermaid" || !parent || index === void 0) return;
18
+ if (preRenderedSvgs) {
19
+ const hash = mermaidContentHash(node.value);
20
+ if (preRenderedSvgs.has(hash)) {
21
+ const element2 = {
22
+ type: "mdxJsxFlowElement",
23
+ name: "MermaidStatic",
24
+ attributes: [
25
+ { type: "mdxJsxAttribute", name: "diagramHash", value: hash }
26
+ ],
27
+ children: []
28
+ };
29
+ parent.children[index] = element2;
30
+ return;
31
+ }
32
+ }
33
+ const element = {
34
+ type: "mdxJsxFlowElement",
35
+ name: "Mermaid",
36
+ attributes: [],
37
+ children: [{ type: "text", value: node.value }]
38
+ };
39
+ parent.children[index] = element;
40
+ });
41
+ };
42
+ }
43
+
44
+ export {
45
+ setPreRenderedMermaidSvgs,
46
+ clearPreRenderedMermaidSvgs,
47
+ remarkMermaidToComponent
48
+ };
@@ -0,0 +1,9 @@
1
+ // src/core/mermaid-utils.ts
2
+ import { createHash } from "crypto";
3
+ function mermaidContentHash(definition) {
4
+ return createHash("sha256").update(definition.trim()).digest("hex").slice(0, 16);
5
+ }
6
+
7
+ export {
8
+ mermaidContentHash
9
+ };
@@ -0,0 +1,8 @@
1
+ // src/types/index.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+
6
+ export {
7
+ defineConfig
8
+ };
@@ -0,0 +1,136 @@
1
+ import {
2
+ loadUserConfig,
3
+ resolveConfig,
4
+ resolveSections
5
+ } from "./chunk-IBK35HZR.js";
6
+ import {
7
+ scanDocs
8
+ } from "./chunk-L24ILRSX.js";
9
+
10
+ // src/node/check.ts
11
+ import { resolve } from "path";
12
+ import { readFileSync, existsSync } from "fs";
13
+ async function checkLinks() {
14
+ const userRoot = process.cwd();
15
+ const userConfig = await loadUserConfig(userRoot);
16
+ const config = resolveConfig(userConfig, userRoot);
17
+ const sections = resolveSections(config, userRoot);
18
+ const validRoutes = /* @__PURE__ */ new Set();
19
+ const allDocs = [];
20
+ for (const section of sections) {
21
+ const docs = scanDocs(section.docsDir, section.exclude, section.basePath);
22
+ for (const doc of docs) {
23
+ validRoutes.add(doc.routePath);
24
+ allDocs.push(doc);
25
+ }
26
+ }
27
+ validRoutes.add("/");
28
+ const changelogPath = resolve(userRoot, "CHANGELOG.md");
29
+ if (existsSync(changelogPath)) {
30
+ validRoutes.add("/changelog");
31
+ }
32
+ const expandedRoutes = new Set(validRoutes);
33
+ for (const route of validRoutes) {
34
+ expandedRoutes.add(route.replace(/\/$/, ""));
35
+ if (route !== "/") expandedRoutes.add(route + "/");
36
+ }
37
+ const mdLinkRegex = /\[(?:[^\]]*)\]\(([^)]+)\)/g;
38
+ const hrefRegex = /href=["']([^"']+)["']/g;
39
+ const brokenLinks = [];
40
+ for (const doc of allDocs) {
41
+ if (!existsSync(doc.filePath)) continue;
42
+ const content = readFileSync(doc.filePath, "utf-8");
43
+ const lines = content.split("\n");
44
+ for (let i = 0; i < lines.length; i++) {
45
+ const line = lines[i];
46
+ const lineNum = i + 1;
47
+ if (line.trimStart().startsWith("```")) continue;
48
+ for (const regex of [mdLinkRegex, hrefRegex]) {
49
+ regex.lastIndex = 0;
50
+ let match;
51
+ while ((match = regex.exec(line)) !== null) {
52
+ const rawLink = match[1].split(/[\s"'#]/)[0];
53
+ if (!rawLink || rawLink.startsWith("http://") || rawLink.startsWith("https://") || rawLink.startsWith("#") || rawLink.startsWith("mailto:") || rawLink.startsWith("tel:") || rawLink.startsWith("//") || rawLink.startsWith("{") || rawLink.startsWith("data:")) {
54
+ continue;
55
+ }
56
+ if (/\.(png|jpe?g|gif|svg|webp|ico|pdf|zip|tar|gz)$/i.test(rawLink)) {
57
+ continue;
58
+ }
59
+ let normalized = rawLink;
60
+ normalized = normalized.replace(/\.(mdx?|html?)$/, "");
61
+ if (!normalized.startsWith("/")) {
62
+ const docDir = doc.routePath.substring(0, doc.routePath.lastIndexOf("/")) || "/";
63
+ normalized = docDir === "/" ? "/" + normalized : docDir + "/" + normalized;
64
+ }
65
+ const check = normalized === "/" ? "/" : normalized.replace(/\/$/, "");
66
+ const checkNoIndex = check.replace(/\/index$/, "") || "/";
67
+ if (!expandedRoutes.has(check) && !expandedRoutes.has(checkNoIndex)) {
68
+ const suggestion = findClosest(check, validRoutes);
69
+ brokenLinks.push({
70
+ file: doc.filePath,
71
+ line: lineNum,
72
+ link: rawLink,
73
+ ...suggestion ? { suggestion } : {}
74
+ });
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ if (brokenLinks.length === 0) {
81
+ console.log("\n No broken links found.\n");
82
+ return;
83
+ }
84
+ console.log(`
85
+ Found ${brokenLinks.length} broken link${brokenLinks.length === 1 ? "" : "s"}:
86
+ `);
87
+ const byFile = /* @__PURE__ */ new Map();
88
+ for (const bl of brokenLinks) {
89
+ const relative = bl.file.replace(userRoot + "/", "");
90
+ if (!byFile.has(relative)) byFile.set(relative, []);
91
+ byFile.get(relative).push(bl);
92
+ }
93
+ for (const [file, links] of byFile) {
94
+ console.log(` ${file}`);
95
+ for (const link of links) {
96
+ const suggestion = link.suggestion ? ` (did you mean ${link.suggestion}?)` : "";
97
+ console.log(` line ${link.line}: ${link.link}${suggestion}`);
98
+ }
99
+ console.log();
100
+ }
101
+ process.exitCode = 1;
102
+ }
103
+ function findClosest(target, routes) {
104
+ let best;
105
+ let bestScore = Infinity;
106
+ const targetSegments = target.split("/").filter(Boolean);
107
+ for (const route of routes) {
108
+ const routeSegments = route.split("/").filter(Boolean);
109
+ const dist = levenshtein(target, route);
110
+ const common = targetSegments.filter((s) => routeSegments.includes(s)).length;
111
+ const score = dist - common * 2;
112
+ if (score < bestScore) {
113
+ bestScore = score;
114
+ best = route;
115
+ }
116
+ }
117
+ if (best && bestScore < target.length * 0.6) return best;
118
+ return void 0;
119
+ }
120
+ function levenshtein(a, b) {
121
+ const m = a.length;
122
+ const n = b.length;
123
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
124
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
125
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
126
+ for (let i = 1; i <= m; i++) {
127
+ for (let j = 1; j <= n; j++) {
128
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
129
+ }
130
+ }
131
+ return dp[m][n];
132
+ }
133
+
134
+ export {
135
+ checkLinks
136
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,41 @@
1
+ // src/cli/index.ts
2
+ import cac from "cac";
3
+ var cli = cac("clearify");
4
+ cli.command("dev", "Start development server").option("--port <port>", "Port to listen on").option("--host", "Expose to network").action(async (options) => {
5
+ const { createServer } = await import("../node/index.js");
6
+ const server = await createServer({
7
+ port: options.port,
8
+ host: options.host
9
+ });
10
+ await server.listen();
11
+ console.log(`
12
+ Clearify dev server running at:
13
+ `);
14
+ server.printUrls();
15
+ console.log();
16
+ });
17
+ cli.command("build", "Build static documentation site").action(async () => {
18
+ const { build } = await import("../node/index.js");
19
+ await build();
20
+ });
21
+ cli.command("init", "Scaffold a docs folder").option("--no-internal", "Skip creating the internal docs section").action(async (options) => {
22
+ const { init } = await import("../node/index.js");
23
+ await init({ noInternal: options.noInternal });
24
+ });
25
+ cli.command("check", "Check for broken internal links").action(async () => {
26
+ const { checkLinks } = await import("../node/check.js");
27
+ await checkLinks();
28
+ });
29
+ cli.command("openapi:generate", "Generate OpenAPI spec from NestJS app").option("--module <path>", "Path to NestJS app module", { default: "./src/app.module.ts" }).option("--output <path>", "Output path for spec file", { default: "./docs/openapi.json" }).option("--title <title>", "API documentation title").option("--description <desc>", "API documentation description").option("--version <version>", "API version").action(async (options) => {
30
+ const { generateSpec } = await import("../presets/nestjs.js");
31
+ await generateSpec({
32
+ appModule: options.module,
33
+ output: options.output,
34
+ title: options.title,
35
+ description: options.description,
36
+ version: options.version
37
+ });
38
+ });
39
+ cli.help();
40
+ cli.version("1.5.0");
41
+ cli.parse();
@@ -0,0 +1,9 @@
1
+ import { ClearifyConfig, ResolvedSection } from '../types/index.js';
2
+ export { defineConfig } from '../types/index.js';
3
+
4
+ declare const defaultConfig: ClearifyConfig;
5
+ declare function loadUserConfig(root: string): Promise<Partial<ClearifyConfig>>;
6
+ declare function resolveConfig(userConfig?: Partial<ClearifyConfig>, root?: string): ClearifyConfig;
7
+ declare function resolveSections(config: ClearifyConfig, root: string): ResolvedSection[];
8
+
9
+ export { defaultConfig, loadUserConfig, resolveConfig, resolveSections };
@@ -0,0 +1,16 @@
1
+ import {
2
+ defaultConfig,
3
+ loadUserConfig,
4
+ resolveConfig,
5
+ resolveSections
6
+ } from "../chunk-IBK35HZR.js";
7
+ import {
8
+ defineConfig
9
+ } from "../chunk-V7LLYIRO.js";
10
+ export {
11
+ defaultConfig,
12
+ defineConfig,
13
+ loadUserConfig,
14
+ resolveConfig,
15
+ resolveSections
16
+ };