@clarify-labs/cli 0.11.4 → 0.11.6

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.
@@ -112,7 +112,7 @@ var rehypePlugins = [
112
112
  ];
113
113
  function createContentCompileDiagnostic(options) {
114
114
  const { format, phase, error, filePath, projectRoot } = options;
115
- const label = format === "mdx" ? "MDX" : "Markdown";
115
+ const label = format === "markdown+jsx" ? "MDX" : "Markdown";
116
116
  return createContentDiagnostic({
117
117
  kind: format,
118
118
  title: `${label} ${phase} error`,
@@ -158,7 +158,7 @@ async function compileMdxContent(content, options = {}) {
158
158
  return {
159
159
  ok: false,
160
160
  diagnostic: createContentCompileDiagnostic({
161
- format: "mdx",
161
+ format: "markdown+jsx",
162
162
  phase: "syntax",
163
163
  error,
164
164
  filePath,
@@ -168,6 +168,21 @@ async function compileMdxContent(content, options = {}) {
168
168
  }
169
169
  }
170
170
  //#endregion
171
+ //#region source/core/runtime/module-ids.ts
172
+ var PAGE_PREFIX = "virtual:clarify-page/";
173
+ var CONTENT_PREFIX = "virtual:clarify-content/";
174
+ function pageVirtualModuleId(path) {
175
+ return `${PAGE_PREFIX}${path.replace(/^\/+/, "").replace(/\/index$/, "").replace(/\/+/g, "/") || "index"}`;
176
+ }
177
+ function pageVirtualModuleIdFromRef(ref) {
178
+ return pageVirtualModuleId(ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, ""));
179
+ }
180
+ function contentVirtualModuleId(filePath, contentRoot) {
181
+ const sourcePath = (0, node_path.relative)(contentRoot, filePath).replace(/\\/g, "/");
182
+ if (!sourcePath || sourcePath.startsWith("../") || (0, node_path.isAbsolute)(sourcePath)) throw new Error(`Content source must be inside the content root: ${filePath}`);
183
+ return `${CONTENT_PREFIX}${sourcePath}`;
184
+ }
185
+ //#endregion
171
186
  //#region source/core/plugin/hooks.ts
172
187
  async function runHooks(plugins, hookName, input, ctx) {
173
188
  let result = input;
@@ -183,32 +198,43 @@ async function runHooks(plugins, hookName, input, ctx) {
183
198
  return result;
184
199
  }
185
200
  async function runDevConfigureServerHooks(plugins, server, ctx) {
201
+ const postHooks = [];
186
202
  for (const plugin of plugins) {
187
203
  const hook = plugin.hooks?.["dev:configureServer"];
188
204
  if (!hook) continue;
189
205
  try {
190
- await hook(server, ctx);
206
+ const postHook = await hook(server, ctx);
207
+ if (postHook) postHooks.push(postHook);
191
208
  } catch (err) {
192
209
  throw new Error(`[clarify] plugin "${plugin.name}" hook "dev:configureServer" failed: ${err}`, { cause: err });
193
210
  }
194
211
  }
212
+ return postHooks;
195
213
  }
196
- async function runCollectorHooks(plugins, hookName, ctx) {
197
- const results = [];
214
+ async function runBuildAssetsHooks(plugins, ctx) {
215
+ const assets = [];
216
+ const producerByFileName = /* @__PURE__ */ new Map();
198
217
  for (const plugin of plugins) {
199
- const hook = plugin.hooks?.[hookName];
218
+ const hook = plugin.hooks?.["build:assets"];
200
219
  if (!hook) continue;
220
+ let pluginAssets;
201
221
  try {
202
- const pluginResults = await hook(ctx);
203
- results.push(...pluginResults);
222
+ pluginAssets = await hook(ctx);
204
223
  } catch (err) {
205
- throw new Error(`[clarify] plugin "${plugin.name}" hook "${hookName}" failed: ${err}`, { cause: err });
224
+ throw new Error(`[clarify] plugin "${plugin.name}" hook "build:assets" failed: ${err}`, { cause: err });
225
+ }
226
+ for (const asset of pluginAssets) {
227
+ const fileName = asset.fileName.replace(/\\/g, "/").replace(/^\/+/, "");
228
+ const previousProducer = producerByFileName.get(fileName);
229
+ if (previousProducer) throw new Error(`[clarify] build asset "${fileName}" is emitted by both plugin "${previousProducer}" and plugin "${plugin.name}"`);
230
+ producerByFileName.set(fileName, plugin.name);
231
+ assets.push({
232
+ ...asset,
233
+ fileName
234
+ });
206
235
  }
207
236
  }
208
- return results;
209
- }
210
- async function runBuildAssetsHooks(plugins, ctx) {
211
- return runCollectorHooks(plugins, "build:assets", ctx);
237
+ return assets;
212
238
  }
213
239
  async function runBuildDoneHooks(plugins, ctx) {
214
240
  for (const plugin of plugins) {
@@ -243,7 +269,7 @@ function createContentProcessor(transform) {
243
269
  async processMdx(source, filePath) {
244
270
  const { frontmatter, content } = parseFrontmatter(source);
245
271
  const transformed = await transformContent(transform, {
246
- kind: "mdx",
272
+ kind: "markdown+jsx",
247
273
  source,
248
274
  filePath,
249
275
  frontmatter,
@@ -395,9 +421,6 @@ function openAPIPagePathFromRef(ref, tagFilter, explicitPath) {
395
421
  function routePathFromRef(ref, explicitPath) {
396
422
  return explicitPath ? normalizePath(explicitPath) : basePathFromRef(ref);
397
423
  }
398
- function virtualModuleIdFromRef(ref) {
399
- return "virtual:clarify-page/" + ref.replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "").replace(/\/+/g, "/");
400
- }
401
424
  function localizedRoutePath(basePath, locale, _i18n) {
402
425
  const normalizedBasePath = normalizePath(basePath);
403
426
  const prefix = normalizePath(locale);
@@ -440,7 +463,7 @@ async function findContentRoutes(dir, base = dir, options = {}) {
440
463
  title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : extractH1(page.content) : lastPart) || "Untitled";
441
464
  }
442
465
  routes.push({
443
- kind: "mdx",
466
+ kind: entry.name.toLowerCase().endsWith(".mdx") ? "markdown+jsx" : "markdown",
444
467
  path: cleanPath,
445
468
  basePath: cleanPath,
446
469
  meta: {
@@ -449,7 +472,10 @@ async function findContentRoutes(dir, base = dir, options = {}) {
449
472
  keywords: frontmatterKeywords(page.frontmatter),
450
473
  sections: extractMdxSections(page.content)
451
474
  },
452
- module: { virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/") },
475
+ module: {
476
+ pageVirtualModuleId: pageVirtualModuleId(relativePath.replace(/\.mdx?$/, "")),
477
+ contentVirtualModuleId: contentVirtualModuleId(fullPath, base)
478
+ },
453
479
  source: {
454
480
  filePath: fullPath,
455
481
  frontmatter: page.frontmatter,
@@ -461,14 +487,24 @@ async function findContentRoutes(dir, base = dir, options = {}) {
461
487
  }
462
488
  return routes;
463
489
  }
464
- function virtualModuleIdForLocalizedRoute(contentRoot, filePath) {
465
- return "virtual:clarify-page/" + (0, node_path.relative)(contentRoot, filePath).replace(/\.mdx?$/, "").replace(/\.openapi\.(json|yaml|yml)$/, "").replace(/\/+/g, "/");
490
+ function pageVirtualModuleIdForLocalizedRoute(contentRoot, filePath) {
491
+ return pageVirtualModuleIdFromRef((0, node_path.relative)(contentRoot, filePath));
466
492
  }
467
493
  function frontmatterKeywords(frontmatter) {
468
494
  const value = frontmatter.keywords ?? frontmatter.keyword ?? frontmatter.tags;
469
495
  const keywords = Array.isArray(value) ? value.filter((item) => typeof item === "string") : typeof value === "string" ? value.split(",").map((keyword) => keyword.trim()).filter(Boolean) : [];
470
496
  return keywords.length > 0 ? keywords : void 0;
471
497
  }
498
+ function withPageIdentity(route, path) {
499
+ return {
500
+ ...route,
501
+ path,
502
+ module: {
503
+ ...route.module,
504
+ pageVirtualModuleId: pageVirtualModuleId(path)
505
+ }
506
+ };
507
+ }
472
508
  function withAlternates(route, routes, locales) {
473
509
  const basePath = route.basePath ?? route.path;
474
510
  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]));
@@ -494,8 +530,7 @@ function generateBareAliases(routes, locales) {
494
530
  if (bp === route.path || seenBare.has(bp)) continue;
495
531
  seenBare.add(bp);
496
532
  bareRoutes.push({
497
- ...route,
498
- path: bp,
533
+ ...withPageIdentity(route, bp),
499
534
  isBareAlias: true
500
535
  });
501
536
  }
@@ -514,7 +549,11 @@ async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
514
549
  path: localizedRoutePath(basePath, locale.code, locales),
515
550
  basePath,
516
551
  locale: locale.code,
517
- module: { virtualModuleId: virtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath) }
552
+ module: {
553
+ ...route.module,
554
+ pageVirtualModuleId: pageVirtualModuleIdForLocalizedRoute(contentRoot, route.source.filePath),
555
+ contentVirtualModuleId: contentVirtualModuleId(route.source.filePath, contentRoot)
556
+ }
518
557
  });
519
558
  }
520
559
  }
@@ -526,9 +565,9 @@ async function findLocalizedContentRoutes(contentRoot, locales, options = {}) {
526
565
  for (const locale of locales.locales) {
527
566
  const key = `${locale.code}:${basePath}`;
528
567
  if (routeByLocaleAndBase.has(key)) continue;
568
+ const path = localizedRoutePath(basePath, locale.code, locales);
529
569
  localizedRoutes.push({
530
- ...sourceRoute,
531
- path: localizedRoutePath(basePath, locale.code, locales),
570
+ ...withPageIdentity(sourceRoute, path),
532
571
  locale: locale.code,
533
572
  isFallback: true
534
573
  });
@@ -623,13 +662,13 @@ function applyConfiguredPageRoutePaths(routes, tabs, locales) {
623
662
  const targetBasePath = routePathFromRef(intent.ref, intent.path);
624
663
  if (sourceBasePath === targetBasePath) continue;
625
664
  for (const route of routes) {
626
- if (route.kind !== "mdx" || (route.basePath ?? route.path) !== sourceBasePath) continue;
665
+ if (route.kind === "openapi" || (route.basePath ?? route.path) !== sourceBasePath) continue;
627
666
  const key = `${route.locale ?? ""}:${targetBasePath}`;
628
667
  if (existingKeys.has(key)) continue;
629
668
  existingKeys.add(key);
669
+ const path = route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath;
630
670
  additions.push({
631
- ...route,
632
- path: route.locale && locales ? localizedRoutePath(targetBasePath, route.locale, locales) : targetBasePath,
671
+ ...withPageIdentity(route, path),
633
672
  basePath: targetBasePath
634
673
  });
635
674
  }
@@ -637,7 +676,8 @@ function applyConfiguredPageRoutePaths(routes, tabs, locales) {
637
676
  const nextRoutes = [...routes, ...additions];
638
677
  const routesWithAlternates = locales ? nextRoutes.map((route) => withAlternates(route, nextRoutes, locales)) : nextRoutes;
639
678
  if (!locales) return routesWithAlternates;
640
- return [...routesWithAlternates, ...generateBareAliases(routesWithAlternates, locales)];
679
+ const markdownRoutes = routesWithAlternates.filter((route) => route.kind !== "openapi");
680
+ return [...routesWithAlternates, ...generateBareAliases(markdownRoutes, locales)];
641
681
  }
642
682
  function buildNavigationFromPagesConfig(routes, config) {
643
683
  if (!config || config === "FileTree") return buildNavigation(routes);
@@ -831,7 +871,7 @@ function inferRouteForFile(filePath, contentRoot, defaultLocale) {
831
871
  filePath,
832
872
  locale: fileLocale,
833
873
  basePath: normalizePath(basePath),
834
- kind: isMdx ? "mdx" : "openapi",
874
+ kind: isMdx ? "markdown+jsx" : "openapi",
835
875
  title,
836
876
  inferred: true
837
877
  };
@@ -1250,6 +1290,9 @@ function resolveVirtualId(id) {
1250
1290
  function stripVirtualPrefix(id) {
1251
1291
  return id.startsWith("\0") ? id.slice(1) : id;
1252
1292
  }
1293
+ function virtualContentModuleId(route) {
1294
+ return route.module.contentVirtualModuleId;
1295
+ }
1253
1296
  /**
1254
1297
  * Fixed virtual module IDs (excluding the client entry, which has a special
1255
1298
  * resolved form). Used to avoid hardcoding the constant list in every
@@ -1277,8 +1320,10 @@ function resolveVirtualModuleId(id, modules, routes) {
1277
1320
  for (const virtualId of FIXED_VIRTUAL_IDS) if (id === virtualId || id === resolveVirtualId(virtualId)) return resolveVirtualId(virtualId);
1278
1321
  const moduleId = stripVirtualPrefix(id);
1279
1322
  if (modules.has(moduleId)) return resolveVirtualId(moduleId);
1280
- const route = routes.find((route) => route.module.virtualModuleId === id || route.module.virtualModuleId === moduleId);
1281
- if (route) return resolveVirtualId(route.module.virtualModuleId);
1323
+ const contentRoute = routes.find((route) => (route.kind === "markdown" || route.kind === "markdown+jsx") && route.module.contentVirtualModuleId === moduleId);
1324
+ if (contentRoute) return contentRoute.source.filePath;
1325
+ const route = routes.find((route) => route.module.pageVirtualModuleId === id || route.module.pageVirtualModuleId === moduleId);
1326
+ if (route) return resolveVirtualId(route.module.pageVirtualModuleId);
1282
1327
  return null;
1283
1328
  }
1284
1329
  function generateConfigModule(projectConfig, version) {
@@ -1332,8 +1377,8 @@ function routeToRuntimeManifestEntry(route, component, mode) {
1332
1377
  return entry;
1333
1378
  }
1334
1379
  function generateRoutesModule(routes, navigation, mode = "client") {
1335
- 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) => {
1336
- 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 }) }))`;
1380
+ 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) => {
1381
+ 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 }) }))`;
1337
1382
  const placeholder = `__COMPONENT_${i}__`;
1338
1383
  const manifestEntry = routeToRuntimeManifestEntry(r, placeholder, mode);
1339
1384
  return ` ${JSON.stringify(manifestEntry, null, 0).replace(`"${placeholder}"`, component).replace(/"([A-Za-z_$][\w$]*)":/g, "$1: ")}`;
@@ -1405,13 +1450,14 @@ function buildVirtualModules(args) {
1405
1450
  modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, "client"));
1406
1451
  modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, "server"));
1407
1452
  modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
1408
- modules.set(VIRTUAL_OPENAPI, "export const openApiSpecs = {};");
1409
1453
  modules.set(VIRTUAL_SLOT, "export { useSlot } from '@clarify-labs/renderer';\n");
1410
- modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
1454
+ modules.set(VIRTUAL_OPENAPI, "export const openApiSpecs = {};");
1411
1455
  modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
1456
+ modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
1412
1457
  for (const route of args.routes) {
1413
- const moduleContent = route.diagnostic ? generateContentDiagnosticModule(route.diagnostic) : `export { default } from ${moduleSpecifier(route.source.filePath)};`;
1414
- modules.set(route.module.virtualModuleId, moduleContent);
1458
+ const moduleContent = route.diagnostic ? generateContentDiagnosticModule(route.diagnostic) : route.kind === "markdown" || route.kind === "markdown+jsx" ? `export { default } from ${moduleSpecifier(virtualContentModuleId(route))};` : null;
1459
+ if (moduleContent === null) continue;
1460
+ modules.set(route.module.pageVirtualModuleId, moduleContent);
1415
1461
  }
1416
1462
  return modules;
1417
1463
  }
@@ -1452,7 +1498,7 @@ function createNormalizedContentPlugin(engine) {
1452
1498
  transform(_code, id) {
1453
1499
  if (!/\.mdx?(?:\?|$)/.test(id)) return null;
1454
1500
  const filePath = id.replace(/\?.*$/, "");
1455
- const route = engine.routes.find((route) => route.kind === "mdx" && route.source.filePath === filePath);
1501
+ const route = engine.routes.find((route) => (route.kind === "markdown+jsx" || route.kind === "markdown") && route.source.filePath === filePath);
1456
1502
  if (!route || route.source.content === void 0) return null;
1457
1503
  return {
1458
1504
  code: route.source.content,
@@ -1567,7 +1613,10 @@ function createClarifyViteCorePlugin(engine, normalizedContentPlugin, markdown,
1567
1613
  };
1568
1614
  server.watcher.on("add", handleContentTreeChange);
1569
1615
  server.watcher.on("unlink", handleContentTreeChange);
1570
- await engine.configureDevServer(server);
1616
+ const postHooks = await engine.configureDevServer(server);
1617
+ if (postHooks.length > 0) return () => {
1618
+ for (const postHook of postHooks) postHook();
1619
+ };
1571
1620
  },
1572
1621
  transformIndexHtml: {
1573
1622
  order: "pre",
@@ -1622,7 +1671,7 @@ function createViteAdapter(engine) {
1622
1671
  const errorPolicy = () => contentCompileErrorPolicy;
1623
1672
  const normalizedContentPlugin = createNormalizedContentPlugin(engine);
1624
1673
  const markdown = createContentCompilerPlugin("markdown", engine.root, errorPolicy);
1625
- const mdx = createContentCompilerPlugin("mdx", engine.root, errorPolicy);
1674
+ const mdx = createContentCompilerPlugin("markdown+jsx", engine.root, errorPolicy);
1626
1675
  const compilePolicyPlugin = {
1627
1676
  name: "clarify:content-compile-policy",
1628
1677
  configResolved(config) {
@@ -1642,7 +1691,7 @@ function createViteAdapter(engine) {
1642
1691
  }
1643
1692
  var cliPackageVersion = {
1644
1693
  name: "@clarify-labs/cli",
1645
- version: "0.11.4",
1694
+ version: "0.11.6",
1646
1695
  license: "AGPL-3.0-only",
1647
1696
  type: "module",
1648
1697
  repository: {
@@ -1742,7 +1791,7 @@ function withUtf8Signature(content) {
1742
1791
  return `${UTF8_SIGNATURE}${content}`;
1743
1792
  }
1744
1793
  function shouldUseUtf8Signature(route) {
1745
- if (route.kind === "mdx") return true;
1794
+ if (route.kind === "markdown+jsx" || route.kind === "markdown") return true;
1746
1795
  return false;
1747
1796
  }
1748
1797
  function routeToMarkdownArtifactUrl(routePath) {
@@ -1814,9 +1863,9 @@ function createLlmsTxt(routes, projectConfig) {
1814
1863
  const lines = [`# ${projectConfig.title}`, ""];
1815
1864
  if (projectConfig.description) lines.push(`> ${projectConfig.description}`, "");
1816
1865
  lines.push("This file lists the source-ready Markdown and OpenAPI artifacts for this documentation site.", "");
1817
- const mdxRoutes = routes.filter((route) => route.kind === "mdx" && isLlmsTxtRoute(route) && !route.isBareAlias);
1818
- if (mdxRoutes.length > 0) {
1819
- const localizedGroups = groupLlmsTxtRoutesByLocale(mdxRoutes, projectConfig);
1866
+ const markdownRoutes = routes.filter((route) => (route.kind === "markdown+jsx" || route.kind === "markdown") && isLlmsTxtRoute(route) && !route.isBareAlias);
1867
+ if (markdownRoutes.length > 0) {
1868
+ const localizedGroups = groupLlmsTxtRoutesByLocale(markdownRoutes, projectConfig);
1820
1869
  for (const [locale, localeRoutes] of localizedGroups) {
1821
1870
  lines.push(`## ${llmsTxtDocsSectionTitle(locale, localizedGroups.size, projectConfig)}`);
1822
1871
  for (const route of localeRoutes) {
@@ -1893,10 +1942,12 @@ function createContentArtifactsPlugin() {
1893
1942
  return input;
1894
1943
  },
1895
1944
  "dev:configureServer": (server, ctx) => {
1896
- server.middlewares.use((req, res, next) => {
1897
- if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
1898
- next();
1899
- });
1945
+ return () => {
1946
+ server.middlewares.use((req, res, next) => {
1947
+ if (serveContentArtifacts(req, res, ctx.projectConfig, ctx.routes)) return;
1948
+ next();
1949
+ });
1950
+ };
1900
1951
  },
1901
1952
  "build:assets": (ctx) => {
1902
1953
  const assets = [];
@@ -2387,7 +2438,7 @@ function findOpenAPIRoutes(dir, base = dir) {
2387
2438
  basePath: cleanPath,
2388
2439
  kind: "openapi",
2389
2440
  meta: { title: kebabToTitle(cleanPath.split("/").pop() ?? "API") },
2390
- module: { virtualModuleId: virtualModuleIdFromRef(ref) },
2441
+ module: { pageVirtualModuleId: pageVirtualModuleIdFromRef(ref) },
2391
2442
  source: { filePath: fullPath }
2392
2443
  });
2393
2444
  }
@@ -2511,34 +2562,7 @@ async function readOpenAPISpec(filePath, contentProcessor, projectRoot) {
2511
2562
  }
2512
2563
  }
2513
2564
  //#endregion
2514
- //#region source/plugins/openapi/virtual-modules.ts
2515
- var openApiRegistryModuleId = VIRTUAL_OPENAPI;
2516
- var openApiServerRegistryModuleId = VIRTUAL_OPENAPI_SERVER;
2517
- var OPENAPI_SPEC_PREFIX = "virtual:clarify/openapi-spec/";
2518
- function specVirtualModuleId(specKey) {
2519
- return `${OPENAPI_SPEC_PREFIX}${specKey}`;
2520
- }
2521
- function generateOpenAPIRegistryModule(openApiSpecs) {
2522
- return `export const openApiSpecs = {\n${Object.entries(openApiSpecs).map(([key, moduleId]) => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(moduleId)}),`).join("\n")}\n};`;
2523
- }
2524
- function generateOpenAPIServerRegistryModule(openApiSpecs) {
2525
- return `export const openApiSpecs = ${JSON.stringify(openApiSpecs)};`;
2526
- }
2527
- /** Generate a virtual module that exports a single spec as default. */
2528
- function generateOpenAPISpecModule(spec) {
2529
- return `export default ${JSON.stringify(spec)};`;
2530
- }
2531
- function generateOpenAPIPageModule(opts) {
2532
- return `import { createOpenApiRouteComponent } from '@clarify-labs/renderer';
2533
- import spec from ${JSON.stringify(opts.specModuleId)};
2534
-
2535
- export const routeData = {};
2536
-
2537
- export default createOpenApiRouteComponent({ ...routeData, spec });
2538
- `;
2539
- }
2540
- //#endregion
2541
- //#region source/plugins/openapi/index.ts
2565
+ //#region source/plugins/openapi/routes.ts
2542
2566
  function collectOpenAPIPageIntents(config) {
2543
2567
  const intents = [];
2544
2568
  function visitItems(items) {
@@ -2558,9 +2582,6 @@ function collectOpenAPIPageIntents(config) {
2558
2582
  for (const tab of config.navigation?.tabs ?? []) visitPages(tab.pages);
2559
2583
  return intents;
2560
2584
  }
2561
- function virtualModuleIdFromPath(path) {
2562
- return `virtual:clarify-page/${path.replace(/^\/+/, "") || "index"}`;
2563
- }
2564
2585
  function cloneOpenAPIRoute(route, overrides = {}) {
2565
2586
  return {
2566
2587
  ...route,
@@ -2578,7 +2599,7 @@ function cloneOpenAPIRoute(route, overrides = {}) {
2578
2599
  ...overrides
2579
2600
  };
2580
2601
  }
2581
- function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
2602
+ function discoverOpenAPIRoutes(contentRoot, locales) {
2582
2603
  if (!locales) return findOpenAPIRoutes(contentRoot);
2583
2604
  const localizedRoutes = [];
2584
2605
  for (const locale of locales.locales) {
@@ -2591,31 +2612,30 @@ function findOpenAPIRoutesForContentRoot(contentRoot, locales) {
2591
2612
  path,
2592
2613
  basePath,
2593
2614
  locale: locale.code,
2594
- module: { virtualModuleId: virtualModuleIdFromPath(path) }
2615
+ module: { pageVirtualModuleId: pageVirtualModuleId(path) }
2595
2616
  });
2596
2617
  }
2597
2618
  }
2598
- if (locales.missing === "fallback") {
2599
- const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
2600
- const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
2601
- for (const sourceRoute of defaultRoutes) {
2602
- const basePath = sourceRoute.basePath ?? sourceRoute.path;
2603
- for (const locale of locales.locales) {
2604
- const key = `${locale.code}:${basePath}`;
2605
- if (routeByLocaleAndBase.has(key)) continue;
2606
- const path = localizedRoutePath(basePath, locale.code, locales);
2607
- localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
2608
- path,
2609
- locale: locale.code,
2610
- module: { virtualModuleId: virtualModuleIdFromPath(path) },
2611
- isFallback: true
2612
- }));
2613
- }
2619
+ if (locales.missing !== "fallback") return localizedRoutes;
2620
+ const routeByLocaleAndBase = new Map(localizedRoutes.map((route) => [`${route.locale ?? ""}:${route.basePath ?? route.path}`, route]));
2621
+ const defaultRoutes = localizedRoutes.filter((route) => route.locale === locales.default);
2622
+ for (const sourceRoute of defaultRoutes) {
2623
+ const basePath = sourceRoute.basePath ?? sourceRoute.path;
2624
+ for (const locale of locales.locales) {
2625
+ const key = `${locale.code}:${basePath}`;
2626
+ if (routeByLocaleAndBase.has(key)) continue;
2627
+ const path = localizedRoutePath(basePath, locale.code, locales);
2628
+ localizedRoutes.push(cloneOpenAPIRoute(sourceRoute, {
2629
+ path,
2630
+ locale: locale.code,
2631
+ module: { pageVirtualModuleId: pageVirtualModuleId(path) },
2632
+ isFallback: true
2633
+ }));
2614
2634
  }
2615
2635
  }
2616
2636
  return localizedRoutes;
2617
2637
  }
2618
- function createConfiguredOpenAPIRoutes(routes, config) {
2638
+ function expandConfiguredOpenAPIRoutes(routes, config) {
2619
2639
  const openAPIIntents = collectOpenAPIPageIntents(config);
2620
2640
  if (!openAPIIntents.length) return routes;
2621
2641
  const additions = [];
@@ -2634,7 +2654,7 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2634
2654
  additions.push(cloneOpenAPIRoute(route, {
2635
2655
  path,
2636
2656
  basePath: targetBasePath,
2637
- module: { virtualModuleId: virtualModuleIdFromPath(path) },
2657
+ module: { pageVirtualModuleId: pageVirtualModuleId(path) },
2638
2658
  openapi: {
2639
2659
  ...route.openapi,
2640
2660
  tagFilter: tagFilter?.length ? tagFilter : void 0
@@ -2648,19 +2668,47 @@ function createConfiguredOpenAPIRoutes(routes, config) {
2648
2668
  const bareAliases = [];
2649
2669
  const seenBare = new Set(routesWithAlternates.map((route) => route.path));
2650
2670
  for (const route of routesWithAlternates) {
2651
- if (route.locale !== config.locales.default) continue;
2671
+ if (route.kind !== "openapi" || route.locale !== config.locales.default) continue;
2652
2672
  const basePath = route.basePath ?? route.path;
2653
2673
  if (basePath === route.path || seenBare.has(basePath)) continue;
2654
2674
  seenBare.add(basePath);
2655
2675
  bareAliases.push(cloneOpenAPIRoute(route, {
2656
2676
  path: basePath,
2657
- module: { virtualModuleId: virtualModuleIdFromPath(basePath) },
2677
+ module: { pageVirtualModuleId: pageVirtualModuleId(basePath) },
2658
2678
  isBareAlias: true
2659
2679
  }));
2660
2680
  }
2661
2681
  return [...routesWithAlternates, ...bareAliases];
2662
2682
  }
2663
- /** Derive a stable, deduplicated id from an absolute spec file path. */
2683
+ //#endregion
2684
+ //#region source/plugins/openapi/virtual-modules.ts
2685
+ var openApiRegistryModuleId = VIRTUAL_OPENAPI;
2686
+ var openApiServerRegistryModuleId = VIRTUAL_OPENAPI_SERVER;
2687
+ var OPENAPI_SPEC_PREFIX = "virtual:clarify/openapi-spec/";
2688
+ function specVirtualModuleId(specKey) {
2689
+ return `${OPENAPI_SPEC_PREFIX}${specKey}`;
2690
+ }
2691
+ function generateOpenAPIRegistryModule(openApiSpecs) {
2692
+ return `export const openApiSpecs = {\n${Object.entries(openApiSpecs).map(([key, moduleId]) => ` ${JSON.stringify(key)}: () => import(${JSON.stringify(moduleId)}),`).join("\n")}\n};`;
2693
+ }
2694
+ function generateOpenAPIServerRegistryModule(openApiSpecs) {
2695
+ return `export const openApiSpecs = ${JSON.stringify(openApiSpecs)};`;
2696
+ }
2697
+ /** Generate a virtual module that exports a single spec as default. */
2698
+ function generateOpenAPISpecModule(spec) {
2699
+ return `export default ${JSON.stringify(spec)};`;
2700
+ }
2701
+ function generateOpenAPIPageModule(opts) {
2702
+ return `import { createOpenApiRouteComponent } from '@clarify-labs/renderer';
2703
+ import spec from ${JSON.stringify(opts.specModuleId)};
2704
+
2705
+ export const routeData = {};
2706
+
2707
+ export default createOpenApiRouteComponent({ ...routeData, spec });
2708
+ `;
2709
+ }
2710
+ //#endregion
2711
+ //#region source/plugins/openapi/state.ts
2664
2712
  function sourceSpecIdFromPath(filePath, projectRoot) {
2665
2713
  return (0, node_path.relative)(projectRoot, filePath).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
2666
2714
  }
@@ -2672,90 +2720,94 @@ function openAPIRegistryKeys(route) {
2672
2720
  const keys = /* @__PURE__ */ new Set();
2673
2721
  const path = (route.path || "/").replace(/^\//, "");
2674
2722
  const base = (route.basePath ?? route.path).replace(/^\//, "");
2675
- if (route.locale) keys.add(`virtual:clarify-page/${route.locale}/${base}`);
2676
- if (path) keys.add(`virtual:clarify-page/${path}`);
2677
- else keys.add("virtual:clarify-page/index");
2678
- if (!route.locale && base) keys.add(`virtual:clarify-page/${base}`);
2723
+ if (route.locale) keys.add(pageVirtualModuleId(`${route.locale}/${base}`));
2724
+ keys.add(pageVirtualModuleId(path));
2725
+ if (!route.locale && base) keys.add(pageVirtualModuleId(base));
2679
2726
  return [...keys];
2680
2727
  }
2728
+ var OpenAPIPluginState = class {
2729
+ specModules = /* @__PURE__ */ new Map();
2730
+ async enrichRoutes(routes, ctx) {
2731
+ this.specModules.clear();
2732
+ const specByFilePath = /* @__PURE__ */ new Map();
2733
+ for (const route of routes) {
2734
+ if (route.kind !== "openapi") continue;
2735
+ const specFromCache = specByFilePath.get(route.source.filePath);
2736
+ const processor = createProjectContentProcessor(ctx.plugins, ctx);
2737
+ const result = specFromCache ? {
2738
+ ok: true,
2739
+ spec: specFromCache
2740
+ } : await readOpenAPISpec(route.source.filePath, processor, ctx.generateOptions.projectRoot);
2741
+ if (!result.ok) {
2742
+ route.diagnostic = result.diagnostic;
2743
+ route.meta.title = route.meta.title || "OpenAPI parse error";
2744
+ route.meta.sections = [];
2745
+ continue;
2746
+ }
2747
+ const spec = normalizeOpenAPISpecSectionIds(result.spec);
2748
+ specByFilePath.set(route.source.filePath, spec);
2749
+ const sourceSpecId = sourceSpecIdFromPath(route.source.filePath, ctx.generateOptions.projectRoot);
2750
+ route.openapi = {
2751
+ ...route.openapi,
2752
+ sourceSpecId
2753
+ };
2754
+ route.meta.title = spec.info?.title ?? route.meta.title;
2755
+ route.meta.description = spec.info?.description ?? route.meta.description;
2756
+ route.meta.sections = extractOpenAPISections(spec, route.openapi.tagFilter);
2757
+ const pageSpec = route.openapi.tagFilter?.length ? filterSpecByTags(spec, route.openapi.tagFilter) : spec;
2758
+ const routeSpecModuleId = routeSpecModuleIdFromSourceSpecId(sourceSpecId, route.openapi.tagFilter);
2759
+ route.openapi.routeSpecModuleId = routeSpecModuleId;
2760
+ this.specModules.set(routeSpecModuleId, pageSpec);
2761
+ route.source.content = JSON.stringify(pageSpec);
2762
+ }
2763
+ return routes;
2764
+ }
2765
+ contributeModules(modules, routes) {
2766
+ const registryEntries = {};
2767
+ const serverRegistryEntries = {};
2768
+ for (const route of routes) {
2769
+ if (route.kind !== "openapi" || !route.openapi?.sourceSpecId) continue;
2770
+ const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2771
+ const spec = this.specModules.get(routeSpecModuleId);
2772
+ if (!spec) continue;
2773
+ for (const registryKey of openAPIRegistryKeys(route)) {
2774
+ registryEntries[registryKey] = specVirtualModuleId(routeSpecModuleId);
2775
+ serverRegistryEntries[registryKey] = spec;
2776
+ }
2777
+ }
2778
+ modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(registryEntries));
2779
+ modules.set(openApiServerRegistryModuleId, generateOpenAPIServerRegistryModule(serverRegistryEntries));
2780
+ for (const [routeSpecModuleId, spec] of this.specModules) modules.set(specVirtualModuleId(routeSpecModuleId), generateOpenAPISpecModule(spec));
2781
+ for (const route of routes) {
2782
+ if (route.kind !== "openapi") continue;
2783
+ if (route.diagnostic) {
2784
+ modules.set(route.module.pageVirtualModuleId, generateContentDiagnosticModule(route.diagnostic));
2785
+ continue;
2786
+ }
2787
+ if (!route.openapi?.sourceSpecId) continue;
2788
+ const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2789
+ if (!this.specModules.has(routeSpecModuleId)) continue;
2790
+ modules.set(route.module.pageVirtualModuleId, generateOpenAPIPageModule({ specModuleId: specVirtualModuleId(routeSpecModuleId) }));
2791
+ }
2792
+ return modules;
2793
+ }
2794
+ };
2795
+ //#endregion
2796
+ //#region source/plugins/openapi/index.ts
2681
2797
  function createOpenAPIPlugin() {
2682
- /** specs keyed by sourceSpecId (deduplicated per source file). */
2683
- const specs = /* @__PURE__ */ new Map();
2684
- const specModules = /* @__PURE__ */ new Map();
2798
+ const state = new OpenAPIPluginState();
2685
2799
  return {
2686
2800
  name: "clarify:openapi",
2687
2801
  hooks: {
2688
2802
  "routes:discover": (input, ctx) => ({
2689
2803
  ...input,
2690
- routes: [...input.routes, ...findOpenAPIRoutesForContentRoot(input.contentRoot, ctx.projectConfig.locales)]
2804
+ routes: [...input.routes, ...discoverOpenAPIRoutes(input.contentRoot, ctx.projectConfig.locales)]
2691
2805
  }),
2692
2806
  "routes:discovered": async (routes, ctx) => {
2693
- specs.clear();
2694
- specModules.clear();
2695
- const nextRoutes = createConfiguredOpenAPIRoutes(routes, ctx.projectConfig);
2696
- const specByFilePath = /* @__PURE__ */ new Map();
2697
- for (const route of nextRoutes.filter((route) => route.kind === "openapi")) {
2698
- const specFromCache = specByFilePath.get(route.source.filePath);
2699
- const processor = createProjectContentProcessor(ctx.plugins, ctx);
2700
- const result = specFromCache ? {
2701
- ok: true,
2702
- spec: specFromCache
2703
- } : await readOpenAPISpec(route.source.filePath, processor, ctx.generateOptions.projectRoot);
2704
- if (!result.ok) {
2705
- route.diagnostic = result.diagnostic;
2706
- route.meta.title = route.meta.title || "OpenAPI parse error";
2707
- route.meta.sections = [];
2708
- continue;
2709
- }
2710
- const spec = normalizeOpenAPISpecSectionIds(result.spec);
2711
- specByFilePath.set(route.source.filePath, spec);
2712
- const sourceSpecId = sourceSpecIdFromPath(route.source.filePath, ctx.generateOptions.projectRoot);
2713
- route.openapi = {
2714
- ...route.openapi,
2715
- sourceSpecId
2716
- };
2717
- specs.set(sourceSpecId, {
2718
- spec,
2719
- filePath: route.source.filePath
2720
- });
2721
- route.meta.title = spec.info?.title ?? route.meta.title;
2722
- route.meta.description = spec.info?.description ?? route.meta.description;
2723
- route.meta.sections = extractOpenAPISections(spec, route.openapi?.tagFilter);
2724
- const pageSpec = route.openapi?.tagFilter?.length ? filterSpecByTags(spec, route.openapi.tagFilter) : spec;
2725
- const routeSpecModuleId = routeSpecModuleIdFromSourceSpecId(sourceSpecId, route.openapi?.tagFilter);
2726
- route.openapi.routeSpecModuleId = routeSpecModuleId;
2727
- specModules.set(routeSpecModuleId, pageSpec);
2728
- route.source.content = JSON.stringify(pageSpec);
2729
- }
2730
- return nextRoutes;
2807
+ const expandedRoutes = expandConfiguredOpenAPIRoutes(routes, ctx.projectConfig);
2808
+ return state.enrichRoutes(expandedRoutes, ctx);
2731
2809
  },
2732
- "modules:before": (modules, ctx) => {
2733
- const registryEntries = {};
2734
- const serverRegistryEntries = {};
2735
- for (const route of ctx.routes) {
2736
- if (route.kind !== "openapi" || !route.openapi?.sourceSpecId) continue;
2737
- const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2738
- const spec = specModules.get(routeSpecModuleId) ?? specs.get(route.openapi.sourceSpecId)?.spec;
2739
- if (!spec) continue;
2740
- for (const registryKey of openAPIRegistryKeys(route)) {
2741
- registryEntries[registryKey] = specVirtualModuleId(routeSpecModuleId);
2742
- serverRegistryEntries[registryKey] = spec;
2743
- }
2744
- }
2745
- modules.set(openApiRegistryModuleId, generateOpenAPIRegistryModule(registryEntries));
2746
- modules.set(openApiServerRegistryModuleId, generateOpenAPIServerRegistryModule(serverRegistryEntries));
2747
- for (const [routeSpecModuleId, spec] of specModules) modules.set(specVirtualModuleId(routeSpecModuleId), generateOpenAPISpecModule(spec));
2748
- for (const route of ctx.routes) {
2749
- if (route.kind !== "openapi") continue;
2750
- if (route.diagnostic) modules.set(route.module.virtualModuleId, generateContentDiagnosticModule(route.diagnostic));
2751
- else if (route.openapi?.sourceSpecId) {
2752
- const routeSpecModuleId = route.openapi.routeSpecModuleId ?? route.openapi.sourceSpecId;
2753
- if (!specModules.has(routeSpecModuleId)) continue;
2754
- modules.set(route.module.virtualModuleId, generateOpenAPIPageModule({ specModuleId: specVirtualModuleId(routeSpecModuleId) }));
2755
- }
2756
- }
2757
- return modules;
2758
- }
2810
+ "modules:before": (modules, ctx) => state.contributeModules(modules, ctx.routes)
2759
2811
  }
2760
2812
  };
2761
2813
  }
@@ -3164,6 +3216,145 @@ async function resolveProjectContext(options = {}, env = {
3164
3216
  };
3165
3217
  }
3166
3218
  //#endregion
3219
+ //#region source/core/routing/route-analysis.ts
3220
+ function routeDescription(route) {
3221
+ return `${route.kind} ${route.source.filePath}`;
3222
+ }
3223
+ function collectConflicts(routes, keyOf) {
3224
+ const grouped = /* @__PURE__ */ new Map();
3225
+ for (const route of routes) {
3226
+ const key = keyOf(route);
3227
+ grouped.set(key, [...grouped.get(key) ?? [], route]);
3228
+ }
3229
+ return grouped;
3230
+ }
3231
+ function describeRouteConflict(route) {
3232
+ return routeDescription(route);
3233
+ }
3234
+ function findRouteConflicts(routes) {
3235
+ const conflicts = [];
3236
+ for (const [path, pathRoutes] of collectConflicts(routes, (route) => route.path)) {
3237
+ if (pathRoutes.length < 2) continue;
3238
+ conflicts.push({
3239
+ kind: "path",
3240
+ key: path,
3241
+ label: `path ${path}`,
3242
+ routes: pathRoutes
3243
+ });
3244
+ }
3245
+ for (const [pageModuleId, moduleRoutes] of collectConflicts(routes, (route) => route.module.pageVirtualModuleId)) {
3246
+ if (moduleRoutes.length < 2) continue;
3247
+ if (moduleRoutes.every((route) => route.path === moduleRoutes[0].path)) continue;
3248
+ conflicts.push({
3249
+ kind: "page-module",
3250
+ key: pageModuleId,
3251
+ label: `page module ${pageModuleId}`,
3252
+ routes: moduleRoutes
3253
+ });
3254
+ }
3255
+ return conflicts;
3256
+ }
3257
+ function formatRouteConflicts(conflicts) {
3258
+ return conflicts.map((conflict) => `- ${conflict.label}: ${conflict.routes.map(describeRouteConflict).join(", ")}`).join("\n");
3259
+ }
3260
+ //#endregion
3261
+ //#region source/core/routing/route-resolution.ts
3262
+ async function resolveRoutePages(routes, plugins, ctx) {
3263
+ const routeToken = Symbol("clarifyRouteToken");
3264
+ const resolvedPages = await runHooks(plugins, "pages:resolved", routes.map((route, routeIndex) => ({
3265
+ path: route.path,
3266
+ filePath: route.source.filePath,
3267
+ frontmatter: route.source.frontmatter ?? {},
3268
+ content: route.source.content ?? "",
3269
+ [routeToken]: routeIndex
3270
+ })), ctx);
3271
+ const pageByRouteIndex = /* @__PURE__ */ new Map();
3272
+ for (const page of resolvedPages) {
3273
+ const routeIndex = page[routeToken];
3274
+ if (!Number.isInteger(routeIndex) || routeIndex < 0 || routeIndex >= routes.length) throw new Error("[clarify] pages:resolved hooks must preserve each page route identity");
3275
+ if (pageByRouteIndex.has(routeIndex)) throw new Error("[clarify] pages:resolved hooks returned the same page route identity more than once");
3276
+ pageByRouteIndex.set(routeIndex, page);
3277
+ }
3278
+ if (pageByRouteIndex.size !== routes.length) throw new Error("[clarify] pages:resolved hooks must return exactly one page for each route");
3279
+ return routes.map((route, routeIndex) => {
3280
+ const page = pageByRouteIndex.get(routeIndex);
3281
+ return {
3282
+ ...route,
3283
+ source: {
3284
+ ...route.source,
3285
+ frontmatter: page.frontmatter,
3286
+ content: page.content
3287
+ }
3288
+ };
3289
+ });
3290
+ }
3291
+ function buildNavigationTree(routes, ctx) {
3292
+ const tabs = ctx.projectConfig.navigation?.tabs;
3293
+ if (!tabs) return {
3294
+ kind: "flat",
3295
+ nodes: buildNavigation(routes)
3296
+ };
3297
+ if (!ctx.projectConfig.locales) return buildNavigationFromTabsConfig(routes, tabs);
3298
+ return buildLocalizedNavigationFromTabsConfig(routes, tabs, ctx.projectConfig.locales) ?? {
3299
+ kind: "localized-tabbed",
3300
+ locales: {}
3301
+ };
3302
+ }
3303
+ function assertNoRouteConflicts(routes) {
3304
+ const conflicts = findRouteConflicts(routes);
3305
+ if (conflicts.length > 0) throw new Error(`[clarify] Route conflicts prevent the site from being built:\n${formatRouteConflicts(conflicts)}`);
3306
+ }
3307
+ function createRouteConflictDiagnostic(label, routes) {
3308
+ return {
3309
+ kind: "route-load",
3310
+ title: "Route conflict",
3311
+ message: `This page cannot be rendered because ${label} is used by multiple routes.`,
3312
+ details: routes.map(describeRouteConflict).join("\n")
3313
+ };
3314
+ }
3315
+ function createDevRouteConflictRoutes(routes) {
3316
+ const conflicts = findRouteConflicts(routes);
3317
+ if (conflicts.length === 0) return routes;
3318
+ const conflictedRoutes = /* @__PURE__ */ new Map();
3319
+ for (const conflict of conflicts) for (const route of conflict.routes) conflictedRoutes.set(route, conflict);
3320
+ function createConflictRoute(route, conflict) {
3321
+ return {
3322
+ ...route,
3323
+ module: {
3324
+ ...route.module,
3325
+ pageVirtualModuleId: `${route.module.pageVirtualModuleId}__conflict`
3326
+ },
3327
+ diagnostic: createRouteConflictDiagnostic(conflict.label, conflict.routes)
3328
+ };
3329
+ }
3330
+ const usedPaths = /* @__PURE__ */ new Set();
3331
+ return routes.flatMap((route) => {
3332
+ const conflict = conflictedRoutes.get(route);
3333
+ if (!conflict) return [route];
3334
+ if (usedPaths.has(route.path)) return [];
3335
+ usedPaths.add(route.path);
3336
+ return [createConflictRoute(route, conflict)];
3337
+ });
3338
+ }
3339
+ function handleRouteConflicts(routes, development) {
3340
+ if (development) return createDevRouteConflictRoutes(routes);
3341
+ assertNoRouteConflicts(routes);
3342
+ return routes;
3343
+ }
3344
+ async function resolveRouteState(routes, plugins, ctx, development) {
3345
+ const tabs = ctx.projectConfig.navigation?.tabs;
3346
+ const configuredRoutes = applyConfiguredPageRoutePaths(routes, tabs, ctx.projectConfig.locales);
3347
+ const resolved = await runHooks(plugins, "routes:resolved", {
3348
+ routes: configuredRoutes,
3349
+ navigation: buildNavigationTree(configuredRoutes, ctx)
3350
+ }, ctx);
3351
+ const finalRoutes = handleRouteConflicts(resolved.routes, development);
3352
+ return {
3353
+ routes: finalRoutes,
3354
+ navigation: finalRoutes === resolved.routes ? resolved.navigation : buildNavigationTree(finalRoutes, ctx)
3355
+ };
3356
+ }
3357
+ //#endregion
3167
3358
  //#region source/core/runtime/env-types.ts
3168
3359
  var GENERATED_FILE_NAME = "clarify-env.d.ts";
3169
3360
  /**
@@ -3704,7 +3895,7 @@ var ClarifyEngine = class {
3704
3895
  async discoverSite() {
3705
3896
  const plugins = this.ctx.plugins;
3706
3897
  const contentRoot = this.ctx.contentRoot;
3707
- const { locales, navigation: navigationConfig } = this.ctx.projectConfig;
3898
+ const { locales } = this.ctx.projectConfig;
3708
3899
  let routes = await runPhase(plugins, "site:discover", this.ctx, async () => {
3709
3900
  const options = { contentProcessor: createProjectContentProcessor(plugins, this.ctx) };
3710
3901
  const discovered = locales ? await findLocalizedContentRoutes(contentRoot, locales, options) : await findContentRoutes(contentRoot, contentRoot, options);
@@ -3712,43 +3903,10 @@ var ClarifyEngine = class {
3712
3903
  contentRoot,
3713
3904
  routes: discovered
3714
3905
  }, this.ctx);
3715
- return runHooks(plugins, "routes:discovered", result.routes, this.ctx);
3906
+ return await runHooks(plugins, "routes:discovered", result.routes, this.ctx);
3716
3907
  });
3717
- routes = await runPhase(plugins, "content:process", this.ctx, async () => {
3718
- const pages = routes.map((route) => ({
3719
- path: route.path,
3720
- filePath: route.source.filePath,
3721
- frontmatter: route.source.frontmatter ?? {},
3722
- content: route.source.content ?? ""
3723
- }));
3724
- const resolvedPages = await runHooks(plugins, "pages:resolved", pages, this.ctx);
3725
- const pageByPath = new Map(resolvedPages.map((p) => [p.path, p]));
3726
- return routes.map((route) => {
3727
- const page = pageByPath.get(route.path);
3728
- if (!page) return route;
3729
- return {
3730
- ...route,
3731
- source: {
3732
- ...route.source,
3733
- frontmatter: page.frontmatter,
3734
- content: page.content
3735
- }
3736
- };
3737
- });
3738
- });
3739
- const tabs = navigationConfig?.tabs;
3740
- routes = applyConfiguredPageRoutePaths(routes, tabs, locales);
3741
- const navigation = tabs ? locales ? buildLocalizedNavigationFromTabsConfig(routes, tabs, locales) ?? {
3742
- kind: "localized-tabbed",
3743
- locales: {}
3744
- } : buildNavigationFromTabsConfig(routes, tabs) : {
3745
- kind: "flat",
3746
- nodes: buildNavigation(routes)
3747
- };
3748
- const resolved = await runHooks(plugins, "routes:resolved", {
3749
- routes,
3750
- navigation
3751
- }, this.ctx);
3908
+ routes = await runPhase(plugins, "content:process", this.ctx, () => resolveRoutePages(routes, plugins, this.ctx));
3909
+ const resolved = await resolveRouteState(routes, plugins, this.ctx, this.runtime.command === "serve");
3752
3910
  this.ctx.routes = resolved.routes;
3753
3911
  this.ctx.navigation = resolved.navigation;
3754
3912
  }
@@ -3798,8 +3956,8 @@ var ClarifyEngine = class {
3798
3956
  return this.buildEnabled;
3799
3957
  }
3800
3958
  async configureDevServer(server) {
3801
- await runPhase(this.plugins, "dev:server", this.ctx, async () => {
3802
- await runDevConfigureServerHooks(this.plugins, server, this.ctx);
3959
+ return runPhase(this.plugins, "dev:server", this.ctx, () => {
3960
+ return runDevConfigureServerHooks(this.plugins, server, this.ctx);
3803
3961
  });
3804
3962
  }
3805
3963
  async runBuildDone() {
@@ -3924,6 +4082,12 @@ Object.defineProperty(exports, "deserializeSearchIndex", {
3924
4082
  return deserializeSearchIndex;
3925
4083
  }
3926
4084
  });
4085
+ Object.defineProperty(exports, "findRouteConflicts", {
4086
+ enumerable: true,
4087
+ get: function() {
4088
+ return findRouteConflicts;
4089
+ }
4090
+ });
3927
4091
  Object.defineProperty(exports, "searchMcpIndex", {
3928
4092
  enumerable: true,
3929
4093
  get: function() {
@@ -3931,4 +4095,4 @@ Object.defineProperty(exports, "searchMcpIndex", {
3931
4095
  }
3932
4096
  });
3933
4097
 
3934
- //# sourceMappingURL=engine-Cgc71w13.cjs.map
4098
+ //# sourceMappingURL=engine-DVw8CV5R.cjs.map