@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 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: docs)
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: docs)
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: "docs" },
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("index.template.html not found in docs directory");
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("Warning: No Markdown files found in docs/");
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 html = this.#marked(markdown);
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.0",
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": "libs/libdoc"
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
@@ -33,7 +33,7 @@ export class DocsServer {
33
33
  * @returns {void}
34
34
  */
35
35
  watch(docsDir, distDir) {
36
- console.log("Watching for changes in docs/...");
36
+ console.log(`Watching for changes in ${docsDir}...`);
37
37
 
38
38
  this.#watcher = this.#fs.watch(
39
39
  docsDir,
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 |