@heroiclands/package-build 22.2.0 → 22.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - b2bb2b2: **`content-build docs item-fields`.** A destination under the content tree
8
+ (`assets/content/`) now writes a complete note — `type: doc`,
9
+ `subType: reference`, a derived `shortcode`, `name.full` and `pack: none` —
10
+ rather than a typeless page the content walk silently drops. A consumer
11
+ declares further note frontmatter under `docs.itemFields.frontmatter`,
12
+ deep-merged over the generated envelope; `--check` compares the whole file.
13
+ A destination outside the content tree is unaffected.
14
+
15
+ ## 22.3.0
16
+
17
+ ### Minor Changes
18
+
19
+ - a79b33a: **A note may declare `pack: none`.** The universal `pack` key accepts one more value: `none` says the note publishes to the site and compiles into no Foundry document. Such a note is walked, published as a page, present in the content index with an address and no UUID, and addressable by wikilink like any other — on the web the link reaches the page, and in a compiled journal the reader gets the link's text with no document to open. Every pack compiler passes over it without a finding. It is accepted only on a type whose sole document is the JournalEntry its prose becomes (`doc`, `place`, `lore`, `scenario`) and refused by name on a type that compiles an Item, an Actor, a Macro, a Scene or an Adventure, naming the document it would drop. It is read per system like any other `pack:`, and a configured pack may not be called `none`.
20
+
21
+ **`site.trees` and `site.readmeSections` are refused.** A page is a note in the content tree, and there is no second mechanism for mounting a directory of markdown beside it. A configuration declaring either key fails with a message saying so. A package that mounted a directory of pages moves them into `assets/content/` as `doc` notes — each with a `shortcode` and `pack: none` — rewrites their relative links as wikilinks, and declares the section that lists them under `site.sections`. The `sohlKb` site pass no longer rewrites repository-relative links, and its `blob` option has no reader.
22
+
3
23
  ## 22.2.0
4
24
 
5
25
  ### Minor Changes
package/CONTENT.md CHANGED
@@ -488,6 +488,17 @@ pack: mysteries
488
488
  type one system maps and another does not stays silent for the system that
489
489
  declines it.
490
490
 
491
+ - **`pack: none` compiles the note into no document, on purpose.** The note is
492
+ walked, published as a page, present in the content index with an address
493
+ and no UUID, and addressable by wikilink; every pass passes over it without
494
+ a finding, and the finding above says nothing about it. It is accepted only
495
+ on a type whose sole document is the JournalEntry its prose becomes — a
496
+ `doc`, say — and refused by name on a type that compiles an Item, an Actor,
497
+ a Macro, a Scene or an Adventure, where it would drop that document. A
498
+ configured pack may not be called `none`. This is how a page of developer
499
+ documentation lives in the content tree beside everything else: it is a
500
+ note like any other, and it reaches no compendium.
501
+
491
502
  **The configuration is found by walking up from the working directory, and from
492
503
  the installed package only if that finds nothing.** `engine/pack-config.mjs`
493
504
  climbs from `process.cwd()` first, so a build reads the tree it was run in —
@@ -2256,10 +2267,10 @@ note-format knowledge against game-system knowledge, and a homepage carries no
2256
2267
 
2257
2268
  `publish.site` then says how much _else_ is published:
2258
2269
 
2259
- | Mode | What is published |
2260
- | ---------- | ------------------------------------------------------------------------------- |
2261
- | `homepage` | The authored homepage, and no other page. **The default, and the floor.** |
2262
- | `content` | The homepage plus every page the content tree compiles to, and its extra trees. |
2270
+ | Mode | What is published |
2271
+ | ---------- | ------------------------------------------------------------------------- |
2272
+ | `homepage` | The authored homepage, and no other page. **The default, and the floor.** |
2273
+ | `content` | The homepage plus every page the content tree compiles to. |
2263
2274
 
2264
2275
  There is no value meaning "no web presence": every package publishes its
2265
2276
  homepage. A boolean is refused, with a message naming the mode to write
@@ -2273,8 +2284,8 @@ content_ — journal text, artwork, item descriptions, compiled notes — and a
2273
2284
  announcing the module discloses none of it. Because the failure mode is silent,
2274
2285
  the mode **fences the content surfaces off** rather than trusting a
2275
2286
  configuration to stay empty: in `homepage` mode the tree is never walked for
