@forwardimpact/libdoc 0.2.0 → 0.2.2
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/bin/fit-doc.js +3 -3
- package/builder.js +106 -4
- package/package.json +5 -2
- package/server.js +1 -1
- package/README.md +0 -41
package/bin/fit-doc.js
CHANGED
|
@@ -22,11 +22,11 @@ Options:
|
|
|
22
22
|
-h, --help Show this help message
|
|
23
23
|
|
|
24
24
|
Build options:
|
|
25
|
-
--src=<dir> Source directory (default:
|
|
25
|
+
--src=<dir> Source directory (default: website)
|
|
26
26
|
--out=<dir> Output directory (default: dist)
|
|
27
27
|
|
|
28
28
|
Serve options:
|
|
29
|
-
--src=<dir> Source directory (default:
|
|
29
|
+
--src=<dir> Source directory (default: website)
|
|
30
30
|
--out=<dir> Output directory (default: dist)
|
|
31
31
|
-p, --port Port to serve on (default: 3000)
|
|
32
32
|
-w, --watch Watch for changes and rebuild
|
|
@@ -93,7 +93,7 @@ async function main() {
|
|
|
93
93
|
const { values } = parseArgs({
|
|
94
94
|
args: commandArgs,
|
|
95
95
|
options: {
|
|
96
|
-
src: { type: "string", default: "
|
|
96
|
+
src: { type: "string", default: "website" },
|
|
97
97
|
out: { type: "string", default: "dist" },
|
|
98
98
|
port: { type: "string", short: "p", default: "3000" },
|
|
99
99
|
watch: { type: "boolean", short: "w", default: false },
|
package/builder.js
CHANGED
|
@@ -55,6 +55,33 @@ export class DocsBuilder {
|
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Transform .md links to match the HTML output structure
|
|
60
|
+
* - index.md → ./
|
|
61
|
+
* - file.md → file/
|
|
62
|
+
* - dir/index.md → dir/
|
|
63
|
+
* - dir/file.md → dir/file/
|
|
64
|
+
* @param {string} html - HTML content to transform
|
|
65
|
+
* @returns {string} HTML with transformed links
|
|
66
|
+
*/
|
|
67
|
+
#transformMarkdownLinks(html) {
|
|
68
|
+
return html.replace(
|
|
69
|
+
/href="([^"]*?)\.md(#[^"]*)?"/g,
|
|
70
|
+
(_match, path, hash) => {
|
|
71
|
+
const fragment = hash || "";
|
|
72
|
+
// Handle index.md links
|
|
73
|
+
if (path === "index" || path === "./index") {
|
|
74
|
+
return `href="./${fragment}"`;
|
|
75
|
+
}
|
|
76
|
+
if (path.endsWith("/index")) {
|
|
77
|
+
return `href="${path.slice(0, -5)}${fragment}"`;
|
|
78
|
+
}
|
|
79
|
+
// Non-index files become directories
|
|
80
|
+
return `href="${path}/${fragment}"`;
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
58
85
|
/**
|
|
59
86
|
* Generate table of contents from h2 headings
|
|
60
87
|
* @param {string} html - HTML content to extract headings from
|
|
@@ -122,6 +149,46 @@ export class DocsBuilder {
|
|
|
122
149
|
});
|
|
123
150
|
}
|
|
124
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Compute URL path from a markdown file's relative path
|
|
154
|
+
* @param {string} mdFile - Relative path to markdown file
|
|
155
|
+
* @returns {string} URL path (e.g. "/docs/pathway/")
|
|
156
|
+
*/
|
|
157
|
+
#urlPathFromMdFile(mdFile) {
|
|
158
|
+
const baseName = mdFile.replace(".md", "");
|
|
159
|
+
const isIndex = baseName === "index" || baseName.endsWith("/index");
|
|
160
|
+
if (isIndex) {
|
|
161
|
+
return baseName === "index"
|
|
162
|
+
? "/"
|
|
163
|
+
: "/" + baseName.replace(/\/index$/, "") + "/";
|
|
164
|
+
}
|
|
165
|
+
return "/" + baseName + "/";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Build breadcrumb HTML for pages two or more levels deep
|
|
170
|
+
* @param {string} urlPath - URL path of the current page
|
|
171
|
+
* @param {Map<string, string>} pageTitles - Map of URL paths to page titles
|
|
172
|
+
* @returns {string} Breadcrumb HTML or empty string
|
|
173
|
+
*/
|
|
174
|
+
#buildBreadcrumbs(urlPath, pageTitles) {
|
|
175
|
+
const segments = urlPath.split("/").filter(Boolean);
|
|
176
|
+
if (segments.length < 2) return "";
|
|
177
|
+
|
|
178
|
+
const parts = [];
|
|
179
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
180
|
+
const ancestorPath = "/" + segments.slice(0, i + 1).join("/") + "/";
|
|
181
|
+
const title = pageTitles.get(ancestorPath) || segments[i];
|
|
182
|
+
parts.push(`<a href="${ancestorPath}">${title}</a>`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const currentTitle =
|
|
186
|
+
pageTitles.get(urlPath) || segments[segments.length - 1];
|
|
187
|
+
parts.push(`<span>${currentTitle}</span>`);
|
|
188
|
+
|
|
189
|
+
return parts.join(" / ");
|
|
190
|
+
}
|
|
191
|
+
|
|
125
192
|
/**
|
|
126
193
|
* Recursively find all Markdown files in a directory
|
|
127
194
|
* @param {string} dir - Directory to search
|
|
@@ -134,6 +201,7 @@ export class DocsBuilder {
|
|
|
134
201
|
|
|
135
202
|
for (const entryName of entries) {
|
|
136
203
|
if (["assets", "public"].includes(entryName)) continue;
|
|
204
|
+
if (["CLAUDE.md", "SKILL.md"].includes(entryName)) continue;
|
|
137
205
|
const fullPath = this.#path.join(dir, entryName);
|
|
138
206
|
|
|
139
207
|
// Use statSync to check if it's a directory or file
|
|
@@ -175,7 +243,7 @@ export class DocsBuilder {
|
|
|
175
243
|
// Read and validate template
|
|
176
244
|
const templatePath = this.#path.join(docsDir, "index.template.html");
|
|
177
245
|
if (!this.#fs.existsSync(templatePath)) {
|
|
178
|
-
throw new Error(
|
|
246
|
+
throw new Error(`index.template.html not found in ${docsDir}`);
|
|
179
247
|
}
|
|
180
248
|
const template = this.#fs.readFileSync(templatePath, "utf-8");
|
|
181
249
|
|
|
@@ -183,7 +251,18 @@ export class DocsBuilder {
|
|
|
183
251
|
const mdFiles = this.#findMarkdownFiles(docsDir);
|
|
184
252
|
|
|
185
253
|
if (mdFiles.length === 0) {
|
|
186
|
-
console.warn(
|
|
254
|
+
console.warn(`Warning: No Markdown files found in ${docsDir}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// First pass: collect page titles by URL path for breadcrumbs
|
|
258
|
+
const pageTitles = new Map();
|
|
259
|
+
for (const mdFile of mdFiles) {
|
|
260
|
+
const { data } = this.#matter(
|
|
261
|
+
this.#fs.readFileSync(this.#path.join(docsDir, mdFile), "utf-8"),
|
|
262
|
+
);
|
|
263
|
+
if (data.title) {
|
|
264
|
+
pageTitles.set(this.#urlPathFromMdFile(mdFile), data.title);
|
|
265
|
+
}
|
|
187
266
|
}
|
|
188
267
|
|
|
189
268
|
for (const mdFile of mdFiles) {
|
|
@@ -196,10 +275,23 @@ export class DocsBuilder {
|
|
|
196
275
|
continue;
|
|
197
276
|
}
|
|
198
277
|
|
|
199
|
-
// Convert Markdown to HTML
|
|
200
|
-
const
|
|
278
|
+
// Convert Markdown to HTML and transform .md links
|
|
279
|
+
const rawHtml = this.#marked(markdown);
|
|
280
|
+
const html = this.#transformMarkdownLinks(rawHtml);
|
|
201
281
|
const toc = frontMatter.toc !== false ? this.#generateToc(html) : "";
|
|
202
282
|
|
|
283
|
+
// Extract hero and layout data from front matter
|
|
284
|
+
const hero = frontMatter.hero;
|
|
285
|
+
const heroCta =
|
|
286
|
+
hero?.cta?.map((item) => ({
|
|
287
|
+
...item,
|
|
288
|
+
btnClass: item.secondary ? "btn-secondary" : "btn-primary",
|
|
289
|
+
})) || [];
|
|
290
|
+
|
|
291
|
+
// Generate breadcrumbs for pages two or more levels deep
|
|
292
|
+
const urlPath = this.#urlPathFromMdFile(mdFile);
|
|
293
|
+
const breadcrumbs = this.#buildBreadcrumbs(urlPath, pageTitles);
|
|
294
|
+
|
|
203
295
|
// Render template with context
|
|
204
296
|
const outputHtml = this.#mustacheRender(template, {
|
|
205
297
|
title: frontMatter.title,
|
|
@@ -207,6 +299,16 @@ export class DocsBuilder {
|
|
|
207
299
|
content: html,
|
|
208
300
|
toc,
|
|
209
301
|
hasToc: !!toc,
|
|
302
|
+
layout: frontMatter.layout || "",
|
|
303
|
+
hasHero: !!hero,
|
|
304
|
+
heroImage: hero?.image || "",
|
|
305
|
+
heroAlt: hero?.alt || "",
|
|
306
|
+
heroTitle: hero?.title || frontMatter.title,
|
|
307
|
+
heroSubtitle: hero?.subtitle || frontMatter.description || "",
|
|
308
|
+
heroCta,
|
|
309
|
+
hasHeroCta: heroCta.length > 0,
|
|
310
|
+
hasBreadcrumbs: !!breadcrumbs,
|
|
311
|
+
breadcrumbs,
|
|
210
312
|
});
|
|
211
313
|
|
|
212
314
|
// Format HTML with prettier
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/libdoc",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Documentation build and serve tools for static site generation from Markdown",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/forwardimpact/monorepo",
|
|
9
|
-
"directory": "
|
|
9
|
+
"directory": "libraries/libdoc"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://www.forwardimpact.team",
|
|
12
12
|
"type": "module",
|
|
@@ -39,5 +39,8 @@
|
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
42
45
|
}
|
|
43
46
|
}
|
package/server.js
CHANGED
package/README.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# libdoc
|
|
2
|
-
|
|
3
|
-
Documentation build and serve tools for the Forward Impact documentation site.
|
|
4
|
-
Generates static sites from Markdown files with Mustache templating support.
|
|
5
|
-
|
|
6
|
-
## Usage
|
|
7
|
-
|
|
8
|
-
```javascript
|
|
9
|
-
import {
|
|
10
|
-
DocsBuilder,
|
|
11
|
-
DocsServer,
|
|
12
|
-
parseFrontMatter,
|
|
13
|
-
} from "@forwardimpact/libdoc";
|
|
14
|
-
|
|
15
|
-
const builder = new DocsBuilder({ srcDir: "docs", outDir: "public" });
|
|
16
|
-
await builder.build();
|
|
17
|
-
|
|
18
|
-
const server = new DocsServer({ port: 3000 });
|
|
19
|
-
await server.start();
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## CLI
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
# Build documentation site
|
|
26
|
-
npx fit-doc build --src=docs --out=dist
|
|
27
|
-
|
|
28
|
-
# Serve documentation locally
|
|
29
|
-
npx fit-doc serve --port=3000
|
|
30
|
-
|
|
31
|
-
# Serve with watch mode for development
|
|
32
|
-
npx fit-doc serve --watch
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## API
|
|
36
|
-
|
|
37
|
-
| Export | Description |
|
|
38
|
-
| ------------------ | -------------------------------------------- |
|
|
39
|
-
| `DocsBuilder` | Build static documentation sites |
|
|
40
|
-
| `DocsServer` | Serve documentation locally with live reload |
|
|
41
|
-
| `parseFrontMatter` | Parse YAML front matter from markdown files |
|