dogsbay 0.2.0-beta.56 → 0.2.0-beta.57

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.
@@ -367,7 +367,16 @@ function buildDogsbayConfig(opts) {
367
367
  // them so format-astro skips emitting routes — the .md files
368
368
  // stay on disk so includes resolve, but they don't become
369
369
  // URLs. See plans/build-at-scale.md.
370
- ` excludeFromRoutes:`, ` - _attributes`, ` - modules`, ` - snippets`, ` - includes`);
370
+ ` excludeFromRoutes:`, ` - _attributes`, ` - modules`, ` - snippets`, ` - includes`,
371
+ // The AsciiBinder topic_map → nav.yml is the authoritative page
372
+ // list. routablesFrom: nav restricts the page set to pages the nav
373
+ // names; everything else (modules, fragments) is pulled into
374
+ // assemblies via `{% include %}` and never emitted standalone — so
375
+ // no fragment leaks into routes / llms.txt / search, and the build
376
+ // skips the ~85% of files that are includes (10.7k → ~1.7k on
377
+ // OpenShift). excludeFromRoutes above stays as the fallback for
378
+ // `routablesFrom: all`. See plans/nav-scoped-processing.md.
379
+ ` routablesFrom: nav`);
371
380
  // Persist the migration-time --attribute values so subsequent
372
381
  // `dogsbay site build` runs resolve {{ var }} / {% if %}
373
382
  // without re-passing flags. The build-time preprocessor
@@ -224,6 +224,23 @@ export async function siteBuild(cwd, options) {
224
224
  // local preview. Nav entries pointing at filtered pages are pruned
225
225
  // recursively so the sidebar doesn't have dead links.
226
226
  let { pages, nav, draftCount } = filterDrafts(importResult.pages, importResult.nav, options.includeDrafts === true, config.site.basePath);
227
+ // 7a. routablesFrom: "nav" — restrict the page set to pages the nav
228
+ // lists (the whitelist). Include-only fragments (modules/, _attributes/,
229
+ // etc.) are inlined into their assemblies by the preprocessor, so they
230
+ // never need to be standalone pages; dropping them here keeps them out
231
+ // of autodoc, ref-validation, llms.txt, the sitemap, and emit — no
232
+ // fragment-detection needed. Self-safe: a no-op when the nav is
233
+ // auto-derived (it already covers every page) or routablesFrom is
234
+ // "all". See plans/nav-scoped-processing.md.
235
+ if (config.content.routablesFrom === "nav") {
236
+ const before = pages.length;
237
+ const { kept, dropped } = filterPagesToNav(pages, nav, config.site.basePath);
238
+ if (dropped.length > 0) {
239
+ pages = kept;
240
+ console.log(` routablesFrom: nav — excluded ${dropped.length} non-nav page(s) of ${before}` +
241
+ ` (e.g. ${dropped.slice(0, 5).join(", ")})`);
242
+ }
243
+ }
227
244
  // 7b. onContentImported — let plugins mutate the page set + nav.
228
245
  if (resolvedPlugins.some((p) => p.plugin.onContentImported)) {
229
246
  const after = await runOnContentImported(resolvedPlugins, baseCtx, pages, nav);
@@ -677,3 +694,56 @@ function hrefMatchesDroppedSlug(href, droppedSlugs, basePath) {
677
694
  }
678
695
  return droppedSlugs.has(normalized) || droppedSlugs.has(normalized || "index");
679
696
  }
697
+ /**
698
+ * Collect the slug of every nav leaf (recursively), normalized the same
699
+ * way `hrefMatchesDroppedSlug` normalizes a single href — strip
700
+ * leading/trailing slashes, strip leading basePath segments; an empty
701
+ * result (the home href `/`) maps to `index`. Used by
702
+ * `content.routablesFrom: "nav"` to restrict the page set to
703
+ * nav-reachable pages.
704
+ */
705
+ function collectNavSlugs(items, basePath) {
706
+ const out = new Set();
707
+ const bp = normalizeBasePath(basePath);
708
+ const segs = basePathSegments(bp);
709
+ const walk = (nodes) => {
710
+ for (const item of nodes) {
711
+ if (item.href) {
712
+ let normalized = item.href.replace(/^\/+/, "").replace(/\/+$/, "");
713
+ for (const seg of segs) {
714
+ if (normalized === seg) {
715
+ normalized = "";
716
+ break;
717
+ }
718
+ if (normalized.startsWith(`${seg}/`)) {
719
+ normalized = normalized.slice(seg.length + 1);
720
+ }
721
+ }
722
+ out.add(normalized || "index");
723
+ }
724
+ if (item.children && item.children.length > 0)
725
+ walk(item.children);
726
+ }
727
+ };
728
+ walk(items);
729
+ return out;
730
+ }
731
+ /**
732
+ * Partition pages by nav membership for `content.routablesFrom: "nav"`.
733
+ * `kept` = pages whose slug is reachable from the nav; `dropped` = the
734
+ * slugs of pages the nav omits (include-only fragments — their content
735
+ * is already inlined into the assemblies that include them). Pure +
736
+ * exported for unit testing.
737
+ */
738
+ export function filterPagesToNav(pages, nav, basePath) {
739
+ const navSlugs = collectNavSlugs(nav, basePath);
740
+ const kept = [];
741
+ const dropped = [];
742
+ for (const page of pages) {
743
+ if (navSlugs.has(page.slug))
744
+ kept.push(page);
745
+ else
746
+ dropped.push(page.slug);
747
+ }
748
+ return { kept, dropped };
749
+ }
@@ -466,6 +466,13 @@ function validateContent(content, sourcePath) {
466
466
  }
467
467
  excludeFromRoutes = c.excludeFromRoutes;
468
468
  }
