@clarify-labs/cli 0.11.0 → 0.11.2

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.
Files changed (31) hide show
  1. package/output/cli/commands/mcp.d.ts +27 -0
  2. package/output/cli/mcp/orama-loader.d.ts +57 -0
  3. package/output/cli/mcp/search-server.d.ts +34 -0
  4. package/output/cli/program.d.ts +0 -1
  5. package/output/cli.cjs +303 -13
  6. package/output/cli.cjs.map +1 -1
  7. package/output/cli.js +304 -14
  8. package/output/cli.js.map +1 -1
  9. package/output/core/config/config-schema.d.ts +6 -220
  10. package/output/core/plugin/hooks.d.ts +0 -1
  11. package/output/core/runtime/html-template.d.ts +0 -6
  12. package/output/core/runtime/virtual-modules.d.ts +0 -4
  13. package/output/{engine-BfkoyVNo.cjs → engine--5unu78V.cjs} +385 -28
  14. package/output/engine--5unu78V.cjs.map +1 -0
  15. package/output/{engine-DfF5RJjC.js → engine-DI8Nd3v0.js} +374 -29
  16. package/output/engine-DI8Nd3v0.js.map +1 -0
  17. package/output/index.cjs +1 -1
  18. package/output/index.js +1 -1
  19. package/output/parsers/routes/routes.d.ts +1 -5
  20. package/output/plugins/content-artifacts/artifacts.d.ts +0 -2
  21. package/output/plugins/mcp-search/index.d.ts +15 -0
  22. package/output/plugins/mcp-search/mcp-config.d.ts +75 -0
  23. package/output/plugins/mcp-search/orama-index.d.ts +153 -0
  24. package/output/plugins/openapi/virtual-modules.d.ts +0 -1
  25. package/output/plugins/page-search/index.d.ts +2 -0
  26. package/output/types.d.ts +3 -2
  27. package/package.json +6 -4
  28. package/output/engine-BfkoyVNo.cjs.map +0 -1
  29. package/output/engine-DfF5RJjC.js.map +0 -1
  30. package/output/plugins/search-index/index.d.ts +0 -2
  31. /package/output/plugins/{search-index → page-search}/search-language.d.ts +0 -0
@@ -18,6 +18,9 @@ import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rea
18
18
  import { build, loadConfigFromFile } from "vite";
19
19
  import { z } from "zod";
20
20
  import { stringify } from "yaml";
21
+ import { createHash } from "node:crypto";
22
+ import { decode, encode } from "@msgpack/msgpack";
23
+ import { create, insertMultiple, load, save, search } from "@orama/orama";
21
24
  import { fileURLToPath, pathToFileURL } from "node:url";
22
25
  import SwaggerParser from "@apidevtools/swagger-parser";
23
26
  import { tmpdir } from "node:os";
@@ -968,7 +971,7 @@ function featureSchema(shape, defaults) {
968
971
  } : value);
969
972
  }
