@clarify-labs/cli 0.6.4 → 0.6.6
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/output/cli/commands/check.d.ts +6 -0
- package/output/cli.cjs +152 -3
- package/output/cli.cjs.map +1 -1
- package/output/cli.js +153 -4
- package/output/cli.js.map +1 -1
- package/output/core/builtin.d.ts +5 -0
- package/output/core/config-schema.d.ts +167 -32
- package/output/core/options.d.ts +1 -0
- package/output/core/site.d.ts +17 -0
- package/output/core/ssg.d.ts +3 -1
- package/output/core/theme.d.ts +5 -3
- package/output/core/virtual-modules.d.ts +7 -2
- package/output/index.cjs +1 -1
- package/output/index.js +1 -1
- package/output/parsers/mdx.d.ts +1 -1
- package/output/parsers/routes.d.ts +2 -0
- package/output/{plugin-DChFdTyI.js → plugin-DC2f5YAH.js} +655 -403
- package/output/plugin-DC2f5YAH.js.map +1 -0
- package/output/{plugin--jQ6D6O4.cjs → plugin-GTN_SQ7d.cjs} +656 -398
- package/output/plugin-GTN_SQ7d.cjs.map +1 -0
- package/output/plugins/openapi/parser.d.ts +1 -1
- package/output/plugins/openapi/virtual-modules.d.ts +2 -2
- package/output/plugins/seo/artifacts.d.ts +4 -0
- package/output/plugins/seo/index.d.ts +2 -0
- package/output/plugins/source-links/index.d.ts +2 -0
- package/output/plugins/source-links/source-links.d.ts +3 -0
- package/output/types.d.ts +40 -10
- package/package.json +3 -3
- package/output/plugin--jQ6D6O4.cjs.map +0 -1
- package/output/plugin-DChFdTyI.js.map +0 -1
|
@@ -11,6 +11,7 @@ import { markdownRemarkPlugins } from "@clarify-labs/renderer";
|
|
|
11
11
|
import { remark } from "remark";
|
|
12
12
|
import matter from "gray-matter";
|
|
13
13
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
14
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
14
15
|
import { z } from "zod";
|
|
15
16
|
import { tmpdir } from "node:os";
|
|
16
17
|
import { pathToFileURL } from "node:url";
|
|
@@ -47,6 +48,11 @@ function rehypeParseCodeBlocks() {
|
|
|
47
48
|
const language = getLanguage(node.properties.className);
|
|
48
49
|
parentNode.properties.language = language;
|
|
49
50
|
node.properties.language = language;
|
|
51
|
+
for (const key of [
|
|
52
|
+
"title",
|
|
53
|
+
"tag",
|
|
54
|
+
"label"
|
|
55
|
+
]) if (node.properties[key] !== void 0) parentNode.properties[key] = node.properties[key];
|
|
50
56
|
});
|
|
51
57
|
};
|
|
52
58
|
}
|
|
@@ -100,6 +106,191 @@ var rehypePlugins = [
|
|
|
100
106
|
rehypeShiki
|
|
101
107
|
];
|
|
102
108
|
//#endregion
|
|
109
|
+
//#region source/plugins/content-artifacts/artifacts.ts
|
|
110
|
+
var UTF8_SIGNATURE = "";
|
|
111
|
+
function normalizeBasePath(basePath) {
|
|
112
|
+
if (!basePath || basePath === "/") return "";
|
|
113
|
+
return "/" + basePath.replace(/^\/+|\/+$/g, "");
|
|
114
|
+
}
|
|
115
|
+
function containsNonAscii(content) {
|
|
116
|
+
return [...content].some((character) => character.codePointAt(0) > 127);
|
|
117
|
+
}
|
|
118
|
+
function withUtf8Signature(content) {
|
|
119
|
+
if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
|
|
120
|
+
return `${UTF8_SIGNATURE}${content}`;
|
|
121
|
+
}
|
|
122
|
+
function shouldUseUtf8Signature(route) {
|
|
123
|
+
if (route.kind === "mdx") return true;
|
|
124
|
+
return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
|
|
125
|
+
}
|
|
126
|
+
function routeToMarkdownArtifactUrl(routePath) {
|
|
127
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
128
|
+
}
|
|
129
|
+
function routeToOpenAPIArtifactUrl(routePath, filePath) {
|
|
130
|
+
const extension = extname(filePath).toLowerCase() || ".json";
|
|
131
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
132
|
+
}
|
|
133
|
+
function attachContentArtifactUrls(routes) {
|
|
134
|
+
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
|
|
135
|
+
}
|
|
136
|
+
function readRouteContent(route) {
|
|
137
|
+
if (route.content !== void 0) return route.content;
|
|
138
|
+
throw new Error(`Route content is missing from route context: ${route.filePath}`);
|
|
139
|
+
}
|
|
140
|
+
function readRouteArtifactContent(route) {
|
|
141
|
+
const content = readRouteContent(route);
|
|
142
|
+
return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
|
|
143
|
+
}
|
|
144
|
+
function writeContentArtifactFiles(routes, outputDirectory) {
|
|
145
|
+
for (const route of routes) {
|
|
146
|
+
if (!route.contentArtifactUrl) continue;
|
|
147
|
+
const outFile = join(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
|
|
148
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
149
|
+
writeFileSync(outFile, readRouteArtifactContent(route), "utf-8");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function createLlmsTxt(routes, projectConfig) {
|
|
153
|
+
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
154
|
+
const lines = [`# ${projectConfig.title}`, ""];
|
|
155
|
+
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
156
|
+
lines.push("## Docs");
|
|
157
|
+
for (const route of routes.filter((route) => route.kind === "mdx")) {
|
|
158
|
+
if (!route.contentArtifactUrl) continue;
|
|
159
|
+
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
160
|
+
}
|
|
161
|
+
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
162
|
+
if (openApiRoutes.length > 0) {
|
|
163
|
+
lines.push("", "## OpenAPI");
|
|
164
|
+
for (const route of openApiRoutes) {
|
|
165
|
+
if (!route.contentArtifactUrl) continue;
|
|
166
|
+
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return `${lines.join("\n")}\n`;
|
|
170
|
+
}
|
|
171
|
+
function createLlmsTxtArtifact(routes, projectConfig) {
|
|
172
|
+
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
173
|
+
}
|
|
174
|
+
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
175
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
176
|
+
writeFileSync(join(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region source/plugins/content-artifacts/server.ts
|
|
180
|
+
function normalizeRoutePrefix$1(routePrefix) {
|
|
181
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
182
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
183
|
+
}
|
|
184
|
+
function resolveContentArtifactPath(url, projectConfig) {
|
|
185
|
+
const requestPath = url?.split("?")[0] ?? "";
|
|
186
|
+
const basePath = normalizeRoutePrefix$1(projectConfig.routePrefix);
|
|
187
|
+
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
188
|
+
}
|
|
189
|
+
function resolveContentArtifactType(route) {
|
|
190
|
+
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
191
|
+
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
192
|
+
}
|
|
193
|
+
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
194
|
+
const contentPath = resolveContentArtifactPath(req.url, projectConfig);
|
|
195
|
+
if (contentPath === "/llms.txt") {
|
|
196
|
+
res.statusCode = 200;
|
|
197
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
198
|
+
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
const route = routes.find((route) => route.contentArtifactUrl === contentPath);
|
|
202
|
+
if (!route) return false;
|
|
203
|
+
res.statusCode = 200;
|
|
204
|
+
res.setHeader("Content-Type", resolveContentArtifactType(route));
|
|
205
|
+
res.end(readRouteArtifactContent(route), "utf8");
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region source/plugins/content-artifacts/index.ts
|
|
210
|
+
function createContentArtifactsPlugin() {
|
|
211
|
+
return {
|
|
212
|
+
name: "clarify:content-artifacts",
|
|
213
|
+
hooks: {
|
|
214
|
+
"routes:resolved": (input) => {
|
|
215
|
+
attachContentArtifactUrls(input.routes);
|
|
216
|
+
return input;
|
|
217
|
+
},
|
|
218
|
+
"dev:configureServer": (server, ctx) => {
|
|
219
|
+
server.middlewares.use((req, res, next) => {
|
|
220
|
+
if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
|
|
221
|
+
next();
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
"build:done": (ctx) => {
|
|
225
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
226
|
+
if (!outputDirectory) return;
|
|
227
|
+
const outputRoot = join(ctx.generateOptions.projectRoot, outputDirectory);
|
|
228
|
+
writeContentArtifactFiles(ctx.routes, outputRoot);
|
|
229
|
+
writeLlmsTxt(ctx.routes, ctx.projectConfig, outputRoot);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region source/plugins/html-shell/index.ts
|
|
236
|
+
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){}})();`;
|
|
237
|
+
var REL_ICON_RE = /<link\s+[^>]*rel=["'][^"']*\bicon\b[^"']*["'][^>]*>/i;
|
|
238
|
+
function inferIconType(href) {
|
|
239
|
+
const path = href.split(/[?#]/, 1)[0]?.toLowerCase() ?? href.toLowerCase();
|
|
240
|
+
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
241
|
+
if (path.endsWith(".png")) return "image/png";
|
|
242
|
+
if (path.endsWith(".ico")) return "image/x-icon";
|
|
243
|
+
if (path.endsWith(".webp")) return "image/webp";
|
|
244
|
+
}
|
|
245
|
+
function createIconTag(href, media) {
|
|
246
|
+
return {
|
|
247
|
+
tag: "link",
|
|
248
|
+
attrs: {
|
|
249
|
+
rel: "icon",
|
|
250
|
+
...inferIconType(href) ? { type: inferIconType(href) } : {},
|
|
251
|
+
href,
|
|
252
|
+
...media ? { media } : {}
|
|
253
|
+
},
|
|
254
|
+
injectTo: "head"
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function createIconTags(icon) {
|
|
258
|
+
if (!icon) return [];
|
|
259
|
+
if (typeof icon === "string") return [createIconTag(icon)];
|
|
260
|
+
const lightIcon = icon.light ?? icon.dark;
|
|
261
|
+
const darkIcon = icon.dark ?? icon.light;
|
|
262
|
+
if (!lightIcon || !darkIcon) return [];
|
|
263
|
+
if (lightIcon === darkIcon) return [createIconTag(lightIcon)];
|
|
264
|
+
return [createIconTag(lightIcon, "(prefers-color-scheme: light)"), createIconTag(darkIcon, "(prefers-color-scheme: dark)")];
|
|
265
|
+
}
|
|
266
|
+
function createHtmlShellPlugin() {
|
|
267
|
+
return {
|
|
268
|
+
name: "clarify:html-shell",
|
|
269
|
+
hooks: { "html:transform": (input, ctx) => ({
|
|
270
|
+
html: input.html,
|
|
271
|
+
tags: [
|
|
272
|
+
...input.tags,
|
|
273
|
+
...REL_ICON_RE.test(input.html) ? [] : createIconTags(ctx.projectConfig.favicon ?? ctx.projectConfig.logo),
|
|
274
|
+
{
|
|
275
|
+
tag: "script",
|
|
276
|
+
children: THEME_BOOTSTRAP_SCRIPT,
|
|
277
|
+
injectTo: "head-prepend"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
tag: "script",
|
|
281
|
+
attrs: {
|
|
282
|
+
type: "module",
|
|
283
|
+
src: input.clientEntryId
|
|
284
|
+
},
|
|
285
|
+
injectTo: "body"
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
clientEntryId: input.clientEntryId,
|
|
289
|
+
dev: input.dev
|
|
290
|
+
}) }
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
103
294
|
//#region source/parsers/frontmatter.ts
|
|
104
295
|
/**
|
|
105
296
|
* 在内容入口统一解析 MDX frontmatter,并返回可供后续流程复用的标准化正文。
|
|
@@ -155,10 +346,21 @@ function normalizePath(path) {
|
|
|
155
346
|
const clean = "/" + path.replace(/^\/+|\/+$/g, "");
|
|
156
347
|
return clean === "/" ? "/" : clean.replace(/\/+/g, "/");
|
|
157
348
|
}
|
|
349
|
+
function pathSegmentFromOpenAPITag(tag) {
|
|
350
|
+
return tag.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tag";
|
|
351
|
+
}
|
|
352
|
+
function openAPITagsPathSegment(tags) {
|
|
353
|
+
return tags.map(pathSegmentFromOpenAPITag).join("-");
|
|
354
|
+
}
|
|
158
355
|
function basePathFromRef(ref) {
|
|
159
356
|
const withoutExtension = ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "");
|
|
160
357
|
return normalizePath(withoutExtension === "index" ? "/" : withoutExtension.replace(/\/index$/, ""));
|
|
161
358
|
}
|
|
359
|
+
function openAPIPagePathFromRef(ref, tagFilter) {
|
|
360
|
+
const basePath = basePathFromRef(ref);
|
|
361
|
+
if (!tagFilter?.length) return basePath;
|
|
362
|
+
return normalizePath(`${basePath}/${openAPITagsPathSegment(tagFilter)}`);
|
|
363
|
+
}
|
|
162
364
|
function routePathFromRef(ref) {
|
|
163
365
|
return basePathFromRef(ref);
|
|
164
366
|
}
|
|
@@ -217,7 +419,7 @@ function frontmatterKeywords(frontmatter) {
|
|
|
217
419
|
function buildNavigation(routes) {
|
|
218
420
|
const root = [];
|
|
219
421
|
for (const route of routes) {
|
|
220
|
-
if (route.path === "/") continue;
|
|
422
|
+
if (route.path === "/" || (route.basePath ?? route.path) === "/404") continue;
|
|
221
423
|
const parts = route.path.replace(/^\//, "").split("/");
|
|
222
424
|
let current = root;
|
|
223
425
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -244,6 +446,7 @@ function resolvePageItem(item) {
|
|
|
244
446
|
if (typeof item === "string") return { pageRef: item };
|
|
245
447
|
if ("openapi" in item) return {
|
|
246
448
|
openapiRef: item.openapi,
|
|
449
|
+
openapiTagFilter: item.filter?.tags,
|
|
247
450
|
title: item.title,
|
|
248
451
|
icon: item.icon
|
|
249
452
|
};
|
|
@@ -282,9 +485,9 @@ function buildNavigationFromConfig(routes, config) {
|
|
|
282
485
|
const routeMap = new Map(routes.map((r) => [r.path, r]));
|
|
283
486
|
return config.map((group) => {
|
|
284
487
|
const children = group.pages.map((item) => {
|
|
285
|
-
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
488
|
+
const { pageRef, openapiRef, openapiTagFilter, redirect, title, icon } = resolvePageItem(item);
|
|
286
489
|
if (openapiRef) {
|
|
287
|
-
const path =
|
|
490
|
+
const path = openAPIPagePathFromRef(openapiRef, openapiTagFilter);
|
|
288
491
|
const route = routeMap.get(path);
|
|
289
492
|
return {
|
|
290
493
|
path,
|
|
@@ -326,227 +529,43 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
|
|
|
326
529
|
const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
|
|
327
530
|
return config.map((group) => {
|
|
328
531
|
const children = group.pages.map((item) => {
|
|
329
|
-
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
532
|
+
const { pageRef, openapiRef, openapiTagFilter, redirect, title, icon } = resolvePageItem(item);
|
|
330
533
|
const ref = openapiRef ?? pageRef ?? "";
|
|
331
|
-
const basePath = basePathFromRef(ref);
|
|
534
|
+
const basePath = openapiRef ? openAPIPagePathFromRef(openapiRef, openapiTagFilter) : basePathFromRef(ref);
|
|
332
535
|
const route = routeMap.get(basePath);
|
|
333
536
|
const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
|
|
334
|
-
return {
|
|
335
|
-
path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale, i18n) : void 0) ?? path,
|
|
336
|
-
title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
|
|
337
|
-
icon,
|
|
338
|
-
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
339
|
-
};
|
|
340
|
-
});
|
|
341
|
-
return {
|
|
342
|
-
path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
|
|
343
|
-
title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
|
|
344
|
-
icon: group.icon,
|
|
345
|
-
children
|
|
346
|
-
};
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
|
|
350
|
-
if (!i18n) return void 0;
|
|
351
|
-
const result = {};
|
|
352
|
-
for (const locale of i18n.locales) {
|
|
353
|
-
const localeRoutes = routes.filter((route) => route.locale === locale.code);
|
|
354
|
-
result[locale.code] = { tabs: tabs.map((tab) => {
|
|
355
|
-
const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
|
|
356
|
-
return {
|
|
357
|
-
type: "tab",
|
|
358
|
-
path: firstNavigationPath(children),
|
|
359
|
-
title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
|
|
360
|
-
icon: tab.icon,
|
|
361
|
-
children
|
|
362
|
-
};
|
|
363
|
-
}) };
|
|
364
|
-
}
|
|
365
|
-
return result;
|
|
366
|
-
}
|
|
367
|
-
//#endregion
|
|
368
|
-
//#region source/plugins/content-artifacts/artifacts.ts
|
|
369
|
-
var UTF8_SIGNATURE = "";
|
|
370
|
-
function normalizeBasePath(basePath) {
|
|
371
|
-
if (!basePath || basePath === "/") return "";
|
|
372
|
-
return "/" + basePath.replace(/^\/+|\/+$/g, "");
|
|
373
|
-
}
|
|
374
|
-
function containsNonAscii(content) {
|
|
375
|
-
return [...content].some((character) => character.codePointAt(0) > 127);
|
|
376
|
-
}
|
|
377
|
-
function withUtf8Signature(content) {
|
|
378
|
-
if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
|
|
379
|
-
return `${UTF8_SIGNATURE}${content}`;
|
|
380
|
-
}
|
|
381
|
-
function shouldUseUtf8Signature(route) {
|
|
382
|
-
if (route.kind === "mdx") return true;
|
|
383
|
-
return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
|
|
384
|
-
}
|
|
385
|
-
function routeToMarkdownArtifactUrl(routePath) {
|
|
386
|
-
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
387
|
-
}
|
|
388
|
-
function routeToOpenAPIArtifactUrl(routePath, filePath) {
|
|
389
|
-
const extension = extname(filePath).toLowerCase() || ".json";
|
|
390
|
-
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
391
|
-
}
|
|
392
|
-
function attachContentArtifactUrls(routes) {
|
|
393
|
-
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
|
|
394
|
-
}
|
|
395
|
-
function readRouteContent(route) {
|
|
396
|
-
if (route.content !== void 0) return route.content;
|
|
397
|
-
throw new Error(`Route content is missing from route context: ${route.filePath}`);
|
|
398
|
-
}
|
|
399
|
-
function readRouteArtifactContent(route) {
|
|
400
|
-
const content = readRouteContent(route);
|
|
401
|
-
return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
|
|
402
|
-
}
|
|
403
|
-
function writeContentArtifactFiles(routes, outputDirectory) {
|
|
404
|
-
for (const route of routes) {
|
|
405
|
-
if (!route.contentArtifactUrl) continue;
|
|
406
|
-
const outFile = join(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
|
|
407
|
-
mkdirSync(dirname(outFile), { recursive: true });
|
|
408
|
-
writeFileSync(outFile, readRouteArtifactContent(route), "utf-8");
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
function createLlmsTxt(routes, projectConfig) {
|
|
412
|
-
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
413
|
-
const lines = [`# ${projectConfig.title}`, ""];
|
|
414
|
-
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
415
|
-
lines.push("## Docs");
|
|
416
|
-
for (const route of routes.filter((route) => route.kind === "mdx")) {
|
|
417
|
-
if (!route.contentArtifactUrl) continue;
|
|
418
|
-
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
419
|
-
}
|
|
420
|
-
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
421
|
-
if (openApiRoutes.length > 0) {
|
|
422
|
-
lines.push("", "## OpenAPI");
|
|
423
|
-
for (const route of openApiRoutes) {
|
|
424
|
-
if (!route.contentArtifactUrl) continue;
|
|
425
|
-
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return `${lines.join("\n")}\n`;
|
|
429
|
-
}
|
|
430
|
-
function createLlmsTxtArtifact(routes, projectConfig) {
|
|
431
|
-
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
432
|
-
}
|
|
433
|
-
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
434
|
-
mkdirSync(outputDirectory, { recursive: true });
|
|
435
|
-
writeFileSync(join(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
|
|
436
|
-
}
|
|
437
|
-
//#endregion
|
|
438
|
-
//#region source/plugins/content-artifacts/server.ts
|
|
439
|
-
function normalizeRoutePrefix(routePrefix) {
|
|
440
|
-
if (!routePrefix || routePrefix === "/") return "";
|
|
441
|
-
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
442
|
-
}
|
|
443
|
-
function resolveContentArtifactPath(url, projectConfig) {
|
|
444
|
-
const requestPath = url?.split("?")[0] ?? "";
|
|
445
|
-
const basePath = normalizeRoutePrefix(projectConfig.routePrefix);
|
|
446
|
-
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
447
|
-
}
|
|
448
|
-
function resolveContentArtifactType(route) {
|
|
449
|
-
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
450
|
-
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
451
|
-
}
|
|
452
|
-
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
453
|
-
const contentPath = resolveContentArtifactPath(req.url, projectConfig);
|
|
454
|
-
if (contentPath === "/llms.txt") {
|
|
455
|
-
res.statusCode = 200;
|
|
456
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
457
|
-
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
458
|
-
return true;
|
|
459
|
-
}
|
|
460
|
-
const route = routes.find((route) => route.contentArtifactUrl === contentPath);
|
|
461
|
-
if (!route) return false;
|
|
462
|
-
res.statusCode = 200;
|
|
463
|
-
res.setHeader("Content-Type", resolveContentArtifactType(route));
|
|
464
|
-
res.end(readRouteArtifactContent(route), "utf8");
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
//#endregion
|
|
468
|
-
//#region source/plugins/content-artifacts/index.ts
|
|
469
|
-
function createContentArtifactsPlugin() {
|
|
470
|
-
return {
|
|
471
|
-
name: "clarify:content-artifacts",
|
|
472
|
-
hooks: {
|
|
473
|
-
"routes:resolved": (input) => {
|
|
474
|
-
attachContentArtifactUrls(input.routes);
|
|
475
|
-
return input;
|
|
476
|
-
},
|
|
477
|
-
"dev:configureServer": (server, ctx) => {
|
|
478
|
-
server.middlewares.use((req, res, next) => {
|
|
479
|
-
if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
|
|
480
|
-
next();
|
|
481
|
-
});
|
|
482
|
-
},
|
|
483
|
-
"build:done": (ctx) => {
|
|
484
|
-
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
485
|
-
if (!outputDirectory) return;
|
|
486
|
-
writeContentArtifactFiles(ctx.routes, outputDirectory);
|
|
487
|
-
writeLlmsTxt(ctx.routes, ctx.projectConfig, outputDirectory);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
//#endregion
|
|
493
|
-
//#region source/plugins/html-shell/index.ts
|
|
494
|
-
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){}})();`;
|
|
495
|
-
var REL_ICON_RE = /<link\s+[^>]*rel=["'][^"']*\bicon\b[^"']*["'][^>]*>/i;
|
|
496
|
-
function inferIconType(href) {
|
|
497
|
-
const path = href.split(/[?#]/, 1)[0]?.toLowerCase() ?? href.toLowerCase();
|
|
498
|
-
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
499
|
-
if (path.endsWith(".png")) return "image/png";
|
|
500
|
-
if (path.endsWith(".ico")) return "image/x-icon";
|
|
501
|
-
if (path.endsWith(".webp")) return "image/webp";
|
|
502
|
-
}
|
|
503
|
-
function createIconTag(href, media) {
|
|
504
|
-
return {
|
|
505
|
-
tag: "link",
|
|
506
|
-
attrs: {
|
|
507
|
-
rel: "icon",
|
|
508
|
-
...inferIconType(href) ? { type: inferIconType(href) } : {},
|
|
509
|
-
href,
|
|
510
|
-
...media ? { media } : {}
|
|
511
|
-
},
|
|
512
|
-
injectTo: "head"
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
function createIconTags(icon) {
|
|
516
|
-
if (!icon) return [];
|
|
517
|
-
if (typeof icon === "string") return [createIconTag(icon)];
|
|
518
|
-
const lightIcon = icon.light ?? icon.dark;
|
|
519
|
-
const darkIcon = icon.dark ?? icon.light;
|
|
520
|
-
if (!lightIcon || !darkIcon) return [];
|
|
521
|
-
if (lightIcon === darkIcon) return [createIconTag(lightIcon)];
|
|
522
|
-
return [createIconTag(lightIcon, "(prefers-color-scheme: light)"), createIconTag(darkIcon, "(prefers-color-scheme: dark)")];
|
|
523
|
-
}
|
|
524
|
-
function createHtmlShellPlugin() {
|
|
525
|
-
return {
|
|
526
|
-
name: "clarify:html-shell",
|
|
527
|
-
hooks: { "html:transform": (input, ctx) => ({
|
|
528
|
-
html: input.html,
|
|
529
|
-
tags: [
|
|
530
|
-
...input.tags,
|
|
531
|
-
...REL_ICON_RE.test(input.html) ? [] : createIconTags(ctx.projectConfig.favicon ?? ctx.projectConfig.logo),
|
|
532
|
-
{
|
|
533
|
-
tag: "script",
|
|
534
|
-
children: THEME_BOOTSTRAP_SCRIPT,
|
|
535
|
-
injectTo: "head-prepend"
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
tag: "script",
|
|
539
|
-
attrs: {
|
|
540
|
-
type: "module",
|
|
541
|
-
src: input.clientEntryId
|
|
542
|
-
},
|
|
543
|
-
injectTo: "body"
|
|
544
|
-
}
|
|
545
|
-
],
|
|
546
|
-
clientEntryId: input.clientEntryId,
|
|
547
|
-
dev: input.dev
|
|
548
|
-
}) }
|
|
549
|
-
};
|
|
537
|
+
return {
|
|
538
|
+
path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale, i18n) : void 0) ?? path,
|
|
539
|
+
title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
|
|
540
|
+
icon,
|
|
541
|
+
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
|
|
546
|
+
title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
|
|
547
|
+
icon: group.icon,
|
|
548
|
+
children
|
|
549
|
+
};
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
|
|
553
|
+
if (!i18n) return void 0;
|
|
554
|
+
const result = {};
|
|
555
|
+
for (const locale of i18n.locales) {
|
|
556
|
+
const localeRoutes = routes.filter((route) => route.locale === locale.code);
|
|
557
|
+
result[locale.code] = { tabs: tabs.map((tab) => {
|
|
558
|
+
const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
|
|
559
|
+
return {
|
|
560
|
+
type: "tab",
|
|
561
|
+
path: firstNavigationPath(children),
|
|
562
|
+
title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
|
|
563
|
+
icon: tab.icon,
|
|
564
|
+
children
|
|
565
|
+
};
|
|
566
|
+
}) };
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
550
569
|
}
|
|
551
570
|
//#endregion
|
|
552
571
|
//#region source/plugins/openapi/parser.ts
|
|
@@ -591,21 +610,26 @@ function findOpenAPIRoutes(dir, base = dir) {
|
|
|
591
610
|
}
|
|
592
611
|
return routes;
|
|
593
612
|
}
|
|
613
|
+
function operationMatchesTags(operationTags, filterTags) {
|
|
614
|
+
if (!filterTags?.length) return true;
|
|
615
|
+
return operationTags?.some((tag) => filterTags.includes(tag)) ?? false;
|
|
616
|
+
}
|
|
594
617
|
/** Extract endpoint operations from an OpenAPI spec as page sections. */
|
|
595
|
-
function extractOpenAPISections(spec) {
|
|
618
|
+
function extractOpenAPISections(spec, filterTags) {
|
|
596
619
|
const sections = [];
|
|
597
620
|
const paths = spec.paths ?? {};
|
|
598
621
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
599
622
|
if (!pathItem) continue;
|
|
600
623
|
for (const method of OPENAPI_HTTP_METHODS) {
|
|
601
624
|
const op = pathItem[method];
|
|
602
|
-
if (!op) continue;
|
|
625
|
+
if (!op || !operationMatchesTags(op.tags, filterTags)) continue;
|
|
603
626
|
const title = op.summary ?? `${method.toUpperCase()} ${path}`;
|
|
604
627
|
sections.push({
|
|
605
628
|
id: slug(`${method} ${path}`),
|
|
606
629
|
title,
|
|
607
630
|
level: 2,
|
|
608
|
-
badge: method.toUpperCase()
|
|
631
|
+
badge: method.toUpperCase(),
|
|
632
|
+
tags: op.tags
|
|
609
633
|
});
|
|
610
634
|
}
|
|
611
635
|
}
|
|
@@ -630,17 +654,89 @@ async function readOpenAPISpec(filePath) {
|
|
|
630
654
|
}
|
|
631
655
|
}
|
|
632
656
|
//#endregion
|
|
657
|
+
//#region source/core/virtual-modules.ts
|
|
658
|
+
var VIRTUAL_CONFIG = "virtual:clarify-config";
|
|
659
|
+
var VIRTUAL_ROUTES = "virtual:clarify-routes";
|
|
660
|
+
var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
|
|
661
|
+
var VIRTUAL_RUNTIME = "virtual:clarify-runtime";
|
|
662
|
+
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
|
|
663
|
+
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
|
|
664
|
+
function resolveVirtualId(id) {
|
|
665
|
+
return "\0" + id;
|
|
666
|
+
}
|
|
667
|
+
function stripVirtualPrefix(id) {
|
|
668
|
+
return id.startsWith("\0") ? id.slice(1) : id;
|
|
669
|
+
}
|
|
670
|
+
function generateConfigModule(projectConfig, buildOptions) {
|
|
671
|
+
return `export const config = ${JSON.stringify({
|
|
672
|
+
...projectConfig,
|
|
673
|
+
...buildOptions
|
|
674
|
+
})};`;
|
|
675
|
+
}
|
|
676
|
+
function moduleSpecifier(value) {
|
|
677
|
+
return JSON.stringify(value);
|
|
678
|
+
}
|
|
679
|
+
function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
|
|
680
|
+
const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
|
|
681
|
+
const routesArray = routes.map((r, i) => {
|
|
682
|
+
const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
|
|
683
|
+
id: s.id,
|
|
684
|
+
title: s.title,
|
|
685
|
+
badge: s.badge,
|
|
686
|
+
tags: s.tags
|
|
687
|
+
})))}` : "";
|
|
688
|
+
const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
|
|
689
|
+
const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
|
|
690
|
+
const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
|
|
691
|
+
const isFallback = r.isFallback ? ", isFallback: true" : "";
|
|
692
|
+
const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
|
|
693
|
+
const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
|
|
694
|
+
const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
|
|
695
|
+
const sourceUrl = r.sourceUrl ? `, sourceUrl: ${JSON.stringify(r.sourceUrl)}` : "";
|
|
696
|
+
const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
|
|
697
|
+
const lazy = mode === "client" ? ", lazy: true" : "";
|
|
698
|
+
return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: ${component}${lazy}, kind: ${JSON.stringify(r.kind)}${basePath}${locale}${isFallback}${alternates}${description}${keywords}${sections}${contentArtifactUrl}${sourceUrl} }`;
|
|
699
|
+
}).join(",\n");
|
|
700
|
+
const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
|
|
701
|
+
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
702
|
+
}
|
|
703
|
+
function createRuntimeModule() {
|
|
704
|
+
return "export const openApis = {};";
|
|
705
|
+
}
|
|
706
|
+
function createClientEntryModule(options = {}) {
|
|
707
|
+
return `
|
|
708
|
+
import '@clarify-labs/renderer/style.css';
|
|
709
|
+
import { render } from '@clarify-labs/renderer/client';
|
|
710
|
+
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
711
|
+
import { config } from '${VIRTUAL_CONFIG}';
|
|
712
|
+
import { openApis } from '${VIRTUAL_RUNTIME}';
|
|
713
|
+
render({ config, routes, navigation, openApis, themeEditor: ${JSON.stringify(options.themeEditor ?? false)} });`;
|
|
714
|
+
}
|
|
715
|
+
function buildVirtualModules(args) {
|
|
716
|
+
const modules = /* @__PURE__ */ new Map();
|
|
717
|
+
const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
|
|
718
|
+
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
719
|
+
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
|
|
720
|
+
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
|
|
721
|
+
modules.set(VIRTUAL_RUNTIME, createRuntimeModule());
|
|
722
|
+
modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
|
|
723
|
+
modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
|
|
724
|
+
for (const route of args.routes) modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
|
|
725
|
+
return modules;
|
|
726
|
+
}
|
|
727
|
+
//#endregion
|
|
633
728
|
//#region source/plugins/openapi/virtual-modules.ts
|
|
634
|
-
var openApiRegistryModuleId =
|
|
729
|
+
var openApiRegistryModuleId = VIRTUAL_RUNTIME;
|
|
635
730
|
function generateOpenAPIRegistryModule(openApis) {
|
|
636
731
|
return `export const openApis = ${JSON.stringify(openApis)};`;
|
|
637
732
|
}
|
|
638
|
-
function generateOpenAPIModule(spec) {
|
|
733
|
+
function generateOpenAPIModule(spec, tagFilter) {
|
|
639
734
|
return `import { createElement } from 'react';
|
|
640
735
|
import { OpenApiDocument } from '@clarify-labs/renderer';
|
|
641
736
|
const spec = ${JSON.stringify(spec)};
|
|
737
|
+
const tagFilter = ${JSON.stringify(tagFilter)};
|
|
642
738
|
export default function OpenApiRoutePage() {
|
|
643
|
-
return createElement(OpenApiDocument, { spec });
|
|
739
|
+
return createElement(OpenApiDocument, { spec, tagFilter });
|
|
644
740
|
}`;
|
|
645
741
|
}
|
|
646
742
|
function generateOpenAPIErrorModule(diagnostic) {
|
|
@@ -666,6 +762,55 @@ export default function OpenApiErrorRoutePage() {
|
|
|
666
762
|
}
|
|
667
763
|
//#endregion
|
|
668
764
|
//#region source/plugins/openapi/index.ts
|
|
765
|
+
function collectOpenAPIPageItems(config) {
|
|
766
|
+
const items = [];
|
|
767
|
+
function visitPages(pages) {
|
|
768
|
+
if (!pages || pages === "FileTree") return;
|
|
769
|
+
for (const group of pages) for (const item of group.pages) if (typeof item !== "string" && "openapi" in item && item.filter?.tags?.length) items.push(item);
|
|
770
|
+
}
|
|
771
|
+
for (const tab of config.tabs ?? []) visitPages(tab.pages);
|
|
772
|
+
return items;
|
|
773
|
+
}
|
|
774
|
+
function withAlternates$1(route, routes, i18n) {
|
|
775
|
+
const basePath = route.basePath ?? route.path;
|
|
776
|
+
const routeByLocaleAndBase = new Map(routes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
777
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
778
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
779
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
780
|
+
}));
|
|
781
|
+
return {
|
|
782
|
+
...route,
|
|
783
|
+
alternates
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function createTaggedOpenAPIRoutes(routes, config) {
|
|
787
|
+
const pageItems = collectOpenAPIPageItems(config);
|
|
788
|
+
if (!pageItems.length) return routes;
|
|
789
|
+
const additions = [];
|
|
790
|
+
const existingKeys = new Set(routes.map((route) => `${route.locale ?? ""}:${route.basePath ?? route.path}`));
|
|
791
|
+
for (const item of pageItems) {
|
|
792
|
+
const tagFilter = item.filter?.tags?.filter(Boolean);
|
|
793
|
+
if (!tagFilter?.length) continue;
|
|
794
|
+
const sourceBasePath = openAPIPagePathFromRef(item.openapi);
|
|
795
|
+
const filteredBasePath = openAPIPagePathFromRef(item.openapi, tagFilter);
|
|
796
|
+
for (const route of routes) {
|
|
797
|
+
if (route.kind !== "openapi" || (route.basePath ?? route.path) !== sourceBasePath) continue;
|
|
798
|
+
const key = `${route.locale ?? ""}:${filteredBasePath}`;
|
|
799
|
+
if (existingKeys.has(key)) continue;
|
|
800
|
+
existingKeys.add(key);
|
|
801
|
+
additions.push({
|
|
802
|
+
...route,
|
|
803
|
+
path: route.locale && config.i18n ? localizedRoutePath(filteredBasePath, route.locale, config.i18n) : filteredBasePath,
|
|
804
|
+
basePath: filteredBasePath,
|
|
805
|
+
title: route.title,
|
|
806
|
+
virtualModuleId: `${route.virtualModuleId}/${openAPITagsPathSegment(tagFilter)}`,
|
|
807
|
+
openapiTagFilter: tagFilter
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const nextRoutes = [...routes, ...additions];
|
|
812
|
+
return config.i18n ? nextRoutes.map((route) => withAlternates$1(route, nextRoutes, config.i18n)) : nextRoutes;
|
|
813
|
+
}
|
|
669
814
|
function createOpenAPIPlugin() {
|
|
670
815
|
const specs = {};
|
|
671
816
|
return {
|
|
@@ -675,10 +820,16 @@ function createOpenAPIPlugin() {
|
|
|
675
820
|
...input,
|
|
676
821
|
routes: [...input.routes, ...findOpenAPIRoutes(input.contentRoot)]
|
|
677
822
|
}),
|
|
678
|
-
"routes:discovered": async (routes) => {
|
|
823
|
+
"routes:discovered": async (routes, ctx) => {
|
|
679
824
|
for (const key of Object.keys(specs)) delete specs[key];
|
|
680
|
-
|
|
681
|
-
|
|
825
|
+
const nextRoutes = createTaggedOpenAPIRoutes(routes, ctx.projectConfig);
|
|
826
|
+
const specByFilePath = /* @__PURE__ */ new Map();
|
|
827
|
+
for (const route of nextRoutes.filter((route) => route.kind === "openapi")) {
|
|
828
|
+
const specFromCache = specByFilePath.get(route.filePath);
|
|
829
|
+
const result = specFromCache ? {
|
|
830
|
+
ok: true,
|
|
831
|
+
spec: specFromCache
|
|
832
|
+
} : await readOpenAPISpec(route.filePath);
|
|
682
833
|
if (!result.ok) {
|
|
683
834
|
route.diagnostic = result.diagnostic;
|
|
684
835
|
route.title = route.title || "OpenAPI parse error";
|
|
@@ -686,17 +837,24 @@ function createOpenAPIPlugin() {
|
|
|
686
837
|
continue;
|
|
687
838
|
}
|
|
688
839
|
const spec = result.spec;
|
|
689
|
-
|
|
690
|
-
specs[
|
|
840
|
+
specByFilePath.set(route.filePath, spec);
|
|
841
|
+
specs[route.virtualModuleId] = {
|
|
842
|
+
spec,
|
|
843
|
+
tagFilter: route.openapiTagFilter
|
|
844
|
+
};
|
|
845
|
+
specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = {
|
|
846
|
+
spec,
|
|
847
|
+
tagFilter: route.openapiTagFilter
|
|
848
|
+
};
|
|
691
849
|
route.title = spec.info?.title ?? route.title;
|
|
692
850
|
route.description = spec.info?.description ?? route.description;
|
|
693
|
-
route.sections = extractOpenAPISections(spec);
|
|
851
|
+
route.sections = extractOpenAPISections(spec, route.openapiTagFilter);
|
|
694
852
|
}
|
|
695
|
-
return
|
|
853
|
+
return nextRoutes;
|
|
696
854
|
},
|
|
697
855
|
"modules:before": (modules, ctx) => {
|
|
698
|
-
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(specs));
|
|
699
|
-
for (const [moduleId,
|
|
856
|
+
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(Object.fromEntries(Object.entries(specs).map(([moduleId, entry]) => [moduleId, entry.spec]))));
|
|
857
|
+
for (const [moduleId, entry] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(entry.spec, entry.tagFilter));
|
|
700
858
|
for (const route of ctx.routes.filter((route) => route.kind === "openapi" && route.diagnostic)) if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
|
|
701
859
|
return modules;
|
|
702
860
|
}
|
|
@@ -704,6 +862,107 @@ function createOpenAPIPlugin() {
|
|
|
704
862
|
};
|
|
705
863
|
}
|
|
706
864
|
//#endregion
|
|
865
|
+
//#region source/plugins/seo/artifacts.ts
|
|
866
|
+
function trimTrailingSlash(value) {
|
|
867
|
+
return value.replace(/\/+$/, "");
|
|
868
|
+
}
|
|
869
|
+
function normalizeRoutePrefix(routePrefix) {
|
|
870
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
871
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
872
|
+
}
|
|
873
|
+
function routeUrl(siteUrl, routePrefix, routePath) {
|
|
874
|
+
const base = trimTrailingSlash(siteUrl);
|
|
875
|
+
return `${base}${normalizeRoutePrefix(routePrefix)}${routePath === "/" ? "" : routePath.replace(/^\/+/, "/")}` || base;
|
|
876
|
+
}
|
|
877
|
+
function escapeXml(value) {
|
|
878
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
879
|
+
}
|
|
880
|
+
function publicRoutes(routes) {
|
|
881
|
+
return routes.filter((route) => route.path !== "/404");
|
|
882
|
+
}
|
|
883
|
+
function createSitemapXml(routes, config) {
|
|
884
|
+
if (!config.siteUrl) return void 0;
|
|
885
|
+
return [
|
|
886
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
887
|
+
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
|
|
888
|
+
...publicRoutes(routes).map((route) => routeUrl(config.siteUrl, config.routePrefix, route.path)).filter((url, index, list) => list.indexOf(url) === index).sort().map((url) => ` <url><loc>${escapeXml(url)}</loc></url>`),
|
|
889
|
+
"</urlset>",
|
|
890
|
+
""
|
|
891
|
+
].join("\n");
|
|
892
|
+
}
|
|
893
|
+
function createRobotsTxt(config) {
|
|
894
|
+
if (!config.siteUrl) return void 0;
|
|
895
|
+
return [
|
|
896
|
+
"User-agent: *",
|
|
897
|
+
"Allow: /",
|
|
898
|
+
`Sitemap: ${routeUrl(config.siteUrl, config.routePrefix, "/sitemap.xml")}`,
|
|
899
|
+
""
|
|
900
|
+
].join("\n");
|
|
901
|
+
}
|
|
902
|
+
async function writeSeoFiles(outputRoot, routes, config) {
|
|
903
|
+
const sitemap = createSitemapXml(routes, config);
|
|
904
|
+
const robots = createRobotsTxt(config);
|
|
905
|
+
if (!sitemap || !robots) return;
|
|
906
|
+
await mkdir(outputRoot, { recursive: true });
|
|
907
|
+
await Promise.all([writeFile(join(outputRoot, "sitemap.xml"), sitemap, "utf-8"), writeFile(join(outputRoot, "robots.txt"), robots, "utf-8")]);
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region source/plugins/seo/index.ts
|
|
911
|
+
function createSeoPlugin() {
|
|
912
|
+
return {
|
|
913
|
+
name: "clarify:seo",
|
|
914
|
+
hooks: { async "build:done"(ctx) {
|
|
915
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
916
|
+
if (!outputDirectory) return;
|
|
917
|
+
await writeSeoFiles(join(ctx.generateOptions.projectRoot, outputDirectory), ctx.routes, ctx.projectConfig);
|
|
918
|
+
} }
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
//#endregion
|
|
922
|
+
//#region source/plugins/source-links/source-links.ts
|
|
923
|
+
function trimSlashes(value) {
|
|
924
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
925
|
+
}
|
|
926
|
+
function normalizeRepositoryUrl(repository) {
|
|
927
|
+
return repository.replace(/\.git$/, "").replace(/\/+$/, "");
|
|
928
|
+
}
|
|
929
|
+
function encodePath(path) {
|
|
930
|
+
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
931
|
+
}
|
|
932
|
+
function createSourceUrl(filePath, contentRoot, source) {
|
|
933
|
+
const repository = normalizeRepositoryUrl(source.repository);
|
|
934
|
+
const branch = source.branch ?? "main";
|
|
935
|
+
const sourcePath = [source.directory ? trimSlashes(source.directory) : "", relative(contentRoot, filePath).replaceAll("\\", "/")].filter(Boolean).join("/");
|
|
936
|
+
return `${repository}/edit/${encodeURIComponent(branch)}/${encodePath(sourcePath)}`;
|
|
937
|
+
}
|
|
938
|
+
function attachSourceUrls(routes, contentRoot, source) {
|
|
939
|
+
if (!source) return;
|
|
940
|
+
for (const route of routes) route.sourceUrl = createSourceUrl(route.filePath, contentRoot, source);
|
|
941
|
+
}
|
|
942
|
+
//#endregion
|
|
943
|
+
//#region source/plugins/source-links/index.ts
|
|
944
|
+
function createSourceLinksPlugin() {
|
|
945
|
+
return {
|
|
946
|
+
name: "clarify:source-links",
|
|
947
|
+
hooks: { "routes:discovered": (routes, ctx) => {
|
|
948
|
+
attachSourceUrls(routes, join(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.source);
|
|
949
|
+
return routes;
|
|
950
|
+
} }
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region source/core/builtin.ts
|
|
955
|
+
function createBuiltinPlugins(options = {}) {
|
|
956
|
+
const plugins = [
|
|
957
|
+
createOpenAPIPlugin(),
|
|
958
|
+
createSourceLinksPlugin(),
|
|
959
|
+
createContentArtifactsPlugin(),
|
|
960
|
+
createSeoPlugin()
|
|
961
|
+
];
|
|
962
|
+
if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
|
|
963
|
+
return plugins;
|
|
964
|
+
}
|
|
965
|
+
//#endregion
|
|
707
966
|
//#region source/core/config-schema.ts
|
|
708
967
|
var clarifyLogoConfigSchema = z.union([z.string(), z.object({
|
|
709
968
|
light: z.string().optional(),
|
|
@@ -761,6 +1020,11 @@ var clarifyFooterConfigSchema = z.object({
|
|
|
761
1020
|
socials: z.record(z.string(), z.string()).optional(),
|
|
762
1021
|
copyright: clarifyLocalizedTextSchema.optional()
|
|
763
1022
|
});
|
|
1023
|
+
var clarifySourceConfigSchema = z.object({
|
|
1024
|
+
repository: z.string(),
|
|
1025
|
+
branch: z.string().optional(),
|
|
1026
|
+
directory: z.string().optional()
|
|
1027
|
+
});
|
|
764
1028
|
var clarifyPagesItemSchema = z.union([
|
|
765
1029
|
z.string(),
|
|
766
1030
|
z.object({
|
|
@@ -772,7 +1036,8 @@ var clarifyPagesItemSchema = z.union([
|
|
|
772
1036
|
z.object({
|
|
773
1037
|
openapi: z.string(),
|
|
774
1038
|
icon: z.string().optional(),
|
|
775
|
-
title: clarifyLocalizedTextSchema.optional()
|
|
1039
|
+
title: clarifyLocalizedTextSchema.optional(),
|
|
1040
|
+
filter: z.object({ tags: z.array(z.string()).optional() }).optional()
|
|
776
1041
|
})
|
|
777
1042
|
]);
|
|
778
1043
|
var clarifyPagesGroupSchema = z.object({
|
|
@@ -788,15 +1053,20 @@ var clarifyTabItemSchema = z.object({
|
|
|
788
1053
|
});
|
|
789
1054
|
var clarifyTabsConfigSchema = z.array(clarifyTabItemSchema);
|
|
790
1055
|
var clarifyThemePresetSchema = z.enum(["default", "base"]);
|
|
1056
|
+
var clarifyThemeModeColorValueSchema = z.object({
|
|
1057
|
+
light: z.string().optional(),
|
|
1058
|
+
dark: z.string().optional()
|
|
1059
|
+
});
|
|
1060
|
+
var clarifyThemeColorValueSchema = z.union([z.string(), clarifyThemeModeColorValueSchema]);
|
|
791
1061
|
var clarifyThemeColorTokensConfigSchema = z.object({
|
|
792
|
-
primary:
|
|
793
|
-
accent:
|
|
794
|
-
background:
|
|
795
|
-
foreground:
|
|
796
|
-
surface:
|
|
797
|
-
muted:
|
|
798
|
-
border:
|
|
799
|
-
codeBackground:
|
|
1062
|
+
primary: clarifyThemeColorValueSchema.optional(),
|
|
1063
|
+
accent: clarifyThemeColorValueSchema.optional(),
|
|
1064
|
+
background: clarifyThemeColorValueSchema.optional(),
|
|
1065
|
+
foreground: clarifyThemeColorValueSchema.optional(),
|
|
1066
|
+
surface: clarifyThemeColorValueSchema.optional(),
|
|
1067
|
+
muted: clarifyThemeColorValueSchema.optional(),
|
|
1068
|
+
border: clarifyThemeColorValueSchema.optional(),
|
|
1069
|
+
codeBackground: clarifyThemeColorValueSchema.optional()
|
|
800
1070
|
});
|
|
801
1071
|
var clarifyThemeRadiusTokensConfigSchema = z.object({
|
|
802
1072
|
sm: z.string().optional(),
|
|
@@ -812,11 +1082,14 @@ var clarifyThemeLayoutConfigSchema = z.object({ maxWidth: z.string().optional()
|
|
|
812
1082
|
var clarifyThemeConfigSchema = z.object({
|
|
813
1083
|
preset: clarifyThemePresetSchema.optional(),
|
|
814
1084
|
tokens: clarifyThemeTokensConfigSchema.optional(),
|
|
815
|
-
layout: clarifyThemeLayoutConfigSchema.optional()
|
|
1085
|
+
layout: clarifyThemeLayoutConfigSchema.optional(),
|
|
1086
|
+
editor: z.boolean().optional()
|
|
816
1087
|
});
|
|
817
1088
|
var clarifyProjectConfigSchema = z.object({
|
|
818
1089
|
title: z.string().optional(),
|
|
819
1090
|
description: z.string().optional(),
|
|
1091
|
+
siteUrl: z.string().optional(),
|
|
1092
|
+
source: clarifySourceConfigSchema.optional(),
|
|
820
1093
|
logo: clarifyLogoConfigSchema.optional(),
|
|
821
1094
|
favicon: clarifyFaviconConfigSchema.optional(),
|
|
822
1095
|
theme: clarifyThemeConfigSchema.optional(),
|
|
@@ -834,14 +1107,14 @@ var clarifyThemePresets = {
|
|
|
834
1107
|
preset: "default",
|
|
835
1108
|
tokens: {
|
|
836
1109
|
colors: {
|
|
837
|
-
primary: "#
|
|
838
|
-
accent: "#
|
|
1110
|
+
primary: "#047857",
|
|
1111
|
+
accent: "#0D9488",
|
|
839
1112
|
background: "#ffffff",
|
|
840
1113
|
foreground: "#111827",
|
|
841
1114
|
surface: "#ffffff",
|
|
842
1115
|
muted: "#64748b",
|
|
843
1116
|
border: "rgb(15 23 42 / 0.12)",
|
|
844
|
-
codeBackground: "#
|
|
1117
|
+
codeBackground: "#f6fbf8"
|
|
845
1118
|
},
|
|
846
1119
|
radius: {
|
|
847
1120
|
sm: "6px",
|
|
@@ -850,7 +1123,8 @@ var clarifyThemePresets = {
|
|
|
850
1123
|
xl: "18px"
|
|
851
1124
|
}
|
|
852
1125
|
},
|
|
853
|
-
layout: { maxWidth: "82rem" }
|
|
1126
|
+
layout: { maxWidth: "82rem" },
|
|
1127
|
+
editor: false
|
|
854
1128
|
},
|
|
855
1129
|
base: {
|
|
856
1130
|
preset: "base",
|
|
@@ -872,7 +1146,8 @@ var clarifyThemePresets = {
|
|
|
872
1146
|
xl: "16px"
|
|
873
1147
|
}
|
|
874
1148
|
},
|
|
875
|
-
layout: { maxWidth: "80rem" }
|
|
1149
|
+
layout: { maxWidth: "80rem" },
|
|
1150
|
+
editor: false
|
|
876
1151
|
}
|
|
877
1152
|
};
|
|
878
1153
|
function resolveThemeConfig(theme = {}) {
|
|
@@ -893,7 +1168,8 @@ function resolveThemeConfig(theme = {}) {
|
|
|
893
1168
|
layout: {
|
|
894
1169
|
...presetTheme.layout,
|
|
895
1170
|
...theme.layout
|
|
896
|
-
}
|
|
1171
|
+
},
|
|
1172
|
+
editor: theme.editor ?? presetTheme.editor
|
|
897
1173
|
};
|
|
898
1174
|
}
|
|
899
1175
|
//#endregion
|
|
@@ -932,6 +1208,8 @@ function resolveProjectConfig(config = {}) {
|
|
|
932
1208
|
return {
|
|
933
1209
|
title: config.title ?? "Clarify Docs",
|
|
934
1210
|
description: config.description ?? "",
|
|
1211
|
+
siteUrl: config.siteUrl,
|
|
1212
|
+
source: config.source,
|
|
935
1213
|
logo: config.logo,
|
|
936
1214
|
favicon: config.favicon,
|
|
937
1215
|
routePrefix: config.routePrefix ?? "/",
|
|
@@ -984,12 +1262,104 @@ async function runBuildDoneHooks(plugins, ctx) {
|
|
|
984
1262
|
//#region source/core/options.ts
|
|
985
1263
|
function resolveBuildOptions(options = {}) {
|
|
986
1264
|
return {
|
|
1265
|
+
projectRoot: options.projectRoot ?? process.cwd(),
|
|
987
1266
|
rootDirectory: options.rootDirectory ?? "source",
|
|
988
1267
|
outputDirectory: options.outputDirectory,
|
|
989
1268
|
ssg: { failOnError: options.ssg?.failOnError ?? true }
|
|
990
1269
|
};
|
|
991
1270
|
}
|
|
992
1271
|
//#endregion
|
|
1272
|
+
//#region source/core/site.ts
|
|
1273
|
+
function withAlternates(route, routeList, i18n) {
|
|
1274
|
+
const basePath = route.basePath ?? route.path;
|
|
1275
|
+
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1276
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1277
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1278
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1279
|
+
}));
|
|
1280
|
+
return {
|
|
1281
|
+
...route,
|
|
1282
|
+
alternates
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
async function discoverRoutesForRoot(routeRoot, locale, plugins, ctx) {
|
|
1286
|
+
return (await runHooks(plugins, "routes:discover", {
|
|
1287
|
+
contentRoot: routeRoot,
|
|
1288
|
+
locale,
|
|
1289
|
+
routes: findContentRoutes(routeRoot)
|
|
1290
|
+
}, ctx)).routes;
|
|
1291
|
+
}
|
|
1292
|
+
async function discoverRoutes(root, contentRoot, plugins, ctx) {
|
|
1293
|
+
const i18n = ctx.projectConfig.i18n;
|
|
1294
|
+
if (!i18n) return discoverRoutesForRoot(contentRoot, void 0, plugins, ctx);
|
|
1295
|
+
const localizedRoutes = [];
|
|
1296
|
+
for (const locale of i18n.locales) {
|
|
1297
|
+
const discovered = await discoverRoutesForRoot(join(contentRoot, locale.code), locale.code, plugins, ctx);
|
|
1298
|
+
for (const route of discovered) {
|
|
1299
|
+
const basePath = route.basePath ?? route.path;
|
|
1300
|
+
localizedRoutes.push({
|
|
1301
|
+
...route,
|
|
1302
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1303
|
+
basePath,
|
|
1304
|
+
locale: locale.code,
|
|
1305
|
+
virtualModuleId: virtualModuleIdFromRef(relative(contentRoot, route.filePath))
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (i18n.missing === "fallback") {
|
|
1310
|
+
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1311
|
+
const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
|
|
1312
|
+
for (const sourceRoute of defaultRoutes) {
|
|
1313
|
+
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
1314
|
+
for (const locale of i18n.locales) {
|
|
1315
|
+
const key = `${locale.code}:${basePath}`;
|
|
1316
|
+
if (routeByLocaleAndBase.has(key)) continue;
|
|
1317
|
+
localizedRoutes.push({
|
|
1318
|
+
...sourceRoute,
|
|
1319
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1320
|
+
locale: locale.code,
|
|
1321
|
+
isFallback: true
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1327
|
+
}
|
|
1328
|
+
async function resolveClarifySite(options = {}, resolveOptions = {}) {
|
|
1329
|
+
const root = options.projectRoot ?? process.cwd();
|
|
1330
|
+
const projectConfig = resolveProjectConfig(options);
|
|
1331
|
+
const generateOptions = resolveBuildOptions(options);
|
|
1332
|
+
const contentRoot = join(root, generateOptions.rootDirectory);
|
|
1333
|
+
const plugins = [...createBuiltinPlugins({ htmlShell: resolveOptions.includeHtmlShellPlugin }), ...options.plugins ?? []];
|
|
1334
|
+
const ctx = {
|
|
1335
|
+
projectConfig,
|
|
1336
|
+
generateOptions,
|
|
1337
|
+
routes: [],
|
|
1338
|
+
navigation: []
|
|
1339
|
+
};
|
|
1340
|
+
let routes = await discoverRoutes(root, contentRoot, plugins, ctx);
|
|
1341
|
+
routes = await runHooks(plugins, "routes:discovered", routes, ctx);
|
|
1342
|
+
const defaultNavigation = projectConfig.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes);
|
|
1343
|
+
const resolved = await runHooks(plugins, "routes:resolved", {
|
|
1344
|
+
routes,
|
|
1345
|
+
navigation: defaultNavigation
|
|
1346
|
+
}, ctx);
|
|
1347
|
+
routes = resolved.routes;
|
|
1348
|
+
const navigation = resolved.navigation;
|
|
1349
|
+
ctx.routes = routes;
|
|
1350
|
+
ctx.navigation = navigation;
|
|
1351
|
+
return {
|
|
1352
|
+
root,
|
|
1353
|
+
contentRoot,
|
|
1354
|
+
projectConfig,
|
|
1355
|
+
generateOptions,
|
|
1356
|
+
plugins,
|
|
1357
|
+
ctx,
|
|
1358
|
+
routes,
|
|
1359
|
+
navigation
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
//#endregion
|
|
993
1363
|
//#region source/core/runtime-deps.ts
|
|
994
1364
|
var requireFromCli = createRequire(import.meta.url);
|
|
995
1365
|
function resolveCliDependency(id) {
|
|
@@ -1084,13 +1454,21 @@ function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
|
|
|
1084
1454
|
html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
|
|
1085
1455
|
return html;
|
|
1086
1456
|
}
|
|
1457
|
+
function isNotFoundRoute(route) {
|
|
1458
|
+
return (route.basePath ?? route.path) === "/404";
|
|
1459
|
+
}
|
|
1460
|
+
function routeOutputFiles(outputDirectory, route) {
|
|
1461
|
+
const files = [join(outputDirectory, route.path, "index.html")];
|
|
1462
|
+
if (isNotFoundRoute(route) && (!route.locale || route.path === "/404")) files.push(join(outputDirectory, "404.html"));
|
|
1463
|
+
return files;
|
|
1464
|
+
}
|
|
1087
1465
|
var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
|
|
1088
1466
|
import { routes, navigation } from 'virtual:clarify-routes/server';
|
|
1089
1467
|
import { config } from 'virtual:clarify-config';
|
|
1090
|
-
import { openApis } from 'virtual:clarify-
|
|
1468
|
+
import { openApis } from 'virtual:clarify-runtime';
|
|
1091
1469
|
|
|
1092
1470
|
export function render(url) {
|
|
1093
|
-
return renderToHTML({ config, routes, navigation, openApis, url });
|
|
1471
|
+
return renderToHTML({ config, routes, navigation, openApis, url, themeEditor: config.theme.editor });
|
|
1094
1472
|
}`;
|
|
1095
1473
|
function createTempEntryFile(content) {
|
|
1096
1474
|
const entryPath = join(mkdtempSync(join(tmpdir(), "clarify-ssr-")), "entry-server.ts");
|
|
@@ -1121,9 +1499,10 @@ async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundle
|
|
|
1121
1499
|
if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
|
|
1122
1500
|
for (const route of routes) try {
|
|
1123
1501
|
const finalHtml = injectSSRIntoTemplate(template, render(route.path), projectConfig, route);
|
|
1124
|
-
const outFile
|
|
1125
|
-
|
|
1126
|
-
|
|
1502
|
+
for (const outFile of routeOutputFiles(outputDirectory, route)) {
|
|
1503
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
1504
|
+
writeFileSync(outFile, finalHtml, "utf-8");
|
|
1505
|
+
}
|
|
1127
1506
|
} catch (err) {
|
|
1128
1507
|
console.error(`[clarify] Failed to render route "${route.path}":`, err);
|
|
1129
1508
|
if (failOnError) throw err;
|
|
@@ -1165,75 +1544,6 @@ async function loadClarifyConfig(root, env) {
|
|
|
1165
1544
|
return assertConfigObject((await loadConfigFromFile(env, configFile, root, "silent"))?.config, configFile);
|
|
1166
1545
|
}
|
|
1167
1546
|
//#endregion
|
|
1168
|
-
//#region source/core/virtual-modules.ts
|
|
1169
|
-
var VIRTUAL_CONFIG = "virtual:clarify-config";
|
|
1170
|
-
var VIRTUAL_ROUTES = "virtual:clarify-routes";
|
|
1171
|
-
var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
|
|
1172
|
-
var VIRTUAL_OPENAPI_REGISTRY = openApiRegistryModuleId;
|
|
1173
|
-
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
|
|
1174
|
-
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
|
|
1175
|
-
function resolveVirtualId(id) {
|
|
1176
|
-
return "\0" + id;
|
|
1177
|
-
}
|
|
1178
|
-
function stripVirtualPrefix(id) {
|
|
1179
|
-
return id.startsWith("\0") ? id.slice(1) : id;
|
|
1180
|
-
}
|
|
1181
|
-
function generateConfigModule(projectConfig, buildOptions) {
|
|
1182
|
-
return `export const config = ${JSON.stringify({
|
|
1183
|
-
...projectConfig,
|
|
1184
|
-
...buildOptions
|
|
1185
|
-
})};`;
|
|
1186
|
-
}
|
|
1187
|
-
function moduleSpecifier(value) {
|
|
1188
|
-
return JSON.stringify(value);
|
|
1189
|
-
}
|
|
1190
|
-
function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
|
|
1191
|
-
const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
|
|
1192
|
-
const routesArray = routes.map((r, i) => {
|
|
1193
|
-
const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
|
|
1194
|
-
id: s.id,
|
|
1195
|
-
title: s.title,
|
|
1196
|
-
badge: s.badge,
|
|
1197
|
-
tags: s.tags
|
|
1198
|
-
})))}` : "";
|
|
1199
|
-
const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
|
|
1200
|
-
const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
|
|
1201
|
-
const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
|
|
1202
|
-
const isFallback = r.isFallback ? ", isFallback: true" : "";
|
|
1203
|
-
const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
|
|
1204
|
-
const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
|
|
1205
|
-
const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
|
|
1206
|
-
const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
|
|
1207
|
-
const lazy = mode === "client" ? ", lazy: true" : "";
|
|
1208
|
-
return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: ${component}${lazy}, kind: ${JSON.stringify(r.kind)}${basePath}${locale}${isFallback}${alternates}${description}${keywords}${sections}${contentArtifactUrl} }`;
|
|
1209
|
-
}).join(",\n");
|
|
1210
|
-
const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
|
|
1211
|
-
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
1212
|
-
}
|
|
1213
|
-
function createClientEntryModule() {
|
|
1214
|
-
return `
|
|
1215
|
-
import '@clarify-labs/renderer/style.css';
|
|
1216
|
-
import { render } from '@clarify-labs/renderer/client';
|
|
1217
|
-
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
1218
|
-
import { config } from '${VIRTUAL_CONFIG}';
|
|
1219
|
-
import { openApis } from '${VIRTUAL_OPENAPI_REGISTRY}';
|
|
1220
|
-
render({ config, routes, navigation, openApis });`;
|
|
1221
|
-
}
|
|
1222
|
-
function buildVirtualModules(args) {
|
|
1223
|
-
const modules = /* @__PURE__ */ new Map();
|
|
1224
|
-
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
1225
|
-
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
|
|
1226
|
-
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
|
|
1227
|
-
modules.set(VIRTUAL_OPENAPI_REGISTRY, "export const openApis = {};");
|
|
1228
|
-
modules.set(VIRTUAL_CLIENT_ENTRY, createClientEntryModule());
|
|
1229
|
-
modules.set(RESOLVED_CLIENT_ENTRY, createClientEntryModule());
|
|
1230
|
-
for (const route of args.routes) {
|
|
1231
|
-
if (route.kind === "openapi") continue;
|
|
1232
|
-
modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
|
|
1233
|
-
}
|
|
1234
|
-
return modules;
|
|
1235
|
-
}
|
|
1236
|
-
//#endregion
|
|
1237
1547
|
//#region source/core/plugin.ts
|
|
1238
1548
|
function loadVirtualModule(id, modules) {
|
|
1239
1549
|
const bareId = stripVirtualPrefix(id);
|
|
@@ -1246,12 +1556,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1246
1556
|
const contentRoot = join(root, generateOptions.rootDirectory);
|
|
1247
1557
|
const configFilePath = findClarifyConfigFile(root);
|
|
1248
1558
|
let routes = [];
|
|
1249
|
-
|
|
1250
|
-
createOpenAPIPlugin(),
|
|
1251
|
-
createContentArtifactsPlugin(),
|
|
1252
|
-
createHtmlShellPlugin(),
|
|
1253
|
-
...options.plugins ?? []
|
|
1254
|
-
];
|
|
1559
|
+
let clarifyPlugins = [...createBuiltinPlugins(), ...options.plugins ?? []];
|
|
1255
1560
|
const ctx = {
|
|
1256
1561
|
projectConfig,
|
|
1257
1562
|
generateOptions,
|
|
@@ -1276,71 +1581,15 @@ function clarifyPlugin(options = {}) {
|
|
|
1276
1581
|
ctx.projectConfig = projectConfig;
|
|
1277
1582
|
ctx.generateOptions = generateOptions;
|
|
1278
1583
|
}
|
|
1279
|
-
function withAlternates(route, routeList, i18n) {
|
|
1280
|
-
const basePath = route.basePath ?? route.path;
|
|
1281
|
-
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1282
|
-
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1283
|
-
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1284
|
-
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1285
|
-
}));
|
|
1286
|
-
return {
|
|
1287
|
-
...route,
|
|
1288
|
-
alternates
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
async function discoverRoutesForRoot(routeRoot, locale) {
|
|
1292
|
-
return (await runHooks(clarifyPlugins, "routes:discover", {
|
|
1293
|
-
contentRoot: routeRoot,
|
|
1294
|
-
locale,
|
|
1295
|
-
routes: findContentRoutes(routeRoot)
|
|
1296
|
-
}, ctx)).routes;
|
|
1297
|
-
}
|
|
1298
|
-
async function discoverRoutes() {
|
|
1299
|
-
const i18n = projectConfig.i18n;
|
|
1300
|
-
if (!i18n) return discoverRoutesForRoot(contentRoot);
|
|
1301
|
-
const localizedRoutes = [];
|
|
1302
|
-
for (const locale of i18n.locales) {
|
|
1303
|
-
const discovered = await discoverRoutesForRoot(join(contentRoot, locale.code), locale.code);
|
|
1304
|
-
for (const route of discovered) {
|
|
1305
|
-
const basePath = route.basePath ?? route.path;
|
|
1306
|
-
localizedRoutes.push({
|
|
1307
|
-
...route,
|
|
1308
|
-
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1309
|
-
basePath,
|
|
1310
|
-
locale: locale.code,
|
|
1311
|
-
virtualModuleId: virtualModuleIdFromRef(relative(contentRoot, route.filePath))
|
|
1312
|
-
});
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
if (i18n.missing === "fallback") {
|
|
1316
|
-
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1317
|
-
const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
|
|
1318
|
-
for (const sourceRoute of defaultRoutes) {
|
|
1319
|
-
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
1320
|
-
for (const locale of i18n.locales) {
|
|
1321
|
-
const key = `${locale.code}:${basePath}`;
|
|
1322
|
-
if (routeByLocaleAndBase.has(key)) continue;
|
|
1323
|
-
localizedRoutes.push({
|
|
1324
|
-
...sourceRoute,
|
|
1325
|
-
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1326
|
-
locale: locale.code,
|
|
1327
|
-
isFallback: true
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
return localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1333
|
-
}
|
|
1334
1584
|
async function resolveRoutesAndSpecs() {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
resolvedNavigation = resolved.navigation;
|
|
1585
|
+
const site = await resolveClarifySite(options);
|
|
1586
|
+
projectConfig = site.projectConfig;
|
|
1587
|
+
generateOptions = site.generateOptions;
|
|
1588
|
+
routes = site.routes;
|
|
1589
|
+
resolvedNavigation = site.navigation;
|
|
1590
|
+
clarifyPlugins = site.plugins;
|
|
1591
|
+
ctx.projectConfig = projectConfig;
|
|
1592
|
+
ctx.generateOptions = generateOptions;
|
|
1344
1593
|
ctx.routes = routes;
|
|
1345
1594
|
ctx.navigation = resolvedNavigation;
|
|
1346
1595
|
}
|
|
@@ -1349,7 +1598,8 @@ function clarifyPlugin(options = {}) {
|
|
|
1349
1598
|
projectConfig,
|
|
1350
1599
|
generateOptions,
|
|
1351
1600
|
routes,
|
|
1352
|
-
navigation: resolvedNavigation
|
|
1601
|
+
navigation: resolvedNavigation,
|
|
1602
|
+
themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor
|
|
1353
1603
|
});
|
|
1354
1604
|
virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
|
|
1355
1605
|
}
|
|
@@ -1429,8 +1679,9 @@ function clarifyPlugin(options = {}) {
|
|
|
1429
1679
|
if (id === "virtual:clarify-config" || id === resolveVirtualId("virtual:clarify-config")) return resolveVirtualId(VIRTUAL_CONFIG);
|
|
1430
1680
|
if (id === "virtual:clarify-routes" || id === resolveVirtualId("virtual:clarify-routes")) return resolveVirtualId(VIRTUAL_ROUTES);
|
|
1431
1681
|
if (id === "virtual:clarify-routes/server" || id === resolveVirtualId("virtual:clarify-routes/server")) return resolveVirtualId(VIRTUAL_SERVER_ROUTES);
|
|
1432
|
-
|
|
1433
|
-
|
|
1682
|
+
const moduleId = stripVirtualPrefix(id);
|
|
1683
|
+
if (virtualModules.has(moduleId)) return resolveVirtualId(moduleId);
|
|
1684
|
+
const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === moduleId);
|
|
1434
1685
|
if (route) return resolveVirtualId(route.virtualModuleId);
|
|
1435
1686
|
return null;
|
|
1436
1687
|
},
|
|
@@ -1452,10 +1703,11 @@ function clarifyPlugin(options = {}) {
|
|
|
1452
1703
|
transformIndexHtml: {
|
|
1453
1704
|
order: "pre",
|
|
1454
1705
|
async handler(html, transformCtx) {
|
|
1706
|
+
const clientEntryId = transformCtx.server ? `/@id/${VIRTUAL_CLIENT_ENTRY}` : VIRTUAL_CLIENT_ENTRY;
|
|
1455
1707
|
const result = await runHooks(clarifyPlugins, "html:transform", {
|
|
1456
1708
|
html,
|
|
1457
1709
|
tags: [],
|
|
1458
|
-
clientEntryId
|
|
1710
|
+
clientEntryId,
|
|
1459
1711
|
dev: Boolean(transformCtx.server)
|
|
1460
1712
|
}, ctx);
|
|
1461
1713
|
return {
|
|
@@ -1483,7 +1735,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1483
1735
|
if (id === "virtual:clarify-config") return id;
|
|
1484
1736
|
if (id === "virtual:clarify-routes") return id;
|
|
1485
1737
|
if (id === "virtual:clarify-routes/server") return id;
|
|
1486
|
-
if (id
|
|
1738
|
+
if (virtualModules.has(stripVirtualPrefix(id))) return stripVirtualPrefix(id);
|
|
1487
1739
|
if (routes.find((r) => r.virtualModuleId === id)) return id;
|
|
1488
1740
|
return null;
|
|
1489
1741
|
},
|
|
@@ -1509,6 +1761,6 @@ function clarifyPlugin(options = {}) {
|
|
|
1509
1761
|
].flat().filter(Boolean);
|
|
1510
1762
|
}
|
|
1511
1763
|
//#endregion
|
|
1512
|
-
export { createClarifyRuntimeAliases as i, defineConfig as n, loadClarifyConfig as r, clarifyPlugin as t };
|
|
1764
|
+
export { resolveClarifySite as a, createClarifyRuntimeAliases as i, defineConfig as n, loadClarifyConfig as r, clarifyPlugin as t };
|
|
1513
1765
|
|
|
1514
|
-
//# sourceMappingURL=plugin-
|
|
1766
|
+
//# sourceMappingURL=plugin-DC2f5YAH.js.map
|