469
+ let routablesFrom;
470
+ if (c.routablesFrom !== undefined) {
471
+ if (c.routablesFrom !== "all" && c.routablesFrom !== "nav") {
472
+ throw new Error(`content.routablesFrom must be "all" or "nav" in ${sourcePath}`);
473
+ }
474
+ routablesFrom = c.routablesFrom;
475
+ }
469
476
  return {
470
477
  sources: validatedSources,
471
478
  section: c.section,
@@ -476,6 +483,7 @@ function validateContent(content, sourcePath) {
476
483
  locales,
477
484
  defaultLocale,
478
485
  excludeFromRoutes,
486
+ routablesFrom,
479
487
  };
480
488
  }
481
489
  function validateLocales(raw, sourcePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dogsbay",
3
- "version": "0.2.0-beta.56",
3
+ "version": "0.2.0-beta.57",
4
4
  "description": "CLI for Dogsbay — scaffold, build, and serve documentation sites with markdown / MkDocs / Obsidian / OpenAPI sources",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,18 +33,18 @@
33
33
  "picocolors": "^1.1.0",
34
34
  "prompts": "^2.4.2",
35
35
  "yaml": "^2.8.3",
36
- "@dogsbay/autodoc-python": "0.2.0-beta.56",
37
- "@dogsbay/format-mkdocs": "0.2.0-beta.56",
38
- "@dogsbay/format-astro": "0.2.0-beta.56",
39
- "@dogsbay/format-mdx": "0.2.0-beta.56",
40
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.56",
41
- "@dogsbay/format-obsidian": "0.2.0-beta.56",
42
- "@dogsbay/format-starlight": "0.2.0-beta.56",
43
- "@dogsbay/format-openapi": "0.2.0-beta.56",
44
- "@dogsbay/adoc2md-modular": "0.2.0-beta.56",
45
- "@dogsbay/format-asciidoc": "0.2.0-beta.56",
46
- "@dogsbay/minja": "0.2.0-beta.56",
47
- "@dogsbay/types": "0.2.0-beta.56"
36
+ "@dogsbay/format-mkdocs": "0.2.0-beta.57",
37
+ "@dogsbay/format-astro": "0.2.0-beta.57",
38
+ "@dogsbay/autodoc-python": "0.2.0-beta.57",
39
+ "@dogsbay/format-obsidian": "0.2.0-beta.57",
40
+ "@dogsbay/format-mdx": "0.2.0-beta.57",
41
+ "@dogsbay/format-starlight": "0.2.0-beta.57",
42
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.57",
43
+ "@dogsbay/format-openapi": "0.2.0-beta.57",
44
+ "@dogsbay/adoc2md-modular": "0.2.0-beta.57",
45
+ "@dogsbay/format-asciidoc": "0.2.0-beta.57",
46
+ "@dogsbay/minja": "0.2.0-beta.57",
47
+ "@dogsbay/types": "0.2.0-beta.57"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/markdown-it": "^14.1.0",