2276
- pages, and `sections`, `trees`, `landing` and `backfillSections` emit nothing
2277
- even when they are declared.
2287
+ pages, and `sections`, `landing` and `backfillSections` emit nothing even when
2288
+ they are declared.
2278
2289
 
2279
2290
  That is separate from the **dependency** edge, which such a module also
2280
2291
  declines: being cited by another package is what would stop it being
@@ -2293,9 +2304,12 @@ does not reach it.
2293
2304
  **What it does not do is decide addresses.** Those come from `publish.address`,
2294
2305
  the same setting the content index reads, so a page and its index record cannot
2295
2306
  disagree about where the page is. Everything under `site:` is _framing_ —
2296
- what a section is called, which extra trees are published beside the content,
2297
- and the residue of the generated Hugo configuration that is this repository's
2298
- own:
2307
+ what a section is called, and the residue of the generated Hugo configuration
2308
+ that is this repository's own. What the site publishes is the content tree and
2309
+ nothing beside it: a page of documentation is a note (`type: doc`, with
2310
+ `pack: none` where it compiles into no document), so there is no second
2311
+ mechanism for mounting a directory of markdown, and a `trees:` or
2312
+ `readmeSections:` key is refused with a message saying so.
2299
2313
 
2300
2314
  ```yaml
2301
2315
  site:
@@ -2307,16 +2321,12 @@ site:
2307
2321
  passOptions:
2308
2322
  apiBase: /sohl/api/
2309
2323
  symbolMap: kb/data/api-symbols.json
2310
- blob: https://github.com/HeroicLands/…/blob/main/
2311
- trees:
2312
- - { from: kb/dev-docs, section: dev-docs }
2313
2324
  sections:
2314
2325
  being:
2315
2326
  title: Beings
2316
2327
  banner: banners/creature.webp
2317
2328
  description: Folk, animals and the things that walk the world.
2318
- readmeSections:
2319
- dev-docs: { title: Developer Documentation, banner: banners/dev-docs.webp }
2329
+ reference: { title: Reference, listType: doc, listSubType: reference }
2320
2330
  list: { shortcodes: true }
2321
2331
  notfound:
2322
2332
  tagline: Song of Heroic Lands has no page at
@@ -2330,10 +2340,8 @@ site:
2330
2340
  | `base` | Where the package is served: the prefix on every rendered `href`, and what a manifest `path` is measured against. It reaches no page's own `url:` — see [A page's URL is its address](#a-pages-url-is-its-address). Defaults to `/<contentPackage>/`. |
2331
2341
  | `packages` | Which content packages this site renders. Defaults to its own. |
2332
2342
  | `sections` | The Hugo sections this site declares, and what each says about itself — see below. |
2333
- | `readmeSections` | The same, for a `trees` entry, whose landing comes from its own `README`. |
2334
2343
  | `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
2335
2344
  | `backfillSections` | Write a bare `_index.md` for any other directory directly under the mount. |
2336
- | `trees` | Extra source trees published beside the content, preserving their source layout below a section. |
2337
2345
  | `pass` | A named bundle of this repository's own body rewrites. |
2338
2346
  | `passOptions` | That bundle's options. |
2339
2347
  | `assets` | The host every package's imagery is served from; the generated `params.cdnBaseURL`. |
@@ -2377,12 +2385,6 @@ Whatever the entry may carry is the whole of what the section can say.
2377
2385
  | `listType` | no | The content **type** whose pages this section lists. |
2378
2386
  | `listSubType` | no | Narrows that to one **subType**. Only with a `listType`. |
2379
2387
 
2380
- `readmeSections` takes the same keys, for a **`trees`** entry: those pages keep
2381
- their source layout below a named section, so the tree's own `README.md` is that
2382
- section's landing. What the section declares wins over what the `README` happens
2383
- to carry — the landing has to match the card that links to it. No content note
2384
- reaches this map any more.
2385
-
2386
2388
  #### Saying what a section lists
2387
2389
 
2388
2390
  A generic list layout has nothing to render, because the membership a section
@@ -2477,15 +2479,9 @@ a site publishes dead `{@link}` tags at exit 0. A map that is read reports
2477
2479
  its symbol count at info level, which is the only way to tell a map that loaded
2478
2480
  from one that loaded empty without reading the emitted HTML.
2479
2481
 
