@clarify-labs/cli 0.9.3 → 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.
@@ -28,6 +28,7 @@ let _tailwindcss_vite = require("@tailwindcss/vite");
28
28
  _tailwindcss_vite = __toESM(_tailwindcss_vite, 1);
29
29
  let _vitejs_plugin_react = require("@vitejs/plugin-react");
30
30
  _vitejs_plugin_react = __toESM(_vitejs_plugin_react, 1);
31
+ let _mdx_js_mdx = require("@mdx-js/mdx");
31
32
  let github_slugger = require("github-slugger");
32
33
  github_slugger = __toESM(github_slugger, 1);
33
34
  let mdast_util_to_string = require("mdast-util-to-string");
@@ -46,6 +47,80 @@ let node_os = require("node:os");
46
47
  let zod = require("zod");
47
48
  let vite = require("vite");
48
49
  let node_module = require("node:module");
50
+ var cliPackageVersion = {
51
+ name: "@clarify-labs/cli",
52
+ version: "0.9.4",
53
+ license: "AGPL-3.0-only",
54
+ type: "module",
55
+ repository: {
56
+ "type": "git",
57
+ "url": "https://github.com/taicode-labs/clarify",
58
+ "directory": "packages/cli"
59
+ },
60
+ main: "output/index.cjs",
61
+ module: "output/index.js",
62
+ types: "output/index.d.ts",
63
+ bin: { "clarify": "bin/clarify.js" },
64
+ exports: {
65
+ ".": {
66
+ "types": "./output/index.d.ts",
67
+ "import": "./output/index.js",
68
+ "require": "./output/index.cjs",
69
+ "default": "./output/index.js"
70
+ },
71
+ "./package.json": "./package.json"
72
+ },
73
+ files: [
74
+ "LICENSE",
75
+ "bin",
76
+ "output"
77
+ ],
78
+ scripts: {
79
+ "dev": "vite build --watch",
80
+ "build": "vite build",
81
+ "lint": "eslint source/",
82
+ "lint:fix": "eslint source/ --fix",
83
+ "typecheck": "tsc -p tsconfig.json --noEmit",
84
+ "test": "vitest run",
85
+ "test:watch": "vitest"
86
+ },
87
+ dependencies: {
88
+ "@apidevtools/swagger-parser": "^12.1.0",
89
+ "@clarify-labs/renderer": "workspace:*",
90
+ "@clarify-labs/templates": "workspace:*",
91
+ "@mdx-js/mdx": "^3.1.1",
92
+ "@mdx-js/rollup": "^3.1.1",
93
+ "@tailwindcss/vite": "^4.1.11",
94
+ "@vitejs/plugin-react": "^6.0.2",
95
+ "cac": "^7.0.0",
96
+ "github-slugger": "^2.0.0",
97
+ "gray-matter": "^4.0.3",
98
+ "mdast-util-to-string": "^4.0.0",
99
+ "mermaid": "^11.4.0",
100
+ "openapi-types": "^12.1.3",
101
+ "pagefind": "^1.5.2",
102
+ "react": "^19.1.0",
103
+ "react-dom": "^19.1.0",
104
+ "react-router": "^7.0.0",
105
+ "react-router-dom": "^7.0.0",
106
+ "react-zoom-pan-pinch": "^4.0.3",
107
+ "remark": "^15.0.1",
108
+ "remark-gfm": "^4.0.1",
109
+ "shiki": "0.14.7",
110
+ "unist-util-visit": "^5.1.0",
111
+ "vite": "^8.0.15",
112
+ "yaml": "^2.9.0",
113
+ "zod": "^4.4.3"
114
+ },
115
+ devDependencies: {
116
+ "@types/react": "^19.1.8",
117
+ "typescript": "^5.8.3",
118
+ "vite-plugin-dts": "^4.5.4",
119
+ "vitest": "^4.1.9"
120
+ }
121
+ }.version ?? "0.0.0";
122
+ var cliPackageVersionWithCaret = `^${cliPackageVersion}`;
123
+ //#endregion
49
124
  //#region source/core/utils.ts
