@clarify-labs/cli 0.2.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.
@@ -102,11 +102,14 @@ var rehypePlugins = [
102
102
  //#endregion
103
103
  //#region source/parsers/frontmatter.ts
104
104
  /**
105
- * 从 MDX 文件内容中提取 frontmatter。
106
- * 使用 gray-matter 支持 YAML frontmatter 的标准语法和类型解析。
105
+ * 在内容入口统一解析 MDX frontmatter,并返回可供后续流程复用的标准化正文。
107
106
  */
108
- function extractFrontmatter(content) {
109
- return matter(content).data;
107
+ function parseFrontmatter(content) {
108
+ const parsed = matter(content);
109
+ return {
110
+ frontmatter: parsed.data,
111
+ content: parsed.content.replace(/^\r?\n/, "")
112
+ };
110
113
  }
111
114
  //#endregion
112
115
  //#region source/parsers/routes.ts
@@ -183,8 +186,7 @@ function findContentRoutes(dir, base = dir) {
183
186
  const relativePath = relative(base, fullPath);
184
187
  const pathParts = relativePath.replace(/\.mdx?$/, "").split("/");
185
188
  const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
186
- const content = readFileSync(fullPath, "utf-8");
187
- const frontmatter = extractFrontmatter(content);
189
+ const { frontmatter, content } = parseFrontmatter(readFileSync(fullPath, "utf-8"));
188
190
  let title = typeof frontmatter.title === "string" ? frontmatter.title : "";
189
191
  if (!title) {
190
192
  const lastPart = pathParts[pathParts.length - 1] ?? "";
@@ -196,13 +198,22 @@ function findContentRoutes(dir, base = dir) {
196
198
  filePath: fullPath,
197
199
  virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/"),
198
200
  title,
201
+ description: typeof frontmatter.description === "string" ? frontmatter.description : void 0,
202
+ keywords: frontmatterKeywords(frontmatter),
199
203
  kind: "mdx",
204
+ frontmatter,
205
+ content,
200
206
  sections: extractMdxSections(content)
201
207
  });
202
208
  }
203
209
  }
204
210
  return routes;
205
211
  }
212
+ function frontmatterKeywords(frontmatter) {
213
+ const value = frontmatter.keywords ?? frontmatter.keyword ?? frontmatter.tags;
214
+ const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
215
+ return keywords.length > 0 ? keywords : void 0;
216
+ }
206
217
  function buildNavigation(routes) {
207
218
  const root = [];
208
219
  for (const route of routes) {
@@ -243,6 +254,30 @@ function resolvePageItem(item) {
243
254
  icon: item.icon
244
255
  };
245
256
  }
257
+ function firstNavigationPath(nodes) {
258
+ for (const node of nodes) {
259
+ if (node.path) return node.path;
260
+ const childPath = node.children ? firstNavigationPath(node.children) : void 0;
261
+ if (childPath) return childPath;
262
+ }
263
+ return "/";
264
+ }
265
+ function buildNavigationFromPagesConfig(routes, config) {
266
+ if (!config || config === "FileTree") return buildNavigation(routes);
267
+ return buildNavigationFromConfig(routes, config);
268
+ }
269
+ function buildNavigationFromTabsConfig(routes, tabs) {
270
+ return { tabs: tabs.map((tab) => {
271
+ const children = buildNavigationFromPagesConfig(routes, tab.pages);
272
+ return {
273
+ type: "tab",
274
+ path: firstNavigationPath(children),
275
+ title: resolveLocalizedText(tab.tab, "", "") ?? "",
276
+ icon: tab.icon,
277
+ children
278
+ };
279
+ }) };
280
+ }
246
281
  function buildNavigationFromConfig(routes, config) {
247
282
  const routeMap = new Map(routes.map((r) => [r.path, r]));
248
283
  return config.map((group) => {
@@ -283,69 +318,94 @@ function localizeNavigationPaths(nodes, locale, i18n) {
283
318
  children: node.children ? localizeNavigationPaths(node.children, locale, i18n) : void 0
284
319
  }));
285
320
  }
286
- function buildLocalizedNavigation(routes, config, i18n) {
321
+ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
322
+ if (!config || config === "FileTree") return localizeNavigationPaths(buildNavigation(routes.map((route) => ({
323
+ ...route,
324
+ path: route.basePath ?? route.path
325
+ }))), locale, i18n);
326
+ const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
327
+ return config.map((group) => {
328
+ const children = group.pages.map((item) => {
329
+ const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
330
+ const ref = openapiRef ?? pageRef ?? "";
331
+ const basePath = basePathFromRef(ref);
332
+ const route = routeMap.get(basePath);
333
+ const path = route?.path ?? localizedRoutePath(basePath, locale, i18n);
334
+ return {
335
+ path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale, i18n) : void 0) ?? path,
336
+ title: resolveLocalizedText(title, locale, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
337
+ icon,
338
+ sections: route?.sections ? navigationSections(route.sections) : void 0
339
+ };
340
+ });
341
+ return {
342
+ path: children[0]?.path ?? localizedRoutePath("/", locale, i18n),
343
+ title: resolveLocalizedText(group.group, locale, i18n.defaultLocale) ?? "",
344
+ icon: group.icon,
345
+ children
346
+ };
347
+ });
348
+ }
349
+ function buildLocalizedNavigationFromTabsConfig(routes, tabs, i18n) {
287
350
  if (!i18n) return void 0;
288
351
  const result = {};
289
352
  for (const locale of i18n.locales) {
290
353
  const localeRoutes = routes.filter((route) => route.locale === locale.code);
291
- if (!config || config === "FileTree") {
292
- const baseNavigation = buildNavigation(localeRoutes.map((route) => ({
293
- ...route,
294
- path: route.basePath ?? route.path
295
- })));
296
- result[locale.code] = localizeNavigationPaths(baseNavigation, locale.code, i18n);
297
- continue;
298
- }
299
- const routeMap = new Map(localeRoutes.map((route) => [route.basePath ?? route.path, route]));
300
- result[locale.code] = config.map((group) => {
301
- const children = group.pages.map((item) => {
302
- const { pageRef, openapiRef, redirect, title, icon } = resolvePageItem(item);
303
- const ref = openapiRef ?? pageRef ?? "";
304
- const basePath = basePathFromRef(ref);
305
- const route = routeMap.get(basePath);
306
- const path = route?.path ?? localizedRoutePath(basePath, locale.code, i18n);
307
- return {
308
- path: (redirect ? localizedRoutePath(basePathFromRef(redirect), locale.code, i18n) : void 0) ?? path,
309
- title: resolveLocalizedText(title, locale.code, i18n.defaultLocale) ?? route?.title ?? kebabToTitle$1(basePath.split("/").pop() ?? ref),
310
- icon,
311
- sections: route?.sections ? navigationSections(route.sections) : void 0
312
- };
313
- });
354
+ result[locale.code] = { tabs: tabs.map((tab) => {
355
+ const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, i18n);
314
356
  return {
315
- path: children[0]?.path ?? localizedRoutePath("/", locale.code, i18n),
316
- title: resolveLocalizedText(group.group, locale.code, i18n.defaultLocale) ?? "",
317
- icon: group.icon,
357
+ type: "tab",
358
+ path: firstNavigationPath(children),
359
+ title: resolveLocalizedText(tab.tab, locale.code, i18n.defaultLocale) ?? "",
360
+ icon: tab.icon,
318
361
  children
319
362
  };
320
- });
363
+ }) };
321
364
  }
