@clarify-labs/cli 0.6.3 → 0.6.5
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/routes.d.ts +2 -0
- package/output/{plugin-DChFdTyI.js → plugin-BgE7WVoS.js} +931 -684
- package/output/plugin-BgE7WVoS.js.map +1 -0
- package/output/{plugin--jQ6D6O4.cjs → plugin-Dq1zHXan.cjs} +935 -682
- package/output/plugin-Dq1zHXan.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
|
@@ -8,10 +8,11 @@ import { toString } from "mdast-util-to-string";
|
|
|
8
8
|
import shiki from "shiki";
|
|
9
9
|
import { visit } from "unist-util-visit";
|
|
10
10
|
import { markdownRemarkPlugins } from "@clarify-labs/renderer";
|
|
11
|
+
import { z } from "zod";
|
|
11
12
|
import { remark } from "remark";
|
|
12
13
|
import matter from "gray-matter";
|
|
13
14
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
14
|
-
import {
|
|
15
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
15
16
|
import { tmpdir } from "node:os";
|
|
16
17
|
import { pathToFileURL } from "node:url";
|
|
17
18
|
import { build, loadConfigFromFile } from "vite";
|
|
@@ -100,65 +101,520 @@ var rehypePlugins = [
|
|
|
100
101
|
rehypeShiki
|
|
101
102
|
];
|
|
102
103
|
//#endregion
|
|
103
|
-
//#region source/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
//#region source/core/config-schema.ts
|
|
105
|
+
var clarifyLogoConfigSchema = z.union([z.string(), z.object({
|
|
106
|
+
light: z.string().optional(),
|
|
107
|
+
dark: z.string().optional()
|
|
108
|
+
})]);
|
|
109
|
+
var clarifyFaviconConfigSchema = z.union([z.string(), z.object({
|
|
110
|
+
light: z.string().optional(),
|
|
111
|
+
dark: z.string().optional()
|
|
112
|
+
})]);
|
|
113
|
+
var clarifyLocalizedTextSchema = z.union([z.string(), z.record(z.string(), z.string())]);
|
|
114
|
+
var clarifyLocaleConfigSchema = z.object({
|
|
115
|
+
code: z.string(),
|
|
116
|
+
label: z.string(),
|
|
117
|
+
dir: z.union([z.literal("ltr"), z.literal("rtl")]).optional()
|
|
118
|
+
});
|
|
119
|
+
var clarifyI18nConfigSchema = z.object({
|
|
120
|
+
defaultLocale: z.string().optional(),
|
|
121
|
+
missing: z.union([
|
|
122
|
+
z.literal("fallback"),
|
|
123
|
+
z.literal("404"),
|
|
124
|
+
z.literal("hide")
|
|
125
|
+
]).optional(),
|
|
126
|
+
locales: z.array(clarifyLocaleConfigSchema).min(1)
|
|
127
|
+
}).superRefine((config, ctx) => {
|
|
128
|
+
const localeCodes = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const [index, locale] of config.locales.entries()) {
|
|
130
|
+
if (localeCodes.has(locale.code)) ctx.addIssue({
|
|
131
|
+
code: z.ZodIssueCode.custom,
|
|
132
|
+
message: `Duplicate locale code "${locale.code}"`,
|
|
133
|
+
path: [
|
|
134
|
+
"locales",
|
|
135
|
+
index,
|
|
136
|
+
"code"
|
|
137
|
+
]
|
|
138
|
+
});
|
|
139
|
+
localeCodes.add(locale.code);
|
|
140
|
+
}
|
|
141
|
+
if (config.defaultLocale && !localeCodes.has(config.defaultLocale)) ctx.addIssue({
|
|
142
|
+
code: z.ZodIssueCode.custom,
|
|
143
|
+
message: "defaultLocale must be one of i18n.locales",
|
|
144
|
+
path: ["defaultLocale"]
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
var clarifyNavbarLinkSchema = z.object({
|
|
148
|
+
label: clarifyLocalizedTextSchema,
|
|
149
|
+
href: z.string(),
|
|
150
|
+
external: z.boolean().optional()
|
|
151
|
+
});
|
|
152
|
+
var clarifyBannerConfigSchema = z.object({
|
|
153
|
+
content: clarifyLocalizedTextSchema,
|
|
154
|
+
dismissible: z.boolean().optional()
|
|
155
|
+
});
|
|
156
|
+
var clarifyFooterConfigSchema = z.object({
|
|
157
|
+
links: z.array(clarifyNavbarLinkSchema).optional(),
|
|
158
|
+
socials: z.record(z.string(), z.string()).optional(),
|
|
159
|
+
copyright: clarifyLocalizedTextSchema.optional()
|
|
160
|
+
});
|
|
161
|
+
var clarifySourceConfigSchema = z.object({
|
|
162
|
+
repository: z.string(),
|
|
163
|
+
branch: z.string().optional(),
|
|
164
|
+
directory: z.string().optional()
|
|
165
|
+
});
|
|
166
|
+
var clarifyPagesItemSchema = z.union([
|
|
167
|
+
z.string(),
|
|
168
|
+
z.object({
|
|
169
|
+
page: z.string(),
|
|
170
|
+
title: clarifyLocalizedTextSchema.optional(),
|
|
171
|
+
icon: z.string().optional(),
|
|
172
|
+
redirect: z.string().optional()
|
|
173
|
+
}),
|
|
174
|
+
z.object({
|
|
175
|
+
openapi: z.string(),
|
|
176
|
+
icon: z.string().optional(),
|
|
177
|
+
title: clarifyLocalizedTextSchema.optional(),
|
|
178
|
+
filter: z.object({ tags: z.array(z.string()).optional() }).optional()
|
|
179
|
+
})
|
|
180
|
+
]);
|
|
181
|
+
var clarifyPagesGroupSchema = z.object({
|
|
182
|
+
group: clarifyLocalizedTextSchema,
|
|
183
|
+
icon: z.string().optional(),
|
|
184
|
+
pages: z.array(clarifyPagesItemSchema)
|
|
185
|
+
});
|
|
186
|
+
var clarifyPagesConfigSchema = z.union([z.array(clarifyPagesGroupSchema), z.literal("FileTree")]);
|
|
187
|
+
var clarifyTabItemSchema = z.object({
|
|
188
|
+
tab: clarifyLocalizedTextSchema,
|
|
189
|
+
icon: z.string().optional(),
|
|
190
|
+
pages: clarifyPagesConfigSchema.optional()
|
|
191
|
+
});
|
|
192
|
+
var clarifyTabsConfigSchema = z.array(clarifyTabItemSchema);
|
|
193
|
+
var clarifyThemePresetSchema = z.enum(["default", "base"]);
|
|
194
|
+
var clarifyThemeModeColorValueSchema = z.object({
|
|
195
|
+
light: z.string().optional(),
|
|
196
|
+
dark: z.string().optional()
|
|
197
|
+
});
|
|
198
|
+
var clarifyThemeColorValueSchema = z.union([z.string(), clarifyThemeModeColorValueSchema]);
|
|
199
|
+
var clarifyThemeColorTokensConfigSchema = z.object({
|
|
200
|
+
primary: clarifyThemeColorValueSchema.optional(),
|
|
201
|
+
accent: clarifyThemeColorValueSchema.optional(),
|
|
202
|
+
background: clarifyThemeColorValueSchema.optional(),
|
|
203
|
+
foreground: clarifyThemeColorValueSchema.optional(),
|
|
204
|
+
surface: clarifyThemeColorValueSchema.optional(),
|
|
205
|
+
muted: clarifyThemeColorValueSchema.optional(),
|
|
206
|
+
border: clarifyThemeColorValueSchema.optional(),
|
|
207
|
+
codeBackground: clarifyThemeColorValueSchema.optional()
|
|
208
|
+
});
|
|
209
|
+
var clarifyThemeRadiusTokensConfigSchema = z.object({
|
|
210
|
+
sm: z.string().optional(),
|
|
211
|
+
md: z.string().optional(),
|
|
212
|
+
lg: z.string().optional(),
|
|
213
|
+
xl: z.string().optional()
|
|
214
|
+
});
|
|
215
|
+
var clarifyThemeTokensConfigSchema = z.object({
|
|
216
|
+
colors: clarifyThemeColorTokensConfigSchema.optional(),
|
|
217
|
+
radius: clarifyThemeRadiusTokensConfigSchema.optional()
|
|
218
|
+
});
|
|
219
|
+
var clarifyThemeLayoutConfigSchema = z.object({ maxWidth: z.string().optional() });
|
|
220
|
+
var clarifyThemeConfigSchema = z.object({
|
|
221
|
+
preset: clarifyThemePresetSchema.optional(),
|
|
222
|
+
tokens: clarifyThemeTokensConfigSchema.optional(),
|
|
223
|
+
layout: clarifyThemeLayoutConfigSchema.optional(),
|
|
224
|
+
editor: z.boolean().optional()
|
|
225
|
+
});
|
|
226
|
+
var clarifyProjectConfigSchema = z.object({
|
|
227
|
+
title: z.string().optional(),
|
|
228
|
+
description: z.string().optional(),
|
|
229
|
+
siteUrl: z.string().optional(),
|
|
230
|
+
source: clarifySourceConfigSchema.optional(),
|
|
231
|
+
logo: clarifyLogoConfigSchema.optional(),
|
|
232
|
+
favicon: clarifyFaviconConfigSchema.optional(),
|
|
233
|
+
theme: clarifyThemeConfigSchema.optional(),
|
|
234
|
+
routePrefix: z.string().optional(),
|
|
235
|
+
navbar: z.object({ links: z.array(clarifyNavbarLinkSchema).optional() }).optional(),
|
|
236
|
+
banner: clarifyBannerConfigSchema.optional(),
|
|
237
|
+
footer: clarifyFooterConfigSchema.optional(),
|
|
238
|
+
i18n: clarifyI18nConfigSchema.optional(),
|
|
239
|
+
tabs: clarifyTabsConfigSchema.optional()
|
|
240
|
+
});
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region source/core/theme.ts
|
|
243
|
+
var clarifyThemePresets = {
|
|
244
|
+
default: {
|
|
245
|
+
preset: "default",
|
|
246
|
+
tokens: {
|
|
247
|
+
colors: {
|
|
248
|
+
primary: "#047857",
|
|
249
|
+
accent: "#0D9488",
|
|
250
|
+
background: "#ffffff",
|
|
251
|
+
foreground: "#111827",
|
|
252
|
+
surface: "#ffffff",
|
|
253
|
+
muted: "#64748b",
|
|
254
|
+
border: "rgb(15 23 42 / 0.12)",
|
|
255
|
+
codeBackground: "#f6fbf8"
|
|
256
|
+
},
|
|
257
|
+
radius: {
|
|
258
|
+
sm: "6px",
|
|
259
|
+
md: "10px",
|
|
260
|
+
lg: "14px",
|
|
261
|
+
xl: "18px"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
layout: { maxWidth: "82rem" },
|
|
265
|
+
editor: false
|
|
266
|
+
},
|
|
267
|
+
base: {
|
|
268
|
+
preset: "base",
|
|
269
|
+
tokens: {
|
|
270
|
+
colors: {
|
|
271
|
+
primary: "#18181b",
|
|
272
|
+
accent: "#52525b",
|
|
273
|
+
background: "#ffffff",
|
|
274
|
+
foreground: "#18181b",
|
|
275
|
+
surface: "#ffffff",
|
|
276
|
+
muted: "#71717a",
|
|
277
|
+
border: "rgb(24 24 27 / 0.12)",
|
|
278
|
+
codeBackground: "#f4f4f5"
|
|
279
|
+
},
|
|
280
|
+
radius: {
|
|
281
|
+
sm: "6px",
|
|
282
|
+
md: "8px",
|
|
283
|
+
lg: "12px",
|
|
284
|
+
xl: "16px"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
layout: { maxWidth: "80rem" },
|
|
288
|
+
editor: false
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
function resolveThemeConfig(theme = {}) {
|
|
292
|
+
const preset = theme.preset ?? "default";
|
|
293
|
+
const presetTheme = clarifyThemePresets[preset];
|
|
109
294
|
return {
|
|
110
|
-
|
|
111
|
-
|
|
295
|
+
preset,
|
|
296
|
+
tokens: {
|
|
297
|
+
colors: {
|
|
298
|
+
...presetTheme.tokens.colors,
|
|
299
|
+
...theme.tokens?.colors
|
|
300
|
+
},
|
|
301
|
+
radius: {
|
|
302
|
+
...presetTheme.tokens.radius,
|
|
303
|
+
...theme.tokens?.radius
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
layout: {
|
|
307
|
+
...presetTheme.layout,
|
|
308
|
+
...theme.layout
|
|
309
|
+
},
|
|
310
|
+
editor: theme.editor ?? presetTheme.editor
|
|
112
311
|
};
|
|
113
312
|
}
|
|
114
313
|
//#endregion
|
|
115
|
-
//#region source/
|
|
116
|
-
function
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
/** 从内容中提取第一个 H1 标题 */
|
|
123
|
-
function extractH1(content) {
|
|
124
|
-
const tree = parseMdxTree(content);
|
|
125
|
-
let title = "";
|
|
126
|
-
visit(tree, "heading", (node) => {
|
|
127
|
-
if (!title && node.depth === 1) title = toString(node);
|
|
128
|
-
});
|
|
129
|
-
return title;
|
|
314
|
+
//#region source/core/config.ts
|
|
315
|
+
function formatIssuePath(path) {
|
|
316
|
+
return path.reduce((result, segment) => {
|
|
317
|
+
if (typeof segment === "number") return `${result}[${segment}]`;
|
|
318
|
+
const key = String(segment);
|
|
319
|
+
return result ? `${result}.${key}` : key;
|
|
320
|
+
}, "");
|
|
130
321
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const title = toString(node);
|
|
138
|
-
sections.push({
|
|
139
|
-
id: slugger.slug(title),
|
|
140
|
-
title,
|
|
141
|
-
level: node.depth
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
return sections;
|
|
322
|
+
function formatProjectConfigError(error) {
|
|
323
|
+
const issue = error.issues[0];
|
|
324
|
+
if (!issue) return "[clarify] config is invalid";
|
|
325
|
+
const path = formatIssuePath(issue.path);
|
|
326
|
+
if (!path) return `[clarify] config must contain an object: ${issue.message}`;
|
|
327
|
+
return `[clarify] config field "${path}" is invalid: ${issue.message}`;
|
|
145
328
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
badge: s.badge,
|
|
151
|
-
tags: s.tags
|
|
152
|
-
}));
|
|
329
|
+
function validateProjectConfig(value) {
|
|
330
|
+
const result = clarifyProjectConfigSchema.safeParse(value);
|
|
331
|
+
if (!result.success) throw new Error(formatProjectConfigError(result.error));
|
|
332
|
+
return result.data;
|
|
153
333
|
}
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
334
|
+
function resolveI18nConfig(i18n) {
|
|
335
|
+
if (!i18n) return void 0;
|
|
336
|
+
const firstLocale = i18n.locales[0]?.code;
|
|
337
|
+
const defaultLocale = i18n.defaultLocale ?? firstLocale;
|
|
338
|
+
if (!defaultLocale) return void 0;
|
|
339
|
+
return {
|
|
340
|
+
defaultLocale,
|
|
341
|
+
missing: i18n.missing ?? "fallback",
|
|
342
|
+
locales: i18n.locales
|
|
343
|
+
};
|
|
157
344
|
}
|
|
158
|
-
function
|
|
345
|
+
function resolveProjectConfig(config = {}) {
|
|
346
|
+
return {
|
|
347
|
+
title: config.title ?? "Clarify Docs",
|
|
348
|
+
description: config.description ?? "",
|
|
349
|
+
siteUrl: config.siteUrl,
|
|
350
|
+
source: config.source,
|
|
351
|
+
logo: config.logo,
|
|
352
|
+
favicon: config.favicon,
|
|
353
|
+
routePrefix: config.routePrefix ?? "/",
|
|
354
|
+
theme: resolveThemeConfig(config.theme),
|
|
355
|
+
navbar: config.navbar,
|
|
356
|
+
banner: config.banner,
|
|
357
|
+
footer: config.footer,
|
|
358
|
+
i18n: resolveI18nConfig(config.i18n),
|
|
359
|
+
tabs: config.tabs
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region source/plugins/content-artifacts/artifacts.ts
|
|
364
|
+
var UTF8_SIGNATURE = "";
|
|
365
|
+
function normalizeBasePath(basePath) {
|
|
366
|
+
if (!basePath || basePath === "/") return "";
|
|
367
|
+
return "/" + basePath.replace(/^\/+|\/+$/g, "");
|
|
368
|
+
}
|
|
369
|
+
function containsNonAscii(content) {
|
|
370
|
+
return [...content].some((character) => character.codePointAt(0) > 127);
|
|
371
|
+
}
|
|
372
|
+
function withUtf8Signature(content) {
|
|
373
|
+
if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
|
|
374
|
+
return `${UTF8_SIGNATURE}${content}`;
|
|
375
|
+
}
|
|
376
|
+
function shouldUseUtf8Signature(route) {
|
|
377
|
+
if (route.kind === "mdx") return true;
|
|
378
|
+
return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
|
|
379
|
+
}
|
|
380
|
+
function routeToMarkdownArtifactUrl(routePath) {
|
|
381
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
|
|
382
|
+
}
|
|
383
|
+
function routeToOpenAPIArtifactUrl(routePath, filePath) {
|
|
384
|
+
const extension = extname(filePath).toLowerCase() || ".json";
|
|
385
|
+
return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
|
|
386
|
+
}
|
|
387
|
+
function attachContentArtifactUrls(routes) {
|
|
388
|
+
for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
|
|
389
|
+
}
|
|
390
|
+
function readRouteContent(route) {
|
|
391
|
+
if (route.content !== void 0) return route.content;
|
|
392
|
+
throw new Error(`Route content is missing from route context: ${route.filePath}`);
|
|
393
|
+
}
|
|
394
|
+
function readRouteArtifactContent(route) {
|
|
395
|
+
const content = readRouteContent(route);
|
|
396
|
+
return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
|
|
397
|
+
}
|
|
398
|
+
function writeContentArtifactFiles(routes, outputDirectory) {
|
|
399
|
+
for (const route of routes) {
|
|
400
|
+
if (!route.contentArtifactUrl) continue;
|
|
401
|
+
const outFile = join(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
|
|
402
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
403
|
+
writeFileSync(outFile, readRouteArtifactContent(route), "utf-8");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function createLlmsTxt(routes, projectConfig) {
|
|
407
|
+
const basePath = normalizeBasePath(projectConfig.routePrefix);
|
|
408
|
+
const lines = [`# ${projectConfig.title}`, ""];
|
|
409
|
+
if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
|
|
410
|
+
lines.push("## Docs");
|
|
411
|
+
for (const route of routes.filter((route) => route.kind === "mdx")) {
|
|
412
|
+
if (!route.contentArtifactUrl) continue;
|
|
413
|
+
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
414
|
+
}
|
|
415
|
+
const openApiRoutes = routes.filter((route) => route.kind === "openapi");
|
|
416
|
+
if (openApiRoutes.length > 0) {
|
|
417
|
+
lines.push("", "## OpenAPI");
|
|
418
|
+
for (const route of openApiRoutes) {
|
|
419
|
+
if (!route.contentArtifactUrl) continue;
|
|
420
|
+
lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return `${lines.join("\n")}\n`;
|
|
424
|
+
}
|
|
425
|
+
function createLlmsTxtArtifact(routes, projectConfig) {
|
|
426
|
+
return withUtf8Signature(createLlmsTxt(routes, projectConfig));
|
|
427
|
+
}
|
|
428
|
+
function writeLlmsTxt(routes, projectConfig, outputDirectory) {
|
|
429
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
430
|
+
writeFileSync(join(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region source/plugins/content-artifacts/server.ts
|
|
434
|
+
function normalizeRoutePrefix$1(routePrefix) {
|
|
435
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
436
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
437
|
+
}
|
|
438
|
+
function resolveContentArtifactPath(url, projectConfig) {
|
|
439
|
+
const requestPath = url?.split("?")[0] ?? "";
|
|
440
|
+
const basePath = normalizeRoutePrefix$1(projectConfig.routePrefix);
|
|
441
|
+
return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
|
|
442
|
+
}
|
|
443
|
+
function resolveContentArtifactType(route) {
|
|
444
|
+
if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
|
|
445
|
+
return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
|
|
446
|
+
}
|
|
447
|
+
function serveContentArtifacts(req, res, projectConfig, routes) {
|
|
448
|
+
const contentPath = resolveContentArtifactPath(req.url, projectConfig);
|
|
449
|
+
if (contentPath === "/llms.txt") {
|
|
450
|
+
res.statusCode = 200;
|
|
451
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
452
|
+
res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
const route = routes.find((route) => route.contentArtifactUrl === contentPath);
|
|
456
|
+
if (!route) return false;
|
|
457
|
+
res.statusCode = 200;
|
|
458
|
+
res.setHeader("Content-Type", resolveContentArtifactType(route));
|
|
459
|
+
res.end(readRouteArtifactContent(route), "utf8");
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region source/plugins/content-artifacts/index.ts
|
|
464
|
+
function createContentArtifactsPlugin() {
|
|
465
|
+
return {
|
|
466
|
+
name: "clarify:content-artifacts",
|
|
467
|
+
hooks: {
|
|
468
|
+
"routes:resolved": (input) => {
|
|
469
|
+
attachContentArtifactUrls(input.routes);
|
|
470
|
+
return input;
|
|
471
|
+
},
|
|
472
|
+
"dev:configureServer": (server, ctx) => {
|
|
473
|
+
server.middlewares.use((req, res, next) => {
|
|
474
|
+
if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
|
|
475
|
+
next();
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
"build:done": (ctx) => {
|
|
479
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
480
|
+
if (!outputDirectory) return;
|
|
481
|
+
const outputRoot = join(ctx.generateOptions.projectRoot, outputDirectory);
|
|
482
|
+
writeContentArtifactFiles(ctx.routes, outputRoot);
|
|
483
|
+
writeLlmsTxt(ctx.routes, ctx.projectConfig, outputRoot);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region source/plugins/html-shell/index.ts
|
|
490
|
+
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){}})();`;
|
|
491
|
+
var REL_ICON_RE = /<link\s+[^>]*rel=["'][^"']*\bicon\b[^"']*["'][^>]*>/i;
|
|
492
|
+
function inferIconType(href) {
|
|
493
|
+
const path = href.split(/[?#]/, 1)[0]?.toLowerCase() ?? href.toLowerCase();
|
|
494
|
+
if (path.endsWith(".svg")) return "image/svg+xml";
|
|
495
|
+
if (path.endsWith(".png")) return "image/png";
|
|
496
|
+
if (path.endsWith(".ico")) return "image/x-icon";
|
|
497
|
+
if (path.endsWith(".webp")) return "image/webp";
|
|
498
|
+
}
|
|
499
|
+
function createIconTag(href, media) {
|
|
500
|
+
return {
|
|
501
|
+
tag: "link",
|
|
502
|
+
attrs: {
|
|
503
|
+
rel: "icon",
|
|
504
|
+
...inferIconType(href) ? { type: inferIconType(href) } : {},
|
|
505
|
+
href,
|
|
506
|
+
...media ? { media } : {}
|
|
507
|
+
},
|
|
508
|
+
injectTo: "head"
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function createIconTags(icon) {
|
|
512
|
+
if (!icon) return [];
|
|
513
|
+
if (typeof icon === "string") return [createIconTag(icon)];
|
|
514
|
+
const lightIcon = icon.light ?? icon.dark;
|
|
515
|
+
const darkIcon = icon.dark ?? icon.light;
|
|
516
|
+
if (!lightIcon || !darkIcon) return [];
|
|
517
|
+
if (lightIcon === darkIcon) return [createIconTag(lightIcon)];
|
|
518
|
+
return [createIconTag(lightIcon, "(prefers-color-scheme: light)"), createIconTag(darkIcon, "(prefers-color-scheme: dark)")];
|
|
519
|
+
}
|
|
520
|
+
function createHtmlShellPlugin() {
|
|
521
|
+
return {
|
|
522
|
+
name: "clarify:html-shell",
|
|
523
|
+
hooks: { "html:transform": (input, ctx) => ({
|
|
524
|
+
html: input.html,
|
|
525
|
+
tags: [
|
|
526
|
+
...input.tags,
|
|
527
|
+
...REL_ICON_RE.test(input.html) ? [] : createIconTags(ctx.projectConfig.favicon ?? ctx.projectConfig.logo),
|
|
528
|
+
{
|
|
529
|
+
tag: "script",
|
|
530
|
+
children: THEME_BOOTSTRAP_SCRIPT,
|
|
531
|
+
injectTo: "head-prepend"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
tag: "script",
|
|
535
|
+
attrs: {
|
|
536
|
+
type: "module",
|
|
537
|
+
src: input.clientEntryId
|
|
538
|
+
},
|
|
539
|
+
injectTo: "body"
|
|
540
|
+
}
|
|
541
|
+
],
|
|
542
|
+
clientEntryId: input.clientEntryId,
|
|
543
|
+
dev: input.dev
|
|
544
|
+
}) }
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region source/parsers/frontmatter.ts
|
|
549
|
+
/**
|
|
550
|
+
* 在内容入口统一解析 MDX frontmatter,并返回可供后续流程复用的标准化正文。
|
|
551
|
+
*/
|
|
552
|
+
function parseFrontmatter(content) {
|
|
553
|
+
const parsed = matter(content);
|
|
554
|
+
return {
|
|
555
|
+
frontmatter: parsed.data,
|
|
556
|
+
content: parsed.content.replace(/^\r?\n/, "")
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region source/parsers/routes.ts
|
|
561
|
+
function kebabToTitle$1(str) {
|
|
562
|
+
return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
563
|
+
}
|
|
564
|
+
function parseMdxTree(content) {
|
|
565
|
+
return remark.parse(content);
|
|
566
|
+
}
|
|
567
|
+
/** 从内容中提取第一个 H1 标题 */
|
|
568
|
+
function extractH1(content) {
|
|
569
|
+
const tree = parseMdxTree(content);
|
|
570
|
+
let title = "";
|
|
571
|
+
visit(tree, "heading", (node) => {
|
|
572
|
+
if (!title && node.depth === 1) title = toString(node);
|
|
573
|
+
});
|
|
574
|
+
return title;
|
|
575
|
+
}
|
|
576
|
+
/** 从 MDX/Markdown 内容中提取 H2/H3 章节 */
|
|
577
|
+
function extractMdxSections(content) {
|
|
578
|
+
const sections = [];
|
|
579
|
+
const slugger = new GithubSlugger();
|
|
580
|
+
visit(parseMdxTree(content), "heading", (node) => {
|
|
581
|
+
if (node.depth !== 2 && node.depth !== 3) return;
|
|
582
|
+
const title = toString(node);
|
|
583
|
+
sections.push({
|
|
584
|
+
id: slugger.slug(title),
|
|
585
|
+
title,
|
|
586
|
+
level: node.depth
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
return sections;
|
|
590
|
+
}
|
|
591
|
+
function navigationSections(sections) {
|
|
592
|
+
return sections.map((s) => ({
|
|
593
|
+
id: s.id,
|
|
594
|
+
title: s.title,
|
|
595
|
+
badge: s.badge,
|
|
596
|
+
tags: s.tags
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
function normalizePath(path) {
|
|
600
|
+
const clean = "/" + path.replace(/^\/+|\/+$/g, "");
|
|
601
|
+
return clean === "/" ? "/" : clean.replace(/\/+/g, "/");
|
|
602
|
+
}
|
|
603
|
+
function pathSegmentFromOpenAPITag(tag) {
|
|
604
|
+
return tag.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tag";
|
|
605
|
+
}
|
|
606
|
+
function openAPITagsPathSegment(tags) {
|
|
607
|
+
return tags.map(pathSegmentFromOpenAPITag).join("-");
|
|
608
|
+
}
|
|
609
|
+
function basePathFromRef(ref) {
|
|
159
610
|
const withoutExtension = ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "");
|
|
160
611
|
return normalizePath(withoutExtension === "index" ? "/" : withoutExtension.replace(/\/index$/, ""));
|
|
161
612
|
}
|
|
613
|
+
function openAPIPagePathFromRef(ref, tagFilter) {
|
|
614
|
+
const basePath = basePathFromRef(ref);
|
|
615
|
+
if (!tagFilter?.length) return basePath;
|
|
616
|
+
return normalizePath(`${basePath}/${openAPITagsPathSegment(tagFilter)}`);
|
|
617
|
+
}
|
|
162
618
|
function routePathFromRef(ref) {
|
|
163
619
|
return basePathFromRef(ref);
|
|
164
620
|
}
|
|
@@ -217,7 +673,7 @@ function frontmatterKeywords(frontmatter) {
|
|
|
217
673
|
function buildNavigation(routes) {
|
|
218
674
|
const root = [];
|
|
219
675
|
for (const route of routes) {
|
|
220
|
-
if (route.path === "/") continue;
|
|
676
|
+
if (route.path === "/" || (route.basePath ?? route.path) === "/404") continue;
|
|
221
677
|
const parts = route.path.replace(/^\//, "").split("/");
|
|
222
678
|
let current = root;
|
|
223
679
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -244,6 +700,7 @@ function resolvePageItem(item) {
|
|
|
244
700
|
if (typeof item === "string") return { pageRef: item };
|
|
245
701
|
if ("openapi" in item) return {
|
|
246
702
|
openapiRef: item.openapi,
|
|
703
|
+
openapiTagFilter: item.filter?.tags,
|
|
247
704
|
title: item.title,
|
|
248
705
|
icon: item.icon
|
|
249
706
|
};
|
|
@@ -282,9 +739,9 @@ function buildNavigationFromConfig(routes, config) {
|
|
|
282
739
|
const routeMap = new Map(routes.map((r) => [r.path, r]));
|
|
283
740
|
return config.map((group) => {
|
|
284
741
|
const children = group.pages.map((item) => {
|
|
285
|
-
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
742
|
+
const { pageRef, openapiRef, openapiTagFilter, redirect, title, icon } = resolvePageItem(item);
|
|
286
743
|
if (openapiRef) {
|
|
287
|
-
const path =
|
|
744
|
+
const path = openAPIPagePathFromRef(openapiRef, openapiTagFilter);
|
|
288
745
|
const route = routeMap.get(path);
|
|
289
746
|
return {
|
|
290
747
|
path,
|
|
@@ -326,9 +783,9 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
|
|
|
326
783
|
const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
|
|
327
784
|
return config.map((group) => {
|
|
328
785
|
const children = group.pages.map((item) => {
|
|
329
|
-
const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
|
|
786
|
+
const { pageRef, openapiRef, openapiTagFilter, redirect, title, icon } = resolvePageItem(item);
|
|
330
787
|
const ref = openapiRef ?? pageRef ?? "";
|
|
331
|
-
const basePath = basePathFromRef(ref);
|
|
788
|
+
const basePath = openapiRef ? openAPIPagePathFromRef(openapiRef, openapiTagFilter) : basePathFromRef(ref);
|
|
332
789
|
const route = routeMap.get(basePath);
|
|
333
790
|
const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
|
|
334
791
|
return {
|
|
@@ -365,190 +822,6 @@ function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
|
|
|
365
822
|
return result;
|
|
366
823
|
}
|
|
367
824
|
//#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
|
-
};
|
|
550
|
-
}
|
|
551
|
-
//#endregion
|
|
552
825
|
//#region source/plugins/openapi/parser.ts
|
|
553
826
|
var OPENAPI_HTTP_METHODS = [
|
|
554
827
|
"get",
|
|
@@ -591,21 +864,26 @@ function findOpenAPIRoutes(dir, base = dir) {
|
|
|
591
864
|
}
|
|
592
865
|
return routes;
|
|
593
866
|
}
|
|
867
|
+
function operationMatchesTags(operationTags, filterTags) {
|
|
868
|
+
if (!filterTags?.length) return true;
|
|
869
|
+
return operationTags?.some((tag) => filterTags.includes(tag)) ?? false;
|
|
870
|
+
}
|
|
594
871
|
/** Extract endpoint operations from an OpenAPI spec as page sections. */
|
|
595
|
-
function extractOpenAPISections(spec) {
|
|
872
|
+
function extractOpenAPISections(spec, filterTags) {
|
|
596
873
|
const sections = [];
|
|
597
874
|
const paths = spec.paths ?? {};
|
|
598
875
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
599
876
|
if (!pathItem) continue;
|
|
600
877
|
for (const method of OPENAPI_HTTP_METHODS) {
|
|
601
878
|
const op = pathItem[method];
|
|
602
|
-
if (!op) continue;
|
|
879
|
+
if (!op || !operationMatchesTags(op.tags, filterTags)) continue;
|
|
603
880
|
const title = op.summary ?? `${method.toUpperCase()} ${path}`;
|
|
604
881
|
sections.push({
|
|
605
882
|
id: slug(`${method} ${path}`),
|
|
606
883
|
title,
|
|
607
884
|
level: 2,
|
|
608
|
-
badge: method.toUpperCase()
|
|
885
|
+
badge: method.toUpperCase(),
|
|
886
|
+
tags: op.tags
|
|
609
887
|
});
|
|
610
888
|
}
|
|
611
889
|
}
|
|
@@ -630,320 +908,315 @@ async function readOpenAPISpec(filePath) {
|
|
|
630
908
|
}
|
|
631
909
|
}
|
|
632
910
|
//#endregion
|
|
633
|
-
//#region source/
|
|
634
|
-
var
|
|
635
|
-
|
|
636
|
-
|
|
911
|
+
//#region source/core/virtual-modules.ts
|
|
912
|
+
var VIRTUAL_CONFIG = "virtual:clarify-config";
|
|
913
|
+
var VIRTUAL_ROUTES = "virtual:clarify-routes";
|
|
914
|
+
var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
|
|
915
|
+
var VIRTUAL_RUNTIME = "virtual:clarify-runtime";
|
|
916
|
+
var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
|
|
917
|
+
var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
|
|
918
|
+
function resolveVirtualId(id) {
|
|
919
|
+
return "\0" + id;
|
|
637
920
|
}
|
|
638
|
-
function
|
|
639
|
-
return
|
|
640
|
-
import { OpenApiDocument } from '@clarify-labs/renderer';
|
|
641
|
-
const spec = ${JSON.stringify(spec)};
|
|
642
|
-
export default function OpenApiRoutePage() {
|
|
643
|
-
return createElement(OpenApiDocument, { spec });
|
|
644
|
-
}`;
|
|
921
|
+
function stripVirtualPrefix(id) {
|
|
922
|
+
return id.startsWith("\0") ? id.slice(1) : id;
|
|
645
923
|
}
|
|
646
|
-
function
|
|
647
|
-
return `
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
createElement('div', { className: 'prose max-w-none flex-auto dark:prose-invert' },
|
|
652
|
-
createElement('p', { className: 'mb-3 text-xs/6 font-medium tracking-widest text-red-500 uppercase dark:text-red-400' }, 'OpenAPI Error'),
|
|
653
|
-
createElement('h1', null, diagnostic.title),
|
|
654
|
-
createElement('p', { className: 'lead' }, diagnostic.message),
|
|
655
|
-
diagnostic.filePath ? createElement('div', { className: 'not-prose mt-6 rounded-lg border border-zinc-200 bg-zinc-50 p-4 text-sm dark:border-white/10 dark:bg-white/5' },
|
|
656
|
-
createElement('div', { className: 'mb-1 font-semibold text-zinc-900 dark:text-white' }, 'File'),
|
|
657
|
-
createElement('code', { className: 'break-all text-zinc-700 dark:text-zinc-300' }, diagnostic.filePath)
|
|
658
|
-
) : null,
|
|
659
|
-
diagnostic.cause ? createElement('div', { className: 'not-prose mt-4 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200' },
|
|
660
|
-
createElement('div', { className: 'mb-1 font-semibold' }, 'Why it happened'),
|
|
661
|
-
createElement('pre', { className: 'whitespace-pre-wrap break-words text-xs' }, diagnostic.cause)
|
|
662
|
-
) : null
|
|
663
|
-
)
|
|
664
|
-
);
|
|
665
|
-
}`;
|
|
924
|
+
function generateConfigModule(projectConfig, buildOptions) {
|
|
925
|
+
return `export const config = ${JSON.stringify({
|
|
926
|
+
...projectConfig,
|
|
927
|
+
...buildOptions
|
|
928
|
+
})};`;
|
|
666
929
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
function createOpenAPIPlugin() {
|
|
670
|
-
const specs = {};
|
|
671
|
-
return {
|
|
672
|
-
name: "clarify:openapi",
|
|
673
|
-
hooks: {
|
|
674
|
-
"routes:discover": (input) => ({
|
|
675
|
-
...input,
|
|
676
|
-
routes: [...input.routes, ...findOpenAPIRoutes(input.contentRoot)]
|
|
677
|
-
}),
|
|
678
|
-
"routes:discovered": async (routes) => {
|
|
679
|
-
for (const key of Object.keys(specs)) delete specs[key];
|
|
680
|
-
for (const route of routes.filter((route) => route.kind === "openapi")) {
|
|
681
|
-
const result = await readOpenAPISpec(route.filePath);
|
|
682
|
-
if (!result.ok) {
|
|
683
|
-
route.diagnostic = result.diagnostic;
|
|
684
|
-
route.title = route.title || "OpenAPI parse error";
|
|
685
|
-
route.sections = [];
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
const spec = result.spec;
|
|
689
|
-
specs[route.virtualModuleId] = spec;
|
|
690
|
-
specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = spec;
|
|
691
|
-
route.title = spec.info?.title ?? route.title;
|
|
692
|
-
route.description = spec.info?.description ?? route.description;
|
|
693
|
-
route.sections = extractOpenAPISections(spec);
|
|
694
|
-
}
|
|
695
|
-
return routes;
|
|
696
|
-
},
|
|
697
|
-
"modules:before": (modules, ctx) => {
|
|
698
|
-
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(specs));
|
|
699
|
-
for (const [moduleId, spec] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(spec));
|
|
700
|
-
for (const route of ctx.routes.filter((route) => route.kind === "openapi" && route.diagnostic)) if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
|
|
701
|
-
return modules;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
};
|
|
930
|
+
function moduleSpecifier(value) {
|
|
931
|
+
return JSON.stringify(value);
|
|
705
932
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
]
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
muted: z.string().optional(),
|
|
798
|
-
border: z.string().optional(),
|
|
799
|
-
codeBackground: z.string().optional()
|
|
800
|
-
});
|
|
801
|
-
var clarifyThemeRadiusTokensConfigSchema = z.object({
|
|
802
|
-
sm: z.string().optional(),
|
|
803
|
-
md: z.string().optional(),
|
|
804
|
-
lg: z.string().optional(),
|
|
805
|
-
xl: z.string().optional()
|
|
806
|
-
});
|
|
807
|
-
var clarifyThemeTokensConfigSchema = z.object({
|
|
808
|
-
colors: clarifyThemeColorTokensConfigSchema.optional(),
|
|
809
|
-
radius: clarifyThemeRadiusTokensConfigSchema.optional()
|
|
810
|
-
});
|
|
811
|
-
var clarifyThemeLayoutConfigSchema = z.object({ maxWidth: z.string().optional() });
|
|
812
|
-
var clarifyThemeConfigSchema = z.object({
|
|
813
|
-
preset: clarifyThemePresetSchema.optional(),
|
|
814
|
-
tokens: clarifyThemeTokensConfigSchema.optional(),
|
|
815
|
-
layout: clarifyThemeLayoutConfigSchema.optional()
|
|
816
|
-
});
|
|
817
|
-
var clarifyProjectConfigSchema = z.object({
|
|
818
|
-
title: z.string().optional(),
|
|
819
|
-
description: z.string().optional(),
|
|
820
|
-
logo: clarifyLogoConfigSchema.optional(),
|
|
821
|
-
favicon: clarifyFaviconConfigSchema.optional(),
|
|
822
|
-
theme: clarifyThemeConfigSchema.optional(),
|
|
823
|
-
routePrefix: z.string().optional(),
|
|
824
|
-
navbar: z.object({ links: z.array(clarifyNavbarLinkSchema).optional() }).optional(),
|
|
825
|
-
banner: clarifyBannerConfigSchema.optional(),
|
|
826
|
-
footer: clarifyFooterConfigSchema.optional(),
|
|
827
|
-
i18n: clarifyI18nConfigSchema.optional(),
|
|
828
|
-
tabs: clarifyTabsConfigSchema.optional()
|
|
829
|
-
});
|
|
830
|
-
//#endregion
|
|
831
|
-
//#region source/core/theme.ts
|
|
832
|
-
var clarifyThemePresets = {
|
|
833
|
-
default: {
|
|
834
|
-
preset: "default",
|
|
835
|
-
tokens: {
|
|
836
|
-
colors: {
|
|
837
|
-
primary: "#00D492",
|
|
838
|
-
accent: "#00F6C9",
|
|
839
|
-
background: "#ffffff",
|
|
840
|
-
foreground: "#111827",
|
|
841
|
-
surface: "#ffffff",
|
|
842
|
-
muted: "#64748b",
|
|
843
|
-
border: "rgb(15 23 42 / 0.12)",
|
|
844
|
-
codeBackground: "#f7fefb"
|
|
845
|
-
},
|
|
846
|
-
radius: {
|
|
847
|
-
sm: "6px",
|
|
848
|
-
md: "10px",
|
|
849
|
-
lg: "14px",
|
|
850
|
-
xl: "18px"
|
|
851
|
-
}
|
|
852
|
-
},
|
|
853
|
-
layout: { maxWidth: "82rem" }
|
|
854
|
-
},
|
|
855
|
-
base: {
|
|
856
|
-
preset: "base",
|
|
857
|
-
tokens: {
|
|
858
|
-
colors: {
|
|
859
|
-
primary: "#18181b",
|
|
860
|
-
accent: "#52525b",
|
|
861
|
-
background: "#ffffff",
|
|
862
|
-
foreground: "#18181b",
|
|
863
|
-
surface: "#ffffff",
|
|
864
|
-
muted: "#71717a",
|
|
865
|
-
border: "rgb(24 24 27 / 0.12)",
|
|
866
|
-
codeBackground: "#f4f4f5"
|
|
867
|
-
},
|
|
868
|
-
radius: {
|
|
869
|
-
sm: "6px",
|
|
870
|
-
md: "8px",
|
|
871
|
-
lg: "12px",
|
|
872
|
-
xl: "16px"
|
|
873
|
-
}
|
|
874
|
-
},
|
|
875
|
-
layout: { maxWidth: "80rem" }
|
|
933
|
+
function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
|
|
934
|
+
const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
|
|
935
|
+
const routesArray = routes.map((r, i) => {
|
|
936
|
+
const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
|
|
937
|
+
id: s.id,
|
|
938
|
+
title: s.title,
|
|
939
|
+
badge: s.badge,
|
|
940
|
+
tags: s.tags
|
|
941
|
+
})))}` : "";
|
|
942
|
+
const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
|
|
943
|
+
const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
|
|
944
|
+
const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
|
|
945
|
+
const isFallback = r.isFallback ? ", isFallback: true" : "";
|
|
946
|
+
const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
|
|
947
|
+
const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
|
|
948
|
+
const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
|
|
949
|
+
const sourceUrl = r.sourceUrl ? `, sourceUrl: ${JSON.stringify(r.sourceUrl)}` : "";
|
|
950
|
+
const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
|
|
951
|
+
const lazy = mode === "client" ? ", lazy: true" : "";
|
|
952
|
+
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} }`;
|
|
953
|
+
}).join(",\n");
|
|
954
|
+
const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
|
|
955
|
+
return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
|
|
956
|
+
}
|
|
957
|
+
function createRuntimeModule() {
|
|
958
|
+
return "export const openApis = {};";
|
|
959
|
+
}
|
|
960
|
+
function createClientEntryModule(options = {}) {
|
|
961
|
+
return `
|
|
962
|
+
import '@clarify-labs/renderer/style.css';
|
|
963
|
+
import { render } from '@clarify-labs/renderer/client';
|
|
964
|
+
import { routes, navigation } from '${VIRTUAL_ROUTES}';
|
|
965
|
+
import { config } from '${VIRTUAL_CONFIG}';
|
|
966
|
+
import { openApis } from '${VIRTUAL_RUNTIME}';
|
|
967
|
+
render({ config, routes, navigation, openApis, themeEditor: ${JSON.stringify(options.themeEditor ?? false)} });`;
|
|
968
|
+
}
|
|
969
|
+
function buildVirtualModules(args) {
|
|
970
|
+
const modules = /* @__PURE__ */ new Map();
|
|
971
|
+
const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
|
|
972
|
+
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
|
|
973
|
+
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
|
|
974
|
+
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
|
|
975
|
+
modules.set(VIRTUAL_RUNTIME, createRuntimeModule());
|
|
976
|
+
modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
|
|
977
|
+
modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
|
|
978
|
+
for (const route of args.routes) modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
|
|
979
|
+
return modules;
|
|
980
|
+
}
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region source/plugins/openapi/virtual-modules.ts
|
|
983
|
+
var openApiRegistryModuleId = VIRTUAL_RUNTIME;
|
|
984
|
+
function generateOpenAPIRegistryModule(openApis) {
|
|
985
|
+
return `export const openApis = ${JSON.stringify(openApis)};`;
|
|
986
|
+
}
|
|
987
|
+
function generateOpenAPIModule(spec, tagFilter) {
|
|
988
|
+
return `import { createElement } from 'react';
|
|
989
|
+
import { OpenApiDocument } from '@clarify-labs/renderer';
|
|
990
|
+
const spec = ${JSON.stringify(spec)};
|
|
991
|
+
const tagFilter = ${JSON.stringify(tagFilter)};
|
|
992
|
+
export default function OpenApiRoutePage() {
|
|
993
|
+
return createElement(OpenApiDocument, { spec, tagFilter });
|
|
994
|
+
}`;
|
|
995
|
+
}
|
|
996
|
+
function generateOpenAPIErrorModule(diagnostic) {
|
|
997
|
+
return `import { createElement } from 'react';
|
|
998
|
+
const diagnostic = ${JSON.stringify(diagnostic)};
|
|
999
|
+
export default function OpenApiErrorRoutePage() {
|
|
1000
|
+
return createElement('article', { className: 'flex h-full flex-col pt-16 pb-10' },
|
|
1001
|
+
createElement('div', { className: 'prose max-w-none flex-auto dark:prose-invert' },
|
|
1002
|
+
createElement('p', { className: 'mb-3 text-xs/6 font-medium tracking-widest text-red-500 uppercase dark:text-red-400' }, 'OpenAPI Error'),
|
|
1003
|
+
createElement('h1', null, diagnostic.title),
|
|
1004
|
+
createElement('p', { className: 'lead' }, diagnostic.message),
|
|
1005
|
+
diagnostic.filePath ? createElement('div', { className: 'not-prose mt-6 rounded-lg border border-zinc-200 bg-zinc-50 p-4 text-sm dark:border-white/10 dark:bg-white/5' },
|
|
1006
|
+
createElement('div', { className: 'mb-1 font-semibold text-zinc-900 dark:text-white' }, 'File'),
|
|
1007
|
+
createElement('code', { className: 'break-all text-zinc-700 dark:text-zinc-300' }, diagnostic.filePath)
|
|
1008
|
+
) : null,
|
|
1009
|
+
diagnostic.cause ? createElement('div', { className: 'not-prose mt-4 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200' },
|
|
1010
|
+
createElement('div', { className: 'mb-1 font-semibold' }, 'Why it happened'),
|
|
1011
|
+
createElement('pre', { className: 'whitespace-pre-wrap break-words text-xs' }, diagnostic.cause)
|
|
1012
|
+
) : null
|
|
1013
|
+
)
|
|
1014
|
+
);
|
|
1015
|
+
}`;
|
|
1016
|
+
}
|
|
1017
|
+
//#endregion
|
|
1018
|
+
//#region source/plugins/openapi/index.ts
|
|
1019
|
+
function collectOpenAPIPageItems(config) {
|
|
1020
|
+
const items = [];
|
|
1021
|
+
function visitPages(pages) {
|
|
1022
|
+
if (!pages || pages === "FileTree") return;
|
|
1023
|
+
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);
|
|
876
1024
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1025
|
+
for (const tab of config.tabs ?? []) visitPages(tab.pages);
|
|
1026
|
+
return items;
|
|
1027
|
+
}
|
|
1028
|
+
function withAlternates$1(route, routes, i18n) {
|
|
1029
|
+
const basePath = route.basePath ?? route.path;
|
|
1030
|
+
const routeByLocaleAndBase = new Map(routes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1031
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1032
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1033
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1034
|
+
}));
|
|
881
1035
|
return {
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1036
|
+
...route,
|
|
1037
|
+
alternates
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function createTaggedOpenAPIRoutes(routes, config) {
|
|
1041
|
+
const pageItems = collectOpenAPIPageItems(config);
|
|
1042
|
+
if (!pageItems.length) return routes;
|
|
1043
|
+
const additions = [];
|
|
1044
|
+
const existingKeys = new Set(routes.map((route) => `${route.locale ?? ""}:${route.basePath ?? route.path}`));
|
|
1045
|
+
for (const item of pageItems) {
|
|
1046
|
+
const tagFilter = item.filter?.tags?.filter(Boolean);
|
|
1047
|
+
if (!tagFilter?.length) continue;
|
|
1048
|
+
const sourceBasePath = openAPIPagePathFromRef(item.openapi);
|
|
1049
|
+
const filteredBasePath = openAPIPagePathFromRef(item.openapi, tagFilter);
|
|
1050
|
+
for (const route of routes) {
|
|
1051
|
+
if (route.kind !== "openapi" || (route.basePath ?? route.path) !== sourceBasePath) continue;
|
|
1052
|
+
const key = `${route.locale ?? ""}:${filteredBasePath}`;
|
|
1053
|
+
if (existingKeys.has(key)) continue;
|
|
1054
|
+
existingKeys.add(key);
|
|
1055
|
+
additions.push({
|
|
1056
|
+
...route,
|
|
1057
|
+
path: route.locale && config.i18n ? localizedRoutePath(filteredBasePath, route.locale, config.i18n) : filteredBasePath,
|
|
1058
|
+
basePath: filteredBasePath,
|
|
1059
|
+
title: route.title,
|
|
1060
|
+
virtualModuleId: `${route.virtualModuleId}/${openAPITagsPathSegment(tagFilter)}`,
|
|
1061
|
+
openapiTagFilter: tagFilter
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
const nextRoutes = [...routes, ...additions];
|
|
1066
|
+
return config.i18n ? nextRoutes.map((route) => withAlternates$1(route, nextRoutes, config.i18n)) : nextRoutes;
|
|
1067
|
+
}
|
|
1068
|
+
function createOpenAPIPlugin() {
|
|
1069
|
+
const specs = {};
|
|
1070
|
+
return {
|
|
1071
|
+
name: "clarify:openapi",
|
|
1072
|
+
hooks: {
|
|
1073
|
+
"routes:discover": (input) => ({
|
|
1074
|
+
...input,
|
|
1075
|
+
routes: [...input.routes, ...findOpenAPIRoutes(input.contentRoot)]
|
|
1076
|
+
}),
|
|
1077
|
+
"routes:discovered": async (routes, ctx) => {
|
|
1078
|
+
for (const key of Object.keys(specs)) delete specs[key];
|
|
1079
|
+
const nextRoutes = createTaggedOpenAPIRoutes(routes, ctx.projectConfig);
|
|
1080
|
+
const specByFilePath = /* @__PURE__ */ new Map();
|
|
1081
|
+
for (const route of nextRoutes.filter((route) => route.kind === "openapi")) {
|
|
1082
|
+
const specFromCache = specByFilePath.get(route.filePath);
|
|
1083
|
+
const result = specFromCache ? {
|
|
1084
|
+
ok: true,
|
|
1085
|
+
spec: specFromCache
|
|
1086
|
+
} : await readOpenAPISpec(route.filePath);
|
|
1087
|
+
if (!result.ok) {
|
|
1088
|
+
route.diagnostic = result.diagnostic;
|
|
1089
|
+
route.title = route.title || "OpenAPI parse error";
|
|
1090
|
+
route.sections = [];
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const spec = result.spec;
|
|
1094
|
+
specByFilePath.set(route.filePath, spec);
|
|
1095
|
+
specs[route.virtualModuleId] = {
|
|
1096
|
+
spec,
|
|
1097
|
+
tagFilter: route.openapiTagFilter
|
|
1098
|
+
};
|
|
1099
|
+
specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = {
|
|
1100
|
+
spec,
|
|
1101
|
+
tagFilter: route.openapiTagFilter
|
|
1102
|
+
};
|
|
1103
|
+
route.title = spec.info?.title ?? route.title;
|
|
1104
|
+
route.description = spec.info?.description ?? route.description;
|
|
1105
|
+
route.sections = extractOpenAPISections(spec, route.openapiTagFilter);
|
|
1106
|
+
}
|
|
1107
|
+
return nextRoutes;
|
|
887
1108
|
},
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1109
|
+
"modules:before": (modules, ctx) => {
|
|
1110
|
+
modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(Object.fromEntries(Object.entries(specs).map(([moduleId, entry]) => [moduleId, entry.spec]))));
|
|
1111
|
+
for (const [moduleId, entry] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(entry.spec, entry.tagFilter));
|
|
1112
|
+
for (const route of ctx.routes.filter((route) => route.kind === "openapi" && route.diagnostic)) if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
|
|
1113
|
+
return modules;
|
|
891
1114
|
}
|
|
892
|
-
},
|
|
893
|
-
layout: {
|
|
894
|
-
...presetTheme.layout,
|
|
895
|
-
...theme.layout
|
|
896
1115
|
}
|
|
897
1116
|
};
|
|
898
1117
|
}
|
|
899
1118
|
//#endregion
|
|
900
|
-
//#region source/
|
|
901
|
-
function
|
|
902
|
-
return
|
|
903
|
-
if (typeof segment === "number") return `${result}[${segment}]`;
|
|
904
|
-
const key = String(segment);
|
|
905
|
-
return result ? `${result}.${key}` : key;
|
|
906
|
-
}, "");
|
|
1119
|
+
//#region source/plugins/seo/artifacts.ts
|
|
1120
|
+
function trimTrailingSlash(value) {
|
|
1121
|
+
return value.replace(/\/+$/, "");
|
|
907
1122
|
}
|
|
908
|
-
function
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const path = formatIssuePath(issue.path);
|
|
912
|
-
if (!path) return `[clarify] config must contain an object: ${issue.message}`;
|
|
913
|
-
return `[clarify] config field "${path}" is invalid: ${issue.message}`;
|
|
1123
|
+
function normalizeRoutePrefix(routePrefix) {
|
|
1124
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
1125
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
914
1126
|
}
|
|
915
|
-
function
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
return result.data;
|
|
1127
|
+
function routeUrl(siteUrl, routePrefix, routePath) {
|
|
1128
|
+
const base = trimTrailingSlash(siteUrl);
|
|
1129
|
+
return `${base}${normalizeRoutePrefix(routePrefix)}${routePath === "/" ? "" : routePath.replace(/^\/+/, "/")}` || base;
|
|
919
1130
|
}
|
|
920
|
-
function
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1131
|
+
function escapeXml(value) {
|
|
1132
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
1133
|
+
}
|
|
1134
|
+
function publicRoutes(routes) {
|
|
1135
|
+
return routes.filter((route) => route.path !== "/404");
|
|
1136
|
+
}
|
|
1137
|
+
function createSitemapXml(routes, config) {
|
|
1138
|
+
if (!config.siteUrl) return void 0;
|
|
1139
|
+
return [
|
|
1140
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
|
|
1141
|
+
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
|
|
1142
|
+
...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>`),
|
|
1143
|
+
"</urlset>",
|
|
1144
|
+
""
|
|
1145
|
+
].join("\n");
|
|
1146
|
+
}
|
|
1147
|
+
function createRobotsTxt(config) {
|
|
1148
|
+
if (!config.siteUrl) return void 0;
|
|
1149
|
+
return [
|
|
1150
|
+
"User-agent: *",
|
|
1151
|
+
"Allow: /",
|
|
1152
|
+
`Sitemap: ${routeUrl(config.siteUrl, config.routePrefix, "/sitemap.xml")}`,
|
|
1153
|
+
""
|
|
1154
|
+
].join("\n");
|
|
1155
|
+
}
|
|
1156
|
+
async function writeSeoFiles(outputRoot, routes, config) {
|
|
1157
|
+
const sitemap = createSitemapXml(routes, config);
|
|
1158
|
+
const robots = createRobotsTxt(config);
|
|
1159
|
+
if (!sitemap || !robots) return;
|
|
1160
|
+
await mkdir(outputRoot, { recursive: true });
|
|
1161
|
+
await Promise.all([writeFile(join(outputRoot, "sitemap.xml"), sitemap, "utf-8"), writeFile(join(outputRoot, "robots.txt"), robots, "utf-8")]);
|
|
1162
|
+
}
|
|
1163
|
+
//#endregion
|
|
1164
|
+
//#region source/plugins/seo/index.ts
|
|
1165
|
+
function createSeoPlugin() {
|
|
925
1166
|
return {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1167
|
+
name: "clarify:seo",
|
|
1168
|
+
hooks: { async "build:done"(ctx) {
|
|
1169
|
+
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
1170
|
+
if (!outputDirectory) return;
|
|
1171
|
+
await writeSeoFiles(join(ctx.generateOptions.projectRoot, outputDirectory), ctx.routes, ctx.projectConfig);
|
|
1172
|
+
} }
|
|
929
1173
|
};
|
|
930
1174
|
}
|
|
931
|
-
|
|
1175
|
+
//#endregion
|
|
1176
|
+
//#region source/plugins/source-links/source-links.ts
|
|
1177
|
+
function trimSlashes(value) {
|
|
1178
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
1179
|
+
}
|
|
1180
|
+
function normalizeRepositoryUrl(repository) {
|
|
1181
|
+
return repository.replace(/\.git$/, "").replace(/\/+$/, "");
|
|
1182
|
+
}
|
|
1183
|
+
function encodePath(path) {
|
|
1184
|
+
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
1185
|
+
}
|
|
1186
|
+
function createSourceUrl(filePath, contentRoot, source) {
|
|
1187
|
+
const repository = normalizeRepositoryUrl(source.repository);
|
|
1188
|
+
const branch = source.branch ?? "main";
|
|
1189
|
+
const sourcePath = [source.directory ? trimSlashes(source.directory) : "", relative(contentRoot, filePath).replaceAll("\\", "/")].filter(Boolean).join("/");
|
|
1190
|
+
return `${repository}/edit/${encodeURIComponent(branch)}/${encodePath(sourcePath)}`;
|
|
1191
|
+
}
|
|
1192
|
+
function attachSourceUrls(routes, contentRoot, source) {
|
|
1193
|
+
if (!source) return;
|
|
1194
|
+
for (const route of routes) route.sourceUrl = createSourceUrl(route.filePath, contentRoot, source);
|
|
1195
|
+
}
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region source/plugins/source-links/index.ts
|
|
1198
|
+
function createSourceLinksPlugin() {
|
|
932
1199
|
return {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
theme: resolveThemeConfig(config.theme),
|
|
939
|
-
navbar: config.navbar,
|
|
940
|
-
banner: config.banner,
|
|
941
|
-
footer: config.footer,
|
|
942
|
-
i18n: resolveI18nConfig(config.i18n),
|
|
943
|
-
tabs: config.tabs
|
|
1200
|
+
name: "clarify:source-links",
|
|
1201
|
+
hooks: { "routes:discovered": (routes, ctx) => {
|
|
1202
|
+
attachSourceUrls(routes, join(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.source);
|
|
1203
|
+
return routes;
|
|
1204
|
+
} }
|
|
944
1205
|
};
|
|
945
1206
|
}
|
|
946
1207
|
//#endregion
|
|
1208
|
+
//#region source/core/builtin.ts
|
|
1209
|
+
function createBuiltinPlugins(options = {}) {
|
|
1210
|
+
const plugins = [
|
|
1211
|
+
createOpenAPIPlugin(),
|
|
1212
|
+
createSourceLinksPlugin(),
|
|
1213
|
+
createContentArtifactsPlugin(),
|
|
1214
|
+
createSeoPlugin()
|
|
1215
|
+
];
|
|
1216
|
+
if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
|
|
1217
|
+
return plugins;
|
|
1218
|
+
}
|
|
1219
|
+
//#endregion
|
|
947
1220
|
//#region source/core/hooks.ts
|
|
948
1221
|
async function runHooks(plugins, hookName, input, ctx) {
|
|
949
1222
|
let result = input;
|
|
@@ -984,12 +1257,104 @@ async function runBuildDoneHooks(plugins, ctx) {
|
|
|
984
1257
|
//#region source/core/options.ts
|
|
985
1258
|
function resolveBuildOptions(options = {}) {
|
|
986
1259
|
return {
|
|
1260
|
+
projectRoot: options.projectRoot ?? process.cwd(),
|
|
987
1261
|
rootDirectory: options.rootDirectory ?? "source",
|
|
988
1262
|
outputDirectory: options.outputDirectory,
|
|
989
1263
|
ssg: { failOnError: options.ssg?.failOnError ?? true }
|
|
990
1264
|
};
|
|
991
1265
|
}
|
|
992
1266
|
//#endregion
|
|
1267
|
+
//#region source/core/site.ts
|
|
1268
|
+
function withAlternates(route, routeList, i18n) {
|
|
1269
|
+
const basePath = route.basePath ?? route.path;
|
|
1270
|
+
const routeByLocaleAndBase = new Map(routeList.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1271
|
+
const alternates = Object.fromEntries(i18n.locales.flatMap((locale) => {
|
|
1272
|
+
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
1273
|
+
return alternate ? [[locale.code, alternate.path]] : [];
|
|
1274
|
+
}));
|
|
1275
|
+
return {
|
|
1276
|
+
...route,
|
|
1277
|
+
alternates
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
async function discoverRoutesForRoot(routeRoot, locale, plugins, ctx) {
|
|
1281
|
+
return (await runHooks(plugins, "routes:discover", {
|
|
1282
|
+
contentRoot: routeRoot,
|
|
1283
|
+
locale,
|
|
1284
|
+
routes: findContentRoutes(routeRoot)
|
|
1285
|
+
}, ctx)).routes;
|
|
1286
|
+
}
|
|
1287
|
+
async function discoverRoutes(root, contentRoot, plugins, ctx) {
|
|
1288
|
+
const i18n = ctx.projectConfig.i18n;
|
|
1289
|
+
if (!i18n) return discoverRoutesForRoot(contentRoot, void 0, plugins, ctx);
|
|
1290
|
+
const localizedRoutes = [];
|
|
1291
|
+
for (const locale of i18n.locales) {
|
|
1292
|
+
const discovered = await discoverRoutesForRoot(join(contentRoot, locale.code), locale.code, plugins, ctx);
|
|
1293
|
+
for (const route of discovered) {
|
|
1294
|
+
const basePath = route.basePath ?? route.path;
|
|
1295
|
+
localizedRoutes.push({
|
|
1296
|
+
...route,
|
|
1297
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1298
|
+
basePath,
|
|
1299
|
+
locale: locale.code,
|
|
1300
|
+
virtualModuleId: virtualModuleIdFromRef(relative(contentRoot, route.filePath))
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (i18n.missing === "fallback") {
|
|
1305
|
+
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
1306
|
+
const defaultRoutes = localizedRoutes.filter((route) => route.locale === i18n.defaultLocale);
|
|
1307
|
+
for (const sourceRoute of defaultRoutes) {
|
|
1308
|
+
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
1309
|
+
for (const locale of i18n.locales) {
|
|
1310
|
+
const key = `${locale.code}:${basePath}`;
|
|
1311
|
+
if (routeByLocaleAndBase.has(key)) continue;
|
|
1312
|
+
localizedRoutes.push({
|
|
1313
|
+
...sourceRoute,
|
|
1314
|
+
path: localizedRoutePath(basePath, locale.code, i18n),
|
|
1315
|
+
locale: locale.code,
|
|
1316
|
+
isFallback: true
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return localizedRoutes.map((route) => withAlternates(route, localizedRoutes, i18n));
|
|
1322
|
+
}
|
|
1323
|
+
async function resolveClarifySite(options = {}, resolveOptions = {}) {
|
|
1324
|
+
const root = options.projectRoot ?? process.cwd();
|
|
1325
|
+
const projectConfig = resolveProjectConfig(options);
|
|
1326
|
+
const generateOptions = resolveBuildOptions(options);
|
|
1327
|
+
const contentRoot = join(root, generateOptions.rootDirectory);
|
|
1328
|
+
const plugins = [...createBuiltinPlugins({ htmlShell: resolveOptions.includeHtmlShellPlugin }), ...options.plugins ?? []];
|
|
1329
|
+
const ctx = {
|
|
1330
|
+
projectConfig,
|
|
1331
|
+
generateOptions,
|
|
1332
|
+
routes: [],
|
|
1333
|
+
navigation: []
|
|
1334
|
+
};
|
|
1335
|
+
let routes = await discoverRoutes(root, contentRoot, plugins, ctx);
|
|
1336
|
+
routes = await runHooks(plugins, "routes:discovered", routes, ctx);
|
|
1337
|
+
const defaultNavigation = projectConfig.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes);
|
|
1338
|
+
const resolved = await runHooks(plugins, "routes:resolved", {
|
|
1339
|
+
routes,
|
|
1340
|
+
navigation: defaultNavigation
|
|
1341
|
+
}, ctx);
|
|
1342
|
+
routes = resolved.routes;
|
|
1343
|
+
const navigation = resolved.navigation;
|
|
1344
|
+
ctx.routes = routes;
|
|
1345
|
+
ctx.navigation = navigation;
|
|
1346
|
+
return {
|
|
1347
|
+
root,
|
|
1348
|
+
contentRoot,
|
|
1349
|
+
projectConfig,
|
|
1350
|
+
generateOptions,
|
|
1351
|
+
plugins,
|
|
1352
|
+
ctx,
|
|
1353
|
+
routes,
|
|
1354
|
+
navigation
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
//#endregion
|
|
993
1358
|
//#region source/core/runtime-deps.ts
|
|
994
1359
|
var requireFromCli = createRequire(import.meta.url);
|
|
995
1360
|
function resolveCliDependency(id) {
|
|
@@ -1084,13 +1449,21 @@ function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
|
|
|
1084
1449
|
html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
|
|
1085
1450
|
return html;
|
|
1086
1451
|
}
|
|
1452
|
+
function isNotFoundRoute(route) {
|
|
1453
|
+
return (route.basePath ?? route.path) === "/404";
|
|
1454
|
+
}
|
|
1455
|
+
function routeOutputFiles(outputDirectory, route) {
|
|
1456
|
+
const files = [join(outputDirectory, route.path, "index.html")];
|
|
1457
|
+
if (isNotFoundRoute(route) && (!route.locale || route.path === "/404")) files.push(join(outputDirectory, "404.html"));
|
|
1458
|
+
return files;
|
|
1459
|
+
}
|
|
1087
1460
|
var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
|
|
1088
1461
|
import { routes, navigation } from 'virtual:clarify-routes/server';
|
|
1089
1462
|
import { config } from 'virtual:clarify-config';
|
|
1090
|
-
import { openApis } from 'virtual:clarify-
|
|
1463
|
+
import { openApis } from 'virtual:clarify-runtime';
|
|
1091
1464
|
|
|
1092
1465
|
export function render(url) {
|
|
1093
|
-
return renderToHTML({ config, routes, navigation, openApis, url });
|
|
1466
|
+
return renderToHTML({ config, routes, navigation, openApis, url, themeEditor: config.theme.editor });
|
|
1094
1467
|
}`;
|
|
1095
1468
|
function createTempEntryFile(content) {
|
|
1096
1469
|
const entryPath = join(mkdtempSync(join(tmpdir(), "clarify-ssr-")), "entry-server.ts");
|
|
@@ -1121,9 +1494,10 @@ async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundle
|
|
|
1121
1494
|
if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
|
|
1122
1495
|
for (const route of routes) try {
|
|
1123
1496
|
const finalHtml = injectSSRIntoTemplate(template, render(route.path), projectConfig, route);
|
|
1124
|
-
const outFile
|
|
1125
|
-
|
|
1126
|
-
|
|
1497
|
+
for (const outFile of routeOutputFiles(outputDirectory, route)) {
|
|
1498
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
1499
|
+
writeFileSync(outFile, finalHtml, "utf-8");
|
|
1500
|
+
}
|
|
1127
1501
|
} catch (err) {
|
|
1128
1502
|
console.error(`[clarify] Failed to render route "${route.path}":`, err);
|
|
1129
1503
|
if (failOnError) throw err;
|
|
@@ -1165,75 +1539,6 @@ async function loadClarifyConfig(root, env) {
|
|
|
1165
1539
|
return assertConfigObject((await loadConfigFromFile(env, configFile, root, "silent"))?.config, configFile);
|
|
1166
1540
|
}
|
|
1167
1541
|
//#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
1542
|
//#region source/core/plugin.ts
|
|
1238
1543
|
function loadVirtualModule(id, modules) {
|
|
1239
1544
|
const bareId = stripVirtualPrefix(id);
|
|
@@ -1246,12 +1551,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1246
1551
|
const contentRoot = join(root, generateOptions.rootDirectory);
|
|
1247
1552
|
const configFilePath = findClarifyConfigFile(root);
|
|
1248
1553
|
let routes = [];
|
|
1249
|
-
|
|
1250
|
-
createOpenAPIPlugin(),
|
|
1251
|
-
createContentArtifactsPlugin(),
|
|
1252
|
-
createHtmlShellPlugin(),
|
|
1253
|
-
...options.plugins ?? []
|
|
1254
|
-
];
|
|
1554
|
+
let clarifyPlugins = [...createBuiltinPlugins(), ...options.plugins ?? []];
|
|
1255
1555
|
const ctx = {
|
|
1256
1556
|
projectConfig,
|
|
1257
1557
|
generateOptions,
|
|
@@ -1276,71 +1576,15 @@ function clarifyPlugin(options = {}) {
|
|
|
1276
1576
|
ctx.projectConfig = projectConfig;
|
|
1277
1577
|
ctx.generateOptions = generateOptions;
|
|
1278
1578
|
}
|
|
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
1579
|
async function resolveRoutesAndSpecs() {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
resolvedNavigation = resolved.navigation;
|
|
1580
|
+
const site = await resolveClarifySite(options);
|
|
1581
|
+
projectConfig = site.projectConfig;
|
|
1582
|
+
generateOptions = site.generateOptions;
|
|
1583
|
+
routes = site.routes;
|
|
1584
|
+
resolvedNavigation = site.navigation;
|
|
1585
|
+
clarifyPlugins = site.plugins;
|
|
1586
|
+
ctx.projectConfig = projectConfig;
|
|
1587
|
+
ctx.generateOptions = generateOptions;
|
|
1344
1588
|
ctx.routes = routes;
|
|
1345
1589
|
ctx.navigation = resolvedNavigation;
|
|
1346
1590
|
}
|
|
@@ -1349,7 +1593,8 @@ function clarifyPlugin(options = {}) {
|
|
|
1349
1593
|
projectConfig,
|
|
1350
1594
|
generateOptions,
|
|
1351
1595
|
routes,
|
|
1352
|
-
navigation: resolvedNavigation
|
|
1596
|
+
navigation: resolvedNavigation,
|
|
1597
|
+
themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor
|
|
1353
1598
|
});
|
|
1354
1599
|
virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
|
|
1355
1600
|
}
|
|
@@ -1429,8 +1674,9 @@ function clarifyPlugin(options = {}) {
|
|
|
1429
1674
|
if (id === "virtual:clarify-config" || id === resolveVirtualId("virtual:clarify-config")) return resolveVirtualId(VIRTUAL_CONFIG);
|
|
1430
1675
|
if (id === "virtual:clarify-routes" || id === resolveVirtualId("virtual:clarify-routes")) return resolveVirtualId(VIRTUAL_ROUTES);
|
|
1431
1676
|
if (id === "virtual:clarify-routes/server" || id === resolveVirtualId("virtual:clarify-routes/server")) return resolveVirtualId(VIRTUAL_SERVER_ROUTES);
|
|
1432
|
-
|
|
1433
|
-
|
|
1677
|
+
const moduleId = stripVirtualPrefix(id);
|
|
1678
|
+
if (virtualModules.has(moduleId)) return resolveVirtualId(moduleId);
|
|
1679
|
+
const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === moduleId);
|
|
1434
1680
|
if (route) return resolveVirtualId(route.virtualModuleId);
|
|
1435
1681
|
return null;
|
|
1436
1682
|
},
|
|
@@ -1452,10 +1698,11 @@ function clarifyPlugin(options = {}) {
|
|
|
1452
1698
|
transformIndexHtml: {
|
|
1453
1699
|
order: "pre",
|
|
1454
1700
|
async handler(html, transformCtx) {
|
|
1701
|
+
const clientEntryId = transformCtx.server ? `/@id/${VIRTUAL_CLIENT_ENTRY}` : VIRTUAL_CLIENT_ENTRY;
|
|
1455
1702
|
const result = await runHooks(clarifyPlugins, "html:transform", {
|
|
1456
1703
|
html,
|
|
1457
1704
|
tags: [],
|
|
1458
|
-
clientEntryId
|
|
1705
|
+
clientEntryId,
|
|
1459
1706
|
dev: Boolean(transformCtx.server)
|
|
1460
1707
|
}, ctx);
|
|
1461
1708
|
return {
|
|
@@ -1483,7 +1730,7 @@ function clarifyPlugin(options = {}) {
|
|
|
1483
1730
|
if (id === "virtual:clarify-config") return id;
|
|
1484
1731
|
if (id === "virtual:clarify-routes") return id;
|
|
1485
1732
|
if (id === "virtual:clarify-routes/server") return id;
|
|
1486
|
-
if (id
|
|
1733
|
+
if (virtualModules.has(stripVirtualPrefix(id))) return stripVirtualPrefix(id);
|
|
1487
1734
|
if (routes.find((r) => r.virtualModuleId === id)) return id;
|
|
1488
1735
|
return null;
|
|
1489
1736
|
},
|
|
@@ -1509,6 +1756,6 @@ function clarifyPlugin(options = {}) {
|
|
|
1509
1756
|
].flat().filter(Boolean);
|
|
1510
1757
|
}
|
|
1511
1758
|
//#endregion
|
|
1512
|
-
export { createClarifyRuntimeAliases as i, defineConfig as n, loadClarifyConfig as r, clarifyPlugin as t };
|
|
1759
|
+
export { resolveClarifySite as a, createClarifyRuntimeAliases as i, defineConfig as n, loadClarifyConfig as r, clarifyPlugin as t };
|
|
1513
1760
|
|
|
1514
|
-
//# sourceMappingURL=plugin-
|
|
1761
|
+
//# sourceMappingURL=plugin-BgE7WVoS.js.map
|