dogsbay 0.2.0-beta.12 → 0.2.0-beta.121

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 (153) hide show
  1. package/dist/audit/deliverable-vars.d.ts +18 -0
  2. package/dist/audit/deliverable-vars.js +143 -0
  3. package/dist/audit/rules/structure/index.d.ts +2 -0
  4. package/dist/audit/rules/structure/index.js +10 -30
  5. package/dist/audit/rules/structure/nav-target-exists.d.ts +2 -0
  6. package/dist/audit/rules/structure/nav-target-exists.js +107 -0
  7. package/dist/audit/rules/structure/topicmap-valid.d.ts +12 -0
  8. package/dist/audit/rules/structure/topicmap-valid.js +25 -0
  9. package/dist/child-links.d.ts +19 -0
  10. package/dist/child-links.js +48 -0
  11. package/dist/commands/add.d.ts +8 -0
  12. package/dist/commands/add.js +1 -1
  13. package/dist/commands/convert.d.ts +64 -0
  14. package/dist/commands/convert.js +682 -52
  15. package/dist/commands/deliverables.d.ts +7 -0
  16. package/dist/commands/deliverables.js +41 -0
  17. package/dist/commands/export-techdocs.d.ts +13 -0
  18. package/dist/commands/import-dita.d.ts +23 -0
  19. package/dist/commands/import-dita.js +129 -0
  20. package/dist/commands/import-ditamap.d.ts +37 -0
  21. package/dist/commands/import-ditamap.js +284 -0
  22. package/dist/commands/import-mkdocs.d.ts +9 -0
  23. package/dist/commands/import-mkdocs.js +112 -69
  24. package/dist/commands/init.d.ts +1 -0
  25. package/dist/commands/lighthouse.d.ts +13 -0
  26. package/dist/commands/migrate-asciidoc.d.ts +72 -0
  27. package/dist/commands/migrate-asciidoc.js +1078 -0
  28. package/dist/commands/migrate-docusaurus.d.ts +44 -0
  29. package/dist/commands/migrate-docusaurus.js +464 -0
  30. package/dist/commands/migrate-fluidtopics-rest.d.ts +21 -0
  31. package/dist/commands/migrate-fluidtopics-rest.js +149 -0
  32. package/dist/commands/migrate-fluidtopics.d.ts +13 -0
  33. package/dist/commands/migrate-fluidtopics.js +158 -0
  34. package/dist/commands/migrate-gitbook.d.ts +11 -0
  35. package/dist/commands/migrate-gitbook.js +323 -0
  36. package/dist/commands/migrate-hugo.d.ts +13 -0
  37. package/dist/commands/migrate-hugo.js +297 -0
  38. package/dist/commands/migrate-mkdocs.d.ts +87 -0
  39. package/dist/commands/migrate-mkdocs.js +1005 -0
  40. package/dist/commands/migrate-myst.d.ts +28 -0
  41. package/dist/commands/migrate-myst.js +256 -0
  42. package/dist/commands/migrate-rst.d.ts +18 -0
  43. package/dist/commands/migrate-rst.js +291 -0
  44. package/dist/commands/preprocess-variants.d.ts +3 -0
  45. package/dist/commands/resolve.d.ts +27 -0
  46. package/dist/commands/resolve.js +79 -0
  47. package/dist/commands/site-build.d.ts +198 -0
  48. package/dist/commands/site-build.js +742 -17
  49. package/dist/commands/site-check.d.ts +36 -0
  50. package/dist/commands/site-check.js +121 -17
  51. package/dist/commands/site-dev.d.ts +57 -0
  52. package/dist/commands/site-dev.js +99 -13
  53. package/dist/commands/site-init.d.ts +44 -0
  54. package/dist/commands/site-init.js +62 -7
  55. package/dist/commands/site-release-diff.d.ts +17 -0
  56. package/dist/commands/site-release-diff.js +85 -0
  57. package/dist/commands/topicmap.d.ts +16 -0
  58. package/dist/commands/topicmap.js +123 -0
  59. package/dist/config/defaults.d.ts +14 -0
  60. package/dist/config/defaults.js +41 -22
  61. package/dist/config/deliverables.d.ts +44 -0
  62. package/dist/config/deliverables.js +68 -0
  63. package/dist/config/find.d.ts +6 -0
  64. package/dist/config/index.d.ts +13 -0
  65. package/dist/config/index.js +2 -1
  66. package/dist/config/load.d.ts +25 -0
  67. package/dist/config/load.js +562 -41
  68. package/dist/config/output.d.ts +2 -0
  69. package/dist/config/schema.d.ts +1202 -0
  70. package/dist/config/schema.js +6 -0
  71. package/dist/config/serialize.d.ts +3 -0
  72. package/dist/config/to-astro-options.d.ts +18 -0
  73. package/dist/config/to-astro-options.js +58 -1
  74. package/dist/engine-cli.d.ts +19 -0
  75. package/dist/engine-cli.js +142 -0
  76. package/dist/formats.d.ts +122 -0
  77. package/dist/formats.js +179 -0
  78. package/dist/import-content.d.ts +218 -0
  79. package/dist/import-content.js +274 -150
  80. package/dist/index.d.ts +2 -0
  81. package/dist/index.js +484 -30
  82. package/dist/lib.d.ts +19 -0
  83. package/dist/lib.js +21 -0
  84. package/dist/passthrough-astro.d.ts +35 -0
  85. package/dist/passthrough-astro.js +152 -0
  86. package/dist/plugins/context.d.ts +36 -0
  87. package/dist/plugins/index.d.ts +11 -0
  88. package/dist/plugins/lifecycle.d.ts +76 -0
  89. package/dist/plugins/loader.d.ts +38 -0
  90. package/dist/registry.d.ts +9 -0
  91. package/dist/registry.js +11 -3
  92. package/dist/release/compare.d.ts +43 -0
  93. package/dist/release/compare.js +419 -0
  94. package/dist/resolve-autodoc.d.ts +24 -0
  95. package/dist/resolve-autodoc.js +239 -0
  96. package/dist/resolve-openapi.d.ts +34 -0
  97. package/dist/resolve-openapi.js +191 -0
  98. package/dist/resolve-relative-links.d.ts +32 -0
  99. package/dist/resolve-relative-links.js +80 -0
  100. package/dist/site-build/preprocess.d.ts +148 -0
  101. package/dist/site-build/preprocess.js +756 -0
  102. package/dist/source-resolver.d.ts +60 -0
  103. package/dist/topicmap-nav.d.ts +45 -0
  104. package/dist/topicmap-nav.js +112 -0
  105. package/dist/utils/git.d.ts +8 -0
  106. package/dist/utils/git.js +34 -0
  107. package/dist/utils/gitignore.d.ts +8 -0
  108. package/dist/utils/gitignore.js +54 -0
  109. package/dist/utils/link-labels.d.ts +25 -0
  110. package/dist/utils/link-labels.js +67 -0
  111. package/dist/utils/project.d.ts +3 -0
  112. package/dist/utils/remote-includes.d.ts +35 -0
  113. package/dist/utils/remote-includes.js +317 -0
  114. package/dist/versioning/docusaurus.d.ts +53 -0
  115. package/dist/versioning/docusaurus.js +136 -0
  116. package/dist/versioning/lineage.d.ts +107 -0
  117. package/dist/versioning/lineage.js +212 -0
  118. package/package.json +66 -18
  119. package/skills/platform/{agent-readiness → dogsbay-agent-readiness}/SKILL.md +2 -2
  120. package/skills/platform/{cli-commands → dogsbay-cli-commands}/SKILL.md +2 -2
  121. package/skills/platform/{config-yml → dogsbay-config-yml}/SKILL.md +34 -7
  122. package/skills/platform/dogsbay-documents/SKILL.md +222 -0
  123. package/skills/platform/{frontmatter-fields → dogsbay-frontmatter-fields}/SKILL.md +25 -6
  124. package/skills/platform/{markdown-directives → dogsbay-markdown-directives}/SKILL.md +45 -4
  125. package/skills/platform/dogsbay-migration-shape/SKILL.md +314 -0
  126. package/skills/platform/{multi-source → dogsbay-multi-source}/SKILL.md +1 -1
  127. package/skills/platform/{nav-file → dogsbay-nav-file}/SKILL.md +26 -1
  128. package/skills/platform/{openapi-source → dogsbay-openapi-source}/SKILL.md +1 -1
  129. package/skills/platform/{plugin-api → dogsbay-plugin-api}/SKILL.md +1 -1
  130. package/skills/platform/{project-anatomy → dogsbay-project-anatomy}/SKILL.md +1 -1
  131. package/skills/platform/{taxonomy-config → dogsbay-taxonomy-config}/SKILL.md +90 -1
  132. package/skills/platform/{theme-tokens → dogsbay-theme-tokens}/SKILL.md +1 -1
  133. package/dist/audit/dist/crawler.js +0 -71
  134. package/dist/audit/output/json.js +0 -3
  135. package/dist/audit/output/text.js +0 -84
  136. package/dist/audit/registry.js +0 -62
  137. package/dist/audit/rules/seo/description-length.js +0 -26
  138. package/dist/audit/rules/seo/description-missing.js +0 -24
  139. package/dist/audit/rules/seo/dist-meta.js +0 -65
  140. package/dist/audit/rules/seo/h1-brand-keyword.js +0 -99
  141. package/dist/audit/rules/seo/index.js +0 -63
  142. package/dist/audit/rules/seo/json-ld-parse.js +0 -32
  143. package/dist/audit/rules/seo/json-ld-required-fields.js +0 -116
  144. package/dist/audit/rules/seo/keyword-placement.js +0 -122
  145. package/dist/audit/rules/seo/sitemap.js +0 -240
  146. package/dist/audit/rules/seo/title-missing.js +0 -33
  147. package/dist/audit/rules/structure/locale-coherence.js +0 -32
  148. package/dist/audit/rules/structure/namespace-coherence.js +0 -39
  149. package/dist/audit/rules/structure/version-coherence.js +0 -42
  150. package/dist/audit/run.js +0 -89
  151. package/dist/audit/types.js +0 -1
  152. package/dist/commands/agent.js +0 -305
  153. package/dist/commands/pull.js +0 -717