2480
- A bundle supplies up to two hooks, and their order around the shared work is the
2481
- point:
2482
-
2483
- 1. `beforeLinks`, on every page, before wikilinks resolve — a `{@link}` tag may
2484
- sit in prose a wikilink also touches.
2485
- 2. `afterLinks`, on pages from an extra tree only — repository-relative links are
2486
- a property of how those pages are authored, not of content notes.
2487
-
2488
- Both run inside code-fence protection, so neither can rewrite a fenced example.
2482
+ A bundle supplies one hook, `beforeLinks`, which runs on every page before
2483
+ wikilinks resolve — a `{@link}` tag may sit in prose a wikilink also touches.
2484
+ It runs inside code-fence protection, so it cannot rewrite a fenced example.
2489
2485
 
2490
2486
  ### The gates
2491
2487
 
@@ -2499,7 +2495,6 @@ after the links that failed because of it reads as a pile of broken notes.
2499
2495
  | Addresses | A note with no shortcode to be addressed by, or no section to be filed under. |
2500
2496
  | Unusable manifest | A vendored manifest this build cannot read. |
2501
2497
  | Unaddressable manifest | One it can read but cannot look anything up in. |
2502
- | Package conflicts | One address claimed by two packages. |
2503
2498
  | Tables and wikilinks | A table directive that cannot be honoured, or a link that lands nowhere. |
2504
2499
 
2505
2500
  None of them exits the process from inside the library; the command decides. That
@@ -80,7 +80,7 @@ import {
80
80
  formatUnaddressableFinding,
81
81
  } from "../engine/metadata-index.mjs";
82
82
  import { fetchNavigation, generateHugoConfig, writeHugoConfig } from "../engine/site-config.mjs";
83
- import { renderItemFieldReference } from "../engine/field-reference.mjs";
83
+ import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
84
84
  import { lintContentTree } from "../engine/content-lint.mjs";
85
85
  import { lintContentCharset } from "../engine/content-charset.mjs";
86
86
  import { lintContentHtml } from "../engine/content-html.mjs";
@@ -284,6 +284,14 @@ const argv = yargs(hideBin(process.argv))
284
284
  * second implementation of the comparison. Staleness is a property of the whole
285
285
  * generated file, so there is no line to name.
286
286
  *
287
+ * **A destination under the content tree gets a note envelope**, so the walk
288
+ * that collects every note by its `type:` picks this one up too, rather than
289
+ * silently dropping it — `type: doc`, `subType: reference`, a `shortcode`
290
+ * derived from the destination's basename, `name.full` from the title, and
291
+ * `pack: none`; `docs.itemFields.frontmatter` deep-merges over it. `--check`
292
+ * compares the whole file, envelope included. A destination outside the
293
+ * content tree gets the page body alone, as before.
294
+ *
287
295
  * `--out` and `--title` still override, for a one-off render.
288
296
  *
289
297
  * @returns {object} The yargs command module.