322
365
  return result;
323
366
  }
324
367
  //#endregion
325
- //#region source/plugins/content-artifacts/raw-content.ts
368
+ //#region source/plugins/content-artifacts/artifacts.ts
369
+ var UTF8_SIGNATURE = "";
326
370
  function normalizeBasePath(basePath) {
327
371
  if (!basePath || basePath === "/") return "";
328
372
  return "/" + basePath.replace(/^\/+|\/+$/g, "");
329
373
  }
330
- function routeToMarkdownUrl(routePath) {
374
+ function containsNonAscii(content) {
375
+ return [...content].some((character) => character.codePointAt(0) > 127);
376
+ }
377
+ function withUtf8Signature(content) {
378
+ if (!containsNonAscii(content) || content.startsWith(UTF8_SIGNATURE)) return content;
379
+ return `${UTF8_SIGNATURE}${content}`;
380
+ }
381
+ function shouldUseUtf8Signature(route) {
382
+ if (route.kind === "mdx") return true;
383
+ return /\.ya?ml$/i.test(route.contentArtifactUrl ?? "");
384
+ }
385
+ function routeToMarkdownArtifactUrl(routePath) {
331
386
  return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.md`;
332
387
  }
333
- function routeToOpenAPIUrl(routePath, filePath) {
388
+ function routeToOpenAPIArtifactUrl(routePath, filePath) {
334
389
  const extension = extname(filePath).toLowerCase() || ".json";
335
390
  return `${routePath === "/" ? "/index" : routePath.replace(/\/$/, "")}.openapi${extension}`;
336
391
  }
337
- function enrichRoutesWithRawContent(routes) {
338
- for (const route of routes) route.rawContentUrl = route.kind === "openapi" ? routeToOpenAPIUrl(route.path, route.filePath) : routeToMarkdownUrl(route.path);
392
+ function attachContentArtifactUrls(routes) {
393
+ for (const route of routes) route.contentArtifactUrl = route.kind === "openapi" ? routeToOpenAPIArtifactUrl(route.path, route.filePath) : routeToMarkdownArtifactUrl(route.path);
339
394
  }
340
- function readRawContent(route) {
341
- return readFileSync(route.filePath, "utf-8");
395
+ function readRouteContent(route) {
396
+ if (route.content !== void 0) return route.content;
397
+ throw new Error(`Route content is missing from route context: ${route.filePath}`);
342
398
  }
343
- function writeRawContentFiles(routes, outputDirectory) {
399
+ function readRouteArtifactContent(route) {
400
+ const content = readRouteContent(route);
401
+ return shouldUseUtf8Signature(route) ? withUtf8Signature(content) : content;
402
+ }
403
+ function writeContentArtifactFiles(routes, outputDirectory) {
344
404
  for (const route of routes) {
345
- if (!route.rawContentUrl) continue;
346
- const outFile = join(outputDirectory, route.rawContentUrl.replace(/^\//, ""));
405
+ if (!route.contentArtifactUrl) continue;
406
+ const outFile = join(outputDirectory, route.contentArtifactUrl.replace(/^\//, ""));
347
407
  mkdirSync(dirname(outFile), { recursive: true });
348
- writeFileSync(outFile, readRawContent(route), "utf-8");
408
+ writeFileSync(outFile, readRouteArtifactContent(route), "utf-8");
349
409
  }
350
410
  }
351
411
  function createLlmsTxt(routes, projectConfig) {
@@ -354,22 +414,25 @@ function createLlmsTxt(routes, projectConfig) {
354
414
  if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
355
415
  lines.push("## Docs");
356
416
  for (const route of routes.filter((route) => route.kind === "mdx")) {
357
- if (!route.rawContentUrl) continue;
358
- lines.push(`- [${route.title}](${basePath}${route.rawContentUrl})`);
417
+ if (!route.contentArtifactUrl) continue;
418
+ lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
359
419
  }
360
420
  const openApiRoutes = routes.filter((route) => route.kind === "openapi");
361
421
  if (openApiRoutes.length > 0) {
362
422
  lines.push("", "## OpenAPI");
363
423
  for (const route of openApiRoutes) {
364
- if (!route.rawContentUrl) continue;
365
- lines.push(`- [${route.title}](${basePath}${route.rawContentUrl})`);
424
+ if (!route.contentArtifactUrl) continue;
425
+ lines.push(`- [${route.title}](${basePath}${route.contentArtifactUrl})`);
366
426
  }
367
427
  }
368
428
  return `${lines.join("\n")}\n`;
369
429
  }
430
+ function createLlmsTxtArtifact(routes, projectConfig) {
431
+ return withUtf8Signature(createLlmsTxt(routes, projectConfig));
432
+ }
370
433
  function writeLlmsTxt(routes, projectConfig, outputDirectory) {
371
434
  mkdirSync(outputDirectory, { recursive: true });
372
- writeFileSync(join(outputDirectory, "llms.txt"), createLlmsTxt(routes, projectConfig), "utf-8");
435
+ writeFileSync(join(outputDirectory, "llms.txt"), createLlmsTxtArtifact(routes, projectConfig), "utf-8");
373
436
  }
374
437
  //#endregion
375
438
  //#region source/plugins/content-artifacts/server.ts
@@ -382,8 +445,8 @@ function resolveContentArtifactPath(url, projectConfig) {
382
445
  const basePath = normalizeRoutePrefix(projectConfig.routePrefix);
383
446
  return basePath && requestPath.startsWith(basePath) ? requestPath.slice(basePath.length) || "/" : requestPath;
384
447
  }
385
- function resolveRawContentType(route) {
386
- if (route.kind === "openapi" && /\.ya?ml$/i.test(route.rawContentUrl ?? "")) return "text/yaml; charset=utf-8";
448
+ function resolveContentArtifactType(route) {
449
+ if (route.kind === "openapi" && /\.ya?ml$/i.test(route.contentArtifactUrl ?? "")) return "text/yaml; charset=utf-8";
387
450
  return route.kind === "openapi" ? "application/json; charset=utf-8" : "text/markdown; charset=utf-8";
388
451
  }
389
452
  function serveContentArtifacts(req, res, projectConfig, routes) {
@@ -391,14 +454,14 @@ function serveContentArtifacts(req, res, projectConfig, routes) {
391
454
  if (contentPath === "/llms.txt") {
392
455
  res.statusCode = 200;
393
456
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
394
- res.end(createLlmsTxt(routes, projectConfig));
457
+ res.end(createLlmsTxtArtifact(routes, projectConfig), "utf8");
395
458
  return true;
396
459
  }
397
- const route = routes.find((route) => route.rawContentUrl === contentPath);
460
+ const route = routes.find((route) => route.contentArtifactUrl === contentPath);
398
461
  if (!route) return false;
399
462
  res.statusCode = 200;
400
- res.setHeader("Content-Type", resolveRawContentType(route));
401
- res.end(readRawContent(route));
463
+ res.setHeader("Content-Type", resolveContentArtifactType(route));
464
+ res.end(readRouteArtifactContent(route), "utf8");
402
465
  return true;
403
466
  }
404
467
  //#endregion
@@ -408,7 +471,7 @@ function createContentArtifactsPlugin() {
408
471
  name: "clarify:content-artifacts",
409
472
  hooks: {
410
473
  "routes:resolved": (input) => {
411
- enrichRoutesWithRawContent(input.routes);
474
+ attachContentArtifactUrls(input.routes);
412
475
  return input;
413
476
  },
414
477
  "dev:configureServer": (server, ctx) => {
@@ -420,7 +483,7 @@ function createContentArtifactsPlugin() {
420
483
  "build:done": (ctx) => {
421
484
  const outputDirectory = ctx.generateOptions.outputDirectory;
422
485
  if (!outputDirectory) return;
423
- writeRawContentFiles(ctx.routes, outputDirectory);
486
+ writeContentArtifactFiles(ctx.routes, outputDirectory);
424
487
  writeLlmsTxt(ctx.routes, ctx.projectConfig, outputDirectory);
425
488
  }
426
489
  }
@@ -497,6 +560,9 @@ var OPENAPI_HTTP_METHODS = [
497
560
  "patch",
498
561
  "trace"
499
562
  ];
563
+ function errorMessage(error) {
564
+ return error instanceof Error ? error.message : String(error);
565
+ }
500
566
  function kebabToTitle(str) {
501
567
  return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
502
568
  }
@@ -519,7 +585,8 @@ function findOpenAPIRoutes(dir, base = dir) {
519
585
  filePath: fullPath,
520
586
  virtualModuleId: virtualModuleIdFromRef(ref),
521
587
  title: kebabToTitle(cleanPath.split("/").pop() ?? "API"),
522
- kind: "openapi"
588
+ kind: "openapi",
589
+ content: readFileSync(fullPath, "utf-8")
523
590
  });
524
591
  }
525
592
  return routes;
@@ -546,9 +613,20 @@ function extractOpenAPISections(spec) {
546
613
  }
547
614
  async function readOpenAPISpec(filePath) {
548
615
  try {
549
- return await SwaggerParser.dereference(filePath);
550
- } catch {
551
- return null;
616
+ return {
617
+ ok: true,
618
+ spec: await SwaggerParser.dereference(filePath)
619
+ };
620
+ } catch (error) {
621
+ return {
622
+ ok: false,
623
+ diagnostic: {
624
+ title: "OpenAPI spec parse failed",
625
+ message: `Clarify could not parse ${filePath}.`,
626
+ filePath,
627
+ cause: errorMessage(error)
628
+ }
629
+ };
552
630
  }
553
631
  }
554
632
  //#endregion
@@ -565,6 +643,27 @@ export default function OpenApiRoutePage() {
565
643
  return createElement(OpenApiPage, { spec });
566
644
  }`;
567
645
  }
646
+ function generateOpenAPIErrorModule(diagnostic) {
647
+ return `import { createElement } from 'react';
648
+ const diagnostic = ${JSON.stringify(diagnostic)};
649
+ export default function OpenApiErrorRoutePage() {
650
+ return createElement('article', { className: 'flex h-full flex-col pt-16 pb-10' },
651
+ createElement('div', { className: 'prose max-w-none flex-auto dark:prose-invert' },
652
+ createElement('p', { className: 'mb-3 font-mono text-xs/6 font-medium tracking-widest text-red-500 uppercase dark:text-red-400' }, 'OpenAPI Error'),
653
+ createElement('h1', null, diagnostic.title),
654
+ createElement('p', { className: 'lead' }, diagnostic.message),
655
+ diagnostic.filePath ? createElement('div', { className: 'not-prose mt-6 rounded-lg border border-zinc-200 bg-zinc-50 p-4 text-sm dark:border-white/10 dark:bg-white/5' },
656
+ createElement('div', { className: 'mb-1 font-semibold text-zinc-900 dark:text-white' }, 'File'),
657
+ createElement('code', { className: 'break-all text-zinc-700 dark:text-zinc-300' }, diagnostic.filePath)
658
+ ) : null,
659
+ diagnostic.cause ? createElement('div', { className: 'not-prose mt-4 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200' },
660
+ createElement('div', { className: 'mb-1 font-semibold' }, 'Why it happened'),
661
+ createElement('pre', { className: 'whitespace-pre-wrap break-words font-mono text-xs' }, diagnostic.cause)
662
+ ) : null
663
+ )
664
+ );
665
+ }`;
666
+ }
568
667
  //#endregion
569
668
  //#region source/plugins/openapi/index.ts
570
669
  function createOpenAPIPlugin() {
@@ -579,18 +678,26 @@ function createOpenAPIPlugin() {
579
678
  "routes:discovered": async (routes) => {
580
679
  for (const key of Object.keys(specs)) delete specs[key];
581
680
  for (const route of routes.filter((route) => route.kind === "openapi")) {
582
- const spec = await readOpenAPISpec(route.filePath);
583
- if (!spec) throw new Error(`Failed to parse OpenAPI spec: ${route.filePath}`);
681
+ const result = await readOpenAPISpec(route.filePath);
682
+ if (!result.ok) {
683
+ route.diagnostic = result.diagnostic;
684
+ route.title = route.title || "OpenAPI parse error";
685
+ route.sections = [];
686
+ continue;
687
+ }
688
+ const spec = result.spec;
584
689
  specs[route.virtualModuleId] = spec;
585
690
  specs[`virtual:clarify-page/${route.path.replace(/^\//, "")}`] = spec;
586
691
  route.title = spec.info?.title ?? route.title;
692
+ route.description = spec.info?.description ?? route.description;
587
693
  route.sections = extractOpenAPISections(spec);
588
694
  }
589
695
  return routes;
590
696
  },
591
- "modules:before": (modules) => {
697
+ "modules:before": (modules, ctx) => {
592
698
  modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(specs));
593
699
  for (const [moduleId, spec] of Object.entries(specs)) modules.set(moduleId, generateOpenAPIModule(spec));
700
+ for (const route of ctx.routes.filter((route) => route.kind === "openapi" && route.diagnostic)) if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
594
701
  return modules;
595
702
  }
596
703
  }
@@ -674,20 +781,122 @@ var clarifyPagesGroupSchema = z.object({
674
781
  pages: z.array(clarifyPagesItemSchema)
675
782
  });
676
783
  var clarifyPagesConfigSchema = z.union([z.array(clarifyPagesGroupSchema), z.literal("FileTree")]);
784
+ var clarifyTabItemSchema = z.object({
785
+ tab: clarifyLocalizedTextSchema,
786
+ icon: z.string().optional(),
787
+ pages: clarifyPagesConfigSchema.optional()
788
+ });
789
+ var clarifyTabsConfigSchema = z.array(clarifyTabItemSchema);
790
+ var clarifyThemePresetSchema = z.enum(["default", "base"]);
791
+ var clarifyThemeColorTokensConfigSchema = z.object({
792
+ primary: z.string().optional(),
793
+ accent: z.string().optional(),
794
+ background: z.string().optional(),
795
+ foreground: z.string().optional(),
796
+ surface: z.string().optional(),
797
+ muted: z.string().optional(),
798
+ border: z.string().optional(),
799
+ codeBackground: z.string().optional()
800
+ });
801
+ var clarifyThemeRadiusTokensConfigSchema = z.object({
802
+ sm: z.string().optional(),
803
+ md: z.string().optional(),
804
+ lg: z.string().optional(),
805
+ xl: z.string().optional()
806
+ });
807
+ var clarifyThemeTokensConfigSchema = z.object({
808
+ colors: clarifyThemeColorTokensConfigSchema.optional(),
809
+ radius: clarifyThemeRadiusTokensConfigSchema.optional()
810
+ });
811
+ var clarifyThemeLayoutConfigSchema = z.object({ maxWidth: z.string().optional() });
812
+ var clarifyThemeConfigSchema = z.object({
813
+ preset: clarifyThemePresetSchema.optional(),
814
+ tokens: clarifyThemeTokensConfigSchema.optional(),
815
+ layout: clarifyThemeLayoutConfigSchema.optional()
816
+ });
677
817
  var clarifyProjectConfigSchema = z.object({
678
818
  title: z.string().optional(),
679
819
  description: z.string().optional(),
680
820
  logo: clarifyLogoConfigSchema.optional(),
681
821
  favicon: clarifyFaviconConfigSchema.optional(),
682
- theme: z.object({ primary: z.string().optional() }).optional(),
822
+ theme: clarifyThemeConfigSchema.optional(),
683
823
  routePrefix: z.string().optional(),
684
824
  navbar: z.object({ links: z.array(clarifyNavbarLinkSchema).optional() }).optional(),
685
825
  banner: clarifyBannerConfigSchema.optional(),
686
826
  footer: clarifyFooterConfigSchema.optional(),
687
827
  i18n: clarifyI18nConfigSchema.optional(),
688
- pages: clarifyPagesConfigSchema.optional()
828
+ tabs: clarifyTabsConfigSchema.optional()
689
829
  });
690
830
  //#endregion
831
+ //#region source/core/theme.ts
832
+ var clarifyThemePresets = {
833
+ default: {
834
+ preset: "default",
835
+ tokens: {
836
+ colors: {
837
+ primary: "#00D492",
838
+ accent: "#00F6C9",
839
+ background: "#ffffff",
840
+ foreground: "#111827",
841
+ surface: "#ffffff",
842
+ muted: "#64748b",
843
+ border: "rgb(15 23 42 / 0.12)",
844
+ codeBackground: "#f7fefb"
845
+ },
846
+ radius: {
847
+ sm: "6px",
848
+ md: "10px",
849
+ lg: "14px",
850
+ xl: "18px"
851
+ }
852
+ },
853
+ layout: { maxWidth: "82rem" }
854
+ },
855
+ base: {
856
+ preset: "base",
857
+ tokens: {
858
+ colors: {
859
+ primary: "#18181b",
860
+ accent: "#52525b",
861
+ background: "#ffffff",
862
+ foreground: "#18181b",
863
+ surface: "#ffffff",
864
+ muted: "#71717a",
865
+ border: "rgb(24 24 27 / 0.12)",
866
+ codeBackground: "#f4f4f5"
867
+ },
868
+ radius: {
869
+ sm: "6px",
870
+ md: "8px",
871
+ lg: "12px",
872
+ xl: "16px"
873
+ }
874
+ },
875
+ layout: { maxWidth: "80rem" }
876
+ }
877
+ };
878
+ function resolveThemeConfig(theme = {}) {
879
+ const preset = theme.preset ?? "default";
880
+ const presetTheme = clarifyThemePresets[preset];
881
+ return {
882
+ preset,
883
+ tokens: {
884
+ colors: {
885
+ ...presetTheme.tokens.colors,
886
+ ...theme.tokens?.colors
887
+ },
888
+ radius: {
889
+ ...presetTheme.tokens.radius,
890
+ ...theme.tokens?.radius
891
+ }
892
+ },
893
+ layout: {
894
+ ...presetTheme.layout,
895
+ ...theme.layout
896
+ }
897
+ };
898
+ }
899
+ //#endregion
691
900
  //#region source/core/config.ts
692
901
  function formatIssuePath(path) {
693
902
  return path.reduce((result, segment) => {
@@ -726,12 +935,12 @@ function resolveProjectConfig(config = {}) {
726
935
  logo: config.logo,
727
936
  favicon: config.favicon,
728
937
  routePrefix: config.routePrefix ?? "/",
729
- theme: config.theme ?? {},
938
+ theme: resolveThemeConfig(config.theme),
730
939
  navbar: config.navbar,
731
940
  banner: config.banner,
732
941
  footer: config.footer,
733
942
  i18n: resolveI18nConfig(config.i18n),
734
- pages: config.pages
943
+ tabs: config.tabs
735
944
  };
736
945
  }
737
946
  //#endregion
@@ -871,18 +1080,28 @@ function injectHtmlLocaleAttributes(html, projectConfig, route) {
871
1080
  return `<html${nextAttributes}>`;
872
1081
  });
873
1082
  }
1083
+ function routeTitle(projectConfig, route) {
1084
+ const title = route?.title?.trim();
1085
+ if (!title || title === projectConfig.title) return projectConfig.title;
1086
+ return `${title} - ${projectConfig.title}`;
1087
+ }
1088
+ function setNamedMeta(html, name, content) {
1089
+ const metaPattern = new RegExp(`<meta\\b(?=[^>]*\\bname=["']${name}["'])[^>]*>\\n?`, "i");
1090
+ if (!content) return html.replace(metaPattern, "");
1091
+ const meta = `<meta name="${name}" content="${escapeHtml(content)}" />`;
1092
+ if (metaPattern.test(html)) return html.replace(metaPattern, meta);
1093
+ return html.replace("</head>", ` ${meta}\n </head>`);
1094
+ }
874
1095
  function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
875
1096
  let html = injectHtmlLocaleAttributes(template, projectConfig, route);
876
- html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(projectConfig.title)}</title>`);
877
- if (projectConfig.description && !html.includes("name=\"description\"")) {
878
- const descriptionMeta = `<meta name="description" content="${escapeHtml(projectConfig.description)}" />`;
879
- html = html.replace("</head>", ` ${descriptionMeta}\n </head>`);
880
- }
1097
+ html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(projectConfig, route))}</title>`);
1098
+ html = setNamedMeta(html, "description", route?.description ?? projectConfig.description);
1099
+ html = setNamedMeta(html, "keywords", route?.keywords?.join(", "));
881
1100
  html = html.replace(/<div id="root">[\s\S]*?<\/div>/, `<div id="root">${appHtml}</div>`);
