@clarify-labs/cli 0.1.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 +661 -0
- package/bin/clarify.js +2 -0
- package/output/cli/commands/build.d.ts +2 -0
- package/output/cli/commands/dev.d.ts +2 -0
- package/output/cli/commands/init.d.ts +2 -0
- package/output/cli/index.d.ts +2 -0
- package/output/cli/options.d.ts +17 -0
- package/output/cli/package.d.ts +1 -0
- package/output/cli.cjs +187 -0
- package/output/cli.cjs.map +1 -0
- package/output/cli.js +187 -0
- package/output/cli.js.map +1 -0
- package/output/core/config-schema.d.ts +140 -0
- package/output/core/config.d.ts +3 -0
- package/output/core/hooks.d.ts +5 -0
- package/output/core/options.d.ts +24 -0
- package/output/core/plugin.d.ts +4 -0
- package/output/core/runtime-deps.d.ts +7 -0
- package/output/core/ssg.d.ts +8 -0
- package/output/core/user-config.d.ts +7 -0
- package/output/core/utils.d.ts +1 -0
- package/output/core/virtual-modules.d.ts +18 -0
- package/output/core/vite-config.d.ts +10 -0
- package/output/index.cjs +4 -0
- package/output/index.d.ts +5 -0
- package/output/index.js +2 -0
- package/output/parsers/frontmatter.d.ts +5 -0
- package/output/parsers/mdx.d.ts +14 -0
- package/output/parsers/routes.d.ts +13 -0
- package/output/plugin-BUWojlPv.js +1277 -0
- package/output/plugin-BUWojlPv.js.map +1 -0
- package/output/plugin-CEyCiD5r.cjs +1330 -0
- package/output/plugin-CEyCiD5r.cjs.map +1 -0
- package/output/plugins/content-artifacts/index.d.ts +2 -0
- package/output/plugins/content-artifacts/raw-content.d.ts +8 -0
- package/output/plugins/content-artifacts/server.d.ts +5 -0
- package/output/plugins/html-shell/index.d.ts +2 -0
- package/output/plugins/openapi/index.d.ts +2 -0
- package/output/plugins/openapi/parser.d.ts +5 -0
- package/output/plugins/openapi/virtual-modules.d.ts +4 -0
- package/output/types.d.ts +191 -0
- package/package.json +63 -0
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import mdxPlugin from "@mdx-js/rollup";
|
|
4
|
+
import tailwindcss from "@tailwindcss/vite";
|
|
5
|
+
import react from "@vitejs/plugin-react";
|
|
6
|
+
import GithubSlugger, { slug } from "github-slugger";
|
|
7
|
+
import { toString } from "mdast-util-to-string";
|
|
8
|
+
import remarkGfm from "remark-gfm";
|
|
9
|
+
import shiki from "shiki";
|
|
10
|
+
import { visit } from "unist-util-visit";
|
|
11
|
+
import { remark } from "remark";
|
|
12
|
+
import matter from "gray-matter";
|
|
13
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { pathToFileURL } from "node:url";
|
|
17
|
+
import { build, loadConfigFromFile } from "vite";
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
//#region source/core/utils.ts
|
|
20
|
+
function escapeHtml(text) {
|
|
21
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region source/parsers/mdx.ts
|
|
25
|
+
function getLanguage(className) {
|
|
26
|
+
return (Array.isArray(className) ? className : typeof className === "string" ? className.split(/\s+/) : []).find((item) => typeof item === "string" && item.startsWith("language-"))?.replace(/^language-/, "") || "txt";
|
|
27
|
+
}
|
|
28
|
+
function getTextContent(node) {
|
|
29
|
+
if (!node) return "";
|
|
30
|
+
if (typeof node.value === "string") return node.value;
|
|
31
|
+
return node.children?.map(getTextContent).join("") ?? "";
|
|
32
|
+
}
|
|
33
|
+
function renderPlainCode(code) {
|
|
34
|
+
return code.split("\n").map((line) => `<span>${escapeHtml(line)}</span>`).join("\n");
|
|
35
|
+
}
|
|
36
|
+
var highlighter;
|
|
37
|
+
async function getHighlighter() {
|
|
38
|
+
highlighter = highlighter ?? await shiki.getHighlighter({ theme: "css-variables" });
|
|
39
|
+
return highlighter;
|
|
40
|
+
}
|
|
41
|
+
function rehypeParseCodeBlocks() {
|
|
42
|
+
return (tree) => {
|
|
43
|
+
visit(tree, "element", (node, _nodeIndex, parentNode) => {
|
|
44
|
+
if (node.tagName !== "code" || !parentNode) return;
|
|
45
|
+
parentNode.properties = parentNode.properties ?? {};
|
|
46
|
+
node.properties = node.properties ?? {};
|
|
47
|
+
const language = getLanguage(node.properties.className);
|
|
48
|
+
parentNode.properties.language = language;
|
|
49
|
+
node.properties.language = language;
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function rehypeShiki() {
|
|
54
|
+
return async (tree) => {
|
|
55
|
+
const shikiHighlighter = await getHighlighter();
|
|
56
|
+
visit(tree, "element", (node) => {
|
|
57
|
+
if (node.tagName !== "pre") return;
|
|
58
|
+
const codeNode = node.children?.[0];
|
|
59
|
+
if (codeNode?.tagName !== "code") return;
|
|
60
|
+
node.properties = node.properties ?? {};
|
|
61
|
+
codeNode.properties = codeNode.properties ?? {};
|
|
62
|
+
const code = getTextContent(codeNode);
|
|
63
|
+
const language = typeof node.properties.language === "string" ? node.properties.language : getLanguage(codeNode.properties.className);
|
|
64
|
+
node.properties.code = code;
|
|
65
|
+
codeNode.properties.code = code;
|
|
66
|
+
codeNode.properties.language = language;
|
|
67
|
+
let highlighted = renderPlainCode(code);
|
|
68
|
+
if (language && language !== "txt") try {
|
|
69
|
+
const tokens = shikiHighlighter.codeToThemedTokens(code, language);
|
|
70
|
+
highlighted = shiki.renderToHtml(tokens, { elements: {
|
|
71
|
+
pre: ({ children }) => children,
|
|
72
|
+
code: ({ children }) => children,
|
|
73
|
+
line: ({ children }) => `<span>${children}</span>`
|
|
74
|
+
} });
|
|
75
|
+
} catch {
|
|
76
|
+
highlighted = renderPlainCode(code);
|
|
77
|
+
}
|
|
78
|
+
codeNode.children = [{
|
|
79
|
+
type: "text",
|
|
80
|
+
value: highlighted
|
|
81
|
+
}];
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function rehypeSlugSections() {
|
|
86
|
+
return (tree) => {
|
|
87
|
+
const slugger = new GithubSlugger();
|
|
88
|
+
visit(tree, "element", (node) => {
|
|
89
|
+
if (node.tagName !== "h2" && node.tagName !== "h3") return;
|
|
90
|
+
node.properties = node.properties ?? {};
|
|
91
|
+
if (node.properties.id) return;
|
|
92
|
+
node.properties.id = slugger.slug(toString(node));
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
var remarkPlugins = [remarkGfm];
|
|
97
|
+
var rehypePlugins = [
|
|
98
|
+
rehypeSlugSections,
|
|
99
|
+
rehypeParseCodeBlocks,
|
|
100
|
+
rehypeShiki
|
|
101
|
+
];
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region source/parsers/frontmatter.ts
|
|
104
|
+
/**
|
|
105
|
+
* 从 MDX 文件内容中提取 frontmatter。
|
|
106
|
+
* 使用 gray-matter 支持 YAML frontmatter 的标准语法和类型解析。
|
|
107
|
+
*/
|
|
108
|
+
function extractFrontmatter(content) {
|
|
109
|
+
return matter(content).data;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region source/parsers/routes.ts
|
|
113
|
+
function kebabToTitle$1(str) {
|
|
114
|
+
return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
115
|
+
}
|
|
116
|
+
function parseMdxTree(content) {
|
|
117
|
+
return remark.parse(content);
|
|
118
|
+
}
|
|
119
|
+
/** 从内容中提取第一个 H1 标题 */
|
|
120
|
+
function extractH1(content) {
|
|
121
|
+
const tree = parseMdxTree(content);
|
|
122
|
+
let title = "";
|
|
123
|
+
visit(tree, "heading", (node) => {
|
|
124
|
+
if (!title && node.depth === 1) title = toString(node);
|
|
125
|
+
});
|
|
126
|
+
return title;
|
|
127
|
+
}
|
|
128
|
+
/** 从 MDX/Markdown 内容中提取 H2/H3 章节 */
|
|
129
|
+
function extractMdxSections(content) {
|
|
130
|
+
const sections = [];
|
|
131
|
+
const slugger = new GithubSlugger();
|
|
132
|
+
visit(parseMdxTree(content), "heading", (node) => {
|
|
133
|
+
if (node.depth !== 2 && node.depth !== 3) return;
|
|
134
|
+
const title = toString(node);
|
|
135
|
+
sections.push({
|
|
136
|
+
id: slugger.slug(title),
|
|
137
|
+
title,
|
|
138
|
+
level: node.depth
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
return sections;
|
|
142
|
+
}
|
|
143
|
+
function navigationSections(sections) {
|
|
144
|
+
return sections.map((s) => ({
|
|
145
|
+
id: s.id,
|
|
146
|
+
title: s.title,
|
|
147
|
+
badge: s.badge,
|
|
148
|
+
tags: s.tags
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
function normalizePath(path) {
|
|
152
|
+
const clean = "/" + path.replace(/^\/+|\/+$/g, "");
|
|
153
|
+
return clean === "/" ? "/" : clean.replace(/\/+/g, "/");
|
|
154
|
+
}
|
|
155
|
+
function basePathFromRef(ref) {
|
|
156
|
+
const withoutExtension = ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "");
|
|
157
|
+
return normalizePath(withoutExtension === "index" ? "/" : withoutExtension.replace(/\/index$/, ""));
|
|
158
|
+
}
|
|
159
|
+
function routePathFromRef(ref) {
|
|
160
|
+
return basePathFromRef(ref);
|
|
161
|
+
}
|
|
162
|
+
function virtualModuleIdFromRef(ref) {
|
|
163
|
+
return "virtual:clarify-page/" + ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "").replace(/\/+/g, "/");
|
|
164
|
+
}
|
|
165
|
+
function localizedRoutePath(basePath, locale, i18n) {
|
|
166
|
+
const normalizedBasePath = normalizePath(basePath);
|
|
167
|
+
if (locale === i18n.defaultLocale) return normalizedBasePath;
|
|
168
|
+
const prefix = normalizePath(locale);
|
|
169
|
+
return normalizedBasePath === "/" ? prefix : normalizePath(`${prefix}${normalizedBasePath}`);
|
|
170
|
+
}
|
|
171
|
+
function resolveLocalizedText(text, locale, fallbackLocale) {
|
|
172
|
+
if (typeof text === "string" || text === void 0) return text;
|
|
173
|
+
return text[locale] ?? (fallbackLocale ? text[fallbackLocale] : void 0) ?? Object.values(text)[0];
|
|
174
|
+
}
|
|
175
|
+
function findContentRoutes(dir, base = dir) {
|
|
176
|
+
const routes = [];
|
|
177
|
+
if (!existsSync(dir)) return routes;
|
|
178
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const fullPath = join(dir, entry.name);
|
|
181
|
+
if (entry.isDirectory()) routes.push(...findContentRoutes(fullPath, base));
|
|
182
|
+
else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
183
|
+
const relativePath = relative(base, fullPath);
|
|
184
|
+
const pathParts = relativePath.replace(/\.mdx?$/, "").split("/");
|
|
185
|
+
const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
186
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
187
|
+
const frontmatter = extractFrontmatter(content);
|
|
188
|
+
let title = typeof frontmatter.title === "string" ? frontmatter.title : "";
|
|
189
|
+
if (!title) {
|
|
190
|
+
const lastPart = pathParts[pathParts.length - 1] ?? "";
|
|
191
|
+
title = kebabToTitle$1(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : extractH1(content) : lastPart) || "Untitled";
|
|
192
|
+
}
|
|
193
|
+
routes.push({
|
|
194
|
+
path: cleanPath,
|
|
195
|
+
basePath: cleanPath,
|
|
196
|
+
filePath: fullPath,
|
|
197
|
+
virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/"),
|
|
198
|
+
title,
|
|
199
|
+
kind: "mdx",
|
|
200
|
+
sections: extractMdxSections(content)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return routes;
|
|
205
|
+
}
|
|
206
|
+
function buildNavigation(routes) {
|
|
207
|
+
const root = [];
|
|
208
|
+
for (const route of routes) {
|
|
209
|
+
if (route.path === "/") continue;
|
|
210
|
+
const parts = route.path.replace(/^\//, "").split("/");
|
|
211
|
+
let current = root;
|
|
212
|
+
for (let i = 0; i < parts.length; i++) {
|
|
213
|
+
const pathSoFar = "/" + parts.slice(0, i + 1).join("/");
|
|
214
|
+
let node = current.find((n) => n.path === pathSoFar);
|
|
215
|
+
if (!node) {
|
|
216
|
+
node = {
|
|
217
|
+
path: pathSoFar,
|
|
218
|
+
title: i === parts.length - 1 ? route.title : kebabToTitle$1(parts[i]),
|
|
219
|
+
children: []
|
|
220
|
+
};
|
|
221
|
+
current.push(node);
|
|
222
|
+
}
|
|
223
|
+
if (i === parts.length - 1 && route.sections) node.sections = navigationSections(route.sections);
|
|
224
|
+
if (i < parts.length - 1) {
|
|
225
|
+
node.children = node.children ?? [];
|
|
226
|
+
current = node.children;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return root;
|
|
231
|
+
}
|
|
232
|
+
function resolvePageItem(item) {
|
|
233
|
+
if (typeof item === "string") return { pageRef: item };
|
|
234
|
+
if ("openapi" in item) return {
|
|
235
|
+
openapiRef: item.openapi,
|
|
236
|
+
title: item.title,
|
|
237
|
+
icon: item.icon
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
pageRef: item.page,
|
|
241
|
+
redirect: item.redirect,
|
|
242
|
+
title: item.title,
|
|
243
|
+
icon: item.icon
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function buildNavigationFromConfig(routes, config) {
|
|
247
|
+
const routeMap = new Map(routes.map((r) => [r.path, r]));
|
|
248
|
+
return config.map((group) => {
|
|
249
|
+
const children = group.pages.map((item) => {
|
|
250
|
+
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
251
|
+
if (openapiRef) {
|
|
252
|
+
const path = "/" + openapiRef.replace(/\.openapi\.(json|yaml|yml)$/, "");
|
|
253
|
+
const route = routeMap.get(path);
|
|
254
|
+
return {
|
|
255
|
+
path,
|
|
256
|
+
title: resolveLocalizedText(title, "", "") ?? route?.title ?? kebabToTitle$1(path.split("/").pop() ?? openapiRef),
|
|
257
|
+
icon,
|
|
258
|
+
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const ref = pageRef ?? "";
|
|
262
|
+
const path = ref === "index" ? "/" : "/" + ref;
|
|
263
|
+
const route = routeMap.get(path);
|
|
264
|
+
return {
|
|
265
|
+
path: redirect ? redirect : path,
|
|
266
|
+
title: resolveLocalizedText(title, "", "") ?? route?.title ?? kebabToTitle$1(path.split("/").pop() ?? ref),
|
|
267
|
+
icon,
|
|
268
|
+
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
path: children[0]?.path ?? "/",
|
|
273
|
+
title: resolveLocalizedText(group.group, "", "") ?? "",
|
|
274
|
+
icon: group.icon,
|
|
275
|
+
children
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
function localizeNavigationPaths(nodes, locale, i18n) {
|
|
280
|
+
return nodes.map((node) => ({
|
|
281
|
+
...node,
|
|
282
|
+
path: localizedRoutePath(node.path, locale, i18n),
|
|
283
|
+
children: node.children ? localizeNavigationPaths(node.children, locale, i18n) : void 0
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
function buildLocalizedNavigation(routes, config, i18n) {
|
|
287
|
+
if (!i18n) return void 0;
|
|
288
|
+
const result = {};
|
|
289
|
+
for (const locale of i18n.locales) {
|
|
290
|
+
const localeRoutes = routes.filter((route) => route.locale === locale.code);
|
|
291
|
+
if (!config || config === "FileTree") {
|
|
292
|
+
const baseNavigation = buildNavigation(localeRoutes.map((route) => ({
|
|
293
|
+
...route,
|
|
294
|
+
path: route.basePath ?? route.path
|
|
295
|
+
})));
|
|
296
|
+
result[locale.code] = localizeNavigationPaths(baseNavigation, locale.code, i18n);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const routeMap = new Map(localeRoutes.map((route) => [route.basePath ?? route.path, route]));
|
|
300
|
+
result[locale.code] = config.map((group) => {
|
|
301
|
+
const children = group.pages.map((item) => {
|
|
302
|
+
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
303
|
+
const ref = openapiRef ?? pageRef ?? "";
|
|
304
|
+
const basePath = basePathFromRef(ref);
|
|
305
|
+
const route = routeMap.get(basePath);
|
|
306
|
+
const path = route?.path ?? localizedRoutePath(basePath, locale.code, i18n);
|
|
307
|
+
return {
|
|
308
|
+
path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale.code, i18n) : void 0) ?? path,
|
|
309
|
+
title: resolveLocalizedText(title, locale.code, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
|
|
310
|
+
icon,
|
|
311
|
+
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
path: children[0]?.path ?? localizedRoutePath("/", locale.code, i18n),
|
|
316
|
+
title: resolveLocalizedText(group.group, locale.code, i18n.defaultLocale) ?? "",
|
|
317
|
+
icon: group.icon,
|
|
318
|
+
children
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region source/plugins/content-artifacts/raw-content.ts
|
|
326
|
+
function normalizeBasePath(basePath) {
|
|
327
|
+
if (!basePath || basePath === "/") return "";
|
|
328
|
+
return "/" + basePath.replace(/^\/+|\/+$/g, "");
|
|
329
|
+
}
|
|
330
|
+
function routeToMarkdownUrl(routePath) {
|
|
331
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
332
|
+
}
|
|
333
|
+
function routeToOpenAPIUrl(routePath, filePath) {
|
|
334
|
+
const extension = extname(filePath).toLowerCase() || ".json";
|
|
335
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
336
|
+
}
|
|
337
|
+
function enrichRoutesWithRawContent(routes) {
|
|
338
|
+
for (const route of routes) route.rawContentUrl = route.kind === "openapi" ? routeToOpenAPIUrl(route.path, route.filePath) : routeToMarkdownUrl(route.path);
|
|
339
|
+
}
|
|
340
|
+
function readRawContent(route) {
|
|
341
|
+
return readFileSync(route.filePath, "utf-8");
|
|
342
|
+
}
|
|
343
|
+
function writeRawContentFiles(routes, outputDirectory) {
|
|
344
|
+
for (const route of routes) {
|
|
345
|
+
if (!route.rawContentUrl) continue;
|
|
346
|
+
const outFile = join(outputDirectory, route.rawContentUrl.replace(/^\//, ""));
|
|
347
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
348
|
+
writeFileSync(outFile, readRawContent(route), "utf-8");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function createLlmsTxt(routes, projectConfig) {
|
|
352
|
+
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
353
|
+
const lines = [`# ${projectConfig.title}`, ""];
|
|
354
|
+
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
355
|
+
lines.push("## Docs");
|
|
356
|
+
for (const route of routes.filter((route) => route.kind === "mdx")) {
|
|
357
|
+
if (!route.rawContentUrl) continue;
|
|
358
|
+
lines.push(`- [${route.title}](${basePath}${route.rawContentUrl})`);
|
|
359
|
+
}
|
|
360
|
+
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
361
|
+
if (openApiRoutes.length > 0) {
|
|
362
|
+
lines.push("", "## OpenAPI");
|
|
363
|
+
for (const route of openApiRoutes) {
|
|
364
|
+
if (!route.rawContentUrl) continue;
|
|
365
|
+
lines.push(`- [${route.title}](${basePath}${route.rawContentUrl})`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return `${lines.join("\n")}\n`;
|
|
369
|
+
}
|
|
370
|
+
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
371
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
372
|
+
writeFileSync(join(outputDirectory, "llms.txt"), createLlmsTxt(routes, projectConfig), "utf-8");
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region source/plugins/content-artifacts/server.ts
|
|
376
|
+
function normalizeRoutePrefix(routePrefix) {
|
|
377
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
378
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
379
|
+
}
|
|
380
|
+
function resolveContentArtifactPath(url, projectConfig) {
|
|
381
|
+
const requestPath = url?.split("?")[0] ?? "";
|
|
382
|
+
const basePath = normalizeRoutePrefix(projectConfig.routePrefix);
|
|
383
|
+
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
384
|
+
}
|
|
385
|
+
function resolveRawContentType(route) {
|
|
386
|
+
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.rawContentUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
387
|
+
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
388
|
+
}
|
|
389
|
+
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
390
|
+
const contentPath = resolveContentArtifactPath(req.url, projectConfig);
|
|
391
|
+
if (contentPath === "/llms.txt") {
|
|
392
|
+
res.statusCode = 200;
|
|
393
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
394
|
+
res.end(createLlmsTxt(routes, projectConfig));
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
const route = routes.find((route) => route.rawContentUrl === contentPath);
|
|
398
|
+
if (!route) return false;
|
|
399
|
+
res.statusCode = 200;
|
|
400
|
+
res.setHeader("Content-Type", resolveRawContentType(route));
|
|
401
|
+
res.end(readRawContent(route));
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region source/plugins/content-artifacts/index.ts
|
|
406
|
+
function createContentArtifactsPlugin() {
|
|
407
|
+
return {
|
|
408
|
+
name: "clarify:content-artifacts",
|
|
409
|
+
hooks: {
|
|
410
|
+
"routes:resolved": (input) => {
|
|
411
|
+
enrichRoutesWithRawContent(input.routes);
|
|
412
|
+
return input;
|
|
413
|
+
},
|
|
414
|
+
"dev:configureServer": (server, ctx) => {
|
|
415
|
+
server.middlewares.use((req, res, next) => {
|
|
416
|
+
if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
|
|
417
|
+
next();
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
"build:done": (ctx) => {
|
|
421
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
422
|
+
if (!outputDirectory) return;
|
|
423
|
+
writeRawContentFiles(ctx.routes, outputDirectory);
|
|
424
|
+
writeLlmsTxt(ctx.routes, ctx.projectConfig, outputDirectory);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region source/plugins/html-shell/index.ts
|
|
431
|
+
var THEME_BOOTSTRAP_SCRIPT = `(function(){try{var e=localStorage.getItem('clarify:theme');var t=e==='dark'||e==='light'||e==='system'?e:'system';var r=t==='system'?window.matchMedia('(prefers-color-scheme: dark)').matches:t==='dark';document.documentElement.classList.toggle('dark',r);document.documentElement.style.colorScheme=r?'dark':'light'}catch(e){}})();`;
|
|
432
|
+
var REL_ICON_RE = /<link\s+[^>]*rel=["'][^"']*\bicon\b[^"']*["'][^>]*>/i;
|
|
433
|
+
function inferIconType(href) {
|
|
434
|
+
const path = href.split(/[?#]/, 1)[0]?.toLowerCase() ?? href.toLowerCase();
|
|
435
|
+
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
436
|
+
if (path.endsWith(".png")) return "image/png";
|
|
437
|
+
if (path.endsWith(".ico")) return "image/x-icon";
|
|
438
|
+
if (path.endsWith(".webp")) return "image/webp";
|
|
439
|
+
}
|
|
440
|
+
function createIconTag(href, media) {
|
|
441
|
+
return {
|
|
442
|
+
tag: "link",
|
|
443
|
+
attrs: {
|
|
444
|
+
rel: "icon",
|
|
445
|
+
...inferIconType(href) ? { type: inferIconType(href) } : {},
|
|
446
|
+
href,
|
|
447
|
+
...media ? { media } : {}
|
|
448
|
+
},
|
|
449
|
+
injectTo: "head"
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function createIconTags(icon) {
|
|
453
|
+
if (!icon) return [];
|
|
454
|
+
if (typeof icon === "string") return [createIconTag(icon)];
|
|
455
|
+
const lightIcon = icon.light ?? icon.dark;
|
|
456
|
+
const darkIcon = icon.dark ?? icon.light;
|
|
457
|
+
if (!lightIcon || !darkIcon) return [];
|
|
458
|
+
if (lightIcon === darkIcon) return [createIconTag(lightIcon)];
|
|
459
|
+
return [createIconTag(lightIcon, "(prefers-color-scheme: light)"), createIconTag(darkIcon, "(prefers-color-scheme: dark)")];
|
|
460
|
+
}
|
|
461
|
+
function createHtmlShellPlugin() {
|
|
462
|
+
return {
|
|
463
|
+
name: "clarify:html-shell",
|
|
464
|
+
hooks: { "html:transform": (input, ctx) => ({
|
|
465
|
+
html: input.html,
|
|
466
|
+
tags: [
|
|
467
|
+
...input.tags,
|
|
468
|
+
...REL_ICON_RE.test(input.html) ? [] : createIconTags(ctx.projectConfig.favicon ?? ctx.projectConfig.logo),
|
|
469
|
+
{
|
|
470
|
+
tag: "script",
|
|
471
|
+
children: THEME_BOOTSTRAP_SCRIPT,
|
|
472
|
+
injectTo: "head-prepend"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
tag: "script",
|
|
476
|
+
attrs: {
|
|
477
|
+
type: "module",
|
|
478
|
+
src: input.clientEntryId
|
|
479
|
+
},
|
|
480
|
+
injectTo: "body"
|
|
481
|
+
}
|
|
482
|
+
],
|
|
483
|
+
clientEntryId: input.clientEntryId,
|
|
484
|
+
dev: input.dev
|
|
485
|
+
}) }
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region source/plugins/openapi/parser.ts
|
|
490
|
+
var OPENAPI_HTTP_METHODS = [
|
|
491
|
+
"get",
|
|
492
|
+
"put",
|
|
493
|
+
"post",
|
|
494
|
+
"delete",
|
|
495
|
+
"options",
|
|
496
|
+
"head",
|
|
497
|
+
"patch",
|
|
498
|
+
"trace"
|
|
499
|
+
];
|
|
500
|
+
function kebabToTitle(str) {
|
|
501
|
+
return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
502
|
+
}
|
|
503
|
+
function findOpenAPIRoutes(dir, base = dir) {
|
|
504
|
+
const routes = [];
|
|
505
|
+
if (!existsSync(dir)) return routes;
|
|
506
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
507
|
+
for (const entry of entries) {
|
|
508
|
+
const fullPath = join(dir, entry.name);
|
|
509
|
+
if (entry.isDirectory()) {
|
|
510
|
+
routes.push(...findOpenAPIRoutes(fullPath, base));
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (!entry.isFile() || !/\.openapi\.(json|yaml|yml)$/.test(entry.name)) continue;
|
|
514
|
+
const ref = relative(base, fullPath);
|
|
515
|
+
const cleanPath = routePathFromRef(ref);
|
|
516
|
+
routes.push({
|
|
517
|
+
path: cleanPath,
|
|
518
|
+
basePath: cleanPath,
|
|
519
|
+
filePath: fullPath,
|
|
520
|
+
virtualModuleId: virtualModuleIdFromRef(ref),
|
|
521
|
+
title: kebabToTitle(cleanPath.split("/").pop() ?? "API"),
|
|
522
|
+
kind: "openapi"
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
return routes;
|
|
526
|
+
}
|
|
527
|
+
/** Extract endpoint operations from an OpenAPI spec as page sections. */
|
|
528
|
+
function extractOpenAPISections(spec) {
|
|
529
|
+
const sections = [];
|
|
530
|
+
const paths = spec.paths ?? {};
|
|
531
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
532
|
+
if (!pathItem) continue;
|
|
533
|
+
for (const method of OPENAPI_HTTP_METHODS) {
|
|
534
|
+
const op = pathItem[method];
|
|
535
|
+
if (!op) continue;
|
|
536
|
+
const title = op.summary ?? `${method.toUpperCase()} ${path}`;
|
|
537
|
+
sections.push({
|
|
538
|
+
id: slug(`${method} ${path}`),
|
|
539
|
+
title,
|
|
540
|
+
level: 2,
|
|
541
|
+
badge: method.toUpperCase()
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return sections;
|
|
546
|
+
}
|
|
547
|
+
async function readOpenAPISpec(filePath) {
|
|
548
|
+
try {
|
|
549
|
+
return await SwaggerParser.dereference(filePath);
|
|
550
|
+
} catch {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region source/plugins/openapi/virtual-modules.ts
|
|
556
|
+
var openApiRegistryModuleId = "virtual:clarify-openapi-registry";
|
|
557
|
+
function generateOpenAPIRegistryModule(openApis) {
|
|
558
|
+
return `export const openApis = ${JSON.stringify(openApis)};`;
|
|
559
|
+
}
|
|
560
|
+
function generateOpenAPIModule(spec) {
|
|
561
|
+
return `import { createElement } from 'react';
|
|
562
|
+
import { OpenApiPage } from '@clarify-labs/renderer';
|
|
563
|
+
const spec = ${JSON.stringify(spec)};
|
|
564
|
+
export default function OpenApiRoutePage() {
|
|
565
|
+
return createElement(OpenApiPage, { spec });
|
|
566
|
+
}`;
|
|
567
|
+
}
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region source/plugins/openapi/index.ts
|
|
570
|
+
function createOpenAPIPlugin() {
|
|
571
|
+
const specs = {};
|
|
572
|
+
return {
|
|
573
|
+
name: "clarify:openapi",
|
|
574
|
+
hooks: {
|
|
575
|
+
"routes:discover": (input) => ({
|
|
576
|
+
...input,
|
|
577
|
+
routes: [...input.routes, ...findOpenAPIRoutes(input.contentRoot)]
|
|
578
|
+
}),
|
|
579
|
+
"routes:discovered": async (routes) => {
|
|
580
|
+
for (const key of Object.keys(specs)) delete specs[key];
|
|
581
|
+
for (const route of routes.filter((route) => route.kind === "openapi")) {
|
|
582
|
+
const spec = await readOpenAPISpec(route.filePath);
|
|
583
|
+
if (!spec) throw new Error(`Failed to parse OpenAPI spec: ${route.filePath}`);
|
|
584
|
+
specs[route.virtualModuleId] = spec;
|
|
585
|
+
specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = spec;
|
|
586
|
+
route.title = spec.info?.title ?? route.title;
|
|
587
|
+
route.sections = extractOpenAPISections(spec);
|
|
588
|
+
}
|
|
589
|
+
return routes;
|
|
590
|
+
},
|
|
591
|
+
"modules:before": (modules) => {
|
|
592
|
+
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(specs));
|
|
593
|
+
for (const [moduleId, spec] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(spec));
|
|
594
|
+
return modules;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region source/core/config-schema.ts
|
|
601
|
+
var clarifyLogoConfigSchema = z.union([z.string(), z.object({
|
|
602
|
+
light: z.string().optional(),
|
|
603
|
+
dark: z.string().optional()
|
|
604
|
+
})]);
|
|
605
|
+
var clarifyFaviconConfigSchema = z.union([z.string(), z.object({
|
|
606
|
+
light: z.string().optional(),
|
|
607
|
+
dark: z.string().optional()
|
|
608
|
+
})]);
|
|
609
|
+
var clarifyLocalizedTextSchema = z.union([z.string(), z.record(z.string(), z.string())]);
|
|
610
|
+
var clarifyLocaleConfigSchema = z.object({
|
|
611
|
+
code: z.string(),
|
|
612
|
+
label: z.string(),
|
|
613
|
+
dir: z.union([z.literal("ltr"), z.literal("rtl")]).optional()
|
|
614
|
+
});
|
|
615
|
+
var clarifyI18nConfigSchema = z.object({
|
|
616
|
+
defaultLocale: z.string().optional(),
|
|
617
|
+
missing: z.union([
|
|
618
|
+
z.literal("fallback"),
|
|
619
|
+
z.literal("404"),
|
|
620
|
+
z.literal("hide")
|
|
621
|
+
]).optional(),
|
|
622
|
+
locales: z.array(clarifyLocaleConfigSchema).min(1)
|
|
623
|
+
}).superRefine((config, ctx) => {
|
|
624
|
+
const localeCodes = /* @__PURE__ */ new Set();
|
|
625
|
+
for (const [index, locale] of config.locales.entries()) {
|
|
626
|
+
if (localeCodes.has(locale.code)) ctx.addIssue({
|
|
627
|
+
code: z.ZodIssueCode.custom,
|
|
628
|
+
message: `Duplicate locale code "${locale.code}"`,
|
|
629
|
+
path: [
|
|
630
|
+
"locales",
|
|
631
|
+
index,
|
|
632
|
+
"code"
|
|
633
|
+
]
|
|
634
|
+
});
|
|
635
|
+
localeCodes.add(locale.code);
|
|
636
|
+
}
|
|
637
|
+
if (config.defaultLocale && !localeCodes.has(config.defaultLocale)) ctx.addIssue({
|
|
638
|
+
code: z.ZodIssueCode.custom,
|
|
639
|
+
message: "defaultLocale must be one of i18n.locales",
|
|
640
|
+
path: ["defaultLocale"]
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
var clarifyNavbarLinkSchema = z.object({
|
|
644
|
+
label: clarifyLocalizedTextSchema,
|
|
645
|
+
href: z.string(),
|
|
646
|
+
external: z.boolean().optional()
|
|
647
|
+
});
|
|
648
|
+
var clarifyBannerConfigSchema = z.object({
|
|
649
|
+
content: clarifyLocalizedTextSchema,
|
|
650
|
+
dismissible: z.boolean().optional()
|
|
651
|
+
});
|
|
652
|
+
var clarifyFooterConfigSchema = z.object({
|
|
653
|
+
links: z.array(clarifyNavbarLinkSchema).optional(),
|
|
654
|
+
socials: z.record(z.string(), z.string()).optional(),
|
|
655
|
+
copyright: clarifyLocalizedTextSchema.optional()
|
|
656
|
+
});
|
|
657
|
+
var clarifyPagesItemSchema = z.union([
|
|
658
|
+
z.string(),
|
|
659
|
+
z.object({
|
|
660
|
+
page: z.string(),
|
|
661
|
+
title: clarifyLocalizedTextSchema.optional(),
|
|
662
|
+
icon: z.string().optional(),
|
|
663
|
+
redirect: z.string().optional()
|
|
664
|
+
}),
|
|
665
|
+
z.object({
|
|
666
|
+
openapi: z.string(),
|
|
667
|
+
icon: z.string().optional(),
|
|
668
|
+
title: clarifyLocalizedTextSchema.optional()
|
|
669
|
+
})
|
|
670
|
+
]);
|
|
671
|
+
var clarifyPagesGroupSchema = z.object({
|
|
672
|
+
group: clarifyLocalizedTextSchema,
|
|
673
|
+
icon: z.string().optional(),
|
|
674
|
+
pages: z.array(clarifyPagesItemSchema)
|
|
675
|
+
});
|
|
676
|
+
var clarifyPagesConfigSchema = z.union([z.array(clarifyPagesGroupSchema), z.literal("FileTree")]);
|
|
677
|
+
var clarifyProjectConfigSchema = z.object({
|
|
678
|
+
title: z.string().optional(),
|
|
679
|
+
description: z.string().optional(),
|
|
680
|
+
logo: clarifyLogoConfigSchema.optional(),
|
|
681
|
+
favicon: clarifyFaviconConfigSchema.optional(),
|
|
682
|
+
theme: z.object({ primary: z.string().optional() }).optional(),
|
|
683
|
+
routePrefix: z.string().optional(),
|
|
684
|
+
navbar: z.object({ links: z.array(clarifyNavbarLinkSchema).optional() }).optional(),
|
|
685
|
+
banner: clarifyBannerConfigSchema.optional(),
|
|
686
|
+
footer: clarifyFooterConfigSchema.optional(),
|
|
687
|
+
i18n: clarifyI18nConfigSchema.optional(),
|
|
688
|
+
pages: clarifyPagesConfigSchema.optional()
|
|
689
|
+
});
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region source/core/config.ts
|
|
692
|
+
function formatIssuePath(path) {
|
|
693
|
+
return path.reduce((result, segment) => {
|
|
694
|
+
if (typeof segment === "number") return `${result}[${segment}]`;
|
|
695
|
+
const key = String(segment);
|
|
696
|
+
return result ? `${result}.${key}` : key;
|
|
697
|
+
}, "");
|
|
698
|
+
}
|
|
699
|
+
function formatProjectConfigError(error) {
|
|
700
|
+
const issue = error.issues[0];
|
|
701
|
+
if (!issue) return "[clarify] config is invalid";
|
|
702
|
+
const path = formatIssuePath(issue.path);
|
|
703
|
+
if (!path) return `[clarify] config must contain an object: ${issue.message}`;
|
|
704
|
+
return `[clarify] config field "${path}" is invalid: ${issue.message}`;
|
|
705
|
+
}
|
|
706
|
+
function validateProjectConfig(value) {
|
|
707
|
+
const result = clarifyProjectConfigSchema.safeParse(value);
|
|
708
|
+
if (!result.success) throw new Error(formatProjectConfigError(result.error));
|
|
709
|
+
return result.data;
|
|
710
|
+
}
|
|
711
|
+
function resolveI18nConfig(i18n) {
|
|
712
|
+
if (!i18n) return void 0;
|
|
713
|
+
const firstLocale = i18n.locales[0]?.code;
|
|
714
|
+
const defaultLocale = i18n.defaultLocale ?? firstLocale;
|
|
715
|
+
if (!defaultLocale) return void 0;
|
|
716
|
+
return {
|
|
717
|
+
defaultLocale,
|
|
718
|
+
missing: i18n.missing ?? "fallback",
|
|
719
|
+
locales: i18n.locales
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
function resolveProjectConfig(config = {}) {
|
|
723
|
+
return {
|
|
724
|
+
title: config.title ?? "Clarify Docs",
|
|
725
|
+
description: config.description ?? "",
|
|
726
|
+
logo: config.logo,
|
|
727
|
+
favicon: config.favicon,
|
|
728
|
+
routePrefix: config.routePrefix ?? "/",
|
|
729
|
+
theme: config.theme ?? {},
|
|
730
|
+
navbar: config.navbar,
|
|
731
|
+
banner: config.banner,
|
|
732
|
+
footer: config.footer,
|
|
733
|
+
i18n: resolveI18nConfig(config.i18n),
|
|
734
|
+
pages: config.pages
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region source/core/hooks.ts
|
|
739
|
+
async function runHooks(plugins, hookName, input, ctx) {
|
|
740
|
+
let result = input;
|
|
741
|
+
for (const plugin of plugins) {
|
|
742
|
+
const hook = plugin.hooks[hookName];
|
|
743
|
+
if (!hook) continue;
|
|
744
|
+
try {
|
|
745
|
+
result = await hook(result, ctx);
|
|
746
|
+
} catch (err) {
|
|
747
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "${hookName}" failed: ${err}`, { cause: err });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
async function runDevConfigureServerHooks(plugins, server, ctx) {
|
|
753
|
+
for (const plugin of plugins) {
|
|
754
|
+
const hook = plugin.hooks["dev:configureServer"];
|
|
755
|
+
if (!hook) continue;
|
|
756
|
+
try {
|
|
757
|
+
await hook(server, ctx);
|
|
758
|
+
} catch (err) {
|
|
759
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "dev:configureServer" failed: ${err}`, { cause: err });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async function runBuildDoneHooks(plugins, ctx) {
|
|
764
|
+
for (const plugin of plugins) {
|
|
765
|
+
const hook = plugin.hooks["build:done"];
|
|
766
|
+
if (!hook) continue;
|
|
767
|
+
try {
|
|
768
|
+
await hook(ctx);
|
|
769
|
+
} catch (err) {
|
|
770
|
+
throw new Error(`[clarify] plugin "${plugin.name}" hook "build:done" failed: ${err}`, { cause: err });
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region source/core/options.ts
|
|
776
|
+
function resolveBuildOptions(options = {}) {
|
|
777
|
+
return {
|
|
778
|
+
rootDirectory: options.rootDirectory ?? "source",
|
|
779
|
+
outputDirectory: options.outputDirectory,
|
|
780
|
+
ssg: { failOnError: options.ssg?.failOnError ?? true }
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
//#endregion
|
|
784
|
+
//#region source/core/runtime-deps.ts
|
|
785
|
+
var requireFromCli = createRequire(import.meta.url);
|
|
786
|
+
function resolveCliDependency(id) {
|
|
787
|
+
return requireFromCli.resolve(id);
|
|
788
|
+
}
|
|
789
|
+
function resolvePackageExport(packageName, subpath, condition = "import") {
|
|
790
|
+
const packageJsonPath = requireFromCli.resolve(`${packageName}/package.json`);
|
|
791
|
+
const packageRoot = dirname(packageJsonPath);
|
|
792
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
793
|
+
const exportKey = subpath ? `./${subpath}` : ".";
|
|
794
|
+
const exportEntry = packageJson.exports?.[exportKey];
|
|
795
|
+
const target = typeof exportEntry === "string" ? exportEntry : exportEntry?.[condition] ?? exportEntry?.default;
|
|
796
|
+
if (!target) throw new Error(`[clarify] Unable to resolve ${packageName}/${subpath}`);
|
|
797
|
+
return join(packageRoot, target);
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Resolve Clarify's framework/runtime dependencies from the CLI package instead
|
|
801
|
+
* of from the user's documentation project. This keeps content-only projects
|
|
802
|
+
* free from React, renderer, and tsconfig requirements.
|
|
803
|
+
*/
|
|
804
|
+
function createClarifyRuntimeAliases() {
|
|
805
|
+
return [
|
|
806
|
+
{
|
|
807
|
+
find: /^@clarify-labs\/renderer\/style\.css$/,
|
|
808
|
+
replacement: resolvePackageExport("@clarify-labs/renderer", "style.css")
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
find: /^@clarify-labs\/renderer\/client$/,
|
|
812
|
+
replacement: resolvePackageExport("@clarify-labs/renderer", "client")
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
find: /^@clarify-labs\/renderer\/server$/,
|
|
816
|
+
replacement: resolvePackageExport("@clarify-labs/renderer", "server")
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
find: /^@clarify-labs\/renderer$/,
|
|
820
|
+
replacement: resolvePackageExport("@clarify-labs/renderer", "")
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
find: /^react\/jsx-runtime$/,
|
|
824
|
+
replacement: resolveCliDependency("react/jsx-runtime")
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
find: /^react\/jsx-dev-runtime$/,
|
|
828
|
+
replacement: resolveCliDependency("react/jsx-dev-runtime")
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
find: /^react-dom\/client$/,
|
|
832
|
+
replacement: resolveCliDependency("react-dom/client")
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
find: /^react-dom\/server$/,
|
|
836
|
+
replacement: resolveCliDependency("react-dom/server")
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
find: /^react-dom$/,
|
|
840
|
+
replacement: resolveCliDependency("react-dom")
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
find: /^react-router-dom$/,
|
|
844
|
+
replacement: resolveCliDependency("react-router-dom")
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
find: /^react$/,
|
|
848
|
+
replacement: resolveCliDependency("react")
|
|
849
|
+
}
|
|
850
|
+
];
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region source/core/ssg.ts
|
|
854
|
+
function readIndexHtml(outputDirectory) {
|
|
855
|
+
const indexPath = join(outputDirectory, "index.html");
|
|
856
|
+
if (!existsSync(indexPath)) return void 0;
|
|
857
|
+
try {
|
|
858
|
+
return readFileSync(indexPath, "utf-8");
|
|
859
|
+
} catch {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function injectHtmlLocaleAttributes(html, projectConfig, route) {
|
|
864
|
+
const localeCode = route?.locale ?? projectConfig.i18n?.defaultLocale;
|
|
865
|
+
if (!localeCode) return html;
|
|
866
|
+
const dir = (projectConfig.i18n?.locales.find((locale) => locale.code === localeCode))?.dir;
|
|
867
|
+
return html.replace(/<html\b([^>]*)>/i, (_match, attributes) => {
|
|
868
|
+
let nextAttributes = attributes.replace(/\s+lang=("[^"]*"|'[^']*'|[^\s>]*)/i, "").replace(/\s+dir=("[^"]*"|'[^']*'|[^\s>]*)/i, "");
|
|
869
|
+
nextAttributes = `${nextAttributes} lang="${escapeHtml(localeCode)}"`;
|
|
870
|
+
if (dir) nextAttributes = `${nextAttributes} dir="${escapeHtml(dir)}"`;
|
|
871
|
+
return `<html${nextAttributes}>`;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
|
|
875
|
+
let html = injectHtmlLocaleAttributes(template, projectConfig, route);
|
|
876
|
+
html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(projectConfig.title)}</title>`);
|
|
877
|
+
if (projectConfig.description && !html.includes("name=\"description\"")) {
|
|
878
|
+
const descriptionMeta = `<meta name="description" content="${escapeHtml(projectConfig.description)}" />`;
|
|
879
|
+
html = html.replace("</head>", ` ${descriptionMeta}\n </head>`);
|
|
880
|
+
}
|
|
881
|
+
html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
|
|
882
|
+
return html;
|
|
883
|
+
}
|
|
884
|
+
var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
|
|
885
|
+
import { routes, navigation } from 'virtual:clarify-routes';
|
|
886
|
+
import { config } from 'virtual:clarify-config';
|
|
887
|
+
import { openApis } from 'virtual:clarify-openapi-registry';
|
|
888
|
+
|
|
889
|
+
export function render(url) {
|
|
890
|
+
return renderToHTML({ config, routes, navigation, openApis, url });
|
|
891
|
+
}`;
|
|
892
|
+
function createTempEntryFile(content) {
|
|
893
|
+
const entryPath = join(mkdtempSync(join(tmpdir(), "clarify-ssr-")), "entry-server.ts");
|
|
894
|
+
writeFileSync(entryPath, content, "utf-8");
|
|
895
|
+
return entryPath;
|
|
896
|
+
}
|
|
897
|
+
async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
|
|
898
|
+
await build({
|
|
899
|
+
root,
|
|
900
|
+
configFile: false,
|
|
901
|
+
resolve: { alias: createClarifyRuntimeAliases() },
|
|
902
|
+
plugins,
|
|
903
|
+
build: {
|
|
904
|
+
ssr: ssrEntry,
|
|
905
|
+
outDir: ssrOutDir,
|
|
906
|
+
emptyOutDir: true,
|
|
907
|
+
rollupOptions: { input: ssrEntry }
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundlePath, failOnError = true) {
|
|
912
|
+
const { render } = await import(pathToFileURL(ssrBundlePath).href);
|
|
913
|
+
const template = readIndexHtml(outputDirectory);
|
|
914
|
+
if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
|
|
915
|
+
for (const route of routes) try {
|
|
916
|
+
const finalHtml = injectSSRIntoTemplate(template, render(route.path), projectConfig, route);
|
|
917
|
+
const outFile = join(outputDirectory, route.path, "index.html");
|
|
918
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
919
|
+
writeFileSync(outFile, finalHtml, "utf-8");
|
|
920
|
+
} catch (err) {
|
|
921
|
+
console.error(`[clarify] Failed to render route "${route.path}":`, err);
|
|
922
|
+
if (failOnError) throw err;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region source/core/user-config.ts
|
|
927
|
+
function defineConfig(config) {
|
|
928
|
+
return config;
|
|
929
|
+
}
|
|
930
|
+
var CONFIG_FILENAMES = [
|
|
931
|
+
"clarify.ts",
|
|
932
|
+
"clarify.js",
|
|
933
|
+
"clarify.json"
|
|
934
|
+
];
|
|
935
|
+
function findClarifyConfigFile(root) {
|
|
936
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
937
|
+
const filePath = resolve(root, filename);
|
|
938
|
+
if (existsSync(filePath)) return filePath;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function assertConfigObject(config, configFile) {
|
|
942
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error(`[clarify] ${configFile} must export a configuration object`);
|
|
943
|
+
return config;
|
|
944
|
+
}
|
|
945
|
+
function loadJsonConfig(configFile) {
|
|
946
|
+
const config = assertConfigObject(JSON.parse(readFileSync(configFile, "utf-8")), configFile);
|
|
947
|
+
const projectConfig = validateProjectConfig(config);
|
|
948
|
+
const ssg = config.ssg && typeof config.ssg === "object" && !Array.isArray(config.ssg) ? config.ssg : void 0;
|
|
949
|
+
return {
|
|
950
|
+
...projectConfig,
|
|
951
|
+
...ssg ? { ssg } : {}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
async function loadClarifyConfig(root, env) {
|
|
955
|
+
const configFile = findClarifyConfigFile(root);
|
|
956
|
+
if (!configFile) return {};
|
|
957
|
+
if (configFile.endsWith(".json")) return loadJsonConfig(configFile);
|
|
958
|
+
return assertConfigObject((await loadConfigFromFile(env, configFile, root, "silent"))?.config, configFile);
|
|
959
|
+
}
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region source/core/virtual-modules.ts
|
|
962
|
+
var VIRTUAL_CONFIG = "virtual:clarify-config";
|
|
963
|
+
var VIRTUAL_ROUTES = "virtual:clarify-routes";
|
|
964
|
+
var VIRTUAL_OPENAPI_REGISTRY = openApiRegistryModuleId;
|
|
965
|
+
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
|
|
966
|
+
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
|
|
967
|
+
function resolveVirtualId(id) {
|
|
968
|
+
return "\0" + id;
|
|
969
|
+
}
|
|
970
|
+
function stripVirtualPrefix(id) {
|
|
971
|
+
return id.startsWith("\0") ? id.slice(1) : id;
|
|
972
|
+
}
|
|
973
|
+
function generateConfigModule(projectConfig, buildOptions) {
|
|
974
|
+
return `export const config = ${JSON.stringify({
|
|
975
|
+
...projectConfig,
|
|
976
|
+
...buildOptions
|
|
977
|
+
})};`;
|
|
978
|
+
}
|
|
979
|
+
function generateRoutesModule(routes, pagesConfig, resolvedNavigation, projectConfig) {
|
|
980
|
+
const imports = routes.map((r, i) => `import Page${i} from '${r.virtualModuleId}';`).join("\n");
|
|
981
|
+
const routesArray = routes.map((r, i) => {
|
|
982
|
+
const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
|
|
983
|
+
id: s.id,
|
|
984
|
+
title: s.title,
|
|
985
|
+
badge: s.badge,
|
|
986
|
+
tags: s.tags
|
|
987
|
+
})))}` : "";
|
|
988
|
+
const rawContentUrl = r.rawContentUrl ? `, rawContentUrl: ${JSON.stringify(r.rawContentUrl)}` : "";
|
|
989
|
+
const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
|
|
990
|
+
const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
|
|
991
|
+
const isFallback = r.isFallback ? ", isFallback: true" : "";
|
|
992
|
+
const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
|
|
993
|
+
return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: Page${i}, kind: '${r.kind}'${basePath}${locale}${isFallback}${alternates}${sections}${rawContentUrl} }`;
|
|
994
|
+
}).join(",\n");
|
|
995
|
+
const navigation = resolvedNavigation ?? (projectConfig?.i18n ? buildLocalizedNavigation(routes, pagesConfig, projectConfig.i18n) ?? {} : pagesConfig && pagesConfig !== "FileTree" ? buildNavigationFromConfig(routes, pagesConfig) : buildNavigation(routes));
|
|
996
|
+
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
997
|
+
}
|
|
998
|
+
function createClientEntryModule() {
|
|
999
|
+
return `
|
|
1000
|
+
import '@clarify-labs/renderer/style.css';
|
|
1001
|
+
import { render } from '@clarify-labs/renderer/client';
|
|
1002
|
+
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
1003
|
+
import { config } from '${VIRTUAL_CONFIG}';
|
|
1004
|
+
import { openApis } from '${VIRTUAL_OPENAPI_REGISTRY}';
|
|
1005
|
+
render({ config, routes, navigation, openApis });`;
|
|
1006
|
+
}
|
|
1007
|
+
function buildVirtualModules(args) {
|
|
1008
|
+
const modules = /* @__PURE__ */ new Map();
|
|
1009
|
+
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
1010
|
+
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.projectConfig.pages, args.navigation, args.projectConfig));
|
|
1011
|
+
modules.set(VIRTUAL_OPENAPI_REGISTRY, "export const openApis = {};");
|
|
1012
|
+
modules.set(VIRTUAL_CLIENT_ENTRY, createClientEntryModule());
|
|
1013
|
+
modules.set(RESOLVED_CLIENT_ENTRY, createClientEntryModule());
|
|
1014
|
+
for (const route of args.routes) {
|
|
1015
|
+
if (route.kind === "openapi") continue;
|
|
1016
|
+
modules.set(route.virtualModuleId, `export { default } from '${route.filePath}';`);
|
|
1017
|
+
}
|
|
1018
|
+
return modules;
|
|
1019
|
+
}
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region source/core/plugin.ts
|
|
1022
|
+
function loadVirtualModule(id, modules) {
|
|
1023
|
+
const bareId = stripVirtualPrefix(id);
|
|
1024
|
+
return modules.get(bareId) ?? modules.get(id) ?? null;
|
|
1025
|
+
}
|
|
1026
|
+
function clarifyPlugin(options = {}) {
|
|
1027
|
+
const root = resolve(options.projectRoot ?? process.cwd());
|
|
1028
|
+
let projectConfig = resolveProjectConfig(options);
|
|
1029
|
+
let generateOptions = resolveBuildOptions(options);
|
|
1030
|
+
const contentRoot = join(root, generateOptions.rootDirectory);
|
|
1031
|
+
const configFilePath = findClarifyConfigFile(root);
|
|
1032
|
+
let routes = [];
|
|
1033
|
+
const clarifyPlugins = [
|
|
1034
|
+
createOpenAPIPlugin(),
|
|
1035
|
+
createContentArtifactsPlugin(),
|
|
1036
|
+
createHtmlShellPlugin(),
|
|
1037
|
+
...options.plugins ?? []
|
|
1038
|
+
];
|
|
1039
|
+
const ctx = {
|
|
1040
|
+
projectConfig,
|
|
1041
|
+
generateOptions,
|
|
1042
|
+
routes,
|
|
1043
|
+
navigation: []
|
|
1044
|
+
};
|
|
1045
|
+
let viteConfig;
|
|
1046
|
+
let resolvedNavigation = [];
|
|
1047
|
+
let virtualModules = /* @__PURE__ */ new Map();
|
|
1048
|
+
async function reloadProjectConfig() {
|
|
1049
|
+
const buildOptions = {
|
|
1050
|
+
...await loadClarifyConfig(root, {
|
|
1051
|
+
command: "serve",
|
|
1052
|
+
mode: viteConfig.mode
|
|
1053
|
+
}),
|
|
1054
|
+
projectRoot: options.projectRoot,
|
|
1055
|
+
rootDirectory: options.rootDirectory,
|
|
1056
|
+
outputDirectory: options.outputDirectory
|
|
1057
|
+
};
|
|
1058
|
+
projectConfig = resolveProjectConfig(buildOptions);
|
|
1059
|
+
generateOptions = resolveBuildOptions(buildOptions);
|
|
1060
|
+
ctx.projectConfig = projectConfig;
|
|
1061
|
+
ctx.generateOptions = generateOptions;
|
|
1062
|
+
}
|
|
1063
|
+
function withAlternates(route, routeList, i18n) {
|
|
1064
|
+
const basePath = route.basePath ?? route.path;
|
|
1065
|
+
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1066
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1067
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1068
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1069
|
+
}));
|
|
1070
|
+
return {
|
|
1071
|
+
...route,
|
|
1072
|
+
alternates
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
async function discoverRoutesForRoot(routeRoot, locale) {
|
|
1076
|
+
return (await runHooks(clarifyPlugins, "routes:discover", {
|
|
1077
|
+
contentRoot: routeRoot,
|
|
1078
|
+
locale,
|
|
1079
|
+
routes: findContentRoutes(routeRoot)
|
|
1080
|
+
}, ctx)).routes;
|
|
1081
|
+
}
|
|
1082
|
+
async function discoverRoutes() {
|
|
1083
|
+
const i18n = projectConfig.i18n;
|
|
1084
|
+
if (!i18n) return discoverRoutesForRoot(contentRoot);
|
|
1085
|
+
const localizedRoutes = [];
|
|
1086
|
+
for (const locale of i18n.locales) {
|
|
1087
|
+
const discovered = await discoverRoutesForRoot(join(contentRoot, locale.code), locale.code);
|
|
1088
|
+
for (const route of discovered) {
|
|
1089
|
+
const basePath = route.basePath ?? route.path;
|
|
1090
|
+
localizedRoutes.push({
|
|
1091
|
+
...route,
|
|
1092
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1093
|
+
basePath,
|
|
1094
|
+
locale: locale.code,
|
|
1095
|
+
virtualModuleId: virtualModuleIdFromRef(relative(contentRoot, route.filePath))
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (i18n.missing === "fallback") {
|
|
1100
|
+
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1101
|
+
const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
|
|
1102
|
+
for (const sourceRoute of defaultRoutes) {
|
|
1103
|
+
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
1104
|
+
for (const locale of i18n.locales) {
|
|
1105
|
+
const key = `${locale.code}:${basePath}`;
|
|
1106
|
+
if (routeByLocaleAndBase.has(key)) continue;
|
|
1107
|
+
localizedRoutes.push({
|
|
1108
|
+
...sourceRoute,
|
|
1109
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1110
|
+
locale: locale.code,
|
|
1111
|
+
isFallback: true
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1117
|
+
}
|
|
1118
|
+
async function resolveRoutesAndSpecs() {
|
|
1119
|
+
routes = await discoverRoutes();
|
|
1120
|
+
routes = await runHooks(clarifyPlugins, "routes:discovered", routes, ctx);
|
|
1121
|
+
const defaultNavigation = projectConfig.i18n ? buildLocalizedNavigation(routes, projectConfig.pages, projectConfig.i18n) ?? {} : projectConfig.pages && projectConfig.pages !== "FileTree" ? buildNavigationFromConfig(routes, projectConfig.pages) : buildNavigation(routes);
|
|
1122
|
+
const resolved = await runHooks(clarifyPlugins, "routes:resolved", {
|
|
1123
|
+
routes,
|
|
1124
|
+
navigation: defaultNavigation
|
|
1125
|
+
}, ctx);
|
|
1126
|
+
routes = resolved.routes;
|
|
1127
|
+
resolvedNavigation = resolved.navigation;
|
|
1128
|
+
ctx.routes = routes;
|
|
1129
|
+
ctx.navigation = resolvedNavigation;
|
|
1130
|
+
}
|
|
1131
|
+
async function rebuildVirtualModules() {
|
|
1132
|
+
virtualModules = buildVirtualModules({
|
|
1133
|
+
projectConfig,
|
|
1134
|
+
generateOptions,
|
|
1135
|
+
routes,
|
|
1136
|
+
navigation: resolvedNavigation
|
|
1137
|
+
});
|
|
1138
|
+
virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
|
|
1139
|
+
}
|
|
1140
|
+
function invalidateVirtualModules(server) {
|
|
1141
|
+
for (const id of virtualModules.keys()) {
|
|
1142
|
+
const moduleNode = server.moduleGraph.getModuleById(resolveVirtualId(id));
|
|
1143
|
+
if (moduleNode) server.moduleGraph.invalidateModule(moduleNode);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async function refreshDevServer(server) {
|
|
1147
|
+
await resolveRoutesAndSpecs();
|
|
1148
|
+
await rebuildVirtualModules();
|
|
1149
|
+
invalidateVirtualModules(server);
|
|
1150
|
+
server.ws.send({ type: "full-reload" });
|
|
1151
|
+
}
|
|
1152
|
+
function hasContentRouteForFile(filePath) {
|
|
1153
|
+
return routes.some((route) => route.filePath === filePath);
|
|
1154
|
+
}
|
|
1155
|
+
const mdx = mdxPlugin({
|
|
1156
|
+
include: ["**/*.{md,mdx}"],
|
|
1157
|
+
jsxImportSource: "react",
|
|
1158
|
+
providerImportSource: "@clarify-labs/renderer",
|
|
1159
|
+
remarkPlugins,
|
|
1160
|
+
rehypePlugins
|
|
1161
|
+
});
|
|
1162
|
+
return [
|
|
1163
|
+
react(),
|
|
1164
|
+
tailwindcss(),
|
|
1165
|
+
{
|
|
1166
|
+
name: "clarify:core",
|
|
1167
|
+
async config() {
|
|
1168
|
+
await resolveRoutesAndSpecs();
|
|
1169
|
+
return {
|
|
1170
|
+
base: projectConfig.routePrefix,
|
|
1171
|
+
build: {
|
|
1172
|
+
...generateOptions.outputDirectory ? { outDir: generateOptions.outputDirectory } : {},
|
|
1173
|
+
manifest: true
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
},
|
|
1177
|
+
configResolved(config) {
|
|
1178
|
+
viteConfig = config;
|
|
1179
|
+
if (!generateOptions.outputDirectory) generateOptions.outputDirectory = config.build.outDir;
|
|
1180
|
+
},
|
|
1181
|
+
async buildStart() {
|
|
1182
|
+
await rebuildVirtualModules();
|
|
1183
|
+
},
|
|
1184
|
+
async handleHotUpdate(ctx) {
|
|
1185
|
+
const changedFile = isAbsolute(ctx.file) ? ctx.file : join(root, ctx.file);
|
|
1186
|
+
if (configFilePath && changedFile === configFilePath) {
|
|
1187
|
+
await reloadProjectConfig();
|
|
1188
|
+
await refreshDevServer(ctx.server);
|
|
1189
|
+
return [];
|
|
1190
|
+
}
|
|
1191
|
+
const relativeContentFile = relative(contentRoot, changedFile);
|
|
1192
|
+
if (!(relativeContentFile && !relativeContentFile.startsWith("..") && !isAbsolute(relativeContentFile)) || !hasContentRouteForFile(changedFile)) return;
|
|
1193
|
+
await refreshDevServer(ctx.server);
|
|
1194
|
+
return [];
|
|
1195
|
+
},
|
|
1196
|
+
resolveId(id) {
|
|
1197
|
+
if (id === "virtual:clarify-entry-client" || id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
|
|
1198
|
+
if (id === "virtual:clarify-config" || id === resolveVirtualId("virtual:clarify-config")) return resolveVirtualId(VIRTUAL_CONFIG);
|
|
1199
|
+
if (id === "virtual:clarify-routes" || id === resolveVirtualId("virtual:clarify-routes")) return resolveVirtualId(VIRTUAL_ROUTES);
|
|
1200
|
+
if (id === VIRTUAL_OPENAPI_REGISTRY || id === resolveVirtualId(VIRTUAL_OPENAPI_REGISTRY)) return resolveVirtualId(VIRTUAL_OPENAPI_REGISTRY);
|
|
1201
|
+
const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === stripVirtualPrefix(id));
|
|
1202
|
+
if (route) return resolveVirtualId(route.virtualModuleId);
|
|
1203
|
+
return null;
|
|
1204
|
+
},
|
|
1205
|
+
load(id) {
|
|
1206
|
+
return loadVirtualModule(id, virtualModules);
|
|
1207
|
+
},
|
|
1208
|
+
async configureServer(server) {
|
|
1209
|
+
server.watcher.add(contentRoot);
|
|
1210
|
+
if (configFilePath) server.watcher.add(configFilePath);
|
|
1211
|
+
const handleContentTreeChange = async (filePath) => {
|
|
1212
|
+
const relativeContentFile = relative(contentRoot, isAbsolute(filePath) ? filePath : join(root, filePath));
|
|
1213
|
+
if (!(relativeContentFile && !relativeContentFile.startsWith("..") && !isAbsolute(relativeContentFile))) return;
|
|
1214
|
+
await refreshDevServer(server);
|
|
1215
|
+
};
|
|
1216
|
+
server.watcher.on("add", handleContentTreeChange);
|
|
1217
|
+
server.watcher.on("unlink", handleContentTreeChange);
|
|
1218
|
+
await runDevConfigureServerHooks(clarifyPlugins, server, ctx);
|
|
1219
|
+
},
|
|
1220
|
+
transformIndexHtml: {
|
|
1221
|
+
order: "pre",
|
|
1222
|
+
async handler(html, transformCtx) {
|
|
1223
|
+
const result = await runHooks(clarifyPlugins, "html:transform", {
|
|
1224
|
+
html,
|
|
1225
|
+
tags: [],
|
|
1226
|
+
clientEntryId: transformCtx.server ? `/@id/${VIRTUAL_CLIENT_ENTRY}` : VIRTUAL_CLIENT_ENTRY,
|
|
1227
|
+
dev: Boolean(transformCtx.server)
|
|
1228
|
+
}, ctx);
|
|
1229
|
+
return {
|
|
1230
|
+
html: result.html,
|
|
1231
|
+
tags: result.tags
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
async closeBundle() {
|
|
1236
|
+
if (process.env.SKIP_CLARIFY_SSG) {
|
|
1237
|
+
await runBuildDoneHooks(clarifyPlugins, ctx);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const outputDir = viteConfig.build.outDir;
|
|
1241
|
+
const ssrOutputDir = join(outputDir, ".ssr");
|
|
1242
|
+
let tempEntryPath;
|
|
1243
|
+
try {
|
|
1244
|
+
tempEntryPath = createTempEntryFile(SSR_ENTRY_CODE);
|
|
1245
|
+
await buildSSRBundle(root, tempEntryPath, ssrOutputDir, [{
|
|
1246
|
+
name: "clarify:virtual-ssg",
|
|
1247
|
+
resolveId(id) {
|
|
1248
|
+
if (id === "virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
|
|
1249
|
+
if (id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
|
|
1250
|
+
if (id === "virtual:clarify-config") return id;
|
|
1251
|
+
if (id === "virtual:clarify-routes") return id;
|
|
1252
|
+
if (id === VIRTUAL_OPENAPI_REGISTRY) return id;
|
|
1253
|
+
if (routes.find((r) => r.virtualModuleId === id)) return id;
|
|
1254
|
+
return null;
|
|
1255
|
+
},
|
|
1256
|
+
load: (id) => loadVirtualModule(id, virtualModules)
|
|
1257
|
+
}, mdx]);
|
|
1258
|
+
const ssrBundlePath = join(ssrOutputDir, "entry-server.js");
|
|
1259
|
+
await renderSSGRoutes(routes, projectConfig, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
console.error("[clarify] SSG failed:", err);
|
|
1262
|
+
if (generateOptions.ssg.failOnError) throw err;
|
|
1263
|
+
} finally {
|
|
1264
|
+
if (tempEntryPath) try {
|
|
1265
|
+
rmSync(tempEntryPath, { force: true });
|
|
1266
|
+
} catch {}
|
|
1267
|
+
}
|
|
1268
|
+
await runBuildDoneHooks(clarifyPlugins, ctx);
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
mdx
|
|
1272
|
+
].flat().filter(Boolean);
|
|
1273
|
+
}
|
|
1274
|
+
//#endregion
|
|
1275
|
+
export { createClarifyRuntimeAliases as i, defineConfig as n, loadClarifyConfig as r, clarifyPlugin as t };
|
|
1276
|
+
|
|
1277
|
+
//# sourceMappingURL=plugin-BUWojlPv.js.map
|