@dogsbay/format-fluidtopics-markdown 0.2.0-beta.92

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.
Files changed (50) hide show
  1. package/README.md +60 -0
  2. package/dist/cli.d.ts +9 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +20 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/frontmatter.d.ts +17 -0
  7. package/dist/frontmatter.d.ts.map +1 -0
  8. package/dist/frontmatter.js +85 -0
  9. package/dist/frontmatter.js.map +1 -0
  10. package/dist/headings.d.ts +13 -0
  11. package/dist/headings.d.ts.map +1 -0
  12. package/dist/headings.js +76 -0
  13. package/dist/headings.js.map +1 -0
  14. package/dist/html-blocks.d.ts +25 -0
  15. package/dist/html-blocks.d.ts.map +1 -0
  16. package/dist/html-blocks.js +151 -0
  17. package/dist/html-blocks.js.map +1 -0
  18. package/dist/importer.d.ts +14 -0
  19. package/dist/importer.d.ts.map +1 -0
  20. package/dist/importer.js +106 -0
  21. package/dist/importer.js.map +1 -0
  22. package/dist/index.d.ts +23 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +8 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/inline-walker.d.ts +14 -0
  27. package/dist/inline-walker.d.ts.map +1 -0
  28. package/dist/inline-walker.js +109 -0
  29. package/dist/inline-walker.js.map +1 -0
  30. package/dist/links.d.ts +28 -0
  31. package/dist/links.d.ts.map +1 -0
  32. package/dist/links.js +101 -0
  33. package/dist/links.js.map +1 -0
  34. package/dist/nav.d.ts +23 -0
  35. package/dist/nav.d.ts.map +1 -0
  36. package/dist/nav.js +101 -0
  37. package/dist/nav.js.map +1 -0
  38. package/dist/parse.d.ts +27 -0
  39. package/dist/parse.d.ts.map +1 -0
  40. package/dist/parse.js +22 -0
  41. package/dist/parse.js.map +1 -0
  42. package/dist/tree.d.ts +2 -0
  43. package/dist/tree.d.ts.map +1 -0
  44. package/dist/tree.js +2 -0
  45. package/dist/tree.js.map +1 -0
  46. package/dist/walker.d.ts +15 -0
  47. package/dist/walker.d.ts.map +1 -0
  48. package/dist/walker.js +106 -0
  49. package/dist/walker.js.map +1 -0
  50. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @dogsbay/format-fluidtopics-markdown
