@heroiclands/package-build 22.3.0 → 22.4.0

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.
@@ -25,12 +25,18 @@
25
25
  *
26
26
  * **What is here is the pass, not the framing.** The walk, the frontmatter read,
27
27
  * the address derivation, the address index, table expansion, wikilink
28
- * resolution, code-fence protection, the foreign-manifest merge, the page
29
- * emission and the section-landing backfill are the same job everywhere. Where a
30
- * page's address comes from (`publish.address`), what a section is called, and
31
- * what a repository's own rewrites are, are not they arrive as configuration
32
- * and as a **named** pass bundle, since a configuration is data and cannot hold
33
- * a function.
28
+ * resolution, code-fence protection, the foreign-manifest merge and the page
29
+ * emission are the same job everywhere. Where a page's address comes from
30
+ * (`publish.address`) and what a repository's own rewrites are, are not — they
31
+ * arrive as configuration and as a **named** pass bundle, since a configuration
32
+ * is data and cannot hold a function.
33
+ *
34
+ * **A site is its homepage and its pages.** The homepage is the mount's
35
+ * `_index.md`, rendered at `/<package>/`; every other note is one page at
36
+ * `/<package>/<type>-<shortcode>/`. Nothing is generated between them — no
37
+ * section directory, no listing, no `_index.md` but the root's. An index of
38
+ * what the package publishes is a `doc` note carrying a content table, and it
39
+ * is authored where every other page is.
34
40
  *
35
41
  * **Every gate reports; none exits.** The integrity checks a site build needs —
36
42
  * a wikilink authored in frontmatter, a name that yields no slug, an unusable
@@ -62,14 +68,13 @@ import { deriveBeingInfo, isBeing } from "../sohl/being-info.mjs";
62
68
  import { loadPackConfig } from "./pack-config.mjs";
63
69
  import { routerFor } from "./pack-router.mjs";
64
70
  import { searchableFrontmatter } from "./note-package.mjs";
65
- import { hasAnyTag } from "./note-vocabulary.mjs";
66
71
  // The corpus, from the one pass that derives it.
67
72
  import { indexRecordsFor } from "./content-index.mjs";
68
73
  import { isNoteRecord, noteFile } from "./index-records.mjs";
69
74
  import { ART_SLOTS, artPathname, assetAddressIndex } from "./art-fields.mjs";