@@ -0,0 +1,18 @@
1
+ import type { Issue } from "@dogsbay/site-check";
2
+ /**
3
+ * Root identifiers referenced by an expression: quoted strings and
4
+ * dotted property tails stripped, reserved words and numbers excluded.
5
+ * `platform == "win" and product.name` → {platform, product}.
6
+ */
7
+ export declare function expressionIdentifiers(expr: string): Set<string>;
8
+ export interface DeliverableVarsOptions {
9
+ /** Resolved source content directories to scan. */
10
+ contentDirs: string[];
11
+ /** The deliverable's merged attribute context. */
12
+ attributes: Record<string, unknown>;
13
+ /** Deliverable name, for the message. */
14
+ deliverable: string;
15
+ /** Paths in issues are made relative to this root. */
16
+ siteRoot: string;
17
+ }
18
+ export declare function auditDeliverableVariables(options: DeliverableVarsOptions): Promise<Issue[]>;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Undefined-variable audit for deliverables (plans/deliverables.md
3
+ * Phase 1): preserve-parse every source markdown file, collect the
4
+ * variables referenced by `{{ … }}` / `{% if … %}` / inline spans, and
5
+ * report any not defined by the deliverable's merged attribute context.
6
+ * A `{% if distro == "rosa" %}` whose variable NO deliverable defines is
7
+ * almost certainly a typo — and invisible at build time, because the
8
+ * terminal build treats undefined conditions as falsy and silently drops
9
+ * the block.
10
+ *
11
+ * Command-side helper (does I/O); the result is surfaced through a
12
+ * registered corpus rule closure so it joins the normal audit report and
13
+ * exit-code accounting.
14
+ */
15
+ import { readdir, readFile } from "node:fs/promises";
16
+ import { join, relative } from "node:path";
17
+ import { dogsbayMdToTree } from "@dogsbay/format-dogsbay-md";
18
+ /** Words that appear in expressions but are never variables. */
19
+ const EXPR_RESERVED = new Set([
20
+ "true",
21
+ "false",
22
+ "null",
23
+ "none",
24
+ "and",
25
+ "or",
26
+ "not",
27
+ "in",
28
+ "is",
29
+ "defined",
30
+ "undefined",
31
+ ]);
32
+ /**
33
+ * Root identifiers referenced by an expression: quoted strings and
34
+ * dotted property tails stripped, reserved words and numbers excluded.
35
+ * `platform == "win" and product.name` → {platform, product}.
36
+ */
37
+ export function expressionIdentifiers(expr) {
38
+ const out = new Set();
39
+ const stripped = expr
40
+ .replace(/"[^"]*"|'[^']*'/g, " ")
41
+ .replace(/\.[A-Za-z_][A-Za-z0-9_-]*/g, "");
42
+ for (const m of stripped.matchAll(/[A-Za-z_][A-Za-z0-9_-]*/g)) {
43
+ const id = m[0];
44
+ if (!EXPR_RESERVED.has(id.toLowerCase()))
45
+ out.add(id);
46
+ }
47
+ return out;
48
+ }
49
+ function collectInline(nodes, acc) {
50
+ for (const n of nodes ?? []) {
51
+ const t = n.type;
52
+ if (t === "variable") {
53
+ for (const id of expressionIdentifiers(n.expr)) {
54
+ addRef(acc, id, "{{ … }}");
55
+ }
56
+ }
57
+ else if (t === "conditional-span") {
58
+ const span = n;
59
+ for (const id of expressionIdentifiers(span.test))
60
+ addRef(acc, id, "{% if %}");
61
+ collectInline(span.children, acc);
62
+ }
63
+ else {
64
+ const children = n.children;
65
+ if (children)
66
+ collectInline(children, acc);
67
+ }
68
+ }
69
+ }
70
+ function addRef(acc, id, kind) {
71
+ const kinds = acc.refs.get(id) ?? new Set();
72
+ kinds.add(kind);
73
+ acc.refs.set(id, kinds);
74
+ }
75
+ function collectBlocks(nodes, acc) {
76
+ for (const node of nodes) {
77
+ if (node.type === "conditional") {
78
+ for (const id of expressionIdentifiers(String(node.props?.test ?? ""))) {
79
+ addRef(acc, id, "{% if %}");
80
+ }
81
+ }
82
+ else if (node.type === "var-def") {
83
+ acc.defined.add(String(node.props?.name ?? ""));
84
+ for (const id of expressionIdentifiers(String(node.props?.value ?? ""))) {
85
+ addRef(acc, id, "{% set %}");
86
+ }
87
+ }
88
+ if (node.inline)
89
+ collectInline(node.inline, acc);
90
+ if (node.children)
91
+ collectBlocks(node.children, acc);
92
+ }
93
+ }
94
+ async function mdFiles(dir) {
95
+ const out = [];
96
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
97
+ for (const entry of entries) {
98
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
99
+ continue;
100
+ const full = join(dir, entry.name);
101
+ if (entry.isDirectory())
102
+ out.push(...(await mdFiles(full)));
103
+ else if (entry.name.endsWith(".md"))
104
+ out.push(full);
105
+ }
106
+ return out;
107
+ }
108
+ /** hyphen/underscore-insensitive (the preprocessor runs hyphenToUnderscore). */
109
+ const canon = (s) => s.replace(/-/g, "_");
110
+ export async function auditDeliverableVariables(options) {
111
+ const known = new Set(Object.keys(options.attributes).map(canon));
112
+ const issues = [];
113
+ for (const dir of options.contentDirs) {
114
+ for (const file of await mdFiles(dir)) {
115
+ const source = await readFile(file, "utf8").catch(() => null);
116
+ if (source === null)
117
+ continue;
118
+ const acc = { refs: new Map(), defined: new Set() };
119
+ try {
120
+ const { tree } = dogsbayMdToTree(source, { directives: "preserve" });
121
+ collectBlocks(tree, acc);
122
+ }
123
+ catch {
124
+ continue; // unparseable file — other rules own that problem
125
+ }
126
+ for (const [id, kinds] of acc.refs) {
127
+ if (known.has(canon(id)))
128
+ continue;
129
+ if ([...acc.defined].some((d) => canon(d) === canon(id)))
130
+ continue;
131
+ issues.push({
132
+ ruleId: "directives/undefined-variable",
133
+ severity: "warning",
134
+ file: relative(options.siteRoot, file),
135
+ message: `"${id}" (used in ${[...kinds].join(", ")}) is not defined by ` +
136
+ `deliverable "${options.deliverable}" — the terminal build treats ` +
137
+ `undefined conditions as falsy, so guarded content is silently dropped.`,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ return issues;
143
+ }
@@ -0,0 +1,2 @@
1
+ /** Idempotent, like the shared register* functions. */
2
+ export declare function registerCliStructureRules(): void;
@@ -1,37 +1,17 @@
1
1
  /**
2
- * Structure rule category — register all `structure/*` rules
3
- * into the global registry. Imported by the audit runner /
4
- * site-check command at startup.
5
- *
6
- * Rules landed in this category:
7
- * - structure/namespace-coherence (corpus, multi-source axis)
8
- *
9
- * Future additions:
10
- * - structure/orphan-pages (corpus; pages not reachable from nav)
11
- * - structure/duplicate-slugs (corpus; two sources produced the same URL)
2
+ * The Astro CLI's own structure rules, registered on top of the shared
3
+ * ones from `@dogsbay/site-check`. Both read nav files and topic maps
4
+ * from disk, which dogsbay-ssg resolves differently.
12
5
  */
13
- import { registerRule } from "../../registry.js";
14
- import { localeCoherence } from "./locale-coherence.js";
15
- import { namespaceCoherence } from "./namespace-coherence.js";
16
- import { versionCoherence } from "./version-coherence.js";
6
+ import { registerRule } from "@dogsbay/site-check";
7
+ import { navTargetExists } from "./nav-target-exists.js";
8
+ import { topicmapValid } from "./topicmap-valid.js";
17
9
  let registered = false;
18
- /**
19
- * Idempotent — calling `registerStructureRules` more than once
20
- * is a no-op.
21
- */
22
- export function registerStructureRules() {
10
+ /** Idempotent, like the shared register* functions. */
11
+ export function registerCliStructureRules() {
23
12
  if (registered)
24
13
  return;
25
14
  registered = true;
26
- registerRule(namespaceCoherence);
27
- registerRule(versionCoherence);
28
- registerRule(localeCoherence);
29
- }
30
- /**
31
- * Test-only: reset the "registered" flag so unit tests can
32
- * register fresh after `_clearRegistry()`. Production code never
33
- * calls this.
34
- */
35
- export function _resetStructureRegistration() {
36
- registered = false;
15
+ registerRule(navTargetExists);
16
+ registerRule(topicmapValid);
37
17
  }
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "@dogsbay/site-check";
2
+ export declare const navTargetExists: Rule;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * `structure/nav-target-exists` — every `file:` reference in a
3
+ * declared nav file resolves to a content file on disk.
4
+ *
5
+ * Re-runs the same resolution `loadNavFile` does at build time,
6
+ * but in `collect` mode: instead of `console.warn` + label-only
7
+ * fallback (the build-pipeline behaviour), the audit surfaces
8
+ * every miss as a structured Issue. This is what makes nav-file
9
+ * typos:
10
+ *
11
+ * - count in the `dogsbay site check` summary
12
+ * - bump the exit code (error severity)
13
+ * - get categorised under `structure` for selective opt-out
14
+ *
15
+ * Without this rule, a misnamed file in nav.yml emitted a
16
+ * `[dogsbay] Nav file references missing file: ...` line to
17
+ * stderr during build but never entered the audit catalog, so
18
+ * CI couldn't catch it. Reported by the docs team against
19
+ * dogsbay-docs-platform.
20
+ *
21
+ * Source content + nav heuristic: matches `buildNavFromDirectory`
22
+ * — an explicit `source.nav` path wins, otherwise we look for
23
+ * `nav.yml` / `nav.yaml` / `nav.json` in the resolved content
24
+ * dir. If no nav file is present, the rule is a no-op (the site
25
+ * uses directory-scan nav, which has its own validation in the
26
+ * importer).
27
+ */
28
+ import { existsSync } from "node:fs";
29
+ import { isAbsolute, join, resolve } from "node:path";
30
+ import { loadNavFile } from "@dogsbay/format-dogsbay-md";
31
+ const HEURISTIC_NAV_FILES = ["nav.yml", "nav.yaml", "nav.json"];
32
+ export const navTargetExists = {
33
+ id: "structure/nav-target-exists",
34
+ category: "structure",
35
+ stage: "source-corpus",
36
+ severity: "error",
37
+ description: "Every `file:` reference in nav files (nav.yml / nav.yaml / nav.json) resolves to a content file that exists on disk.",
38
+ run(rawCtx) {
39
+ const ctx = rawCtx;
40
+ const auditSources = ctx.auditSources;
41
+ const siteRoot = ctx.siteRoot;
42
+ if (!auditSources || auditSources.length === 0 || !siteRoot) {
43
+ // No way to find nav files — typically a unit-test run with
44
+ // pre-imported nav.
45
+ return [];
46
+ }
47
+ const issues = [];
48
+ for (const src of auditSources) {
49
+ const { contentDir, navOverride } = src;
50
+ const navPath = resolveNavFile(navOverride, contentDir, siteRoot);
51
+ if (!navPath)
52
+ continue; // directory-scan source → skip
53
+ // Use `collect` mode so the loader pushes structured
54
+ // findings to onMissingTarget instead of console.warn'ing.
55
+ const misses = [];
56
+ try {
57
+ loadNavFile(navPath, contentDir, {
58
+ missingFile: "collect",
59
+ onMissingTarget: (m) => misses.push(m),
60
+ });
61
+ }
62
+ catch (err) {
63
+ // Nav file itself is unreadable / malformed — emit as a
64
+ // distinct issue. The build's heuristic-loader warns and
65
+ // falls back to directory scan; the audit makes the
66
+ // condition visible.
67
+ issues.push({
68
+ ruleId: "structure/nav-target-exists",
69
+ severity: "error",
70
+ file: navPath,
71
+ message: `Nav file ${navPath} failed to load: ${err.message}`,
72
+ });
73
+ continue;
74
+ }
75
+ for (const miss of misses) {
76
+ issues.push({
77
+ ruleId: "structure/nav-target-exists",
78
+ severity: "error",
79
+ file: navPath,
80
+ message: `Nav references missing file: ${miss.file} ` +
81
+ `(resolved to ${miss.resolvedAbs}). To fix: rename the ` +
82
+ `file to match, correct the path in the nav file, or ` +
83
+ `remove the entry.`,
84
+ context: miss.file,
85
+ });
86
+ }
87
+ }
88
+ return issues;
89
+ },
90
+ };
91
+ /**
92
+ * Mirror the resolution `buildNavFromDirectory` uses:
93
+ * 1. Explicit `source.nav` wins (relative to siteRoot)
94
+ * 2. Auto-detect nav.{yml,yaml,json} in the resolved content dir
95
+ * 3. Otherwise undefined (directory-scan nav — no nav file to audit)
96
+ */
97
+ function resolveNavFile(navOverride, contentDir, siteRoot) {
98
+ if (navOverride) {
99
+ return isAbsolute(navOverride) ? navOverride : resolve(siteRoot, navOverride);
100
+ }
101
+ for (const fileName of HEURISTIC_NAV_FILES) {
102
+ const candidate = join(contentDir, fileName);
103
+ if (existsSync(candidate))
104
+ return candidate;
105
+ }
106
+ return undefined;
107
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * structure/topicmap-valid — surfaces topic-map resolution problems
3
+ * (cycles, missing includes, malformed entries, variable errors) as
4
+ * structured audit issues.
5
+ *
6
+ * The audit runner is synchronous while topic-map resolution is async, so
7
+ * site-check resolves each topic-map nav BEFORE the audit run and attaches
8
+ * the errors to `AuditSourceInfo.topicmapErrors`; this rule just reports
9
+ * them with the standard exit-code/severity plumbing.
10
+ */
11
+ import type { Rule } from "@dogsbay/site-check";
12
+ export declare const topicmapValid: Rule;
@@ -0,0 +1,25 @@
1
+ export const topicmapValid = {
2
+ id: "structure/topicmap-valid",
3
+ category: "structure",
4
+ stage: "source-corpus",
5
+ severity: "error",
6
+ description: "Topic-map navs (nav: *.topicmap.yml) must resolve cleanly: no cycles, missing includes, or malformed entries",
7
+ run(rawCtx) {
8
+ const ctx = rawCtx;
9
+ const issues = [];
10
+ for (const source of ctx.auditSources ?? []) {
11
+ if (!source.topicmapErrors)
12
+ continue;
13
+ const fallback = source.topicmapPath ?? source.navOverride ?? "(topic map)";
14
+ for (const error of source.topicmapErrors) {
15
+ issues.push({
16
+ ruleId: "structure/topicmap-valid",
17
+ severity: "error",
18
+ file: error.mapPath || fallback,
19
+ message: `[${error.code}] ${error.message}`,
20
+ });
21
+ }
22
+ }
23
+ return issues;
24
+ },
25
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Realize `::child-links` on every page against the site's nav
3
+ * (plans/child-links.md). Shared by `site build` (after the nav is final) and
4
+ * `convert --to <anything but dogsbay-md>`; Dogsbay MD keeps the directive.
5
+ */
6
+ import type { ExportPage, NavItem } from "@dogsbay/types";
7
+ export interface SiteChildLinksResult {
8
+ realized: number;
9
+ /** `page: folder` for every listing that found no child pages. */
10
+ empty: string[];
11
+ /** `page: folder` for listings answered from the page directory, not the nav. */
12
+ fromDirectory: string[];
13
+ }
14
+ /**
15
+ * @param prefix the href prefix the nav was built with (`""` for `convert`,
16
+ * urlBase + basePath for `site build`): a page's nav href is
17
+ * `prefix/slug`, and a `folder=` path is site-relative, so it is prefixed too.
18
+ */
19
+ export declare function realizeSiteChildLinks(pages: ExportPage[], nav: NavItem[], prefix: string): SiteChildLinksResult;
@@ -0,0 +1,48 @@
1
+ import { directoryChildren, hasChildLinks, navKey, realizeChildLinks } from "@dogsbay/serialize-core";
2
+ /**
3
+ * @param prefix the href prefix the nav was built with (`""` for `convert`,
4
+ * urlBase + basePath for `site build`): a page's nav href is
5
+ * `prefix/slug`, and a `folder=` path is site-relative, so it is prefixed too.
6
+ */
7
+ export function realizeSiteChildLinks(pages, nav, prefix) {
8
+ const out = { realized: 0, empty: [], fromDirectory: [] };
9
+ if (!pages.some((p) => hasChildLinks(p.tree)))
10
+ return out;
11
+ const base = prefix.replace(/\/+$/, "");
12
+ const hrefOf = (slug) => `${base}/${slug.replace(/(^|\/)index$/, "")}`;
13
+ const info = new Map();
14
+ for (const page of pages) {
15
+ const { description, tags } = page.frontmatter ?? {};
16
+ info.set(navKey(hrefOf(page.slug)), {
17
+ title: page.title || undefined,
18
+ description: typeof description === "string" ? description : undefined,
19
+ tags: Array.isArray(tags) ? tags.map(String) : undefined,
20
+ // For `match=` (front-matter filters).
21
+ data: page.frontmatter,
22
+ });
23
+ }
24
+ // Trees carry SITE-relative links: Astro adds the base path itself, and an
25
+ // export target (a vault, MkDocs) has none. The nav is built with it on.
26
+ const outputHref = (href) => base && (href === base || href.startsWith(`${base}/`)) ? href.slice(base.length) || "/" : href;
27
+ let fallbackChildren;
28
+ for (const page of pages) {
29
+ if (!hasChildLinks(page.tree))
30
+ continue;
31
+ // Fragments, partials (`_`-prefixed segments) and redirect stubs are not pages to list.
32
+ fallbackChildren ??= directoryChildren(pages.filter((p) => !p.fragment && !p.redirect && !p.slug.split("/").some((seg) => seg.startsWith("_"))), hrefOf);
33
+ const r = realizeChildLinks(page.tree, {
34
+ nav,
35
+ fallbackChildren,
36
+ outputHref,
37
+ pageHref: hrefOf(page.slug),
38
+ folderHref: (folder) => `${base}/${folder.replace(/^\/+/, "")}`,
39
+ lookup: (href) => info.get(navKey(href)),
40
+ });
41
+ out.realized += r.realized;
42
+ for (const e of r.empty)
43
+ out.empty.push(`${page.slug}: ${e}`);
44
+ for (const e of r.fromFallback)
45
+ out.fromDirectory.push(`${page.slug}: ${e}`);
46
+ }
47
+ return out;
48
+ }
@@ -0,0 +1,8 @@
1
+ import { type RegistryEntry } from "../registry.js";
2
+ export declare function collectDependencies(names: string[]): {
3
+ components: RegistryEntry[];
4
+ npmDeps: string[];
5
+ primitives: Set<string>;
6
+ };
7
+ export declare function add(componentNames: string[]): void;
8
+ export declare function list(): void;
@@ -22,7 +22,7 @@ function getSourceDir() {
22
22
  return monorepo;
23
23
  throw new Error("Could not find component source files.");
24
24
  }
25
- function collectDependencies(names) {
25
+ export function collectDependencies(names) {
26
26
  const visited = new Set();
27
27
  const components = [];
28
28
  const npmDeps = [];
@@ -0,0 +1,64 @@
1
+ import type { ExportPage, FormatPlugin, NavItem } from "@dogsbay/types";
2
+ import { FORMATS as formats, detectFormat } from "../formats.js";
3
+ export { formats, detectFormat };
4
+ /**
5
+ * Conversion failure with an optional hint line (e.g. the list of
6
+ * available formats). `convert` (the CLI action) renders it red and
7
+ * exits; programmatic callers (the corpus update loop) catch it and
8
+ * ledger the failure instead of aborting the run.
9
+ */
10
+ export declare class ConvertError extends Error {
11
+ hint?: string;
12
+ constructor(message: string, hint?: string);
13
+ }
14
+ /**
15
+ * Parse + VALIDATE a declarative components config.
16
+ *
17
+ * The components map is the only thing that overrides format-mdx's built-in
18
+ * strip list, so a config that fails to load silently re-enables stripping —
19
+ * content vanishes from the output with no error. Every way that could happen
20
+ * is now an error instead:
21
+ *
22
+ * - empty / all-comment file → parsed as null, previously fell through as
23
+ * a falsy config and compilation was skipped entirely
24
+ * - wrapper-key typo (`component:` for `components:`) → the old
25
+ * `parsed?.components ?? parsed` fallback accepted the whole file and
26
+ * registered one mapping for a tag literally named "component"
27
+ * - a non-object entry, or an entry whose keys are all unrecognised
28
+ *
29
+ * Accepts either a bare mapping or a `{ components: … }` wrapper.
30
+ */
31
+ export declare function parseComponentsConfig(text: string, path: string): Record<string, unknown>;
32
+ /**
33
+ * Resolve the importer for a source and run it — the IMPORT half of
34
+ * `runConvert`, without an exporter.
35
+ *
36
+ * `dogsbay chunk` needs pages and nav but has no target format, and consumers
37
+ * that duplicate the plugin list drift from it (the registry is order-
38
+ * sensitive: gitbook and hugo must be tried before the generic dogsbay-md
39
+ * detector or their markup is silently skipped). One resolver, one order.
40
+ */
41
+ export declare function importPages(source: string, options: {
42
+ from?: string;
43
+ [key: string]: unknown;
44
+ }, log?: (msg: string) => void): Promise<{
45
+ pages: ExportPage[];
46
+ nav: NavItem[];
47
+ importer: FormatPlugin;
48
+ }>;
49
+ export declare function runConvert(source: string, options: {
50
+ from?: string;
51
+ to?: string;
52
+ output?: string;
53
+ [key: string]: unknown;
54
+ }, log?: (msg: string) => void): Promise<{
55
+ output: string;
56
+ pageCount: number;
57
+ }>;
58
+ /** CLI action: `dogsbay convert` — renders ConvertError red and exits. */
59
+ export declare function convert(source: string, options: {
60
+ from?: string;
61
+ to?: string;
62
+ output?: string;
63
+ [key: string]: unknown;
64
+ }): Promise<void>;