2
+
3
+ Importer for the **Fluid Topics Markdown export** shape — e.g. the
4
+ [`ServiceNow/ServiceNowDocs`](https://github.com/ServiceNow/ServiceNowDocs)
5
+ GitHub repo (Apache-2.0). Converts an FT Markdown repo into Dogsbay's universal
6
+ contract (`ExportPage[]` + `NavItem[]`) via the shared `TreeNode` model, so every
7
+ existing exporter/renderer consumes it unchanged.
8
+
9
+ Import-only. (Export back to Fluid Topics is not supported.)
10
+
11
+ ## The format it handles
12
+
13
+ A `markdown/{publication}/**.md` tree where:
14
+
15
+ - each publication's **`index.md`** has `doc_type: toc` and an indented Markdown
16
+ link list (the authored sidebar); the **README** "List of publications" gives
17
+ the top-level order;
18
+ - topic frontmatter carries DITA semantics — `topic_type` (concept / task /
19
+ reference), `title`, `description`, `locale`, `release`, `last_updated`;
20
+ - bodies are *dirty* Markdown: embedded raw `<table>` / DITA **nav-card** grids,
21
+ **two link schemes** (relative `.md` in bodies, absolute
22
+ `raw.githubusercontent` URLs in TOCs), and **omitted media** (dangling
23
+ relative image paths).
24
+
25
+ See the format analysis in `dogsbay-research`
26
+ (`content/formats/fluidtopics-markdown-format.md`).
27
+
28
+ ## Usage
29
+
30
+ ```ts
31
+ import { importFluidTopicsProject } from "@dogsbay/format-fluidtopics-markdown";
32
+
33
+ const { pages, nav } = await importFluidTopicsProject("/path/to/ServiceNowDocs", {
34
+ hrefPrefix: "/docs",
35
+ });
36
+ ```
37
+
38
+ Or via the format plugin (for CLI composition):
39
+
40
+ ```ts
41
+ import { plugin } from "@dogsbay/format-fluidtopics-markdown/cli";
42
+ // plugin.name === "fluidtopics-markdown"; plugin.detectSource(path); plugin.import(...)
43
+ ```
44
+
45
+ ## What it does
46
+
47
+ - **Frontmatter** → Dogsbay conventions (`topic_type`→`type`, `last_updated`→`date`,
48
+ `locale`→`lang`, `release`→`version`); unknown keys preserved.
49
+ - **Embedded HTML tables** → structured `table` nodes; **`nav-card`** grids →
50
+ `cards` nodes (title / description / icon / href).
51
+ - **Links** → resolved to routes from both the relative-`.md` and the absolute
52
+ `raw.githubusercontent` schemes; unresolved hrefs are reported, not fatal.
53
+ - **Nav** → built from each publication's `index.md` TOC + the README order.
54
+
55
+ ## Status
56
+
57
+ Phase 1–3 scaffold (parser + importer + nav, tests green). Not yet wired into the
58
+ CLI `convert` / a `migrate-fluidtopics` command, and media handling currently
59
+ leaves icon/image paths untouched — see
60
+ `dogsbay-research/plans/format-fluidtopics-markdown.md`.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { FormatPlugin } from "@dogsbay/types";
2
+ /**
3
+ * Fluid Topics Markdown format plugin.
4
+ *
5
+ * Import-only: consumes a ServiceNowDocs-style FT Markdown export (a
6
+ * `markdown/{publication}/**` tree). Export back to FT is not supported.
7
+ */
8
+ export declare const plugin: FormatPlugin;
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,gBAAgB,CAAC;AAGxE;;;;;GAKG;AACH,eAAO,MAAM,MAAM,EAAE,YAepB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,20 @@
1
+ import { detectSource, importFluidTopicsProject } from "./importer.js";
2
+ /**
3
+ * Fluid Topics Markdown format plugin.
4
+ *
5
+ * Import-only: consumes a ServiceNowDocs-style FT Markdown export (a
6
+ * `markdown/{publication}/**` tree). Export back to FT is not supported.
7
+ */
8
+ export const plugin = {
9
+ name: "fluidtopics-markdown",
10
+ canImport: true,
11
+ canExport: false,
12
+ detectSource,
13
+ importOptions: [],
14
+ async import(source, opts) {
15
+ const { pages, nav } = await importFluidTopicsProject(source, opts);
16
+ opts.siteName = opts.siteName || "Documentation";
17
+ return { pages, nav };
18
+ },
19
+ };
20
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAiB;IAClC,IAAI,EAAE,sBAAsB;IAC5B,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,KAAK;IAChB,YAAY;IACZ,aAAa,EAAE,EAAE;IAEjB,KAAK,CAAC,MAAM,CACV,MAAc,EACd,IAA6B;QAE7B,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,MAAM,wBAAwB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACpE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,eAAe,CAAC;QACjD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACxB,CAAC;CACF,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Lift YAML frontmatter from a raw markdown source. Returns the parsed object
3
+ * and the body with the block stripped. Tolerates malformed/empty YAML.
4
+ */
5
+ export declare function extractFrontmatter(source: string): {
6
+ frontmatter: Record<string, unknown> | undefined;
7
+ body: string;
8
+ };
9
+ /**
10
+ * Map a Fluid-Topics frontmatter object onto Dogsbay frontmatter conventions.
11
+ *
12
+ * Known FT keys are translated to their Dogsbay equivalents; unrecognised keys
13
+ * pass through unchanged so nothing is lost. The result is suitable for
14
+ * `parseMeta(...)` and for emitting as page frontmatter.
15
+ */
16
+ export declare function mapFrontmatter(fm: Record<string, unknown> | undefined): Record<string, unknown>;
17
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG;IAClD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACjD,IAAI,EAAE,MAAM,CAAC;CACd,CAeA;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACtC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkCzB"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Frontmatter handling for the Fluid Topics Markdown export.
3
+ *
4
+ * The export's frontmatter keys are inconsistent across files and over time
5
+ * (e.g. `llms.txt` advertises `product_area`/`canonical_url`, while topic
6
+ * files actually carry `topic_type`/`description`/`locale`/`release`). So
7
+ * everything here is **presence-keyed and optional** — we map what's there
8
+ * and never assume a key exists.
9
+ *
10
+ * See content/formats/fluidtopics-markdown-format.md in dogsbay-research.
11
+ */
12
+ import YAML from "yaml";
13
+ /** Match a YAML frontmatter block at the start of a markdown source. */
14
+ const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
15
+ /**
16
+ * Lift YAML frontmatter from a raw markdown source. Returns the parsed object
17
+ * and the body with the block stripped. Tolerates malformed/empty YAML.
18
+ */
19
+ export function extractFrontmatter(source) {
20
+ const match = FRONTMATTER_RE.exec(source);
21
+ if (!match)
22
+ return { frontmatter: undefined, body: source };
23
+ const body = source.slice(match[0].length);
24
+ const raw = match[1];
25
+ if (raw.trim().length === 0)
26
+ return { frontmatter: undefined, body };
27
+ try {
28
+ const parsed = YAML.parse(raw);
29
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
30
+ return { frontmatter: parsed, body };
31
+ }
32
+ }
33
+ catch {
34
+ /* tolerate malformed YAML */
35
+ }
36
+ return { frontmatter: undefined, body };
37
+ }
38
+ /**
39
+ * Map a Fluid-Topics frontmatter object onto Dogsbay frontmatter conventions.
40
+ *
41
+ * Known FT keys are translated to their Dogsbay equivalents; unrecognised keys
42
+ * pass through unchanged so nothing is lost. The result is suitable for
43
+ * `parseMeta(...)` and for emitting as page frontmatter.
44
+ */
45
+ export function mapFrontmatter(fm) {
46
+ if (!fm)
47
+ return {};
48
+ const out = {};
49
+ // DITA topic type (concept | task | reference) → Dogsbay `type`.
50
+ if (typeof fm.topic_type === "string")
51
+ out.type = fm.topic_type;
52
+ // Last-edited date → `date`.
53
+ if (fm.last_updated != null)
54
+ out.date = fm.last_updated;
55
+ // Locale → `lang`.
56
+ if (typeof fm.locale === "string")
57
+ out.lang = fm.locale;
58
+ // Release family → `version`.
59
+ if (typeof fm.release === "string")
60
+ out.version = fm.release;
61
+ // Straight-through fields.
62
+ if (typeof fm.title === "string")
63
+ out.title = fm.title;
64
+ if (typeof fm.description === "string")
65
+ out.description = fm.description;
66
+ // Preserve every other source key (excluding the ones already remapped or
67
+ // intentionally dropped) so the import is non-lossy.
68
+ const remapped = new Set([
69
+ "topic_type",
70
+ "last_updated",
71
+ "locale",
72
+ "release",
73
+ "title",
74
+ "description",
75
+ "reading_time_minutes", // derivable; dropped from output
76
+ "doc_type", // structural marker for TOC files
77
+ "bundle", // structural marker for TOC files
78
+ ]);
79
+ for (const [k, v] of Object.entries(fm)) {
80
+ if (!remapped.has(k) && !(k in out))
81
+ out[k] = v;
82
+ }
83
+ return out;
84
+ }
85
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../src/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,wEAAwE;AACxE,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAI/C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACrB,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACrE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,EAAE,WAAW,EAAE,MAAiC,EAAE,IAAI,EAAE,CAAC;QAClE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,EAAuC;IAEvC,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IACnB,MAAM,GAAG,GAA4B,EAAE,CAAC;IAExC,iEAAiE;IACjE,IAAI,OAAO,EAAE,CAAC,UAAU,KAAK,QAAQ;QAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,UAAU,CAAC;IAChE,6BAA6B;IAC7B,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI;QAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC;IACxD,mBAAmB;IACnB,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ;QAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,MAAM,CAAC;IACxD,8BAA8B;IAC9B,IAAI,OAAO,EAAE,CAAC,OAAO,KAAK,QAAQ;QAAE,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;IAC7D,2BAA2B;IAC3B,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ;QAAE,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACvD,IAAI,OAAO,EAAE,CAAC,WAAW,KAAK,QAAQ;QAAE,GAAG,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC;IAEzE,0EAA0E;IAC1E,qDAAqD;IACrD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;QACvB,YAAY;QACZ,cAAc;QACd,QAAQ;QACR,SAAS;QACT,OAAO;QACP,aAAa;QACb,sBAAsB,EAAE,iCAAiC;QACzD,UAAU,EAAE,kCAAkC;QAC9C,QAAQ,EAAE,kCAAkC;KAC7C,CAAC,CAAC;IACH,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Heading extraction + slug population.
3
+ *
4
+ * Walks the tree, computes a stable slug for each heading (de-duplicated),
5
+ * writes `slug`/`text` back onto the heading node's props (so the renderer
6
+ * can emit anchors), and returns the flat Heading[] for TOC generation.
7
+ */
8
+ import type { TreeNode, Heading } from "@dogsbay/types";
9
+ export declare function extractHeadings(tree: TreeNode[]): Heading[];
10
+ /** Extract plain text from a node, preferring structured inline content. */
11
+ export declare function extractText(node: TreeNode): string;
12
+ export declare function slugify(text: string): string;
13
+ //# sourceMappingURL=headings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.d.ts","sourceRoot":"","sources":["../src/headings.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAc,MAAM,gBAAgB,CAAC;AAEpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CA6B3D;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAWlD;AAyBD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU5C"}
@@ -0,0 +1,76 @@
1
+ export function extractHeadings(tree) {
2
+ const headings = [];
3
+ const seen = new Map();
4
+ function walk(nodes) {
5
+ for (const node of nodes) {
6
+ if (node.type === "heading") {
7
+ const level = node.props?.level ?? 1;
8
+ const text = extractText(node);
9
+ let slug = node.props?.slug;
10
+ if (!slug) {
11
+ const base = slugify(text);
12
+ const count = seen.get(base) ?? 0;
13
+ slug = count === 0 ? base : `${base}-${count}`;
14
+ seen.set(base, count + 1);
15
+ }
16
+ if (!node.props)
17
+ node.props = {};
18
+ node.props.slug = slug;
19
+ node.props.text = text;
20
+ headings.push({ depth: level, slug, text });
21
+ }
22
+ if (node.children)
23
+ walk(node.children);
24
+ }
25
+ }
26
+ walk(tree);
27
+ return headings;
28
+ }
29
+ /** Extract plain text from a node, preferring structured inline content. */
30
+ export function extractText(node) {
31
+ const parts = [];
32
+ if (node.inline) {
33
+ parts.push(extractInlineText(node.inline));
34
+ }
35
+ else if (node.html) {
36
+ parts.push(node.html.replace(/<[^>]+>/g, ""));
37
+ }
38
+ if (node.children) {
39
+ for (const child of node.children)
40
+ parts.push(extractText(child));
41
+ }
42
+ return parts.filter(Boolean).join(" ").trim();
43
+ }
44
+ function extractInlineText(nodes) {
45
+ const parts = [];
46
+ for (const node of nodes) {
47
+ switch (node.type) {
48
+ case "text":
49
+ case "code":
50
+ parts.push(node.text);
51
+ break;
52
+ case "link":
53
+ case "highlight":
54
+ parts.push(extractInlineText(node.children));
55
+ break;
56
+ case "image":
57
+ if (node.alt)
58
+ parts.push(node.alt);
59
+ break;
60
+ case "html-inline":
61
+ parts.push(node.html.replace(/<[^>]+>/g, ""));
62
+ break;
63
+ }
64
+ }
65
+ return parts.join("");
66
+ }
67
+ export function slugify(text) {
68
+ return (text
69
+ .toLowerCase()
70
+ .replace(/[^\w\s-]/g, "")
71
+ .replace(/\s+/g, "-")
72
+ .replace(/-+/g, "-")
73
+ .replace(/^-+|-+$/g, "")
74
+ .trim() || "section");
75
+ }
76
+ //# sourceMappingURL=headings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.js","sourceRoot":"","sources":["../src/headings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Embedded-HTML handling for the Fluid Topics Markdown export.
3
+ *
4
+ * FT/DITA exports emit raw `<table>` blocks inside the Markdown — including
5
+ * DITA "nav-card" landing grids — often with blank lines inside cells (which
6
+ * would otherwise shred the table across multiple markdown-it html_blocks) and
7
+ * sometimes with no blank line before the following heading (`</table>##...`).
8
+ *
9
+ * Strategy: BEFORE markdown-it runs, replace each `<table>...</table>` span
10
+ * with an isolated placeholder comment (so the surrounding markdown parses
11
+ * cleanly); AFTER walking, swap each placeholder node for a structured
12
+ * TreeNode:
13
+ * - `class="nav-card"` -> a `cards` node (title / description / icon / href)
14
+ * - any other table -> a `table` node (thead/tbody/tr/th/td + prose cells)
15
+ */
16
+ import type MarkdownIt from "markdown-it";
17
+ import type { TreeNode } from "@dogsbay/types";
18
+ /** Pull `<table>...</table>` spans out of the source, leaving placeholders. */
19
+ export declare function protectTables(source: string): {
20
+ source: string;
21
+ tables: string[];
22
+ };
23
+ /** Replace placeholder html nodes with structured table/cards TreeNodes. */
24
+ export declare function restoreTables(tree: TreeNode[], tables: string[], md: MarkdownIt): void;
25
+ //# sourceMappingURL=html-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-blocks.d.ts","sourceRoot":"","sources":["../src/html-blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,UAAU,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK/C,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAQlF;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,CAkBtF"}
@@ -0,0 +1,151 @@
1
+ const TABLE_RE = /<table[\s\S]*?<\/table>/gi;
2
+ const PLACEHOLDER_RE = /<!--FT_TABLE_(\d+)-->/;
3
+ /** Pull `<table>...</table>` spans out of the source, leaving placeholders. */
4
+ export function protectTables(source) {
5
+ const tables = [];
6
+ const out = source.replace(TABLE_RE, (m) => {
7
+ const i = tables.length;
8
+ tables.push(m);
9
+ return `\n\n<!--FT_TABLE_${i}-->\n\n`;
10
+ });
11
+ return { source: out, tables };
12
+ }
13
+ /** Replace placeholder html nodes with structured table/cards TreeNodes. */
14
+ export function restoreTables(tree, tables, md) {
15
+ function walk(nodes) {
16
+ for (let i = 0; i < nodes.length; i++) {
17
+ const node = nodes[i];
18
+ if (node.type === "html" && node.html) {
19
+ const m = PLACEHOLDER_RE.exec(node.html);
20
+ if (m) {
21
+ const idx = Number(m[1]);
22
+ if (tables[idx] !== undefined) {
23
+ nodes[i] = convertTable(tables[idx], md);
24
+ continue;
25
+ }
26
+ }
27
+ }
28
+ if (node.children)
29
+ walk(node.children);
30
+ }
31
+ }
32
+ walk(tree);
33
+ }
34
+ const ROW_RE = /<tr[^>]*>([\s\S]*?)<\/tr>/gi;
35
+ const CELL_RE = /<t([dh])\b[^>]*>([\s\S]*?)<\/t\1>/gi;
36
+ const LINK_RE = /\[([\s\S]*?)\]\(([^)]+)\)/;
37
+ const IMG_RE = /!\[[^\]]*\]\(([^)]+)\)/;
38
+ /** Private-use marker that stands in for an extracted inline image so the
39
+ * remaining link label can be split into title / description around it. */
40
+ const ICON_MARKER = String.fromCharCode(0xe000);
41
+ function isNavCard(html) {
42
+ return /<table[^>]*\bclass="[^"]*\bnav-card\b[^"]*"/i.test(html);
43
+ }
44
+ /** Parse one nav-card cell into a card item. */
45
+ function parseCard(cellInner) {
46
+ const trimmed = cellInner.trim();
47
+ if (!trimmed)
48
+ return null;
49
+ // Extract the inline image (icon) FIRST and replace it with a marker, so the
50
+ // outer link regex isn't fooled by the image's own `](...)`. The nav-card
51
+ // label shape is "Title text ![](icon.svg) Description text".
52
+ let icon;
53
+ let work = trimmed;
54
+ const img = IMG_RE.exec(trimmed);
55
+ if (img) {
56
+ icon = img[1].trim();
57
+ work = trimmed.slice(0, img.index) + ICON_MARKER + trimmed.slice(img.index + img[0].length);
58
+ }
59
+ const link = LINK_RE.exec(work);
60
+ if (!link) {
61
+ const card = { title: stripTags(trimmed) };
62
+ if (icon)
63
+ card.icon = icon;
64
+ return card;
65
+ }
66
+ const href = link[2].trim();
67
+ const [titleRaw, ...descParts] = link[1].split(ICON_MARKER);
68
+ const title = stripTags(titleRaw || "").trim() || stripTags(trimmed.split(ICON_MARKER).join(" "));
69
+ const description = descParts.length > 0 ? stripTags(descParts.join("")).trim() || undefined : undefined;
70
+ const card = { title };
71
+ if (description)
72
+ card.description = description;
73
+ if (icon)
74
+ card.icon = icon;
75
+ if (href)
76
+ card.href = href;
77
+ return card;
78
+ }
79
+ function convertTable(html, md) {
80
+ const rows = [];
81
+ let rowMatch;
82
+ ROW_RE.lastIndex = 0;
83
+ while ((rowMatch = ROW_RE.exec(html)) !== null) {
84
+ const cells = [];
85
+ let header = false;
86
+ let cellMatch;
87
+ CELL_RE.lastIndex = 0;
88
+ while ((cellMatch = CELL_RE.exec(rowMatch[1])) !== null) {
89
+ if (cellMatch[1].toLowerCase() === "h")
90
+ header = true;
91
+ cells.push(cellMatch[2]);
92
+ }
93
+ if (cells.length > 0)
94
+ rows.push({ header, cells });
95
+ }
96
+ if (isNavCard(html)) {
97
+ // Emit `cards` with child `card` nodes (props: title/href/description/icon)
98
+ // — the shape @dogsbay/format-dogsbay-md's serializer renders as :::cards.
99
+ const children = [];
100
+ for (const row of rows) {
101
+ for (const cell of row.cells) {
102
+ const card = parseCard(cell);
103
+ if (!card)
104
+ continue;
105
+ const props = { title: card.title };
106
+ if (card.description)
107
+ props.description = card.description;
108
+ if (card.icon)
109
+ props.icon = card.icon;
110
+ if (card.href)
111
+ props.href = card.href;
112
+ children.push({ type: "card", props });
113
+ }
114
+ }
115
+ return { type: "cards", props: { variant: "nav-card" }, children };
116
+ }
117
+ // Generic table -> thead/tbody/tr/th/td with prose cells.
118
+ const headerRows = rows.filter((r) => r.header);
119
+ const bodyRows = rows.filter((r) => !r.header);
120
+ const children = [];
121
+ const cellNode = (inner, tag) => ({
122
+ type: tag,
123
+ children: [{ type: "prose", html: md.renderInline(stripBlank(inner)) }],
124
+ });
125
+ if (headerRows.length > 0) {
126
+ children.push({
127
+ type: "thead",
128
+ children: headerRows.map((r) => ({
129
+ type: "tr",
130
+ children: r.cells.map((c) => cellNode(c, "th")),
131
+ })),
132
+ });
133
+ }
134
+ if (bodyRows.length > 0) {
135
+ children.push({
136
+ type: "tbody",
137
+ children: bodyRows.map((r) => ({
138
+ type: "tr",
139
+ children: r.cells.map((c) => cellNode(c, "td")),
140
+ })),
141
+ });
142
+ }
143
+ return { type: "table", children };
144
+ }
145
+ function stripTags(s) {
146
+ return s.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
147
+ }
148
+ function stripBlank(s) {
149
+ return s.replace(/\s+/g, " ").trim();
150
+ }
151
+ //# sourceMappingURL=html-blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-blocks.js","sourceRoot":"","sources":["../src/html-blocks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ExportPage, NavItem } from "@dogsbay/types";
2
+ export interface FluidTopicsImportResult {
3
+ pages: ExportPage[];
4
+ nav: NavItem[];
5
+ /** Hrefs that could not be resolved, keyed by page slug (for reporting). */
6
+ unresolved: Record<string, string[]>;
7
+ }
8
+ /**
9
+ * Detect a Fluid Topics Markdown export: a `markdown/` directory whose
10
+ * immediate subfolders each contain an `index.md`, and/or an `llms.txt`.
11
+ */
12
+ export declare function detectSource(path: string): boolean;
13
+ export declare function importFluidTopicsProject(sourceDir: string, opts?: Record<string, unknown>): Promise<FluidTopicsImportResult>;
14
+ //# sourceMappingURL=importer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAM1D,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,GAAG,EAAE,OAAO,EAAE,CAAC;IACf,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYlD;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,uBAAuB,CAAC,CA+DlC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Fluid Topics Markdown export importer.
3
+ *
4
+ * Reads a ServiceNowDocs-style repo (a `markdown/{publication}/**.md` tree with
5
+ * per-publication `index.md` TOCs and a README "List of publications") and
6
+ * produces ExportPage[] + NavItem[] — the universal format contract.
7
+ */
8
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
9
+ import { join, relative, basename } from "node:path";
10
+ import MarkdownIt from "markdown-it";
11
+ import { parseMeta } from "@dogsbay/types";
12
+ import { parseFluidTopicsMarkdown } from "./parse.js";
13
+ import { mapFrontmatter } from "./frontmatter.js";
14
+ import { buildNav } from "./nav.js";
15
+ /**
16
+ * Detect a Fluid Topics Markdown export: a `markdown/` directory whose
17
+ * immediate subfolders each contain an `index.md`, and/or an `llms.txt`.
18
+ */
19
+ export function detectSource(path) {
20
+ const markdownDir = join(path, "markdown");
21
+ if (!existsSync(markdownDir))
22
+ return false;
23
+ if (existsSync(join(path, "llms.txt")))
24
+ return true;
25
+ try {
26
+ return readdirSync(markdownDir).some((entry) => {
27
+ const sub = join(markdownDir, entry);
28
+ return statSync(sub).isDirectory() && existsSync(join(sub, "index.md"));
29
+ });
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ export async function importFluidTopicsProject(sourceDir, opts = {}) {
36
+ const markdownDir = join(sourceDir, "markdown");
37
+ if (!existsSync(markdownDir)) {
38
+ throw new Error(`No markdown/ directory found in ${sourceDir}`);
39
+ }
40
+ const hrefPrefix = typeof opts.hrefPrefix === "string" ? opts.hrefPrefix : "/docs";
41
+ const taxonomyNames = Array.isArray(opts.taxonomyNames)
42
+ ? opts.taxonomyNames
43
+ : undefined;
44
+ // Optional publication allow-list (the top-level markdown/{pub} folder).
45
+ // Lets callers scope a migration to one or a few publications.
46
+ const publications = Array.isArray(opts.publications) && opts.publications.length > 0
47
+ ? new Set(opts.publications)
48
+ : undefined;
49
+ const md = new MarkdownIt({ html: true, linkify: true });
50
+ let files = findMarkdownFiles(markdownDir);
51
+ if (publications) {
52
+ files = files.filter((f) => {
53
+ const top = relative(markdownDir, f).split(/[\\/]/)[0];
54
+ return publications.has(top);
55
+ });
56
+ }
57
+ const pages = [];
58
+ const unresolved = {};
59
+ for (const filePath of files) {
60
+ const relPath = relative(markdownDir, filePath).split("\\").join("/");
61
+ const slug = relPath.replace(/\.md$/i, "").replace(/\/index$/i, "").replace(/^index$/i, "") || "index";
62
+ try {
63
+ const source = readFileSync(filePath, "utf-8");
64
+ const { tree, headings, frontmatter, unresolvedLinks } = parseFluidTopicsMarkdown(source, md, { fromRelPath: relPath, hrefPrefix });
65
+ const mapped = mapFrontmatter(frontmatter);
66
+ const title = (typeof mapped.title === "string" && mapped.title) ||
67
+ headings.find((h) => h.depth === 1)?.text ||
68
+ labelFromPath(relPath);
69
+ const meta = parseMeta(mapped, { slug, taxonomyNames });
70
+ pages.push({ slug, title, tree, headings, frontmatter: mapped, meta });
71
+ if (unresolvedLinks.length > 0)
72
+ unresolved[slug] = unresolvedLinks;
73
+ }
74
+ catch (err) {
75
+ console.warn(`Warning: failed to parse ${relPath}: ${err.message}`);
76
+ }
77
+ }
78
+ const nav = buildNav(sourceDir, {
79
+ hrefPrefix,
80
+ publications: publications ? [...publications] : undefined,
81
+ });
82
+ return { pages, nav, unresolved };
83
+ }
84
+ // ── helpers ─────────────────────────────────────────────
85
+ function findMarkdownFiles(dir) {
86
+ const results = [];
87
+ function walk(d) {
88
+ for (const entry of readdirSync(d)) {
89
+ const full = join(d, entry);
90
+ if (statSync(full).isDirectory()) {
91
+ walk(full);
92
+ }
93
+ else if (entry.toLowerCase().endsWith(".md")) {
94
+ results.push(full);
95
+ }
96
+ }
97
+ }
98
+ walk(dir);
99
+ return results.sort();
100
+ }
101
+ function labelFromPath(path) {
102
+ return basename(path, ".md")
103
+ .replace(/[-_]/g, " ")
104
+ .replace(/\b\w/g, (c) => c.toUpperCase());
105
+ }
106
+ //# sourceMappingURL=importer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importer.js","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"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"}