70
75
  import {
76
+ HOMEPAGE_DESTINATION,
71
77
  checkHomepageCount,
72
- homepageDestination,
73
78
  homepageFrontmatter,
74
79
  homepageTitle,
75
80
  isHomepage,
@@ -87,20 +92,12 @@ const require = createRequire(import.meta.url);
87
92
  * which files are the content. The note is still read for its `{fm, body}`: the
88
93
  * index carries no note text, and a page *is* its text.
89
94
  *
90
- * **The order changes, and that is the point of stating it here.** The walk this
91
- * replaces yielded directory order, and this module kept it deliberately
92
- * "a site's emitted pages should not reorder for no reason". Records are in
93
- * content-path order, which is the same set in a different sequence. Nothing
94
- * downstream depends on it any more: the first-writer-wins fallbacks that made
95
- * order load-bearing went with the bare `[[Name]]` form, each page is
96
- * emitted to its own file at an address derived from its frontmatter, and the
97
- * one place order could still show — a section's page list — is sorted by the
98
- * theme. Verified rather than argued: over `sohl`'s tree the emitted mount is
99
- * byte-identical, all 1,749 files.
100
- *
101
- * A **content-path** order is also the better of the two. Directory-read order
102
- * is a fact about the filesystem, not about the content, so it can differ
103
- * between two checkouts of one tree; this order cannot.
95
+ * **Records are in content-path order**, and nothing downstream depends on
96
+ * it: each page is emitted to its own file at an address derived from its
97
+ * frontmatter, and a content table orders what it lists by its own clause.
98
+ * A content-path order is a fact about the content, where directory-read
99
+ * order is a fact about the filesystem and can differ between two checkouts
100
+ * of one tree.
104
101
  *
105
102
  * @param {string} contentBase - Root of the content tree.
106
103
  * @param {object} ctx - The build context. `ctx.records` is the corpus when the
@@ -216,8 +213,8 @@ export function collectContentPages(contentBase, ctx) {
216
213
  // Location below the content root, POSIX-separated — what a
217
214
  // generated table reads as `file.path` and scopes on with `FROM`.
218
215
  relPath: rel.split(path.sep).join("/"),
219
- // The immediate source subfolder, the only surviving record of the
220
- // authoring folder, for grouped landings.
216
+ // The immediate source subfolder what a content table reads as
217
+ // `file.folder`, and the page's `kbfolder`.
221
218
  folder: path.basename(path.dirname(file)),
222
219
  // Every page is addressed by `(type, shortcode)` at the package
223
220
  // root, which takes no content mount. The file is written
@@ -275,25 +272,24 @@ export function collectHomepages(contentBase, ctx) {
275
272
  }
276
273
 
277
274
  /**
278
- * Writes each homepage at its address, below the package's own root.
275
+ * Writes each homepage as the package root's `_index.md`.
279
276
  *
280
277
  * Its own writer, deliberately small. A homepage is authored markdown published
281
- * verbatim — no table expansion, no section landing and no link resolution — so
282
- * routing it through {@link renderPages} would buy it a pipeline it has no input
283
- * for, and would make homepage-only mode depend on the index, the foreign
284
- * manifests and the table universe that mode exists to not build.
285
- *
286
- * **Verbatim is the answer, not a gap.** A landing's links
287
- * could not be *resolved* here without giving `homepage` mode the index its
288
- * licensing fence exists to not build, so they are **checked** instead:
289
- * {@link auditHomepageLinks} reads the `landing:` addresses and the body's
290
- * markdown links, and reports a wikilink on the page rather than resolving one.
291
- *
292
- * **Its destination is no longer fixed**. The file is written at the
293
- * note's address, flat at the package's site root, and the page states that
294
- * address as its `url` — the same separation of file from URL every other page
295
- * has. Nothing is written at `/<package>/` itself: that becomes a redirect the
296
- * package's own repository authors, which is a routing fact rather than a page.
278
+ * verbatim — no table expansion and no link resolution — so routing it through
279
+ * {@link renderPages} would buy it a pipeline it has no input for, and would
280
+ * make homepage-only mode depend on the index, the foreign manifests and the
281
+ * table universe that mode exists to not build.
282
+ *
283
+ * **Verbatim is the answer, not a gap.** A homepage's links could not be
284
+ * *resolved* here without giving `homepage` mode the index its licensing fence
285
+ * exists to not build, so they are **checked** instead:
286
+ * {@link auditHomepageLinks} reads the body's markdown links, and reports a
287
+ * wikilink on the page rather than resolving one.
288
+ *
289
+ * **Its destination is fixed**: {@link HOMEPAGE_DESTINATION}, at the package's
290
+ * site root, which Hugo renders as the `home` kind at `/<package>/`. The page
291
+ * states no `url` — the home kind has none to state and the mount below it
292
+ * holds pages and nothing else, so this is the only `_index.md` in the tree.
297
293
  *
298
294
  * @param {string} outRoot - The package's site root — the content mount's
299
295
  * root, `build/hugo/content`, one level above the mount itself.
@@ -308,7 +304,7 @@ export function writeHomepages(outRoot, pages, config) {
308
304
  contentPackage: config.contentPackage,
309
305
  title: homepageTitle(page.fm, config),
310
306
  });
311
- const dest = path.join(outRoot, homepageDestination(page.fm));
307
+ const dest = path.join(outRoot, HOMEPAGE_DESTINATION);
312
308
  fs.mkdirSync(path.dirname(dest), { recursive: true });
313
309
  fs.writeFileSync(dest, matter.stringify(page.body, data));
314
310
  }
@@ -325,7 +321,7 @@ export function writeHomepages(outRoot, pages, config) {
325
321
  * - **Frontmatter wikilinks** first, because frontmatter is copied to the page
326
322
  * verbatim and a link written in one reaches the reader as literal `[[…]]`.
327
323
  * - **Addresses** next: a note that has no address — no shortcode to be
328
- * addressed by, or no section to be filed under — would silently drop a page.
324
+ * addressed by — would silently drop a page.
329
325
  * There is no collision gate beside it: an address is `(type, shortcode)`,
330
326
  * which is unique within a package by rule, so two pages cannot claim one URL.
331
327
  * - **Foreign manifests** last, in two steps. *Unusable* is a file this build
@@ -441,39 +437,6 @@ export function tableUniverse(pages) {
441
437
  return byPackage;
442
438
  }
443
439
 
444
- /**
445
- * The front matter a section's landing states about itself.
446
- *
447
- * The section metadata a configuration resolved, ready to be written or merged
448
- * onto a page. Two things happen here and nothing else does:
449
- *
450
- * - **`title` leads.** It is the one key every landing has carried since the
451
- * first one, and a landing whose block opened with `banner:` would be a
452
- * gratuitous diff on every consumer's tree.
453
- * - **An absent value is left off**, not written as `undefined` — which is not
454
- * a value YAML can carry, and would abort the serializer.
455
- *
456
- * Everything else the section declared is passed through. That is the point of
457
- * the function: two writers transcribing `title` and `banner` by
458
- * name, so the vocabulary lived in three places — the schema that admits a key
459
- * and the two writers that copy it — and a key added to the schema alone
460
- * validated cleanly and then reached no page. The *schema* is the bound worth
461
- * keeping (see `normalizeSectionMeta`, which refuses a key it does not know and
462
- * names it); a second, silent bound in the writers is not.
463
- *
464
- * @param {object} meta - A resolved `site.sections` / `site.readmeSections`
465
- * entry.
466
- * @returns {object} Its front matter, `title` first.
467
- */
468
- export function sectionFrontmatter(meta) {
469
- const data = { title: meta.title };
470
- for (const [key, value] of Object.entries(meta)) {
471
- if (key === "title" || value === undefined) continue;
472
- data[key] = value;
473
- }
474
- return data;
475
- }
476
-
477
440
  /**
478
441
  * The frontmatter a page publishes with.
479
442
  *
@@ -504,10 +467,8 @@ export function sectionFrontmatter(meta) {
504
467
  * A content page carries the package the build **derived**. No note
505
468
  * declares one — `package:` is retired — so the note's frontmatter alone
506
469
  * would publish a page that does not say which package it belongs to. The
507
- * emitted page is what a
508
- * theme reads: `breadcrumbs.html` builds its middle crumb from
509
- * `.Params.package`, so without it that crumb degrades from a linked, labelled
510
- * section to a bare type slug. Writing the derived value keeps a page
470
+ * emitted page is what a theme reads: `breadcrumbs.html` reads
471
+ * `.Params.package`. Writing the derived value keeps a page
511
472
  * self-describing and makes sweeping the field out of a content tree
512
473
  * output-preserving for a site as it already is for the packs.
513
474
  *
@@ -592,16 +553,15 @@ function isPlainObject(value) {
592
553
  * Where a page is written, relative to the output root.
593
554
  *
594
555
  * **Flat, under the mount, named by its address**. A content page's URL
595
- * is its address — `/<package>/<type>-<shortcode>/` — and the file is now named
596
- * the same way, so the two agree. Filing it into `<section>/` so that
597
- * Hugo would read a section off its path; a section appears in no address, and
598
- * a directory chosen only to satisfy a rendering engine's idea of what a
599
- * section is has no business in the note format.
556
+ * is its address — `/<package>/<type>-<shortcode>/` — and the file is named
557
+ * the same way, so the two agree. No page creates a directory: a section is
558
+ * a Hugo listing over a content directory, the `section` kind is disabled on
559
+ * every site, and a directory chosen only to satisfy a rendering engine's idea
560
+ * of what a section is has no business in the note format.
600
561
  *
601
- * The name is the *whole* address rather than a section-relative half of it, so
602
- * two types cannot fight over one file: a `doc` note's `subType` may be spelled
603
- * the same as another note's `type`, and `doc-gear.md` and `weapongear-gear.md`
604
- * are distinct whatever the sections.
562
+ * The name is the *whole* address, so two types cannot fight over one file: a
563
+ * `doc` note's `subType` may be spelled the same as another note's `type`, and
564
+ * `doc-gear.md` and `weapongear-gear.md` are distinct.
605
565
  *
606
566
  * @param {object} page - The page.
607
567
  * @returns {string} The file, relative to the mount.
@@ -699,8 +659,7 @@ export function renderPages(pages, options) {
699
659
 
700
660
  for (const page of pages) {
701
661
  // The page's path in the tree an author edits, below the content
702
- // root. It is not composed as `<section>/<basename>`, which named a
703
- // directory that was never the note's.
662
+ // root.
704
663
  const src = page.relPath ?? page.base;
705
664
  const ctx = wikiContext(index, {
706
665
  src,
@@ -749,111 +708,6 @@ export function renderPages(pages, options) {
749
708
  return { written: pages.length, byKind, tableErrors, wikiErrors, imageErrors };
750
709
  }
751
710
 
752
- /**
753
- * Writes the Hugo sections a published tree declares.
754
- *
755
- * **This is where a section lives now, and the only place**. A content
756
- * note carries none: it is addressed by `(type, shortcode)` and emitted flat
757
- * under the mount, so nothing a page does creates a directory. A site that wants
758
- * `/<package>/<prefix><section>/` to answer — with a title, a hero, and whatever
759
- * listing its layout builds — says so here, in configuration, and this writes
760
- * the `_index.md` that makes Hugo agree it is a section.
761
- *
762
- * Three jobs, all of them Hugo's directory semantics rather than the note
763
- * format's:
764
- *
765
- * - **The mount's own landing**, so `/<package>/<prefix>` is a page rather than
766
- * a directory listing. It carries a `type` of its own: Hugo's template lookup
767
- * walks up a page's path, so an untyped landing template at the mount would
768
- * also serve every section below it that has none.
769
- * - **Declared sections** get a titled `_index.md` with their hero, so a landing
770
- * matches the card that links to it instead of showing Hugo's auto-humanised
771
- * directory name. The body is empty, which lets the theme decide what to list.
772
- * - **Every other directory directly under the mount** gets a bare `_index.md`,
773
- * or its own address publishes nothing. Hugo generates a section page
774
- * automatically only for a *top-level* content directory; below that, a
775
- * directory without an `_index.md` is not a section, so its URL 404s while its
776
- * children publish normally. With content pages flat, no note creates a
777
- * directory below the mount, so this reaches only what something else
778
- * placed there.
779
- *
780
- * **A section listing is not a page listing any more.** A layout that reads
781
- * `.Pages` off a section it declares here will find nothing, because no file is
782
- * filed into it; one that queries `site.RegularPages` by `Params.type` — which
783
- * is how `sohl`'s eleven catalog layouts already work — is unaffected. That is a
784
- * consumer's layout to choose, and it is stated here because the choice is no
785
- * longer free.
786
- *
787
- * Scoped to one level on purpose. A directory further down was not a section
788
- * before either, and giving it one here would silently re-scope the prev/next
789
- * navigation of every page inside it.
790
- *
791
- * @param {string} outRoot - The mount directory.
792
- * @param {object} options
793
- * @param {Record<string, object>} [options.sections] - The declared sections,
794
- * each written as a titled `_index.md` carrying its own frontmatter.
795
- * @param {object} [options.landing] - The mount's own landing frontmatter.
796
- * Omitted, the mount gets no `_index.md` of its own.
797
- * @param {((name: string) => string)|null} [options.sectionTitle] - Titles a
798
- * directory below the mount that declared no section. `null` leaves such a
799
- * directory without an `_index.md`.
800
- * @returns {number} How many landings were written.
801
- */
802
- export function writeSectionLandings(outRoot, { sections = {}, landing, sectionTitle }) {
803
- let written = 0;
804
-
805
- if (landing) {
806
- fs.mkdirSync(outRoot, { recursive: true });
807
- fs.writeFileSync(path.join(outRoot, "_index.md"), matter.stringify("", landing));
808
- written += 1;
809
- }
810
-
811
- for (const [sec, meta] of Object.entries(sections)) {
812
- const dir = path.join(outRoot, sec);
813
- fs.mkdirSync(dir, { recursive: true });
814
- // Whatever the section declared, not a list of keys named here — see
815
- // {@link sectionFrontmatter} for why the two lists were one too many.
816
- fs.writeFileSync(
817
- path.join(dir, "_index.md"),
818
- matter.stringify("", sectionFrontmatter(meta)),
819
- );
820
- written += 1;
821
- }
822
-
823
- if (!sectionTitle) return written;
824
- for (const entry of fs.readdirSync(outRoot, { withFileTypes: true })) {
825
- if (!entry.isDirectory()) continue;
826
- const index = path.join(outRoot, entry.name, "_index.md");
827
- if (fs.existsSync(index)) continue;
828
- fs.writeFileSync(index, matter.stringify("", { title: sectionTitle(entry.name) }));
829
- written += 1;
830
- }
831
- return written;
832
- }
833
-
834
- /**
835
- * A section landing's title, from its directory name — `macro` → `Macros`.
836
- *
837
- * Hugo derives exactly this for a section page it generates itself, but not for
838
- * one backed by an `_index.md`: an explicit file with no `title` renders a blank
839
- * heading. So a backfilled landing states its own, in plain English
840
- * pluralisation rather than Hugo's inflector, which spells that section
841
- * "Macroes".
842
- *
843
- * @param {string} name - The directory name.
844
- * @returns {string} The display title.
845
- */
846
- export function pluralTitle(name) {
847
- const plural =
848
- /(?:s|x|z|ch|sh)$/.test(name) ? `${name}es`
849
- : /[^aeiou]y$/.test(name) ? `${name.slice(0, -1)}ies`
850
- : `${name}s`;
851
- return plural
852
- .split("-")
853
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
854
- .join(" ");
855
- }
856
-
857
711
  /**
858
712
  * The named pass bundles a configuration may select.
859
713
  *
@@ -905,10 +759,7 @@ export function resolveSitePass(name, options) {
905
759
  * `sql` directive with none prepared is a table error: nothing here runs a
906
760
  * query.
907
761
  * @returns {{gates: object, stats: object|null, tableErrors: object[],
908
- * wikiErrors: object[], imageErrors: object[], manifests: object|null,
909
- * hasTags: boolean}} `hasTags` is whether any note the walk read carries
910
- * `tags:` — what {@link module:engine/site-config.hugoConfig} reads to
911
- * decide whether the site emits taxonomy pages.
762
+ * wikiErrors: object[], imageErrors: object[], manifests: object|null}}
912
763
  */
913
764
  export function buildSite({ config, sqlTables } = {}) {
914
765
  const resolved = config ?? loadPackConfig();
@@ -927,8 +778,8 @@ export function buildSite({ config, sqlTables } = {}) {
927
778
  const base = site.base || `/${resolved.contentPackage}/`;
928
779
  const mount = `${base}${scheme.prefix}`;
929
780
 
930
- // The Hugo content tree mirrors that mount: a page written to
931
- // `<out>/<prefix>/<section>/` publishes at `<base><prefix><section>/`.
781
+ // The Hugo content tree mirrors that mount: the homepage is written at
782
+ // `<out>/_index.md` and every page under `<out>/<prefix>/`.
932
783
  // The root is fixed — `build/hugo/content`, beside the generated
933
784
  // `hugo.toml` — and resolved against the repository root for the same
934
785
  // reason every configured path is, so the build reads and writes the same
@@ -942,9 +793,9 @@ export function buildSite({ config, sqlTables } = {}) {
942
793
  // Homepage-only has no content mount, so the package's root *is*
943
794
  // the output root.
944
795
  : outBase;
945
- // The homepage publishes at `/<contentPackage>/<type>-<shortcode>/`, so its
946
- // file goes at the package's own root — one level above the content mount,
947
- // and the same directory in homepage-only mode.
796
+ // The homepage publishes at `/<contentPackage>/`, so its file goes at the
797
+ // package's own root — one level above the content mount, and the same
798
+ // directory in homepage-only mode.
948
799
  const homeRoot = publishesContent ? outBase : out;
949
800
 
950
801
  const packages = new Set(site.packages.length ? site.packages : [resolved.contentPackage]);
@@ -1007,7 +858,6 @@ export function buildSite({ config, sqlTables } = {}) {
1007
858
  wikiErrors: [],
1008
859
  imageErrors: [],
1009
860
  stats: null,
1010
- hasTags: homepages.some((p) => hasAnyTag(p.fm)),
1011
861
  };
1012
862
  }
1013
863
 
@@ -1027,23 +877,21 @@ export function buildSite({ config, sqlTables } = {}) {
1027
877
  imageErrors: [],
1028
878
  stats: {
1029
879
  homepages: writeHomepages(homeRoot, homepages, resolved),
1030
- landings: 0,
1031
880
  out: homeRoot,
1032
881
  },
1033
- hasTags: homepages.some((p) => hasAnyTag(p.fm)),
1034
882
  };
1035
883
  }
1036
884
 
1037
885
  const content = collectContentPages(resolved.paths.content, ctx);
1038
886
  const { pages } = content;
1039
887
 
1040
- // The homepage is **indexed but not rendered**. Now that it has an
1041
- // address, `[[homepage-root|Text]]` is an ordinary wikilink and has to
1042
- // resolve to the page the build publishes which means the address index
1043
- // must hold it. It still takes no part in `renderPages`: a homepage is
1044
- // authored markdown published verbatim, with no table expansion and no link
1045
- // resolution of its own, and routing it through that pipeline would buy it
1046
- // a pass it has no input for.
888
+ // The homepage is **indexed but not rendered**. `[[homepage-root|Text]]`
889
+ // is an ordinary wikilink and has to resolve to the page the build
890
+ // publishes which means the address index must hold it, at the address
891
+ // it publishes at: the package root. It takes no part in `renderPages`: a
892
+ // homepage is authored markdown published verbatim, with no table
893
+ // expansion and no link resolution of its own, and routing it through
894
+ // that pipeline would buy it a pass it has no input for.
1047
895
  const homepageEntries = homepages.map((page) => ({
1048
896
  kind: "content",
1049
897
  fm: page.fm,
@@ -1051,7 +899,7 @@ export function buildSite({ config, sqlTables } = {}) {
1051
899
  name: page.fm.name?.full ?? homepageTitle(page.fm, resolved),
1052
900
  slug: addressSlug(page.fm),
1053
901
  base: path.basename(page.file),
1054
- url: `${base}${addressSlug(page.fm)}/`,
902
+ url: base,
1055
903
  }));
1056
904
 
1057
905
  const gates = siteGates([...pages, ...homepageEntries], content, { config: resolved });
@@ -1062,7 +910,6 @@ export function buildSite({ config, sqlTables } = {}) {
1062
910
  tableErrors: [],
1063
911
  wikiErrors: [],
1064
912
  imageErrors: [],
1065
- hasTags: [...pages, ...homepageEntries].some((p) => hasAnyTag(p.fm)),
1066
913
  };
1067
914
  }
1068
915
 
@@ -1099,12 +946,6 @@ export function buildSite({ config, sqlTables } = {}) {
1099
946
  },
1100
947
  });
1101
948
 
1102
- const landings = writeSectionLandings(out, {
1103
- sections: site.sections,
1104
- landing: site.landing,
1105
- sectionTitle: site.backfillSections ? pluralTitle : null,
1106
- });
1107
-
1108
949
  // Last, and outside the mount: the package's front page is not part of the
1109
950
  // content tree it introduces.
1110
951
  const homepagesWritten = writeHomepages(homeRoot, homepages, resolved);
@@ -1117,10 +958,8 @@ export function buildSite({ config, sqlTables } = {}) {
1117
958
  stats: {
1118
959
  ...rendered.byKind,
1119
960
  homepages: homepagesWritten,
1120
- landings,
1121
961
  out,
1122
962
  },
1123
- hasTags: [...pages, ...homepageEntries].some((p) => hasAnyTag(p.fm)),
1124
963
  };
1125
964
  }
