barodoc 2.0.0 → 3.0.0
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.
- package/dist/{chunk-JIP64MDC.js → chunk-RK4USU7V.js} +12 -0
- package/dist/cli.js +471 -28
- package/dist/index.js +1 -1
- package/package.json +5 -3
|
@@ -102,6 +102,12 @@ async function createProject(options) {
|
|
|
102
102
|
} else {
|
|
103
103
|
await fs.ensureDir(publicLink);
|
|
104
104
|
}
|
|
105
|
+
const overridesDir = path.join(root, "overrides");
|
|
106
|
+
const overridesLink = path.join(projectDir, "overrides");
|
|
107
|
+
if (fs.existsSync(overridesDir)) {
|
|
108
|
+
await fs.symlink(overridesDir, overridesLink, "dir");
|
|
109
|
+
console.log(pc.dim(" Linked overrides/ directory"));
|
|
110
|
+
}
|
|
105
111
|
return projectDir;
|
|
106
112
|
}
|
|
107
113
|
async function installDependencies(projectDir, force = false) {
|
|
@@ -160,6 +166,12 @@ const docsCollection = defineCollection({
|
|
|
160
166
|
schema: z.object({
|
|
161
167
|
title: z.string().optional(),
|
|
162
168
|
description: z.string().optional(),
|
|
169
|
+
tags: z.array(z.string()).optional(),
|
|
170
|
+
related: z.array(z.string()).optional(),
|
|
171
|
+
category: z.string().optional(),
|
|
172
|
+
api_reference: z.boolean().optional(),
|
|
173
|
+
difficulty: z.enum(["beginner", "intermediate", "advanced"]).optional(),
|
|
174
|
+
lastUpdated: z.date().optional(),
|
|
163
175
|
}),
|
|
164
176
|
});
|
|
165
177
|
|
package/dist/cli.js
CHANGED
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
installDependencies,
|
|
7
7
|
isCustomProject,
|
|
8
8
|
loadProjectConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RK4USU7V.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
12
12
|
import cac from "cac";
|
|
13
|
-
import
|
|
13
|
+
import pc10 from "picocolors";
|
|
14
14
|
|
|
15
15
|
// package.json
|
|
16
|
-
var version = "
|
|
16
|
+
var version = "3.0.0";
|
|
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}/
|
|
164
|
-
{slug}.md
|
|
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/
|
|
168
|
-
barodoc.config.json
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
-
|
|
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
|
-
##
|
|
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
|
|
837
|
-
if (
|
|
838
|
-
|
|
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 =
|
|
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(
|
|
1040
|
-
console.log(
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "barodoc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
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
|
-
"
|
|
28
|
-
"
|
|
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",
|