@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
|
@@ -40,6 +40,7 @@ let gray_matter = require("gray-matter");
|
|
|
40
40
|
gray_matter = __toESM(gray_matter, 1);
|
|
41
41
|
let _apidevtools_swagger_parser = require("@apidevtools/swagger-parser");
|
|
42
42
|
_apidevtools_swagger_parser = __toESM(_apidevtools_swagger_parser, 1);
|
|
43
|
+
let node_fs_promises = require("node:fs/promises");
|
|
43
44
|
let zod = require("zod");
|
|
44
45
|
let node_os = require("node:os");
|
|
45
46
|
let node_url = require("node:url");
|
|
@@ -76,6 +77,11 @@ function rehypeParseCodeBlocks() {
|
|
|
76
77
|
const language = getLanguage(node.properties.className);
|
|
77
78
|
parentNode.properties.language = language;
|
|
78
79
|
node.properties.language = language;
|
|
80
|
+
for (const key of [
|
|
81
|
+
"title",
|
|
82
|
+
"tag",
|
|
83
|
+
"label"
|
|
84
|
+
]) if (node.properties[key] !== void 0) parentNode.properties[key] = node.properties[key];
|
|
79
85
|
});
|
|
80
86
|
};
|
|
81
87
|
}
|
|
@@ -129,6 +135,191 @@ var rehypePlugins = [
|
|
|
129
135
|
rehypeShiki
|
|
130
136
|
];
|
|
131
137
|
//#endregion
|
|
138
|
+
//#region source/plugins/content-artifacts/artifacts.ts
|
|
139
|
+
var UTF8_SIGNATURE = "";
|
|
140
|
+
function normalizeBasePath(basePath) {
|
|
141
|
+
if (!basePath || basePath === "/") return "";
|
|
142
|
+
return "/" + basePath.replace(/^\/+|\/+$/g, "");
|
|
143
|
+
}
|
|
144
|
+
function containsNonAscii(content) {
|
|
145
|
+
return [...content].some((character) => character.codePointAt(0) > 127);
|
|
146
|
+
}
|
|
147
|
+
function withUtf8Signature(content) {
|
|
148
|
+
if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
|
|
149
|
+
return `${UTF8_SIGNATURE}${content}`;
|
|
150
|
+
}
|
|
151
|
+
function shouldUseUtf8Signature(route) {
|
|
152
|
+
if (route.kind === "mdx") return true;
|
|
153
|
+
return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
|
|
154
|
+
}
|
|
155
|
+
function routeToMarkdownArtifactUrl(routePath) {
|
|
156
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
157
|
+
}
|
|
158
|
+
function routeToOpenAPIArtifactUrl(routePath, filePath) {
|
|
159
|
+
const extension = (0, node_path.extname)(filePath).toLowerCase() || ".json";
|
|
160
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
161
|
+
}
|
|
162
|
+
function attachContentArtifactUrls(routes) {
|
|
163
|
+
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
|
|
164
|
+
}
|
|
165
|
+
function readRouteContent(route) {
|
|
166
|
+
if (route.content !== void 0) return route.content;
|
|
167
|
+
throw new Error(`Route content is missing from route context: ${route.filePath}`);
|
|
168
|
+
}
|
|
169
|
+
function readRouteArtifactContent(route) {
|
|
170
|
+
const content = readRouteContent(route);
|
|
171
|
+
return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
|
|
172
|
+
}
|
|
173
|
+
function writeContentArtifactFiles(routes, outputDirectory) {
|
|
174
|
+
for (const route of routes) {
|
|
175
|
+
if (!route.contentArtifactUrl) continue;
|
|
176
|
+
const outFile = (0, node_path.join)(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
|
|
177
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
|
|
178
|
+
(0, node_fs.writeFileSync)(outFile, readRouteArtifactContent(route), "utf-8");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function createLlmsTxt(routes, projectConfig) {
|
|
182
|
+
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
183
|
+
const lines = [`# ${projectConfig.title}`, ""];
|
|
184
|
+
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
185
|
+
lines.push("## Docs");
|
|
186
|
+
for (const route of routes.filter((route) => route.kind === "mdx")) {
|
|
187
|
+
if (!route.contentArtifactUrl) continue;
|
|
188
|
+
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
189
|
+
}
|
|
190
|
+
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
191
|
+
if (openApiRoutes.length > 0) {
|
|
192
|
+
lines.push("", "## OpenAPI");
|
|
193
|
+
for (const route of openApiRoutes) {
|
|
194
|
+
if (!route.contentArtifactUrl) continue;
|
|
195
|
+
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return `${lines.join("\n")}\n`;
|
|
199
|
+
}
|
|
200
|
+
function createLlmsTxtArtifact(routes, projectConfig) {
|
|
201
|
+
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
202
|
+
}
|
|
203
|
+
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
204
|
+
(0, node_fs.mkdirSync)(outputDirectory, { recursive: true });
|
|
205
|
+
(0, node_fs.writeFileSync)((0, node_path.join)(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region source/plugins/content-artifacts/server.ts
|
|
209
|
+
function normalizeRoutePrefix$1(routePrefix) {
|
|
210
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
211
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
212
|
+
}
|
|
213
|
+
function resolveContentArtifactPath(url, projectConfig) {
|
|
214
|
+
const requestPath = url?.split("?")[0] ?? "";
|
|
215
|
+
const basePath = normalizeRoutePrefix$1(projectConfig.routePrefix);
|
|
216
|
+
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
217
|
+
}
|
|
218
|
+
function resolveContentArtifactType(route) {
|
|
219
|
+
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
220
|
+
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
221
|
+
}
|
|
222
|
+
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
223
|
+
const contentPath = resolveContentArtifactPath(req.url, projectConfig);
|
|
224
|
+
if (contentPath === "/llms.txt") {
|
|
225
|
+
res.statusCode = 200;
|
|
226
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
227
|
+
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
const route = routes.find((route) => route.contentArtifactUrl === contentPath);
|
|
231
|
+
if (!route) return false;
|
|
232
|
+
res.statusCode = 200;
|
|
233
|
+
res.setHeader("Content-Type", resolveContentArtifactType(route));
|
|
234
|
+
res.end(readRouteArtifactContent(route), "utf8");
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region source/plugins/content-artifacts/index.ts
|
|
239
|
+
function createContentArtifactsPlugin() {
|
|
240
|
+
return {
|
|
241
|
+
name: "clarify:content-artifacts",
|
|
242
|
+
hooks: {
|
|
243
|
+
"routes:resolved": (input) => {
|
|
244
|
+
attachContentArtifactUrls(input.routes);
|
|
245
|
+
return input;
|
|
246
|
+
},
|
|
247
|
+
"dev:configureServer": (server, ctx) => {
|
|
248
|
+
server.middlewares.use((req, res, next) => {
|
|
249
|
+
if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
|
|
250
|
+
next();
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
"build:done": (ctx) => {
|
|
254
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
255
|
+
if (!outputDirectory) return;
|
|
256
|
+
const outputRoot = (0, node_path.join)(ctx.generateOptions.projectRoot, outputDirectory);
|
|
257
|
+
writeContentArtifactFiles(ctx.routes, outputRoot);
|
|
258
|
+
writeLlmsTxt(ctx.routes, ctx.projectConfig, outputRoot);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region source/plugins/html-shell/index.ts
|
|
265
|
+
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){}})();`;
|
|
266
|
+
var REL_ICON_RE = /<link\s+[^>]*rel=["'][^"']*\bicon\b[^"']*["'][^>]*>/i;
|
|
267
|
+
function inferIconType(href) {
|
|
268
|
+
const path = href.split(/[?#]/, 1)[0]?.toLowerCase() ?? href.toLowerCase();
|
|
269
|
+
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
270
|
+
if (path.endsWith(".png")) return "image/png";
|
|
271
|
+
if (path.endsWith(".ico")) return "image/x-icon";
|
|
272
|
+
if (path.endsWith(".webp")) return "image/webp";
|
|
273
|
+
}
|
|
274
|
+
function createIconTag(href, media) {
|
|
275
|
+
return {
|
|
276
|
+
tag: "link",
|
|
277
|
+
attrs: {
|
|
278
|
+
rel: "icon",
|
|
279
|
+
...inferIconType(href) ? { type: inferIconType(href) } : {},
|
|
280
|
+
href,
|
|
281
|
+
...media ? { media } : {}
|
|
282
|
+
},
|
|
283
|
+
injectTo: "head"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function createIconTags(icon) {
|
|
287
|
+
if (!icon) return [];
|
|
288
|
+
if (typeof icon === "string") return [createIconTag(icon)];
|
|
289
|
+
const lightIcon = icon.light ?? icon.dark;
|
|
290
|
+
const darkIcon = icon.dark ?? icon.light;
|
|
291
|
+
if (!lightIcon || !darkIcon) return [];
|
|
292
|
+
if (lightIcon === darkIcon) return [createIconTag(lightIcon)];
|
|
293
|
+
return [createIconTag(lightIcon, "(prefers-color-scheme: light)"), createIconTag(darkIcon, "(prefers-color-scheme: dark)")];
|
|
294
|
+
}
|
|
295
|
+
function createHtmlShellPlugin() {
|
|
296
|
+
return {
|
|
297
|
+
name: "clarify:html-shell",
|
|
298
|
+
hooks: { "html:transform": (input, ctx) => ({
|
|
299
|
+
html: input.html,
|
|
300
|
+
tags: [
|
|
301
|
+
...input.tags,
|
|
302
|
+
...REL_ICON_RE.test(input.html) ? [] : createIconTags(ctx.projectConfig.favicon ?? ctx.projectConfig.logo),
|
|
303
|
+
{
|
|
304
|
+
tag: "script",
|
|
305
|
+
children: THEME_BOOTSTRAP_SCRIPT,
|
|
306
|
+
injectTo: "head-prepend"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
tag: "script",
|
|
310
|
+
attrs: {
|
|
311
|
+
type: "module",
|
|
312
|
+
src: input.clientEntryId
|
|
313
|
+
},
|
|
314
|
+
injectTo: "body"
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
clientEntryId: input.clientEntryId,
|
|
318
|
+
dev: input.dev
|
|
319
|
+
}) }
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
132
323
|
//#region source/parsers/frontmatter.ts
|
|
133
324
|
/**
|
|
134
325
|
* 在内容入口统一解析 MDX frontmatter,并返回可供后续流程复用的标准化正文。
|
|
@@ -184,10 +375,21 @@ function normalizePath(path) {
|
|
|
184
375
|
const clean = "/" + path.replace(/^\/+|\/+$/g, "");
|
|
185
376
|
return clean === "/" ? "/" : clean.replace(/\/+/g, "/");
|
|
186
377
|
}
|
|
378
|
+
function pathSegmentFromOpenAPITag(tag) {
|
|
379
|
+
return tag.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tag";
|
|
380
|
+
}
|
|
381
|
+
function openAPITagsPathSegment(tags) {
|
|
382
|
+
return tags.map(pathSegmentFromOpenAPITag).join("-");
|
|
383
|
+
}
|
|
187
384
|
function basePathFromRef(ref) {
|
|
188
385
|
const withoutExtension = ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "");
|
|
189
386
|
return normalizePath(withoutExtension === "index" ? "/" : withoutExtension.replace(/\/index$/, ""));
|
|
190
387
|
}
|
|
388
|
+
function openAPIPagePathFromRef(ref, tagFilter) {
|
|
389
|
+
const basePath = basePathFromRef(ref);
|
|
390
|
+
if (!tagFilter?.length) return basePath;
|
|
391
|
+
return normalizePath(`${basePath}/${openAPITagsPathSegment(tagFilter)}`);
|
|
392
|
+
}
|
|
191
393
|
function routePathFromRef(ref) {
|
|
192
394
|
return basePathFromRef(ref);
|
|
193
395
|
}
|
|
@@ -246,7 +448,7 @@ function frontmatterKeywords(frontmatter) {
|
|
|
246
448
|
function buildNavigation(routes) {
|
|
247
449
|
const root = [];
|
|
248
450
|
for (const route of routes) {
|
|
249
|
-
if (route.path === "/") continue;
|
|
451
|
+
if (route.path === "/" || (route.basePath ?? route.path) === "/404") continue;
|
|
250
452
|
const parts = route.path.replace(/^\//, "").split("/");
|
|
251
453
|
let current = root;
|
|
252
454
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -273,6 +475,7 @@ function resolvePageItem(item) {
|
|
|
273
475
|
if (typeof item === "string") return { pageRef: item };
|
|
274
476
|
if ("openapi" in item) return {
|
|
275
477
|
openapiRef: item.openapi,
|
|
478
|
+
openapiTagFilter: item.filter?.tags,
|
|
276
479
|
title: item.title,
|
|
277
480
|
icon: item.icon
|
|
278
481
|
};
|
|
@@ -311,9 +514,9 @@ function buildNavigationFromConfig(routes, config) {
|
|
|
311
514
|
const routeMap = new Map(routes.map((r) => [r.path, r]));
|
|
312
515
|
return config.map((group) => {
|
|
313
516
|
const children = group.pages.map((item) => {
|
|
314
|
-
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
517
|
+
const { pageRef, openapiRef, openapiTagFilter, redirect, title, icon } = resolvePageItem(item);
|
|
315
518
|
if (openapiRef) {
|
|
316
|
-
const path =
|
|
519
|
+
const path = openAPIPagePathFromRef(openapiRef, openapiTagFilter);
|
|
317
520
|
const route = routeMap.get(path);
|
|
318
521
|
return {
|
|
319
522
|
path,
|
|
@@ -355,227 +558,43 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
|
|
|
355
558
|
const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
|
|
356
559
|
return config.map((group) => {
|
|
357
560
|
const children = group.pages.map((item) => {
|
|
358
|
-
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
561
|
+
const { pageRef, openapiRef, openapiTagFilter, redirect, title, icon } = resolvePageItem(item);
|
|
359
562
|
const ref = openapiRef ?? pageRef ?? "";
|
|
360
|
-
const basePath = basePathFromRef(ref);
|
|
563
|
+
const basePath = openapiRef ? openAPIPagePathFromRef(openapiRef, openapiTagFilter) : basePathFromRef(ref);
|
|
361
564
|
const route = routeMap.get(basePath);
|
|
362
565
|
const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
|
|
363
566
|
return {
|
|
364
567
|
path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale, i18n) : void 0) ?? path,
|
|
365
568
|
title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
|
|
366
569
|
icon,
|
|
367
|
-
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
368
|
-
};
|
|
369
|
-
});
|
|
370
|
-
return {
|
|
371
|
-
path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
|
|
372
|
-
title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
|
|
373
|
-
icon: group.icon,
|
|
374
|
-
children
|
|
375
|
-
};
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
|
|
379
|
-
if (!i18n) return void 0;
|
|
380
|
-
const result = {};
|
|
381
|
-
for (const locale of i18n.locales) {
|
|
382
|
-
const localeRoutes = routes.filter((route) => route.locale === locale.code);
|
|
383
|
-
result[locale.code] = { tabs: tabs.map((tab) => {
|
|
384
|
-
const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
|
|
385
|
-
return {
|
|
386
|
-
type: "tab",
|
|
387
|
-
path: firstNavigationPath(children),
|
|
388
|
-
title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
|
|
389
|
-
icon: tab.icon,
|
|
390
|
-
children
|
|
391
|
-
};
|
|
392
|
-
}) };
|
|
393
|
-
}
|
|
394
|
-
return result;
|
|
395
|
-
}
|
|
396
|
-
//#endregion
|
|
397
|
-
//#region source/plugins/content-artifacts/artifacts.ts
|
|
398
|
-
var UTF8_SIGNATURE = "";
|
|
399
|
-
function normalizeBasePath(basePath) {
|
|
400
|
-
if (!basePath || basePath === "/") return "";
|
|
401
|
-
return "/" + basePath.replace(/^\/+|\/+$/g, "");
|
|
402
|
-
}
|
|
403
|
-
function containsNonAscii(content) {
|
|
404
|
-
return [...content].some((character) => character.codePointAt(0) > 127);
|
|
405
|
-
}
|
|
406
|
-
function withUtf8Signature(content) {
|
|
407
|
-
if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
|
|
408
|
-
return `${UTF8_SIGNATURE}${content}`;
|
|
409
|
-
}
|
|
410
|
-
function shouldUseUtf8Signature(route) {
|
|
411
|
-
if (route.kind === "mdx") return true;
|
|
412
|
-
return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
|
|
413
|
-
}
|
|
414
|
-
function routeToMarkdownArtifactUrl(routePath) {
|
|
415
|
-
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
416
|
-
}
|
|
417
|
-
function routeToOpenAPIArtifactUrl(routePath, filePath) {
|
|
418
|
-
const extension = (0, node_path.extname)(filePath).toLowerCase() || ".json";
|
|
419
|
-
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
420
|
-
}
|
|
421
|
-
function attachContentArtifactUrls(routes) {
|
|
422
|
-
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
|
|
423
|
-
}
|
|
424
|
-
function readRouteContent(route) {
|
|
425
|
-
if (route.content !== void 0) return route.content;
|
|
426
|
-
throw new Error(`Route content is missing from route context: ${route.filePath}`);
|
|
427
|
-
}
|
|
428
|
-
function readRouteArtifactContent(route) {
|
|
429
|
-
const content = readRouteContent(route);
|
|
430
|
-
return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
|
|
431
|
-
}
|
|
432
|
-
function writeContentArtifactFiles(routes, outputDirectory) {
|
|
433
|
-
for (const route of routes) {
|
|
434
|
-
if (!route.contentArtifactUrl) continue;
|
|
435
|
-
const outFile = (0, node_path.join)(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
|
|
436
|
-
(0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
|
|
437
|
-
(0, node_fs.writeFileSync)(outFile, readRouteArtifactContent(route), "utf-8");
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
function createLlmsTxt(routes, projectConfig) {
|
|
441
|
-
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
442
|
-
const lines = [`# ${projectConfig.title}`, ""];
|
|
443
|
-
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
444
|
-
lines.push("## Docs");
|
|
445
|
-
for (const route of routes.filter((route) => route.kind === "mdx")) {
|
|
446
|
-
if (!route.contentArtifactUrl) continue;
|
|
447
|
-
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
448
|
-
}
|
|
449
|
-
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
450
|
-
if (openApiRoutes.length > 0) {
|
|
451
|
-
lines.push("", "## OpenAPI");
|
|
452
|
-
for (const route of openApiRoutes) {
|
|
453
|
-
if (!route.contentArtifactUrl) continue;
|
|
454
|
-
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return `${lines.join("\n")}\n`;
|
|
458
|
-
}
|
|
459
|
-
function createLlmsTxtArtifact(routes, projectConfig) {
|
|
460
|
-
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
461
|
-
}
|
|
462
|
-
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
463
|
-
(0, node_fs.mkdirSync)(outputDirectory, { recursive: true });
|
|
464
|
-
(0, node_fs.writeFileSync)((0, node_path.join)(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
|
|
465
|
-
}
|
|
466
|
-
//#endregion
|
|
467
|
-
//#region source/plugins/content-artifacts/server.ts
|
|
468
|
-
function normalizeRoutePrefix(routePrefix) {
|
|
469
|
-
if (!routePrefix || routePrefix === "/") return "";
|
|
470
|
-
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
471
|
-
}
|
|
472
|
-
function resolveContentArtifactPath(url, projectConfig) {
|
|
473
|
-
const requestPath = url?.split("?")[0] ?? "";
|
|
474
|
-
const basePath = normalizeRoutePrefix(projectConfig.routePrefix);
|
|
475
|
-
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
476
|
-
}
|
|
477
|
-
function resolveContentArtifactType(route) {
|
|
478
|
-
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
479
|
-
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
480
|
-
}
|
|
481
|
-
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
482
|
-
const contentPath = resolveContentArtifactPath(req.url, projectConfig);
|
|
483
|
-
if (contentPath === "/llms.txt") {
|
|
484
|
-
res.statusCode = 200;
|
|
485
|
-
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
486
|
-
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
487
|
-
return true;
|
|
488
|
-
}
|
|
489
|
-
const route = routes.find((route) => route.contentArtifactUrl === contentPath);
|
|
490
|
-
if (!route) return false;
|
|
491
|
-
res.statusCode = 200;
|
|
492
|
-
res.setHeader("Content-Type", resolveContentArtifactType(route));
|
|
493
|
-
res.end(readRouteArtifactContent(route), "utf8");
|
|
494
|
-
return true;
|
|
495
|
-
}
|
|
496
|
-
//#endregion
|
|
497
|
-
//#region source/plugins/content-artifacts/index.ts
|
|
498
|
-
function createContentArtifactsPlugin() {
|
|
499
|
-
return {
|
|
500
|
-
name: "clarify:content-artifacts",
|
|
501
|
-
hooks: {
|
|
502
|
-
"routes:resolved": (input) => {
|
|
503
|
-
attachContentArtifactUrls(input.routes);
|
|
504
|
-
return input;
|
|
505
|
-
},
|
|
506
|
-
"dev:configureServer": (server, ctx) => {
|
|
507
|
-
server.middlewares.use((req, res, next) => {
|
|
508
|
-
if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
|
|
509
|
-
next();
|
|
510
|
-
});
|
|
511
|
-
},
|
|
512
|
-
"build:done": (ctx) => {
|
|
513
|
-
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
514
|
-
if (!outputDirectory) return;
|
|
515
|
-
writeContentArtifactFiles(ctx.routes, outputDirectory);
|
|
516
|
-
writeLlmsTxt(ctx.routes, ctx.projectConfig, outputDirectory);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
//#endregion
|
|
522
|
-
//#region source/plugins/html-shell/index.ts
|
|
523
|
-
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){}})();`;
|
|
524
|
-
var REL_ICON_RE = /<link\s+[^>]*rel=["'][^"']*\bicon\b[^"']*["'][^>]*>/i;
|
|
525
|
-
function inferIconType(href) {
|
|
526
|
-
const path = href.split(/[?#]/, 1)[0]?.toLowerCase() ?? href.toLowerCase();
|
|
527
|
-
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
528
|
-
if (path.endsWith(".png")) return "image/png";
|
|
529
|
-
if (path.endsWith(".ico")) return "image/x-icon";
|
|
530
|
-
if (path.endsWith(".webp")) return "image/webp";
|
|
531
|
-
}
|
|
532
|
-
function createIconTag(href, media) {
|
|
533
|
-
return {
|
|
534
|
-
tag: "link",
|
|
535
|
-
attrs: {
|
|
536
|
-
rel: "icon",
|
|
537
|
-
...inferIconType(href) ? { type: inferIconType(href) } : {},
|
|
538
|
-
href,
|
|
539
|
-
...media ? { media } : {}
|
|
540
|
-
},
|
|
541
|
-
injectTo: "head"
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
function createIconTags(icon) {
|
|
545
|
-
if (!icon) return [];
|
|
546
|
-
if (typeof icon === "string") return [createIconTag(icon)];
|
|
547
|
-
const lightIcon = icon.light ?? icon.dark;
|
|
548
|
-
const darkIcon = icon.dark ?? icon.light;
|
|
549
|
-
if (!lightIcon || !darkIcon) return [];
|
|
550
|
-
if (lightIcon === darkIcon) return [createIconTag(lightIcon)];
|
|
551
|
-
return [createIconTag(lightIcon, "(prefers-color-scheme: light)"), createIconTag(darkIcon, "(prefers-color-scheme: dark)")];
|
|
552
|
-
}
|
|
553
|
-
function createHtmlShellPlugin() {
|
|
554
|
-
return {
|
|
555
|
-
name: "clarify:html-shell",
|
|
556
|
-
hooks: { "html:transform": (input, ctx) => ({
|
|
557
|
-
html: input.html,
|
|
558
|
-
tags: [
|
|
559
|
-
...input.tags,
|
|
560
|
-
...REL_ICON_RE.test(input.html) ? [] : createIconTags(ctx.projectConfig.favicon ?? ctx.projectConfig.logo),
|
|
561
|
-
{
|
|
562
|
-
tag: "script",
|
|
563
|
-
children: THEME_BOOTSTRAP_SCRIPT,
|
|
564
|
-
injectTo: "head-prepend"
|
|
565
|
-
},
|
|
566
|
-
{
|
|
567
|
-
tag: "script",
|
|
568
|
-
attrs: {
|
|
569
|
-
type: "module",
|
|
570
|
-
src: input.clientEntryId
|
|
571
|
-
},
|
|
572
|
-
injectTo: "body"
|
|
573
|
-
}
|
|
574
|
-
],
|
|
575
|
-
clientEntryId: input.clientEntryId,
|
|
576
|
-
dev: input.dev
|
|
577
|
-
}) }
|
|
578
|
-
};
|
|
570
|
+
sections: route?.sections ? navigationSections(route.sections) : void 0
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
return {
|
|
574
|
+
path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
|
|
575
|
+
title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
|
|
576
|
+
icon: group.icon,
|
|
577
|
+
children
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
|
|
582
|
+
if (!i18n) return void 0;
|
|
583
|
+
const result = {};
|
|
584
|
+
for (const locale of i18n.locales) {
|
|
585
|
+
const localeRoutes = routes.filter((route) => route.locale === locale.code);
|
|
586
|
+
result[locale.code] = { tabs: tabs.map((tab) => {
|
|
587
|
+
const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
|
|
588
|
+
return {
|
|
589
|
+
type: "tab",
|
|
590
|
+
path: firstNavigationPath(children),
|
|
591
|
+
title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
|
|
592
|
+
icon: tab.icon,
|
|
593
|
+
children
|
|
594
|
+
};
|
|
595
|
+
}) };
|
|
596
|
+
}
|
|
597
|
+
return result;
|
|
579
598
|
}
|
|
580
599
|
//#endregion
|
|
581
600
|
//#region source/plugins/openapi/parser.ts
|
|
@@ -620,21 +639,26 @@ function findOpenAPIRoutes(dir, base = dir) {
|
|
|
620
639
|
}
|
|
621
640
|
return routes;
|
|
622
641
|
}
|
|
642
|
+
function operationMatchesTags(operationTags, filterTags) {
|
|
643
|
+
if (!filterTags?.length) return true;
|
|
644
|
+
return operationTags?.some((tag) => filterTags.includes(tag)) ?? false;
|
|
645
|
+
}
|
|
623
646
|
/** Extract endpoint operations from an OpenAPI spec as page sections. */
|
|
624
|
-
function extractOpenAPISections(spec) {
|
|
647
|
+
function extractOpenAPISections(spec, filterTags) {
|
|
625
648
|
const sections = [];
|
|
626
649
|
const paths = spec.paths ?? {};
|
|
627
650
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
628
651
|
if (!pathItem) continue;
|
|
629
652
|
for (const method of OPENAPI_HTTP_METHODS) {
|
|
630
653
|
const op = pathItem[method];
|
|
631
|
-
if (!op) continue;
|
|
654
|
+
if (!op || !operationMatchesTags(op.tags, filterTags)) continue;
|
|
632
655
|
const title = op.summary ?? `${method.toUpperCase()} ${path}`;
|
|
633
656
|
sections.push({
|
|
634
657
|
id: (0, github_slugger.slug)(`${method} ${path}`),
|
|
635
658
|
title,
|
|
636
659
|
level: 2,
|
|
637
|
-
badge: method.toUpperCase()
|
|
660
|
+
badge: method.toUpperCase(),
|
|
661
|
+
tags: op.tags
|
|
638
662
|
});
|
|
639
663
|
}
|
|
640
664
|
}
|
|
@@ -659,17 +683,89 @@ async function readOpenAPISpec(filePath) {
|
|
|
659
683
|
}
|
|
660
684
|
}
|
|
661
685
|
//#endregion
|
|
686
|
+
//#region source/core/virtual-modules.ts
|
|
687
|
+
var VIRTUAL_CONFIG = "virtual:clarify-config";
|
|
688
|
+
var VIRTUAL_ROUTES = "virtual:clarify-routes";
|
|
689
|
+
var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
|
|
690
|
+
var VIRTUAL_RUNTIME = "virtual:clarify-runtime";
|
|
691
|
+
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
|
|
692
|
+
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
|
|
693
|
+
function resolveVirtualId(id) {
|
|
694
|
+
return "\0" + id;
|
|
695
|
+
}
|
|
696
|
+
function stripVirtualPrefix(id) {
|
|
697
|
+
return id.startsWith("\0") ? id.slice(1) : id;
|
|
698
|
+
}
|
|
699
|
+
function generateConfigModule(projectConfig, buildOptions) {
|
|
700
|
+
return `export const config = ${JSON.stringify({
|
|
701
|
+
...projectConfig,
|
|
702
|
+
...buildOptions
|
|
703
|
+
})};`;
|
|
704
|
+
}
|
|
705
|
+
function moduleSpecifier(value) {
|
|
706
|
+
return JSON.stringify(value);
|
|
707
|
+
}
|
|
708
|
+
function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
|
|
709
|
+
const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
|
|
710
|
+
const routesArray = routes.map((r, i) => {
|
|
711
|
+
const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
|
|
712
|
+
id: s.id,
|
|
713
|
+
title: s.title,
|
|
714
|
+
badge: s.badge,
|
|
715
|
+
tags: s.tags
|
|
716
|
+
})))}` : "";
|
|
717
|
+
const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
|
|
718
|
+
const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
|
|
719
|
+
const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
|
|
720
|
+
const isFallback = r.isFallback ? ", isFallback: true" : "";
|
|
721
|
+
const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
|
|
722
|
+
const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
|
|
723
|
+
const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
|
|
724
|
+
const sourceUrl = r.sourceUrl ? `, sourceUrl: ${JSON.stringify(r.sourceUrl)}` : "";
|
|
725
|
+
const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
|
|
726
|
+
const lazy = mode === "client" ? ", lazy: true" : "";
|
|
727
|
+
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} }`;
|
|
728
|
+
}).join(",\n");
|
|
729
|
+
const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
|
|
730
|
+
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
731
|
+
}
|
|
732
|
+
function createRuntimeModule() {
|
|
733
|
+
return "export const openApis = {};";
|
|
734
|
+
}
|
|
735
|
+
function createClientEntryModule(options = {}) {
|
|
736
|
+
return `
|
|
737
|
+
import '@clarify-labs/renderer/style.css';
|
|
738
|
+
import { render } from '@clarify-labs/renderer/client';
|
|
739
|
+
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
740
|
+
import { config } from '${VIRTUAL_CONFIG}';
|
|
741
|
+
import { openApis } from '${VIRTUAL_RUNTIME}';
|
|
742
|
+
render({ config, routes, navigation, openApis, themeEditor: ${JSON.stringify(options.themeEditor ?? false)} });`;
|
|
743
|
+
}
|
|
744
|
+
function buildVirtualModules(args) {
|
|
745
|
+
const modules = /* @__PURE__ */ new Map();
|
|
746
|
+
const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
|
|
747
|
+
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
748
|
+
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
|
|
749
|
+
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
|
|
750
|
+
modules.set(VIRTUAL_RUNTIME, createRuntimeModule());
|
|
751
|
+
modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
|
|
752
|
+
modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
|
|
753
|
+
for (const route of args.routes) modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
|
|
754
|
+
return modules;
|
|
755
|
+
}
|
|
756
|
+
//#endregion
|
|
662
757
|
//#region source/plugins/openapi/virtual-modules.ts
|
|
663
|
-
var openApiRegistryModuleId =
|
|
758
|
+
var openApiRegistryModuleId = VIRTUAL_RUNTIME;
|
|
664
759
|
function generateOpenAPIRegistryModule(openApis) {
|
|
665
760
|
return `export const openApis = ${JSON.stringify(openApis)};`;
|
|
666
761
|
}
|
|
667
|
-
function generateOpenAPIModule(spec) {
|
|
762
|
+
function generateOpenAPIModule(spec, tagFilter) {
|
|
668
763
|
return `import { createElement } from 'react';
|
|
669
764
|
import { OpenApiDocument } from '@clarify-labs/renderer';
|
|
670
765
|
const spec = ${JSON.stringify(spec)};
|
|
766
|
+
const tagFilter = ${JSON.stringify(tagFilter)};
|
|
671
767
|
export default function OpenApiRoutePage() {
|
|
672
|
-
return createElement(OpenApiDocument, { spec });
|
|
768
|
+
return createElement(OpenApiDocument, { spec, tagFilter });
|
|
673
769
|
}`;
|
|
674
770
|
}
|
|
675
771
|
function generateOpenAPIErrorModule(diagnostic) {
|
|
@@ -695,6 +791,55 @@ export default function OpenApiErrorRoutePage() {
|
|
|
695
791
|
}
|
|
696
792
|
//#endregion
|
|
697
793
|
//#region source/plugins/openapi/index.ts
|
|
794
|
+
function collectOpenAPIPageItems(config) {
|
|
795
|
+
const items = [];
|
|
796
|
+
function visitPages(pages) {
|
|
797
|
+
if (!pages || pages === "FileTree") return;
|
|
798
|
+
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);
|
|
799
|
+
}
|
|
800
|
+
for (const tab of config.tabs ?? []) visitPages(tab.pages);
|
|
801
|
+
return items;
|
|
802
|
+
}
|
|
803
|
+
function withAlternates$1(route, routes, i18n) {
|
|
804
|
+
const basePath = route.basePath ?? route.path;
|
|
805
|
+
const routeByLocaleAndBase = new Map(routes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
806
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
807
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
808
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
809
|
+
}));
|
|
810
|
+
return {
|
|
811
|
+
...route,
|
|
812
|
+
alternates
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function createTaggedOpenAPIRoutes(routes, config) {
|
|
816
|
+
const pageItems = collectOpenAPIPageItems(config);
|
|
817
|
+
if (!pageItems.length) return routes;
|
|
818
|
+
const additions = [];
|
|
819
|
+
const existingKeys = new Set(routes.map((route) => `${route.locale ?? ""}:${route.basePath ?? route.path}`));
|
|
820
|
+
for (const item of pageItems) {
|
|
821
|
+
const tagFilter = item.filter?.tags?.filter(Boolean);
|
|
822
|
+
if (!tagFilter?.length) continue;
|
|
823
|
+
const sourceBasePath = openAPIPagePathFromRef(item.openapi);
|
|
824
|
+
const filteredBasePath = openAPIPagePathFromRef(item.openapi, tagFilter);
|
|
825
|
+
for (const route of routes) {
|
|
826
|
+
if (route.kind !== "openapi" || (route.basePath ?? route.path) !== sourceBasePath) continue;
|
|
827
|
+
const key = `${route.locale ?? ""}:${filteredBasePath}`;
|
|
828
|
+
if (existingKeys.has(key)) continue;
|
|
829
|
+
existingKeys.add(key);
|
|
830
|
+
additions.push({
|
|
831
|
+
...route,
|
|
832
|
+
path: route.locale && config.i18n ? localizedRoutePath(filteredBasePath, route.locale, config.i18n) : filteredBasePath,
|
|
833
|
+
basePath: filteredBasePath,
|
|
834
|
+
title: route.title,
|
|
835
|
+
virtualModuleId: `${route.virtualModuleId}/${openAPITagsPathSegment(tagFilter)}`,
|
|
836
|
+
openapiTagFilter: tagFilter
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
const nextRoutes = [...routes, ...additions];
|
|
841
|
+
return config.i18n ? nextRoutes.map((route) => withAlternates$1(route, nextRoutes, config.i18n)) : nextRoutes;
|
|
842
|
+
}
|
|
698
843
|
function createOpenAPIPlugin() {
|
|
699
844
|
const specs = {};
|
|
700
845
|
return {
|
|
@@ -704,10 +849,16 @@ function createOpenAPIPlugin() {
|
|
|
704
849
|
...input,
|
|
705
850
|
routes: [...input.routes, ...findOpenAPIRoutes(input.contentRoot)]
|
|
706
851
|
}),
|
|
707
|
-
"routes:discovered": async (routes) => {
|
|
852
|
+
"routes:discovered": async (routes, ctx) => {
|
|
708
853
|
for (const key of Object.keys(specs)) delete specs[key];
|
|
709
|
-
|
|
710
|
-
|
|
854
|
+
const nextRoutes = createTaggedOpenAPIRoutes(routes, ctx.projectConfig);
|
|
855
|
+
const specByFilePath = /* @__PURE__ */ new Map();
|
|
856
|
+
for (const route of nextRoutes.filter((route) => route.kind === "openapi")) {
|
|
857
|
+
const specFromCache = specByFilePath.get(route.filePath);
|
|
858
|
+
const result = specFromCache ? {
|
|
859
|
+
ok: true,
|
|
860
|
+
spec: specFromCache
|
|
861
|
+
} : await readOpenAPISpec(route.filePath);
|
|
711
862
|
if (!result.ok) {
|
|
712
863
|
route.diagnostic = result.diagnostic;
|
|
713
864
|
route.title = route.title || "OpenAPI parse error";
|
|
@@ -715,17 +866,24 @@ function createOpenAPIPlugin() {
|
|
|
715
866
|
continue;
|
|
716
867
|
}
|
|
717
868
|
const spec = result.spec;
|
|
718
|
-
|
|
719
|
-
specs[
|
|
869
|
+
specByFilePath.set(route.filePath, spec);
|
|
870
|
+
specs[route.virtualModuleId] = {
|
|
871
|
+
spec,
|
|
872
|
+
tagFilter: route.openapiTagFilter
|
|
873
|
+
};
|
|
874
|
+
specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = {
|
|
875
|
+
spec,
|
|
876
|
+
tagFilter: route.openapiTagFilter
|
|
877
|
+
};
|
|
720
878
|
route.title = spec.info?.title ?? route.title;
|
|
721
879
|
route.description = spec.info?.description ?? route.description;
|
|
722
|
-
route.sections = extractOpenAPISections(spec);
|
|
880
|
+
route.sections = extractOpenAPISections(spec, route.openapiTagFilter);
|
|
723
881
|
}
|
|
724
|
-
return
|
|
882
|
+
return nextRoutes;
|
|
725
883
|
},
|
|
726
884
|
"modules:before": (modules, ctx) => {
|
|
727
|
-
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(specs));
|
|
728
|
-
for (const [moduleId,
|
|
885
|
+
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(Object.fromEntries(Object.entries(specs).map(([moduleId, entry]) => [moduleId, entry.spec]))));
|
|
886
|
+
for (const [moduleId, entry] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(entry.spec, entry.tagFilter));
|
|
729
887
|
for (const route of ctx.routes.filter((route) => route.kind === "openapi" && route.diagnostic)) if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
|
|
730
888
|
return modules;
|
|
731
889
|
}
|
|
@@ -733,6 +891,107 @@ function createOpenAPIPlugin() {
|
|
|
733
891
|
};
|
|
734
892
|
}
|
|
735
893
|
//#endregion
|
|
894
|
+
//#region source/plugins/seo/artifacts.ts
|
|
895
|
+
function trimTrailingSlash(value) {
|
|
896
|
+
return value.replace(/\/+$/, "");
|
|
897
|
+
}
|
|
898
|
+
function normalizeRoutePrefix(routePrefix) {
|
|
899
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
900
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
901
|
+
}
|
|
902
|
+
function routeUrl(siteUrl, routePrefix, routePath) {
|
|
903
|
+
const base = trimTrailingSlash(siteUrl);
|
|
904
|
+
return `${base}${normalizeRoutePrefix(routePrefix)}${routePath === "/" ? "" : routePath.replace(/^\/+/, "/")}` || base;
|
|
905
|
+
}
|
|
906
|
+
function escapeXml(value) {
|
|
907
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
908
|
+
}
|
|
909
|
+
function publicRoutes(routes) {
|
|
910
|
+
return routes.filter((route) => route.path !== "/404");
|
|
911
|
+
}
|
|
912
|
+
function createSitemapXml(routes, config) {
|
|
913
|
+
if (!config.siteUrl) return void 0;
|
|
914
|
+
return [
|
|
915
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
916
|
+
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
|
|
917
|
+
...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>`),
|
|
918
|
+
"</urlset>",
|
|
919
|
+
""
|
|
920
|
+
].join("\n");
|
|
921
|
+
}
|
|
922
|
+
function createRobotsTxt(config) {
|
|
923
|
+
if (!config.siteUrl) return void 0;
|
|
924
|
+
return [
|
|
925
|
+
"User-agent: *",
|
|
926
|
+
"Allow: /",
|
|
927
|
+
`Sitemap: ${routeUrl(config.siteUrl, config.routePrefix, "/sitemap.xml")}`,
|
|
928
|
+
""
|
|
929
|
+
].join("\n");
|
|
930
|
+
}
|
|
931
|
+
async function writeSeoFiles(outputRoot, routes, config) {
|
|
932
|
+
const sitemap = createSitemapXml(routes, config);
|
|
933
|
+
const robots = createRobotsTxt(config);
|
|
934
|
+
if (!sitemap || !robots) return;
|
|
935
|
+
await (0, node_fs_promises.mkdir)(outputRoot, { recursive: true });
|
|
936
|
+
await Promise.all([(0, node_fs_promises.writeFile)((0, node_path.join)(outputRoot, "sitemap.xml"), sitemap, "utf-8"), (0, node_fs_promises.writeFile)((0, node_path.join)(outputRoot, "robots.txt"), robots, "utf-8")]);
|
|
937
|
+
}
|
|
938
|
+
//#endregion
|
|
939
|
+
//#region source/plugins/seo/index.ts
|
|
940
|
+
function createSeoPlugin() {
|
|
941
|
+
return {
|
|
942
|
+
name: "clarify:seo",
|
|
943
|
+
hooks: { async "build:done"(ctx) {
|
|
944
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
945
|
+
if (!outputDirectory) return;
|
|
946
|
+
await writeSeoFiles((0, node_path.join)(ctx.generateOptions.projectRoot, outputDirectory), ctx.routes, ctx.projectConfig);
|
|
947
|
+
} }
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
//#endregion
|
|
951
|
+
//#region source/plugins/source-links/source-links.ts
|
|
952
|
+
function trimSlashes(value) {
|
|
953
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
954
|
+
}
|
|
955
|
+
function normalizeRepositoryUrl(repository) {
|
|
956
|
+
return repository.replace(/\.git$/, "").replace(/\/+$/, "");
|
|
957
|
+
}
|
|
958
|
+
function encodePath(path) {
|
|
959
|
+
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
960
|
+
}
|
|
961
|
+
function createSourceUrl(filePath, contentRoot, source) {
|
|
962
|
+
const repository = normalizeRepositoryUrl(source.repository);
|
|
963
|
+
const branch = source.branch ?? "main";
|
|
964
|
+
const sourcePath = [source.directory ? trimSlashes(source.directory) : "", (0, node_path.relative)(contentRoot, filePath).replaceAll("\\", "/")].filter(Boolean).join("/");
|
|
965
|
+
return `${repository}/edit/${encodeURIComponent(branch)}/${encodePath(sourcePath)}`;
|
|
966
|
+
}
|
|
967
|
+
function attachSourceUrls(routes, contentRoot, source) {
|
|
968
|
+
if (!source) return;
|
|
969
|
+
for (const route of routes) route.sourceUrl = createSourceUrl(route.filePath, contentRoot, source);
|
|
970
|
+
}
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region source/plugins/source-links/index.ts
|
|
973
|
+
function createSourceLinksPlugin() {
|
|
974
|
+
return {
|
|
975
|
+
name: "clarify:source-links",
|
|
976
|
+
hooks: { "routes:discovered": (routes, ctx) => {
|
|
977
|
+
attachSourceUrls(routes, (0, node_path.join)(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.source);
|
|
978
|
+
return routes;
|
|
979
|
+
} }
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
//#endregion
|
|
983
|
+
//#region source/core/builtin.ts
|
|
984
|
+
function createBuiltinPlugins(options = {}) {
|
|
985
|
+
const plugins = [
|
|
986
|
+
createOpenAPIPlugin(),
|
|
987
|
+
createSourceLinksPlugin(),
|
|
988
|
+
createContentArtifactsPlugin(),
|
|
989
|
+
createSeoPlugin()
|
|
990
|
+
];
|
|
991
|
+
if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
|
|
992
|
+
return plugins;
|
|
993
|
+
}
|
|
994
|
+
//#endregion
|
|
736
995
|
//#region source/core/config-schema.ts
|
|
737
996
|
var clarifyLogoConfigSchema = zod.z.union([zod.z.string(), zod.z.object({
|
|
738
997
|
light: zod.z.string().optional(),
|
|
@@ -790,6 +1049,11 @@ var clarifyFooterConfigSchema = zod.z.object({
|
|
|
790
1049
|
socials: zod.z.record(zod.z.string(), zod.z.string()).optional(),
|
|
791
1050
|
copyright: clarifyLocalizedTextSchema.optional()
|
|
792
1051
|
});
|
|
1052
|
+
var clarifySourceConfigSchema = zod.z.object({
|
|
1053
|
+
repository: zod.z.string(),
|
|
1054
|
+
branch: zod.z.string().optional(),
|
|
1055
|
+
directory: zod.z.string().optional()
|
|
1056
|
+
});
|
|
793
1057
|
var clarifyPagesItemSchema = zod.z.union([
|
|
794
1058
|
zod.z.string(),
|
|
795
1059
|
zod.z.object({
|
|
@@ -801,7 +1065,8 @@ var clarifyPagesItemSchema = zod.z.union([
|
|
|
801
1065
|
zod.z.object({
|
|
802
1066
|
openapi: zod.z.string(),
|
|
803
1067
|
icon: zod.z.string().optional(),
|
|
804
|
-
title: clarifyLocalizedTextSchema.optional()
|
|
1068
|
+
title: clarifyLocalizedTextSchema.optional(),
|
|
1069
|
+
filter: zod.z.object({ tags: zod.z.array(zod.z.string()).optional() }).optional()
|
|
805
1070
|
})
|
|
806
1071
|
]);
|
|
807
1072
|
var clarifyPagesGroupSchema = zod.z.object({
|
|
@@ -817,15 +1082,20 @@ var clarifyTabItemSchema = zod.z.object({
|
|
|
817
1082
|
});
|
|
818
1083
|
var clarifyTabsConfigSchema = zod.z.array(clarifyTabItemSchema);
|
|
819
1084
|
var clarifyThemePresetSchema = zod.z.enum(["default", "base"]);
|
|
1085
|
+
var clarifyThemeModeColorValueSchema = zod.z.object({
|
|
1086
|
+
light: zod.z.string().optional(),
|
|
1087
|
+
dark: zod.z.string().optional()
|
|
1088
|
+
});
|
|
1089
|
+
var clarifyThemeColorValueSchema = zod.z.union([zod.z.string(), clarifyThemeModeColorValueSchema]);
|
|
820
1090
|
var clarifyThemeColorTokensConfigSchema = zod.z.object({
|
|
821
|
-
primary:
|
|
822
|
-
accent:
|
|
823
|
-
background:
|
|
824
|
-
foreground:
|
|
825
|
-
surface:
|
|
826
|
-
muted:
|
|
827
|
-
border:
|
|
828
|
-
codeBackground:
|
|
1091
|
+
primary: clarifyThemeColorValueSchema.optional(),
|
|
1092
|
+
accent: clarifyThemeColorValueSchema.optional(),
|
|
1093
|
+
background: clarifyThemeColorValueSchema.optional(),
|
|
1094
|
+
foreground: clarifyThemeColorValueSchema.optional(),
|
|
1095
|
+
surface: clarifyThemeColorValueSchema.optional(),
|
|
1096
|
+
muted: clarifyThemeColorValueSchema.optional(),
|
|
1097
|
+
border: clarifyThemeColorValueSchema.optional(),
|
|
1098
|
+
codeBackground: clarifyThemeColorValueSchema.optional()
|
|
829
1099
|
});
|
|
830
1100
|
var clarifyThemeRadiusTokensConfigSchema = zod.z.object({
|
|
831
1101
|
sm: zod.z.string().optional(),
|
|
@@ -841,11 +1111,14 @@ var clarifyThemeLayoutConfigSchema = zod.z.object({ maxWidth: zod.z.string().opt
|
|
|
841
1111
|
var clarifyThemeConfigSchema = zod.z.object({
|
|
842
1112
|
preset: clarifyThemePresetSchema.optional(),
|
|
843
1113
|
tokens: clarifyThemeTokensConfigSchema.optional(),
|
|
844
|
-
layout: clarifyThemeLayoutConfigSchema.optional()
|
|
1114
|
+
layout: clarifyThemeLayoutConfigSchema.optional(),
|
|
1115
|
+
editor: zod.z.boolean().optional()
|
|
845
1116
|
});
|
|
846
1117
|
var clarifyProjectConfigSchema = zod.z.object({
|
|
847
1118
|
title: zod.z.string().optional(),
|
|
848
1119
|
description: zod.z.string().optional(),
|
|
1120
|
+
siteUrl: zod.z.string().optional(),
|
|
1121
|
+
source: clarifySourceConfigSchema.optional(),
|
|
849
1122
|
logo: clarifyLogoConfigSchema.optional(),
|
|
850
1123
|
favicon: clarifyFaviconConfigSchema.optional(),
|
|
851
1124
|
theme: clarifyThemeConfigSchema.optional(),
|
|
@@ -863,14 +1136,14 @@ var clarifyThemePresets = {
|
|
|
863
1136
|
preset: "default",
|
|
864
1137
|
tokens: {
|
|
865
1138
|
colors: {
|
|
866
|
-
primary: "#
|
|
867
|
-
accent: "#
|
|
1139
|
+
primary: "#047857",
|
|
1140
|
+
accent: "#0D9488",
|
|
868
1141
|
background: "#ffffff",
|
|
869
1142
|
foreground: "#111827",
|
|
870
1143
|
surface: "#ffffff",
|
|
871
1144
|
muted: "#64748b",
|
|
872
1145
|
border: "rgb(15 23 42 / 0.12)",
|
|
873
|
-
codeBackground: "#
|
|
1146
|
+
codeBackground: "#f6fbf8"
|
|
874
1147
|
},
|
|
875
1148
|
radius: {
|
|
876
1149
|
sm: "6px",
|
|
@@ -879,7 +1152,8 @@ var clarifyThemePresets = {
|
|
|
879
1152
|
xl: "18px"
|
|
880
1153
|
}
|
|
881
1154
|
},
|
|
882
|
-
layout: { maxWidth: "82rem" }
|
|
1155
|
+
layout: { maxWidth: "82rem" },
|
|
1156
|
+
editor: false
|
|
883
1157
|
},
|
|
884
1158
|
base: {
|
|
885
1159
|
preset: "base",
|
|
@@ -901,7 +1175,8 @@ var clarifyThemePresets = {
|
|
|
901
1175
|
xl: "16px"
|
|
902
1176
|
}
|
|
903
1177
|
},
|
|
904
|
-
layout: { maxWidth: "80rem" }
|
|
1178
|
+
layout: { maxWidth: "80rem" },
|
|
1179
|
+
editor: false
|
|
905
1180
|
}
|
|
906
1181
|
};
|
|
907
1182
|
function resolveThemeConfig(theme = {}) {
|
|
@@ -922,7 +1197,8 @@ function resolveThemeConfig(theme = {}) {
|
|
|
922
1197
|
layout: {
|
|
923
1198
|
...presetTheme.layout,
|
|
924
1199
|
...theme.layout
|
|
925
|
-
}
|
|
1200
|
+
},
|
|
1201
|
+
editor: theme.editor ?? presetTheme.editor
|
|
926
1202
|
};
|
|
927
1203
|
}
|
|
928
1204
|
//#endregion
|
|
@@ -961,6 +1237,8 @@ function resolveProjectConfig(config = {}) {
|
|
|
961
1237
|
return {
|
|
962
1238
|
title: config.title ?? "Clarify Docs",
|
|
963
1239
|
description: config.description ?? "",
|
|
1240
|
+
siteUrl: config.siteUrl,
|
|
1241
|
+
source: config.source,
|
|
964
1242
|
logo: config.logo,
|
|
965
1243
|
favicon: config.favicon,
|
|
966
1244
|
routePrefix: config.routePrefix ?? "/",
|
|
@@ -1013,12 +1291,104 @@ async function runBuildDoneHooks(plugins, ctx) {
|
|
|
1013
1291
|
//#region source/core/options.ts
|
|
1014
1292
|
function resolveBuildOptions(options = {}) {
|
|
1015
1293
|
return {
|
|
1294
|
+
projectRoot: options.projectRoot ?? process.cwd(),
|
|
1016
1295
|
rootDirectory: options.rootDirectory ?? "source",
|
|
1017
1296
|
outputDirectory: options.outputDirectory,
|
|
1018
1297
|
ssg: { failOnError: options.ssg?.failOnError ?? true }
|
|
1019
1298
|
};
|
|
1020
1299
|
}
|
|
1021
1300
|
//#endregion
|
|
1301
|
+
//#region source/core/site.ts
|
|
1302
|
+
function withAlternates(route, routeList, i18n) {
|
|
1303
|
+
const basePath = route.basePath ?? route.path;
|
|
1304
|
+
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1305
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1306
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1307
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1308
|
+
}));
|
|
1309
|
+
return {
|
|
1310
|
+
...route,
|
|
1311
|
+
alternates
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
async function discoverRoutesForRoot(routeRoot, locale, plugins, ctx) {
|
|
1315
|
+
return (await runHooks(plugins, "routes:discover", {
|
|
1316
|
+
contentRoot: routeRoot,
|
|
1317
|
+
locale,
|
|
1318
|
+
routes: findContentRoutes(routeRoot)
|
|
1319
|
+
}, ctx)).routes;
|
|
1320
|
+
}
|
|
1321
|
+
async function discoverRoutes(root, contentRoot, plugins, ctx) {
|
|
1322
|
+
const i18n = ctx.projectConfig.i18n;
|
|
1323
|
+
if (!i18n) return discoverRoutesForRoot(contentRoot, void 0, plugins, ctx);
|
|
1324
|
+
const localizedRoutes = [];
|
|
1325
|
+
for (const locale of i18n.locales) {
|
|
1326
|
+
const discovered = await discoverRoutesForRoot((0, node_path.join)(contentRoot, locale.code), locale.code, plugins, ctx);
|
|
1327
|
+
for (const route of discovered) {
|
|
1328
|
+
const basePath = route.basePath ?? route.path;
|
|
1329
|
+
localizedRoutes.push({
|
|
1330
|
+
...route,
|
|
1331
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1332
|
+
basePath,
|
|
1333
|
+
locale: locale.code,
|
|
1334
|
+
virtualModuleId: virtualModuleIdFromRef((0, node_path.relative)(contentRoot, route.filePath))
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
if (i18n.missing === "fallback") {
|
|
1339
|
+
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1340
|
+
const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
|
|
1341
|
+
for (const sourceRoute of defaultRoutes) {
|
|
1342
|
+
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
1343
|
+
for (const locale of i18n.locales) {
|
|
1344
|
+
const key = `${locale.code}:${basePath}`;
|
|
1345
|
+
if (routeByLocaleAndBase.has(key)) continue;
|
|
1346
|
+
localizedRoutes.push({
|
|
1347
|
+
...sourceRoute,
|
|
1348
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1349
|
+
locale: locale.code,
|
|
1350
|
+
isFallback: true
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1356
|
+
}
|
|
1357
|
+
async function resolveClarifySite(options = {}, resolveOptions = {}) {
|
|
1358
|
+
const root = options.projectRoot ?? process.cwd();
|
|
1359
|
+
const projectConfig = resolveProjectConfig(options);
|
|
1360
|
+
const generateOptions = resolveBuildOptions(options);
|
|
1361
|
+
const contentRoot = (0, node_path.join)(root, generateOptions.rootDirectory);
|
|
1362
|
+
const plugins = [...createBuiltinPlugins({ htmlShell: resolveOptions.includeHtmlShellPlugin }), ...options.plugins ?? []];
|
|
1363
|
+
const ctx = {
|
|
1364
|
+
projectConfig,
|
|
1365
|
+
generateOptions,
|
|
1366
|
+
routes: [],
|
|
1367
|
+
navigation: []
|
|
1368
|
+
};
|
|
1369
|
+
let routes = await discoverRoutes(root, contentRoot, plugins, ctx);
|
|
1370
|
+
routes = await runHooks(plugins, "routes:discovered", routes, ctx);
|
|
1371
|
+
const defaultNavigation = projectConfig.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes);
|
|
1372
|
+
const resolved = await runHooks(plugins, "routes:resolved", {
|
|
1373
|
+
routes,
|
|
1374
|
+
navigation: defaultNavigation
|
|
1375
|
+
}, ctx);
|
|
1376
|
+
routes = resolved.routes;
|
|
1377
|
+
const navigation = resolved.navigation;
|
|
1378
|
+
ctx.routes = routes;
|
|
1379
|
+
ctx.navigation = navigation;
|
|
1380
|
+
return {
|
|
1381
|
+
root,
|
|
1382
|
+
contentRoot,
|
|
1383
|
+
projectConfig,
|
|
1384
|
+
generateOptions,
|
|
1385
|
+
plugins,
|
|
1386
|
+
ctx,
|
|
1387
|
+
routes,
|
|
1388
|
+
navigation
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
//#endregion
|
|
1022
1392
|
//#region source/core/runtime-deps.ts
|
|
1023
1393
|
var requireFromCli = (0, node_module.createRequire)({}.url);
|
|
1024
1394
|
function resolveCliDependency(id) {
|
|
@@ -1113,13 +1483,21 @@ function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
|
|
|
1113
1483
|
html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
|
|
1114
1484
|
return html;
|
|
1115
1485
|
}
|
|
1486
|
+
function isNotFoundRoute(route) {
|
|
1487
|
+
return (route.basePath ?? route.path) === "/404";
|
|
1488
|
+
}
|
|
1489
|
+
function routeOutputFiles(outputDirectory, route) {
|
|
1490
|
+
const files = [(0, node_path.join)(outputDirectory, route.path, "index.html")];
|
|
1491
|
+
if (isNotFoundRoute(route) && (!route.locale || route.path === "/404")) files.push((0, node_path.join)(outputDirectory, "404.html"));
|
|
1492
|
+
return files;
|
|
1493
|
+
}
|
|
1116
1494
|
var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
|
|
1117
1495
|
import { routes, navigation } from 'virtual:clarify-routes/server';
|
|
1118
1496
|
import { config } from 'virtual:clarify-config';
|
|
1119
|
-
import { openApis } from 'virtual:clarify-
|
|
1497
|
+
import { openApis } from 'virtual:clarify-runtime';
|
|
1120
1498
|
|
|
1121
1499
|
export function render(url) {
|
|
1122
|
-
return renderToHTML({ config, routes, navigation, openApis, url });
|
|
1500
|
+
return renderToHTML({ config, routes, navigation, openApis, url, themeEditor: config.theme.editor });
|
|
1123
1501
|
}`;
|
|
1124
1502
|
function createTempEntryFile(content) {
|
|
1125
1503
|
const entryPath = (0, node_path.join)((0, node_fs.mkdtempSync)((0, node_path.join)((0, node_os.tmpdir)(), "clarify-ssr-")), "entry-server.ts");
|
|
@@ -1150,9 +1528,10 @@ async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundle
|
|
|
1150
1528
|
if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
|
|
1151
1529
|
for (const route of routes) try {
|
|
1152
1530
|
const finalHtml = injectSSRIntoTemplate(template, render(route.path), projectConfig, route);
|
|
1153
|
-
const outFile
|
|
1154
|
-
|
|
1155
|
-
|
|
1531
|
+
for (const outFile of routeOutputFiles(outputDirectory, route)) {
|
|
1532
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
|
|
1533
|
+
(0, node_fs.writeFileSync)(outFile, finalHtml, "utf-8");
|
|
1534
|
+
}
|
|
1156
1535
|
} catch (err) {
|
|
1157
1536
|
console.error(`[clarify] Failed to render route "${route.path}":`, err);
|
|
1158
1537
|
if (failOnError) throw err;
|
|
@@ -1194,75 +1573,6 @@ async function loadClarifyConfig(root, env) {
|
|
|
1194
1573
|
return assertConfigObject((await (0, vite.loadConfigFromFile)(env, configFile, root, "silent"))?.config, configFile);
|
|
1195
1574
|
}
|
|
1196
1575
|
//#endregion
|
|
1197
|
-
//#region source/core/virtual-modules.ts
|
|
1198
|
-
var VIRTUAL_CONFIG = "virtual:clarify-config";
|
|
1199
|
-
var VIRTUAL_ROUTES = "virtual:clarify-routes";
|
|
1200
|
-
var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
|
|
1201
|
-
var VIRTUAL_OPENAPI_REGISTRY = openApiRegistryModuleId;
|
|
1202
|
-
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
|
|
1203
|
-
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
|
|
1204
|
-
function resolveVirtualId(id) {
|
|
1205
|
-
return "\0" + id;
|
|
1206
|
-
}
|
|
1207
|
-
function stripVirtualPrefix(id) {
|
|
1208
|
-
return id.startsWith("\0") ? id.slice(1) : id;
|
|
1209
|
-
}
|
|
1210
|
-
function generateConfigModule(projectConfig, buildOptions) {
|
|
1211
|
-
return `export const config = ${JSON.stringify({
|
|
1212
|
-
...projectConfig,
|
|
1213
|
-
...buildOptions
|
|
1214
|
-
})};`;
|
|
1215
|
-
}
|
|
1216
|
-
function moduleSpecifier(value) {
|
|
1217
|
-
return JSON.stringify(value);
|
|
1218
|
-
}
|
|
1219
|
-
function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
|
|
1220
|
-
const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
|
|
1221
|
-
const routesArray = routes.map((r, i) => {
|
|
1222
|
-
const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
|
|
1223
|
-
id: s.id,
|
|
1224
|
-
title: s.title,
|
|
1225
|
-
badge: s.badge,
|
|
1226
|
-
tags: s.tags
|
|
1227
|
-
})))}` : "";
|
|
1228
|
-
const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
|
|
1229
|
-
const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
|
|
1230
|
-
const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
|
|
1231
|
-
const isFallback = r.isFallback ? ", isFallback: true" : "";
|
|
1232
|
-
const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
|
|
1233
|
-
const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
|
|
1234
|
-
const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
|
|
1235
|
-
const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
|
|
1236
|
-
const lazy = mode === "client" ? ", lazy: true" : "";
|
|
1237
|
-
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} }`;
|
|
1238
|
-
}).join(",\n");
|
|
1239
|
-
const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
|
|
1240
|
-
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
1241
|
-
}
|
|
1242
|
-
function createClientEntryModule() {
|
|
1243
|
-
return `
|
|
1244
|
-
import '@clarify-labs/renderer/style.css';
|
|
1245
|
-
import { render } from '@clarify-labs/renderer/client';
|
|
1246
|
-
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
1247
|
-
import { config } from '${VIRTUAL_CONFIG}';
|
|
1248
|
-
import { openApis } from '${VIRTUAL_OPENAPI_REGISTRY}';
|
|
1249
|
-
render({ config, routes, navigation, openApis });`;
|
|
1250
|
-
}
|
|
1251
|
-
function buildVirtualModules(args) {
|
|
1252
|
-
const modules = /* @__PURE__ */ new Map();
|
|
1253
|
-
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
1254
|
-
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
|
|
1255
|
-
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
|
|
1256
|
-
modules.set(VIRTUAL_OPENAPI_REGISTRY, "export const openApis = {};");
|
|
1257
|
-
modules.set(VIRTUAL_CLIENT_ENTRY, createClientEntryModule());
|
|
1258
|
-
modules.set(RESOLVED_CLIENT_ENTRY, createClientEntryModule());
|
|
1259
|
-
for (const route of args.routes) {
|
|
1260
|
-
if (route.kind === "openapi") continue;
|
|
1261
|
-
modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
|
|
1262
|
-
}
|
|
1263
|
-
return modules;
|
|
1264
|
-
}
|
|
1265
|
-
//#endregion
|
|
1266
1576
|
//#region source/core/plugin.ts
|
|
1267
1577
|
function loadVirtualModule(id, modules) {
|
|
1268
1578
|
const bareId = stripVirtualPrefix(id);
|
|
@@ -1275,12 +1585,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1275
1585
|
const contentRoot = (0, node_path.join)(root, generateOptions.rootDirectory);
|
|
1276
1586
|
const configFilePath = findClarifyConfigFile(root);
|
|
1277
1587
|
let routes = [];
|
|
1278
|
-
|
|
1279
|
-
createOpenAPIPlugin(),
|
|
1280
|
-
createContentArtifactsPlugin(),
|
|
1281
|
-
createHtmlShellPlugin(),
|
|
1282
|
-
...options.plugins ?? []
|
|
1283
|
-
];
|
|
1588
|
+
let clarifyPlugins = [...createBuiltinPlugins(), ...options.plugins ?? []];
|
|
1284
1589
|
const ctx = {
|
|
1285
1590
|
projectConfig,
|
|
1286
1591
|
generateOptions,
|
|
@@ -1305,71 +1610,15 @@ function clarifyPlugin(options = {}) {
|
|
|
1305
1610
|
ctx.projectConfig = projectConfig;
|
|
1306
1611
|
ctx.generateOptions = generateOptions;
|
|
1307
1612
|
}
|
|
1308
|
-
function withAlternates(route, routeList, i18n) {
|
|
1309
|
-
const basePath = route.basePath ?? route.path;
|
|
1310
|
-
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1311
|
-
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1312
|
-
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1313
|
-
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1314
|
-
}));
|
|
1315
|
-
return {
|
|
1316
|
-
...route,
|
|
1317
|
-
alternates
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
async function discoverRoutesForRoot(routeRoot, locale) {
|
|
1321
|
-
return (await runHooks(clarifyPlugins, "routes:discover", {
|
|
1322
|
-
contentRoot: routeRoot,
|
|
1323
|
-
locale,
|
|
1324
|
-
routes: findContentRoutes(routeRoot)
|
|
1325
|
-
}, ctx)).routes;
|
|
1326
|
-
}
|
|
1327
|
-
async function discoverRoutes() {
|
|
1328
|
-
const i18n = projectConfig.i18n;
|
|
1329
|
-
if (!i18n) return discoverRoutesForRoot(contentRoot);
|
|
1330
|
-
const localizedRoutes = [];
|
|
1331
|
-
for (const locale of i18n.locales) {
|
|
1332
|
-
const discovered = await discoverRoutesForRoot((0, node_path.join)(contentRoot, locale.code), locale.code);
|
|
1333
|
-
for (const route of discovered) {
|
|
1334
|
-
const basePath = route.basePath ?? route.path;
|
|
1335
|
-
localizedRoutes.push({
|
|
1336
|
-
...route,
|
|
1337
|
-
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1338
|
-
basePath,
|
|
1339
|
-
locale: locale.code,
|
|
1340
|
-
virtualModuleId: virtualModuleIdFromRef((0, node_path.relative)(contentRoot, route.filePath))
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
if (i18n.missing === "fallback") {
|
|
1345
|
-
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1346
|
-
const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
|
|
1347
|
-
for (const sourceRoute of defaultRoutes) {
|
|
1348
|
-
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
1349
|
-
for (const locale of i18n.locales) {
|
|
1350
|
-
const key = `${locale.code}:${basePath}`;
|
|
1351
|
-
if (routeByLocaleAndBase.has(key)) continue;
|
|
1352
|
-
localizedRoutes.push({
|
|
1353
|
-
...sourceRoute,
|
|
1354
|
-
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1355
|
-
locale: locale.code,
|
|
1356
|
-
isFallback: true
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
return localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1362
|
-
}
|
|
1363
1613
|
async function resolveRoutesAndSpecs() {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
resolvedNavigation = resolved.navigation;
|
|
1614
|
+
const site = await resolveClarifySite(options);
|
|
1615
|
+
projectConfig = site.projectConfig;
|
|
1616
|
+
generateOptions = site.generateOptions;
|
|
1617
|
+
routes = site.routes;
|
|
1618
|
+
resolvedNavigation = site.navigation;
|
|
1619
|
+
clarifyPlugins = site.plugins;
|
|
1620
|
+
ctx.projectConfig = projectConfig;
|
|
1621
|
+
ctx.generateOptions = generateOptions;
|
|
1373
1622
|
ctx.routes = routes;
|
|
1374
1623
|
ctx.navigation = resolvedNavigation;
|
|
1375
1624
|
}
|
|
@@ -1378,7 +1627,8 @@ function clarifyPlugin(options = {}) {
|
|
|
1378
1627
|
projectConfig,
|
|
1379
1628
|
generateOptions,
|
|
1380
1629
|
routes,
|
|
1381
|
-
navigation: resolvedNavigation
|
|
1630
|
+
navigation: resolvedNavigation,
|
|
1631
|
+
themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor
|
|
1382
1632
|
});
|
|
1383
1633
|
virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
|
|
1384
1634
|
}
|
|
@@ -1458,8 +1708,9 @@ function clarifyPlugin(options = {}) {
|
|
|
1458
1708
|
if (id === "virtual:clarify-config" || id === resolveVirtualId("virtual:clarify-config")) return resolveVirtualId(VIRTUAL_CONFIG);
|
|
1459
1709
|
if (id === "virtual:clarify-routes" || id === resolveVirtualId("virtual:clarify-routes")) return resolveVirtualId(VIRTUAL_ROUTES);
|
|
1460
1710
|
if (id === "virtual:clarify-routes/server" || id === resolveVirtualId("virtual:clarify-routes/server")) return resolveVirtualId(VIRTUAL_SERVER_ROUTES);
|
|
1461
|
-
|
|
1462
|
-
|
|
1711
|
+
const moduleId = stripVirtualPrefix(id);
|
|
1712
|
+
if (virtualModules.has(moduleId)) return resolveVirtualId(moduleId);
|
|
1713
|
+
const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === moduleId);
|
|
1463
1714
|
if (route) return resolveVirtualId(route.virtualModuleId);
|
|
1464
1715
|
return null;
|
|
1465
1716
|
},
|
|
@@ -1481,10 +1732,11 @@ function clarifyPlugin(options = {}) {
|
|
|
1481
1732
|
transformIndexHtml: {
|
|
1482
1733
|
order: "pre",
|
|
1483
1734
|
async handler(html, transformCtx) {
|
|
1735
|
+
const clientEntryId = transformCtx.server ? `/@id/${VIRTUAL_CLIENT_ENTRY}` : VIRTUAL_CLIENT_ENTRY;
|
|
1484
1736
|
const result = await runHooks(clarifyPlugins, "html:transform", {
|
|
1485
1737
|
html,
|
|
1486
1738
|
tags: [],
|
|
1487
|
-
clientEntryId
|
|
1739
|
+
clientEntryId,
|
|
1488
1740
|
dev: Boolean(transformCtx.server)
|
|
1489
1741
|
}, ctx);
|
|
1490
1742
|
return {
|
|
@@ -1512,7 +1764,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1512
1764
|
if (id === "virtual:clarify-config") return id;
|
|
1513
1765
|
if (id === "virtual:clarify-routes") return id;
|
|
1514
1766
|
if (id === "virtual:clarify-routes/server") return id;
|
|
1515
|
-
if (id
|
|
1767
|
+
if (virtualModules.has(stripVirtualPrefix(id))) return stripVirtualPrefix(id);
|
|
1516
1768
|
if (routes.find((r) => r.virtualModuleId === id)) return id;
|
|
1517
1769
|
return null;
|
|
1518
1770
|
},
|
|
@@ -1562,5 +1814,11 @@ Object.defineProperty(exports, "loadClarifyConfig", {
|
|
|
1562
1814
|
return loadClarifyConfig;
|
|
1563
1815
|
}
|
|
1564
1816
|
});
|
|
1817
|
+
Object.defineProperty(exports, "resolveClarifySite", {
|
|
1818
|
+
enumerable: true,
|
|
1819
|
+
get: function() {
|
|
1820
|
+
return resolveClarifySite;
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1565
1823
|
|
|
1566
|
-
//# sourceMappingURL=plugin
|
|
1824
|
+
//# sourceMappingURL=plugin-GTN_SQ7d.cjs.map
|