1126
965
 
@@ -32,8 +32,8 @@
32
32
  * and the site build writes `[menu.main]` from the cache.
33
33
  *
34
34
  * What a repository still says for itself is the residue that is genuinely
35
- * its own — the wording of its "page not found" page, whether its listings
36
- * show shortcodes — and, through `site.hugo`, the one key nobody anticipated.
35
+ * its own — the wording of its "page not found" page and, through
36
+ * `site.hugo`, the one key nobody anticipated.
37
37
  * {@link module:content-config.DERIVED_HUGO_KEYS} refuses everything the
38
38
  * generator writes from being authored there too.
39
39
  *
@@ -83,38 +83,17 @@ export const BRAND = Object.freeze({
83
83
  });
84
84
 
85
85
  /**
86
- * The kinds a site with no tagged notes renders.
87
- *
88
- * A section exists only where `site.sections` declares one, so a tree holding
89
- * only the homepage emits nothing beyond it; a taxonomy nobody's notes fill
90
- * and a feed would be empty shells. A site whose notes carry `tags:` emits
91
- * `taxonomy` and `term` after all see {@link hugoConfig} but `RSS` is
92
- * disabled either way: nothing here publishes a feed.
93
- */
94
- export const DISABLE_KINDS = Object.freeze(["taxonomy", "term", "RSS"]);
95
-
96
- /**
97
- * The kinds a site with at least one tagged note renders — everything but
98
- * `RSS`.
99
- */
100
- const DISABLE_KINDS_TAGGED = Object.freeze(["RSS"]);
101
-
102
- /**
103
- * The single taxonomy a tagged site declares.
104
- *
105
- * Only `tag` — Hugo's default pair also declares `category`, which nothing
106
- * here authors and which would publish an empty `/categories/`.
86
+ * The kinds no site renders.
87
+ *
88
+ * A site is its homepage and its pages: `home` and `page` are the only kinds,
89
+ * for every package and whatever its notes carry. A `section` is a listing
90
+ * Hugo would generate from a content directory; `taxonomy` and `term` are
91
+ * listings it would generate from `tags:`; `RSS` is a feed. Every structure
92
+ * between the homepage and the pages is authored instead, as a `doc` note
93
+ * carrying a content table — a tag is a field such a table filters on, not a
94
+ * page of its own.
107
95
  */
