@clarify-labs/cli 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -132,11 +132,14 @@ var rehypePlugins = [
132
132
  //#endregion
133
133
  //#region source/parsers/frontmatter.ts
134
134
  /**
135
- * 从 MDX 文件内容中提取 frontmatter。
136
- * 使用 gray-matter 支持 YAML frontmatter 的标准语法和类型解析。
135
+ * 在内容入口统一解析 MDX frontmatter,并返回可供后续流程复用的标准化正文。
137
136
  */
138
- function extractFrontmatter(content) {
139
- return (0, gray_matter.default)(content).data;
137
+ function parseFrontmatter(content) {
138
+ const parsed = (0, gray_matter.default)(content);
139
+ return {
140
+ frontmatter: parsed.data,
141
+ content: parsed.content.replace(/^\r?\n/, "")
142
+ };
140
143
  }
141
144
  //#endregion
142
145
  //#region source/parsers/routes.ts
@@ -213,8 +216,7 @@ function findContentRoutes(dir, base = dir) {
213
216
  const relativePath = (0, node_path.relative)(base, fullPath);
214
217
  const pathParts = relativePath.replace(/\.mdx?$/, "").split("/");
215
218
  const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
216
- const content = (0, node_fs.readFileSync)(fullPath, "utf-8");
217
- const frontmatter = extractFrontmatter(content);
219
+ const { frontmatter, content } = parseFrontmatter((0, node_fs.readFileSync)(fullPath, "utf-8"));
218
220
  let title = typeof frontmatter.title === "string" ? frontmatter.title : "";
219
221
  if (!title) {
220
222
  const lastPart = pathParts[pathParts.length - 1] ?? "";
@@ -226,13 +228,22 @@ function findContentRoutes(dir, base = dir) {
226
228
  filePath: fullPath,
227
229
  virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/"),
228
230
  title,
231
+ description: typeof frontmatter.description === "string" ? frontmatter.description : void 0,
232
+ keywords: frontmatterKeywords(frontmatter),
229
233
  kind: "mdx",
234
+ frontmatter,
235
+ content,
230
236
  sections: extractMdxSections(content)
231
237
  });
232
238
  }
233
239
  }
234
240
  return routes;
235
241
  }
242
+ function frontmatterKeywords(frontmatter) {
243
+ const value = frontmatter.keywords ?? frontmatter.keyword ?? frontmatter.tags;
244
+ const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
245
+ return keywords.length > 0 ? keywords : void 0;
246
+ }
236
247
  function buildNavigation(routes) {
237
248
  const root = [];
238
249
  for (const route of routes) {
@@ -273,6 +284,30 @@ function resolvePageItem(item) {
273
284
  icon: item.icon
274
285
  };
275
286
  }
287
+ function firstNavigationPath(nodes) {
288
+ for (const node of nodes) {
289
+ if (node.path) return node.path;
290
+ const childPath = node.children ? firstNavigationPath(node.children) : void 0;
291
+ if (childPath) return childPath;
292
+ }
293
+ return "/";
294
+ }
295
+ function buildNavigationFromPagesConfig(routes, config) {
296
+ if (!config || config === "FileTree") return buildNavigation(routes);
297
+ return buildNavigationFromConfig(routes, config);
298
+ }
299
+ function buildNavigationFromTabsConfig(routes, tabs) {
300
+ return { tabs: tabs.map((tab) => {
301
+ const children = buildNavigationFromPagesConfig(routes, tab.pages);
302
+ return {
303
+ type: "tab",
304
+ path: firstNavigationPath(children),
305
+ title: resolveLocalizedText(tab.tab, "", "") ?? "",
306
+ icon: tab.icon,
307
+ children
308
+ };
309
+ }) };
310
+ }
276
311
  function buildNavigationFromConfig(routes, config) {
277
312
  const routeMap = new Map(routes.map((r) => [r.path, r]));
278
313
  return config.map((group) => {
@@ -313,69 +348,94 @@ function localizeNavigationPaths(nodes, locale, i18n) {
313
348
  children: node.children ? localizeNavigationPaths(node.children, locale, i18n) : void 0
314
349
  }));
315
350
  }
316
- function buildLocalizedNavigation(routes, config, i18n) {
351
+ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
352
+ if (!config || config === "FileTree") return localizeNavigationPaths(buildNavigation(routes.map((route) => ({
353
+ ...route,
354
+ path: route.basePath ?? route.path
355
+ }))), locale, i18n);
356
+ const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
357
+ return config.map((group) => {
358
+ const children = group.pages.map((item) => {
359
+ const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
360
+ const ref = openapiRef ?? pageRef ?? "";
361
+ const basePath = basePathFromRef(ref);
362
+ const route = routeMap.get(basePath);
363
+ const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
364
+ return {
365
+ path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale, i18n) : void 0) ?? path,
366
+ title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
367
+ icon,
368
+ sections: route?.sections ? navigationSections(route.sections) : void 0
369
+ };
370
+ });
371
+ return {
372
+ path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
373
+ title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
374
+ icon: group.icon,
375
+ children
376
+ };
377
+ });
378
+ }
379
+ function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
317
380
  if (!i18n) return void 0;
