@clarify-labs/cli 0.9.2 → 0.9.4

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.
@@ -3,6 +3,7 @@ import { dirname, extname, isAbsolute, join, relative, resolve } from "node:path
3
3
  import mdxPlugin from "@mdx-js/rollup";
4
4
  import tailwindcss from "@tailwindcss/vite";
5
5
  import react from "@vitejs/plugin-react";
6
+ import { compile } from "@mdx-js/mdx";
6
7
  import GithubSlugger, { slug } from "github-slugger";
7
8
  import { toString } from "mdast-util-to-string";
8
9
  import shiki from "shiki";
@@ -17,6 +18,80 @@ import { tmpdir } from "node:os";
17
18
  import { z } from "zod";
18
19
  import { build, loadConfigFromFile } from "vite";
19
20
  import { createRequire } from "node:module";
21
+ var cliPackageVersion = {
22
+ name: "@clarify-labs/cli",
23
+ version: "0.9.4",
24
+ license: "AGPL-3.0-only",
25
+ type: "module",
26
+ repository: {
27
+ "type": "git",
28
+ "url": "https://github.com/taicode-labs/clarify",
29
+ "directory": "packages/cli"
30
+ },
31
+ main: "output/index.cjs",
32
+ module: "output/index.js",
33
+ types: "output/index.d.ts",
34
+ bin: { "clarify": "bin/clarify.js" },
35
+ exports: {
36
+ ".": {
37
+ "types": "./output/index.d.ts",
38
+ "import": "./output/index.js",
39
+ "require": "./output/index.cjs",
40
+ "default": "./output/index.js"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ files: [
45
+ "LICENSE",
46
+ "bin",
47
+ "output"
48
+ ],
49
+ scripts: {
50
+ "dev": "vite build --watch",
51
+ "build": "vite build",
52
+ "lint": "eslint source/",
53
+ "lint:fix": "eslint source/ --fix",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest"
57
+ },
58
+ dependencies: {
59
+ "@apidevtools/swagger-parser": "^12.1.0",
60
+ "@clarify-labs/renderer": "workspace:*",
61
+ "@clarify-labs/templates": "workspace:*",
62
+ "@mdx-js/mdx": "^3.1.1",
63
+ "@mdx-js/rollup": "^3.1.1",
64
+ "@tailwindcss/vite": "^4.1.11",
65
+ "@vitejs/plugin-react": "^6.0.2",
66
+ "cac": "^7.0.0",
67
+ "github-slugger": "^2.0.0",
68
+ "gray-matter": "^4.0.3",
69
+ "mdast-util-to-string": "^4.0.0",
70
+ "mermaid": "^11.4.0",
71
+ "openapi-types": "^12.1.3",
72
+ "pagefind": "^1.5.2",
73
+ "react": "^19.1.0",
74
+ "react-dom": "^19.1.0",
75
+ "react-router": "^7.0.0",
76
+ "react-router-dom": "^7.0.0",
77
+ "react-zoom-pan-pinch": "^4.0.3",
78
+ "remark": "^15.0.1",
79
+ "remark-gfm": "^4.0.1",
80
+ "shiki": "0.14.7",
81
+ "unist-util-visit": "^5.1.0",
82
+ "vite": "^8.0.15",
83
+ "yaml": "^2.9.0",
84
+ "zod": "^4.4.3"
85
+ },
86
+ devDependencies: {
87
+ "@types/react": "^19.1.8",
88
+ "typescript": "^5.8.3",
89
+ "vite-plugin-dts": "^4.5.4",
90
+ "vitest": "^4.1.9"
91
+ }
92
+ }.version ?? "0.0.0";
93
+ var cliPackageVersionWithCaret = `^${cliPackageVersion}`;
94
+ //#endregion
20
95
  //#region source/core/utils.ts
21
96
  function escapeHtml(text) {
22
97
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -105,6 +180,32 @@ var rehypePlugins = [
105
180
  rehypeParseCodeBlocks,
106
181
  rehypeShiki
107
182
  ];
183
+ function formatMdxDiagnostic(error) {
184
+ if (error instanceof Error) return error.message;
185
+ return String(error);
186
+ }
187
+ async function compileMdxContent(content, filePath, projectRoot) {
188
+ try {
189
+ await compile(content, {
190
+ jsx: true,
191
+ providerImportSource: "@clarify-labs/renderer",
192
+ remarkPlugins,
193
+ rehypePlugins
194
+ });
195
+ return { ok: true };
196
+ } catch (error) {
197
+ return {
198
+ ok: false,
199
+ diagnostic: {
200
+ kind: "mdx",
201
+ title: "MDX syntax error",
202
+ message: "This page could not be compiled. Fix the MDX syntax or component usage above, then reload the route.",
203
+ filePath: filePath && projectRoot ? filePath.replace(projectRoot + "/", "").replace(/^\/+/, "") : filePath ? filePath.replace(/^[A-Za-z]:\//, "").replace(/^\/+/, "") : void 0,
204
+ details: formatMdxDiagnostic(error)
205
+ }
206
+ };
207
+ }
208
+ }
108
209
  //#endregion
109
210
  //#region source/plugins/content-artifacts/artifacts.ts
110
211
  var UTF8_SIGNATURE = "";
@@ -553,23 +654,25 @@ async function findContentRoutes(dir, base = dir, options = {}) {
553
654
  const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
554
655
  const source = readFileSync(fullPath, "utf-8");
555
656
  const { frontmatter, content } = await (options.contentProcessor ?? createContentProcessor()).processMdx(source, fullPath);
657
+ const mdxResult = await compileMdxContent(content, fullPath, base);
556
658
  let title = typeof frontmatter.title === "string" ? frontmatter.title : "";
557
659
  if (!title) {
558
660
  const lastPart = pathParts[pathParts.length - 1] ?? "";
559
661
  title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : extractH1(content) : lastPart) || "Untitled";
560
662
  }
561
663
  routes.push({
664
+ kind: "mdx",
665
+ title,
562
666
  path: cleanPath,
563
667
  basePath: cleanPath,
564
668
  filePath: fullPath,
565
669
  virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/"),
566
- title,
567
670
  description: typeof frontmatter.description === "string" ? frontmatter.description : void 0,
568
671
  keywords: frontmatterKeywords(frontmatter),
569
- kind: "mdx",
570
672
  frontmatter,
571
673
  content,
572
- sections: extractMdxSections(content)
674
+ sections: extractMdxSections(content),
675
+ diagnostic: mdxResult.ok ? void 0 : mdxResult.diagnostic
573
676
  });
574
677
  }
575
678
  }
@@ -903,10 +1006,11 @@ async function readOpenAPISpec(filePath, contentProcessor) {
903
1006
  return {
904
1007
  ok: false,
905
1008
  diagnostic: {
1009
+ kind: "openapi",
906
1010
  title: "OpenAPI spec parse failed",
907
1011
  message: `Clarify could not parse ${filePath}.`,
908
1012
  filePath,
909
- cause: errorMessage(error)
1013
+ details: errorMessage(error)
910
1014
  }
911
1015
  };
912
1016
  }
@@ -927,17 +1031,19 @@ function resolveVirtualId(id) {
927
1031
  function stripVirtualPrefix(id) {
928
1032
  return id.startsWith("\0") ? id.slice(1) : id;
929
1033
  }
930
- function generateConfigModule(projectConfig, buildOptions) {
931
- return `export const config = ${JSON.stringify({
1034
+ function generateConfigModule(projectConfig, buildOptions, version) {
1035
+ const runtimeConfig = {
932
1036
  ...projectConfig,
933
1037
  ...buildOptions
934
- })};`;
1038
+ };
1039
+ if (version) runtimeConfig.version = version;
1040
+ return `export const config = ${JSON.stringify(runtimeConfig)};`;
935
1041
  }
936
1042
  function moduleSpecifier(value) {
937
1043
  return JSON.stringify(value);
938
1044
  }
939
1045
  function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
940
- const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
1046
+ const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';`;
941
1047
  const routesArray = routes.map((r, i) => {
942
1048
  const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
943
1049
  id: s.id,
@@ -955,7 +1061,7 @@ function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode =
955
1061
  const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
956
1062
  const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
957
1063
  const sourceUrl = r.sourceUrl ? `, sourceUrl: ${JSON.stringify(r.sourceUrl)}` : "";
958
- const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
1064
+ const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.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 }) }))`;
959
1065
  const lazy = mode === "client" ? ", lazy: true" : "";
960
1066
  return ` { path: ${JSON.stringify(r.path)}, title: ${JSON.stringify(r.title)}, component: ${component}${lazy}, kind: ${JSON.stringify(r.kind)}${basePath}${locale}${isFallback}${isBareAlias}${alternates}${description}${keywords}${sections}${contentArtifactUrl}${sourceUrl} }`;
961
1067
  }).join(",\n");
@@ -1014,11 +1120,25 @@ function createRuntimeSlotsModule(plugins = [], root = process.cwd()) {
1014
1120
  function createSlotModule() {
1015
1121
  return `export { useSlot } from '@clarify-labs/renderer';\n`;
1016
1122
  }
1123
+ function generateMdxErrorModule(diagnostic) {
1124
+ return `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';
1125
+
1126
+ export const contentDiagnostic = ${JSON.stringify({
1127
+ kind: diagnostic.kind ?? "mdx",
1128
+ title: diagnostic.title,
1129
+ message: diagnostic.message,
1130
+ filePath: diagnostic.filePath,
1131
+ details: diagnostic.details
1132
+ })};
1133
+
1134
+ export default createContentDiagnosticComponent(contentDiagnostic);
1135
+ `;
1136
+ }
1017
1137
  function buildVirtualModules(args) {
1018
1138
  const modules = /* @__PURE__ */ new Map();
1019
1139
  const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
1020
1140
  const allPlugins = [...args.plugins ?? []];
1021
- modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
1141
+ modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions, args.version));
1022
1142
  modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
1023
1143
  modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
1024
1144
  modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
@@ -1026,7 +1146,10 @@ function buildVirtualModules(args) {
1026
1146
  modules.set(VIRTUAL_SLOT, createSlotModule());
1027
1147
  modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
1028
1148
  modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
1029
- for (const route of args.routes) modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
1149
+ for (const route of args.routes) {
1150
+ const moduleContent = route.diagnostic ? generateMdxErrorModule(route.diagnostic) : `export { default } from ${moduleSpecifier(route.filePath)};`;
1151
+ modules.set(route.virtualModuleId, moduleContent);
1152
+ }
1030
1153
  return modules;
1031
1154
  }
1032
1155
  function generateOpenApiModule() {
@@ -1047,53 +1170,29 @@ function generateOpenAPISpecModule(spec) {
1047
1170
  return `export default ${JSON.stringify(spec)};`;
1048
1171
  }
1049
1172
  function generateOpenAPIPageModule(opts) {
1050
- const { specKey, specRegistryKey, tagFilter } = opts;
1051
- return [
1052
- `import { createElement, useState, useEffect, useRef } from 'react';`,
1053
- `import { OpenApiDocument, useOpenApis } from '@clarify-labs/renderer';`,
1054
- `const SPEC_KEY = ${JSON.stringify(specRegistryKey)};`,
1055
- `const TAG_FILTER = ${JSON.stringify(tagFilter ?? void 0)};`,
1056
- `let loadPromise = null;`,
1057
- `function loadSpec() {`,
1058
- ` if (!loadPromise) loadPromise = import(${JSON.stringify(specVirtualModuleId(specKey))}).then(function(m) { return m.default; });`,
1059
- ` return loadPromise;`,
1060
- `}`,
1061
- `export default function OpenApiRoutePage() {`,
1062
- ` const specs = useOpenApis();`,
1063
- ` const serverSpec = specs[SPEC_KEY];`,
1064
- ` const [spec, setSpec] = useState(serverSpec || null);`,
1065
- ` const mountedRef = useRef(false);`,
1066
- ` useEffect(function() {`,
1067
- ` if (mountedRef.current) return;`,
1068
- ` mountedRef.current = true;`,
1069
- ` if (spec) return;`,
1070
- ` loadSpec().then(setSpec);`,
1071
- ` }, []);`,
1072
- ` if (!spec) return null;`,
1073
- ` return createElement(OpenApiDocument, { spec: spec, tagFilter: TAG_FILTER });`,
1074
- `}`
1075
- ].join("\n");
1173
+ return `import { createOpenApiRouteComponent } from '@clarify-labs/renderer';
1174
+
1175
+ export const routeData = ${JSON.stringify({
1176
+ spec: opts.spec,
1177
+ tagFilter: opts.tagFilter
1178
+ })};
1179
+
1180
+ export default createOpenApiRouteComponent(routeData);
1181
+ `;
1076
1182
  }
1077
1183
  function generateOpenAPIErrorModule(diagnostic) {
1078
- return `import { createElement } from 'react';
1079
- const diagnostic = ${JSON.stringify(diagnostic)};
1080
- export default function OpenApiErrorRoutePage() {
1081
- return createElement('article', { className: 'flex h-full flex-col pt-16 pb-10' },
1082
- createElement('div', { className: 'prose max-w-none flex-auto dark:prose-invert' },
1083
- createElement('p', { className: 'mb-3 text-xs/6 font-medium tracking-widest text-red-500 uppercase dark:text-red-400' }, 'OpenAPI Error'),
1084
- createElement('h1', null, diagnostic.title),
1085
- createElement('p', { className: 'lead' }, diagnostic.message),
1086
- diagnostic.filePath ? createElement('div', { className: 'not-prose mt-6 rounded-lg border border-zinc-200 bg-zinc-50 p-4 text-sm dark:border-white/10 dark:bg-white/5' },
1087
- createElement('div', { className: 'mb-1 font-semibold text-zinc-900 dark:text-white' }, 'File'),
1088
- createElement('code', { className: 'break-all text-zinc-700 dark:text-zinc-300' }, diagnostic.filePath)
1089
- ) : null,
1090
- diagnostic.cause ? createElement('div', { className: 'not-prose mt-4 rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-800 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200' },
1091
- createElement('div', { className: 'mb-1 font-semibold' }, 'Why it happened'),
1092
- createElement('pre', { className: 'whitespace-pre-wrap break-words text-xs' }, diagnostic.cause)
1093
- ) : null
1094
- )
1095
- );
1096
- }`;
1184
+ return `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';
1185
+
1186
+ export const contentDiagnostic = ${JSON.stringify({
1187
+ kind: diagnostic.kind ?? "openapi",
1188
+ title: diagnostic.title,
1189
+ message: diagnostic.message,
1190
+ filePath: diagnostic.filePath,
1191
+ details: diagnostic.details
1192
+ })};
1193
+
1194
+ export default createContentDiagnosticComponent(contentDiagnostic);
1195
+ `;
1097
1196
  }
1098
1197
  //#endregion
1099
1198
  //#region source/plugins/openapi/index.ts
@@ -1199,11 +1298,10 @@ function createOpenAPIPlugin() {
1199
1298
  if (route.kind !== "openapi") continue;
1200
1299
  if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
1201
1300
  else if (route.specFileKey) {
1202
- const base = (route.basePath ?? route.path).replace(/^\//, "");
1203
- const specRegistryKey = route.locale ? `virtual:clarify-page/${route.locale}/${base}` : `virtual:clarify-page/${base}`;
1301
+ const entry = specs.get(route.specFileKey);
1302
+ if (!entry) continue;
1204
1303
  modules.set(route.virtualModuleId, generateOpenAPIPageModule({
1205
- specKey: route.specFileKey,
1206
- specRegistryKey,
1304
+ spec: entry.spec,
1207
1305
  tagFilter: route.openapiTagFilter
1208
1306
  }));
1209
1307
  }
@@ -1871,7 +1969,7 @@ function readJsonBody(req) {
1871
1969
  * Handle a request to the dev route endpoint. Pure function — does not touch
1872
1970
  * the network directly, only reads the request body and writes to `res`.
1873
1971
  */
1874
- async function handleDevRouteRequest(req, res, routes, projectConfig, contentRoot) {
1972
+ async function handleDevRouteRequest(req, res, routes, context) {
1875
1973
  res.setHeader("Content-Type", "application/json; charset=utf-8");
1876
1974
  let body;
1877
1975
  try {
@@ -1883,12 +1981,12 @@ async function handleDevRouteRequest(req, res, routes, projectConfig, contentRoo
1883
1981
  }
1884
1982
  const file = typeof body === "object" && body !== null ? body.file : void 0;
1885
1983
  if (typeof file === "string" && file.length > 0) {
1886
- const route = resolveRouteForFile(file, routes, projectConfig.i18n?.defaultLocale);
1984
+ const route = resolveRouteForFile(file, routes, context.projectConfig.i18n?.defaultLocale);
1887
1985
  if (route) {
1888
1986
  res.end(JSON.stringify(toDevRouteEntry(route)));
1889
1987
  return;
1890
1988
  }
1891
- const inferred = inferRouteForFile(file, contentRoot, projectConfig.i18n?.defaultLocale);
1989
+ const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.i18n?.defaultLocale);
1892
1990
  res.end(JSON.stringify(inferred));
1893
1991
  return;
1894
1992
  }
@@ -1911,18 +2009,6 @@ declare module 'virtual:clarify/slot' {
1911
2009
 
1912
2010
  export function useSlot(): SlotContext
1913
2011
  }
1914
-
1915
- declare module 'virtual:clarify/slots' {
1916
- import type { ComponentType } from 'react'
1917
-
1918
- export type RuntimeSlotEntry = {
1919
- plugin: string
1920
- /** Lazy import factory — yields the default-exported React component. */
1921
- component: () => Promise<{ default: ComponentType }>
1922
- }
1923
-
1924
- export const runtimeSlots: Record<string, RuntimeSlotEntry[]>
1925
- }
1926
2012
  `;