882
1101
  return html;
883
1102
  }
884
1103
  var SSR_ENTRY_CODE = `import { renderToHTML } from '@clarify-labs/renderer/server';
885
- import { routes, navigation } from 'virtual:clarify-routes';
1104
+ import { routes, navigation } from 'virtual:clarify-routes/server';
886
1105
  import { config } from 'virtual:clarify-config';
887
1106
  import { openApis } from 'virtual:clarify-openapi-registry';
888
1107
 
@@ -904,7 +1123,10 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
904
1123
  ssr: ssrEntry,
905
1124
  outDir: ssrOutDir,
906
1125
  emptyOutDir: true,
907
- rollupOptions: { input: ssrEntry }
1126
+ rolldownOptions: {
1127
+ input: ssrEntry,
1128
+ checks: { pluginTimings: false }
1129
+ }
908
1130
  }
909
1131
  });
910
1132
  }
@@ -961,6 +1183,7 @@ async function loadClarifyConfig(root, env) {
961
1183
  //#region source/core/virtual-modules.ts
962
1184
  var VIRTUAL_CONFIG = "virtual:clarify-config";
963
1185
  var VIRTUAL_ROUTES = "virtual:clarify-routes";
1186
+ var VIRTUAL_SERVER_ROUTES = "virtual:clarify-routes/server";
964
1187
  var VIRTUAL_OPENAPI_REGISTRY = openApiRegistryModuleId;
965
1188
  var VIRTUAL_CLIENT_ENTRY = "virtual:clarify-entry-client";
966
1189
  var RESOLVED_CLIENT_ENTRY = "\0virtual:clarify-entry-client";
@@ -976,8 +1199,8 @@ function generateConfigModule(projectConfig, buildOptions) {
976
1199
  ...buildOptions
977
1200
  })};`;