50
125
  function escapeHtml(text) {
51
126
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -134,6 +209,32 @@ var rehypePlugins = [
134
209
  rehypeParseCodeBlocks,
135
210
  rehypeShiki
136
211
  ];
212
+ function formatMdxDiagnostic(error) {
213
+ if (error instanceof Error) return error.message;
214
+ return String(error);
215
+ }
216
+ async function compileMdxContent(content, filePath, projectRoot) {
217
+ try {
218
+ await (0, _mdx_js_mdx.compile)(content, {
219
+ jsx: true,
220
+ providerImportSource: "@clarify-labs/renderer",
221
+ remarkPlugins,
222
+ rehypePlugins
223
+ });
224
+ return { ok: true };
225
+ } catch (error) {
226
+ return {
227
+ ok: false,
228
+ diagnostic: {
229
+ kind: "mdx",
230
+ title: "MDX syntax error",
231
+ message: "This page could not be compiled. Fix the MDX syntax or component usage above, then reload the route.",
232
+ filePath: filePath && projectRoot ? filePath.replace(projectRoot + "/", "").replace(/^\/+/, "") : filePath ? filePath.replace(/^[A-Za-z]:\//, "").replace(/^\/+/, "") : void 0,
233
+ details: formatMdxDiagnostic(error)
234
+ }
235
+ };
236
+ }
237
+ }
137
238
  //#endregion
138
239
  //#region source/plugins/content-artifacts/artifacts.ts
139
240
  var UTF8_SIGNATURE = "";
@@ -582,23 +683,25 @@ async function findContentRoutes(dir, base = dir, options = {}) {
582
683
  const cleanPath = ("/" + pathParts.map((p) => p === "index" ? "" : p).filter(Boolean).join("/")).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
583
684
  const source = (0, node_fs.readFileSync)(fullPath, "utf-8");
584
685
  const { frontmatter, content } = await (options.contentProcessor ?? createContentProcessor()).processMdx(source, fullPath);
686
+ const mdxResult = await compileMdxContent(content, fullPath, base);
585
687
  let title = typeof frontmatter.title === "string" ? frontmatter.title : "";
586
688
  if (!title) {
587
689
  const lastPart = pathParts[pathParts.length - 1] ?? "";
588
690
  title = kebabToTitle(lastPart === "index" ? pathParts.length >= 2 ? pathParts[pathParts.length - 2] : extractH1(content) : lastPart) || "Untitled";
589
691
  }
590
692
  routes.push({
693
+ kind: "mdx",
694
+ title,
591
695
  path: cleanPath,
592
696
  basePath: cleanPath,
593
697
  filePath: fullPath,
594
698
  virtualModuleId: "virtual:clarify-page/" + relativePath.replace(/\.mdx?$/, "").replace(/\/+/g, "/"),
595
- title,
596
699
  description: typeof frontmatter.description === "string" ? frontmatter.description : void 0,
597
700
  keywords: frontmatterKeywords(frontmatter),
598
- kind: "mdx",
599
701
  frontmatter,
600
702
  content,
601
- sections: extractMdxSections(content)
703
+ sections: extractMdxSections(content),
704
+ diagnostic: mdxResult.ok ? void 0 : mdxResult.diagnostic
602
705
  });
603
706
  }
604
707
  }
@@ -932,10 +1035,11 @@ async function readOpenAPISpec(filePath, contentProcessor) {
932
1035
  return {
933
1036
  ok: false,
934
1037
  diagnostic: {
1038
+ kind: "openapi",
935
1039
  title: "OpenAPI spec parse failed",
936
1040
  message: `Clarify could not parse ${filePath}.`,
937
1041
  filePath,
938
- cause: errorMessage(error)
1042
+ details: errorMessage(error)
939
1043
  }
940
1044
  };
941
1045
  }
@@ -956,17 +1060,19 @@ function resolveVirtualId(id) {
956
1060
  function stripVirtualPrefix(id) {
957
1061
  return id.startsWith("\0") ? id.slice(1) : id;
958
1062
  }
959
- function generateConfigModule(projectConfig, buildOptions) {
960
- return `export const config = ${JSON.stringify({
1063
+ function generateConfigModule(projectConfig, buildOptions, version) {
1064
+ const runtimeConfig = {
961
1065
  ...projectConfig,
962
1066
  ...buildOptions
963
- })};`;
1067
+ };
1068
+ if (version) runtimeConfig.version = version;
1069
+ return `export const config = ${JSON.stringify(runtimeConfig)};`;
964
1070
  }
965
1071
  function moduleSpecifier(value) {
966
1072
  return JSON.stringify(value);
967
1073
  }
968
1074
  function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode = "client") {
969
- const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : "";
1075
+ const imports = mode === "server" ? routes.map((r, i) => `import Page${i} from ${moduleSpecifier(r.virtualModuleId)};`).join("\n") : `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';`;
970
1076
  const routesArray = routes.map((r, i) => {
971
1077
  const sections = r.sections && r.sections.length > 0 ? `, sections: ${JSON.stringify(r.sections.map((s) => ({
972
1078
  id: s.id,
@@ -984,7 +1090,7 @@ function generateRoutesModule(routes, resolvedNavigation, projectConfig, mode =
984
1090
  const description = r.description ? `, description: ${JSON.stringify(r.description)}` : "";
985
1091
  const keywords = r.keywords && r.keywords.length > 0 ? `, keywords: ${JSON.stringify(r.keywords)}` : "";
986
1092
  const sourceUrl = r.sourceUrl ? `, sourceUrl: ${JSON.stringify(r.sourceUrl)}` : "";
987
- const component = mode === "server" ? `Page${i}` : `() => import(${moduleSpecifier(r.virtualModuleId)})`;
1093
+ 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 }) }))`;
988
1094
  const lazy = mode === "client" ? ", lazy: true" : "";
989
1095
  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} }`;
990
1096
  }).join(",\n");
@@ -1043,11 +1149,25 @@ function createRuntimeSlotsModule(plugins = [], root = process.cwd()) {
1043
1149
  function createSlotModule() {
1044
1150
  return `export { useSlot } from '@clarify-labs/renderer';\n`;
1045
1151
  }
1152
+ function generateMdxErrorModule(diagnostic) {
1153
+ return `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';
1154
+
1155
+ export const contentDiagnostic = ${JSON.stringify({
1156
+ kind: diagnostic.kind ?? "mdx",
1157
+ title: diagnostic.title,
1158
+ message: diagnostic.message,
1159
+ filePath: diagnostic.filePath,
1160
+ details: diagnostic.details
1161
+ })};
1162
+
1163
+ export default createContentDiagnosticComponent(contentDiagnostic);
1164
+ `;
1165
+ }
1046
1166
  function buildVirtualModules(args) {
1047
1167
  const modules = /* @__PURE__ */ new Map();
1048
1168
  const clientEntryModule = createClientEntryModule({ themeEditor: args.themeEditor });
1049
1169
  const allPlugins = [...args.plugins ?? []];
1050
- modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions));
1170
+ modules.set(VIRTUAL_CONFIG, generateConfigModule(args.projectConfig, args.generateOptions, args.version));
1051
1171
  modules.set(VIRTUAL_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "client"));
1052
1172
  modules.set(VIRTUAL_SERVER_ROUTES, generateRoutesModule(args.routes, args.navigation, args.projectConfig, "server"));
1053
1173
  modules.set(VIRTUAL_SLOTS, createRuntimeSlotsModule(allPlugins, args.generateOptions.projectRoot));
@@ -1055,7 +1175,10 @@ function buildVirtualModules(args) {
1055
1175
  modules.set(VIRTUAL_SLOT, createSlotModule());
1056
1176
  modules.set(VIRTUAL_CLIENT_ENTRY, clientEntryModule);
1057
1177
  modules.set(RESOLVED_CLIENT_ENTRY, clientEntryModule);
1058
- for (const route of args.routes) modules.set(route.virtualModuleId, `export { default } from ${moduleSpecifier(route.filePath)};`);
1178
+ for (const route of args.routes) {
1179
+ const moduleContent = route.diagnostic ? generateMdxErrorModule(route.diagnostic) : `export { default } from ${moduleSpecifier(route.filePath)};`;
1180
+ modules.set(route.virtualModuleId, moduleContent);
1181
+ }
1059
1182
  return modules;
1060
1183
  }
1061
1184
  function generateOpenApiModule() {
@@ -1076,53 +1199,29 @@ function generateOpenAPISpecModule(spec) {
1076
1199
  return `export default ${JSON.stringify(spec)};`;
1077
1200
  }
1078
1201
  function generateOpenAPIPageModule(opts) {
1079
- const { specKey, specRegistryKey, tagFilter } = opts;
1080
- return [
1081
- `import { createElement, useState, useEffect, useRef } from 'react';`,
1082
- `import { OpenApiDocument, useOpenApis } from '@clarify-labs/renderer';`,
1083
- `const SPEC_KEY = ${JSON.stringify(specRegistryKey)};`,
1084
- `const TAG_FILTER = ${JSON.stringify(tagFilter ?? void 0)};`,
1085
- `let loadPromise = null;`,
1086
- `function loadSpec() {`,
1087
- ` if (!loadPromise) loadPromise = import(${JSON.stringify(specVirtualModuleId(specKey))}).then(function(m) { return m.default; });`,
1088
- ` return loadPromise;`,
1089
- `}`,
1090
- `export default function OpenApiRoutePage() {`,
1091
- ` const specs = useOpenApis();`,
1092
- ` const serverSpec = specs[SPEC_KEY];`,
1093
- ` const [spec, setSpec] = useState(serverSpec || null);`,
1094
- ` const mountedRef = useRef(false);`,
1095
- ` useEffect(function() {`,
1096
- ` if (mountedRef.current) return;`,
1097
- ` mountedRef.current = true;`,
1098
- ` if (spec) return;`,
1099
- ` loadSpec().then(setSpec);`,
1100
- ` }, []);`,
1101
- ` if (!spec) return null;`,
1102
- ` return createElement(OpenApiDocument, { spec: spec, tagFilter: TAG_FILTER });`,
1103
- `}`
1104
- ].join("\n");
1202
+ return `import { createOpenApiRouteComponent } from '@clarify-labs/renderer';
1203
+
1204
+ export const routeData = ${JSON.stringify({
1205
+ spec: opts.spec,
1206
+ tagFilter: opts.tagFilter
1207
+ })};
1208
+
1209
+ export default createOpenApiRouteComponent(routeData);
1210
+ `;
1105
1211
  }
1106
1212
  function generateOpenAPIErrorModule(diagnostic) {
1107
- return `import { createElement } from 'react';
1108
- const diagnostic = ${JSON.stringify(diagnostic)};
1109
- export default function OpenApiErrorRoutePage() {
1110
- return createElement('article', { className: 'flex h-full flex-col pt-16 pb-10' },
1111
- createElement('div', { className: 'prose max-w-none flex-auto dark:prose-invert' },
1112
- createElement('p', { className: 'mb-3 text-xs/6 font-medium tracking-widest text-red-500 uppercase dark:text-red-400' }, 'OpenAPI Error'),
1113
- createElement('h1', null, diagnostic.title),
1114
- createElement('p', { className: 'lead' }, diagnostic.message),
1115
- 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' },
1116
- createElement('div', { className: 'mb-1 font-semibold text-zinc-900 dark:text-white' }, 'File'),
1117
- createElement('code', { className: 'break-all text-zinc-700 dark:text-zinc-300' }, diagnostic.filePath)
1118
- ) : null,
1119
- 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' },
1120
- createElement('div', { className: 'mb-1 font-semibold' }, 'Why it happened'),
1121
- createElement('pre', { className: 'whitespace-pre-wrap break-words text-xs' }, diagnostic.cause)
1122
- ) : null
1123
- )
1124
- );
1125
- }`;
1213
+ return `import { createContentDiagnosticComponent } from '@clarify-labs/renderer';
1214
+
1215
+ export const contentDiagnostic = ${JSON.stringify({
1216
+ kind: diagnostic.kind ?? "openapi",
1217
+ title: diagnostic.title,
1218
+ message: diagnostic.message,
1219
+ filePath: diagnostic.filePath,
1220
+ details: diagnostic.details
1221
+ })};
1222
+
1223
+ export default createContentDiagnosticComponent(contentDiagnostic);
1224
+ `;
1126
1225
  }
1127
1226
  //#endregion
1128
1227
  //#region source/plugins/openapi/index.ts
@@ -1228,11 +1327,10 @@ function createOpenAPIPlugin() {
1228
1327
  if (route.kind !== "openapi") continue;
1229
1328
  if (route.diagnostic) modules.set(route.virtualModuleId, generateOpenAPIErrorModule(route.diagnostic));
1230
1329
  else if (route.specFileKey) {
1231
- const base = (route.basePath ?? route.path).replace(/^\//, "");
1232
- const specRegistryKey = route.locale ? `virtual:clarify-page/${route.locale}/${base}` : `virtual:clarify-page/${base}`;
1330
+ const entry = specs.get(route.specFileKey);
1331
+ if (!entry) continue;
1233
1332
  modules.set(route.virtualModuleId, generateOpenAPIPageModule({
1234
- specKey: route.specFileKey,
1235
- specRegistryKey,
1333
+ spec: entry.spec,
1236
1334
  tagFilter: route.openapiTagFilter
1237
1335
  }));
1238
1336
  }
@@ -1900,7 +1998,7 @@ function readJsonBody(req) {
1900
1998
  * Handle a request to the dev route endpoint. Pure function — does not touch
1901
1999
  * the network directly, only reads the request body and writes to `res`.
1902
2000
  */
1903
- async function handleDevRouteRequest(req, res, routes, projectConfig, contentRoot) {
2001
+ async function handleDevRouteRequest(req, res, routes, context) {
1904
2002
  res.setHeader("Content-Type", "application/json; charset=utf-8");
1905
2003
  let body;
1906
2004
  try {
@@ -1912,12 +2010,12 @@ async function handleDevRouteRequest(req, res, routes, projectConfig, contentRoo
1912
2010
  }
1913
2011
  const file = typeof body === "object" && body !== null ? body.file : void 0;
1914
2012
  if (typeof file === "string" && file.length > 0) {
1915
- const route = resolveRouteForFile(file, routes, projectConfig.i18n?.defaultLocale);
2013
+ const route = resolveRouteForFile(file, routes, context.projectConfig.i18n?.defaultLocale);
1916
2014
  if (route) {
1917
2015
  res.end(JSON.stringify(toDevRouteEntry(route)));
1918
2016
  return;
1919
2017
  }
1920
- const inferred = inferRouteForFile(file, contentRoot, projectConfig.i18n?.defaultLocale);
2018
+ const inferred = inferRouteForFile(file, context.contentRoot, context.projectConfig.i18n?.defaultLocale);
1921
2019
  res.end(JSON.stringify(inferred));
1922
2020
  return;
1923
2021
  }
@@ -1940,18 +2038,6 @@ declare module 'virtual:clarify/slot' {
1940
2038
 
1941
2039
  export function useSlot(): SlotContext
1942
2040
  }
1943
-
1944
- declare module 'virtual:clarify/slots' {
1945
- import type { ComponentType } from 'react'
1946
-
1947
- export type RuntimeSlotEntry = {
1948
- plugin: string
1949
- /** Lazy import factory — yields the default-exported React component. */
1950
- component: () => Promise<{ default: ComponentType }>
1951
- }
1952
-
1953
- export const runtimeSlots: Record<string, RuntimeSlotEntry[]>
1954
- }
1955
2041
  `;