318
381
  const result = {};
319
382
  for (const locale of i18n.locales) {
320
383
  const localeRoutes = routes.filter((route) => route.locale === locale.code);
321
- if (!config || config === "FileTree") {
322
- const baseNavigation = buildNavigation(localeRoutes.map((route) => ({
323
- ...route,
324
- path: route.basePath ?? route.path
325
- })));
326
- result[locale.code] = localizeNavigationPaths(baseNavigation, locale.code, i18n);
327
- continue;
328
- }
329
- const routeMap = new Map(localeRoutes.map((route) => [route.basePath ?? route.path, route]));
330
- result[locale.code] = config.map((group) => {
331
- const children = group.pages.map((item) => {
332
- const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
333
- const ref = openapiRef ?? pageRef ?? "";
334
- const basePath = basePathFromRef(ref);
335
- const route = routeMap.get(basePath);
336
- const path = route?.path ?? localizedRoutePath(basePath, locale.code, i18n);
337
- return {
338
- path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale.code, i18n) : void 0) ?? path,
339
- title: resolveLocalizedText(title, locale.code, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
340
- icon,
341
- sections: route?.sections ? navigationSections(route.sections) : void 0
342
- };
343
- });
384
+ result[locale.code] = { tabs: tabs.map((tab) => {
385
+ const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
344
386
  return {
345
- path: children[0]?.path ?? localizedRoutePath("/", locale.code, i18n),
346
- title: resolveLocalizedText(group.group, locale.code, i18n.defaultLocale) ?? "",
347
- icon: group.icon,
387
+ type: "tab",
388
+ path: firstNavigationPath(children),
389
+ title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
390
+ icon: tab.icon,
348
391
  children
349
392
  };
350
- });
393
+ }) };
351
394
  }
352
395
  return result;
353
396
  }
354
397
  //#endregion
355
- //#region source/plugins/content-artifacts/raw-content.ts
398
+ //#region source/plugins/content-artifacts/artifacts.ts
399
+ var UTF8_SIGNATURE = "";
356
400
  function normalizeBasePath(basePath) {
357
401
  if (!basePath || basePath === "/") return "";
358
402
  return "/" + basePath.replace(/^\/+|\/+$/g, "");
359
403
  }