978
1201
  }
979
- function generateRoutesModule(routes, pagesConfig, resolvedNavigation, projectConfig) {
980
- const imports = routes.map((r, i) => `import Page${i} from '${r.virtualModuleId}';`).join("\n");
1202
+ function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
1203
+ const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from '${r.virtualModuleId}';`).join("\n") : "";
981
1204
  const routesArray = routes.map((r, i) => {
982
1205
  const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
983
1206
  id: s.id,
@@ -985,14 +1208,18 @@ function generateRoutesModule(routes, pagesConfig, resolvedNavigation, projectCo
985
1208
  badge: s.badge,
986
1209
  tags: s.tags
987
1210
  })))}` : "";
988
- const rawContentUrl = r.rawContentUrl ? `, rawContentUrl: ${JSON.stringify(r.rawContentUrl)}` : "";
1211
+ const contentArtifactUrl = r.contentArtifactUrl ? `, contentArtifactUrl: ${JSON.stringify(r.contentArtifactUrl)}` : "";
989
1212
  const basePath = r.basePath ? `, basePath: ${JSON.stringify(r.basePath)}` : "";
990
1213
  const locale = r.locale ? `, locale: ${JSON.stringify(r.locale)}` : "";
991
1214
  const isFallback = r.isFallback ? ", isFallback: true" : "";
992
1215
  const alternates = r.alternates ? `, alternates: ${JSON.stringify(r.alternates)}` : "";
993
- return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: Page${i}, kind: '${r.kind}'${basePath}${locale}${isFallback}${alternates}${sections}${rawContentUrl} }`;
1216
+ const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
1217
+ const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
1218
+ const component = mode === "server" ? `Page${i}` : `() => import('${r.virtualModuleId}')`;
1219
+ const lazy = mode === "client" ? ", lazy: true" : "";
1220
+ 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} }`;
994
1221
  }).join(",\n");