108
- const TAXONOMIES = Object.freeze({ tag: "tags" });
109
-
110
- /**
111
- * The taxonomy output formats a tagged site declares — `HTML` only, so no
112
- * feed is produced for `/tags/` or a single tag.
113
- */
114
- const TAXONOMY_OUTPUTS = Object.freeze({
115
- taxonomy: Object.freeze(["HTML"]),
116
- term: Object.freeze(["HTML"]),
117
- });
96
+ export const DISABLE_KINDS = Object.freeze(["section", "taxonomy", "term", "RSS"]);
118
97
 
119
98
  /**
120
99
  * The markup settings the toolchain's own output requires.
@@ -383,15 +362,12 @@ function deepMerge(base, overrides) {
383
362
  * @param {object} options.config - The resolved build configuration.
384
363
  * @param {readonly NavigationEntry[]} options.navigation - The navigation.
385
364
  * @param {string} options.themesDir - From {@link resolveThemesDir}.
386
- * @param {boolean} [options.hasTags] - Whether any note the site build walked
387
- * carries `tags:`, from {@link module:engine/site-build.buildSite}'s
388
- * `hasTags`. Defaults to `false` — no tagged note, no taxonomy pages.
389
365
  * @returns {Record<string, any>} The configuration Hugo reads.
390
366
  * @throws {TypeError} When `homepage` fails `checkHomepage`, or the
391
367
  * configuration declares no `packageBuild.manifest.title`, no
392
368
  * `site.description`, or no `site.assets`.
393
369
  */