1956
2042
  function generateClarifyEnvDts(_plugins) {
1957
2043
  return CLARIFY_ENV_TEMPLATE;
@@ -2022,6 +2108,48 @@ async function loadClarifyConfig(root, env) {
2022
2108
  if (configFile.endsWith(".json")) return loadJsonConfig(configFile);
2023
2109
  return assertConfigObject((await (0, vite.loadConfigFromFile)(env, configFile, root, "silent"))?.config, configFile);
2024
2110
  }
2111
+ //#endregion
2112
+ //#region source/core/project-context.ts
2113
+ async function resolveProjectContext(options = {}, env = {
2114
+ command: "build",
2115
+ mode: "production"
2116
+ }) {
2117
+ const projectRoot = (0, node_path.resolve)(options.projectRoot ?? process.cwd());
2118
+ const initialBuildOptions = resolveBuildOptions(options);
2119
+ const contentRoot = (0, node_path.join)(projectRoot, initialBuildOptions.rootDirectory);
2120
+ const configFilePath = findClarifyConfigFile(projectRoot);
2121
+ const config = configFilePath && (0, node_fs.existsSync)(configFilePath) ? await loadClarifyConfig(projectRoot, env) : {};
2122
+ const mergedOptions = {
2123
+ ...config,
2124
+ ...options,
2125
+ projectRoot,
2126
+ rootDirectory: initialBuildOptions.rootDirectory,
2127
+ outputDirectory: initialBuildOptions.outputDirectory
2128
+ };
2129
+ const resolvedProjectConfig = resolveProjectConfig(mergedOptions);
2130
+ const resolvedBuildOptions = resolveBuildOptions(mergedOptions);
2131
+ const projectContext = {
2132
+ projectRoot,
2133
+ contentRoot,
2134
+ version: cliPackageVersion,
2135
+ projectConfig: resolvedProjectConfig,
2136
+ generateOptions: resolvedBuildOptions
2137
+ };
2138
+ return {
2139
+ projectRoot,
2140
+ contentRoot,
2141
+ configFilePath,
2142
+ projectConfig: resolvedProjectConfig,
2143
+ buildOptions: resolvedBuildOptions,
2144
+ config,
2145
+ hookContext: {
2146
+ ...projectContext,
2147
+ routes: [],
2148
+ navigation: []
2149
+ },
2150
+ projectContext
2151
+ };
2152
+ }
2025
2153
  /**
2026
2154
  * File extensions Clarify treats as content files. Kept in sync with the
2027
2155
  * patterns used by route discovery in `parsers/routes.ts` and the OpenAPI
@@ -2038,7 +2166,8 @@ var CONTENT_FILE_EXTENSIONS = [
2038
2166
  /**
2039
2167
  * Build the project-info response payload from the resolved plugin state.
2040
2168
  */
2041
- function buildProjectInfo(projectRoot, contentRoot, projectConfig) {
2169
+ function buildProjectInfo(context) {
2170
+ const { projectRoot, contentRoot, projectConfig } = context;
2042
2171
  const response = {
2043
2172
  configFilenames: CONFIG_FILENAMES,
2044
2173
  contentFileExtensions: CONTENT_FILE_EXTENSIONS,
@@ -2055,9 +2184,9 @@ function buildProjectInfo(projectRoot, contentRoot, projectConfig) {
2055
2184
  * Handle a request to the project-info endpoint. Responds to any method
2056
2185
  * (GET is typical) with the JSON payload — there is no request body to parse.
2057
2186
  */
2058
- function handleProjectInfoRequest(_req, res, projectRoot, contentRoot, projectConfig) {
2187
+ function handleProjectInfoRequest(_req, res, context) {
2059
2188
  res.setHeader("Content-Type", "application/json; charset=utf-8");
2060
- res.end(JSON.stringify(buildProjectInfo(projectRoot, contentRoot, projectConfig)));
2189
+ res.end(JSON.stringify(buildProjectInfo(context)));
2061
2190
  }
2062
2191
  //#endregion
2063
2192
  //#region source/core/site.ts
@@ -2120,16 +2249,18 @@ async function discoverRoutes(root, contentRoot, plugins, ctx) {
2120
2249
  return [...routesWithAlternates, ...bareRoutes];
2121
2250
  }
2122
2251
  async function resolveClarifySite(options = {}, resolveOptions = {}) {
2123
- const root = options.projectRoot ?? process.cwd();
2124
- const projectConfig = resolveProjectConfig(options);
2125
- const generateOptions = resolveBuildOptions(options);
2126
- const contentRoot = (0, node_path.join)(root, generateOptions.rootDirectory);
2252
+ const context = await resolveProjectContext(options);
2253
+ const root = context.projectRoot;
2254
+ const projectConfig = context.projectConfig;
2255
+ const generateOptions = context.buildOptions;
2256
+ const contentRoot = context.contentRoot;
2127
2257
  const plugins = [...createBuiltinPlugins({ htmlShell: resolveOptions.includeHtmlShellPlugin }), ...options.plugins ?? []];
2128
2258
  const ctx = {
2259
+ ...context.hookContext,
2260
+ projectRoot: root,
2261
+ contentRoot,
2129
2262
  projectConfig,
2130
- generateOptions,
2131
- routes: [],
2132
- navigation: []
2263
+ generateOptions
2133
2264
  };
2134
2265
  setProjectContentProcessor(ctx, createProjectContentProcessor(plugins, ctx));
2135
2266
  let routes = await discoverRoutes(root, contentRoot, plugins, ctx);
@@ -2227,7 +2358,11 @@ function readIndexHtml(outputDirectory) {
2227
2358
  return;
2228
2359
  }
2229
2360
  }
2230
- function injectHtmlLocaleAttributes(html, projectConfig, route) {
2361
+ function resolveProjectConfigFromContext(contextOrConfig) {
2362
+ return "projectConfig" in contextOrConfig ? contextOrConfig.projectConfig : contextOrConfig;
2363
+ }
2364
+ function injectHtmlLocaleAttributes(html, contextOrConfig, route) {
2365
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2231
2366
  const localeCode = route?.locale ?? projectConfig.i18n?.defaultLocale;
2232
2367
  if (!localeCode) return html;
2233
2368
  const dir = (projectConfig.i18n?.locales.find((locale) => locale.code === localeCode))?.dir;
@@ -2238,7 +2373,8 @@ function injectHtmlLocaleAttributes(html, projectConfig, route) {
2238
2373
  return `<html${nextAttributes}>`;
2239
2374
  });
2240
2375
  }
2241
- function routeTitle(projectConfig, route) {
2376
+ function routeTitle(contextOrConfig, route) {
2377
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2242
2378
  const title = route?.title?.trim();
2243
2379
  if (!title || title === projectConfig.title) return projectConfig.title;
2244
2380
  return `${title} - ${projectConfig.title}`;
@@ -2250,24 +2386,26 @@ function setNamedMeta(html, name, content) {
2250
2386
  if (metaPattern.test(html)) return html.replace(metaPattern, meta);
2251
2387
  return html.replace("</head>", ` ${meta}\n </head>`);
2252
2388
  }
2253
- function canonicalUrl(projectConfig, route) {
2389
+ function canonicalUrl(contextOrConfig, route) {
2390
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2254
2391
  if (!projectConfig.siteUrl || !route.alternates || !route.locale) return void 0;
2255
2392
  const prefixedPath = route.alternates[route.locale];
2256
2393
  if (!prefixedPath || prefixedPath === route.path) return void 0;
2257
2394
  return `${projectConfig.siteUrl.replace(/\/+$/, "")}${!projectConfig.routePrefix || projectConfig.routePrefix === "/" ? "" : `/${projectConfig.routePrefix.replace(/^\/+|\/+$/g, "")}`}${prefixedPath}`;
2258
2395
  }
2259
- function injectCanonicalUrl(html, projectConfig, route) {
2260
- const url = route ? canonicalUrl(projectConfig, route) : void 0;
2396
+ function injectCanonicalUrl(html, contextOrConfig, route) {
2397
+ const url = route ? canonicalUrl(contextOrConfig, route) : void 0;
2261
2398
  html = html.replace(/<link\b[^>]*\brel=["']canonical["'][^>]*\/?>\n?/gi, "");
2262
2399
  if (!url) return html;
2263
2400
  return html.replace("</head>", ` <link rel="canonical" href="${escapeHtml(url)}" />\n </head>`);
2264
2401
  }
2265
- function injectSSRIntoTemplate(template, appHtml, projectConfig, route) {
2266
- let html = injectHtmlLocaleAttributes(template, projectConfig, route);
2267
- html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(projectConfig, route))}</title>`);
2402
+ function injectSSRIntoTemplate(template, appHtml, contextOrConfig, route) {
2403
+ const projectConfig = resolveProjectConfigFromContext(contextOrConfig);
2404
+ let html = injectHtmlLocaleAttributes(template, contextOrConfig, route);
2405
+ html = html.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(routeTitle(contextOrConfig, route))}</title>`);
2268
2406
  html = setNamedMeta(html, "description", route?.description ?? projectConfig.description);
2269
2407
  html = setNamedMeta(html, "keywords", route?.keywords?.join(", "));
2270
- html = injectCanonicalUrl(html, projectConfig, route);
2408
+ html = injectCanonicalUrl(html, contextOrConfig, route);
2271
2409
  const dataPagefindIgnore = route?.isBareAlias ? " data-pagefind-ignore" : "";
2272
2410
  html = html.replace(/<div id="root">([\s\S]*?)<\/div>/, `<div id="root"${dataPagefindIgnore}>${appHtml}</div>`);
2273
2411
  return html;
@@ -2319,12 +2457,12 @@ async function buildSSRBundle(root, ssrEntry, ssrOutDir, plugins) {
2319
2457
  }
2320
2458
  });
2321
2459
  }
2322
- async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundlePath, failOnError = true) {
2460
+ async function renderSSGRoutes(routes, contextOrConfig, outputDirectory, ssrBundlePath, failOnError = true) {
2323
2461
  const { render } = await import((0, node_url.pathToFileURL)(ssrBundlePath).href);
2324
2462
  const template = readIndexHtml(outputDirectory);
2325
2463
  if (!template) throw new Error(`[clarify] index.html not found in outputDirectory "${outputDirectory}". Make sure Vite build produces it.`);
2326
2464
  for (const route of routes) try {
2327
- const finalHtml = injectSSRIntoTemplate(template, await render(route.path), projectConfig, route);
2465
+ const finalHtml = injectSSRIntoTemplate(template, await render(route.path), contextOrConfig, route);
2328
2466
  for (const outFile of routeOutputFiles(outputDirectory, route)) {
2329
2467
  (0, node_fs.mkdirSync)((0, node_path.dirname)(outFile), { recursive: true });
2330
2468
  (0, node_fs.writeFileSync)(outFile, finalHtml, "utf-8");
@@ -2335,6 +2473,33 @@ async function renderSSGRoutes(routes, projectConfig, outputDirectory, ssrBundle
2335
2473
  }
2336
2474
  }
2337
2475
  //#endregion
2476
+ //#region source/core/startup.ts
2477
+ function contentDirectoryLabel(projectRoot, contentRoot) {
2478
+ const relativePath = (0, node_path.relative)(projectRoot, contentRoot);
2479
+ return relativePath && relativePath !== "." ? relativePath : "source";
2480
+ }
2481
+ function contentFileExampleLabel(projectRoot, contentRoot) {
2482
+ const label = contentDirectoryLabel(projectRoot, contentRoot);
2483
+ return label === "." ? "index.mdx" : `${label}/index.mdx`;
2484
+ }
2485
+ function getStartupHints(options) {
2486
+ const hints = [];
2487
+ if (!options.contentDirExists) {
2488
+ const label = contentDirectoryLabel(options.projectRoot, options.contentRoot);
2489
+ hints.push(`Content directory "${label}" was not found. Create it or pass --content <dir> to point Clarify at a different directory.`);
2490
+ } else if (!options.hasRoutes) {
2491
+ const example = contentFileExampleLabel(options.projectRoot, options.contentRoot);
2492
+ hints.push(`No content pages were found yet. Add ${example} to get started.`);
2493
+ }
2494
+ return hints;
2495
+ }
2496
+ function logStartupHints(options) {
2497
+ const hints = getStartupHints(options);
2498
+ if (hints.length === 0) return;
2499
+ const message = hints.map((hint) => `[clarify] ${hint}`).join("\n");
2500
+ console.warn(message);
2501
+ }
2502
+ //#endregion
2338
2503
  //#region source/core/plugin.ts
2339
2504
  function loadVirtualModule(id, modules) {
2340
2505
  const bareId = stripVirtualPrefix(id);
@@ -2344,13 +2509,23 @@ function clarifyPlugin(options = {}) {
2344
2509
  const root = (0, node_path.resolve)(options.projectRoot ?? process.cwd());
2345
2510
  let projectConfig = resolveProjectConfig(options);
2346
2511
  let generateOptions = resolveBuildOptions(options);
2347
- const contentRoot = (0, node_path.join)(root, generateOptions.rootDirectory);
2512
+ let contentRoot = (0, node_path.join)(root, generateOptions.rootDirectory);
2513
+ let runtimeContext = {
2514
+ projectRoot: root,
2515
+ contentRoot,
2516
+ projectConfig,
2517
+ generateOptions,
2518
+ version: cliPackageVersion
2519
+ };
2348
2520
  const configFilePath = findClarifyConfigFile(root);
2349
2521
  let routes = [];
2350
2522
  let clarifyPlugins = [...createBuiltinPlugins(), ...options.plugins ?? []];
2351
2523
  const ctx = {
2524
+ projectRoot: root,
2525
+ contentRoot,
2352
2526
  projectConfig,
2353
2527
  generateOptions,
2528
+ version: cliPackageVersion,
2354
2529
  routes,
2355
2530
  navigation: []
2356
2531
  };
@@ -2361,11 +2536,22 @@ function clarifyPlugin(options = {}) {
2361
2536
  const site = await resolveClarifySite(overrides ?? options);
2362
2537
  projectConfig = site.projectConfig;
2363
2538
  generateOptions = site.generateOptions;
2539
+ contentRoot = (0, node_path.join)(root, generateOptions.rootDirectory);
2540
+ runtimeContext = {
2541
+ projectRoot: root,
2542
+ contentRoot,
2543
+ projectConfig,
2544
+ generateOptions,
2545
+ version: cliPackageVersion
2546
+ };
2364
2547
  routes = site.routes;
2365
2548
  resolvedNavigation = site.navigation;
2366
2549
  clarifyPlugins = site.plugins;
2550
+ ctx.projectRoot = root;
2551
+ ctx.contentRoot = contentRoot;
2367
2552
  ctx.projectConfig = projectConfig;
2368
2553
  ctx.generateOptions = generateOptions;
2554
+ ctx.version = cliPackageVersion;
2369
2555
  ctx.routes = routes;
2370
2556
  ctx.navigation = resolvedNavigation;
2371
2557
  }
@@ -2376,7 +2562,8 @@ function clarifyPlugin(options = {}) {
2376
2562
  routes,
2377
2563
  navigation: resolvedNavigation,
2378
2564
  plugins: clarifyPlugins,
2379
- themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor
2565
+ themeEditor: viteConfig.command === "serve" || projectConfig.theme.editor,
2566
+ version: ctx.version
2380
2567
  });
2381
2568
  virtualModules = await runHooks(clarifyPlugins, "modules:before", virtualModules, ctx);
2382
2569
  }
@@ -2424,6 +2611,12 @@ function clarifyPlugin(options = {}) {
2424
2611
  name: "clarify:core",
2425
2612
  async config() {
2426
2613
  await resolveRoutesAndSpecs();
2614
+ logStartupHints({
2615
+ projectRoot: root,
2616
+ contentRoot,
2617
+ contentDirExists: (0, node_fs.existsSync)(contentRoot),
2618
+ hasRoutes: routes.length > 0
2619
+ });
2427
2620
  return {
2428
2621
  base: projectConfig.assetPrefix,
2429
2622
  build: {
@@ -2443,14 +2636,23 @@ function clarifyPlugin(options = {}) {
2443
2636
  async handleHotUpdate(ctx) {
2444
2637
  const changedFile = (0, node_path.isAbsolute)(ctx.file) ? ctx.file : (0, node_path.join)(root, ctx.file);
2445
2638
  if (configFilePath && changedFile === configFilePath) {
2446
- await resolveRoutesAndSpecs({
2447
- ...await loadClarifyConfig(root, {
2448
- command: "serve",
2449
- mode: viteConfig.mode
2450
- }),
2451
- projectRoot: options.projectRoot,
2639
+ const newContext = await resolveProjectContext({
2640
+ ...options,
2641
+ projectRoot: root,
2452
2642
  rootDirectory: options.rootDirectory,
2453
2643
  outputDirectory: options.outputDirectory
2644
+ }, {
2645
+ command: "serve",
2646
+ mode: viteConfig.mode
2647
+ });
2648
+ projectConfig = newContext.projectConfig;
2649
+ generateOptions = newContext.buildOptions;
2650
+ contentRoot = newContext.contentRoot;
2651
+ await resolveRoutesAndSpecs({
2652
+ ...newContext.config,
2653
+ projectRoot: newContext.projectRoot,
2654
+ rootDirectory: newContext.buildOptions.rootDirectory,
2655
+ outputDirectory: newContext.buildOptions.outputDirectory
2454
2656
  });
2455
2657
  await rebuildVirtualModules();
2456
2658
  invalidateVirtualModules(ctx.server);
@@ -2483,12 +2685,13 @@ function clarifyPlugin(options = {}) {
2483
2685
  server.watcher.add(contentRoot);
2484
2686
  if (configFilePath) server.watcher.add(configFilePath);
2485
2687
  server.middlewares.use((req, res, next) => {
2486
- if (req.url === "/dev/query-preview-route" && req.method === "POST") return handleDevRouteRequest(req, res, routes, projectConfig, contentRoot);
2487
- if (req.url === "/dev/project-info" && (req.method === "GET" || req.method === "HEAD")) return handleProjectInfoRequest(req, res, root, contentRoot, projectConfig);
2688
+ if (req.url === "/dev/query-preview-route" && req.method === "POST") return handleDevRouteRequest(req, res, routes, runtimeContext);
2689
+ if (req.url === "/dev/project-info" && (req.method === "GET" || req.method === "HEAD")) return handleProjectInfoRequest(req, res, runtimeContext);
2488
2690
  next();
2489
2691
  });
2490
2692
  const handleContentTreeChange = async (filePath) => {
2491
- const relativeContentFile = (0, node_path.relative)(contentRoot, (0, node_path.isAbsolute)(filePath) ? filePath : (0, node_path.join)(root, filePath));
2693
+ const changedFile = (0, node_path.isAbsolute)(filePath) ? filePath : (0, node_path.join)(root, filePath);
2694
+ const relativeContentFile = (0, node_path.relative)(contentRoot, changedFile);
2492
2695
  if (!(relativeContentFile && !relativeContentFile.startsWith("..") && !(0, node_path.isAbsolute)(relativeContentFile))) return;
2493
2696
  await refreshDevServer(server);
2494
2697
  };
@@ -2552,7 +2755,7 @@ function clarifyPlugin(options = {}) {
2552
2755
  mdx
2553
2756
  ]);
2554
2757
  const ssrBundlePath = (0, node_path.join)(ssrOutputDir, "entry-server.js");
2555
- await renderSSGRoutes(routes, projectConfig, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
2758
+ await renderSSGRoutes(routes, runtimeContext, outputDir, ssrBundlePath, generateOptions.ssg.failOnError);
2556
2759
  } catch (err) {
2557
2760
  console.error("[clarify] SSG failed:", err);
2558
2761
  if (generateOptions.ssg.failOnError) throw err;
@@ -2574,6 +2777,18 @@ Object.defineProperty(exports, "clarifyPlugin", {
2574
2777
  return clarifyPlugin;
2575
2778
  }
2576
2779
  });
2780
+ Object.defineProperty(exports, "cliPackageVersion", {
2781
+ enumerable: true,
2782
+ get: function() {
2783
+ return cliPackageVersion;
2784
+ }
2785
+ });
2786
+ Object.defineProperty(exports, "cliPackageVersionWithCaret", {
2787
+ enumerable: true,
2788
+ get: function() {
2789
+ return cliPackageVersionWithCaret;
2790
+ }
2791
+ });
2577
2792
  Object.defineProperty(exports, "createClarifyRuntimeAliases", {
2578
2793
  enumerable: true,
2579
2794
  get: function() {
@@ -2592,17 +2807,17 @@ Object.defineProperty(exports, "defineConfig", {
2592
2807
  return defineConfig;
2593
2808
  }
2594
2809
  });
2595
- Object.defineProperty(exports, "loadClarifyConfig", {
2810
+ Object.defineProperty(exports, "resolveClarifySite", {
2596
2811
  enumerable: true,
2597
2812
  get: function() {
2598
- return loadClarifyConfig;
2813
+ return resolveClarifySite;
2599
2814
  }
2600
2815
  });
2601
- Object.defineProperty(exports, "resolveClarifySite", {
2816
+ Object.defineProperty(exports, "resolveProjectContext", {
2602
2817
  enumerable: true,
2603
2818
  get: function() {
2604
- return resolveClarifySite;
2819
+ return resolveProjectContext;
2605
2820
  }
2606
2821
  });
2607
2822
 
2608
- //# sourceMappingURL=plugin-BUFe4vEb.cjs.map
2823
+ //# sourceMappingURL=plugin-Etn8ZfYN.cjs.map