barodoc 2.0.0 → 4.0.1

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.
@@ -59,6 +59,11 @@ async function createProject(options) {
59
59
  private: true,
60
60
  dependencies: {
61
61
  astro: "^5.0.0",
62
+ "@astrojs/mdx": "^4.0.0",
63
+ "@astrojs/react": "^4.0.0",
64
+ "@tailwindcss/typography": "^0.5.19",
65
+ "@tailwindcss/vite": "^4.0.0",
66
+ tailwindcss: "^4.0.0",
62
67
  "@barodoc/core": "^1.0.0",
63
68
  "@barodoc/theme-docs": "^1.0.0",
64
69
  react: "^19.0.0",
@@ -102,6 +107,12 @@ async function createProject(options) {
102
107
  } else {
103
108
  await fs.ensureDir(publicLink);
104
109
  }
110
+ const overridesDir = path.join(root, "overrides");
111
+ const overridesLink = path.join(projectDir, "overrides");
112
+ if (fs.existsSync(overridesDir)) {
113
+ await fs.symlink(overridesDir, overridesLink, "dir");
114
+ console.log(pc.dim(" Linked overrides/ directory"));
115
+ }
105
116
  return projectDir;
106
117
  }
107
118
  async function installDependencies(projectDir, force = false) {
@@ -160,6 +171,12 @@ const docsCollection = defineCollection({
160
171
  schema: z.object({
161
172
  title: z.string().optional(),
162
173
  description: z.string().optional(),
174
+ tags: z.array(z.string()).optional(),
175
+ related: z.array(z.string()).optional(),
176
+ category: z.string().optional(),
177
+ api_reference: z.boolean().optional(),
178
+ difficulty: z.enum(["beginner", "intermediate", "advanced"]).optional(),
179
+ lastUpdated: z.date().optional(),
163
180
  }),
164
181
  });
165
182
 
package/dist/cli.js CHANGED
@@ -6,14 +6,14 @@ import {
6
6
  installDependencies,
7
7
  isCustomProject,
8
8
  loadProjectConfig
9
- } from "./chunk-JIP64MDC.js";
9
+ } from "./chunk-XWNFQM7S.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import cac from "cac";
13
- import pc8 from "picocolors";
13
+ import pc10 from "picocolors";
14
14
 
15
15
  // package.json
16
- var version = "2.0.0";
16
+ var version = "4.0.1";
17
17
 
18
18
  // src/commands/serve.ts
19
19
  import path from "path";
@@ -160,16 +160,22 @@ markdown files that are fully compatible with barodoc.
160
160
 
161
161
  \`\`\`
162
162
  docs/
163
- {locale}/ # e.g., en/, ko/, ja/
164
- {slug}.md # Markdown or MDX files
163
+ {locale}/ # e.g., en/, ko/, ja/
164
+ {slug}.md # Markdown or MDX files
165
165
  {slug}.mdx
166
+ {category}/{slug}.mdx # Nested paths (e.g., guides/installation.mdx)
167
+
168
+ overrides/ # Optional: component/layout overrides
169
+ components/ # Custom or overridden MDX components
170
+ layouts/ # Layout overrides
166
171
 
167
- public/ # Static assets (images, logo, etc.)
168
- barodoc.config.json # Site configuration
172
+ public/ # Static assets (images, logo, etc.)
173
+ barodoc.config.json # Site configuration
169
174
  \`\`\`
170
175
 
171
176
  - Each page is a single \`.md\` or \`.mdx\` file inside \`docs/{locale}/\`.
172
177
  - The filename (without extension) becomes the **slug** used in navigation.
178
+ - Nested paths are supported: \`docs/en/guides/installation.mdx\` \u2192 slug \`guides/installation\`.
173
179
  - Use \`.mdx\` when you need to include interactive components (Callout, Steps, etc.).
174
180
  - Use \`.md\` for plain text-only pages.
175
181
 
@@ -183,11 +189,25 @@ Every page **should** include frontmatter at the top:
183
189
  ---
184
190
  title: Page Title
185
191
  description: A brief description shown in search results and meta tags.
192
+ tags: [topic1, topic2]
193
+ related: [other-page-slug, guides/related-guide]
194
+ category: guides
195
+ difficulty: beginner
186
196
  ---
187
197
  \`\`\`
188
198
 
189
- - \`title\` \u2014 Optional. If omitted, the first \`#\` heading is used.
190
- - \`description\` \u2014 Recommended. Used for SEO and search result snippets.
199
+ | Field | Type | Description |
200
+ |-------|------|-------------|
201
+ | \`title\` | string | Optional. If omitted, the first \`#\` heading is used. |
202
+ | \`description\` | string | Recommended. Used for SEO and search result snippets. |
203
+ | \`tags\` | string[] | Classification tags for search and AI filtering. |
204
+ | \`related\` | string[] | Slugs of related pages. Must be valid slugs in navigation. |
205
+ | \`category\` | string | Explicit category. Auto-detected from navigation group if omitted. |
206
+ | \`difficulty\` | \`"beginner"\` | \`"intermediate"\` | \`"advanced"\` | Content difficulty. |
207
+ | \`api_reference\` | boolean | Marks the page as API reference content. |
208
+ | \`lastUpdated\` | date | Manual override for last-updated timestamp. |
209
+
210
+ **Rules for \`related\`:** Only use slugs that exist in \`barodoc.config.json\` navigation. \`barodoc check\` validates these.
191
211
 
192
212
  ---
193
213
 
@@ -346,22 +366,53 @@ If the project has multiple locales:
346
366
 
347
367
  - Use \`##\` for top-level sections within a page (the page \`#\` title is auto-rendered).
348
368
  - Keep descriptions concise (1\u20132 sentences for \`description\` frontmatter).
349
- - Use fenced code blocks with language identifiers for syntax highlighting.
369
+ - **Always include a language identifier on fenced code blocks** (e.g., \\\`\\\`\\\`bash, \\\`\\\`\\\`json, \\\`\\\`\\\`typescript). This is required for syntax highlighting and AI code classification.
350
370
  - Prefer \`<Callout type="warning">\` over bold text for important notices.
351
371
  - Use \`<Steps>\` for any multi-step process (installation, setup, etc.).
352
372
  - Images go in \`public/\` and are referenced as \`/image.png\`.
373
+ - When referencing other doc pages, use the slug (e.g., \`guides/configuration\`), not file paths.
374
+
375
+ ---
376
+
377
+ ## Overrides (Custom Components)
378
+
379
+ Place custom components in \`overrides/components/\` to extend or replace theme components:
380
+
381
+ \`\`\`
382
+ overrides/
383
+ components/
384
+ CustomBanner.tsx # New component
385
+ Callout.tsx # Overrides built-in Callout
386
+ \`\`\`
387
+
388
+ Import in MDX via the alias:
389
+
390
+ \`\`\`mdx
391
+ import CustomBanner from "@overrides/components/CustomBanner";
392
+
393
+ <CustomBanner text="Hello" />
394
+ \`\`\`
353
395
 
354
396
  ---
355
397
 
356
- ## Validation
398
+ ## CLI Commands
357
399
 
358
400
  \`\`\`bash
359
- # Check for issues (missing files, orphan pages, frontmatter)
401
+ # Check for issues (missing files, orphan pages, frontmatter, invalid related links)
360
402
  barodoc check
361
403
 
362
404
  # Auto-fix navigation mismatches
363
405
  barodoc check --fix
364
406
 
407
+ # Generate docs-manifest.json (structured metadata + content for AI agents)
408
+ barodoc manifest
409
+
410
+ # Generate RAG-optimized chunks
411
+ barodoc manifest --chunks
412
+
413
+ # Generate JSON Schema for config and frontmatter
414
+ barodoc schema
415
+
365
416
  # Start dev server
366
417
  barodoc serve
367
418
 
@@ -791,6 +842,25 @@ async function eject(dir, options) {
791
842
  import path7 from "path";
792
843
  import pc7 from "picocolors";
793
844
  import fs6 from "fs-extra";
845
+ async function collectSlugs(baseDir, prefix = "") {
846
+ const slugs = [];
847
+ if (!await fs6.pathExists(baseDir)) return slugs;
848
+ const entries = await fs6.readdir(baseDir, { withFileTypes: true });
849
+ for (const entry of entries) {
850
+ if (entry.isDirectory()) {
851
+ slugs.push(...await collectSlugs(path7.join(baseDir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name));
852
+ } else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) {
853
+ const slug = entry.name.replace(/\.(mdx?)$/, "");
854
+ slugs.push(prefix ? `${prefix}/${slug}` : slug);
855
+ }
856
+ }
857
+ return slugs;
858
+ }
859
+ function resolveDocsDir(root) {
860
+ const customMode = path7.join(root, "src", "content", "docs");
861
+ if (fs6.existsSync(customMode)) return customMode;
862
+ return path7.join(root, "docs");
863
+ }
794
864
  async function scanDocsFiles(docsDir) {
795
865
  const result = /* @__PURE__ */ new Map();
796
866
  if (!await fs6.pathExists(docsDir)) {
@@ -801,13 +871,7 @@ async function scanDocsFiles(docsDir) {
801
871
  const localeDir = path7.join(docsDir, locale);
802
872
  const stat = await fs6.stat(localeDir);
803
873
  if (!stat.isDirectory()) continue;
804
- const slugs = [];
805
- const files = await fs6.readdir(localeDir);
806
- for (const file of files) {
807
- if (file.endsWith(".md") || file.endsWith(".mdx")) {
808
- slugs.push(file.replace(/\.(mdx?)$/, ""));
809
- }
810
- }
874
+ const slugs = await collectSlugs(localeDir);
811
875
  result.set(locale, slugs);
812
876
  }
813
877
  return result;
@@ -827,27 +891,67 @@ function getNavSlugs(config) {
827
891
  }
828
892
  return result;
829
893
  }
894
+ function parseFrontmatter(content) {
895
+ if (!content.startsWith("---")) return null;
896
+ const end = content.indexOf("---", 3);
897
+ if (end === -1) return null;
898
+ const block = content.slice(3, end).trim();
899
+ const result = {};
900
+ for (const line of block.split("\n")) {
901
+ const match = line.match(/^(\w[\w_]*):\s*(.*)/);
902
+ if (!match) continue;
903
+ const [, key, raw] = match;
904
+ const value = raw.trim();
905
+ if (value.startsWith("[") && value.endsWith("]")) {
906
+ result[key] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
907
+ } else if (value === "true") {
908
+ result[key] = true;
909
+ } else if (value === "false") {
910
+ result[key] = false;
911
+ } else {
912
+ result[key] = value;
913
+ }
914
+ }
915
+ return result;
916
+ }
830
917
  async function checkFrontmatter(filePath) {
831
918
  const content = await fs6.readFile(filePath, "utf-8");
832
919
  const missing = [];
833
920
  if (!content.startsWith("---")) {
834
921
  return missing;
835
922
  }
836
- const end = content.indexOf("---", 3);
837
- if (end === -1) return missing;
838
- const frontmatter = content.slice(3, end);
839
- if (!frontmatter.includes("description:")) {
840
- missing.push("description");
841
- }
923
+ const fm = parseFrontmatter(content);
924
+ if (!fm) return missing;
925
+ if (!fm.description) missing.push("description");
842
926
  return missing;
843
927
  }
928
+ async function checkRelatedLinks(docsDir, locales, defaultLocale, navSlugs) {
929
+ const issues = [];
930
+ const allSlugs = navSlugs.get(defaultLocale) ?? /* @__PURE__ */ new Set();
931
+ const defaultDir = path7.join(docsDir, defaultLocale);
932
+ if (!await fs6.pathExists(defaultDir)) return issues;
933
+ const files = await fs6.readdir(defaultDir);
934
+ for (const file of files) {
935
+ if (!file.endsWith(".md") && !file.endsWith(".mdx")) continue;
936
+ const slug = file.replace(/\.(mdx?)$/, "");
937
+ const filePath = path7.join(defaultDir, file);
938
+ const content = await fs6.readFile(filePath, "utf-8");
939
+ const fm = parseFrontmatter(content);
940
+ if (!fm || !Array.isArray(fm.related)) continue;
941
+ const invalid = fm.related.filter((r) => !allSlugs.has(r));
942
+ if (invalid.length > 0) {
943
+ issues.push({ filePath: path7.relative(path7.dirname(docsDir), filePath), slug, invalidRelated: invalid });
944
+ }
945
+ }
946
+ return issues;
947
+ }
844
948
  async function check(dir, options) {
845
949
  const root = path7.resolve(process.cwd(), dir);
846
950
  const { config } = await loadProjectConfig(root, options.config);
847
951
  console.log();
848
952
  console.log(pc7.bold(pc7.cyan(" barodoc check")));
849
953
  console.log();
850
- const docsDir = path7.join(root, "docs");
954
+ const docsDir = resolveDocsDir(root);
851
955
  const locales = config.i18n?.locales ?? ["en"];
852
956
  const defaultLocale = config.i18n?.defaultLocale ?? "en";
853
957
  const fileMap = await scanDocsFiles(docsDir);
@@ -904,6 +1008,7 @@ async function check(dir, options) {
904
1008
  });
905
1009
  }
906
1010
  }
1011
+ const relatedIssues = await checkRelatedLinks(docsDir, locales, defaultLocale, navSlugs);
907
1012
  let hasIssues = false;
908
1013
  if (result.missingFiles.length > 0) {
909
1014
  hasIssues = true;
@@ -940,6 +1045,18 @@ async function check(dir, options) {
940
1045
  }
941
1046
  console.log();
942
1047
  }
1048
+ if (relatedIssues.length > 0) {
1049
+ hasIssues = true;
1050
+ console.log(pc7.bold(pc7.yellow(` Invalid related links (${relatedIssues.length})`)));
1051
+ console.log(pc7.dim(" Frontmatter 'related' references slugs not in navigation."));
1052
+ console.log();
1053
+ for (const item of relatedIssues) {
1054
+ console.log(
1055
+ ` ${pc7.yellow("\u26A0")} ${pc7.bold(item.slug)} ${pc7.dim(`\u2192 invalid: ${item.invalidRelated.join(", ")}`)}`
1056
+ );
1057
+ }
1058
+ console.log();
1059
+ }
943
1060
  if (options.fix && (result.missingFiles.length > 0 || result.orphanFiles.length > 0)) {
944
1061
  console.log(pc7.bold(pc7.cyan(" Fixing issues...")));
945
1062
  console.log();
@@ -1008,6 +1125,326 @@ Add your content here.
1008
1125
  }
1009
1126
  }
1010
1127
 
1128
+ // src/commands/manifest.ts
1129
+ import path8 from "path";
1130
+ import pc8 from "picocolors";
1131
+ import fs7 from "fs-extra";
1132
+ import matter from "gray-matter";
1133
+ function extractHeadings(content) {
1134
+ const headings = [];
1135
+ const lines = content.split("\n");
1136
+ let inCodeBlock = false;
1137
+ for (const line of lines) {
1138
+ if (line.startsWith("```")) {
1139
+ inCodeBlock = !inCodeBlock;
1140
+ continue;
1141
+ }
1142
+ if (inCodeBlock) continue;
1143
+ const match = line.match(/^(#{1,6})\s+(.+)/);
1144
+ if (match) {
1145
+ headings.push({ depth: match[1].length, text: match[2].trim() });
1146
+ }
1147
+ }
1148
+ return headings;
1149
+ }
1150
+ function extractCodeBlocksFull(content) {
1151
+ const blocks = [];
1152
+ const regex = /```(\w*)\n([\s\S]*?)```/g;
1153
+ let m;
1154
+ while ((m = regex.exec(content)) !== null) {
1155
+ blocks.push({
1156
+ lang: m[1] || "text",
1157
+ code: m[2].trimEnd(),
1158
+ lines: m[2].split("\n").length - (m[2].endsWith("\n") ? 1 : 0)
1159
+ });
1160
+ }
1161
+ return blocks;
1162
+ }
1163
+ function extractSections(content) {
1164
+ const sections = [];
1165
+ const lines = content.split("\n");
1166
+ let current = null;
1167
+ let buffer = [];
1168
+ let inCodeBlock = false;
1169
+ function flush() {
1170
+ if (current) {
1171
+ current.content = buffer.join("\n").trim();
1172
+ if (current.content) sections.push(current);
1173
+ }
1174
+ }
1175
+ for (const line of lines) {
1176
+ if (line.startsWith("```")) inCodeBlock = !inCodeBlock;
1177
+ if (!inCodeBlock) {
1178
+ const match = line.match(/^(#{2,3})\s+(.+)/);
1179
+ if (match) {
1180
+ flush();
1181
+ current = { heading: match[2].trim(), depth: match[1].length, content: "" };
1182
+ buffer = [];
1183
+ continue;
1184
+ }
1185
+ }
1186
+ if (current) {
1187
+ buffer.push(line);
1188
+ } else if (!sections.length) {
1189
+ if (!current) {
1190
+ current = { heading: "(intro)", depth: 0, content: "" };
1191
+ }
1192
+ buffer.push(line);
1193
+ }
1194
+ }
1195
+ flush();
1196
+ return sections;
1197
+ }
1198
+ function extractSummary(content, description) {
1199
+ if (description) return description;
1200
+ const stripped = content.replace(/^import\s+.*$/gm, "").replace(/<[^>]+>/g, "").replace(/```[\s\S]*?```/g, "").replace(/^#{1,6}\s+.*$/gm, "").trim();
1201
+ const sentences = stripped.split(/(?<=[.!?])\s+/);
1202
+ return sentences.slice(0, 2).join(" ").trim().slice(0, 300);
1203
+ }
1204
+ function extractComponentAPI(content, slug) {
1205
+ const props = [];
1206
+ const paramRegex = /<ParamField\s+([^>]*?)(?:\/>|>([\s\S]*?)<\/ParamField>)/g;
1207
+ let m;
1208
+ while ((m = paramRegex.exec(content)) !== null) {
1209
+ const attrs = m[1];
1210
+ const innerText = m[2]?.trim() || "";
1211
+ const nameMatch = attrs.match(/name=["']([^"']+)["']/);
1212
+ const typeMatch = attrs.match(/type=["']([^"']+)["']/);
1213
+ const defaultMatch = attrs.match(/default=["']([^"']+)["']/);
1214
+ const required = /\brequired\b/.test(attrs);
1215
+ if (nameMatch) {
1216
+ props.push({
1217
+ name: nameMatch[1],
1218
+ type: typeMatch?.[1] ?? "unknown",
1219
+ required,
1220
+ description: innerText.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim(),
1221
+ ...defaultMatch ? { default: defaultMatch[1] } : {}
1222
+ });
1223
+ }
1224
+ }
1225
+ if (props.length === 0) return void 0;
1226
+ const name = slug.split("/").pop().split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1227
+ return { name, props };
1228
+ }
1229
+ function labelCodeExamples(content) {
1230
+ const sections = extractSections(content);
1231
+ const examples = [];
1232
+ for (const section of sections) {
1233
+ const regex = /```(\w*)\n([\s\S]*?)```/g;
1234
+ let m;
1235
+ while ((m = regex.exec(section.content)) !== null) {
1236
+ examples.push({
1237
+ lang: m[1] || "text",
1238
+ code: m[2].trimEnd(),
1239
+ section: section.heading
1240
+ });
1241
+ }
1242
+ }
1243
+ return examples;
1244
+ }
1245
+ function countWords(content) {
1246
+ const stripped = content.replace(/```[\s\S]*?```/g, "").replace(/<[^>]+>/g, "").replace(/^---[\s\S]*?---/m, "").replace(/[#*_~`\[\]()>|]/g, "");
1247
+ return stripped.split(/\s+/).filter((w) => w.length > 0).length;
1248
+ }
1249
+ function findCategory(slug, navigation) {
1250
+ for (const group of navigation) {
1251
+ if (group.pages.includes(slug)) return group.group;
1252
+ }
1253
+ return null;
1254
+ }
1255
+ function approxTokens(text) {
1256
+ return Math.ceil(text.length / 4);
1257
+ }
1258
+ function slugToType(slug) {
1259
+ if (slug.startsWith("components/")) return "component";
1260
+ if (slug.startsWith("guides/")) return "guide";
1261
+ return "page";
1262
+ }
1263
+ async function scanDir(dir) {
1264
+ if (!await fs7.pathExists(dir)) return [];
1265
+ const entries = await fs7.readdir(dir, { withFileTypes: true });
1266
+ let results = [];
1267
+ for (const entry of entries) {
1268
+ const full = path8.join(dir, entry.name);
1269
+ if (entry.isDirectory()) {
1270
+ results = results.concat(await scanDir(full));
1271
+ } else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdx")) {
1272
+ results.push(full);
1273
+ }
1274
+ }
1275
+ return results;
1276
+ }
1277
+ function generateChunks(pages) {
1278
+ const chunks = [];
1279
+ for (const page of pages) {
1280
+ if (!page.sections) continue;
1281
+ for (const section of page.sections) {
1282
+ const sectionId = section.heading.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1283
+ const id = `${page.locale}/${page.slug}#${sectionId}`;
1284
+ const tokens = approxTokens(section.content);
1285
+ if (tokens <= 500 || section.depth >= 3) {
1286
+ chunks.push({
1287
+ id,
1288
+ page_slug: page.slug,
1289
+ section: section.heading,
1290
+ locale: page.locale,
1291
+ content: section.content,
1292
+ tokens_approx: tokens,
1293
+ tags: page.tags,
1294
+ difficulty: page.difficulty,
1295
+ type: slugToType(page.slug)
1296
+ });
1297
+ } else {
1298
+ const subSections = splitByH3(section.content, section.heading);
1299
+ for (const sub of subSections) {
1300
+ const subId = sub.heading.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1301
+ chunks.push({
1302
+ id: `${page.locale}/${page.slug}#${subId}`,
1303
+ page_slug: page.slug,
1304
+ section: sub.heading,
1305
+ locale: page.locale,
1306
+ content: sub.content,
1307
+ tokens_approx: approxTokens(sub.content),
1308
+ tags: page.tags,
1309
+ difficulty: page.difficulty,
1310
+ type: slugToType(page.slug)
1311
+ });
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+ return chunks;
1317
+ }
1318
+ function splitByH3(content, parentHeading) {
1319
+ const parts = [];
1320
+ const lines = content.split("\n");
1321
+ let current = { heading: parentHeading, content: "" };
1322
+ let buffer = [];
1323
+ let inCode = false;
1324
+ function flush() {
1325
+ current.content = buffer.join("\n").trim();
1326
+ if (current.content) parts.push({ ...current });
1327
+ }
1328
+ for (const line of lines) {
1329
+ if (line.startsWith("```")) inCode = !inCode;
1330
+ if (!inCode) {
1331
+ const match = line.match(/^###\s+(.+)/);
1332
+ if (match) {
1333
+ flush();
1334
+ current = { heading: match[1].trim(), content: "" };
1335
+ buffer = [];
1336
+ continue;
1337
+ }
1338
+ }
1339
+ buffer.push(line);
1340
+ }
1341
+ flush();
1342
+ return parts;
1343
+ }
1344
+ async function manifest(dir, options) {
1345
+ const root = path8.resolve(process.cwd(), dir);
1346
+ const { config } = await loadProjectConfig(root, options.config);
1347
+ const isLite = options.lite === true;
1348
+ const emitChunks = options.chunks === true;
1349
+ console.log();
1350
+ console.log(pc8.bold(pc8.cyan(" barodoc manifest")));
1351
+ if (isLite) console.log(pc8.dim(" (lite mode \u2014 metadata only)"));
1352
+ console.log();
1353
+ const customModeDir = path8.join(root, "src", "content", "docs");
1354
+ const quickModeDir = path8.join(root, "docs");
1355
+ const docsDir = fs7.existsSync(customModeDir) ? customModeDir : quickModeDir;
1356
+ const locales = config.i18n?.locales ?? ["en"];
1357
+ const defaultLocale = config.i18n?.defaultLocale ?? "en";
1358
+ const navigation = config.navigation ?? [];
1359
+ const pages = [];
1360
+ for (const locale of locales) {
1361
+ const localeDir = path8.join(docsDir, locale);
1362
+ const files = await scanDir(localeDir);
1363
+ for (const filePath of files) {
1364
+ const raw = await fs7.readFile(filePath, "utf-8");
1365
+ const { data: fm, content } = matter(raw);
1366
+ const relPath = path8.relative(localeDir, filePath);
1367
+ const slug = relPath.replace(/\.(mdx?)$/, "").replace(/\\/g, "/");
1368
+ const title = fm.title || extractHeadings(content).find((h) => h.depth === 1)?.text || slug.split("/").pop().split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1369
+ const codeBlocksFull = extractCodeBlocksFull(content);
1370
+ const page = {
1371
+ slug,
1372
+ title,
1373
+ description: fm.description ?? "",
1374
+ summary: extractSummary(content, fm.description ?? ""),
1375
+ category: fm.category ?? findCategory(slug, navigation),
1376
+ locale,
1377
+ tags: Array.isArray(fm.tags) ? fm.tags : [],
1378
+ related: Array.isArray(fm.related) ? fm.related : [],
1379
+ difficulty: fm.difficulty ?? null,
1380
+ api_reference: fm.api_reference === true,
1381
+ headings: extractHeadings(content),
1382
+ codeBlocks: codeBlocksFull.map((b) => ({ lang: b.lang, lines: b.lines })),
1383
+ wordCount: countWords(raw),
1384
+ filePath: `${locale}/${relPath}`
1385
+ };
1386
+ if (!isLite) {
1387
+ page.sections = extractSections(content);
1388
+ page.codeExamples = labelCodeExamples(content);
1389
+ const api = extractComponentAPI(content, slug);
1390
+ if (api) page.componentAPI = api;
1391
+ }
1392
+ pages.push(page);
1393
+ }
1394
+ }
1395
+ const result = {
1396
+ name: config.name ?? "Docs",
1397
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1398
+ locales,
1399
+ defaultLocale,
1400
+ pages,
1401
+ navigation: navigation.map((g) => ({ group: g.group, pages: g.pages }))
1402
+ };
1403
+ const outPath = options.output ? path8.resolve(root, options.output) : path8.join(root, "docs-manifest.json");
1404
+ await fs7.writeJSON(outPath, result, { spaces: 2 });
1405
+ console.log(pc8.green(` \u2713 Generated ${path8.relative(root, outPath)}`));
1406
+ console.log(pc8.dim(` ${pages.length} pages across ${locales.length} locale(s)`));
1407
+ if (emitChunks) {
1408
+ const chunks = generateChunks(pages);
1409
+ const chunksPath = outPath.replace(/\.json$/, "") + "-chunks.jsonl";
1410
+ const jsonl = chunks.map((c) => JSON.stringify(c)).join("\n");
1411
+ await fs7.writeFile(chunksPath, jsonl, "utf-8");
1412
+ console.log(pc8.green(` \u2713 Generated ${path8.relative(root, chunksPath)}`));
1413
+ console.log(pc8.dim(` ${chunks.length} chunks`));
1414
+ }
1415
+ console.log();
1416
+ }
1417
+
1418
+ // src/commands/schema.ts
1419
+ import path9 from "path";
1420
+ import pc9 from "picocolors";
1421
+ import fs8 from "fs-extra";
1422
+ import { zodToJsonSchema } from "zod-to-json-schema";
1423
+ import { barodocConfigSchema, docsFrontmatterSchema } from "@barodoc/core";
1424
+ async function schema(dir, options) {
1425
+ const root = path9.resolve(process.cwd(), dir);
1426
+ const outDir = options.output ? path9.resolve(root, options.output) : root;
1427
+ console.log();
1428
+ console.log(pc9.bold(pc9.cyan(" barodoc schema")));
1429
+ console.log();
1430
+ await fs8.ensureDir(outDir);
1431
+ const configSchema = zodToJsonSchema(barodocConfigSchema, {
1432
+ name: "BarodocConfig",
1433
+ $refStrategy: "none"
1434
+ });
1435
+ const fmSchema = zodToJsonSchema(docsFrontmatterSchema, {
1436
+ name: "DocsFrontmatter",
1437
+ $refStrategy: "none"
1438
+ });
1439
+ const configPath = path9.join(outDir, "config-schema.json");
1440
+ const fmPath = path9.join(outDir, "frontmatter-schema.json");
1441
+ await fs8.writeJSON(configPath, configSchema, { spaces: 2 });
1442
+ await fs8.writeJSON(fmPath, fmSchema, { spaces: 2 });
1443
+ console.log(pc9.green(` \u2713 Generated ${path9.relative(root, configPath)}`));
1444
+ console.log(pc9.green(` \u2713 Generated ${path9.relative(root, fmPath)}`));
1445
+ console.log();
1446
+ }
1447
+
1011
1448
  // src/cli.ts
1012
1449
  var cli = cac("barodoc");
1013
1450
  cli.command("serve [dir]", "Start development server").option("-p, --port <port>", "Port to listen on", { default: 4321 }).option("-h, --host", "Expose to network").option("--open", "Open browser on start").option("--clean", "Force reinstall dependencies").option("-c, --config <file>", "Config file path").action(async (dir = ".", options) => {
@@ -1028,6 +1465,12 @@ cli.command("init [dir]", "Initialize Barodoc in an existing directory").option(
1028
1465
  cli.command("check [dir]", "Validate docs against navigation config").option("--fix", "Auto-fix missing files and orphan navigation entries").option("-c, --config <file>", "Config file path").action(async (dir = ".", options) => {
1029
1466
  await check(dir, options);
1030
1467
  });
1468
+ cli.command("manifest [dir]", "Generate docs-manifest.json for AI consumption").option("-o, --output <file>", "Output file path", { default: "docs-manifest.json" }).option("--lite", "Metadata only (no sections/content)").option("--chunks", "Also generate JSONL chunks for RAG").option("-c, --config <file>", "Config file path").action(async (dir = ".", options) => {
1469
+ await manifest(dir, options);
1470
+ });
1471
+ cli.command("schema [dir]", "Export JSON Schema for config and frontmatter").option("-o, --output <dir>", "Output directory").action(async (dir = ".", options) => {
1472
+ await schema(dir, options);
1473
+ });
1031
1474
  cli.command("eject [dir]", "Eject to a full Astro project").option("-c, --config <file>", "Config file path").action(async (dir = ".", options) => {
1032
1475
  await eject(dir, options);
1033
1476
  });
@@ -1036,8 +1479,8 @@ cli.version(version);
1036
1479
  cli.parse();
1037
1480
  if (!process.argv.slice(2).length) {
1038
1481
  console.log();
1039
- console.log(pc8.bold(pc8.cyan(" barodoc")));
1040
- console.log(pc8.dim(" Documentation framework powered by Astro"));
1482
+ console.log(pc10.bold(pc10.cyan(" barodoc")));
1483
+ console.log(pc10.dim(" Documentation framework powered by Astro"));
1041
1484
  console.log();
1042
1485
  cli.outputHelp();
1043
1486
  }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cleanupProject,
3
3
  createProject
4
- } from "./chunk-JIP64MDC.js";
4
+ } from "./chunk-XWNFQM7S.js";
5
5
 
6
6
  // src/index.ts
7
7
  export * from "@barodoc/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "barodoc",
3
- "version": "2.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Documentation framework powered by Astro",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,8 +24,10 @@
24
24
  "fs-extra": "^11.2.0",
25
25
  "gray-matter": "^4.0.3",
26
26
  "picocolors": "^1.1.1",
27
- "@barodoc/core": "2.0.0",
28
- "@barodoc/theme-docs": "2.0.0"
27
+ "zod": "^3.24.0",
28
+ "zod-to-json-schema": "^3.25.1",
29
+ "@barodoc/core": "3.0.0",
30
+ "@barodoc/theme-docs": "3.0.0"
29
31
  },
30
32
  "devDependencies": {
31
33
  "@types/fs-extra": "^11.0.4",