@clarify-labs/cli 0.10.3 → 0.11.1
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/mcp.d.ts +27 -0
- package/output/cli/mcp/orama-loader.d.ts +56 -0
- package/output/cli/mcp/search-server.d.ts +34 -0
- package/output/cli/program.d.ts +0 -1
- package/output/cli.cjs +294 -13
- package/output/cli.cjs.map +1 -1
- package/output/cli.js +295 -14
- package/output/cli.js.map +1 -1
- package/output/core/config/config-schema.d.ts +138 -253
- package/output/core/config/config.d.ts +2 -1
- package/output/core/config/options.d.ts +0 -8
- package/output/core/config/user-config.d.ts +4 -3
- package/output/core/plugin/hooks.d.ts +0 -1
- package/output/core/project/project-info.d.ts +3 -3
- package/output/core/runtime/html-template.d.ts +0 -6
- package/output/core/runtime/ssg.d.ts +1 -1
- package/output/core/runtime/virtual-modules.d.ts +1 -5
- package/output/{engine-BDdPszs2.cjs → engine-Cbn_coqg.cjs} +569 -170
- package/output/engine-Cbn_coqg.cjs.map +1 -0
- package/output/{engine-CyFtr8Pz.js → engine-CxnKEKv8.js} +558 -171
- package/output/engine-CxnKEKv8.js.map +1 -0
- package/output/index.cjs +1 -1
- package/output/index.js +1 -1
- package/output/parsers/routes/routes.d.ts +7 -11
- package/output/parsers/routes/routes.test-utils.d.ts +2 -2
- package/output/plugins/content-artifacts/artifacts.d.ts +0 -2
- package/output/plugins/mcp-search/index.d.ts +15 -0
- package/output/plugins/mcp-search/mcp-config.d.ts +72 -0
- package/output/plugins/mcp-search/orama-index.d.ts +153 -0
- package/output/plugins/openapi/virtual-modules.d.ts +0 -1
- package/output/plugins/page-search/index.d.ts +2 -0
- package/output/plugins/source-links/source-links.d.ts +3 -3
- package/output/types.d.ts +64 -33
- package/package.json +6 -4
- package/output/engine-BDdPszs2.cjs.map +0 -1
- package/output/engine-CyFtr8Pz.js.map +0 -1
- package/output/plugins/search-index/index.d.ts +0 -2
- /package/output/plugins/{search-index → page-search}/search-language.d.ts +0 -0
|
@@ -48,6 +48,8 @@ let node_fs = require("node:fs");
|
|
|
48
48
|
let vite = require("vite");
|
|
49
49
|
let zod = require("zod");
|
|
50
50
|
let yaml = require("yaml");
|
|
51
|
+
let _msgpack_msgpack = require("@msgpack/msgpack");
|
|
52
|
+
let _orama_orama = require("@orama/orama");
|
|
51
53
|
let node_url = require("node:url");
|
|
52
54
|
let _apidevtools_swagger_parser = require("@apidevtools/swagger-parser");
|
|
53
55
|
_apidevtools_swagger_parser = __toESM(_apidevtools_swagger_parser, 1);
|
|
@@ -410,10 +412,10 @@ function frontmatterKeywords(frontmatter) {
|
|
|
410
412
|
const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
|
|
411
413
|
return keywords.length > 0 ? keywords : void 0;
|
|
412
414
|
}
|
|
413
|
-
function withAlternates(route, routes,
|
|
415
|
+
function withAlternates(route, routes, locales) {
|
|
414
416
|
const basePath = route.basePath ?? route.path;
|
|
415
417
|
const routeByLocaleAndBase = new Map(routes.filter((r) => !r.locale || r.path === `/${r.locale}` || r.path.startsWith(`/${r.locale}/`)).map((r) => [`${r.locale ?? ""}:${r.basePath ?? r.path}`, r]));
|
|
416
|
-
const alternates = Object.fromEntries(
|
|
418
|
+
const alternates = Object.fromEntries(locales.locales.flatMap((locale) => {
|
|
417
419
|
const alternate = routeByLocaleAndBase.get(`${locale.code}:${basePath}`);
|
|
418
420
|
return alternate ? [[locale.code, alternate.path]] : [];
|
|
419
421
|
}));
|
|
@@ -426,11 +428,11 @@ function withAlternates(route, routes, i18n) {
|
|
|
426
428
|
* 为默认语言的 locale-prefixed 路由生成无前缀的"裸路径别名",方便不带语言
|
|
427
429
|
* 前缀的 URL 也能访问。标记 `isBareAlias` 以便搜索索引生成时过滤重复索引。
|
|
428
430
|
*/
|
|
429
|
-
function generateBareAliases(routes,
|
|
431
|
+
function generateBareAliases(routes, locales) {
|
|
430
432
|
const bareRoutes = [];
|
|
431
433
|
const seenBare = new Set(routes.map((r) => r.path));
|
|
432
434
|
for (const route of routes) {
|
|
433
|
-
if (route.locale !==
|
|
435
|
+
if (route.locale !== locales.default) continue;
|
|
434
436
|
const bp = route.basePath ?? route.path;
|
|
435
437
|
if (bp === route.path || seenBare.has(bp)) continue;
|
|
436
438
|
seenBare.add(bp);
|
|
@@ -442,42 +444,42 @@ function generateBareAliases(routes, i18n) {
|
|
|
442
444
|
}
|
|
443
445
|
return bareRoutes;
|
|
444
446
|
}
|
|
445
|
-
async function findLocalizedContentRoutes(contentRoot,
|
|
446
|
-
if (!
|
|
447
|
+
async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
|
|
448
|
+
if (!locales) return findContentRoutes(contentRoot, contentRoot, options);
|
|
447
449
|
const localizedRoutes = [];
|
|
448
|
-
for (const locale of
|
|
450
|
+
for (const locale of locales.locales) {
|
|
449
451
|
const localeRoot = (0, node_path.join)(contentRoot, locale.code);
|
|
450
452
|
const discovered = await findContentRoutes(localeRoot, localeRoot, options);
|
|
451
453
|
for (const route of discovered) {
|
|
452
454
|
const basePath = route.basePath ?? route.path;
|
|
453
455
|
localizedRoutes.push({
|
|
454
456
|
...route,
|
|
455
|
-
path: localizedRoutePath(basePath, locale.code,
|
|
457
|
+
path: localizedRoutePath(basePath, locale.code, locales),
|
|
456
458
|
basePath,
|
|
457
459
|
locale: locale.code,
|
|
458
460
|
module: { virtualModuleId: virtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath) }
|
|
459
461
|
});
|
|
460
462
|
}
|
|
461
463
|
}
|
|
462
|
-
if (
|
|
464
|
+
if (locales.missing === "fallback") {
|
|
463
465
|
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
464
|
-
const defaultRoutes = localizedRoutes.filter((route) => route.locale ===
|
|
466
|
+
const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
|
|
465
467
|
for (const sourceRoute of defaultRoutes) {
|
|
466
468
|
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
467
|
-
for (const locale of
|
|
469
|
+
for (const locale of locales.locales) {
|
|
468
470
|
const key = `${locale.code}:${basePath}`;
|
|
469
471
|
if (routeByLocaleAndBase.has(key)) continue;
|
|
470
472
|
localizedRoutes.push({
|
|
471
473
|
...sourceRoute,
|
|
472
|
-
path: localizedRoutePath(basePath, locale.code,
|
|
474
|
+
path: localizedRoutePath(basePath, locale.code, locales),
|
|
473
475
|
locale: locale.code,
|
|
474
476
|
isFallback: true
|
|
475
477
|
});
|
|
476
478
|
}
|
|
477
479
|
}
|
|
478
480
|
}
|
|
479
|
-
const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes,
|
|
480
|
-
return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates,
|
|
481
|
+
const routesWithAlternates = localizedRoutes.map((route) => withAlternates(route, localizedRoutes, locales));
|
|
482
|
+
return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
|
|
481
483
|
}
|
|
482
484
|
function buildNavigation(routes) {
|
|
483
485
|
const root = [];
|
|
@@ -554,7 +556,7 @@ function collectExplicitPagePathIntents(tabs) {
|
|
|
554
556
|
}
|
|
555
557
|
return intents;
|
|
556
558
|
}
|
|
557
|
-
function applyConfiguredPageRoutePaths(routes, tabs,
|
|
559
|
+
function applyConfiguredPageRoutePaths(routes, tabs, locales) {
|
|
558
560
|
const pageIntents = collectExplicitPagePathIntents(tabs);
|
|
559
561
|
if (!pageIntents.length) return routes;
|
|
560
562
|
const additions = [];
|
|
@@ -570,15 +572,15 @@ function applyConfiguredPageRoutePaths(routes, tabs, i18n) {
|
|
|
570
572
|
existingKeys.add(key);
|
|
571
573
|
additions.push({
|
|
572
574
|
...route,
|
|
573
|
-
path: route.locale &&
|
|
575
|
+
path: route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath,
|
|
574
576
|
basePath: targetBasePath
|
|
575
577
|
});
|
|
576
578
|
}
|
|
577
579
|
}
|
|
578
580
|
const nextRoutes = [...routes, ...additions];
|
|
579
|
-
const routesWithAlternates =
|
|
580
|
-
if (!
|
|
581
|
-
return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates,
|
|
581
|
+
const routesWithAlternates = locales ? nextRoutes.map((route) => withAlternates(route, nextRoutes, locales)) : nextRoutes;
|
|
582
|
+
if (!locales) return routesWithAlternates;
|
|
583
|
+
return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
|
|
582
584
|
}
|
|
583
585
|
function buildNavigationFromPagesConfig(routes, config) {
|
|
584
586
|
if (!config || config === "FileTree") return buildNavigation(routes);
|
|
@@ -641,25 +643,25 @@ function buildNavigationFromConfig(routes, config) {
|
|
|
641
643
|
};
|
|
642
644
|
});
|
|
643
645
|
}
|
|
644
|
-
function localizeNavigationPaths(nodes, locale,
|
|
646
|
+
function localizeNavigationPaths(nodes, locale, locales) {
|
|
645
647
|
return nodes.map((node) => ({
|
|
646
648
|
...node,
|
|
647
|
-
path: localizedRoutePath(node.path, locale,
|
|
648
|
-
children: node.children ? localizeNavigationPaths(node.children, locale,
|
|
649
|
+
path: localizedRoutePath(node.path, locale, locales),
|
|
650
|
+
children: node.children ? localizeNavigationPaths(node.children, locale, locales) : void 0
|
|
649
651
|
}));
|
|
650
652
|
}
|
|
651
|
-
function buildLocalizedNavigationForLocale(routes, config, locale,
|
|
653
|
+
function buildLocalizedNavigationForLocale(routes, config, locale, locales) {
|
|
652
654
|
if (!config || config === "FileTree") return localizeNavigationPaths(buildNavigation(routes.map((route) => ({
|
|
653
655
|
...route,
|
|
654
656
|
path: route.basePath ?? route.path
|
|
655
|
-
}))), locale,
|
|
657
|
+
}))), locale, locales);
|
|
656
658
|
const routeMap = new Map(routes.map((route) => [route.basePath ?? route.path, route]));
|
|
657
659
|
const buildItem = (item) => {
|
|
658
660
|
if (typeof item !== "string" && "group" in item) {
|
|
659
661
|
const children = item.pages.map(buildItem);
|
|
660
662
|
return {
|
|
661
663
|
path: firstNavigationPath(children),
|
|
662
|
-
title: resolveLocalizedText(item.group, locale,
|
|
664
|
+
title: resolveLocalizedText(item.group, locale, locales.default) ?? "",
|
|
663
665
|
icon: item.icon,
|
|
664
666
|
children
|
|
665
667
|
};
|
|
@@ -667,10 +669,10 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
|
|
|
667
669
|
const intent = routeIntentFromPagesItem(item);
|
|
668
670
|
const basePath = intent.kind === "openapi" ? openAPIPagePathFromRef(intent.ref, intent.tagFilter, intent.path) : routePathFromRef(intent.ref, intent.path);
|
|
669
671
|
const route = routeMap.get(basePath);
|
|
670
|
-
const path = route?.path ?? localizedRoutePath(basePath, locale,
|
|
672
|
+
const path = route?.path ?? localizedRoutePath(basePath, locale, locales);
|
|
671
673
|
return {
|
|
672
|
-
path: (intent.kind === "page" && intent.redirect ? localizedRoutePath(routePathFromRef(intent.redirect), locale,
|
|
673
|
-
title: resolveLocalizedText(intent.title, locale,
|
|
674
|
+
path: (intent.kind === "page" && intent.redirect ? localizedRoutePath(routePathFromRef(intent.redirect), locale, locales) : void 0) ?? path,
|
|
675
|
+
title: resolveLocalizedText(intent.title, locale, locales.default) ?? route?.meta.title ?? kebabToTitle(basePath.split("/").pop() ?? intent.ref),
|
|
674
676
|
icon: intent.icon,
|
|
675
677
|
sections: route?.meta.sections ? navigationSections(route.meta.sections) : void 0
|
|
676
678
|
};
|
|
@@ -678,24 +680,24 @@ function buildLocalizedNavigationForLocale(routes, config, locale, i18n) {
|
|
|
678
680
|
return config.map((group) => {
|
|
679
681
|
const children = group.pages.map(buildItem);
|
|
680
682
|
return {
|
|
681
|
-
path: children[0]?.path ?? localizedRoutePath("/", locale,
|
|
682
|
-
title: resolveLocalizedText(group.group, locale,
|
|
683
|
+
path: children[0]?.path ?? localizedRoutePath("/", locale, locales),
|
|
684
|
+
title: resolveLocalizedText(group.group, locale, locales.default) ?? "",
|
|
683
685
|
icon: group.icon,
|
|
684
686
|
children
|
|
685
687
|
};
|
|
686
688
|
});
|
|
687
689
|
}
|
|
688
|
-
function buildLocalizedNavigationFromTabsConfig(routes, tabs,
|
|
689
|
-
if (!
|
|
690
|
+
function buildLocalizedNavigationFromTabsConfig(routes, tabs, localesConfig) {
|
|
691
|
+
if (!localesConfig) return void 0;
|
|
690
692
|
const locales = {};
|
|
691
|
-
for (const locale of
|
|
693
|
+
for (const locale of localesConfig.locales) {
|
|
692
694
|
const localeRoutes = routes.filter((route) => route.locale === locale.code);
|
|
693
695
|
locales[locale.code] = { tabs: tabs.map((tab) => {
|
|
694
|
-
const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code,
|
|
696
|
+
const children = buildLocalizedNavigationForLocale(localeRoutes, tab.pages, locale.code, localesConfig);
|
|
695
697
|
return {
|
|
696
698
|
type: "tab",
|
|
697
699
|
path: firstNavigationPath(children),
|
|
698
|
-
title: resolveLocalizedText(tab.tab, locale.code,
|
|
700
|
+
title: resolveLocalizedText(tab.tab, locale.code, localesConfig.default) ?? "",
|
|
699
701
|
icon: tab.icon,
|
|
700
702
|
children
|
|
701
703
|
};
|
|
@@ -762,7 +764,7 @@ function inferRouteForFile(filePath, contentRoot, defaultLocale) {
|
|
|
762
764
|
const localeSegmentIndex = fileLocale ? segments.indexOf(fileLocale) : -1;
|
|
763
765
|
const basePath = basePathFromRef(localeSegmentIndex >= 0 ? segments.slice(localeSegmentIndex + 1).join("/") : ref);
|
|
764
766
|
const path = fileLocale && defaultLocale ? localizedRoutePath(basePath, fileLocale, {
|
|
765
|
-
defaultLocale,
|
|
767
|
+
default: defaultLocale,
|
|
766
768
|
missing: "fallback",
|
|
767
769
|
locales: []
|
|
768
770
|
}) : basePath;
|
|
@@ -815,12 +817,12 @@ async function handleDevRouteRequest(req, res, routes, context) {
|
|
|
815
817
|
}
|
|
816
818
|
const file = typeof body === "object" && body !== null ? body.file : void 0;
|
|
817
819
|
if (typeof file === "string" && file.length > 0) {
|
|
818
|
-
const route = resolveRouteForFile(file, routes, context.projectConfig.
|
|
820
|
+
const route = resolveRouteForFile(file, routes, context.projectConfig.locales?.default);
|
|
819
821
|
if (route) {
|
|
820
822
|
res.end(JSON.stringify(toDevRouteEntry(route)));
|
|
821
823
|
return;
|
|
822
824
|
}
|
|
823
|
-
const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.
|
|
825
|
+
const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.locales?.default);
|
|
824
826
|
res.end(JSON.stringify(inferred));
|
|
825
827
|
return;
|
|
826
828
|
}
|
|
@@ -846,8 +848,7 @@ function resolveThemeConfig(theme = {}) {
|
|
|
846
848
|
layout: {
|
|
847
849
|
...presetTheme.layout,
|
|
848
850
|
...theme.layout
|
|
849
|
-
}
|
|
850
|
-
editor: theme.editor ?? presetTheme.editor
|
|
851
|
+
}
|
|
851
852
|
};
|
|
852
853
|
}
|
|
853
854
|
//#endregion
|
|
@@ -855,26 +856,26 @@ function resolveThemeConfig(theme = {}) {
|
|
|
855
856
|
var clarifyLogoConfigSchema = zod.z.union([zod.z.string(), zod.z.object({
|
|
856
857
|
light: zod.z.string().optional(),
|
|
857
858
|
dark: zod.z.string().optional()
|
|
858
|
-
})]);
|
|
859
|
+
}).strict()]);
|
|
859
860
|
var clarifyFaviconConfigSchema = zod.z.union([zod.z.string(), zod.z.object({
|
|
860
861
|
light: zod.z.string().optional(),
|
|
861
862
|
dark: zod.z.string().optional()
|
|
862
|
-
})]);
|
|
863
|
+
}).strict()]);
|
|
863
864
|
var clarifyLocalizedTextSchema = zod.z.union([zod.z.string(), zod.z.record(zod.z.string(), zod.z.string())]);
|
|
864
865
|
var clarifyLocaleConfigSchema = zod.z.object({
|
|
865
866
|
code: zod.z.string(),
|
|
866
867
|
label: zod.z.string(),
|
|
867
868
|
dir: zod.z.union([zod.z.literal("ltr"), zod.z.literal("rtl")]).optional()
|
|
868
|
-
});
|
|
869
|
-
var
|
|
870
|
-
|
|
869
|
+
}).strict();
|
|
870
|
+
var clarifyLocalesConfigSchema = zod.z.object({
|
|
871
|
+
default: zod.z.string().optional(),
|
|
871
872
|
missing: zod.z.union([
|
|
872
873
|
zod.z.literal("fallback"),
|
|
873
874
|
zod.z.literal("404"),
|
|
874
875
|
zod.z.literal("hide")
|
|
875
876
|
]).optional(),
|
|
876
877
|
locales: zod.z.array(clarifyLocaleConfigSchema).min(1)
|
|
877
|
-
}).superRefine((config, ctx) => {
|
|
878
|
+
}).strict().superRefine((config, ctx) => {
|
|
878
879
|
const localeCodes = /* @__PURE__ */ new Set();
|
|
879
880
|
for (const [index, locale] of config.locales.entries()) {
|
|
880
881
|
if (localeCodes.has(locale.code)) ctx.addIssue({
|
|
@@ -888,17 +889,17 @@ var clarifyI18nConfigSchema = zod.z.object({
|
|
|
888
889
|
});
|
|
889
890
|
localeCodes.add(locale.code);
|
|
890
891
|
}
|
|
891
|
-
if (config.
|
|
892
|
+
if (config.default && !localeCodes.has(config.default)) ctx.addIssue({
|
|
892
893
|
code: zod.z.ZodIssueCode.custom,
|
|
893
|
-
message: "
|
|
894
|
-
path: ["
|
|
894
|
+
message: "default must be one of locales.locales",
|
|
895
|
+
path: ["default"]
|
|
895
896
|
});
|
|
896
897
|
});
|
|
897
898
|
var clarifyNavbarLinkSchema = zod.z.object({
|
|
898
899
|
label: clarifyLocalizedTextSchema,
|
|
899
900
|
href: zod.z.string(),
|
|
900
901
|
external: zod.z.boolean().optional()
|
|
901
|
-
});
|
|
902
|
+
}).strict();
|
|
902
903
|
var clarifyBannerConfigOptionsSchema = zod.z.object({
|
|
903
904
|
content: clarifyLocalizedTextSchema,
|
|
904
905
|
dismissible: zod.z.boolean().optional(),
|
|
@@ -916,11 +917,11 @@ var clarifyVariableValueSchema = zod.z.lazy(() => zod.z.union([
|
|
|
916
917
|
zod.z.record(zod.z.string(), clarifyVariableValueSchema)
|
|
917
918
|
]));
|
|
918
919
|
var clarifyVariablesConfigSchema = zod.z.record(zod.z.string(), clarifyVariableValueSchema);
|
|
919
|
-
var
|
|
920
|
-
|
|
920
|
+
var clarifyRepositoryConfigSchema = zod.z.object({
|
|
921
|
+
url: zod.z.string().optional(),
|
|
921
922
|
branch: zod.z.string().optional(),
|
|
922
923
|
directory: zod.z.string().optional()
|
|
923
|
-
});
|
|
924
|
+
}).strict();
|
|
924
925
|
var clarifyPagesItemSchema = zod.z.lazy(() => zod.z.union([
|
|
925
926
|
zod.z.string(),
|
|
926
927
|
clarifyPagesGroupSchema,
|
|
@@ -930,32 +931,32 @@ var clarifyPagesItemSchema = zod.z.lazy(() => zod.z.union([
|
|
|
930
931
|
title: clarifyLocalizedTextSchema.optional(),
|
|
931
932
|
icon: zod.z.string().optional(),
|
|
932
933
|
redirect: zod.z.string().optional()
|
|
933
|
-
}),
|
|
934
|
+
}).strict(),
|
|
934
935
|
zod.z.object({
|
|
935
936
|
openapi: zod.z.string(),
|
|
936
937
|
path: zod.z.string().optional(),
|
|
937
938
|
icon: zod.z.string().optional(),
|
|
938
939
|
title: clarifyLocalizedTextSchema.optional(),
|
|
939
|
-
filter: zod.z.object({ tags: zod.z.array(zod.z.string()).optional() }).optional()
|
|
940
|
-
})
|
|
940
|
+
filter: zod.z.object({ tags: zod.z.array(zod.z.string()).optional() }).strict().optional()
|
|
941
|
+
}).strict()
|
|
941
942
|
]));
|
|
942
943
|
var clarifyPagesGroupSchema = zod.z.lazy(() => zod.z.object({
|
|
943
944
|
group: clarifyLocalizedTextSchema,
|
|
944
945
|
icon: zod.z.string().optional(),
|
|
945
946
|
pages: zod.z.array(clarifyPagesItemSchema)
|
|
946
|
-
}));
|
|
947
|
+
}).strict());
|
|
947
948
|
var clarifyPagesConfigSchema = zod.z.union([zod.z.array(clarifyPagesGroupSchema), zod.z.literal("FileTree")]);
|
|
948
949
|
var clarifyTabItemSchema = zod.z.object({
|
|
949
950
|
tab: clarifyLocalizedTextSchema,
|
|
950
951
|
icon: zod.z.string().optional(),
|
|
951
952
|
pages: clarifyPagesConfigSchema.optional()
|
|
952
|
-
});
|
|
953
|
+
}).strict();
|
|
953
954
|
var clarifyTabsConfigSchema = zod.z.array(clarifyTabItemSchema);
|
|
954
955
|
var clarifyThemePresetSchema = zod.z.enum(["default", "base"]);
|
|
955
956
|
var clarifyThemeModeColorValueSchema = zod.z.object({
|
|
956
957
|
light: zod.z.string().optional(),
|
|
957
958
|
dark: zod.z.string().optional()
|
|
958
|
-
});
|
|
959
|
+
}).strict();
|
|
959
960
|
var clarifyThemeColorValueSchema = zod.z.union([zod.z.string(), clarifyThemeModeColorValueSchema]);
|
|
960
961
|
var clarifyThemeColorTokensConfigSchema = zod.z.object({
|
|
961
962
|
primary: clarifyThemeColorValueSchema.optional(),
|
|
@@ -966,42 +967,65 @@ var clarifyThemeColorTokensConfigSchema = zod.z.object({
|
|
|
966
967
|
muted: clarifyThemeColorValueSchema.optional(),
|
|
967
968
|
border: clarifyThemeColorValueSchema.optional(),
|
|
968
969
|
codeBackground: clarifyThemeColorValueSchema.optional()
|
|
969
|
-
});
|
|
970
|
+
}).strict();
|
|
970
971
|
var clarifyThemeRadiusTokensConfigSchema = zod.z.object({
|
|
971
972
|
sm: zod.z.string().optional(),
|
|
972
973
|
md: zod.z.string().optional(),
|
|
973
974
|
lg: zod.z.string().optional(),
|
|
974
975
|
xl: zod.z.string().optional()
|
|
975
|
-
});
|
|
976
|
+
}).strict();
|
|
976
977
|
var clarifyThemeTokensConfigSchema = zod.z.object({
|
|
977
978
|
colors: clarifyThemeColorTokensConfigSchema.optional(),
|
|
978
979
|
radius: clarifyThemeRadiusTokensConfigSchema.optional()
|
|
979
|
-
});
|
|
980
|
-
var clarifyThemeLayoutConfigSchema = zod.z.object({ maxWidth: zod.z.string().optional() });
|
|
980
|
+
}).strict();
|
|
981
|
+
var clarifyThemeLayoutConfigSchema = zod.z.object({ maxWidth: zod.z.string().optional() }).strict();
|
|
981
982
|
var clarifyThemeConfigSchema = zod.z.object({
|
|
982
983
|
preset: clarifyThemePresetSchema.optional(),
|
|
983
984
|
tokens: clarifyThemeTokensConfigSchema.optional(),
|
|
984
|
-
layout: clarifyThemeLayoutConfigSchema.optional()
|
|
985
|
-
|
|
986
|
-
|
|
985
|
+
layout: clarifyThemeLayoutConfigSchema.optional()
|
|
986
|
+
}).strict();
|
|
987
|
+
function featureSchema(shape, defaults) {
|
|
988
|
+
const objectSchema = zod.z.object({
|
|
989
|
+
enabled: zod.z.boolean().default(true),
|
|
990
|
+
...shape
|
|
991
|
+
}).strict().default({
|
|
992
|
+
enabled: true,
|
|
993
|
+
...defaults
|
|
994
|
+
});
|
|
995
|
+
return zod.z.union([zod.z.boolean(), objectSchema]).default({
|
|
996
|
+
enabled: true,
|
|
997
|
+
...defaults
|
|
998
|
+
}).transform((value) => typeof value === "boolean" ? {
|
|
999
|
+
enabled: value,
|
|
1000
|
+
...defaults
|
|
1001
|
+
} : value);
|
|
1002
|
+
}
|
|
1003
|
+
var clarifyFeaturesConfigSchema = zod.z.object({
|
|
1004
|
+
search: featureSchema({ mcp: zod.z.boolean().default(true) }, { mcp: true }),
|
|
1005
|
+
repository: featureSchema(clarifyRepositoryConfigSchema.shape, {}),
|
|
1006
|
+
themeEditor: featureSchema({}, {}),
|
|
1007
|
+
openapi: featureSchema({ playground: zod.z.boolean().default(true) }, { playground: true })
|
|
1008
|
+
}).strict();
|
|
987
1009
|
var clarifyProjectConfigSchema = zod.z.object({
|
|
988
1010
|
title: zod.z.string().optional(),
|
|
989
1011
|
description: zod.z.string().optional(),
|
|
990
1012
|
siteUrl: zod.z.string().optional(),
|
|
991
|
-
|
|
1013
|
+
routePrefix: zod.z.string().default("/"),
|
|
1014
|
+
assetPrefix: zod.z.string().optional(),
|
|
992
1015
|
logo: clarifyLogoConfigSchema.optional(),
|
|
993
1016
|
homeUrl: zod.z.string().optional(),
|
|
994
1017
|
favicon: clarifyFaviconConfigSchema.optional(),
|
|
995
1018
|
theme: clarifyThemeConfigSchema.optional(),
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1019
|
+
navigation: zod.z.object({
|
|
1020
|
+
links: zod.z.array(clarifyNavbarLinkSchema).optional(),
|
|
1021
|
+
tabs: clarifyTabsConfigSchema.optional()
|
|
1022
|
+
}).strict().optional(),
|
|
999
1023
|
banner: clarifyBannerConfigOptionsSchema.optional(),
|
|
1000
1024
|
footer: clarifyFooterConfigSchema.optional(),
|
|
1025
|
+
locales: clarifyLocalesConfigSchema.optional(),
|
|
1001
1026
|
variables: clarifyVariablesConfigSchema.optional(),
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
});
|
|
1027
|
+
features: clarifyFeaturesConfigSchema.optional()
|
|
1028
|
+
}).strict();
|
|
1005
1029
|
//#endregion
|
|
1006
1030
|
//#region source/core/config/config.ts
|
|
1007
1031
|
function formatIssuePath(path) {
|
|
@@ -1023,17 +1047,20 @@ function validateProjectConfig(value) {
|
|
|
1023
1047
|
if (!result.success) throw new Error(formatProjectConfigError(result.error));
|
|
1024
1048
|
return result.data;
|
|
1025
1049
|
}
|
|
1026
|
-
function
|
|
1027
|
-
if (!
|
|
1028
|
-
const firstLocale =
|
|
1029
|
-
const defaultLocale =
|
|
1050
|
+
function resolveLocalesConfig(locales) {
|
|
1051
|
+
if (!locales) return void 0;
|
|
1052
|
+
const firstLocale = locales.locales[0]?.code;
|
|
1053
|
+
const defaultLocale = locales.default ?? firstLocale;
|
|
1030
1054
|
if (!defaultLocale) return void 0;
|
|
1031
1055
|
return {
|
|
1032
|
-
defaultLocale,
|
|
1033
|
-
missing:
|
|
1034
|
-
locales:
|
|
1056
|
+
default: defaultLocale,
|
|
1057
|
+
missing: locales.missing ?? "fallback",
|
|
1058
|
+
locales: locales.locales
|
|
1035
1059
|
};
|
|
1036
1060
|
}
|
|
1061
|
+
function resolveFeaturesConfig(features = {}) {
|
|
1062
|
+
return clarifyFeaturesConfigSchema.parse(features);
|
|
1063
|
+
}
|
|
1037
1064
|
function resolveRoutePrefix(routePrefix) {
|
|
1038
1065
|
const trimmed = routePrefix?.trim();
|
|
1039
1066
|
if (!trimmed || trimmed === "/") return "/";
|
|
@@ -1054,26 +1081,24 @@ function resolveProjectConfig(config = {}) {
|
|
|
1054
1081
|
title: config.title ?? "Clarify Docs",
|
|
1055
1082
|
description: config.description ?? "",
|
|
1056
1083
|
siteUrl: config.siteUrl,
|
|
1057
|
-
|
|
1084
|
+
routePrefix,
|
|
1085
|
+
assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
|
|
1058
1086
|
logo: config.logo,
|
|
1059
1087
|
homeUrl: config.homeUrl,
|
|
1060
1088
|
favicon: config.favicon,
|
|
1061
|
-
routePrefix,
|
|
1062
|
-
assetPrefix: resolveAssetPrefix(config.assetPrefix, routePrefix),
|
|
1063
1089
|
theme: resolveThemeConfig(config.theme),
|
|
1064
|
-
|
|
1090
|
+
navigation: config.navigation,
|
|
1065
1091
|
banner: config.banner,
|
|
1066
1092
|
footer: config.footer,
|
|
1093
|
+
locales: resolveLocalesConfig(config.locales),
|
|
1067
1094
|
variables: config.variables ?? {},
|
|
1068
|
-
|
|
1069
|
-
tabs: config.tabs
|
|
1095
|
+
features: resolveFeaturesConfig(config.features)
|
|
1070
1096
|
};
|
|
1071
1097
|
}
|
|
1072
1098
|
//#endregion
|
|
1073
1099
|
//#region source/core/config/user-config.ts
|
|
1074
1100
|
function defineConfig(config) {
|
|
1075
|
-
|
|
1076
|
-
return config;
|
|
1101
|
+
return validateClarifyConfig(config);
|
|
1077
1102
|
}
|
|
1078
1103
|
/**
|
|
1079
1104
|
* Config filenames Clarify CLI looks for at the project root, in resolution
|
|
@@ -1097,18 +1122,23 @@ function assertConfigObject(config, configFile) {
|
|
|
1097
1122
|
}
|
|
1098
1123
|
function loadJsonConfig(configFile) {
|
|
1099
1124
|
const config = assertConfigObject(JSON.parse((0, node_fs.readFileSync)(configFile, "utf-8")), configFile);
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1125
|
+
if ("plugins" in config) throw new Error("[clarify] clarify.json does not support plugins; use clarify.ts or clarify.js");
|
|
1126
|
+
return validateClarifyConfig(config);
|
|
1127
|
+
}
|
|
1128
|
+
function validateClarifyConfig(config) {
|
|
1129
|
+
const { plugins, ...projectConfigInput } = config;
|
|
1130
|
+
if (plugins !== void 0 && !Array.isArray(plugins)) throw new Error("[clarify] config field \"plugins\" is invalid: Expected an array");
|
|
1131
|
+
const projectConfig = validateProjectConfig(projectConfigInput);
|
|
1132
|
+
return plugins ? {
|
|
1103
1133
|
...projectConfig,
|
|
1104
|
-
|
|
1105
|
-
};
|
|
1134
|
+
plugins
|
|
1135
|
+
} : projectConfig;
|
|
1106
1136
|
}
|
|
1107
1137
|
async function loadClarifyConfig(root, env) {
|
|
1108
1138
|
const configFile = findClarifyConfigFile(root);
|
|
1109
1139
|
if (!configFile) return {};
|
|
1110
1140
|
if (configFile.endsWith(".json")) return loadJsonConfig(configFile);
|
|
1111
|
-
return assertConfigObject((await (0, vite.loadConfigFromFile)(env, configFile, root, "silent"))?.config, configFile);
|
|
1141
|
+
return validateClarifyConfig(assertConfigObject((await (0, vite.loadConfigFromFile)(env, configFile, root, "silent"))?.config, configFile));
|
|
1112
1142
|
}
|
|
1113
1143
|
/**
|
|
1114
1144
|
* File extensions Clarify treats as content files. Kept in sync with the
|
|
@@ -1134,9 +1164,9 @@ function buildProjectInfo(context) {
|
|
|
1134
1164
|
contentRoot: (contentRoot.startsWith(projectRoot) ? contentRoot.slice(projectRoot.length).replace(/^[/\\]+/, "") : contentRoot) || "source",
|
|
1135
1165
|
projectRoot
|
|
1136
1166
|
};
|
|
1137
|
-
if (projectConfig.
|
|
1138
|
-
|
|
1139
|
-
locales: projectConfig.
|
|
1167
|
+
if (projectConfig.locales) response.locales = {
|
|
1168
|
+
default: projectConfig.locales.default,
|
|
1169
|
+
locales: projectConfig.locales.locales.map((l) => l.code)
|
|
1140
1170
|
};
|
|
1141
1171
|
return response;
|
|
1142
1172
|
}
|
|
@@ -1196,10 +1226,23 @@ function resolveVirtualModuleId(id, modules, routes) {
|
|
|
1196
1226
|
if (route) return resolveVirtualId(route.module.virtualModuleId);
|
|
1197
1227
|
return null;
|
|
1198
1228
|
}
|
|
1199
|
-
function generateConfigModule(projectConfig,
|
|
1229
|
+
function generateConfigModule(projectConfig, version) {
|
|
1200
1230
|
const runtimeConfig = {
|
|
1201
|
-
|
|
1202
|
-
|
|
1231
|
+
title: projectConfig.title,
|
|
1232
|
+
description: projectConfig.description,
|
|
1233
|
+
siteUrl: projectConfig.siteUrl,
|
|
1234
|
+
routePrefix: projectConfig.routePrefix,
|
|
1235
|
+
assetPrefix: projectConfig.assetPrefix,
|
|
1236
|
+
logo: projectConfig.logo,
|
|
1237
|
+
homeUrl: projectConfig.homeUrl,
|
|
1238
|
+
favicon: projectConfig.favicon,
|
|
1239
|
+
theme: projectConfig.theme,
|
|
1240
|
+
navigation: projectConfig.navigation,
|
|
1241
|
+
banner: projectConfig.banner,
|
|
1242
|
+
footer: projectConfig.footer,
|
|
1243
|
+
locales: projectConfig.locales,
|
|
1244
|
+
variables: projectConfig.variables,
|
|
1245
|
+
features: projectConfig.features
|
|
1203
1246
|
};
|
|
1204
1247
|
if (version) runtimeConfig.version = version;
|
|
1205
1248
|
return `export const config = ${JSON.stringify(runtimeConfig)};`;
|
|
@@ -1303,7 +1346,7 @@ function buildVirtualModules(args) {
|
|
|
1303
1346
|
const modules = /* @__PURE__ */ new Map();
|
|
1304
1347
|
const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
|
|
1305
1348
|
const allPlugins = [...args.plugins ?? []];
|
|
1306
|
-
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.
|
|
1349
|
+
modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.version));
|
|
1307
1350
|
modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, "client"));
|
|
1308
1351
|
modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, "server"));
|
|
1309
1352
|
modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
|
|
@@ -1528,7 +1571,7 @@ function createViteAdapter(engine) {
|
|
|
1528
1571
|
const compilePolicyPlugin = {
|
|
1529
1572
|
name: "clarify:content-compile-policy",
|
|
1530
1573
|
configResolved(config) {
|
|
1531
|
-
contentCompileErrorPolicy = config.command === "build"
|
|
1574
|
+
contentCompileErrorPolicy = config.command === "build" ? "throw" : "diagnostic";
|
|
1532
1575
|
}
|
|
1533
1576
|
};
|
|
1534
1577
|
const tailwind = (0, _tailwindcss_vite.default)();
|
|
@@ -1544,7 +1587,7 @@ function createViteAdapter(engine) {
|
|
|
1544
1587
|
}
|
|
1545
1588
|
var cliPackageVersion = {
|
|
1546
1589
|
name: "@clarify-labs/cli",
|
|
1547
|
-
version: "0.
|
|
1590
|
+
version: "0.11.1",
|
|
1548
1591
|
license: "AGPL-3.0-only",
|
|
1549
1592
|
type: "module",
|
|
1550
1593
|
repository: {
|
|
@@ -1585,6 +1628,9 @@ var cliPackageVersion = {
|
|
|
1585
1628
|
"@clarify-labs/templates": "workspace:*",
|
|
1586
1629
|
"@mdx-js/mdx": "^3.1.1",
|
|
1587
1630
|
"@mdx-js/rollup": "^3.1.1",
|
|
1631
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
1632
|
+
"@msgpack/msgpack": "^3.1.3",
|
|
1633
|
+
"@orama/orama": "^3.1.18",
|
|
1588
1634
|
"@shikijs/rehype": "^4.3.0",
|
|
1589
1635
|
"@tailwindcss/vite": "^4.3.2",
|
|
1590
1636
|
"@vitejs/plugin-react": "^6.0.3",
|
|
@@ -1603,7 +1649,6 @@ var cliPackageVersion = {
|
|
|
1603
1649
|
"rehype-raw": "^7.0.0",
|
|
1604
1650
|
"rehype-slug": "^6.0.0",
|
|
1605
1651
|
"remark": "^15.0.1",
|
|
1606
|
-
"remark-gfm": "^4.0.1",
|
|
1607
1652
|
"shiki": "4.3.0",
|
|
1608
1653
|
"unist-util-visit": "^5.1.0",
|
|
1609
1654
|
"vite": "^8.1.4",
|
|
@@ -1624,8 +1669,7 @@ function resolveBuildOptions(options = {}) {
|
|
|
1624
1669
|
return {
|
|
1625
1670
|
projectRoot: options.projectRoot ?? process.cwd(),
|
|
1626
1671
|
rootDirectory: options.rootDirectory ?? "source",
|
|
1627
|
-
outputDirectory: options.outputDirectory
|
|
1628
|
-
ssg: { failOnError: options.ssg?.failOnError ?? true }
|
|
1672
|
+
outputDirectory: options.outputDirectory
|
|
1629
1673
|
};
|
|
1630
1674
|
}
|
|
1631
1675
|
//#endregion
|
|
@@ -1683,7 +1727,7 @@ function llmsTxtListItem(route, basePath) {
|
|
|
1683
1727
|
return description ? `- [${route.meta.title}](${basePath}${contentArtifactUrl}): ${description}` : `- [${route.meta.title}](${basePath}${contentArtifactUrl})`;
|
|
1684
1728
|
}
|
|
1685
1729
|
function llmsTxtLocaleLabel(locale, projectConfig) {
|
|
1686
|
-
return projectConfig.
|
|
1730
|
+
return projectConfig.locales?.locales.find((item) => item.code === locale)?.label ?? locale;
|
|
1687
1731
|
}
|
|
1688
1732
|
function groupRoutesByLocale(routes) {
|
|
1689
1733
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -1695,7 +1739,7 @@ function groupRoutesByLocale(routes) {
|
|
|
1695
1739
|
}
|
|
1696
1740
|
function groupLlmsTxtRoutesByLocale(routes, projectConfig) {
|
|
1697
1741
|
const routesBySource = /* @__PURE__ */ new Map();
|
|
1698
|
-
const defaultLocale = projectConfig.
|
|
1742
|
+
const defaultLocale = projectConfig.locales?.default;
|
|
1699
1743
|
for (const route of routes) {
|
|
1700
1744
|
const sourceKey = `${route.locale ?? defaultLocale ?? "default"}:${route.basePath ?? route.artifacts?.contentArtifactUrl ?? route.path}`;
|
|
1701
1745
|
const previousRoute = routesBySource.get(sourceKey);
|
|
@@ -1885,6 +1929,347 @@ function createHtmlShellPlugin() {
|
|
|
1885
1929
|
};
|
|
1886
1930
|
}
|
|
1887
1931
|
//#endregion
|
|
1932
|
+
//#region source/plugins/mcp-search/mcp-config.ts
|
|
1933
|
+
/**
|
|
1934
|
+
* Normalize a route prefix into a root-relative path segment.
|
|
1935
|
+
*
|
|
1936
|
+
* `'/'` -> `''`, `'/docs'` -> `'/docs'`, `'docs/'` -> `'/docs'`.
|
|
1937
|
+
*/
|
|
1938
|
+
function normalizePrefix(routePrefix) {
|
|
1939
|
+
if (!routePrefix || routePrefix === "/") return "";
|
|
1940
|
+
return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Assemble the MCP site config from build-time metadata.
|
|
1944
|
+
*
|
|
1945
|
+
* Unlike the previous pagefind-based descriptor, this no longer reads an
|
|
1946
|
+
* entry JSON file - the Orama index is a single binary blob, so the caller
|
|
1947
|
+
* supplies the document count and locale list directly from the build step
|
|
1948
|
+
* that produced the index.
|
|
1949
|
+
*/
|
|
1950
|
+
function createMcpSiteConfig(projectConfig, options) {
|
|
1951
|
+
const prefix = normalizePrefix(projectConfig.routePrefix);
|
|
1952
|
+
return {
|
|
1953
|
+
version: 3,
|
|
1954
|
+
site: {
|
|
1955
|
+
title: projectConfig.title,
|
|
1956
|
+
description: projectConfig.description,
|
|
1957
|
+
url: projectConfig.siteUrl,
|
|
1958
|
+
routePrefix: projectConfig.routePrefix ?? "/"
|
|
1959
|
+
},
|
|
1960
|
+
capabilities: { search: {
|
|
1961
|
+
type: "search",
|
|
1962
|
+
indexPath: `${prefix}/mcp-search.msp`,
|
|
1963
|
+
defaultLocale: projectConfig.locales?.default,
|
|
1964
|
+
documentCount: options.documentCount,
|
|
1965
|
+
locales: options.locales
|
|
1966
|
+
} }
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
//#endregion
|
|
1970
|
+
//#region source/plugins/mcp-search/orama-index.ts
|
|
1971
|
+
/**
|
|
1972
|
+
* Orama schema for the MCP search index.
|
|
1973
|
+
*
|
|
1974
|
+
* - `path`, `title`, `description`, `content` are full-text searchable strings.
|
|
1975
|
+
* - `locale` is an `enum` so we can use `where: { locale: { eq } }` filters at
|
|
1976
|
+
* query time. (`string` fields cannot be filtered in Orama.)
|
|
1977
|
+
* - `keywords` is a `string[]` also indexed for full-text matching.
|
|
1978
|
+
*/
|
|
1979
|
+
var mcpSearchSchema = {
|
|
1980
|
+
path: "string",
|
|
1981
|
+
title: "string",
|
|
1982
|
+
description: "string",
|
|
1983
|
+
locale: "enum",
|
|
1984
|
+
content: "string",
|
|
1985
|
+
keywords: "string[]"
|
|
1986
|
+
};
|
|
1987
|
+
/**
|
|
1988
|
+
* Intl.Segmenter cache keyed by BCP 47 tag. Segmenter construction is not
|
|
1989
|
+
* cheap, and the MCP server issues many queries against the same locale, so
|
|
1990
|
+
* we reuse instances. The cache is module-scoped: build and runtime share it
|
|
1991
|
+
* in the same process, and separate processes get their own - which is fine.
|
|
1992
|
+
*/
|
|
1993
|
+
var segmenterCache = /* @__PURE__ */ new Map();
|
|
1994
|
+
function getSegmenter(locale) {
|
|
1995
|
+
let seg = segmenterCache.get(locale);
|
|
1996
|
+
if (!seg) {
|
|
1997
|
+
seg = new Intl.Segmenter(locale, { granularity: "word" });
|
|
1998
|
+
segmenterCache.set(locale, seg);
|
|
1999
|
+
}
|
|
2000
|
+
return seg;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Map a Clarify locale code (e.g. `zh-CN`, `en-US`, `ja-JP`) to a BCP 47 tag
|
|
2004
|
+
* suitable for `Intl.Segmenter`. Returns `null` for locales we intentionally
|
|
2005
|
+
* want to tokenize with the Latin/whitespace fallback (faster, and avoids
|
|
2006
|
+
* forcing CJK-style word segmentation on Latin scripts).
|
|
2007
|
+
*/
|
|
2008
|
+
function segmenterLocale(locale) {
|
|
2009
|
+
if (!locale) return null;
|
|
2010
|
+
const lower = locale.toLowerCase();
|
|
2011
|
+
if (lower.startsWith("zh")) return "zh-CN";
|
|
2012
|
+
if (lower.startsWith("ja")) return "ja-JP";
|
|
2013
|
+
if (lower.startsWith("ko")) return "ko-KR";
|
|
2014
|
+
return null;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Tokenize text for Orama indexing and querying.
|
|
2018
|
+
*
|
|
2019
|
+
* CJK locales use `Intl.Segmenter` (word granularity), which splits
|
|
2020
|
+
* spaceless scripts into words and individual characters. Latin/whitespace
|
|
2021
|
+
* locales fall back to a Unicode-aware split on spaces and punctuation.
|
|
2022
|
+
*
|
|
2023
|
+
* All tokens are lowercased so queries match regardless of case.
|
|
2024
|
+
*/
|
|
2025
|
+
function tokenizeForSearch(text, locale) {
|
|
2026
|
+
if (!text) return [];
|
|
2027
|
+
const segLocale = segmenterLocale(locale);
|
|
2028
|
+
if (segLocale) {
|
|
2029
|
+
const seg = getSegmenter(segLocale);
|
|
2030
|
+
return Array.from(seg.segment(text)).filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
|
|
2031
|
+
}
|
|
2032
|
+
return text.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Build an Orama tokenizer component bound to a specific default locale.
|
|
2036
|
+
*
|
|
2037
|
+
* The tokenizer receives the raw field value and the per-field language hint
|
|
2038
|
+
* from Orama (which we don't set per-field, so it falls back to the default
|
|
2039
|
+
* language). We resolve the segmenter locale from the default locale at
|
|
2040
|
+
* construction time so the same tokenizer handles all documents in a
|
|
2041
|
+
* predominantly single-locale index.
|
|
2042
|
+
*
|
|
2043
|
+
* For multi-locale indexes, the tokenizer is also used at query time. Since
|
|
2044
|
+
* the query locale is not known to Orama's tokenizer callback, we use the
|
|
2045
|
+
* index's default locale - this is acceptable because:
|
|
2046
|
+
* - CJK queries get CJK segmentation (correct)
|
|
2047
|
+
* - Latin queries against a CJK-default index still work because Latin
|
|
2048
|
+
* text has no word boundaries for CJK segmenters to mis-split; the
|
|
2049
|
+
* segmenter passes through Latin words as-is.
|
|
2050
|
+
*/
|
|
2051
|
+
function createMcpTokenizer(defaultLocale) {
|
|
2052
|
+
return {
|
|
2053
|
+
language: defaultLocale ?? "en",
|
|
2054
|
+
normalizationCache: /* @__PURE__ */ new Map(),
|
|
2055
|
+
tokenize(raw, language) {
|
|
2056
|
+
return tokenizeForSearch(typeof raw === "string" ? raw : String(raw ?? ""), language ?? defaultLocale);
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Compose the searchable text for a route. Shared between the Pagefind
|
|
2062
|
+
* (browser) and Orama (MCP) indexing paths so both backends see the same
|
|
2063
|
+
* field order and content.
|
|
2064
|
+
*/
|
|
2065
|
+
function routeSearchContent(route) {
|
|
2066
|
+
return [
|
|
2067
|
+
route.meta.title,
|
|
2068
|
+
route.meta.description,
|
|
2069
|
+
route.meta.keywords?.join(" "),
|
|
2070
|
+
route.meta.sections?.map((section) => section.title).join(" "),
|
|
2071
|
+
route.source.content
|
|
2072
|
+
].filter(Boolean).join("\n\n");
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Convert a `ContentRoute` into the document shape stored in the Orama index.
|
|
2076
|
+
*/
|
|
2077
|
+
function routeToSearchDocument(route, defaultLocale) {
|
|
2078
|
+
if (route.isBareAlias) return null;
|
|
2079
|
+
const content = routeSearchContent(route);
|
|
2080
|
+
if (!content.trim()) return null;
|
|
2081
|
+
return {
|
|
2082
|
+
path: route.path,
|
|
2083
|
+
title: route.meta.title ?? "",
|
|
2084
|
+
description: route.meta.description ?? "",
|
|
2085
|
+
locale: route.locale ?? defaultLocale ?? "en",
|
|
2086
|
+
content,
|
|
2087
|
+
keywords: route.meta.keywords ?? []
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Create an Orama index pre-configured with the MCP schema and CJK-aware
|
|
2092
|
+
* tokenizer. Both the build step and the runtime loader use this so the
|
|
2093
|
+
* tokenizer is guaranteed to match between indexing and querying.
|
|
2094
|
+
*
|
|
2095
|
+
* Annotated as `AnyOrama` (Orama's exported base type) rather than inferred
|
|
2096
|
+
* via `ReturnType<typeof create>` because the inference would pull in
|
|
2097
|
+
* Orama's internal component types (`DocumentsStore`, `Index`, `Sorter`)
|
|
2098
|
+
* which are not exported and break `.d.ts` generation (TS2883).
|
|
2099
|
+
*/
|
|
2100
|
+
function createMcpSearchIndex(defaultLocale) {
|
|
2101
|
+
return (0, _orama_orama.create)({
|
|
2102
|
+
schema: mcpSearchSchema,
|
|
2103
|
+
components: { tokenizer: createMcpTokenizer(defaultLocale) }
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Insert search documents into an Orama index.
|
|
2108
|
+
*/
|
|
2109
|
+
function indexSearchDocuments(db, documents) {
|
|
2110
|
+
if (documents.length === 0) return;
|
|
2111
|
+
(0, _orama_orama.insertMultiple)(db, documents);
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Build a ready-to-search Orama index from content routes.
|
|
2115
|
+
*
|
|
2116
|
+
* Filters out bare aliases and empty routes. Returns the populated db.
|
|
2117
|
+
*/
|
|
2118
|
+
function buildSearchIndex(routes, defaultLocale) {
|
|
2119
|
+
const db = createMcpSearchIndex(defaultLocale);
|
|
2120
|
+
const documents = [];
|
|
2121
|
+
for (const route of routes) {
|
|
2122
|
+
const doc = routeToSearchDocument(route, defaultLocale);
|
|
2123
|
+
if (doc) documents.push(doc);
|
|
2124
|
+
}
|
|
2125
|
+
indexSearchDocuments(db, documents);
|
|
2126
|
+
return {
|
|
2127
|
+
db,
|
|
2128
|
+
documentCount: documents.length
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Collect the distinct locale codes that will be present in the Orama index,
|
|
2133
|
+
* in stable insertion order. Bare aliases are skipped (they duplicate the
|
|
2134
|
+
* default-locale route). Locales with no code fall back to the project
|
|
2135
|
+
* default so single-locale sites still surface one locale.
|
|
2136
|
+
*/
|
|
2137
|
+
function collectIndexedLocales(routes, defaultLocale) {
|
|
2138
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2139
|
+
const locales = [];
|
|
2140
|
+
for (const route of routes) {
|
|
2141
|
+
if (route.isBareAlias) continue;
|
|
2142
|
+
const code = route.locale ?? defaultLocale ?? "en";
|
|
2143
|
+
if (!seen.has(code)) {
|
|
2144
|
+
seen.add(code);
|
|
2145
|
+
locales.push(code);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
return locales;
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Search an Orama MCP index.
|
|
2152
|
+
*
|
|
2153
|
+
* Applies an optional locale filter (via the `enum` field) and returns a
|
|
2154
|
+
* compact, LLM-friendly hit shape. Excerpts are derived from the matched
|
|
2155
|
+
* content: Orama does not return highlighted excerpts, so we synthesize a
|
|
2156
|
+
* short window around the first query term occurrence.
|
|
2157
|
+
*/
|
|
2158
|
+
function searchMcpIndex(db, params) {
|
|
2159
|
+
const limit = Math.max(1, Math.min(50, params.limit ?? 10));
|
|
2160
|
+
const result = (0, _orama_orama.search)(db, {
|
|
2161
|
+
term: params.query,
|
|
2162
|
+
limit,
|
|
2163
|
+
where: params.locale ? { locale: { eq: params.locale } } : void 0
|
|
2164
|
+
});
|
|
2165
|
+
return {
|
|
2166
|
+
hits: result.hits.map((hit) => {
|
|
2167
|
+
const doc = hit.document;
|
|
2168
|
+
return {
|
|
2169
|
+
path: doc.path,
|
|
2170
|
+
title: doc.title || guessTitleFromPath(doc.path),
|
|
2171
|
+
description: doc.description,
|
|
2172
|
+
locale: doc.locale,
|
|
2173
|
+
score: hit.score,
|
|
2174
|
+
excerpt: makeExcerpt(doc.content, params.query),
|
|
2175
|
+
keywords: doc.keywords
|
|
2176
|
+
};
|
|
2177
|
+
}),
|
|
2178
|
+
count: result.count
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Build a short excerpt around the first occurrence of any query term in the
|
|
2183
|
+
* content. Falls back to the beginning of the content if no term is found.
|
|
2184
|
+
*/
|
|
2185
|
+
function makeExcerpt(content, query, maxLen = 240) {
|
|
2186
|
+
if (!content) return "";
|
|
2187
|
+
const terms = query.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
|
|
2188
|
+
const lower = content.toLowerCase();
|
|
2189
|
+
let pos = -1;
|
|
2190
|
+
for (const term of terms) {
|
|
2191
|
+
const i = lower.indexOf(term);
|
|
2192
|
+
if (i >= 0 && (pos < 0 || i < pos)) pos = i;
|
|
2193
|
+
}
|
|
2194
|
+
if (pos < 0) pos = 0;
|
|
2195
|
+
const half = Math.floor(maxLen / 2);
|
|
2196
|
+
const start = Math.max(0, pos - half);
|
|
2197
|
+
const end = Math.min(content.length, start + maxLen);
|
|
2198
|
+
const prefix = start > 0 ? "…" : "";
|
|
2199
|
+
const suffix = end < content.length ? "…" : "";
|
|
2200
|
+
return `${prefix}${content.slice(start, end).trim()}${suffix}`;
|
|
2201
|
+
}
|
|
2202
|
+
function guessTitleFromPath(path) {
|
|
2203
|
+
if (!path) return "(untitled)";
|
|
2204
|
+
const segments = path.split("/").filter(Boolean);
|
|
2205
|
+
if (segments.length === 0) return "Home";
|
|
2206
|
+
return segments[segments.length - 1].replace(/\.(md|mdx|html)$/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* Serialize an Orama index to a msgpack binary buffer.
|
|
2210
|
+
*
|
|
2211
|
+
* Uses `save()` to extract the raw index data, then msgpack-encodes it for a
|
|
2212
|
+
* compact binary representation. The same schema + tokenizer must be supplied
|
|
2213
|
+
* when loading so query-time tokenization matches indexing-time tokenization.
|
|
2214
|
+
*/
|
|
2215
|
+
function serializeSearchIndex(db) {
|
|
2216
|
+
return (0, _msgpack_msgpack.encode)((0, _orama_orama.save)(db));
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Deserialize a msgpack binary buffer into a ready-to-search Orama index.
|
|
2220
|
+
*
|
|
2221
|
+
* The caller must supply the same `defaultLocale` used when the index was
|
|
2222
|
+
* built, so the tokenizer matches. A mismatch would not corrupt data but
|
|
2223
|
+
* would cause CJK query terms to be tokenized incorrectly (e.g. "文档" left
|
|
2224
|
+
* as a single token instead of split into "文" + "档").
|
|
2225
|
+
*/
|
|
2226
|
+
function deserializeSearchIndex(buffer, defaultLocale) {
|
|
2227
|
+
const raw = (0, _msgpack_msgpack.decode)(buffer);
|
|
2228
|
+
const db = createMcpSearchIndex(defaultLocale);
|
|
2229
|
+
(0, _orama_orama.load)(db, raw);
|
|
2230
|
+
return db;
|
|
2231
|
+
}
|
|
2232
|
+
//#endregion
|
|
2233
|
+
//#region source/plugins/mcp-search/index.ts
|
|
2234
|
+
/**
|
|
2235
|
+
* MCP search index plugin.
|
|
2236
|
+
*
|
|
2237
|
+
* Generates the Orama binary index (`mcp-search.msp`) and the MCP site
|
|
2238
|
+
* descriptor (`mcp.json`) as build assets consumed by the `clarify mcp`
|
|
2239
|
+
* command to serve documentation search to MCP-aware clients (e.g. AI coding
|
|
2240
|
+
* assistants).
|
|
2241
|
+
*
|
|
2242
|
+
* This plugin is independent of `clarify:page-search` (Pagefind), which
|
|
2243
|
+
* powers in-browser search. The MCP server runs in Node and needs a
|
|
2244
|
+
* self-contained, compact index with CJK-aware tokenization that doesn't
|
|
2245
|
+
* depend on the browser-only Pagefind runtime.
|
|
2246
|
+
*/
|
|
2247
|
+
function createMcpSearchPlugin() {
|
|
2248
|
+
return {
|
|
2249
|
+
name: "clarify:mcp-search",
|
|
2250
|
+
hooks: { "build:assets"(ctx) {
|
|
2251
|
+
const searchConfig = ctx.projectConfig.features.search;
|
|
2252
|
+
if (!searchConfig.enabled || !searchConfig.mcp) return [];
|
|
2253
|
+
const defaultLocale = ctx.projectConfig.locales?.default;
|
|
2254
|
+
const { db, documentCount } = buildSearchIndex(ctx.routes, defaultLocale);
|
|
2255
|
+
const locales = collectIndexedLocales(ctx.routes, defaultLocale);
|
|
2256
|
+
const mcpConfig = createMcpSiteConfig(ctx.projectConfig, {
|
|
2257
|
+
documentCount,
|
|
2258
|
+
locales
|
|
2259
|
+
});
|
|
2260
|
+
const assets = [{
|
|
2261
|
+
fileName: `${mcpConfig.capabilities.search?.indexPath.replace(/\/mcp-search\.msp$/, "") ?? ""}/mcp-search.msp`.replace(/^\//, ""),
|
|
2262
|
+
source: serializeSearchIndex(db)
|
|
2263
|
+
}, {
|
|
2264
|
+
fileName: "mcp.json",
|
|
2265
|
+
source: `${JSON.stringify(mcpConfig, null, 2)}\n`
|
|
2266
|
+
}];
|
|
2267
|
+
console.log(`[clarify] MCP search index (${documentCount} docs, ${locales.length} locales) written to mcp-search.msp.`);
|
|
2268
|
+
return assets;
|
|
2269
|
+
} }
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
//#endregion
|
|
1888
2273
|
//#region source/plugins/openapi/parser.ts
|
|
1889
2274
|
var OPENAPI_HTTP_METHODS = [
|
|
1890
2275
|
"get",
|
|
@@ -2090,7 +2475,7 @@ function collectOpenAPIPageIntents(config) {
|
|
|
2090
2475
|
if (!pages || pages === "FileTree") return;
|
|
2091
2476
|
for (const group of pages) visitItems(group.pages);
|
|
2092
2477
|
}
|
|
2093
|
-
for (const tab of config.tabs ?? []) visitPages(tab.pages);
|
|
2478
|
+
for (const tab of config.navigation?.tabs ?? []) visitPages(tab.pages);
|
|
2094
2479
|
return intents;
|
|
2095
2480
|
}
|
|
2096
2481
|
function virtualModuleIdFromPath(path) {
|
|
@@ -2113,14 +2498,14 @@ function cloneOpenAPIRoute(route, overrides = {}) {
|
|
|
2113
2498
|
...overrides
|
|
2114
2499
|
};
|
|
2115
2500
|
}
|
|
2116
|
-
function findOpenAPIRoutesForContentRoot(contentRoot,
|
|
2117
|
-
if (!
|
|
2501
|
+
function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
|
|
2502
|
+
if (!locales) return findOpenAPIRoutes(contentRoot);
|
|
2118
2503
|
const localizedRoutes = [];
|
|
2119
|
-
for (const locale of
|
|
2504
|
+
for (const locale of locales.locales) {
|
|
2120
2505
|
const localeRoot = (0, node_path.join)(contentRoot, locale.code);
|
|
2121
2506
|
for (const route of findOpenAPIRoutes(localeRoot, localeRoot)) {
|
|
2122
2507
|
const basePath = route.basePath ?? route.path;
|
|
2123
|
-
const path = localizedRoutePath(basePath, locale.code,
|
|
2508
|
+
const path = localizedRoutePath(basePath, locale.code, locales);
|
|
2124
2509
|
localizedRoutes.push({
|
|
2125
2510
|
...route,
|
|
2126
2511
|
path,
|
|
@@ -2130,15 +2515,15 @@ function findOpenAPIRoutesForContentRoot(contentRoot, i18n) {
|
|
|
2130
2515
|
});
|
|
2131
2516
|
}
|
|
2132
2517
|
}
|
|
2133
|
-
if (
|
|
2518
|
+
if (locales.missing === "fallback") {
|
|
2134
2519
|
const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
|
|
2135
|
-
const defaultRoutes = localizedRoutes.filter((route) => route.locale ===
|
|
2520
|
+
const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
|
|
2136
2521
|
for (const sourceRoute of defaultRoutes) {
|
|
2137
2522
|
const basePath = sourceRoute.basePath ?? sourceRoute.path;
|
|
2138
|
-
for (const locale of
|
|
2523
|
+
for (const locale of locales.locales) {
|
|
2139
2524
|
const key = `${locale.code}:${basePath}`;
|
|
2140
2525
|
if (routeByLocaleAndBase.has(key)) continue;
|
|
2141
|
-
const path = localizedRoutePath(basePath, locale.code,
|
|
2526
|
+
const path = localizedRoutePath(basePath, locale.code, locales);
|
|
2142
2527
|
localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
|
|
2143
2528
|
path,
|
|
2144
2529
|
locale: locale.code,
|
|
@@ -2165,7 +2550,7 @@ function createConfiguredOpenAPIRoutes(routes, config) {
|
|
|
2165
2550
|
const key = `${route.locale ?? ""}:${targetBasePath}`;
|
|
2166
2551
|
if (existingKeys.has(key)) continue;
|
|
2167
2552
|
existingKeys.add(key);
|
|
2168
|
-
const path = route.locale && config.
|
|
2553
|
+
const path = route.locale && config.locales ? localizedRoutePath(targetBasePath, route.locale, config.locales) : targetBasePath;
|
|
2169
2554
|
additions.push(cloneOpenAPIRoute(route, {
|
|
2170
2555
|
path,
|
|
2171
2556
|
basePath: targetBasePath,
|
|
@@ -2178,12 +2563,12 @@ function createConfiguredOpenAPIRoutes(routes, config) {
|
|
|
2178
2563
|
}
|
|
2179
2564
|
}
|
|
2180
2565
|
const nextRoutes = [...routes, ...additions];
|
|
2181
|
-
if (!config.
|
|
2182
|
-
const routesWithAlternates = nextRoutes.map((route) => withAlternates(route, nextRoutes, config.
|
|
2566
|
+
if (!config.locales) return nextRoutes;
|
|
2567
|
+
const routesWithAlternates = nextRoutes.map((route) => withAlternates(route, nextRoutes, config.locales));
|
|
2183
2568
|
const bareAliases = [];
|
|
2184
2569
|
const seenBare = new Set(routesWithAlternates.map((route) => route.path));
|
|
2185
2570
|
for (const route of routesWithAlternates) {
|
|
2186
|
-
if (route.locale !== config.
|
|
2571
|
+
if (route.locale !== config.locales.default) continue;
|
|
2187
2572
|
const basePath = route.basePath ?? route.path;
|
|
2188
2573
|
if (basePath === route.path || seenBare.has(basePath)) continue;
|
|
2189
2574
|
seenBare.add(basePath);
|
|
@@ -2222,7 +2607,7 @@ function createOpenAPIPlugin() {
|
|
|
2222
2607
|
hooks: {
|
|
2223
2608
|
"routes:discover": (input, ctx) => ({
|
|
2224
2609
|
...input,
|
|
2225
|
-
routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.
|
|
2610
|
+
routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.locales)]
|
|
2226
2611
|
}),
|
|
2227
2612
|
"routes:discovered": async (routes, ctx) => {
|
|
2228
2613
|
specs.clear();
|
|
@@ -2307,12 +2692,12 @@ function removeClarifyTempDir(path) {
|
|
|
2307
2692
|
});
|
|
2308
2693
|
}
|
|
2309
2694
|
//#endregion
|
|
2310
|
-
//#region source/plugins/search
|
|
2695
|
+
//#region source/plugins/page-search/search-language.ts
|
|
2311
2696
|
function toPagefindLanguage(locale) {
|
|
2312
2697
|
return locale?.trim().replace(/_/g, "-").split("-").filter(Boolean).join("-").toLowerCase() || "en";
|
|
2313
2698
|
}
|
|
2314
2699
|
//#endregion
|
|
2315
|
-
//#region source/plugins/search
|
|
2700
|
+
//#region source/plugins/page-search/index.ts
|
|
2316
2701
|
var pagefindContentTypes = {
|
|
2317
2702
|
".css": "text/css; charset=utf-8",
|
|
2318
2703
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -2352,15 +2737,6 @@ async function generatePagefindIndex(options, pagefind) {
|
|
|
2352
2737
|
if (!pagefind) await runtime.close();
|
|
2353
2738
|
}
|
|
2354
2739
|
}
|
|
2355
|
-
function routeSearchContent(route) {
|
|
2356
|
-
return [
|
|
2357
|
-
route.meta.title,
|
|
2358
|
-
route.meta.description,
|
|
2359
|
-
route.meta.keywords?.join(" "),
|
|
2360
|
-
route.meta.sections?.map((section) => section.title).join(" "),
|
|
2361
|
-
route.source.content
|
|
2362
|
-
].filter(Boolean).join("\n\n");
|
|
2363
|
-
}
|
|
2364
2740
|
async function generateDevSearchIndex(ctx, root, pagefind) {
|
|
2365
2741
|
const { errors, index } = await pagefind.createIndex({
|
|
2366
2742
|
includeCharacters: "._-/#:@",
|
|
@@ -2379,7 +2755,7 @@ async function generateDevSearchIndex(ctx, root, pagefind) {
|
|
|
2379
2755
|
const result = await index.addCustomRecord({
|
|
2380
2756
|
url: route.path,
|
|
2381
2757
|
content,
|
|
2382
|
-
language: toPagefindLanguage(route.locale ?? ctx.projectConfig.
|
|
2758
|
+
language: toPagefindLanguage(route.locale ?? ctx.projectConfig.locales?.default),
|
|
2383
2759
|
meta: { title: route.meta.title }
|
|
2384
2760
|
});
|
|
2385
2761
|
assertNoPagefindErrors(`addCustomRecord ${route.path}`, result.errors);
|
|
@@ -2417,11 +2793,12 @@ function serveDevPagefind(server, getRoot, ctx) {
|
|
|
2417
2793
|
(0, node_fs.createReadStream)(filePath).pipe(res);
|
|
2418
2794
|
});
|
|
2419
2795
|
}
|
|
2420
|
-
function
|
|
2796
|
+
function createPageSearchPlugin() {
|
|
2421
2797
|
return {
|
|
2422
|
-
name: "clarify:search
|
|
2798
|
+
name: "clarify:page-search",
|
|
2423
2799
|
hooks: {
|
|
2424
2800
|
async "dev:configureServer"(server, ctx) {
|
|
2801
|
+
if (!ctx.projectConfig.features.search.enabled) return;
|
|
2425
2802
|
const pagefind = await import("pagefind");
|
|
2426
2803
|
let currentRoot;
|
|
2427
2804
|
const staleRoots = /* @__PURE__ */ new Set();
|
|
@@ -2461,10 +2838,11 @@ function createSearchIndexPlugin() {
|
|
|
2461
2838
|
});
|
|
2462
2839
|
},
|
|
2463
2840
|
async "build:done"(ctx) {
|
|
2841
|
+
if (!ctx.projectConfig.features.search.enabled) return;
|
|
2464
2842
|
const outputDirectory = ctx.generateOptions.outputDirectory;
|
|
2465
2843
|
if (!outputDirectory) return;
|
|
2466
2844
|
const pageCount = await generatePagefindIndex({ outputRoot: (0, node_path.resolve)(ctx.generateOptions.projectRoot, outputDirectory) });
|
|
2467
|
-
console.log(`[clarify]
|
|
2845
|
+
console.log(`[clarify] Page search index generated for ${pageCount} pages.`);
|
|
2468
2846
|
}
|
|
2469
2847
|
}
|
|
2470
2848
|
};
|
|
@@ -2513,16 +2891,18 @@ function createSeoPlugin() {
|
|
|
2513
2891
|
return {
|
|
2514
2892
|
name: "clarify:seo",
|
|
2515
2893
|
hooks: { "build:assets"(ctx) {
|
|
2894
|
+
const assets = [];
|
|
2516
2895
|
const sitemap = createSitemapXml(ctx.routes, ctx.projectConfig);
|
|
2517
2896
|
const robots = createRobotsTxt(ctx.projectConfig);
|
|
2518
|
-
if (
|
|
2519
|
-
return [{
|
|
2897
|
+
if (sitemap) assets.push({
|
|
2520
2898
|
fileName: "sitemap.xml",
|
|
2521
2899
|
source: sitemap
|
|
2522
|
-
}
|
|
2900
|
+
});
|
|
2901
|
+
if (robots) assets.push({
|
|
2523
2902
|
fileName: "robots.txt",
|
|
2524
2903
|
source: robots
|
|
2525
|
-
}
|
|
2904
|
+
});
|
|
2905
|
+
return assets;
|
|
2526
2906
|
} }
|
|
2527
2907
|
};
|
|
2528
2908
|
}
|
|
@@ -2538,7 +2918,8 @@ function encodePath(path) {
|
|
|
2538
2918
|
return path.split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/");
|
|
2539
2919
|
}
|
|
2540
2920
|
function createSourceEditUrl(filePath, contentRoot, source) {
|
|
2541
|
-
|
|
2921
|
+
if (!source.url) return "";
|
|
2922
|
+
const repository = normalizeRepositoryUrl(source.url);
|
|
2542
2923
|
const branch = source.branch ?? "main";
|
|
2543
2924
|
const sourcePath = [source.directory ? trimSlashes(source.directory) : "", (0, node_path.relative)(contentRoot, filePath).replaceAll("\\", "/")].filter(Boolean).join("/");
|
|
2544
2925
|
return `${repository}/edit/${encodeURIComponent(branch)}/${encodePath(sourcePath)}`;
|
|
@@ -2556,7 +2937,8 @@ function createSourceLinksPlugin() {
|
|
|
2556
2937
|
return {
|
|
2557
2938
|
name: "clarify:source-links",
|
|
2558
2939
|
hooks: { "routes:discovered": (routes, ctx) => {
|
|
2559
|
-
|
|
2940
|
+
if (!ctx.projectConfig.features.repository.enabled) return routes;
|
|
2941
|
+
attachSourceEditUrls(routes, (0, node_path.join)(ctx.generateOptions.projectRoot, ctx.generateOptions.rootDirectory), ctx.projectConfig.features.repository);
|
|
2560
2942
|
return routes;
|
|
2561
2943
|
} }
|
|
2562
2944
|
};
|
|
@@ -2609,7 +2991,8 @@ function createBuiltinPlugins(options = {}) {
|
|
|
2609
2991
|
createSourceLinksPlugin(),
|
|
2610
2992
|
createContentArtifactsPlugin(),
|
|
2611
2993
|
createSeoPlugin(),
|
|
2612
|
-
|
|
2994
|
+
createPageSearchPlugin(),
|
|
2995
|
+
createMcpSearchPlugin()
|
|
2613
2996
|
];
|
|
2614
2997
|
if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
|
|
2615
2998
|
return plugins;
|
|
@@ -2671,18 +3054,18 @@ async function resolveProjectContext(options = {}, env = {
|
|
|
2671
3054
|
}) {
|
|
2672
3055
|
const projectRoot = (0, node_path.resolve)(options.projectRoot ?? process.cwd());
|
|
2673
3056
|
const initialBuildOptions = resolveBuildOptions(options);
|
|
2674
|
-
const contentRoot = (0, node_path.join)(projectRoot, initialBuildOptions.rootDirectory);
|
|
2675
3057
|
const configFilePath = findClarifyConfigFile(projectRoot);
|
|
2676
3058
|
const config = configFilePath && (0, node_fs.existsSync)(configFilePath) ? await loadClarifyConfig(projectRoot, env) : {};
|
|
2677
3059
|
const mergedOptions = {
|
|
2678
3060
|
...config,
|
|
2679
3061
|
...options,
|
|
2680
3062
|
projectRoot,
|
|
2681
|
-
rootDirectory: initialBuildOptions.rootDirectory,
|
|
2682
|
-
outputDirectory: initialBuildOptions.outputDirectory
|
|
3063
|
+
rootDirectory: options.rootDirectory ?? initialBuildOptions.rootDirectory,
|
|
3064
|
+
outputDirectory: options.outputDirectory ?? initialBuildOptions.outputDirectory
|
|
2683
3065
|
};
|
|
2684
3066
|
const resolvedProjectConfig = resolveProjectConfig(mergedOptions);
|
|
2685
3067
|
const resolvedBuildOptions = resolveBuildOptions(mergedOptions);
|
|
3068
|
+
const contentRoot = (0, node_path.join)(projectRoot, resolvedBuildOptions.rootDirectory);
|
|
2686
3069
|
return {
|
|
2687
3070
|
projectRoot,
|
|
2688
3071
|
contentRoot,
|
|
@@ -2772,9 +3155,9 @@ function canonicalUrl(contextOrConfig, route) {
|
|
|
2772
3155
|
/** Inject `lang` (and optional `dir`) onto the `<html>` tag for the route locale. */
|
|
2773
3156
|
function injectHtmlLocaleAttributes(html, contextOrConfig, route) {
|
|
2774
3157
|
const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
|
|
2775
|
-
const localeCode = route?.locale ?? projectConfig.
|
|
3158
|
+
const localeCode = route?.locale ?? projectConfig.locales?.default;
|
|
2776
3159
|
if (!localeCode) return html;
|
|
2777
|
-
const dir = (projectConfig.
|
|
3160
|
+
const dir = (projectConfig.locales?.locales.find((locale) => locale.code === localeCode))?.dir;
|
|
2778
3161
|
return html.replace(/<html\b([^>]*)>/i, (_match, attributes) => {
|
|
2779
3162
|
let nextAttributes = attributes.replace(/\s+lang=("[^"]*"|'[^']*'|[^\s>]*)/i, "").replace(/\s+dir=("[^"]*"|'[^']*'|[^\s>]*)/i, "");
|
|
2780
3163
|
nextAttributes = `${nextAttributes} lang="${escapeHtml(localeCode)}"`;
|
|
@@ -2952,20 +3335,23 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
|
|
|
2952
3335
|
}
|
|
2953
3336
|
});
|
|
2954
3337
|
}
|
|
2955
|
-
async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath
|
|
3338
|
+
async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath) {
|
|
2956
3339
|
const { render } = await import((0, node_url.pathToFileURL)(ssrBundlePath).href);
|
|
2957
3340
|
const template = readIndexHtml(outputDirectory);
|
|
2958
3341
|
if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
(
|
|
2963
|
-
(
|
|
3342
|
+
await Promise.all(routes.map(async (route) => {
|
|
3343
|
+
try {
|
|
3344
|
+
const appHtml = await render(route.path);
|
|
3345
|
+
const finalHtml = injectSSRIntoTemplate(template, appHtml, contextOrConfig, route);
|
|
3346
|
+
for (const outFile of routeOutputFiles(outputDirectory, route)) {
|
|
3347
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
|
|
3348
|
+
(0, node_fs.writeFileSync)(outFile, finalHtml, "utf-8");
|
|
3349
|
+
}
|
|
3350
|
+
} catch (err) {
|
|
3351
|
+
console.error(`[clarify] Failed to render route "${route.path}":`, err);
|
|
3352
|
+
throw err;
|
|
2964
3353
|
}
|
|
2965
|
-
}
|
|
2966
|
-
console.error(`[clarify] Failed to render route "${route.path}":`, err);
|
|
2967
|
-
if (failOnError) throw err;
|
|
2968
|
-
}
|
|
3354
|
+
}));
|
|
2969
3355
|
}
|
|
2970
3356
|
//#endregion
|
|
2971
3357
|
//#region source/core/runtime/startup.ts
|
|
@@ -3041,10 +3427,10 @@ var ClarifyContext = class {
|
|
|
3041
3427
|
this.currentPlugins = plugins;
|
|
3042
3428
|
}
|
|
3043
3429
|
get isI18n() {
|
|
3044
|
-
return Boolean(this.projectConfig.
|
|
3430
|
+
return Boolean(this.projectConfig.locales);
|
|
3045
3431
|
}
|
|
3046
3432
|
get defaultLocale() {
|
|
3047
|
-
return this.projectConfig.
|
|
3433
|
+
return this.projectConfig.locales?.default;
|
|
3048
3434
|
}
|
|
3049
3435
|
get(key) {
|
|
3050
3436
|
return this.values.get(key);
|
|
@@ -3238,10 +3624,10 @@ var ClarifyEngine = class {
|
|
|
3238
3624
|
async discoverSite() {
|
|
3239
3625
|
const plugins = this.ctx.plugins;
|
|
3240
3626
|
const contentRoot = this.ctx.contentRoot;
|
|
3241
|
-
const {
|
|
3627
|
+
const { locales, navigation: navigationConfig } = this.ctx.projectConfig;
|
|
3242
3628
|
let routes = await runPhase(plugins, "site:discover", this.ctx, async () => {
|
|
3243
3629
|
const options = { contentProcessor: createProjectContentProcessor(plugins, this.ctx) };
|
|
3244
|
-
const discovered =
|
|
3630
|
+
const discovered = locales ? await findLocalizedContentRoutes(contentRoot, locales, options) : await findContentRoutes(contentRoot, contentRoot, options);
|
|
3245
3631
|
const result = await runHooks(plugins, "routes:discover", {
|
|
3246
3632
|
contentRoot,
|
|
3247
3633
|
routes: discovered
|
|
@@ -3270,8 +3656,9 @@ var ClarifyEngine = class {
|
|
|
3270
3656
|
};
|
|
3271
3657
|
});
|
|
3272
3658
|
});
|
|
3273
|
-
|
|
3274
|
-
|
|
3659
|
+
const tabs = navigationConfig?.tabs;
|
|
3660
|
+
routes = applyConfiguredPageRoutePaths(routes, tabs, locales);
|
|
3661
|
+
const navigation = tabs ? locales ? buildLocalizedNavigationFromTabsConfig(routes, tabs, locales) ?? {
|
|
3275
3662
|
kind: "localized-tabbed",
|
|
3276
3663
|
locales: {}
|
|
3277
3664
|
} : buildNavigationFromTabsConfig(routes, tabs) : {
|
|
@@ -3301,7 +3688,7 @@ var ClarifyEngine = class {
|
|
|
3301
3688
|
routes: this.routes,
|
|
3302
3689
|
navigation: this.navigation,
|
|
3303
3690
|
plugins: this.plugins,
|
|
3304
|
-
themeEditor: this.runtime.command === "serve" || this.runtime.themeEditor === true || this.projectConfig.
|
|
3691
|
+
themeEditor: this.runtime.command === "serve" || this.runtime.themeEditor === true || this.projectConfig.features.themeEditor.enabled,
|
|
3305
3692
|
version: this.ctx.version
|
|
3306
3693
|
});
|
|
3307
3694
|
this.virtualModules = await runHooks(this.plugins, "modules:before", this.virtualModules, this.ctx);
|
|
@@ -3344,7 +3731,7 @@ var ClarifyEngine = class {
|
|
|
3344
3731
|
await runPhase(this.plugins, "ssg", this.ctx, async () => {
|
|
3345
3732
|
if (process.env.SKIP_CLARIFY_SSG) return;
|
|
3346
3733
|
if (!await runInterceptHooks(this.plugins, "ssg:shouldRun", this.ctx)) return;
|
|
3347
|
-
|
|
3734
|
+
assertNoContentDiagnostics(this.routes);
|
|
3348
3735
|
const outputDir = this.runtime.outputDirectory ?? this.generateOptions.outputDirectory;
|
|
3349
3736
|
if (!outputDir) throw new Error("[clarify] outputDirectory is required before SSG runs");
|
|
3350
3737
|
const ssrOutputDir = (0, node_path.join)(outputDir, ".ssr");
|
|
@@ -3358,10 +3745,10 @@ var ClarifyEngine = class {
|
|
|
3358
3745
|
plugins: this.runtime.ssrPlugins ?? []
|
|
3359
3746
|
});
|
|
3360
3747
|
const ssrBundlePath = (0, node_path.join)(ssrOutputDir, "entry-server.js");
|
|
3361
|
-
await renderSSGRoutes(this.routes, this.runtimeContext(), outputDir, ssrBundlePath
|
|
3748
|
+
await renderSSGRoutes(this.routes, this.runtimeContext(), outputDir, ssrBundlePath);
|
|
3362
3749
|
} catch (err) {
|
|
3363
3750
|
console.error("[clarify] SSG failed:", err);
|
|
3364
|
-
|
|
3751
|
+
throw err;
|
|
3365
3752
|
} finally {
|
|
3366
3753
|
if (tempEntryPath) try {
|
|
3367
3754
|
(0, node_fs.rmSync)(tempEntryPath, { force: true });
|
|
@@ -3451,5 +3838,17 @@ Object.defineProperty(exports, "defineConfig", {
|
|
|
3451
3838
|
return defineConfig;
|
|
3452
3839
|
}
|
|
3453
3840
|
});
|
|
3841
|
+
Object.defineProperty(exports, "deserializeSearchIndex", {
|
|
3842
|
+
enumerable: true,
|
|
3843
|
+
get: function() {
|
|
3844
|
+
return deserializeSearchIndex;
|
|
3845
|
+
}
|
|
3846
|
+
});
|
|
3847
|
+
Object.defineProperty(exports, "searchMcpIndex", {
|
|
3848
|
+
enumerable: true,
|
|
3849
|
+
get: function() {
|
|
3850
|
+
return searchMcpIndex;
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3454
3853
|
|
|
3455
|
-
//# sourceMappingURL=engine-
|
|
3854
|
+
//# sourceMappingURL=engine-Cbn_coqg.cjs.map
|