@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.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/bin/clearify.js +2 -0
- package/dist/node/chunk-5TD7NQIW.js +25 -0
- package/dist/node/chunk-B2Q23JW3.js +55 -0
- package/dist/node/chunk-CQ4MNGBE.js +301 -0
- package/dist/node/chunk-GFD54GNO.js +223 -0
- package/dist/node/chunk-IBK35HZR.js +194 -0
- package/dist/node/chunk-L24ILRSX.js +125 -0
- package/dist/node/chunk-NXQNNLGC.js +395 -0
- package/dist/node/chunk-PRTER35L.js +48 -0
- package/dist/node/chunk-SCZZB7OE.js +9 -0
- package/dist/node/chunk-V7LLYIRO.js +8 -0
- package/dist/node/chunk-WT5W333R.js +136 -0
- package/dist/node/cli/index.d.ts +2 -0
- package/dist/node/cli/index.js +41 -0
- package/dist/node/core/config.d.ts +9 -0
- package/dist/node/core/config.js +16 -0
- package/dist/node/core/mermaid-renderer.d.ts +22 -0
- package/dist/node/core/mermaid-renderer.js +125 -0
- package/dist/node/core/mermaid-utils.d.ts +3 -0
- package/dist/node/core/mermaid-utils.js +6 -0
- package/dist/node/core/navigation.d.ts +2 -0
- package/dist/node/core/navigation.js +13 -0
- package/dist/node/core/openapi-parser.d.ts +14 -0
- package/dist/node/core/openapi-parser.js +6 -0
- package/dist/node/core/remark-mermaid.d.ts +10 -0
- package/dist/node/core/remark-mermaid.js +11 -0
- package/dist/node/core/search.d.ts +31 -0
- package/dist/node/core/search.js +6 -0
- package/dist/node/node/build.d.ts +3 -0
- package/dist/node/node/build.js +14 -0
- package/dist/node/node/check.d.ts +3 -0
- package/dist/node/node/check.js +10 -0
- package/dist/node/node/index.d.ts +11 -0
- package/dist/node/node/index.js +108 -0
- package/dist/node/node/init.d.ts +6 -0
- package/dist/node/node/init.js +6 -0
- package/dist/node/presets/nestjs.d.ts +15 -0
- package/dist/node/presets/nestjs.js +98 -0
- package/dist/node/types/index.d.ts +79 -0
- package/dist/node/types/index.js +6 -0
- package/dist/node/vite-plugin/index.d.ts +13 -0
- package/dist/node/vite-plugin/index.js +11 -0
- package/package.json +94 -0
- package/src/client/App.tsx +101 -0
- package/src/client/Page.tsx +15 -0
- package/src/client/entry-server.tsx +79 -0
- package/src/client/index.html +18 -0
- package/src/client/main.tsx +11 -0
- package/src/theme/CodeBlock.tsx +103 -0
- package/src/theme/Content.tsx +32 -0
- package/src/theme/Footer.tsx +53 -0
- package/src/theme/Head.tsx +80 -0
- package/src/theme/HeadContext.tsx +32 -0
- package/src/theme/Header.tsx +177 -0
- package/src/theme/Layout.tsx +44 -0
- package/src/theme/MDXComponents.tsx +40 -0
- package/src/theme/NotFound.tsx +246 -0
- package/src/theme/Search.tsx +359 -0
- package/src/theme/Sidebar.tsx +325 -0
- package/src/theme/TableOfContents.tsx +153 -0
- package/src/theme/ThemeProvider.tsx +77 -0
- package/src/theme/components/Accordion.tsx +109 -0
- package/src/theme/components/Badge.tsx +72 -0
- package/src/theme/components/Breadcrumbs.tsx +88 -0
- package/src/theme/components/Callout.tsx +115 -0
- package/src/theme/components/Card.tsx +103 -0
- package/src/theme/components/CodeGroup.tsx +79 -0
- package/src/theme/components/Columns.tsx +42 -0
- package/src/theme/components/Frame.tsx +55 -0
- package/src/theme/components/Mermaid.tsx +99 -0
- package/src/theme/components/MermaidStatic.tsx +32 -0
- package/src/theme/components/OpenAPI.tsx +160 -0
- package/src/theme/components/OpenAPIPage.tsx +16 -0
- package/src/theme/components/Steps.tsx +76 -0
- package/src/theme/components/Tabs.tsx +75 -0
- package/src/theme/components/Tooltip.tsx +108 -0
- package/src/theme/components/index.ts +14 -0
- package/src/theme/styles/globals.css +363 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface RenderedMermaid {
|
|
2
|
+
lightSvg: string;
|
|
3
|
+
darkSvg: string;
|
|
4
|
+
}
|
|
5
|
+
declare class MermaidRenderer {
|
|
6
|
+
private cacheDir;
|
|
7
|
+
private browser;
|
|
8
|
+
private page;
|
|
9
|
+
constructor(options: {
|
|
10
|
+
cacheDir: string;
|
|
11
|
+
});
|
|
12
|
+
launch(): Promise<void>;
|
|
13
|
+
render(definition: string): Promise<RenderedMermaid>;
|
|
14
|
+
renderBatch(definitions: Map<string, string>): Promise<Map<string, RenderedMermaid>>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
private renderWithTheme;
|
|
17
|
+
private readCache;
|
|
18
|
+
private writeCache;
|
|
19
|
+
private static loadPuppeteer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { MermaidRenderer, type RenderedMermaid };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mermaidContentHash
|
|
3
|
+
} from "../chunk-SCZZB7OE.js";
|
|
4
|
+
|
|
5
|
+
// src/core/mermaid-renderer.ts
|
|
6
|
+
import { resolve } from "path";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
var MermaidRenderer = class _MermaidRenderer {
|
|
9
|
+
cacheDir;
|
|
10
|
+
browser = null;
|
|
11
|
+
page = null;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.cacheDir = options.cacheDir;
|
|
14
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
async launch() {
|
|
17
|
+
const puppeteer = await _MermaidRenderer.loadPuppeteer();
|
|
18
|
+
this.browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox"] });
|
|
19
|
+
this.page = await this.browser.newPage();
|
|
20
|
+
await this.page.setContent(`
|
|
21
|
+
<!DOCTYPE html>
|
|
22
|
+
<html><head></head><body>
|
|
23
|
+
<div id="container"></div>
|
|
24
|
+
<script type="module">
|
|
25
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
|
26
|
+
window.__mermaid = mermaid;
|
|
27
|
+
window.__mermaidReady = true;
|
|
28
|
+
</script>
|
|
29
|
+
</body></html>
|
|
30
|
+
`);
|
|
31
|
+
await this.page.waitForFunction("window.__mermaidReady === true", { timeout: 3e4 });
|
|
32
|
+
}
|
|
33
|
+
async render(definition) {
|
|
34
|
+
const hash = mermaidContentHash(definition);
|
|
35
|
+
const cached = this.readCache(hash);
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
if (!this.page) {
|
|
38
|
+
throw new Error("MermaidRenderer not launched. Call launch() first.");
|
|
39
|
+
}
|
|
40
|
+
const lightSvg = await this.renderWithTheme(definition, "default", `light-${hash}`);
|
|
41
|
+
const darkSvg = await this.renderWithTheme(definition, "dark", `dark-${hash}`);
|
|
42
|
+
const result = { lightSvg, darkSvg };
|
|
43
|
+
this.writeCache(hash, result);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
async renderBatch(definitions) {
|
|
47
|
+
const results = /* @__PURE__ */ new Map();
|
|
48
|
+
const uncached = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const [hash, definition] of definitions) {
|
|
50
|
+
const cached = this.readCache(hash);
|
|
51
|
+
if (cached) {
|
|
52
|
+
results.set(hash, cached);
|
|
53
|
+
} else {
|
|
54
|
+
uncached.set(hash, definition);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (uncached.size === 0) return results;
|
|
58
|
+
if (!this.page) {
|
|
59
|
+
throw new Error("MermaidRenderer not launched. Call launch() first.");
|
|
60
|
+
}
|
|
61
|
+
for (const [hash, definition] of uncached) {
|
|
62
|
+
try {
|
|
63
|
+
const lightSvg = await this.renderWithTheme(definition, "default", `light-${hash}`);
|
|
64
|
+
const darkSvg = await this.renderWithTheme(definition, "dark", `dark-${hash}`);
|
|
65
|
+
const result = { lightSvg, darkSvg };
|
|
66
|
+
this.writeCache(hash, result);
|
|
67
|
+
results.set(hash, result);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.warn(` Warning: Failed to render mermaid diagram (${hash}):`, err instanceof Error ? err.message : err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
async close() {
|
|
75
|
+
if (this.browser) {
|
|
76
|
+
await this.browser.close();
|
|
77
|
+
this.browser = null;
|
|
78
|
+
this.page = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async renderWithTheme(definition, theme, id) {
|
|
82
|
+
return await this.page.evaluate(
|
|
83
|
+
async (def, thm, diagId) => {
|
|
84
|
+
const mermaid = window.__mermaid;
|
|
85
|
+
mermaid.initialize({ startOnLoad: false, theme: thm, securityLevel: "loose" });
|
|
86
|
+
const { svg } = await mermaid.render(diagId, def);
|
|
87
|
+
return svg;
|
|
88
|
+
},
|
|
89
|
+
definition,
|
|
90
|
+
theme,
|
|
91
|
+
id
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
readCache(hash) {
|
|
95
|
+
const cachePath = resolve(this.cacheDir, `${hash}.json`);
|
|
96
|
+
if (existsSync(cachePath)) {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
writeCache(hash, data) {
|
|
106
|
+
const cachePath = resolve(this.cacheDir, `${hash}.json`);
|
|
107
|
+
writeFileSync(cachePath, JSON.stringify(data));
|
|
108
|
+
}
|
|
109
|
+
static async loadPuppeteer() {
|
|
110
|
+
try {
|
|
111
|
+
const moduleName = "puppeteer";
|
|
112
|
+
return await import(
|
|
113
|
+
/* @vite-ignore */
|
|
114
|
+
moduleName
|
|
115
|
+
);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new Error(
|
|
118
|
+
'Puppeteer is required for mermaid build-time rendering.\nInstall it with: npm install puppeteer\nOr set mermaid.strategy to "client" in your clearify config.'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
export {
|
|
124
|
+
MermaidRenderer
|
|
125
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface OpenAPIOperation {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
operationId?: string;
|
|
6
|
+
}
|
|
7
|
+
interface OpenAPITagGroup {
|
|
8
|
+
tag: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
operations: OpenAPIOperation[];
|
|
11
|
+
}
|
|
12
|
+
declare function parseOpenAPISpec(spec: Record<string, any>): OpenAPITagGroup[];
|
|
13
|
+
|
|
14
|
+
export { type OpenAPIOperation, type OpenAPITagGroup, parseOpenAPISpec };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Root } from 'mdast';
|
|
2
|
+
|
|
3
|
+
declare function setPreRenderedMermaidSvgs(svgs: Map<string, {
|
|
4
|
+
lightSvg: string;
|
|
5
|
+
darkSvg: string;
|
|
6
|
+
}>): void;
|
|
7
|
+
declare function clearPreRenderedMermaidSvgs(): void;
|
|
8
|
+
declare function remarkMermaidToComponent(): (tree: Root) => void;
|
|
9
|
+
|
|
10
|
+
export { clearPreRenderedMermaidSvgs, remarkMermaidToComponent, setPreRenderedMermaidSvgs };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearPreRenderedMermaidSvgs,
|
|
3
|
+
remarkMermaidToComponent,
|
|
4
|
+
setPreRenderedMermaidSvgs
|
|
5
|
+
} from "../chunk-PRTER35L.js";
|
|
6
|
+
import "../chunk-SCZZB7OE.js";
|
|
7
|
+
export {
|
|
8
|
+
clearPreRenderedMermaidSvgs,
|
|
9
|
+
remarkMermaidToComponent,
|
|
10
|
+
setPreRenderedMermaidSvgs
|
|
11
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PageFrontmatter, ResolvedSection, SectionNavigation, RouteEntry, NavigationItem } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
interface SearchEntry {
|
|
4
|
+
id: number;
|
|
5
|
+
path: string;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
content: string;
|
|
9
|
+
sectionId?: string;
|
|
10
|
+
sectionLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
declare function buildSearchIndex(docs: DocFile[], sectionId?: string, sectionLabel?: string): SearchEntry[];
|
|
13
|
+
|
|
14
|
+
interface DocFile {
|
|
15
|
+
filePath: string;
|
|
16
|
+
routePath: string;
|
|
17
|
+
frontmatter: PageFrontmatter;
|
|
18
|
+
}
|
|
19
|
+
declare function scanDocs(docsDir: string, exclude?: string[], basePath?: string): DocFile[];
|
|
20
|
+
declare function buildNavigation(docs: DocFile[], basePath?: string): NavigationItem[];
|
|
21
|
+
declare function buildRoutes(docs: DocFile[], sectionId?: string): RouteEntry[];
|
|
22
|
+
interface SectionData {
|
|
23
|
+
section: ResolvedSection;
|
|
24
|
+
docs: DocFile[];
|
|
25
|
+
navigation: SectionNavigation;
|
|
26
|
+
routes: RouteEntry[];
|
|
27
|
+
searchEntries: SearchEntry[];
|
|
28
|
+
}
|
|
29
|
+
declare function buildSectionData(section: ResolvedSection, changelogPath?: string, customNavigation?: NavigationItem[] | null): SectionData;
|
|
30
|
+
|
|
31
|
+
export { type DocFile as D, type SectionData as S, type SearchEntry, buildRoutes as a, buildNavigation as b, buildSearchIndex, buildSectionData as c, scanDocs as s };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSite
|
|
3
|
+
} from "../chunk-CQ4MNGBE.js";
|
|
4
|
+
import "../chunk-PRTER35L.js";
|
|
5
|
+
import "../chunk-SCZZB7OE.js";
|
|
6
|
+
import "../chunk-NXQNNLGC.js";
|
|
7
|
+
import "../chunk-B2Q23JW3.js";
|
|
8
|
+
import "../chunk-IBK35HZR.js";
|
|
9
|
+
import "../chunk-V7LLYIRO.js";
|
|
10
|
+
import "../chunk-L24ILRSX.js";
|
|
11
|
+
import "../chunk-5TD7NQIW.js";
|
|
12
|
+
export {
|
|
13
|
+
buildSite
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as vite from 'vite';
|
|
2
|
+
export { buildSite as build } from './build.js';
|
|
3
|
+
export { init } from './init.js';
|
|
4
|
+
export { checkLinks } from './check.js';
|
|
5
|
+
|
|
6
|
+
declare function createServer(options?: {
|
|
7
|
+
port?: number;
|
|
8
|
+
host?: boolean;
|
|
9
|
+
}): Promise<vite.ViteDevServer>;
|
|
10
|
+
|
|
11
|
+
export { createServer };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSite
|
|
3
|
+
} from "../chunk-CQ4MNGBE.js";
|
|
4
|
+
import {
|
|
5
|
+
remarkMermaidToComponent
|
|
6
|
+
} from "../chunk-PRTER35L.js";
|
|
7
|
+
import "../chunk-SCZZB7OE.js";
|
|
8
|
+
import {
|
|
9
|
+
clearifyPlugin
|
|
10
|
+
} from "../chunk-NXQNNLGC.js";
|
|
11
|
+
import "../chunk-B2Q23JW3.js";
|
|
12
|
+
import {
|
|
13
|
+
init
|
|
14
|
+
} from "../chunk-GFD54GNO.js";
|
|
15
|
+
import {
|
|
16
|
+
checkLinks
|
|
17
|
+
} from "../chunk-WT5W333R.js";
|
|
18
|
+
import {
|
|
19
|
+
loadUserConfig,
|
|
20
|
+
resolveConfig
|
|
21
|
+
} from "../chunk-IBK35HZR.js";
|
|
22
|
+
import "../chunk-V7LLYIRO.js";
|
|
23
|
+
import "../chunk-L24ILRSX.js";
|
|
24
|
+
import "../chunk-5TD7NQIW.js";
|
|
25
|
+
|
|
26
|
+
// src/node/index.ts
|
|
27
|
+
import { createServer as createViteServer } from "vite";
|
|
28
|
+
import react from "@vitejs/plugin-react";
|
|
29
|
+
import mdx from "@mdx-js/rollup";
|
|
30
|
+
import remarkGfm from "remark-gfm";
|
|
31
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
32
|
+
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
33
|
+
import rehypeShiki from "@shikijs/rehype";
|
|
34
|
+
import { resolve } from "path";
|
|
35
|
+
import { fileURLToPath } from "url";
|
|
36
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
37
|
+
import { existsSync } from "fs";
|
|
38
|
+
function findPackageRoot() {
|
|
39
|
+
let dir = fileURLToPath(new URL(".", import.meta.url));
|
|
40
|
+
for (let i = 0; i < 10; i++) {
|
|
41
|
+
if (existsSync(resolve(dir, "package.json"))) return dir;
|
|
42
|
+
const parent = resolve(dir, "..");
|
|
43
|
+
if (parent === dir) break;
|
|
44
|
+
dir = parent;
|
|
45
|
+
}
|
|
46
|
+
return process.cwd();
|
|
47
|
+
}
|
|
48
|
+
function resolveClientPath(...segments) {
|
|
49
|
+
const packageRoot = findPackageRoot();
|
|
50
|
+
return resolve(packageRoot, "src", "client", ...segments);
|
|
51
|
+
}
|
|
52
|
+
function createDevConfig(overrides = {}) {
|
|
53
|
+
const root = resolveClientPath();
|
|
54
|
+
const { userRoot, mermaidStrategy, ...viteOverrides } = overrides;
|
|
55
|
+
return {
|
|
56
|
+
root,
|
|
57
|
+
plugins: [
|
|
58
|
+
tailwindcss(),
|
|
59
|
+
...clearifyPlugin({
|
|
60
|
+
root: userRoot ?? process.cwd(),
|
|
61
|
+
mermaidStrategy
|
|
62
|
+
}),
|
|
63
|
+
{ enforce: "pre", ...mdx({
|
|
64
|
+
providerImportSource: "@mdx-js/react",
|
|
65
|
+
remarkPlugins: [remarkMermaidToComponent, remarkGfm, remarkFrontmatter, [remarkMdxFrontmatter, { name: "frontmatter" }]],
|
|
66
|
+
rehypePlugins: [
|
|
67
|
+
[rehypeShiki, {
|
|
68
|
+
themes: { light: "github-light", dark: "github-dark" },
|
|
69
|
+
defaultColor: false
|
|
70
|
+
}]
|
|
71
|
+
]
|
|
72
|
+
}) },
|
|
73
|
+
react({ include: /\.(jsx|tsx|md|mdx)$/ })
|
|
74
|
+
],
|
|
75
|
+
server: {
|
|
76
|
+
port: 4747,
|
|
77
|
+
...viteOverrides.server
|
|
78
|
+
},
|
|
79
|
+
resolve: {
|
|
80
|
+
alias: {
|
|
81
|
+
"@clearify": resolve(root, "..")
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
...viteOverrides
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function createServer(options = {}) {
|
|
88
|
+
const userRoot = process.cwd();
|
|
89
|
+
const userConfig = await loadUserConfig(userRoot);
|
|
90
|
+
const siteConfig = resolveConfig(userConfig, userRoot);
|
|
91
|
+
const mermaidStrategy = siteConfig.mermaid?.strategy ?? "client";
|
|
92
|
+
const config = createDevConfig({
|
|
93
|
+
userRoot,
|
|
94
|
+
mermaidStrategy,
|
|
95
|
+
server: {
|
|
96
|
+
port: options.port ?? siteConfig.port,
|
|
97
|
+
host: options.host
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
const server = await createViteServer(config);
|
|
101
|
+
return server;
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
buildSite as build,
|
|
105
|
+
checkLinks,
|
|
106
|
+
createServer,
|
|
107
|
+
init
|
|
108
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface NestJSPresetOptions {
|
|
2
|
+
/** Path to the NestJS app module (default: './src/app.module.ts') */
|
|
3
|
+
appModule?: string;
|
|
4
|
+
/** Output path for the generated spec (default: './docs/openapi.json') */
|
|
5
|
+
output?: string;
|
|
6
|
+
/** Swagger document title */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Swagger document description */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** API version */
|
|
11
|
+
version?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function generateSpec(options?: NestJSPresetOptions): Promise<void>;
|
|
14
|
+
|
|
15
|
+
export { type NestJSPresetOptions, generateSpec };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/presets/nestjs.ts
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
import { tmpdir } from "os";
|
|
7
|
+
import { randomBytes } from "crypto";
|
|
8
|
+
async function bundleModule(entryPoint) {
|
|
9
|
+
const { build } = await import("esbuild");
|
|
10
|
+
const tmpId = randomBytes(4).toString("hex");
|
|
11
|
+
const outFile = resolve(tmpdir(), `clearify-nestjs-${tmpId}.mjs`);
|
|
12
|
+
try {
|
|
13
|
+
await build({
|
|
14
|
+
entryPoints: [entryPoint],
|
|
15
|
+
outfile: outFile,
|
|
16
|
+
bundle: true,
|
|
17
|
+
format: "esm",
|
|
18
|
+
platform: "node",
|
|
19
|
+
write: true,
|
|
20
|
+
// Keep NestJS packages external — they're loaded from node_modules at runtime
|
|
21
|
+
external: ["@nestjs/*", "reflect-metadata", "class-transformer", "class-validator", "rxjs", "rxjs/*"]
|
|
22
|
+
});
|
|
23
|
+
return await import(pathToFileURL(outFile).href);
|
|
24
|
+
} finally {
|
|
25
|
+
try {
|
|
26
|
+
rmSync(outFile, { force: true });
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function generateSpec(options = {}) {
|
|
32
|
+
const {
|
|
33
|
+
appModule = "./src/app.module.ts",
|
|
34
|
+
output = "./docs/openapi.json",
|
|
35
|
+
title = "API Documentation",
|
|
36
|
+
description = "",
|
|
37
|
+
version = "1.0.0"
|
|
38
|
+
} = options;
|
|
39
|
+
let NestFactory;
|
|
40
|
+
let SwaggerModule;
|
|
41
|
+
let DocumentBuilder;
|
|
42
|
+
const nestCorePkg = "@nestjs/core";
|
|
43
|
+
const swaggerPkg = "@nestjs/swagger";
|
|
44
|
+
try {
|
|
45
|
+
const nestCore = await import(
|
|
46
|
+
/* @vite-ignore */
|
|
47
|
+
nestCorePkg
|
|
48
|
+
);
|
|
49
|
+
NestFactory = nestCore.NestFactory;
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"@nestjs/core is not installed. Install it to use the NestJS preset:\n npm install @nestjs/core @nestjs/common"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const swagger = await import(
|
|
57
|
+
/* @vite-ignore */
|
|
58
|
+
swaggerPkg
|
|
59
|
+
);
|
|
60
|
+
SwaggerModule = swagger.SwaggerModule;
|
|
61
|
+
DocumentBuilder = swagger.DocumentBuilder;
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"@nestjs/swagger is not installed. Install it to generate OpenAPI specs:\n npm install @nestjs/swagger"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const modulePath = resolve(process.cwd(), appModule);
|
|
68
|
+
let AppModule;
|
|
69
|
+
try {
|
|
70
|
+
const mod = modulePath.endsWith(".ts") || modulePath.endsWith(".tsx") ? await bundleModule(modulePath) : await import(
|
|
71
|
+
/* @vite-ignore */
|
|
72
|
+
pathToFileURL(modulePath).href
|
|
73
|
+
);
|
|
74
|
+
AppModule = mod.AppModule ?? mod.default;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Failed to import NestJS app module from "${modulePath}":
|
|
78
|
+
${err instanceof Error ? err.message : err}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (!AppModule) {
|
|
82
|
+
throw new Error(`No AppModule export found in "${modulePath}"`);
|
|
83
|
+
}
|
|
84
|
+
const app = await NestFactory.create(AppModule, { logger: false });
|
|
85
|
+
try {
|
|
86
|
+
const config = new DocumentBuilder().setTitle(title).setDescription(description).setVersion(version).build();
|
|
87
|
+
const document = SwaggerModule.createDocument(app, config);
|
|
88
|
+
const outputPath = resolve(process.cwd(), output);
|
|
89
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
90
|
+
writeFileSync(outputPath, JSON.stringify(document, null, 2));
|
|
91
|
+
console.log(` OpenAPI spec written to ${output}`);
|
|
92
|
+
} finally {
|
|
93
|
+
await app.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
generateSpec
|
|
98
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
interface SectionConfig {
|
|
2
|
+
label: string;
|
|
3
|
+
docsDir: string;
|
|
4
|
+
basePath?: string;
|
|
5
|
+
draft?: boolean;
|
|
6
|
+
sitemap?: boolean;
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
}
|
|
9
|
+
interface ResolvedSection {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
docsDir: string;
|
|
13
|
+
basePath: string;
|
|
14
|
+
draft: boolean;
|
|
15
|
+
sitemap: boolean;
|
|
16
|
+
exclude: string[];
|
|
17
|
+
}
|
|
18
|
+
interface SectionNavigation {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
basePath: string;
|
|
22
|
+
navigation: NavigationItem[];
|
|
23
|
+
}
|
|
24
|
+
interface ClearifyConfig {
|
|
25
|
+
name: string;
|
|
26
|
+
docsDir: string;
|
|
27
|
+
outDir: string;
|
|
28
|
+
port: number;
|
|
29
|
+
siteUrl?: string;
|
|
30
|
+
sections?: SectionConfig[];
|
|
31
|
+
theme: {
|
|
32
|
+
primaryColor: string;
|
|
33
|
+
mode: 'light' | 'dark' | 'auto';
|
|
34
|
+
};
|
|
35
|
+
logo?: {
|
|
36
|
+
light?: string;
|
|
37
|
+
dark?: string;
|
|
38
|
+
};
|
|
39
|
+
navigation?: NavigationItem[] | null;
|
|
40
|
+
exclude?: string[];
|
|
41
|
+
mermaid?: {
|
|
42
|
+
strategy?: 'client' | 'build';
|
|
43
|
+
};
|
|
44
|
+
openapi?: {
|
|
45
|
+
spec: string;
|
|
46
|
+
basePath?: string;
|
|
47
|
+
generatePages?: boolean;
|
|
48
|
+
};
|
|
49
|
+
links?: {
|
|
50
|
+
github?: string;
|
|
51
|
+
[key: string]: string | undefined;
|
|
52
|
+
};
|
|
53
|
+
customCss?: string;
|
|
54
|
+
headTags?: string[];
|
|
55
|
+
}
|
|
56
|
+
interface NavigationItem {
|
|
57
|
+
label: string;
|
|
58
|
+
path?: string;
|
|
59
|
+
icon?: string;
|
|
60
|
+
badge?: string;
|
|
61
|
+
badgeColor?: string;
|
|
62
|
+
children?: NavigationItem[];
|
|
63
|
+
}
|
|
64
|
+
interface PageFrontmatter {
|
|
65
|
+
title?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
icon?: string;
|
|
68
|
+
order?: number;
|
|
69
|
+
}
|
|
70
|
+
interface RouteEntry {
|
|
71
|
+
path: string;
|
|
72
|
+
filePath: string;
|
|
73
|
+
frontmatter: PageFrontmatter;
|
|
74
|
+
sectionId?: string;
|
|
75
|
+
componentPath?: string;
|
|
76
|
+
}
|
|
77
|
+
declare function defineConfig(config: Partial<ClearifyConfig>): Partial<ClearifyConfig>;
|
|
78
|
+
|
|
79
|
+
export { type ClearifyConfig, type NavigationItem, type PageFrontmatter, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface ClearifyPluginOptions {
|
|
4
|
+
root?: string;
|
|
5
|
+
mermaidSvgs?: Record<string, {
|
|
6
|
+
lightSvg: string;
|
|
7
|
+
darkSvg: string;
|
|
8
|
+
}>;
|
|
9
|
+
mermaidStrategy?: 'client' | 'build';
|
|
10
|
+
}
|
|
11
|
+
declare function clearifyPlugin(options?: ClearifyPluginOptions): Plugin[];
|
|
12
|
+
|
|
13
|
+
export { clearifyPlugin };
|