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