@@ -332,13 +340,27 @@ function docsCommand() {
332
340
  const spec = config.docs?.itemFields ?? {};
333
341
  const destination =
334
342
  argv.out ?? (spec.out ? path.resolve(config.rootDir, spec.out) : null);
343
+ const pageTitle = title ?? spec.title ?? "Item Note Frontmatter";
335
344
 
336
- const page = `${renderItemFieldReference({
337
- ...((title ?? spec.title) ? { title: title ?? spec.title } : {}),
345
+ const body = `${renderItemFieldReference({
346
+ title: pageTitle,
338
347
  ...(spec.preamble ? { preamble: spec.preamble } : {}),
339
348
  generatedBy: "`content-build docs item-fields`",
340
349
  config,
341
350
  })}\n`;
351
+ // A page filed under the content tree is walked for its
352
+ // `type:` like any other note, so it needs the envelope; one
353
+ // filed anywhere else — a repository's own `docs/` — is not,
354
+ // and gets exactly the body it always did.
355
+ const page =
356
+ destination ?
357
+ renderItemFieldsPage(body, {
358
+ title: pageTitle,
359
+ destination,
360
+ contentRoot: config.paths.content,
361
+ frontmatter: spec.frontmatter,
362
+ })
363
+ : body;
342
364
 
343
365
  if (check) {
344
366
  if (!destination) {
@@ -1561,9 +1583,6 @@ function siteCommand() {
1561
1583
  for (const f of gates.unaddressable) {
1562
1584
  console.error(formatUnaddressable(f, loadPackConfig()));
1563
1585
  }
1564
- for (const c of gates.conflicts) {
1565
- log.error(`address ${c.key} is also published by ${c.package}`);
1566
- }
1567
1586
  if (gatesFailed(gates)) {
1568
1587
  process.exitCode = 1;
1569
1588
  return;
@@ -73,7 +73,7 @@ import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./engine/address-char
73
73
  import { ASSET_TYPE_NAMES } from "./engine/asset-types.mjs";
74
74
  import { isReservedPackage } from "./engine/packages.mjs";
75
75
  import { EMPTY_ICON_REGISTRY, checkIconRegistry } from "./engine/content-icons.mjs";
76
- import { MAP_TYPES, PACK_BY_TYPE } from "./engine/ids.mjs";
76
+ import { MAP_TYPES, NO_PACK, PACK_BY_TYPE } from "./engine/ids.mjs";
77
77
  import { ACTOR_TYPES } from "./engine/subtype-registry.mjs";
78
78
  import { NOTE_VOCABULARY } from "./engine/note-vocabulary.mjs";
79
79
 
@@ -256,10 +256,10 @@ export const DEFAULT_ADDRESS_SCHEME = Object.freeze({
256
256
  * and the default.
257
257
  *
258
258
  * - `homepage` — the authored homepage, and **no other page**. The content tree
259
- * is not walked for pages, `site.sections` / `site.trees` / `site.landing`
260
- * emit nothing, and nothing serves a page for its addresses.
259
+ * is not walked for pages, `site.sections` / `site.landing` emit nothing,
260
+ * and nothing serves a page for its addresses.
261
261
  * - `content` — the homepage *plus* every page the content tree publishes: the
262
- * knowledgebase, the extra trees, the section landings.
262
+ * knowledgebase and the section landings.
263
263
  *
264
264
  * **Homepage-only is a first-class mode, not an accommodation.**
265
265
  * `sohl-kethira-basic` (unofficial Hârn fan material under Keléstia Productions'
@@ -528,6 +528,11 @@ export function publishesContentPages(config) {
528
528
  * Without it the page goes to stdout.
529
529
  * @property {string[]} [preamble] Lines between the generated banner and the
530
530
  * first table. Markdown, emitted verbatim.
531
+ * @property {Record<string, unknown>} [frontmatter] Further note frontmatter,
532
+ * deep-merged over the generated envelope
533
+ * (`type: doc`, `subType: reference`,
534
+ * `shortcode`, `name.full`, `pack: none`)
535
+ * when `out` is under the content tree.
531
536
  */
532
537
 
533
538
  /**
@@ -762,9 +767,7 @@ const SITE_KEYS = [
762
767
  "description",
763
768
  "packages",
764
769
  "sections",
765
- "readmeSections",
766
770
  "landing",
767
- "trees",
768
771
  "pass",
769
772
  "passOptions",
770
773
  "backfillSections",
@@ -772,7 +775,6 @@ const SITE_KEYS = [
772
775
  "notfound",
773
776
  "hugo",
774
777
  ];
775
- const SITE_TREE_KEYS = ["from", "section"];
776
778
  const SITE_LIST_KEYS = ["shortcodes"];
777
779
  const SITE_NOTFOUND_KEYS = ["tagline", "sitenoun", "heroimage", "links"];
778
780
  const SITE_NOTFOUND_LINK_KEYS = ["title", "url", "text"];
@@ -780,7 +782,7 @@ const PDF_KEYS = ["title", "subtitle", "document", "out", "front", "fonts", "ico
780
782
  const PDF_FONT_KEYS = ["serif", "sans", "mono", "path"];
781
783
  const EMPTY_PDF_FONTS = Object.freeze({ serif: "", sans: "", mono: "", path: "" });
782
784
  const SECTION_META_KEYS = ["title", "banner", "description", "listType", "listSubType"];
783
- const DOC_PAGE_KEYS = ["title", "out", "preamble"];
785
+ const DOC_PAGE_KEYS = ["title", "out", "preamble", "frontmatter"];
784
786
  const RELATIONSHIP_KINDS = ["systems", "requires", "recommends", "conflicts"];
785
787
  const RELATIONSHIP_KEYS = [
786
788
  "id",
@@ -1079,6 +1081,15 @@ function normalizePack(value, where, nested = false) {
1079
1081
  rejectUnknownKeys(pack, PACK_KEYS, `${where}.`);
1080
1082
 
1081
1083
  const name = requireNonEmptyString(pack.name, `${where}.name`);
1084
+ // A note writes `pack: none` to compile into no document, so a pack of
1085
+ // that name could never be addressed by one.
1086
+ if (name === NO_PACK) {
1087
+ fail(
1088
+ `${where}.name`,
1089
+ `may not be \`${NO_PACK}\` — a note declares \`pack: ${NO_PACK}\` to ` +
1090
+ `compile into no document, so no pack may answer to the name`,
1091
+ );
1092
+ }
1082
1093
  const type = pack.type;