360
- function routeToMarkdownUrl(routePath) {
404
+ function containsNonAscii(content) {
405
+ return [...content].some((character) => character.codePointAt(0) > 127);
406
+ }
407
+ function withUtf8Signature(content) {
408
+ if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
409
+ return `${UTF8_SIGNATURE}${content}`;
410
+ }
411
+ function shouldUseUtf8Signature(route) {
412
+ if (route.kind === "mdx") return true;
413
+ return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
414
+ }
415
+ function routeToMarkdownArtifactUrl(routePath) {
361
416
  return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
362
417
  }
363
- function routeToOpenAPIUrl(routePath, filePath) {
418
+ function routeToOpenAPIArtifactUrl(routePath, filePath) {
364
419
  const extension = (0, node_path.extname)(filePath).toLowerCase() || ".json";
365
420
  return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
366
421
  }
367
- function enrichRoutesWithRawContent(routes) {
368
- for (const route of routes) route.rawContentUrl = route.kind === "openapi" ? routeToOpenAPIUrl(route.path, route.filePath) : routeToMarkdownUrl(route.path);
422
+ function attachContentArtifactUrls(routes) {
423
+ for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
369
424
  }
370
- function readRawContent(route) {
371
- return (0, node_fs.readFileSync)(route.filePath, "utf-8");
425
+ function readRouteContent(route) {
426
+ if (route.content !== void 0) return route.content;
427
+ throw new Error(`Route content is missing from route context: ${route.filePath}`);
372
428
  }
373
- function writeRawContentFiles(routes, outputDirectory) {
429
+ function readRouteArtifactContent(route) {
430
+ const content = readRouteContent(route);
431
+ return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
432
+ }
433
+ function writeContentArtifactFiles(routes, outputDirectory) {
374
434
  for (const route of routes) {
375
- if (!route.rawContentUrl) continue;
376
- const outFile = (0, node_path.join)(outputDirectory, route.rawContentUrl.replace(/^\//, ""));
435
+ if (!route.contentArtifactUrl) continue;
436
+ const outFile = (0, node_path.join)(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
377
437
  (0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
378
- (0, node_fs.writeFileSync)(outFile, readRawContent(route), "utf-8");
438
+ (0, node_fs.writeFileSync)(outFile, readRouteArtifactContent(route), "utf-8");
379
439
  }
380
440
  }
381
441
  function createLlmsTxt(routes, projectConfig) {
@@ -384,22 +444,25 @@ function createLlmsTxt(routes, projectConfig) {
384
444
  if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
385
445
  lines.push("## Docs");
386
446
  for (const route of routes.filter((route) => route.kind === "mdx")) {
387
- if (!route.rawContentUrl) continue;
388
- lines.push(`- [${route.title}](${basePath}${route.rawContentUrl})`);
447
+ if (!route.contentArtifactUrl) continue;
448
+ lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
389
449
  }
390
450
  const openApiRoutes = routes.filter((route) => route.kind === "openapi");
391
451
  if (openApiRoutes.length > 0) {
392
452
  lines.push("", "## OpenAPI");
393
453
  for (const route of openApiRoutes) {
394
- if (!route.rawContentUrl) continue;
395
- lines.push(`- [${route.title}](${basePath}${route.rawContentUrl})`);
454
+ if (!route.contentArtifactUrl) continue;
455
+ lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
396
456
  }
397
457
  }
398
458
  return `${lines.join("\n")}\n`;
399
459
  }
460
+ function createLlmsTxtArtifact(routes, projectConfig) {
461
+ return withUtf8Signature(createLlmsTxt(routes, projectConfig));
462
+ }
400
463
  function writeLlmsTxt(routes, projectConfig, outputDirectory) {
401
464
  (0, node_fs.mkdirSync)(outputDirectory, { recursive: true });
402
- (0, node_fs.writeFileSync)((0, node_path.join)(outputDirectory, "llms.txt"), createLlmsTxt(routes, projectConfig), "utf-8");
465
+ (0, node_fs.writeFileSync)((0, node_path.join)(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
403
466
  }
404
467
  //#endregion
405
468
  //#region source/plugins/content-artifacts/server.ts
@@ -412,8 +475,8 @@ function resolveContentArtifactPath(url, projectConfig) {
412
475
  const basePath = normalizeRoutePrefix(projectConfig.routePrefix);
413
476
  return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
414
477
  }
415
- function resolveRawContentType(route) {
416
- if (route.kind === "openapi" && /\.ya?ml$/i.test(route.rawContentUrl ?? "")) return "text/yaml; charset=utf-8";
478
+ function resolveContentArtifactType(route) {
479
+ if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
417
480
  return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
418
481
  }
419
482
  function serveContentArtifacts(req, res, projectConfig, routes) {
@@ -421,14 +484,14 @@ function serveContentArtifacts(req, res, projectConfig, routes) {
421
484
  if (contentPath === "/llms.txt") {
422
485
  res.statusCode = 200;
423
486
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
424
- res.end(createLlmsTxt(routes, projectConfig));
487
+ res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
425
488
  return true;
426
489
  }
427
- const route = routes.find((route) => route.rawContentUrl === contentPath);
490
+ const route = routes.find((route) => route.contentArtifactUrl === contentPath);
428
491
  if (!route) return false;
429
492
  res.statusCode = 200;
430
- res.setHeader("Content-Type", resolveRawContentType(route));
431
- res.end(readRawContent(route));
493
+ res.setHeader("Content-Type", resolveContentArtifactType(route));
494
+ res.end(readRouteArtifactContent(route), "utf8");
432
495
  return true;
433
496
  }
434
497
  //#endregion
@@ -438,7 +501,7 @@ function createContentArtifactsPlugin() {
438
501
  name: "clarify:content-artifacts",
439
502
  hooks: {
440
503
  "routes:resolved": (input) => {
441
- enrichRoutesWithRawContent(input.routes);
504
+ attachContentArtifactUrls(input.routes);
442
505
  return input;
443
506
  },
444
507
  "dev:configureServer": (server, ctx) => {
@@ -450,7 +513,7 @@ function createContentArtifactsPlugin() {
450
513
  "build:done": (ctx) => {
451
514
  const outputDirectory = ctx.generateOptions.outputDirectory;
452
515
  if (!outputDirectory) return;
453
- writeRawContentFiles(ctx.routes, outputDirectory);
516
+ writeContentArtifactFiles(ctx.routes, outputDirectory);
454
517
  writeLlmsTxt(ctx.routes, ctx.projectConfig, outputDirectory);
455
518
  }
456
519
  }
@@ -527,6 +590,9 @@ var OPENAPI_HTTP_METHODS = [
527
590
  "patch",
528
591
  "trace"
529
592
  ];
593
+ function errorMessage(error) {
594
+ return error instanceof Error ? error.message : String(error);
595
+ }
530
596
  function kebabToTitle(str) {
531
597
  return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
532
598
  }
@@ -549,7 +615,8 @@ function findOpenAPIRoutes(dir, base = dir) {
549
615
  filePath: fullPath,
550
616
  virtualModuleId: virtualModuleIdFromRef(ref),
551
617
  title: kebabToTitle(cleanPath.split("/").pop() ?? "API"),
552
- kind: "openapi"
618
+ kind: "openapi",
619
+ content: (0, node_fs.readFileSync)(fullPath, "utf-8")
553
620
  });
554
621
  }
555
622
  return routes;
@@ -576,9 +643,20 @@ function extractOpenAPISections(spec) {
576
643
  }
577
644
  async function readOpenAPISpec(filePath) {
578
645
  try {
579
- return await _apidevtools_swagger_parser.default.dereference(filePath);
580
- } catch {
581
- return null;
646
+ return {
647
+ ok: true,
648
+ spec: await _apidevtools_swagger_parser.default.dereference(filePath)
649
+ };
650
+ } catch (error) {
651
+ return {
652
+ ok: false,
653
+ diagnostic: {
654
+ title: "OpenAPI spec parse failed",
655
+ message: `Clarify could not parse ${filePath}.`,
656
+ filePath,
657
+ cause: errorMessage(error)
658
+ }
659
+ };
582
660
  }
583
661
  }
584
662
  //#endregion
@@ -595,6 +673,27 @@ export default function OpenApiRoutePage() {
595
673
  return createElement(OpenApiPage, { spec });
596
674
  }`;
597
675
  }
676
+ function generateOpenAPIErrorModule(diagnostic) {
677
+ return `import { createElement } from 'react';
678
+ const diagnostic = ${JSON.stringify(diagnostic)};
679
+ export default function OpenApiErrorRoutePage() {
680
+ return createElement('article', { className: 'flex h-full flex-col pt-16 pb-10' },
681
+ createElement('div', { className: 'prose max-w-none flex-auto dark:prose-invert' },
682
+ createElement('p', { className: 'mb-3 font-mono text-xs/6 font-medium tracking-widest text-red-500 uppercase dark:text-red-400' }, 'OpenAPI Error'),
683
+ createElement('h1', null, diagnostic.title),
684
+ createElement('p', { className: 'lead' }, diagnostic.message),
685
+ 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' },
686
+ createElement('div', { className: 'mb-1 font-semibold text-zinc-900 dark:text-white' }, 'File'),
687
+ createElement('code', { className: 'break-all text-zinc-700 dark:text-zinc-300' }, diagnostic.filePath)
688
+ ) : null,
689
+ 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' },
690
+ createElement('div', { className: 'mb-1 font-semibold' }, 'Why it happened'),
691
+ createElement('pre', { className: 'whitespace-pre-wrap break-words font-mono text-xs' }, diagnostic.cause)
692
+ ) : null
693
+ )
694
+ );
695
+ }`;
696
+ }
598
697
  //#endregion
599
698
  //#region source/plugins/openapi/index.ts
600
699
  function createOpenAPIPlugin() {
@@ -609,18 +708,26 @@ function createOpenAPIPlugin() {
609
708
  "routes:discovered": async (routes) => {
610
709
  for (const key of Object.keys(specs)) delete specs[key];
611
710
  for (const route of routes.filter((route) => route.kind === "openapi")) {
612
- const spec = await readOpenAPISpec(route.filePath);
613
- if (!spec) throw new Error(`Failed to parse OpenAPI spec: ${route.filePath}`);
711
+ const result = await readOpenAPISpec(route.filePath);
712
+ if (!result.ok) {
713
+ route.diagnostic = result.diagnostic;
714
+ route.title = route.title || "OpenAPI parse error";
715
+ route.sections = [];
716
+ continue;
717
+ }
718
+ const spec = result.spec;
614
719
  specs[route.virtualModuleId] = spec;
615
720
  specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = spec;
616
721
  route.title = spec.info?.title ?? route.title;
722
+ route.description = spec.info?.description ?? route.description;
617
723
  route.sections = extractOpenAPISections(spec);
618
724
  }
619
725
  return routes;
620
726
  },
621
- "modules:before": (modules) => {
727
+ "modules:before": (modules, ctx) => {
622
728
  modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(specs));
623
729
  for (const [moduleId, spec] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(spec));
730
+ for (const route of ctx.routes.filter((route) => route.kind === "openapi" && route.diagnostic)) if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
624
731
  return modules;
625
732
  }
626
733
  }
@@ -704,20 +811,122 @@ var clarifyPagesGroupSchema = zod.z.object({
704
811
  pages: zod.z.array(clarifyPagesItemSchema)
705
812
  });
706
813
  var clarifyPagesConfigSchema = zod.z.union([zod.z.array(clarifyPagesGroupSchema), zod.z.literal("FileTree")]);
814
+ var clarifyTabItemSchema = zod.z.object({
815
+ tab: clarifyLocalizedTextSchema,
816
+ icon: zod.z.string().optional(),
817
+ pages: clarifyPagesConfigSchema.optional()
818
+ });
819
+ var clarifyTabsConfigSchema = zod.z.array(clarifyTabItemSchema);
820
+ var clarifyThemePresetSchema = zod.z.enum(["default", "base"]);
821
+ var clarifyThemeColorTokensConfigSchema = zod.z.object({
822
+ primary: zod.z.string().optional(),
823
+ accent: zod.z.string().optional(),
824
+ background: zod.z.string().optional(),
825
+ foreground: zod.z.string().optional(),
826
+ surface: zod.z.string().optional(),
827
+ muted: zod.z.string().optional(),
828
+ border: zod.z.string().optional(),
829
+ codeBackground: zod.z.string().optional()
830
+ });
831
+ var clarifyThemeRadiusTokensConfigSchema = zod.z.object({
832
+ sm: zod.z.string().optional(),
833
+ md: zod.z.string().optional(),
834
+ lg: zod.z.string().optional(),
835
+ xl: zod.z.string().optional()
836
+ });
837
+ var clarifyThemeTokensConfigSchema = zod.z.object({
838
+ colors: clarifyThemeColorTokensConfigSchema.optional(),
839
+ radius: clarifyThemeRadiusTokensConfigSchema.optional()
840
+ });
841
+ var clarifyThemeLayoutConfigSchema = zod.z.object({ maxWidth: zod.z.string().optional() });
842
+ var clarifyThemeConfigSchema = zod.z.object({
843
+ preset: clarifyThemePresetSchema.optional(),
844
+ tokens: clarifyThemeTokensConfigSchema.optional(),
845
+ layout: clarifyThemeLayoutConfigSchema.optional()
846
+ });
707
847
  var clarifyProjectConfigSchema = zod.z.object({
708
848
  title: zod.z.string().optional(),
709
849
  description: zod.z.string().optional(),
710
850
  logo: clarifyLogoConfigSchema.optional(),
711
851
  favicon: clarifyFaviconConfigSchema.optional(),
712
- theme: zod.z.object({ primary: zod.z.string().optional() }).optional(),
852
+ theme: clarifyThemeConfigSchema.optional(),
713
853
  routePrefix: zod.z.string().optional(),
714
854
  navbar: zod.z.object({ links: zod.z.array(clarifyNavbarLinkSchema).optional() }).optional(),
715
855
  banner: clarifyBannerConfigSchema.optional(),
716
856
  footer: clarifyFooterConfigSchema.optional(),
717
857
  i18n: clarifyI18nConfigSchema.optional(),
718
- pages: clarifyPagesConfigSchema.optional()
858
+ tabs: clarifyTabsConfigSchema.optional()
719
859
  });
720
860
  //#endregion
861
+ //#region source/core/theme.ts
862
+ var clarifyThemePresets = {
863
+ default: {
864
+ preset: "default",
865
+ tokens: {
866
+ colors: {
867
+ primary: "#00D492",
868
+ accent: "#00F6C9",
869
+ background: "#ffffff",
870
+ foreground: "#111827",
871
+ surface: "#ffffff",
872
+ muted: "#64748b",
873
+ border: "rgb(15 23 42 / 0.12)",
874
+ codeBackground: "#f7fefb"
875
+ },
876
+ radius: {
877
+ sm: "6px",
878
+ md: "10px",
879
+ lg: "14px",
880
+ xl: "18px"
881
+ }
882
+ },
883
+ layout: { maxWidth: "82rem" }
884
+ },
885
+ base: {
886
+ preset: "base",
887
+ tokens: {
888
+ colors: {
889
+ primary: "#18181b",
890
+ accent: "#52525b",
891
+ background: "#ffffff",
892
+ foreground: "#18181b",
893
+ surface: "#ffffff",
894
+ muted: "#71717a",
895
+ border: "rgb(24 24 27 / 0.12)",
896
+ codeBackground: "#f4f4f5"
897
+ },
898
+ radius: {
899
+ sm: "6px",
900
+ md: "8px",
901
+ lg: "12px",
902
+ xl: "16px"
903
+ }
904
+ },
905
+ layout: { maxWidth: "80rem" }
906
+ }
907
+ };
908
+ function resolveThemeConfig(theme = {}) {
909
+ const preset = theme.preset ?? "default";
910
+ const presetTheme = clarifyThemePresets[preset];
911
+ return {
912
+ preset,
913
+ tokens: {
914
+ colors: {
915
+ ...presetTheme.tokens.colors,
916
+ ...theme.tokens?.colors
917
+ },
918
+ radius: {
919
+ ...presetTheme.tokens.radius,
920
+ ...theme.tokens?.radius
921
+ }
922
+ },
923
+ layout: {
924
+ ...presetTheme.layout,
925
+ ...theme.layout
926
+ }
927
+ };
928
+ }
929
+ //#endregion
721
930
  //#region source/core/config.ts
722
931
  function formatIssuePath(path) {
723
932
  return path.reduce((result, segment) => {
@@ -756,12 +965,12 @@ function resolveProjectConfig(config = {}) {
756
965
  logo: config.logo,
757
966
  favicon: config.favicon,
758
967
  routePrefix: config.routePrefix ?? "/",
759
- theme: config.theme ?? {},
968
+ theme: resolveThemeConfig(config.theme),
760
969
  navbar: config.navbar,
761
970
  banner: config.banner,
762
971
  footer: config.footer,
763
972
  i18n: resolveI18nConfig(config.i18n),
764
- pages: config.pages
973
+ tabs: config.tabs
765
974
  };
766
975
  }
767
976
  //#endregion
@@ -901,18 +1110,28 @@ function injectHtmlLocaleAttributes(html, projectConfig, route) {
901
1110
  return `<html${nextAttributes}>`;
902
1111
  });
903
1112
  }
1113
+ function routeTitle(projectConfig, route) {
1114
+ const title = route?.title?.trim();
1115
+ if (!title || title === projectConfig.title) return projectConfig.title;
1116
+ return `${title} - ${projectConfig.title}`;
1117
+ }
1118
+ function setNamedMeta(html, name, content) {
1119
+ const metaPattern = new RegExp(`<meta\\b(?=[^>]*\\bname=["']${name}["'])[^>]*>\\n?`, "i");
1120
+ if (!content) return html.replace(metaPattern, "");
1121
+ const meta = `<meta name="${name}" content="${escapeHtml(content)}" />`;
1122
+ if (metaPattern.test(html)) return html.replace(metaPattern, meta);
1123
+ return html.replace("</head>", ` ${meta}\n </head>`);
1124
+ }
904
1125
  function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
905
1126
  let html = injectHtmlLocaleAttributes(template, projectConfig, route);
906
- html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(projectConfig.title)}</title>`);
907
- if (projectConfig.description && !html.includes("name=\"description\"")) {
908
- const descriptionMeta = `<meta name="description" content="${escapeHtml(projectConfig.description)}" />`;
909
- html = html.replace("</head>", ` ${descriptionMeta}\n </head>`);
910
- }
1127
+ html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(projectConfig, route))}</title>`);
1128
+ html = setNamedMeta(html, "description", route?.description ?? projectConfig.description);
1129
+ html = setNamedMeta(html, "keywords", route?.keywords?.join(", "));
911
1130
  html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
912
1131
  return html;
913
1132
  }
914
1133
  var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
915
- import { routes, navigation } from 'virtual:clarify-routes';
1134
+ import { routes, navigation } from 'virtual:clarify-routes/server';
916
1135
  import { config } from 'virtual:clarify-config';
917
1136
  import { openApis } from 'virtual:clarify-openapi-registry';
918
1137
 
@@ -934,7 +1153,10 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
934
1153
  ssr: ssrEntry,
935
1154
  outDir: ssrOutDir,
936
1155
  emptyOutDir: true,
937
- rollupOptions: { input: ssrEntry }
1156
+ rolldownOptions: {
1157
+ input: ssrEntry,
1158
+ checks: { pluginTimings: false }
1159
+ }
938
1160
  }
939
1161
  });
940
1162
  }
@@ -991,6 +1213,7 @@ async function loadClarifyConfig(root, env) {
991
1213
  //#region source/core/virtual-modules.ts
992
1214
  var VIRTUAL_CONFIG = "virtual:clarify-config";
993
1215
  var VIRTUAL_ROUTES = "virtual:clarify-routes";
1216
+ var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
994
1217
  var VIRTUAL_OPENAPI_REGISTRY = openApiRegistryModuleId;
995
1218
  var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
996
1219
  var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
@@ -1006,8 +1229,8 @@ function generateConfigModule(projectConfig, buildOptions) {
1006
1229
  ...buildOptions
1007
1230
  })};`;
1008
1231
  }
1009
- function generateRoutesModule(routes, pagesConfig, resolvedNavigation, projectConfig) {
1010
- const imports = routes.map((r, i) => `import Page${i} from '${r.virtualModuleId}';`).join("\n");
1232
+ function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
1233
+ const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from '${r.virtualModuleId}';`).join("\n") : "";
1011
1234
  const routesArray = routes.map((r, i) => {
1012
1235
  const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
1013
1236
  id: s.id,
@@ -1015,14 +1238,18 @@ function generateRoutesModule(routes, pagesConfig, resolvedNavigation, projectCo
1015
1238
  badge: s.badge,
1016
1239
  tags: s.tags
1017
1240
  })))}` : "";
1018
- const rawContentUrl = r.rawContentUrl ? `, rawContentUrl: ${JSON.stringify(r.rawContentUrl)}` : "";
1241
+ const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
1019
1242
  const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
1020
1243
  const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
1021
1244
  const isFallback = r.isFallback ? ", isFallback: true" : "";
1022
1245
  const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
1023
- return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: Page${i}, kind: '${r.kind}'${basePath}${locale}${isFallback}${alternates}${sections}${rawContentUrl} }`;
1246
+ const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
1247
+ const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
1248
+ const component = mode === "server" ? `Page${i}` : `() => import('${r.virtualModuleId}')`;
1249
+ const lazy = mode === "client" ? ", lazy: true" : "";
1250
+ return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: ${component}${lazy}, kind: '${r.kind}'${basePath}${locale}${isFallback}${alternates}${description}${keywords}${sections}${contentArtifactUrl} }`;
1024
1251
  }).join(",\n");
1025
- const navigation = resolvedNavigation ?? (projectConfig?.i18n ? buildLocalizedNavigation(routes, pagesConfig, projectConfig.i18n) ?? {} : pagesConfig && pagesConfig !== "FileTree" ? buildNavigationFromConfig(routes, pagesConfig) : buildNavigation(routes));
1252
+ const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
1026
1253
  return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
1027
1254
  }
1028
1255
  function createClientEntryModule() {
@@ -1037,7 +1264,8 @@ render({ config, routes, navigation, openApis });`;
1037
1264
  function buildVirtualModules(args) {
1038
1265
  const modules = /* @__PURE__ */ new Map();
1039
1266
  modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
1040
- modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.projectConfig.pages, args.navigation, args.projectConfig));
1267
+ modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
1268
+ modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
1041
1269
  modules.set(VIRTUAL_OPENAPI_REGISTRY, "export const openApis = {};");
1042
1270
  modules.set(VIRTUAL_CLIENT_ENTRY, createClientEntryModule());
1043
1271
  modules.set(RESOLVED_CLIENT_ENTRY, createClientEntryModule());
@@ -1148,7 +1376,7 @@ function clarifyPlugin(options = {}) {
1148
1376
  async function resolveRoutesAndSpecs() {
1149
1377
  routes = await discoverRoutes();
1150
1378
  routes = await runHooks(clarifyPlugins, "routes:discovered", routes, ctx);
1151
- const defaultNavigation = projectConfig.i18n ? buildLocalizedNavigation(routes, projectConfig.pages, projectConfig.i18n) ?? {} : projectConfig.pages && projectConfig.pages !== "FileTree" ? buildNavigationFromConfig(routes, projectConfig.pages) : buildNavigation(routes);
1379
+ const defaultNavigation = projectConfig.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes);
1152
1380
  const resolved = await runHooks(clarifyPlugins, "routes:resolved", {
1153
1381
  routes,
1154
1382
  navigation: defaultNavigation
@@ -1182,6 +1410,20 @@ function clarifyPlugin(options = {}) {
1182
1410
  function hasContentRouteForFile(filePath) {
1183
1411
  return routes.some((route) => route.filePath === filePath);
1184
1412
  }
1413
+ const normalizedMdxContentPlugin = {
1414
+ name: "clarify:normalized-mdx-content",
1415
+ enforce: "pre",
1416
+ transform(_code, id) {
1417
+ if (!/\.mdx?(?:\?|$)/.test(id)) return null;
1418
+ const filePath = id.replace(/\?.*$/, "");
1419
+ const route = routes.find((route) => route.kind === "mdx" && route.filePath === filePath);
1420
+ if (!route || route.content === void 0) return null;
1421
+ return {
1422
+ code: route.content,
1423
+ map: null
1424
+ };
1425
+ }
1426
+ };
1185
1427
  const mdx = (0, _mdx_js_rollup.default)({
1186
1428
  include: ["**/*.{md,mdx}"],
1187
1429
  jsxImportSource: "react",
@@ -1192,6 +1434,7 @@ function clarifyPlugin(options = {}) {
1192
1434
  return [
1193
1435
  (0, _vitejs_plugin_react.default)(),
1194
1436
  (0, _tailwindcss_vite.default)(),
1437
+ normalizedMdxContentPlugin,
1195
1438
  {
1196
1439
  name: "clarify:core",
1197
1440
  async config() {
@@ -1227,6 +1470,7 @@ function clarifyPlugin(options = {}) {
1227
1470
  if (id === "virtual:clarify-entry-client" || id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1228
1471
  if (id === "virtual:clarify-config" || id === resolveVirtualId("virtual:clarify-config")) return resolveVirtualId(VIRTUAL_CONFIG);
1229
1472
  if (id === "virtual:clarify-routes" || id === resolveVirtualId("virtual:clarify-routes")) return resolveVirtualId(VIRTUAL_ROUTES);
1473
+ if (id === "virtual:clarify-routes/server" || id === resolveVirtualId("virtual:clarify-routes/server")) return resolveVirtualId(VIRTUAL_SERVER_ROUTES);
1230
1474
  if (id === VIRTUAL_OPENAPI_REGISTRY || id === resolveVirtualId(VIRTUAL_OPENAPI_REGISTRY)) return resolveVirtualId(VIRTUAL_OPENAPI_REGISTRY);
1231
1475
  const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === stripVirtualPrefix(id));
1232
1476
  if (route) return resolveVirtualId(route.virtualModuleId);
@@ -1272,19 +1516,24 @@ function clarifyPlugin(options = {}) {
1272
1516
  let tempEntryPath;
1273
1517
  try {
1274
1518
  tempEntryPath = createTempEntryFile(SSR_ENTRY_CODE);
1275
- await buildSSRBundle(root, tempEntryPath, ssrOutputDir, [{
1276
- name: "clarify:virtual-ssg",
1277
- resolveId(id) {
1278
- if (id === "virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1279
- if (id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1280
- if (id === "virtual:clarify-config") return id;
1281
- if (id === "virtual:clarify-routes") return id;
1282
- if (id === VIRTUAL_OPENAPI_REGISTRY) return id;
1283
- if (routes.find((r) => r.virtualModuleId === id)) return id;
1284
- return null;
1519
+ await buildSSRBundle(root, tempEntryPath, ssrOutputDir, [
1520
+ {
1521
+ name: "clarify:virtual-ssg",
1522
+ resolveId(id) {
1523
+ if (id === "virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1524
+ if (id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1525
+ if (id === "virtual:clarify-config") return id;
1526
+ if (id === "virtual:clarify-routes") return id;
1527
+ if (id === "virtual:clarify-routes/server") return id;
1528
+ if (id === VIRTUAL_OPENAPI_REGISTRY) return id;
1529
+ if (routes.find((r) => r.virtualModuleId === id)) return id;
1530
+ return null;
1531
+ },
1532
+ load: (id) => loadVirtualModule(id, virtualModules)
1285
1533
  },
1286
- load: (id) => loadVirtualModule(id, virtualModules)
1287
- }, mdx]);
1534
+ normalizedMdxContentPlugin,
1535
+ mdx
1536
+ ]);
1288
1537
  const ssrBundlePath = (0, node_path.join)(ssrOutputDir, "entry-server.js");
1289
1538
  await renderSSGRoutes(routes, projectConfig, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
1290
1539
  } catch (err) {
@@ -1327,4 +1576,4 @@ Object.defineProperty(exports, "loadClarifyConfig", {
1327
1576
  }
1328
1577
  });
1329
1578
 
1330
- //# sourceMappingURL=plugin-CEyCiD5r.cjs.map
1579
+ //# sourceMappingURL=plugin-BSPUvgb3.cjs.map