1927
2013
  function generateClarifyEnvDts(_plugins) {
1928
2014
  return CLARIFY_ENV_TEMPLATE;
@@ -1993,6 +2079,48 @@ async function loadClarifyConfig(root, env) {
1993
2079
  if (configFile.endsWith(".json")) return loadJsonConfig(configFile);
1994
2080
  return assertConfigObject((await loadConfigFromFile(env, configFile, root, "silent"))?.config, configFile);
1995
2081
  }
2082
+ //#endregion
2083
+ //#region source/core/project-context.ts
2084
+ async function resolveProjectContext(options = {}, env = {
2085
+ command: "build",
2086
+ mode: "production"
2087
+ }) {
2088
+ const projectRoot = resolve(options.projectRoot ?? process.cwd());
2089
+ const initialBuildOptions = resolveBuildOptions(options);
2090
+ const contentRoot = join(projectRoot, initialBuildOptions.rootDirectory);
2091
+ const configFilePath = findClarifyConfigFile(projectRoot);
2092
+ const config = configFilePath && existsSync(configFilePath) ? await loadClarifyConfig(projectRoot, env) : {};
2093
+ const mergedOptions = {
2094
+ ...config,
2095
+ ...options,
2096
+ projectRoot,
2097
+ rootDirectory: initialBuildOptions.rootDirectory,
2098
+ outputDirectory: initialBuildOptions.outputDirectory
2099
+ };
2100
+ const resolvedProjectConfig = resolveProjectConfig(mergedOptions);
2101
+ const resolvedBuildOptions = resolveBuildOptions(mergedOptions);
2102
+ const projectContext = {
2103
+ projectRoot,
2104
+ contentRoot,
2105
+ version: cliPackageVersion,
2106
+ projectConfig: resolvedProjectConfig,
2107
+ generateOptions: resolvedBuildOptions
2108
+ };
2109
+ return {
2110
+ projectRoot,
2111
+ contentRoot,
2112
+ configFilePath,
2113
+ projectConfig: resolvedProjectConfig,
2114
+ buildOptions: resolvedBuildOptions,
2115
+ config,
2116
+ hookContext: {
2117
+ ...projectContext,
2118
+ routes: [],
2119
+ navigation: []
2120
+ },
2121
+ projectContext
2122
+ };
2123
+ }
1996
2124
  /**
1997
2125
  * File extensions Clarify treats as content files. Kept in sync with the
1998
2126
  * patterns used by route discovery in `parsers/routes.ts` and the OpenAPI
@@ -2009,7 +2137,8 @@ var CONTENT_FILE_EXTENSIONS = [
2009
2137
  /**
2010
2138
  * Build the project-info response payload from the resolved plugin state.
2011
2139
  */
2012
- function buildProjectInfo(projectRoot, contentRoot, projectConfig) {
2140
+ function buildProjectInfo(context) {
2141
+ const { projectRoot, contentRoot, projectConfig } = context;
2013
2142
  const response = {
2014
2143
  configFilenames: CONFIG_FILENAMES,
2015
2144
  contentFileExtensions: CONTENT_FILE_EXTENSIONS,
@@ -2026,9 +2155,9 @@ function buildProjectInfo(projectRoot, contentRoot, projectConfig) {
2026
2155
  * Handle a request to the project-info endpoint. Responds to any method
2027
2156
  * (GET is typical) with the JSON payload — there is no request body to parse.
2028
2157
  */
2029
- function handleProjectInfoRequest(_req, res, projectRoot, contentRoot, projectConfig) {
2158
+ function handleProjectInfoRequest(_req, res, context) {
2030
2159
  res.setHeader("Content-Type", "application/json; charset=utf-8");
2031
- res.end(JSON.stringify(buildProjectInfo(projectRoot, contentRoot, projectConfig)));
2160
+ res.end(JSON.stringify(buildProjectInfo(context)));
2032
2161
  }
2033
2162
  //#endregion
2034
2163
  //#region source/core/site.ts
@@ -2091,16 +2220,18 @@ async function discoverRoutes(root, contentRoot, plugins, ctx) {
2091
2220
  return [...routesWithAlternates, ...bareRoutes];
2092
2221
  }
2093
2222
  async function resolveClarifySite(options = {}, resolveOptions = {}) {
2094
- const root = options.projectRoot ?? process.cwd();
2095
- const projectConfig = resolveProjectConfig(options);
2096
- const generateOptions = resolveBuildOptions(options);
2097
- const contentRoot = join(root, generateOptions.rootDirectory);
2223
+ const context = await resolveProjectContext(options);
2224
+ const root = context.projectRoot;
2225
+ const projectConfig = context.projectConfig;
2226
+ const generateOptions = context.buildOptions;
2227
+ const contentRoot = context.contentRoot;
2098
2228
  const plugins = [...createBuiltinPlugins({ htmlShell: resolveOptions.includeHtmlShellPlugin }), ...options.plugins ?? []];
2099
2229
  const ctx = {
2230
+ ...context.hookContext,
2231
+ projectRoot: root,
2232
+ contentRoot,
2100
2233
  projectConfig,
2101
- generateOptions,
2102
- routes: [],
2103
- navigation: []
2234
+ generateOptions
2104
2235
  };
2105
2236
  setProjectContentProcessor(ctx, createProjectContentProcessor(plugins, ctx));
2106
2237
  let routes = await discoverRoutes(root, contentRoot, plugins, ctx);
@@ -2198,7 +2329,11 @@ function readIndexHtml(outputDirectory) {
2198
2329
  return;
2199
2330
  }
2200
2331
  }
2201
- function injectHtmlLocaleAttributes(html, projectConfig, route) {
2332
+ function resolveProjectConfigFromContext(contextOrConfig) {
2333
+ return "projectConfig" in contextOrConfig ? contextOrConfig.projectConfig : contextOrConfig;
2334
+ }
2335
+ function injectHtmlLocaleAttributes(html, contextOrConfig, route) {
2336
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2202
2337
  const localeCode = route?.locale ?? projectConfig.i18n?.defaultLocale;
2203
2338
  if (!localeCode) return html;
2204
2339
  const dir = (projectConfig.i18n?.locales.find((locale) => locale.code === localeCode))?.dir;
@@ -2209,7 +2344,8 @@ function injectHtmlLocaleAttributes(html, projectConfig, route) {
2209
2344
  return `<html${nextAttributes}>`;
2210
2345
  });
2211
2346
  }
2212
- function routeTitle(projectConfig, route) {
2347
+ function routeTitle(contextOrConfig, route) {
2348
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2213
2349
  const title = route?.title?.trim();
2214
2350
  if (!title || title === projectConfig.title) return projectConfig.title;
2215
2351
  return `${title} - ${projectConfig.title}`;
@@ -2221,24 +2357,26 @@ function setNamedMeta(html, name, content) {
2221
2357
  if (metaPattern.test(html)) return html.replace(metaPattern, meta);
2222
2358
  return html.replace("</head>", ` ${meta}\n </head>`);
2223
2359
  }
2224
- function canonicalUrl(projectConfig, route) {
2360
+ function canonicalUrl(contextOrConfig, route) {
2361
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2225
2362
  if (!projectConfig.siteUrl || !route.alternates || !route.locale) return void 0;
2226
2363
  const prefixedPath = route.alternates[route.locale];
2227
2364
  if (!prefixedPath || prefixedPath === route.path) return void 0;
2228
2365
  return `${projectConfig.siteUrl.replace(/\/+$/, "")}${!projectConfig.routePrefix || projectConfig.routePrefix === "/" ? "" : `/${projectConfig.routePrefix.replace(/^\/+|\/+$/g, "")}`}${prefixedPath}`;
2229
2366
  }
2230
- function injectCanonicalUrl(html, projectConfig, route) {
2231
- const url = route ? canonicalUrl(projectConfig, route) : void 0;
2367
+ function injectCanonicalUrl(html, contextOrConfig, route) {
2368
+ const url = route ? canonicalUrl(contextOrConfig, route) : void 0;
2232
2369
  html = html.replace(/<link\b[^>]*\brel=["']canonical["'][^>]*\/?>\n?/gi, "");
2233
2370
  if (!url) return html;
2234
2371
  return html.replace("</head>", ` <link rel="canonical" href="${escapeHtml(url)}" />\n </head>`);
2235
2372
  }
2236
- function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
2237
- let html = injectHtmlLocaleAttributes(template, projectConfig, route);
2238
- html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(projectConfig, route))}</title>`);
2373
+ function injectSSRIntoTemplate(template, appHtml, contextOrConfig, route) {
2374
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2375
+ let html = injectHtmlLocaleAttributes(template, contextOrConfig, route);
2376
+ html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(contextOrConfig, route))}</title>`);
2239
2377
  html = setNamedMeta(html, "description", route?.description ?? projectConfig.description);
2240
2378
  html = setNamedMeta(html, "keywords", route?.keywords?.join(", "));
2241
- html = injectCanonicalUrl(html, projectConfig, route);
2379
+ html = injectCanonicalUrl(html, contextOrConfig, route);
2242
2380
  const dataPagefindIgnore = route?.isBareAlias ? " data-pagefind-ignore" : "";
2243
2381
  html = html.replace(/<div id="root">([\s\S]*?)<\/div>/, `<div id="root"${dataPagefindIgnore}>${appHtml}</div>`);
2244
2382
  return html;
@@ -2290,12 +2428,12 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
2290
2428
  }
2291
2429
  });
2292
2430
  }
2293
- async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundlePath, failOnError = true) {
2431
+ async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath, failOnError = true) {
2294
2432
  const { render } = await import(pathToFileURL(ssrBundlePath).href);
2295
2433
  const template = readIndexHtml(outputDirectory);
2296
2434
  if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
2297
2435
  for (const route of routes) try {
2298
- const finalHtml = injectSSRIntoTemplate(template, await render(route.path), projectConfig, route);
2436
+ const finalHtml = injectSSRIntoTemplate(template, await render(route.path), contextOrConfig, route);
2299
2437
  for (const outFile of routeOutputFiles(outputDirectory, route)) {
2300
2438
  mkdirSync(dirname(outFile), { recursive: true });
2301
2439
  writeFileSync(outFile, finalHtml, "utf-8");
@@ -2306,6 +2444,33 @@ async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundle
2306
2444
  }
2307
2445
  }
2308
2446
  //#endregion
2447
+ //#region source/core/startup.ts
2448
+ function contentDirectoryLabel(projectRoot, contentRoot) {
2449
+ const relativePath = relative(projectRoot, contentRoot);
2450
+ return relativePath && relativePath !== "." ? relativePath : "source";
2451
+ }
2452
+ function contentFileExampleLabel(projectRoot, contentRoot) {
2453
+ const label = contentDirectoryLabel(projectRoot, contentRoot);
2454
+ return label === "." ? "index.mdx" : `${label}/index.mdx`;
2455
+ }
2456
+ function getStartupHints(options) {
2457
+ const hints = [];
2458
+ if (!options.contentDirExists) {
2459
+ const label = contentDirectoryLabel(options.projectRoot, options.contentRoot);
2460
+ hints.push(`Content directory "${label}" was not found. Create it or pass --content <dir> to point Clarify at a different directory.`);
2461
+ } else if (!options.hasRoutes) {
2462
+ const example = contentFileExampleLabel(options.projectRoot, options.contentRoot);
2463
+ hints.push(`No content pages were found yet. Add ${example} to get started.`);
2464
+ }
2465
+ return hints;
2466
+ }
2467
+ function logStartupHints(options) {
2468
+ const hints = getStartupHints(options);
2469
+ if (hints.length === 0) return;
2470
+ const message = hints.map((hint) => `[clarify] ${hint}`).join("\n");
2471
+ console.warn(message);
2472
+ }
2473
+ //#endregion
2309
2474
  //#region source/core/plugin.ts
2310
2475
  function loadVirtualModule(id, modules) {
2311
2476
  const bareId = stripVirtualPrefix(id);
@@ -2315,13 +2480,23 @@ function clarifyPlugin(options = {}) {
2315
2480
  const root = resolve(options.projectRoot ?? process.cwd());
2316
2481
  let projectConfig = resolveProjectConfig(options);
2317
2482
  let generateOptions = resolveBuildOptions(options);
2318
- const contentRoot = join(root, generateOptions.rootDirectory);
2483
+ let contentRoot = join(root, generateOptions.rootDirectory);
2484
+ let runtimeContext = {
2485
+ projectRoot: root,
2486
+ contentRoot,
2487
+ projectConfig,
2488
+ generateOptions,
2489
+ version: cliPackageVersion
2490
+ };
2319
2491
  const configFilePath = findClarifyConfigFile(root);
2320
2492
  let routes = [];
2321
2493
  let clarifyPlugins = [...createBuiltinPlugins(), ...options.plugins ?? []];
2322
2494
  const ctx = {
2495
+ projectRoot: root,
2496
+ contentRoot,
2323
2497
  projectConfig,
2324
2498
  generateOptions,
2499
+ version: cliPackageVersion,
2325
2500
  routes,
2326
2501
  navigation: []
2327
2502
  };
@@ -2332,11 +2507,22 @@ function clarifyPlugin(options = {}) {
2332
2507
  const site = await resolveClarifySite(overrides ?? options);
2333
2508
  projectConfig = site.projectConfig;
2334
2509
  generateOptions = site.generateOptions;
2510
+ contentRoot = join(root, generateOptions.rootDirectory);
2511
+ runtimeContext = {
2512
+ projectRoot: root,
2513
+ contentRoot,
2514
+ projectConfig,
2515
+ generateOptions,
2516
+ version: cliPackageVersion
2517
+ };
2335
2518
  routes = site.routes;
2336
2519
  resolvedNavigation = site.navigation;
2337
2520
  clarifyPlugins = site.plugins;
2521
+ ctx.projectRoot = root;
2522
+ ctx.contentRoot = contentRoot;
2338
2523
  ctx.projectConfig = projectConfig;
2339
2524
  ctx.generateOptions = generateOptions;
2525
+ ctx.version = cliPackageVersion;
2340
2526
  ctx.routes = routes;
2341
2527
  ctx.navigation = resolvedNavigation;
2342
2528
  }
@@ -2347,7 +2533,8 @@ function clarifyPlugin(options = {}) {
2347
2533
  routes,
2348
2534
  navigation: resolvedNavigation,
2349
2535
  plugins: clarifyPlugins,
2350
- themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor
2536
+ themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor,
2537
+ version: ctx.version
2351
2538
  });
2352
2539
  virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
2353
2540
  }
@@ -2395,6 +2582,12 @@ function clarifyPlugin(options = {}) {
2395
2582
  name: "clarify:core",
2396
2583
  async config() {
2397
2584
  await resolveRoutesAndSpecs();
2585
+ logStartupHints({
2586
+ projectRoot: root,
2587
+ contentRoot,
2588
+ contentDirExists: existsSync(contentRoot),
2589
+ hasRoutes: routes.length > 0
2590
+ });
2398
2591
  return {
2399
2592
  base: projectConfig.assetPrefix,
2400
2593
  build: {
@@ -2414,14 +2607,23 @@ function clarifyPlugin(options = {}) {
2414
2607
  async handleHotUpdate(ctx) {
2415
2608
  const changedFile = isAbsolute(ctx.file) ? ctx.file : join(root, ctx.file);
2416
2609
  if (configFilePath && changedFile === configFilePath) {
2417
- await resolveRoutesAndSpecs({
2418
- ...await loadClarifyConfig(root, {
2419
- command: "serve",
2420
- mode: viteConfig.mode
2421
- }),
2422
- projectRoot: options.projectRoot,
2610
+ const newContext = await resolveProjectContext({
2611
+ ...options,
2612
+ projectRoot: root,
2423
2613
  rootDirectory: options.rootDirectory,
2424
2614
  outputDirectory: options.outputDirectory
2615
+ }, {
2616
+ command: "serve",
2617
+ mode: viteConfig.mode
2618
+ });
2619
+ projectConfig = newContext.projectConfig;
2620
+ generateOptions = newContext.buildOptions;
2621
+ contentRoot = newContext.contentRoot;
2622
+ await resolveRoutesAndSpecs({
2623
+ ...newContext.config,
2624
+ projectRoot: newContext.projectRoot,
2625
+ rootDirectory: newContext.buildOptions.rootDirectory,
2626
+ outputDirectory: newContext.buildOptions.outputDirectory
2425
2627
  });
2426
2628
  await rebuildVirtualModules();
2427
2629
  invalidateVirtualModules(ctx.server);
@@ -2454,12 +2656,13 @@ function clarifyPlugin(options = {}) {
2454
2656
  server.watcher.add(contentRoot);
2455
2657
  if (configFilePath) server.watcher.add(configFilePath);
2456
2658
  server.middlewares.use((req, res, next) => {
2457
- if (req.url === "/dev/query-preview-route" && req.method === "POST") return handleDevRouteRequest(req, res, routes, projectConfig, contentRoot);
2458
- if (req.url === "/dev/project-info" && (req.method === "GET" || req.method === "HEAD")) return handleProjectInfoRequest(req, res, root, contentRoot, projectConfig);
2659
+ if (req.url === "/dev/query-preview-route" && req.method === "POST") return handleDevRouteRequest(req, res, routes, runtimeContext);
2660
+ if (req.url === "/dev/project-info" && (req.method === "GET" || req.method === "HEAD")) return handleProjectInfoRequest(req, res, runtimeContext);
2459
2661
  next();
2460
2662
  });
2461
2663
  const handleContentTreeChange = async (filePath) => {
2462
- const relativeContentFile = relative(contentRoot, isAbsolute(filePath) ? filePath : join(root, filePath));
2664
+ const changedFile = isAbsolute(filePath) ? filePath : join(root, filePath);
2665
+ const relativeContentFile = relative(contentRoot, changedFile);
2463
2666
  if (!(relativeContentFile && !relativeContentFile.startsWith("..") && !isAbsolute(relativeContentFile))) return;
2464
2667
  await refreshDevServer(server);
2465
2668
  };
@@ -2523,7 +2726,7 @@ function clarifyPlugin(options = {}) {
2523
2726
  mdx
2524
2727
  ]);
2525
2728
  const ssrBundlePath = join(ssrOutputDir, "entry-server.js");
2526
- await renderSSGRoutes(routes, projectConfig, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
2729
+ await renderSSGRoutes(routes, runtimeContext, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
2527
2730
  } catch (err) {
2528
2731
  console.error("[clarify] SSG failed:", err);
2529
2732
  if (generateOptions.ssg.failOnError) throw err;
@@ -2539,6 +2742,6 @@ function clarifyPlugin(options = {}) {
2539
2742
  ].flat().filter(Boolean);
2540
2743
  }
2541
2744
  //#endregion
2542
- export { loadClarifyConfig as a, defineConfig as i, createClarifyRuntimeAliases as n, createClarifyTempDir as o, resolveClarifySite as r, clarifyPlugin as t };
2745
+ export { defineConfig as a, cliPackageVersionWithCaret as c, resolveProjectContext as i, createClarifyRuntimeAliases as n, createClarifyTempDir as o, resolveClarifySite as r, cliPackageVersion as s, clarifyPlugin as t };
2543
2746
 
2544
- //# sourceMappingURL=plugin-poS3N83A.js.map
2747
+ //# sourceMappingURL=plugin-Z3fYTMcb.js.map