1083
1094
  if (
1084
1095
  typeof type !== "string" ||
@@ -1429,6 +1440,12 @@ function normalizeDocPage(value, where) {
1429
1440
  }),
1430
1441
  );
1431
1442
  }
1443
+ if (input.frontmatter !== undefined) {
1444
+ if (!isPlainObject(input.frontmatter)) {
1445
+ fail(`${where}.frontmatter`, "must be a mapping");
1446
+ }
1447
+ out.frontmatter = deepFreeze({ .../** @type {object} */ (input.frontmatter) });
1448
+ }
1432
1449
  return Object.freeze(out);
1433
1450
  }
1434
1451
 
@@ -1750,15 +1767,22 @@ function normalizeSiteHugo(value) {
1750
1767
  /**
1751
1768
  * The `site` section — how this repository frames the website it publishes.
1752
1769
  *
1753
- * Everything here is *framing*: what a section is called, which extra trees
1754
- * are published beside the content, which named pass bundle supplies the
1755
- * repository's own body rewrites, and the residue of the generated Hugo
1756
- * configuration that is genuinely this repository's own. Where the Hugo tree
1757
- * is written is not a choice: `content-build site` writes it under
1758
- * `build/hugo/`, and a `site.out` is refused by name. How a page gets its
1759
- * **address** is deliberately not here either that is `publish.address`,
1760
- * shared with the link manifest so the two cannot disagree about where a
1761
- * page is.
1770
+ * Everything here is *framing*: what a section is called, which named pass
1771
+ * bundle supplies the repository's own body rewrites, and the residue of the
1772
+ * generated Hugo configuration that is genuinely this repository's own. Where
1773
+ * the Hugo tree is written is not a choice: `content-build site` writes it
1774
+ * under `build/hugo/`, and a `site.out` is refused by name. How a page gets
1775
+ * its **address** is deliberately not here either that is
1776
+ * `publish.address`, shared with the link manifest so the two cannot disagree
1777
+ * about where a page is.
1778
+ *
1779
+ * **What the site publishes is the content tree, and nothing beside it.** A
1780
+ * page of documentation is a note — `type: doc`, addressed by its shortcode,
1781
+ * compiling into no document where it says `pack: none` — so there is no
1782
+ * second mechanism for mounting a directory of markdown, and a configuration
1783
+ * that names one (`site.trees`, and the `site.readmeSections` that titled
1784
+ * such a tree's landing) is refused with a message saying where the page
1785
+ * goes instead.
1762
1786
  *
1763
1787
  * @param {unknown} value - The `site` block, or `undefined`.
1764
1788
  * @returns {Readonly<object>} It, frozen, with every default filled.
@@ -1770,9 +1794,7 @@ function normalizeSite(value) {
1770
1794
  description: "",
1771
1795
  packages: Object.freeze([]),
1772
1796
  sections: Object.freeze({}),
1773
- readmeSections: Object.freeze({}),
1774
1797
  landing: null,
1775
- trees: Object.freeze([]),
1776
1798
  pass: "",
1777
1799
  passOptions: Object.freeze({}),
1778
1800
  backfillSections: false,
@@ -1793,27 +1815,20 @@ function normalizeSite(value) {
1793
1815
  "the location is not configurable. Remove the key",
1794
1816
  );
1795
1817
  }
1796
- rejectUnknownKeys(input, SITE_KEYS, "site.");
1797
-
1798
- const trees = [];
1799
- if (input.trees !== undefined) {
1800
- if (!Array.isArray(input.trees)) fail("site.trees", "must be a list");
1801
- input.trees.forEach((entry, i) => {
1802
- const where = `site.trees[${i}]`;
1803
- if (!isPlainObject(entry)) fail(where, "must be a mapping");
1804
- const tree = /** @type {Record<string, unknown>} */ (entry);
1805
- rejectUnknownKeys(tree, SITE_TREE_KEYS, `${where}.`);
1806
- trees.push(
1807
- Object.freeze({
1808
- from: requireNonEmptyString(tree.from, `${where}.from`),
1809
- section: requireNonEmptyString(tree.section, `${where}.section`),
1810
- // The tree's own path, POSIX-separated — what a
1811
- // repository-relative link inside it is resolved against.
1812
- rel: String(tree.from).split(path.sep).join("/"),
1813
- }),
1814
- );
1815
- });
1818
+ // Refused by name too, and ahead of the vocabulary check for the same
1819
+ // reason: a page is a note in the content tree, and the useful thing to
1820
+ // say is where it goes rather than that the key is unknown.
1821
+ for (const key of ["trees", "readmeSections"]) {
1822
+ if (input[key] === undefined) continue;
1823
+ fail(
1824
+ `site.${key}`,
1825
+ "is retired a page is a note in the content tree. Give each page " +
1826
+ "`type: doc`, a `shortcode` and `pack: none`, file it under " +
1827
+ "`assets/content/`, and declare the section that lists it under " +
1828
+ "`site.sections`",
1829
+ );
1816
1830
  }
