@clarify-labs/cli 0.11.4 → 0.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,7 +81,7 @@ var rehypePlugins = [
81
81
  ];
82
82
  function createContentCompileDiagnostic(options) {
83
83
  const { format, phase, error, filePath, projectRoot } = options;
84
- const label = format === "mdx" ? "MDX" : "Markdown";
84
+ const label = format === "markdown+jsx" ? "MDX" : "Markdown";
85
85
  return createContentDiagnostic({
86
86
  kind: format,
87
87
  title: `${label} ${phase} error`,
@@ -127,7 +127,7 @@ async function compileMdxContent(content, options = {}) {
127
127
  return {
128
128
  ok: false,
129
129
  diagnostic: createContentCompileDiagnostic({
130
- format: "mdx",
130
+ format: "markdown+jsx",
131
131
  phase: "syntax",
132
132
  error,
133
133
  filePath,
@@ -137,6 +137,21 @@ async function compileMdxContent(content, options = {}) {
137
137
  }
138
138
  }
139
139
  //#endregion
140
+ //#region source/core/runtime/module-ids.ts
141
+ var PAGE_PREFIX = "virtual:clarify-page/";
142
+ var CONTENT_PREFIX = "virtual:clarify-content/";
143
+ function pageVirtualModuleId(path) {
144
+ return `${PAGE_PREFIX}${path.replace(/^\/+/, "").replace(/\/index$/, "").replace(/\/+/g, "/") || "index"}`;
145
+ }
146
+ function pageVirtualModuleIdFromRef(ref) {
147
+ return pageVirtualModuleId(ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, ""));
148
+ }
149
+ function contentVirtualModuleId(filePath, contentRoot) {
150
+ const sourcePath = relative(contentRoot, filePath).replace(/\\/g, "/");
151
+ if (!sourcePath || sourcePath.startsWith("../") || isAbsolute(sourcePath)) throw new Error(`Content source must be inside the content root: ${filePath}`);
152
+ return `${CONTENT_PREFIX}${sourcePath}`;
153
+ }
154
+ //#endregion
140
155
  //#region source/core/plugin/hooks.ts
141
156
  async function runHooks(plugins, hookName, input, ctx) {
142
157
  let result = input;
@@ -152,32 +167,43 @@ async function runHooks(plugins, hookName, input, ctx) {
152
167
  return result;
153
168
  }
154
169
  async function runDevConfigureServerHooks(plugins, server, ctx) {
170
+ const postHooks = [];
155
171
  for (const plugin of plugins) {
156
172
  const hook = plugin.hooks?.["dev:configureServer"];
157
173
  if (!hook) continue;
158
174
  try {
159
- await hook(server, ctx);
175
+ const postHook = await hook(server, ctx);
176
+ if (postHook) postHooks.push(postHook);
160
177
  } catch (err) {
161
178
  throw new Error(`[clarify] plugin "${plugin.name}" hook "dev:configureServer" failed: ${err}`, { cause: err });
162
179
  }
163
180
  }
181
+ return postHooks;
164
182
  }
165
- async function runCollectorHooks(plugins, hookName, ctx) {
166
- const results = [];
183
+ async function runBuildAssetsHooks(plugins, ctx) {
184
+ const assets = [];
185
+ const producerByFileName = /* @__PURE__ */ new Map();
167
186
  for (const plugin of plugins) {
168
- const hook = plugin.hooks?.[hookName];
187
+ const hook = plugin.hooks?.["build:assets"];
169
188
  if (!hook) continue;
189
+ let pluginAssets;
170
190
  try {
171
- const pluginResults = await hook(ctx);
172
- results.push(...pluginResults);
191
+ pluginAssets = await hook(ctx);
173
192
  } catch (err) {
174
- throw new Error(`[clarify] plugin "${plugin.name}" hook "${hookName}" failed: ${err}`, { cause: err });
193
+ throw new Error(`[clarify] plugin "${plugin.name}" hook "build:assets" failed: ${err}`, { cause: err });
194
+ }
195
+ for (const asset of pluginAssets) {
196
+ const fileName = asset.fileName.replace(/\\/g, "/").replace(/^\/+/, "");
197
+ const previousProducer = producerByFileName.get(fileName);
198
+ if (previousProducer) throw new Error(`[clarify] build asset "${fileName}" is emitted by both plugin "${previousProducer}" and plugin "${plugin.name}"`);
199
+ producerByFileName.set(fileName, plugin.name);
200
+ assets.push({
201
+ ...asset,
202
+ fileName
203
+ });
175
204
  }
176
205
  }
177
- return results;
178
- }
179
- async function runBuildAssetsHooks(plugins, ctx) {
180
- return runCollectorHooks(plugins, "build:assets", ctx);
206
+ return assets;
181
207
  }
182
208
  async function runBuildDoneHooks(plugins, ctx) {
183
209
  for (const plugin of plugins) {
@@ -212,7 +238,7 @@ function createContentProcessor(transform) {
212
238
  async processMdx(source, filePath) {
213
239
  const { frontmatter, content } = parseFrontmatter(source);
214
240
  const transformed = await transformContent(transform, {
215
- kind: "mdx",
241
+ kind: "markdown+jsx",
216
242
  source,
217
243
  filePath,
218
244
  frontmatter,
@@ -364,9 +390,6 @@ function openAPIPagePathFromRef(ref, tagFilter, explicitPath) {
364
390
  function routePathFromRef(ref, explicitPath) {
365
391
  return explicitPath ? normalizePath(explicitPath) : basePathFromRef(ref);
366
392
  }
367
- function virtualModuleIdFromRef(ref) {
368
- return "virtual:clarify-page/" + ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "").replace(/\/+/g, "/");
369
- }
370
393
  function localizedRoutePath(basePath, locale, _i18n) {
371
394
  const normalizedBasePath = normalizePath(basePath);
372
395
  const prefix = normalizePath(locale);
@@ -409,7 +432,7 @@ async function findContentRoutes(dir, base = dir, options = {}) {
409
432
  title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : extractH1(page.content) : lastPart) || "Untitled";
410
433
  }
411
434
  routes.push({
412
- kind: "mdx",
435
+ kind: entry.name.toLowerCase().endsWith(".mdx") ? "markdown+jsx" : "markdown",
413
436
  path: cleanPath,
414
437
  basePath: cleanPath,
415
438
  meta: {
@@ -418,7 +441,10 @@ async function findContentRoutes(dir, base = dir, options = {}) {
418
441
  keywords: frontmatterKeywords(page.frontmatter),
419
442
  sections: extractMdxSections(page.content)
420
443
  },
421
- module: { virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/") },
444
+ module: {
445
+ pageVirtualModuleId: pageVirtualModuleId(relativePath.replace(/\.mdx?$/, "")),
446
+ contentVirtualModuleId: contentVirtualModuleId(fullPath, base)
447
+ },
422
448
  source: {
423
449
  filePath: fullPath,
424
450
  frontmatter: page.frontmatter,
@@ -430,14 +456,24 @@ async function findContentRoutes(dir, base = dir, options = {}) {
430
456
  }
431
457
  return routes;
432
458
  }
433
- function virtualModuleIdForLocalizedRoute(contentRoot, filePath) {
434
- return "virtual:clarify-page/" + relative(contentRoot, filePath).replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "").replace(/\/+/g, "/");
459
+ function pageVirtualModuleIdForLocalizedRoute(contentRoot, filePath) {
460
+ return pageVirtualModuleIdFromRef(relative(contentRoot, filePath));
435
461
  }
436
462
  function frontmatterKeywords(frontmatter) {
437
463
  const value = frontmatter.keywords ?? frontmatter.keyword ?? frontmatter.tags;
438
464
  const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
439
465
  return keywords.length > 0 ? keywords : void 0;
440
466
  }
467
+ function withPageIdentity(route, path) {
468
+ return {
469
+ ...route,
470
+ path,
471
+ module: {
472
+ ...route.module,
473
+ pageVirtualModuleId: pageVirtualModuleId(path)
474
+ }
475
+ };
476
+ }
441
477
  function withAlternates(route, routes, locales) {
442
478
  const basePath = route.basePath ?? route.path;
443
479
  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]));
@@ -463,8 +499,7 @@ function generateBareAliases(routes, locales) {
463
499
  if (bp === route.path || seenBare.has(bp)) continue;
464
500
  seenBare.add(bp);
465
501
  bareRoutes.push({
466
- ...route,
467
- path: bp,
502
+ ...withPageIdentity(route, bp),
468
503
  isBareAlias: true
469
504
  });
470
505
  }
@@ -483,7 +518,11 @@ async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
483
518
  path: localizedRoutePath(basePath, locale.code, locales),
484
519
  basePath,
485
520
  locale: locale.code,
486
- module: { virtualModuleId: virtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath) }
521
+ module: {
522
+ ...route.module,
523
+ pageVirtualModuleId: pageVirtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath),
524
+ contentVirtualModuleId: contentVirtualModuleId(route.source.filePath, contentRoot)
525
+ }
487
526
  });
488
527
  }
489
528
  }
@@ -495,9 +534,9 @@ async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
495
534
  for (const locale of locales.locales) {
496
535
  const key = `${locale.code}:${basePath}`;
497
536
  if (routeByLocaleAndBase.has(key)) continue;
537
+ const path = localizedRoutePath(basePath, locale.code, locales);
498
538
  localizedRoutes.push({
499
- ...sourceRoute,
500
- path: localizedRoutePath(basePath, locale.code, locales),
539
+ ...withPageIdentity(sourceRoute, path),
501
540
  locale: locale.code,
502
541
  isFallback: true
503
542
  });
@@ -592,13 +631,13 @@ function applyConfiguredPageRoutePaths(routes, tabs, locales) {
592
631
  const targetBasePath = routePathFromRef(intent.ref, intent.path);
593
632
  if (sourceBasePath === targetBasePath) continue;
594
633
  for (const route of routes) {
595
- if (route.kind !== "mdx" || (route.basePath ?? route.path) !== sourceBasePath) continue;
634
+ if (route.kind === "openapi" || (route.basePath ?? route.path) !== sourceBasePath) continue;
596
635
  const key = `${route.locale ?? ""}:${targetBasePath}`;
597
636
  if (existingKeys.has(key)) continue;
598
637
  existingKeys.add(key);
638
+ const path = route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath;
599
639
  additions.push({
600
- ...route,
601
- path: route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath,
640
+ ...withPageIdentity(route, path),
602
641
  basePath: targetBasePath
603
642
  });
604
643
  }
@@ -606,7 +645,8 @@ function applyConfiguredPageRoutePaths(routes, tabs, locales) {
606
645
  const nextRoutes = [...routes, ...additions];
607
646
  const routesWithAlternates = locales ? nextRoutes.map((route) => withAlternates(route, nextRoutes, locales)) : nextRoutes;
608
647
  if (!locales) return routesWithAlternates;
609
- return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
648
+ const markdownRoutes = routesWithAlternates.filter((route) => route.kind !== "openapi");
649
+ return [...routesWithAlternates, ...generateBareAliases(markdownRoutes, locales)];
610
650
  }
611
651
  function buildNavigationFromPagesConfig(routes, config) {
612
652
  if (!config || config === "FileTree") return buildNavigation(routes);
@@ -800,7 +840,7 @@ function inferRouteForFile(filePath, contentRoot, defaultLocale) {
800
840
  filePath,
801
841
  locale: fileLocale,
802
842
  basePath: normalizePath(basePath),
803
- kind: isMdx ? "mdx" : "openapi",
843
+ kind: isMdx ? "markdown+jsx" : "openapi",
804
844
  title,
805
845
  inferred: true
806
846
  };
@@ -1219,6 +1259,9 @@ function resolveVirtualId(id) {
1219
1259
  function stripVirtualPrefix(id) {
1220
1260
  return id.startsWith("\0") ? id.slice(1) : id;
1221
1261
  }
1262
+ function virtualContentModuleId(route) {
1263
+ return route.module.contentVirtualModuleId;
1264
+ }
1222
1265
  /**
1223
1266
  * Fixed virtual module IDs (excluding the client entry, which has a special
1224
1267
  * resolved form). Used to avoid hardcoding the constant list in every
@@ -1246,8 +1289,10 @@ function resolveVirtualModuleId(id, modules, routes) {
1246
1289
  for (const virtualId of FIXED_VIRTUAL_IDS) if (id === virtualId || id === resolveVirtualId(virtualId)) return resolveVirtualId(virtualId);
1247
1290
  const moduleId = stripVirtualPrefix(id);
1248
1291
  if (modules.has(moduleId)) return resolveVirtualId(moduleId);
1249
- const route = routes.find((route) => route.module.virtualModuleId === id || route.module.virtualModuleId === moduleId);
1250
- if (route) return resolveVirtualId(route.module.virtualModuleId);
1292
+ const contentRoute = routes.find((route) => (route.kind === "markdown" || route.kind === "markdown+jsx") && route.module.contentVirtualModuleId === moduleId);
1293
+ if (contentRoute) return contentRoute.source.filePath;
1294
+ const route = routes.find((route) => route.module.pageVirtualModuleId === id || route.module.pageVirtualModuleId === moduleId);
1295
+ if (route) return resolveVirtualId(route.module.pageVirtualModuleId);
1251
1296
  return null;
1252
1297
  }
1253
1298
  function generateConfigModule(projectConfig, version) {
@@ -1301,8 +1346,8 @@ function routeToRuntimeManifestEntry(route, component, mode) {
1301
1346
  return entry;
1302
1347
  }
1303
1348
  function generateRoutesModule(routes, navigation, mode = "client") {
1304
- return `${mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.module.virtualModuleId)};`).join("\n") : `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';`}\n\nexport const routes = [\n${routes.map((r, i) => {
1305
- const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.module.virtualModuleId)}).catch((error) => Promise.resolve({ default: createContentDiagnosticComponent({ kind: 'route-load', title: 'This page could not be loaded', message: error instanceof Error ? error.message : String(error), details: error instanceof Error ? error.stack : undefined }) }))`;
1349
+ return `${mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.module.pageVirtualModuleId)};`).join("\n") : `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';`}\n\nexport const routes = [\n${routes.map((r, i) => {
1350
+ const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.module.pageVirtualModuleId)}).catch((error) => Promise.resolve({ default: createContentDiagnosticComponent({ kind: 'route-load', title: 'This page could not be loaded', message: error instanceof Error ? error.message : String(error), details: error instanceof Error ? error.stack : undefined }) }))`;
1306
1351
  const placeholder = `__COMPONENT_${i}__`;
1307
1352
  const manifestEntry = routeToRuntimeManifestEntry(r, placeholder, mode);
1308
1353
  return ` ${JSON.stringify(manifestEntry, null, 0).replace(`"${placeholder}"`, component).replace(/"([A-Za-z_$][\w$]*)":/g, "$1: ")}`;
@@ -1374,13 +1419,14 @@ function buildVirtualModules(args) {
1374
1419
  modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, "client"));
1375
1420
  modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, "server"));
1376
1421
  modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
1377
- modules.set(VIRTUAL_OPENAPI, "export const openApiSpecs = {};");
1378
1422
  modules.set(VIRTUAL_SLOT, "export { useSlot } from '@clarify-labs/renderer';\n");
1379
- modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
1423
+ modules.set(VIRTUAL_OPENAPI, "export const openApiSpecs = {};");
1380
1424
  modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
1425
+ modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
1381
1426
  for (const route of args.routes) {
1382
- const moduleContent = route.diagnostic ? generateContentDiagnosticModule(route.diagnostic) : `export { default } from ${moduleSpecifier(route.source.filePath)};`;
1383
- modules.set(route.module.virtualModuleId, moduleContent);
1427
+ const moduleContent = route.diagnostic ? generateContentDiagnosticModule(route.diagnostic) : route.kind === "markdown" || route.kind === "markdown+jsx" ? `export { default } from ${moduleSpecifier(virtualContentModuleId(route))};` : null;
1428
+ if (moduleContent === null) continue;
1429
+ modules.set(route.module.pageVirtualModuleId, moduleContent);
1384
1430
  }
1385
1431
  return modules;
1386
1432
  }
@@ -1421,7 +1467,7 @@ function createNormalizedContentPlugin(engine) {
1421
1467
  transform(_code, id) {
1422
1468
  if (!/\.mdx?(?:\?|$)/.test(id)) return null;
1423
1469
  const filePath = id.replace(/\?.*$/, "");
1424
- const route = engine.routes.find((route) => route.kind === "mdx" && route.source.filePath === filePath);
1470
+ const route = engine.routes.find((route) => (route.kind === "markdown+jsx" || route.kind === "markdown") && route.source.filePath === filePath);
1425
1471
  if (!route || route.source.content === void 0) return null;
1426
1472
  return {
1427
1473
  code: route.source.content,
@@ -1536,7 +1582,10 @@ function createClarifyViteCorePlugin(engine, normalizedContentPlugin, markdown,
1536
1582
  };
1537
1583
  server.watcher.on("add", handleContentTreeChange);
1538
1584
  server.watcher.on("unlink", handleContentTreeChange);
1539
- await engine.configureDevServer(server);
1585
+ const postHooks = await engine.configureDevServer(server);
1586
+ if (postHooks.length > 0) return () => {
1587
+ for (const postHook of postHooks) postHook();
1588
+ };
1540
1589
  },
1541
1590
  transformIndexHtml: {
1542
1591
  order: "pre",
@@ -1591,7 +1640,7 @@ function createViteAdapter(engine) {
1591
1640
  const errorPolicy = () => contentCompileErrorPolicy;
1592
1641
  const normalizedContentPlugin = createNormalizedContentPlugin(engine);
1593
1642
  const markdown = createContentCompilerPlugin("markdown", engine.root, errorPolicy);
1594
- const mdx = createContentCompilerPlugin("mdx", engine.root, errorPolicy);
1643
+ const mdx = createContentCompilerPlugin("markdown+jsx", engine.root, errorPolicy);
1595
1644
  const compilePolicyPlugin = {
1596
1645
  name: "clarify:content-compile-policy",
1597
1646
  configResolved(config) {
@@ -1611,7 +1660,7 @@ function createViteAdapter(engine) {
1611
1660
  }
1612
1661
  var cliPackageVersion = {
1613
1662
  name: "@clarify-labs/cli",
1614
- version: "0.11.4",
1663
+ version: "0.11.5",
1615
1664
  license: "AGPL-3.0-only",
1616
1665
  type: "module",
1617
1666
  repository: {
@@ -1711,7 +1760,7 @@ function withUtf8Signature(content) {
1711
1760
  return `${UTF8_SIGNATURE}${content}`;
1712
1761
  }
1713
1762
  function shouldUseUtf8Signature(route) {
1714
- if (route.kind === "mdx") return true;
1763
+ if (route.kind === "markdown+jsx" || route.kind === "markdown") return true;
1715
1764
  return false;
1716
1765
  }
1717
1766
  function routeToMarkdownArtifactUrl(routePath) {
@@ -1783,9 +1832,9 @@ function createLlmsTxt(routes, projectConfig) {
1783
1832
  const lines = [`# ${projectConfig.title}`, ""];
1784
1833
  if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
1785
1834
  lines.push("This file lists the source-ready Markdown and OpenAPI artifacts for this documentation site.", "");
1786
- const mdxRoutes = routes.filter((route) => route.kind === "mdx" && isLlmsTxtRoute(route) && !route.isBareAlias);
1787
- if (mdxRoutes.length > 0) {
1788
- const localizedGroups = groupLlmsTxtRoutesByLocale(mdxRoutes, projectConfig);
1835
+ const markdownRoutes = routes.filter((route) => (route.kind === "markdown+jsx" || route.kind === "markdown") && isLlmsTxtRoute(route) && !route.isBareAlias);
1836
+ if (markdownRoutes.length > 0) {
1837
+ const localizedGroups = groupLlmsTxtRoutesByLocale(markdownRoutes, projectConfig);
1789
1838
  for (const [locale, localeRoutes] of localizedGroups) {
1790
1839
  lines.push(`## ${llmsTxtDocsSectionTitle(locale, localizedGroups.size, projectConfig)}`);
1791
1840
  for (const route of localeRoutes) {
@@ -1862,10 +1911,12 @@ function createContentArtifactsPlugin() {
1862
1911
  return input;
1863
1912
  },
1864
1913
  "dev:configureServer": (server, ctx) => {
1865
- server.middlewares.use((req, res, next) => {
1866
- if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
1867
- next();
1868
- });
1914
+ return () => {
1915
+ server.middlewares.use((req, res, next) => {
1916
+ if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
1917
+ next();
1918
+ });
1919
+ };
1869
1920
  },
1870
1921
  "build:assets": (ctx) => {
1871
1922
  const assets = [];
@@ -2356,7 +2407,7 @@ function findOpenAPIRoutes(dir, base = dir) {
2356
2407
  basePath: cleanPath,
2357
2408
  kind: "openapi",
2358
2409
  meta: { title: kebabToTitle(cleanPath.split("/").pop() ?? "API") },
2359
- module: { virtualModuleId: virtualModuleIdFromRef(ref) },
2410
+ module: { pageVirtualModuleId: pageVirtualModuleIdFromRef(ref) },
2360
2411
  source: { filePath: fullPath }
2361
2412
  });
2362
2413
  }
@@ -2480,34 +2531,7 @@ async function readOpenAPISpec(filePath, contentProcessor, projectRoot) {
2480
2531
  }
2481
2532
  }
2482
2533
  //#endregion
2483
- //#region source/plugins/openapi/virtual-modules.ts
2484
- var openApiRegistryModuleId = VIRTUAL_OPENAPI;
2485
- var openApiServerRegistryModuleId = VIRTUAL_OPENAPI_SERVER;
2486
- var OPENAPI_SPEC_PREFIX = "virtual:clarify/openapi-spec/";
2487
- function specVirtualModuleId(specKey) {
2488
- return `${OPENAPI_SPEC_PREFIX}${specKey}`;
2489
- }
2490
- function generateOpenAPIRegistryModule(openApiSpecs) {
2491
- return `export const openApiSpecs = {\n${Object.entries(openApiSpecs).map(([key, moduleId]) => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(moduleId)}),`).join("\n")}\n};`;
2492
- }
2493
- function generateOpenAPIServerRegistryModule(openApiSpecs) {
2494
- return `export const openApiSpecs = ${JSON.stringify(openApiSpecs)};`;
2495
- }
2496
- /** Generate a virtual module that exports a single spec as default. */
2497
- function generateOpenAPISpecModule(spec) {
2498
- return `export default ${JSON.stringify(spec)};`;
2499
- }
2500
- function generateOpenAPIPageModule(opts) {
2501
- return `import { createOpenApiRouteComponent } from '@clarify-labs/renderer';
2502
- import spec from ${JSON.stringify(opts.specModuleId)};
2503
-
2504
- export const routeData = {};
2505
-
2506
- export default createOpenApiRouteComponent({ ...routeData, spec });
2507
- `;
2508
- }
2509
- //#endregion
2510
- //#region source/plugins/openapi/index.ts
2534
+ //#region source/plugins/openapi/routes.ts
2511
2535
  function collectOpenAPIPageIntents(config) {
2512
2536
  const intents = [];
2513
2537
  function visitItems(items) {
@@ -2527,9 +2551,6 @@ function collectOpenAPIPageIntents(config) {
2527
2551
  for (const tab of config.navigation?.tabs ?? []) visitPages(tab.pages);
2528
2552
  return intents;
2529
2553
  }
2530
- function virtualModuleIdFromPath(path) {
2531
- return `virtual:clarify-page/${path.replace(/^\/+/, "") || "index"}`;
2532
- }
2533
2554
  function cloneOpenAPIRoute(route, overrides = {}) {
2534
2555
  return {
2535
2556
  ...route,
@@ -2547,7 +2568,7 @@ function cloneOpenAPIRoute(route, overrides = {}) {
2547
2568
  ...overrides
2548
2569
  };
2549
2570
  }
2550
- function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
2571
+ function discoverOpenAPIRoutes(contentRoot, locales) {
2551
2572
  if (!locales) return findOpenAPIRoutes(contentRoot);
2552
2573
  const localizedRoutes = [];
2553
2574
  for (const locale of locales.locales) {
@@ -2560,31 +2581,30 @@ function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
2560
2581
  path,
2561
2582
  basePath,
2562
2583
  locale: locale.code,
2563
- module: { virtualModuleId: virtualModuleIdFromPath(path) }
2584
+ module: { pageVirtualModuleId: pageVirtualModuleId(path) }
2564
2585
  });
2565
2586
  }
2566
2587
  }
2567
- if (locales.missing === "fallback") {
2568
- const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
2569
- const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
2570
- for (const sourceRoute of defaultRoutes) {
2571
- const basePath = sourceRoute.basePath ?? sourceRoute.path;
2572
- for (const locale of locales.locales) {
2573
- const key = `${locale.code}:${basePath}`;
2574
- if (routeByLocaleAndBase.has(key)) continue;
2575
- const path = localizedRoutePath(basePath, locale.code, locales);
2576
- localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
2577
- path,
2578
- locale: locale.code,
2579
- module: { virtualModuleId: virtualModuleIdFromPath(path) },
2580
- isFallback: true
2581
- }));
2582
- }
2588
+ if (locales.missing !== "fallback") return localizedRoutes;
2589
+ const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
2590
+ const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
2591
+ for (const sourceRoute of defaultRoutes) {
2592
+ const basePath = sourceRoute.basePath ?? sourceRoute.path;
2593
+ for (const locale of locales.locales) {
2594
+ const key = `${locale.code}:${basePath}`;
2595
+ if (routeByLocaleAndBase.has(key)) continue;
2596
+ const path = localizedRoutePath(basePath, locale.code, locales);
2597
+ localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
2598
+ path,
2599
+ locale: locale.code,
2600
+ module: { pageVirtualModuleId: pageVirtualModuleId(path) },
2601
+ isFallback: true
2602
+ }));
2583
2603
  }
2584
2604
  }
2585
2605
  return localizedRoutes;
2586
2606
  }
2587
- function createConfiguredOpenAPIRoutes(routes, config) {
2607
+ function expandConfiguredOpenAPIRoutes(routes, config) {
2588
2608
  const openAPIIntents = collectOpenAPIPageIntents(config);
2589
2609
  if (!openAPIIntents.length) return routes;
2590
2610
  const additions = [];
@@ -2603,7 +2623,7 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2603
2623
  additions.push(cloneOpenAPIRoute(route, {
2604
2624
  path,
2605
2625
  basePath: targetBasePath,
2606
- module: { virtualModuleId: virtualModuleIdFromPath(path) },
2626
+ module: { pageVirtualModuleId: pageVirtualModuleId(path) },
2607
2627
  openapi: {
2608
2628
  ...route.openapi,
2609
2629
  tagFilter: tagFilter?.length ? tagFilter : void 0
@@ -2617,19 +2637,47 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2617
2637
  const bareAliases = [];
2618
2638
  const seenBare = new Set(routesWithAlternates.map((route) => route.path));
2619
2639
  for (const route of routesWithAlternates) {
2620
- if (route.locale !== config.locales.default) continue;
2640
+ if (route.kind !== "openapi" || route.locale !== config.locales.default) continue;
2621
2641
  const basePath = route.basePath ?? route.path;
2622
2642
  if (basePath === route.path || seenBare.has(basePath)) continue;
2623
2643
  seenBare.add(basePath);
2624
2644
  bareAliases.push(cloneOpenAPIRoute(route, {
2625
2645
  path: basePath,
2626
- module: { virtualModuleId: virtualModuleIdFromPath(basePath) },
2646
+ module: { pageVirtualModuleId: pageVirtualModuleId(basePath) },
2627
2647
  isBareAlias: true
2628
2648
  }));
2629
2649
  }
2630
2650
  return [...routesWithAlternates, ...bareAliases];
2631
2651
  }
2632
- /** Derive a stable, deduplicated id from an absolute spec file path. */
2652
+ //#endregion
2653
+ //#region source/plugins/openapi/virtual-modules.ts
2654
+ var openApiRegistryModuleId = VIRTUAL_OPENAPI;
2655
+ var openApiServerRegistryModuleId = VIRTUAL_OPENAPI_SERVER;
2656
+ var OPENAPI_SPEC_PREFIX = "virtual:clarify/openapi-spec/";
2657
+ function specVirtualModuleId(specKey) {
2658
+ return `${OPENAPI_SPEC_PREFIX}${specKey}`;
2659
+ }
2660
+ function generateOpenAPIRegistryModule(openApiSpecs) {
2661
+ return `export const openApiSpecs = {\n${Object.entries(openApiSpecs).map(([key, moduleId]) => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(moduleId)}),`).join("\n")}\n};`;
2662
+ }
2663
+ function generateOpenAPIServerRegistryModule(openApiSpecs) {
2664
+ return `export const openApiSpecs = ${JSON.stringify(openApiSpecs)};`;
2665
+ }
2666
+ /** Generate a virtual module that exports a single spec as default. */
2667
+ function generateOpenAPISpecModule(spec) {
2668
+ return `export default ${JSON.stringify(spec)};`;
2669
+ }
2670
+ function generateOpenAPIPageModule(opts) {
2671
+ return `import { createOpenApiRouteComponent } from '@clarify-labs/renderer';
2672
+ import spec from ${JSON.stringify(opts.specModuleId)};
2673
+
2674
+ export const routeData = {};
2675
+
2676
+ export default createOpenApiRouteComponent({ ...routeData, spec });
2677
+ `;
2678
+ }
2679
+ //#endregion
2680
+ //#region source/plugins/openapi/state.ts
2633
2681
  function sourceSpecIdFromPath(filePath, projectRoot) {
2634
2682
  return relative(projectRoot, filePath).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
2635
2683
  }
@@ -2641,90 +2689,94 @@ function openAPIRegistryKeys(route) {
2641
2689
  const keys = /* @__PURE__ */ new Set();
2642
2690
  const path = (route.path || "/").replace(/^\//, "");
2643
2691
  const base = (route.basePath ?? route.path).replace(/^\//, "");
2644
- if (route.locale) keys.add(`virtual:clarify-page/${route.locale}/${base}`);
2645
- if (path) keys.add(`virtual:clarify-page/${path}`);
2646
- else keys.add("virtual:clarify-page/index");
2647
- if (!route.locale && base) keys.add(`virtual:clarify-page/${base}`);
2692
+ if (route.locale) keys.add(pageVirtualModuleId(`${route.locale}/${base}`));
2693
+ keys.add(pageVirtualModuleId(path));
2694
+ if (!route.locale && base) keys.add(pageVirtualModuleId(base));
2648
2695
  return [...keys];
2649
2696
  }
2697
+ var OpenAPIPluginState = class {
2698
+ specModules = /* @__PURE__ */ new Map();
2699
+ async enrichRoutes(routes, ctx) {
2700
+ this.specModules.clear();
2701
+ const specByFilePath = /* @__PURE__ */ new Map();
2702
+ for (const route of routes) {
2703
+ if (route.kind !== "openapi") continue;
2704
+ const specFromCache = specByFilePath.get(route.source.filePath);
2705
+ const processor = createProjectContentProcessor(ctx.plugins, ctx);
2706
+ const result = specFromCache ? {
2707
+ ok: true,
2708
+ spec: specFromCache
2709
+ } : await readOpenAPISpec(route.source.filePath, processor, ctx.generateOptions.projectRoot);
2710
+ if (!result.ok) {
2711
+ route.diagnostic = result.diagnostic;
2712
+ route.meta.title = route.meta.title || "OpenAPI parse error";
2713
+ route.meta.sections = [];
2714
+ continue;
2715
+ }
2716
+ const spec = normalizeOpenAPISpecSectionIds(result.spec);
2717
+ specByFilePath.set(route.source.filePath, spec);
2718
+ const sourceSpecId = sourceSpecIdFromPath(route.source.filePath, ctx.generateOptions.projectRoot);
2719
+ route.openapi = {
2720
+ ...route.openapi,
2721
+ sourceSpecId
2722
+ };
2723
+ route.meta.title = spec.info?.title ?? route.meta.title;
2724
+ route.meta.description = spec.info?.description ?? route.meta.description;
2725
+ route.meta.sections = extractOpenAPISections(spec, route.openapi.tagFilter);
2726
+ const pageSpec = route.openapi.tagFilter?.length ? filterSpecByTags(spec, route.openapi.tagFilter) : spec;
2727
+ const routeSpecModuleId = routeSpecModuleIdFromSourceSpecId(sourceSpecId, route.openapi.tagFilter);
2728
+ route.openapi.routeSpecModuleId = routeSpecModuleId;
2729
+ this.specModules.set(routeSpecModuleId, pageSpec);
2730
+ route.source.content = JSON.stringify(pageSpec);
2731
+ }
2732
+ return routes;
2733
+ }
2734
+ contributeModules(modules, routes) {
2735
+ const registryEntries = {};
2736
+ const serverRegistryEntries = {};
2737
+ for (const route of routes) {
2738
+ if (route.kind !== "openapi" || !route.openapi?.sourceSpecId) continue;
2739
+ const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2740
+ const spec = this.specModules.get(routeSpecModuleId);
2741
+ if (!spec) continue;
2742
+ for (const registryKey of openAPIRegistryKeys(route)) {
2743
+ registryEntries[registryKey] = specVirtualModuleId(routeSpecModuleId);
2744
+ serverRegistryEntries[registryKey] = spec;
2745
+ }
2746
+ }
2747
+ modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(registryEntries));
2748
+ modules.set(openApiServerRegistryModuleId, generateOpenAPIServerRegistryModule(serverRegistryEntries));
2749
+ for (const [routeSpecModuleId, spec] of this.specModules) modules.set(specVirtualModuleId(routeSpecModuleId), generateOpenAPISpecModule(spec));
2750
+ for (const route of routes) {
2751
+ if (route.kind !== "openapi") continue;
2752
+ if (route.diagnostic) {
2753
+ modules.set(route.module.pageVirtualModuleId, generateContentDiagnosticModule(route.diagnostic));
2754
+ continue;
2755
+ }
2756
+ if (!route.openapi?.sourceSpecId) continue;
2757
+ const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2758
+ if (!this.specModules.has(routeSpecModuleId)) continue;
2759
+ modules.set(route.module.pageVirtualModuleId, generateOpenAPIPageModule({ specModuleId: specVirtualModuleId(routeSpecModuleId) }));
2760
+ }
2761
+ return modules;
2762
+ }
2763
+ };
2764
+ //#endregion
2765
+ //#region source/plugins/openapi/index.ts
2650
2766
  function createOpenAPIPlugin() {
2651
- /** specs keyed by sourceSpecId (deduplicated per source file). */
2652
- const specs = /* @__PURE__ */ new Map();
2653
- const specModules = /* @__PURE__ */ new Map();
2767
+ const state = new OpenAPIPluginState();
2654
2768
  return {
2655
2769
  name: "clarify:openapi",
2656
2770
  hooks: {
2657
2771
  "routes:discover": (input, ctx) => ({
2658
2772
  ...input,
2659
- routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.locales)]
2773
+ routes: [...input.routes, ...discoverOpenAPIRoutes(input.contentRoot, ctx.projectConfig.locales)]
2660
2774
  }),
2661
2775
  "routes:discovered": async (routes, ctx) => {
2662
- specs.clear();
2663
- specModules.clear();
2664
- const nextRoutes = createConfiguredOpenAPIRoutes(routes, ctx.projectConfig);
2665
- const specByFilePath = /* @__PURE__ */ new Map();
2666
- for (const route of nextRoutes.filter((route) => route.kind === "openapi")) {
2667
- const specFromCache = specByFilePath.get(route.source.filePath);
2668
- const processor = createProjectContentProcessor(ctx.plugins, ctx);
2669
- const result = specFromCache ? {
2670
- ok: true,
2671
- spec: specFromCache
2672
- } : await readOpenAPISpec(route.source.filePath, processor, ctx.generateOptions.projectRoot);
2673
- if (!result.ok) {
2674
- route.diagnostic = result.diagnostic;
2675
- route.meta.title = route.meta.title || "OpenAPI parse error";
2676
- route.meta.sections = [];
2677
- continue;
2678
- }
2679
- const spec = normalizeOpenAPISpecSectionIds(result.spec);
2680
- specByFilePath.set(route.source.filePath, spec);
2681
- const sourceSpecId = sourceSpecIdFromPath(route.source.filePath, ctx.generateOptions.projectRoot);
2682
- route.openapi = {
2683
- ...route.openapi,
2684
- sourceSpecId
2685
- };
2686
- specs.set(sourceSpecId, {
2687
- spec,
2688
- filePath: route.source.filePath
2689
- });
2690
- route.meta.title = spec.info?.title ?? route.meta.title;
2691
- route.meta.description = spec.info?.description ?? route.meta.description;
2692
- route.meta.sections = extractOpenAPISections(spec, route.openapi?.tagFilter);
2693
- const pageSpec = route.openapi?.tagFilter?.length ? filterSpecByTags(spec, route.openapi.tagFilter) : spec;
2694
- const routeSpecModuleId = routeSpecModuleIdFromSourceSpecId(sourceSpecId, route.openapi?.tagFilter);
2695
- route.openapi.routeSpecModuleId = routeSpecModuleId;
2696
- specModules.set(routeSpecModuleId, pageSpec);
2697
- route.source.content = JSON.stringify(pageSpec);
2698
- }
2699
- return nextRoutes;
2776
+ const expandedRoutes = expandConfiguredOpenAPIRoutes(routes, ctx.projectConfig);
2777
+ return state.enrichRoutes(expandedRoutes, ctx);
2700
2778
  },
2701
- "modules:before": (modules, ctx) => {
2702
- const registryEntries = {};
2703
- const serverRegistryEntries = {};
2704
- for (const route of ctx.routes) {
2705
- if (route.kind !== "openapi" || !route.openapi?.sourceSpecId) continue;
2706
- const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2707
- const spec = specModules.get(routeSpecModuleId) ?? specs.get(route.openapi.sourceSpecId)?.spec;
2708
- if (!spec) continue;
2709
- for (const registryKey of openAPIRegistryKeys(route)) {
2710
- registryEntries[registryKey] = specVirtualModuleId(routeSpecModuleId);
2711
- serverRegistryEntries[registryKey] = spec;
2712
- }
2713
- }
2714
- modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(registryEntries));
2715
- modules.set(openApiServerRegistryModuleId, generateOpenAPIServerRegistryModule(serverRegistryEntries));
2716
- for (const [routeSpecModuleId, spec] of specModules) modules.set(specVirtualModuleId(routeSpecModuleId), generateOpenAPISpecModule(spec));
2717
- for (const route of ctx.routes) {
2718
- if (route.kind !== "openapi") continue;
2719
- if (route.diagnostic) modules.set(route.module.virtualModuleId, generateContentDiagnosticModule(route.diagnostic));
2720
- else if (route.openapi?.sourceSpecId) {
2721
- const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2722
- if (!specModules.has(routeSpecModuleId)) continue;
2723
- modules.set(route.module.virtualModuleId, generateOpenAPIPageModule({ specModuleId: specVirtualModuleId(routeSpecModuleId) }));
2724
- }
2725
- }
2726
- return modules;
2727
- }
2779
+ "modules:before": (modules, ctx) => state.contributeModules(modules, ctx.routes)
2728
2780
  }
2729
2781
  };
2730
2782
  }
@@ -3133,6 +3185,145 @@ async function resolveProjectContext(options = {}, env = {
3133
3185
  };
3134
3186
  }
3135
3187
  //#endregion
3188
+ //#region source/core/routing/route-analysis.ts
3189
+ function routeDescription(route) {
3190
+ return `${route.kind} ${route.source.filePath}`;
3191
+ }
3192
+ function collectConflicts(routes, keyOf) {
3193
+ const grouped = /* @__PURE__ */ new Map();
3194
+ for (const route of routes) {
3195
+ const key = keyOf(route);
3196
+ grouped.set(key, [...grouped.get(key) ?? [], route]);
3197
+ }
3198
+ return grouped;
3199
+ }
3200
+ function describeRouteConflict(route) {
3201
+ return routeDescription(route);
3202
+ }
3203
+ function findRouteConflicts(routes) {
3204
+ const conflicts = [];
3205
+ for (const [path, pathRoutes] of collectConflicts(routes, (route) => route.path)) {
3206
+ if (pathRoutes.length < 2) continue;
3207
+ conflicts.push({
3208
+ kind: "path",
3209
+ key: path,
3210
+ label: `path ${path}`,
3211
+ routes: pathRoutes
3212
+ });
3213
+ }
3214
+ for (const [pageModuleId, moduleRoutes] of collectConflicts(routes, (route) => route.module.pageVirtualModuleId)) {
3215
+ if (moduleRoutes.length < 2) continue;
3216
+ if (moduleRoutes.every((route) => route.path === moduleRoutes[0].path)) continue;
3217
+ conflicts.push({
3218
+ kind: "page-module",
3219
+ key: pageModuleId,
3220
+ label: `page module ${pageModuleId}`,
3221
+ routes: moduleRoutes
3222
+ });
3223
+ }
3224
+ return conflicts;
3225
+ }
3226
+ function formatRouteConflicts(conflicts) {
3227
+ return conflicts.map((conflict) => `- ${conflict.label}: ${conflict.routes.map(describeRouteConflict).join(", ")}`).join("\n");
3228
+ }
3229
+ //#endregion
3230
+ //#region source/core/routing/route-resolution.ts
3231
+ async function resolveRoutePages(routes, plugins, ctx) {
3232
+ const routeToken = Symbol("clarifyRouteToken");
3233
+ const resolvedPages = await runHooks(plugins, "pages:resolved", routes.map((route, routeIndex) => ({
3234
+ path: route.path,
3235
+ filePath: route.source.filePath,
3236
+ frontmatter: route.source.frontmatter ?? {},
3237
+ content: route.source.content ?? "",
3238
+ [routeToken]: routeIndex
3239
+ })), ctx);
3240
+ const pageByRouteIndex = /* @__PURE__ */ new Map();
3241
+ for (const page of resolvedPages) {
3242
+ const routeIndex = page[routeToken];
3243
+ if (!Number.isInteger(routeIndex) || routeIndex < 0 || routeIndex >= routes.length) throw new Error("[clarify] pages:resolved hooks must preserve each page route identity");
3244
+ if (pageByRouteIndex.has(routeIndex)) throw new Error("[clarify] pages:resolved hooks returned the same page route identity more than once");
3245
+ pageByRouteIndex.set(routeIndex, page);
3246
+ }
3247
+ if (pageByRouteIndex.size !== routes.length) throw new Error("[clarify] pages:resolved hooks must return exactly one page for each route");
3248
+ return routes.map((route, routeIndex) => {
3249
+ const page = pageByRouteIndex.get(routeIndex);
3250
+ return {
3251
+ ...route,
3252
+ source: {
3253
+ ...route.source,
3254
+ frontmatter: page.frontmatter,
3255
+ content: page.content
3256
+ }
3257
+ };
3258
+ });
3259
+ }
3260
+ function buildNavigationTree(routes, ctx) {
3261
+ const tabs = ctx.projectConfig.navigation?.tabs;
3262
+ if (!tabs) return {
3263
+ kind: "flat",
3264
+ nodes: buildNavigation(routes)
3265
+ };
3266
+ if (!ctx.projectConfig.locales) return buildNavigationFromTabsConfig(routes, tabs);
3267
+ return buildLocalizedNavigationFromTabsConfig(routes, tabs, ctx.projectConfig.locales) ?? {
3268
+ kind: "localized-tabbed",
3269
+ locales: {}
3270
+ };
3271
+ }
3272
+ function assertNoRouteConflicts(routes) {
3273
+ const conflicts = findRouteConflicts(routes);
3274
+ if (conflicts.length > 0) throw new Error(`[clarify] Route conflicts prevent the site from being built:\n${formatRouteConflicts(conflicts)}`);
3275
+ }
3276
+ function createRouteConflictDiagnostic(label, routes) {
3277
+ return {
3278
+ kind: "route-load",
3279
+ title: "Route conflict",
3280
+ message: `This page cannot be rendered because ${label} is used by multiple routes.`,
3281
+ details: routes.map(describeRouteConflict).join("\n")
3282
+ };
3283
+ }
3284
+ function createDevRouteConflictRoutes(routes) {
3285
+ const conflicts = findRouteConflicts(routes);
3286
+ if (conflicts.length === 0) return routes;
3287
+ const conflictedRoutes = /* @__PURE__ */ new Map();
3288
+ for (const conflict of conflicts) for (const route of conflict.routes) conflictedRoutes.set(route, conflict);
3289
+ function createConflictRoute(route, conflict) {
3290
+ return {
3291
+ ...route,
3292
+ module: {
3293
+ ...route.module,
3294
+ pageVirtualModuleId: `${route.module.pageVirtualModuleId}__conflict`
3295
+ },
3296
+ diagnostic: createRouteConflictDiagnostic(conflict.label, conflict.routes)
3297
+ };
3298
+ }
3299
+ const usedPaths = /* @__PURE__ */ new Set();
3300
+ return routes.flatMap((route) => {
3301
+ const conflict = conflictedRoutes.get(route);
3302
+ if (!conflict) return [route];
3303
+ if (usedPaths.has(route.path)) return [];
3304
+ usedPaths.add(route.path);
3305
+ return [createConflictRoute(route, conflict)];
3306
+ });
3307
+ }
3308
+ function handleRouteConflicts(routes, development) {
3309
+ if (development) return createDevRouteConflictRoutes(routes);
3310
+ assertNoRouteConflicts(routes);
3311
+ return routes;
3312
+ }
3313
+ async function resolveRouteState(routes, plugins, ctx, development) {
3314
+ const tabs = ctx.projectConfig.navigation?.tabs;
3315
+ const configuredRoutes = applyConfiguredPageRoutePaths(routes, tabs, ctx.projectConfig.locales);
3316
+ const resolved = await runHooks(plugins, "routes:resolved", {
3317
+ routes: configuredRoutes,
3318
+ navigation: buildNavigationTree(configuredRoutes, ctx)
3319
+ }, ctx);
3320
+ const finalRoutes = handleRouteConflicts(resolved.routes, development);
3321
+ return {
3322
+ routes: finalRoutes,
3323
+ navigation: finalRoutes === resolved.routes ? resolved.navigation : buildNavigationTree(finalRoutes, ctx)
3324
+ };
3325
+ }
3326
+ //#endregion
3136
3327
  //#region source/core/runtime/env-types.ts
3137
3328
  var GENERATED_FILE_NAME = "clarify-env.d.ts";
3138
3329
  /**
@@ -3673,7 +3864,7 @@ var ClarifyEngine = class {
3673
3864
  async discoverSite() {
3674
3865
  const plugins = this.ctx.plugins;
3675
3866
  const contentRoot = this.ctx.contentRoot;
3676
- const { locales, navigation: navigationConfig } = this.ctx.projectConfig;
3867
+ const { locales } = this.ctx.projectConfig;
3677
3868
  let routes = await runPhase(plugins, "site:discover", this.ctx, async () => {
3678
3869
  const options = { contentProcessor: createProjectContentProcessor(plugins, this.ctx) };
3679
3870
  const discovered = locales ? await findLocalizedContentRoutes(contentRoot, locales, options) : await findContentRoutes(contentRoot, contentRoot, options);
@@ -3681,43 +3872,10 @@ var ClarifyEngine = class {
3681
3872
  contentRoot,
3682
3873
  routes: discovered
3683
3874
  }, this.ctx);
3684
- return runHooks(plugins, "routes:discovered", result.routes, this.ctx);
3875
+ return await runHooks(plugins, "routes:discovered", result.routes, this.ctx);
3685
3876
  });
3686
- routes = await runPhase(plugins, "content:process", this.ctx, async () => {
3687
- const pages = routes.map((route) => ({
3688
- path: route.path,
3689
- filePath: route.source.filePath,
3690
- frontmatter: route.source.frontmatter ?? {},
3691
- content: route.source.content ?? ""
3692
- }));
3693
- const resolvedPages = await runHooks(plugins, "pages:resolved", pages, this.ctx);
3694
- const pageByPath = new Map(resolvedPages.map((p) => [p.path, p]));
3695
- return routes.map((route) => {
3696
- const page = pageByPath.get(route.path);
3697
- if (!page) return route;
3698
- return {
3699
- ...route,
3700
- source: {
3701
- ...route.source,
3702
- frontmatter: page.frontmatter,
3703
- content: page.content
3704
- }
3705
- };
3706
- });
3707
- });
3708
- const tabs = navigationConfig?.tabs;
3709
- routes = applyConfiguredPageRoutePaths(routes, tabs, locales);
3710
- const navigation = tabs ? locales ? buildLocalizedNavigationFromTabsConfig(routes, tabs, locales) ?? {
3711
- kind: "localized-tabbed",
3712
- locales: {}
3713
- } : buildNavigationFromTabsConfig(routes, tabs) : {
3714
- kind: "flat",
3715
- nodes: buildNavigation(routes)
3716
- };
3717
- const resolved = await runHooks(plugins, "routes:resolved", {
3718
- routes,
3719
- navigation
3720
- }, this.ctx);
3877
+ routes = await runPhase(plugins, "content:process", this.ctx, () => resolveRoutePages(routes, plugins, this.ctx));
3878
+ const resolved = await resolveRouteState(routes, plugins, this.ctx, this.runtime.command === "serve");
3721
3879
  this.ctx.routes = resolved.routes;
3722
3880
  this.ctx.navigation = resolved.navigation;
3723
3881
  }
@@ -3767,8 +3925,8 @@ var ClarifyEngine = class {
3767
3925
  return this.buildEnabled;
3768
3926
  }
3769
3927
  async configureDevServer(server) {
3770
- await runPhase(this.plugins, "dev:server", this.ctx, async () => {
3771
- await runDevConfigureServerHooks(this.plugins, server, this.ctx);
3928
+ return runPhase(this.plugins, "dev:server", this.ctx, () => {
3929
+ return runDevConfigureServerHooks(this.plugins, server, this.ctx);
3772
3930
  });
3773
3931
  }
3774
3932
  async runBuildDone() {
@@ -3839,6 +3997,6 @@ function createClarifyEngine(options = {}) {
3839
3997
  return new ClarifyEngine(options);
3840
3998
  }
3841
3999
  //#endregion
3842
- 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 };
4000
+ export { createClarifyTempDir as a, cliPackageVersion as c, defineConfig as d, findRouteConflicts as i, cliPackageVersionWithCaret as l, createClarifyEngine as n, deserializeSearchIndex as o, createClarifyRuntimeAliases as r, searchMcpIndex as s, ClarifyEngine as t, createViteAdapter as u };
3843
4001
 
3844
- //# sourceMappingURL=engine-CJmXVTo5.js.map
4002
+ //# sourceMappingURL=engine-DuiJVdO8.js.map