394
- export function hugoConfig({ config, navigation, themesDir, hasTags = false }) {
370
+ export function hugoConfig({ config, navigation, themesDir }) {
395
371
  checkHomepage(config.homepage, config.contentPackage);
396
372
 
397
373
  const title = config.packageBuild?.manifest?.title;
@@ -421,7 +397,6 @@ export function hugoConfig({ config, navigation, themesDir, hasTags = false }) {
421
397
  if (config.author?.name) params.author = config.author.name;
422
398
  if (config.site.assets) params.cdnBaseURL = config.site.assets;
423
399
  params.brand = { ...BRAND };
424
- params.list = { ...config.site.list };
425
400
  if (config.site.notfound) params.notfound = structuredClone(config.site.notfound);
426
401
 
427
402
  const generated = {
@@ -432,18 +407,11 @@ export function hugoConfig({ config, navigation, themesDir, hasTags = false }) {
432
407
  themesDir,
433
408
  theme: THEME,
434
409
  contentDir: path.posix.relative(HUGO_SOURCE, HUGO_CONTENT),
435
- disableKinds: hasTags ? [...DISABLE_KINDS_TAGGED] : [...DISABLE_KINDS],
410
+ disableKinds: [...DISABLE_KINDS],
436
411
  params,
437
412
  markup: structuredClone(MARKUP),
438
413
  menu: { main: menuEntries(navigation) },
439
414
  };
440
- if (hasTags) {
441
- generated.taxonomies = { ...TAXONOMIES };
442
- generated.outputs = {
443
- taxonomy: [...TAXONOMY_OUTPUTS.taxonomy],
444
- term: [...TAXONOMY_OUTPUTS.term],
445
- };
446
- }
447
415
  return deepMerge(generated, config.site.hugo);
448
416
  }
449
417
 
@@ -470,20 +438,14 @@ export function hugoToml(generated) {
470
438
  * run this before touching the output tree and fail with it intact.
471
439
  *
472
440
  * @param {object} config - The resolved build configuration.
473
- * @param {object} [options] - Options.
474
- * @param {boolean} [options.hasTags] - Whether any note the site build walked
475
- * carries `tags:`. Defaults to `false`, so a caller generating the
476
- * configuration before the walk (to fail fast on a missing source) gets the
477
- * untagged shape; pass the site build's own `hasTags` once it is known.
478
441
  * @returns {Record<string, any>} The configuration Hugo reads.
479
442
  * @throws {Error} When any source is missing or wrong.
480
443
  */
481
- export function generateHugoConfig(config, { hasTags = false } = {}) {
444
+ export function generateHugoConfig(config) {
482
445
  return hugoConfig({
483
446
  config,
484
447
  navigation: readCachedNavigation(config),
485
448
  themesDir: resolveThemesDir(config.rootDir),
486
- hasTags,
487
449
  });
488
450
  }
489
451