995
- const navigation = resolvedNavigation ?? (projectConfig?.i18n ? buildLocalizedNavigation(routes, pagesConfig, projectConfig.i18n) ?? {} : pagesConfig && pagesConfig !== "FileTree" ? buildNavigationFromConfig(routes, pagesConfig) : buildNavigation(routes));
1222
+ const navigation = resolvedNavigation ?? (projectConfig?.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes));
996
1223
  return `${imports}\n\nexport const routes = [\n${routesArray}\n];\n\nexport const navigation = ${JSON.stringify(navigation, null, 2)};\n`;
997
1224
  }
998
1225
  function createClientEntryModule() {
@@ -1007,7 +1234,8 @@ render({ config, routes, navigation, openApis });`;
1007
1234
  function buildVirtualModules(args) {
1008
1235
  const modules = /* @__PURE__ */ new Map();
1009
1236
  modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
1010
- modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.projectConfig.pages, args.navigation, args.projectConfig));
1237
+ modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
1238
+ modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
1011
1239
  modules.set(VIRTUAL_OPENAPI_REGISTRY, "export const openApis = {};");
1012
1240
  modules.set(VIRTUAL_CLIENT_ENTRY, createClientEntryModule());
1013
1241
  modules.set(RESOLVED_CLIENT_ENTRY, createClientEntryModule());
@@ -1118,7 +1346,7 @@ function clarifyPlugin(options = {}) {
1118
1346
  async function resolveRoutesAndSpecs() {
1119
1347
  routes = await discoverRoutes();
1120
1348
  routes = await runHooks(clarifyPlugins, "routes:discovered", routes, ctx);
1121
- const defaultNavigation = projectConfig.i18n ? buildLocalizedNavigation(routes, projectConfig.pages, projectConfig.i18n) ?? {} : projectConfig.pages && projectConfig.pages !== "FileTree" ? buildNavigationFromConfig(routes, projectConfig.pages) : buildNavigation(routes);
1349
+ const defaultNavigation = projectConfig.tabs ? projectConfig.i18n ? buildLocalizedNavigationFromTabsConfig(routes, projectConfig.tabs, projectConfig.i18n) ?? {} : buildNavigationFromTabsConfig(routes, projectConfig.tabs) : buildNavigation(routes);
1122
1350
  const resolved = await runHooks(clarifyPlugins, "routes:resolved", {
1123
1351
  routes,
1124
1352
  navigation: defaultNavigation
@@ -1152,6 +1380,20 @@ function clarifyPlugin(options = {}) {
1152
1380
  function hasContentRouteForFile(filePath) {
1153
1381
  return routes.some((route) => route.filePath === filePath);
1154
1382
  }
1383
+ const normalizedMdxContentPlugin = {
1384
+ name: "clarify:normalized-mdx-content",
1385
+ enforce: "pre",
1386
+ transform(_code, id) {
1387
+ if (!/\.mdx?(?:\?|$)/.test(id)) return null;
1388
+ const filePath = id.replace(/\?.*$/, "");
1389
+ const route = routes.find((route) => route.kind === "mdx" && route.filePath === filePath);
1390
+ if (!route || route.content === void 0) return null;
1391
+ return {
1392
+ code: route.content,
1393
+ map: null
1394
+ };
1395
+ }
1396
+ };
1155
1397
  const mdx = mdxPlugin({
1156
1398
  include: ["**/*.{md,mdx}"],
1157
1399
  jsxImportSource: "react",
@@ -1162,6 +1404,7 @@ function clarifyPlugin(options = {}) {
1162
1404
  return [
1163
1405
  react(),
1164
1406
  tailwindcss(),
1407
+ normalizedMdxContentPlugin,
1165
1408
  {
1166
1409
  name: "clarify:core",
1167
1410
  async config() {
@@ -1197,6 +1440,7 @@ function clarifyPlugin(options = {}) {
1197
1440
  if (id === "virtual:clarify-entry-client" || id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1198
1441
  if (id === "virtual:clarify-config" || id === resolveVirtualId("virtual:clarify-config")) return resolveVirtualId(VIRTUAL_CONFIG);
1199
1442
  if (id === "virtual:clarify-routes" || id === resolveVirtualId("virtual:clarify-routes")) return resolveVirtualId(VIRTUAL_ROUTES);
1443
+ if (id === "virtual:clarify-routes/server" || id === resolveVirtualId("virtual:clarify-routes/server")) return resolveVirtualId(VIRTUAL_SERVER_ROUTES);
1200
1444
  if (id === VIRTUAL_OPENAPI_REGISTRY || id === resolveVirtualId(VIRTUAL_OPENAPI_REGISTRY)) return resolveVirtualId(VIRTUAL_OPENAPI_REGISTRY);
1201
1445
  const route = routes.find((r) => r.virtualModuleId === id || r.virtualModuleId === stripVirtualPrefix(id));
1202
1446
  if (route) return resolveVirtualId(route.virtualModuleId);
@@ -1242,19 +1486,24 @@ function clarifyPlugin(options = {}) {
1242
1486
  let tempEntryPath;
1243
1487
  try {
1244
1488
  tempEntryPath = createTempEntryFile(SSR_ENTRY_CODE);
1245
- await buildSSRBundle(root, tempEntryPath, ssrOutputDir, [{
1246
- name: "clarify:virtual-ssg",
1247
- resolveId(id) {
1248
- if (id === "virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1249
- if (id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1250
- if (id === "virtual:clarify-config") return id;
1251
- if (id === "virtual:clarify-routes") return id;
1252
- if (id === VIRTUAL_OPENAPI_REGISTRY) return id;
1253
- if (routes.find((r) => r.virtualModuleId === id)) return id;
1254
- return null;
1489
+ await buildSSRBundle(root, tempEntryPath, ssrOutputDir, [
1490
+ {
1491
+ name: "clarify:virtual-ssg",
1492
+ resolveId(id) {
1493
+ if (id === "virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1494
+ if (id === "\0virtual:clarify-entry-client") return RESOLVED_CLIENT_ENTRY;
1495
+ if (id === "virtual:clarify-config") return id;
1496
+ if (id === "virtual:clarify-routes") return id;
1497
+ if (id === "virtual:clarify-routes/server") return id;
1498
+ if (id === VIRTUAL_OPENAPI_REGISTRY) return id;
1499
+ if (routes.find((r) => r.virtualModuleId === id)) return id;
1500
+ return null;
1501
+ },
1502
+ load: (id) => loadVirtualModule(id, virtualModules)
1255
1503
  },
1256
- load: (id) => loadVirtualModule(id, virtualModules)
1257
- }, mdx]);
1504
+ normalizedMdxContentPlugin,
1505
+ mdx
1506
+ ]);
1258
1507
  const ssrBundlePath = join(ssrOutputDir, "entry-server.js");
1259
1508
  await renderSSGRoutes(routes, projectConfig, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
1260
1509
  } catch (err) {
@@ -1274,4 +1523,4 @@ function clarifyPlugin(options = {}) {
1274
1523
  //#endregion
1275
1524
  export { createClarifyRuntimeAliases as i, defineConfig as n, loadClarifyConfig as r, clarifyPlugin as t };
1276
1525
 
1277
- //# sourceMappingURL=plugin-BUWojlPv.js.map
1526
+ //# sourceMappingURL=plugin-B-_5mQmO.js.map