1831
+ rejectUnknownKeys(input, SITE_KEYS, "site.");
1817
1832
 
1818
1833
  let packages = [];
1819
1834
  if (input.packages !== undefined) {
@@ -1839,9 +1854,7 @@ function normalizeSite(value) {
1839
1854
  description: normalizeSiteDescription(input.description),
1840
1855
  packages: Object.freeze(packages),
1841
1856
  sections: normalizeSectionMap(input.sections, "site.sections"),
1842
- readmeSections: normalizeSectionMap(input.readmeSections, "site.readmeSections"),
1843
1857
  landing,
1844
- trees: Object.freeze(trees),
1845
1858
  pass: input.pass === undefined ? "" : requireNonEmptyString(input.pass, "site.pass"),
1846
1859
  passOptions:
1847
1860
  input.passOptions === undefined ?
package/docs/api.md CHANGED
@@ -103,6 +103,7 @@ Deterministic document ids, derived by hashing rather than stored, so compile pa
103
103
  | `MAP_TYPES` | `const MAP_TYPES` | — | enumerating the content types that compile into a Foundry `Scene` |
104
104
  | `MAP_SUBTYPES` | `const MAP_SUBTYPES` | — | reading the map subTypes, which differ only in derived canvas defaults |
105
105
  | `JOURNAL_TYPES` | `const JOURNAL_TYPES` | — | enumerating content types whose whole document _is_ a JournalEntry |
106
+ | `NO_PACK` | `const NO_PACK` | — | spelling the `pack:` value (`none`) that routes a note's document into no compendium |
106
107
  | `PACK_BY_TYPE` | `const PACK_BY_TYPE` | — | looking up the conventional pack name and document type for a content type |
107
108
  | `RETIRED_TYPES` | `const RETIRED_TYPES` | — | looking up what a retired content type was replaced by |
108
109
  | `assertTypeNotRetired` | `assertTypeNotRetired(type, where)` | throws | refusing a note whose type has been retired outright |
@@ -200,6 +201,8 @@ Which pack a note's document lands in, when a document type has more than one co
200
201
  | ------------------ | -------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------- |
201
202
  | `PackRoutingError` | `class PackRoutingError extends Error` | — | catching a note that cannot be routed to any configured pack |
202
203
  | `PACK_FIELD` | `const PACK_FIELD` | — | naming the frontmatter field a note declares its pack in |
204
+ | `NO_PACK` | `const NO_PACK` | — | spelling the `pack:` value (`none`) that routes a note's document into no compendium |
205
+ | `declaresNoPack` | `declaresNoPack(fm, system)` | `boolean` | asking, without a router, whether a note declares `pack: none` for one system's document |
203
206
  | `createPackRouter` | `createPackRouter(packs)` | `{resolve, resolveOrNull}` | building a router for one configured pack list, purely, for testing without a config file on disk |
204
207
  | `routerFor` | `routerFor(config)` | the router | getting the router for a resolved configuration, built once per configuration |
205
208
  | `packRouter` | `packRouter()` | the router | asking, as every module that emits a UUID does, where a note's document lives |
@@ -605,25 +608,23 @@ The toolchain's own content index — the files it ships, addressed. Every other
605
608
 
606
609
  Publishing a content tree as a website. Compiling a content tree into compendium packs is `content-build package compile`. Publishing the _same tree_ as a website was a script each consumer wrote for itself — 473 code lines in `sohl` and 462 in `sohl-thalorna`, 87 of them identical — and the copies drifted in ways neither repository could see. `sohl-thalorna` reimplemented four things this package already exported, not because it needed different behaviour but because its script predates the extraction. That is the failure a command removes: a consumer cannot accidentally reimplement one.
607
610
 
608
- | Export | Signature | Returns | Use it when |
609
- | ---------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
610
- | `walkSiteTree` | `function walkSiteTree(dir, skip` | {string[]} Absolute paths. | Every `.md` file under `dir`, depth-first in directory order. |
611
- | `collectContentPages` | `function collectContentPages(contentBase, ctx)` | {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}} | The content tree's pages, and what could not be addressed. |
612
- | `collectTreePages` | `function collectTreePages(tree, ctx)` | {{pages: object[], fmLinkFindings: object[]}} | An extra tree's pages a documentation tree published alongside the content. |
613
- | `collectHomepages` | `function collectHomepages(contentBase, ctx)` | {{pages: object[], addressFindings: object[]}} The homepage notes, in walk order, and the ones among them that could not be addressed. | The package's homepage notes the authored page at `/<contentPackage>/`. |
614
- | `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage at its address, below the package's own root. |
615
- | `siteGates` | `function siteGates(pages, findings,` | {object} The gate results and, when they pass, the built index. | The integrity gates a site build runs before it writes anything. |
616
- | `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
617
- | `gatesFailed` | `function gatesFailed(gates)` | | Whether any gate produced a finding. |
618
- | `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package the notes it may tabulate. | The universe a generated table searches, grouped by package. |
619
- | `sectionFrontmatter` | `function sectionFrontmatter(meta)` | {object} Its front matter, `title` first. | The front matter a section's landing states about itself. |
620
- | `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
621
- | `pageDestination` | `function pageDestination(page)` | — | Where a page is written, relative to the output root. |
622
- | `renderPages` | `function renderPages(pages, options)` | {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}} | Renders and writes every page. |
623
- | `writeSectionLandings` | `function writeSectionLandings(outRoot,` | {number} How many landings were written. | Writes the Hugo sections a published tree declares. |
624
- | `pluralTitle` | `function pluralTitle(name)` | {string} The display title. | A section landing's title, from its directory name `macro` `Macros`. |
625
- | `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function, afterLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
626
- | `buildSite` | `buildSite({ config, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
611
+ | Export | Signature | Returns | Use it when |
612
+ | ---------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
613
+ | `collectContentPages` | `function collectContentPages(contentBase, ctx)` | {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}} | The content tree's pages, and what could not be addressed. |
614
+ | `collectHomepages` | `function collectHomepages(contentBase, ctx)` | {{pages: object[], addressFindings: object[]}} The homepage notes, in walk order, and the ones among them that could not be addressed. | The package's homepage notes — the authored page at `/<contentPackage>/`. |
615
+ | `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage at its address, below the package's own root. |
616
+ | `siteGates` | `function siteGates(pages, findings,` | {object} The gate results and, when they pass, the built index. | The integrity gates a site build runs before it writes anything. |
617
+ | `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
618
+ | `gatesFailed` | `function gatesFailed(gates)` | | Whether any gate produced a finding. |
619
+ | `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package the notes it may tabulate. | The universe a generated table searches, grouped by package. |
620
+ | `sectionFrontmatter` | `function sectionFrontmatter(meta)` | {object} Its front matter, `title` first. | The front matter a section's landing states about itself. |
621
+ | `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
622
+ | `pageDestination` | `function pageDestination(page)` | — | Where a page is written, relative to the output root. |
623
+ | `renderPages` | `function renderPages(pages, options)` | {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}} | Renders and writes every page. |
624
+ | `writeSectionLandings` | `function writeSectionLandings(outRoot,` | {number} How many landings were written. | Writes the Hugo sections a published tree declares. |
625
+ | `pluralTitle` | `function pluralTitle(name)` | {string} The display title. | A section landing's title, from its directory name `macro` → `Macros`. |
626
+ | `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
627
+ | `buildSite` | `buildSite({ config, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
627
628
 
628
629
  ### `engine.contentLint`
629
630
 
@@ -1138,13 +1139,12 @@ Which of SoHL's facts a note's summary panel carries, and how they group. Read o
1138
1139
 
1139
1140
  ### `sohl.kbPasses`
1140
1141
 
1141
- The `sohl` knowledgebase's own body passes: two rewrites driven by a TypeDoc symbol map and a repository layout only this package has, named from `site.passOptions` the same way an asset transform is named from configuration. Neither rewrite ever fails a build — an unresolved `{@link}` degrades to a code span, and a relative link outside the documentation tree becomes a GitHub blob URL — but building the bundle from a misconfigured `symbolMap` fails loudly before any page renders.
1142
+ The `sohl` knowledgebase's own body pass: a rewrite driven by a TypeDoc symbol map only this package has, named from `site.passOptions` the same way an asset transform is named from configuration. The rewrite never fails a build — an unresolved `{@link}` degrades to a code span — but building the bundle from a misconfigured `symbolMap` fails loudly before any page renders.
1142
1143
 
1143
- | Export | Signature | Returns | Use it when |
1144
- | ------------------ | ----------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1145
- | `resolveApiLinks` | `resolveApiLinks(body, symbols, apiBase)` | `string` — the body with every tag resolved | resolving inline TypeDoc `{@link}` / `{@linkcode}` / `{@linkplain}` tags in a markdown body against the API symbol map |
1146
- | `rewriteRepoLinks` | `rewriteRepoLinks(body, docRel, options)` | `string` the body with every relative link rewritten | rewriting a developer doc's repository-relative links so they resolve on the published site (documentation-tree links become routes, everything else becomes a GitHub blob URL) |
1147
- | `sohlKbPass` | `sohlKbPass(options)` | `{beforeLinks: Function, afterLinks: Function}` | building the `sohl` knowledgebase pass bundle the site renderer calls around wikilink resolution |
1144
+ | Export | Signature | Returns | Use it when |
1145
+ | ----------------- | ----------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
1146
+ | `resolveApiLinks` | `resolveApiLinks(body, symbols, apiBase)` | `string` — the body with every tag resolved | resolving inline TypeDoc `{@link}` / `{@linkcode}` / `{@linkplain}` tags in a markdown body against the API symbol map |
1147
+ | `sohlKbPass` | `sohlKbPass(options)` | `{beforeLinks: Function}` | building the `sohl` knowledgebase pass bundle the site renderer calls before wikilink resolution |
1148
1148
 
1149
1149
  ### Flat exports (not under a namespace)
1150
1150
 
package/docs/commands.md CHANGED
@@ -1064,6 +1064,29 @@ named. Reads the configured `itemBuilders` registries; writes (or checks)
1064
1064
  the destination file, or prints to stdout when none is configured and
1065
1065
  `--out` is not given.
1066
1066
 
1067
+ **A destination under `paths.content` gets a complete note, not a typeless
1068
+ page.** The content-tree walk collects every published page by its `type:`,
1069
+ so a generated page filed there needs the envelope every other note carries
1070
+ or the walk drops it silently — no build failure, no page, every wikilink
1071
+ into it dead. So when `--out` (or `docs.itemFields.out`) resolves inside the
1072
+ content tree, the written file carries:
1073
+
1074
+ - `type: doc`, `subType: reference` — out-of-world lookup material, like
1075
+ every other generated reference page;
1076
+ - `shortcode`, derived from the destination's basename — lowercase
1077
+ alphanumerics only, so `item-frontmatter.md` derives `itemfrontmatter` —
1078
+ unless `docs.itemFields.frontmatter.shortcode` gives one;
1079
+ - `name.full`, from the page's title;
1080
+ - `pack: none` — the page publishes to the website and compiles into no
1081
+ compendium document.
1082
+
1083
+ `docs.itemFields.frontmatter` is deep-merged over that envelope, so a
1084
+ consumer may add keys (`description`, `tags`) or override any of the derived
1085
+ ones. `--check` then compares the **whole** file, envelope included — a page
1086
+ committed with a hand-written or stale envelope reads as out of date exactly
1087
+ as a stale body does. A destination outside the content tree gets the page
1088
+ body alone, with no frontmatter, exactly as before.
1089
+
1067
1090
  **OPTIONS**
1068
1091
 
1069
1092
  | Positional | Type | Default | Description |