970
973
  var clarifyFeaturesConfigSchema = z.object({
971
- search: featureSchema({ provider: z.literal("pagefind").default("pagefind") }, { provider: "pagefind" }),
974
+ search: featureSchema({ mcp: z.boolean().default(true) }, { mcp: true }),
972
975
  repository: featureSchema(clarifyRepositoryConfigSchema.shape, {}),
973
976
  themeEditor: featureSchema({}, {}),
974
977
  openapi: featureSchema({ playground: z.boolean().default(true) }, { playground: true })
@@ -1554,7 +1557,7 @@ function createViteAdapter(engine) {
1554
1557
  }
1555
1558
  var cliPackageVersion = {
1556
1559
  name: "@clarify-labs/cli",
1557
- version: "0.11.0",
1560
+ version: "0.11.2",
1558
1561
  license: "AGPL-3.0-only",
1559
1562
  type: "module",
1560
1563
  repository: {
@@ -1595,6 +1598,9 @@ var cliPackageVersion = {
1595
1598
  "@clarify-labs/templates": "workspace:*",
1596
1599
  "@mdx-js/mdx": "^3.1.1",
1597
1600
  "@mdx-js/rollup": "^3.1.1",
1601
+ "@modelcontextprotocol/sdk": "^1.29.0",
1602
+ "@msgpack/msgpack": "^3.1.3",
1603
+ "@orama/orama": "^3.1.18",
1598
1604
  "@shikijs/rehype": "^4.3.0",
1599
1605
  "@tailwindcss/vite": "^4.3.2",
1600
1606
  "@vitejs/plugin-react": "^6.0.3",
@@ -1613,7 +1619,6 @@ var cliPackageVersion = {
1613
1619
  "rehype-raw": "^7.0.0",
1614
1620
  "rehype-slug": "^6.0.0",
1615
1621
  "remark": "^15.0.1",
1616
- "remark-gfm": "^4.0.1",
1617
1622
  "shiki": "4.3.0",
1618
1623
  "unist-util-visit": "^5.1.0",
1619
1624
  "vite": "^8.1.4",
@@ -1894,6 +1899,351 @@ function createHtmlShellPlugin() {
1894
1899
  };
1895
1900
  }
1896
1901
  //#endregion
1902
+ //#region source/plugins/mcp-search/mcp-config.ts
1903
+ /**
1904
+ * Normalize a route prefix into a root-relative path segment.
1905
+ *
1906
+ * `'/'` -> `''`, `'/docs'` -> `'/docs'`, `'docs/'` -> `'/docs'`.
1907
+ */
1908
+ function normalizePrefix(routePrefix) {
1909
+ if (!routePrefix || routePrefix === "/") return "";
1910
+ return `/${routePrefix.replace(/^\/+|\/+$/g, "")}`;
1911
+ }
1912
+ /**
1913
+ * Assemble the MCP site config from build-time metadata.
1914
+ *
1915
+ * Unlike the previous pagefind-based descriptor, this no longer reads an
1916
+ * entry JSON file - the Orama index is a single binary blob, so the caller
1917
+ * supplies the document count and locale list directly from the build step
1918
+ * that produced the index.
1919
+ */
1920
+ function createMcpSiteConfig(projectConfig, options) {
1921
+ const prefix = normalizePrefix(projectConfig.routePrefix);
1922
+ return {
1923
+ version: 3,
1924
+ site: {
1925
+ title: projectConfig.title,
1926
+ description: projectConfig.description,
1927
+ url: projectConfig.siteUrl,
1928
+ routePrefix: projectConfig.routePrefix ?? "/"
1929
+ },
1930
+ capabilities: { search: {
1931
+ type: "search",
1932
+ indexPath: `${prefix}/mcp-search.msp`,
1933
+ indexHash: options.indexHash,
1934
+ defaultLocale: projectConfig.locales?.default,
1935
+ documentCount: options.documentCount,
1936
+ locales: options.locales
1937
+ } }
1938
+ };
1939
+ }
1940
+ //#endregion
1941
+ //#region source/plugins/mcp-search/orama-index.ts
1942
+ /**
1943
+ * Orama schema for the MCP search index.
1944
+ *
1945
+ * - `path`, `title`, `description`, `content` are full-text searchable strings.
1946
+ * - `locale` is an `enum` so we can use `where: { locale: { eq } }` filters at
1947
+ * query time. (`string` fields cannot be filtered in Orama.)
1948
+ * - `keywords` is a `string[]` also indexed for full-text matching.
1949
+ */
1950
+ var mcpSearchSchema = {
1951
+ path: "string",
1952
+ title: "string",
1953
+ description: "string",
1954
+ locale: "enum",
1955
+ content: "string",
1956
+ keywords: "string[]"
1957
+ };
1958
+ /**
1959
+ * Intl.Segmenter cache keyed by BCP 47 tag. Segmenter construction is not
1960
+ * cheap, and the MCP server issues many queries against the same locale, so
1961
+ * we reuse instances. The cache is module-scoped: build and runtime share it
1962
+ * in the same process, and separate processes get their own - which is fine.
1963
+ */
1964
+ var segmenterCache = /* @__PURE__ */ new Map();
1965
+ function getSegmenter(locale) {
1966
+ let seg = segmenterCache.get(locale);
1967
+ if (!seg) {
1968
+ seg = new Intl.Segmenter(locale, { granularity: "word" });
1969
+ segmenterCache.set(locale, seg);
1970
+ }
1971
+ return seg;
1972
+ }
1973
+ /**
1974
+ * Map a Clarify locale code (e.g. `zh-CN`, `en-US`, `ja-JP`) to a BCP 47 tag
1975
+ * suitable for `Intl.Segmenter`. Returns `null` for locales we intentionally
1976
+ * want to tokenize with the Latin/whitespace fallback (faster, and avoids
1977
+ * forcing CJK-style word segmentation on Latin scripts).
1978
+ */
1979
+ function segmenterLocale(locale) {
1980
+ if (!locale) return null;
1981
+ const lower = locale.toLowerCase();
1982
+ if (lower.startsWith("zh")) return "zh-CN";
1983
+ if (lower.startsWith("ja")) return "ja-JP";
1984
+ if (lower.startsWith("ko")) return "ko-KR";
1985
+ return null;
1986
+ }
1987
+ /**
1988
+ * Tokenize text for Orama indexing and querying.
1989
+ *
1990
+ * CJK locales use `Intl.Segmenter` (word granularity), which splits
1991
+ * spaceless scripts into words and individual characters. Latin/whitespace
1992
+ * locales fall back to a Unicode-aware split on spaces and punctuation.
1993
+ *
1994
+ * All tokens are lowercased so queries match regardless of case.
1995
+ */
1996
+ function tokenizeForSearch(text, locale) {
1997
+ if (!text) return [];
1998
+ const segLocale = segmenterLocale(locale);
1999
+ if (segLocale) {
2000
+ const seg = getSegmenter(segLocale);
2001
+ return Array.from(seg.segment(text)).filter((s) => s.isWordLike).map((s) => s.segment.toLowerCase());
2002
+ }
2003
+ return text.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
2004
+ }
2005
+ /**
2006
+ * Build an Orama tokenizer component bound to a specific default locale.
2007
+ *
2008
+ * The tokenizer receives the raw field value and the per-field language hint
2009
+ * from Orama (which we don't set per-field, so it falls back to the default
2010
+ * language). We resolve the segmenter locale from the default locale at
2011
+ * construction time so the same tokenizer handles all documents in a
2012
+ * predominantly single-locale index.
2013
+ *
2014
+ * For multi-locale indexes, the tokenizer is also used at query time. Since
2015
+ * the query locale is not known to Orama's tokenizer callback, we use the
2016
+ * index's default locale - this is acceptable because:
2017
+ * - CJK queries get CJK segmentation (correct)
2018
+ * - Latin queries against a CJK-default index still work because Latin
2019
+ * text has no word boundaries for CJK segmenters to mis-split; the
2020
+ * segmenter passes through Latin words as-is.
2021
+ */
2022
+ function createMcpTokenizer(defaultLocale) {
2023
+ return {
2024
+ language: defaultLocale ?? "en",
2025
+ normalizationCache: /* @__PURE__ */ new Map(),
2026
+ tokenize(raw, language) {
2027
+ return tokenizeForSearch(typeof raw === "string" ? raw : String(raw ?? ""), language ?? defaultLocale);
2028
+ }
2029
+ };
2030
+ }
2031
+ /**
2032
+ * Compose the searchable text for a route. Shared between the Pagefind
2033
+ * (browser) and Orama (MCP) indexing paths so both backends see the same
2034
+ * field order and content.
2035
+ */
2036
+ function routeSearchContent(route) {
2037
+ return [
2038
+ route.meta.title,
2039
+ route.meta.description,
2040
+ route.meta.keywords?.join(" "),
2041
+ route.meta.sections?.map((section) => section.title).join(" "),
2042
+ route.source.content
2043
+ ].filter(Boolean).join("\n\n");
2044
+ }
2045
+ /**
2046
+ * Convert a `ContentRoute` into the document shape stored in the Orama index.
2047
+ */
2048
+ function routeToSearchDocument(route, defaultLocale) {
2049
+ if (route.isBareAlias) return null;
2050
+ const content = routeSearchContent(route);
2051
+ if (!content.trim()) return null;
2052
+ return {
2053
+ path: route.path,
2054
+ title: route.meta.title ?? "",
2055
+ description: route.meta.description ?? "",
2056
+ locale: route.locale ?? defaultLocale ?? "en",
2057
+ content,
2058
+ keywords: route.meta.keywords ?? []
2059
+ };
2060
+ }
2061
+ /**
2062
+ * Create an Orama index pre-configured with the MCP schema and CJK-aware
2063
+ * tokenizer. Both the build step and the runtime loader use this so the
2064
+ * tokenizer is guaranteed to match between indexing and querying.
2065
+ *
2066
+ * Annotated as `AnyOrama` (Orama's exported base type) rather than inferred
2067
+ * via `ReturnType<typeof create>` because the inference would pull in
2068
+ * Orama's internal component types (`DocumentsStore`, `Index`, `Sorter`)
2069
+ * which are not exported and break `.d.ts` generation (TS2883).
2070
+ */
2071
+ function createMcpSearchIndex(defaultLocale) {
2072
+ return create({
2073
+ schema: mcpSearchSchema,
2074
+ components: { tokenizer: createMcpTokenizer(defaultLocale) }
2075
+ });
2076
+ }
2077
+ /**
2078
+ * Insert search documents into an Orama index.
2079
+ */
2080
+ function indexSearchDocuments(db, documents) {
2081
+ if (documents.length === 0) return;
2082
+ insertMultiple(db, documents);
2083
+ }
2084
+ /**
2085
+ * Build a ready-to-search Orama index from content routes.
2086
+ *
2087
+ * Filters out bare aliases and empty routes. Returns the populated db.
2088
+ */
2089
+ function buildSearchIndex(routes, defaultLocale) {
2090
+ const db = createMcpSearchIndex(defaultLocale);
2091
+ const documents = [];
2092
+ for (const route of routes) {
2093
+ const doc = routeToSearchDocument(route, defaultLocale);
2094
+ if (doc) documents.push(doc);
2095
+ }
2096
+ indexSearchDocuments(db, documents);
2097
+ return {
2098
+ db,
2099
+ documentCount: documents.length
2100
+ };
2101
+ }
2102
+ /**
2103
+ * Collect the distinct locale codes that will be present in the Orama index,
2104
+ * in stable insertion order. Bare aliases are skipped (they duplicate the
2105
+ * default-locale route). Locales with no code fall back to the project
2106
+ * default so single-locale sites still surface one locale.
2107
+ */
2108
+ function collectIndexedLocales(routes, defaultLocale) {
2109
+ const seen = /* @__PURE__ */ new Set();
2110
+ const locales = [];
2111
+ for (const route of routes) {
2112
+ if (route.isBareAlias) continue;
2113
+ const code = route.locale ?? defaultLocale ?? "en";
2114
+ if (!seen.has(code)) {
2115
+ seen.add(code);
2116
+ locales.push(code);
2117
+ }
2118
+ }
2119
+ return locales;
2120
+ }
2121
+ /**
2122
+ * Search an Orama MCP index.
2123
+ *
2124
+ * Applies an optional locale filter (via the `enum` field) and returns a
2125
+ * compact, LLM-friendly hit shape. Excerpts are derived from the matched
2126
+ * content: Orama does not return highlighted excerpts, so we synthesize a
2127
+ * short window around the first query term occurrence.
2128
+ */
2129
+ function searchMcpIndex(db, params) {
2130
+ const limit = Math.max(1, Math.min(50, params.limit ?? 10));
2131
+ const result = search(db, {
2132
+ term: params.query,
2133
+ limit,
2134
+ where: params.locale ? { locale: { eq: params.locale } } : void 0
2135
+ });
2136
+ return {
2137
+ hits: result.hits.map((hit) => {
2138
+ const doc = hit.document;
2139
+ return {
2140
+ path: doc.path,
2141
+ title: doc.title || guessTitleFromPath(doc.path),
2142
+ description: doc.description,
2143
+ locale: doc.locale,
2144
+ score: hit.score,
2145
+ excerpt: makeExcerpt(doc.content, params.query),
2146
+ keywords: doc.keywords
2147
+ };
2148
+ }),
2149
+ count: result.count
2150
+ };
2151
+ }
2152
+ /**
2153
+ * Build a short excerpt around the first occurrence of any query term in the
2154
+ * content. Falls back to the beginning of the content if no term is found.
2155
+ */
2156
+ function makeExcerpt(content, query, maxLen = 240) {
2157
+ if (!content) return "";
2158
+ const terms = query.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
2159
+ const lower = content.toLowerCase();
2160
+ let pos = -1;
2161
+ for (const term of terms) {
2162
+ const i = lower.indexOf(term);
2163
+ if (i >= 0 && (pos < 0 || i < pos)) pos = i;
2164
+ }
2165
+ if (pos < 0) pos = 0;
2166
+ const half = Math.floor(maxLen / 2);
2167
+ const start = Math.max(0, pos - half);
2168
+ const end = Math.min(content.length, start + maxLen);
2169
+ const prefix = start > 0 ? "…" : "";
2170
+ const suffix = end < content.length ? "…" : "";
2171
+ return `${prefix}${content.slice(start, end).trim()}${suffix}`;
2172
+ }
2173
+ function guessTitleFromPath(path) {
2174
+ if (!path) return "(untitled)";
2175
+ const segments = path.split("/").filter(Boolean);
2176
+ if (segments.length === 0) return "Home";
2177
+ return segments[segments.length - 1].replace(/\.(md|mdx|html)$/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2178
+ }
2179
+ /**
2180
+ * Serialize an Orama index to a msgpack binary buffer.
2181
+ *
2182
+ * Uses `save()` to extract the raw index data, then msgpack-encodes it for a
2183
+ * compact binary representation. The same schema + tokenizer must be supplied
2184
+ * when loading so query-time tokenization matches indexing-time tokenization.
2185
+ */
2186
+ function serializeSearchIndex(db) {
2187
+ return encode(save(db));
2188
+ }
2189
+ /**
2190
+ * Deserialize a msgpack binary buffer into a ready-to-search Orama index.
2191
+ *
2192
+ * The caller must supply the same `defaultLocale` used when the index was
2193
+ * built, so the tokenizer matches. A mismatch would not corrupt data but
2194
+ * would cause CJK query terms to be tokenized incorrectly (e.g. "文档" left
2195
+ * as a single token instead of split into "文" + "档").
2196
+ */
2197
+ function deserializeSearchIndex(buffer, defaultLocale) {
2198
+ const raw = decode(buffer);
2199
+ const db = createMcpSearchIndex(defaultLocale);
2200
+ load(db, raw);
2201
+ return db;
2202
+ }
2203
+ //#endregion
2204
+ //#region source/plugins/mcp-search/index.ts
2205
+ /**
2206
+ * MCP search index plugin.
2207
+ *
2208
+ * Generates the Orama binary index (`mcp-search.msp`) and the MCP site
2209
+ * descriptor (`mcp.json`) as build assets consumed by the `clarify mcp`
2210
+ * command to serve documentation search to MCP-aware clients (e.g. AI coding
2211
+ * assistants).
2212
+ *
2213
+ * This plugin is independent of `clarify:page-search` (Pagefind), which
2214
+ * powers in-browser search. The MCP server runs in Node and needs a
2215
+ * self-contained, compact index with CJK-aware tokenization that doesn't
2216
+ * depend on the browser-only Pagefind runtime.
2217
+ */
2218
+ function createMcpSearchPlugin() {
2219
+ return {
2220
+ name: "clarify:mcp-search",
2221
+ hooks: { "build:assets"(ctx) {
2222
+ const searchConfig = ctx.projectConfig.features.search;
2223
+ if (!searchConfig.enabled || !searchConfig.mcp) return [];
2224
+ const defaultLocale = ctx.projectConfig.locales?.default;
2225
+ const { db, documentCount } = buildSearchIndex(ctx.routes, defaultLocale);
2226
+ const locales = collectIndexedLocales(ctx.routes, defaultLocale);
2227
+ const indexBytes = serializeSearchIndex(db);
2228
+ const indexHash = createHash("sha256").update(indexBytes).digest("hex");
2229
+ const mcpConfig = createMcpSiteConfig(ctx.projectConfig, {
2230
+ documentCount,
2231
+ locales,
2232
+ indexHash
2233
+ });
2234
+ const assets = [{
2235
+ fileName: `${mcpConfig.capabilities.search?.indexPath.replace(/\/mcp-search\.msp$/, "") ?? ""}/mcp-search.msp`.replace(/^\//, ""),
2236
+ source: indexBytes
2237
+ }, {
2238
+ fileName: "mcp.json",
2239
+ source: `${JSON.stringify(mcpConfig, null, 2)}\n`
2240
+ }];
2241
+ console.log(`[clarify] MCP search index (${documentCount} docs, ${locales.length} locales) written to mcp-search.msp.`);
2242
+ return assets;
2243
+ } }
2244
+ };
2245
+ }
2246
+ //#endregion
1897
2247
  //#region source/plugins/openapi/parser.ts
1898
2248
  var OPENAPI_HTTP_METHODS = [
1899
2249
  "get",
@@ -2316,12 +2666,12 @@ function removeClarifyTempDir(path) {
2316
2666
  });
2317
2667
  }
2318
2668
  //#endregion
2319
- //#region source/plugins/search-index/search-language.ts
2669
+ //#region source/plugins/page-search/search-language.ts
2320
2670
  function toPagefindLanguage(locale) {
2321
2671
  return locale?.trim().replace(/_/g, "-").split("-").filter(Boolean).join("-").toLowerCase() || "en";
2322
2672
  }
2323
2673
  //#endregion
2324
- //#region source/plugins/search-index/index.ts
2674
+ //#region source/plugins/page-search/index.ts
2325
2675
  var pagefindContentTypes = {
2326
2676
  ".css": "text/css; charset=utf-8",
2327
2677
  ".js": "text/javascript; charset=utf-8",
@@ -2361,15 +2711,6 @@ async function generatePagefindIndex(options, pagefind) {
2361
2711
  if (!pagefind) await runtime.close();
2362
2712
  }
2363
2713
  }
2364
- function routeSearchContent(route) {
2365
- return [
2366
- route.meta.title,
2367
- route.meta.description,
2368
- route.meta.keywords?.join(" "),
2369
- route.meta.sections?.map((section) => section.title).join(" "),
2370
- route.source.content
2371
- ].filter(Boolean).join("\n\n");
2372
- }
2373
2714
  async function generateDevSearchIndex(ctx, root, pagefind) {
2374
2715
  const { errors, index } = await pagefind.createIndex({
2375
2716
  includeCharacters: "._-/#:@",
@@ -2426,9 +2767,9 @@ function serveDevPagefind(server, getRoot, ctx) {
2426
2767
  createReadStream(filePath).pipe(res);
2427
2768
  });
2428
2769
  }
2429
- function createSearchIndexPlugin() {
2770
+ function createPageSearchPlugin() {
2430
2771
  return {
2431
- name: "clarify:search-index",
2772
+ name: "clarify:page-search",
2432
2773
  hooks: {
2433
2774
  async "dev:configureServer"(server, ctx) {
2434
2775
  if (!ctx.projectConfig.features.search.enabled) return;
@@ -2475,7 +2816,7 @@ function createSearchIndexPlugin() {
2475
2816
  const outputDirectory = ctx.generateOptions.outputDirectory;
2476
2817
  if (!outputDirectory) return;
2477
2818
  const pageCount = await generatePagefindIndex({ outputRoot: resolve(ctx.generateOptions.projectRoot, outputDirectory) });
2478
- console.log(`[clarify] Search index generated for ${pageCount} pages.`);
2819
+ console.log(`[clarify] Page search index generated for ${pageCount} pages.`);
2479
2820
  }
2480
2821
  }
2481
2822
  };
@@ -2624,7 +2965,8 @@ function createBuiltinPlugins(options = {}) {
2624
2965
  createSourceLinksPlugin(),
2625
2966
  createContentArtifactsPlugin(),
2626
2967
  createSeoPlugin(),
2627
- createSearchIndexPlugin()
2968
+ createPageSearchPlugin(),
2969
+ createMcpSearchPlugin()
2628
2970
  ];
2629
2971
  if (options.htmlShell ?? true) plugins.push(createHtmlShellPlugin());
2630
2972
  return plugins;
@@ -2971,16 +3313,19 @@ async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBund
2971
3313
  const { render } = await import(pathToFileURL(ssrBundlePath).href);
2972
3314
  const template = readIndexHtml(outputDirectory);
2973
3315
  if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
2974
- for (const route of routes) try {
2975
- const finalHtml = injectSSRIntoTemplate(template, await render(route.path), contextOrConfig, route);
2976
- for (const outFile of routeOutputFiles(outputDirectory, route)) {
2977
- mkdirSync(dirname(outFile), { recursive: true });
2978
- writeFileSync(outFile, finalHtml, "utf-8");
3316
+ await Promise.all(routes.map(async (route) => {
3317
+ try {
3318
+ const appHtml = await render(route.path);
3319
+ const finalHtml = injectSSRIntoTemplate(template, appHtml, contextOrConfig, route);
3320
+ for (const outFile of routeOutputFiles(outputDirectory, route)) {
3321
+ mkdirSync(dirname(outFile), { recursive: true });
3322
+ writeFileSync(outFile, finalHtml, "utf-8");
3323
+ }
3324
+ } catch (err) {
3325
+ console.error(`[clarify] Failed to render route "${route.path}":`, err);
3326
+ throw err;
2979
3327
  }
2980
- } catch (err) {
2981
- console.error(`[clarify] Failed to render route "${route.path}":`, err);
2982
- throw err;
2983
- }
3328
+ }));
2984
3329
  }
2985
3330
  //#endregion
2986
3331
  //#region source/core/runtime/startup.ts
@@ -3419,6 +3764,6 @@ function createClarifyEngine(options = {}) {
3419
3764
  return new ClarifyEngine(options);
3420
3765
  }
3421
3766
  //#endregion
3422
- export { cliPackageVersion as a, defineConfig as c, createClarifyTempDir as i, createClarifyEngine as n, cliPackageVersionWithCaret as o, createClarifyRuntimeAliases as r, createViteAdapter as s, ClarifyEngine as t };
3767
+ export { deserializeSearchIndex as a, cliPackageVersionWithCaret as c, createClarifyTempDir as i, createViteAdapter as l, createClarifyEngine as n, searchMcpIndex as o, createClarifyRuntimeAliases as r, cliPackageVersion as s, ClarifyEngine as t, defineConfig as u };
3423
3768
 
3424
- //# sourceMappingURL=engine-DfF5RJjC.js.map
3769
+ //# sourceMappingURL=engine-DI8Nd3v0.js.map