@heroiclands/package-build 22.2.0 → 22.3.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.
- package/CHANGELOG.md +8 -0
- package/CONTENT.md +27 -32
- package/bin/content-build.mjs +0 -3
- package/content-config.mjs +42 -40
- package/docs/api.md +25 -25
- package/docs/configuration.md +19 -29
- package/docs/content-format.md +36 -6
- package/engine/foundry-entries.mjs +12 -1
- package/engine/frontmatter-lint.mjs +2 -3
- package/engine/helpers.mjs +4 -1
- package/engine/ids.mjs +12 -0
- package/engine/note-claims.mjs +21 -0
- package/engine/pack-router.mjs +82 -5
- package/engine/pdf-build.mjs +2 -2
- package/engine/retired-fields.mjs +2 -3
- package/engine/site-build.mjs +59 -218
- package/engine/site-index.mjs +18 -62
- package/engine/web-wikilinks.mjs +2 -16
- package/engine/wikilinks.mjs +35 -14
- package/package.json +1 -1
- package/sohl/kb-passes.mjs +14 -85
- package/types/content-config.d.mts +6 -6
- package/types/engine/ids.d.mts +11 -0
- package/types/engine/pack-router.d.mts +20 -2
- package/types/engine/site-build.d.mts +12 -62
- package/types/engine/site-index.d.mts +3 -28
- package/types/engine/web-wikilinks.d.mts +1 -1
- package/types/engine/wikilinks.d.mts +6 -3
- package/types/sohl/kb-passes.d.mts +5 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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`.
|
|
8
|
+
|
|
9
|
+
**`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.
|
|
10
|
+
|
|
3
11
|
## 22.2.0
|
|
4
12
|
|
|
5
13
|
### 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
|
|
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`, `
|
|
2277
|
-
|
|
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,
|
|
2297
|
-
|
|
2298
|
-
|
|
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
|
-
|
|
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
|
|
2481
|
-
|
|
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
|
package/bin/content-build.mjs
CHANGED
|
@@ -1561,9 +1561,6 @@ function siteCommand() {
|
|
|
1561
1561
|
for (const f of gates.unaddressable) {
|
|
1562
1562
|
console.error(formatUnaddressable(f, loadPackConfig()));
|
|
1563
1563
|
}
|
|
1564
|
-
for (const c of gates.conflicts) {
|
|
1565
|
-
log.error(`address ${c.key} is also published by ${c.package}`);
|
|
1566
|
-
}
|
|
1567
1564
|
if (gatesFailed(gates)) {
|
|
1568
1565
|
process.exitCode = 1;
|
|
1569
1566
|
return;
|
package/content-config.mjs
CHANGED
|
@@ -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.
|
|
260
|
-
*
|
|
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
|
|
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'
|
|
@@ -762,9 +762,7 @@ const SITE_KEYS = [
|
|
|
762
762
|
"description",
|
|
763
763
|
"packages",
|
|
764
764
|
"sections",
|
|
765
|
-
"readmeSections",
|
|
766
765
|
"landing",
|
|
767
|
-
"trees",
|
|
768
766
|
"pass",
|
|
769
767
|
"passOptions",
|
|
770
768
|
"backfillSections",
|
|
@@ -772,7 +770,6 @@ const SITE_KEYS = [
|
|
|
772
770
|
"notfound",
|
|
773
771
|
"hugo",
|
|
774
772
|
];
|
|
775
|
-
const SITE_TREE_KEYS = ["from", "section"];
|
|
776
773
|
const SITE_LIST_KEYS = ["shortcodes"];
|
|
777
774
|
const SITE_NOTFOUND_KEYS = ["tagline", "sitenoun", "heroimage", "links"];
|
|
778
775
|
const SITE_NOTFOUND_LINK_KEYS = ["title", "url", "text"];
|
|
@@ -1079,6 +1076,15 @@ function normalizePack(value, where, nested = false) {
|
|
|
1079
1076
|
rejectUnknownKeys(pack, PACK_KEYS, `${where}.`);
|
|
1080
1077
|
|
|
1081
1078
|
const name = requireNonEmptyString(pack.name, `${where}.name`);
|
|
1079
|
+
// A note writes `pack: none` to compile into no document, so a pack of
|
|
1080
|
+
// that name could never be addressed by one.
|
|
1081
|
+
if (name === NO_PACK) {
|
|
1082
|
+
fail(
|
|
1083
|
+
`${where}.name`,
|
|
1084
|
+
`may not be \`${NO_PACK}\` — a note declares \`pack: ${NO_PACK}\` to ` +
|
|
1085
|
+
`compile into no document, so no pack may answer to the name`,
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1082
1088
|
const type = pack.type;
|
|
1083
1089
|
if (
|
|
1084
1090
|
typeof type !== "string" ||
|
|
@@ -1750,15 +1756,22 @@ function normalizeSiteHugo(value) {
|
|
|
1750
1756
|
/**
|
|
1751
1757
|
* The `site` section — how this repository frames the website it publishes.
|
|
1752
1758
|
*
|
|
1753
|
-
* Everything here is *framing*: what a section is called, which
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
*
|
|
1759
|
+
* Everything here is *framing*: what a section is called, which named pass
|
|
1760
|
+
* bundle supplies the repository's own body rewrites, and the residue of the
|
|
1761
|
+
* generated Hugo configuration that is genuinely this repository's own. Where
|
|
1762
|
+
* the Hugo tree is written is not a choice: `content-build site` writes it
|
|
1763
|
+
* under `build/hugo/`, and a `site.out` is refused by name. How a page gets
|
|
1764
|
+
* its **address** is deliberately not here either — that is
|
|
1765
|
+
* `publish.address`, shared with the link manifest so the two cannot disagree
|
|
1766
|
+
* about where a page is.
|
|
1767
|
+
*
|
|
1768
|
+
* **What the site publishes is the content tree, and nothing beside it.** A
|
|
1769
|
+
* page of documentation is a note — `type: doc`, addressed by its shortcode,
|
|
1770
|
+
* compiling into no document where it says `pack: none` — so there is no
|
|
1771
|
+
* second mechanism for mounting a directory of markdown, and a configuration
|
|
1772
|
+
* that names one (`site.trees`, and the `site.readmeSections` that titled
|
|
1773
|
+
* such a tree's landing) is refused with a message saying where the page
|
|
1774
|
+
* goes instead.
|
|
1762
1775
|
*
|
|
1763
1776
|
* @param {unknown} value - The `site` block, or `undefined`.
|
|
1764
1777
|
* @returns {Readonly<object>} It, frozen, with every default filled.
|
|
@@ -1770,9 +1783,7 @@ function normalizeSite(value) {
|
|
|
1770
1783
|
description: "",
|
|
1771
1784
|
packages: Object.freeze([]),
|
|
1772
1785
|
sections: Object.freeze({}),
|
|
1773
|
-
readmeSections: Object.freeze({}),
|
|
1774
1786
|
landing: null,
|
|
1775
|
-
trees: Object.freeze([]),
|
|
1776
1787
|
pass: "",
|
|
1777
1788
|
passOptions: Object.freeze({}),
|
|
1778
1789
|
backfillSections: false,
|
|
@@ -1793,27 +1804,20 @@ function normalizeSite(value) {
|
|
|
1793
1804
|
"the location is not configurable. Remove the key",
|
|
1794
1805
|
);
|
|
1795
1806
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
if (
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
-
});
|
|
1807
|
+
// Refused by name too, and ahead of the vocabulary check for the same
|
|
1808
|
+
// reason: a page is a note in the content tree, and the useful thing to
|
|
1809
|
+
// say is where it goes rather than that the key is unknown.
|
|
1810
|
+
for (const key of ["trees", "readmeSections"]) {
|
|
1811
|
+
if (input[key] === undefined) continue;
|
|
1812
|
+
fail(
|
|
1813
|
+
`site.${key}`,
|
|
1814
|
+
"is retired — a page is a note in the content tree. Give each page " +
|
|
1815
|
+
"`type: doc`, a `shortcode` and `pack: none`, file it under " +
|
|
1816
|
+
"`assets/content/`, and declare the section that lists it under " +
|
|
1817
|
+
"`site.sections`",
|
|
1818
|
+
);
|
|
1816
1819
|
}
|
|
1820
|
+
rejectUnknownKeys(input, SITE_KEYS, "site.");
|
|
1817
1821
|
|
|
1818
1822
|
let packages = [];
|
|
1819
1823
|
if (input.packages !== undefined) {
|
|
@@ -1839,9 +1843,7 @@ function normalizeSite(value) {
|
|
|
1839
1843
|
description: normalizeSiteDescription(input.description),
|
|
1840
1844
|
packages: Object.freeze(packages),
|
|
1841
1845
|
sections: normalizeSectionMap(input.sections, "site.sections"),
|
|
1842
|
-
readmeSections: normalizeSectionMap(input.readmeSections, "site.readmeSections"),
|
|
1843
1846
|
landing,
|
|
1844
|
-
trees: Object.freeze(trees),
|
|
1845
1847
|
pass: input.pass === undefined ? "" : requireNonEmptyString(input.pass, "site.pass"),
|
|
1846
1848
|
passOptions:
|
|
1847
1849
|
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
|
-
| `
|
|
611
|
-
| `
|
|
612
|
-
| `
|
|
613
|
-
| `
|
|
614
|
-
| `
|
|
615
|
-
| `
|
|
616
|
-
| `
|
|
617
|
-
| `
|
|
618
|
-
| `
|
|
619
|
-
| `
|
|
620
|
-
| `
|
|
621
|
-
| `
|
|
622
|
-
| `
|
|
623
|
-
| `
|
|
624
|
-
| `
|
|
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
|
|
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
|
|
1144
|
-
|
|
|
1145
|
-
| `resolveApiLinks`
|
|
1146
|
-
| `
|
|
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/configuration.md
CHANGED
|
@@ -759,9 +759,7 @@ Any other key under `docs.itemFields` is refused:
|
|
|
759
759
|
| `site.description` | string | `""`, but required for `content-build site` |
|
|
760
760
|
| `site.packages` | string[] | `[]` |
|
|
761
761
|
| `site.sections` | object | `{}` |
|
|
762
|
-
| `site.readmeSections` | object | `{}` |
|
|
763
762
|
| `site.landing` | object | `null` |
|
|
764
|
-
| `site.trees` | array | `[]` |
|
|
765
763
|
| `site.pass` | string | `""` |
|
|
766
764
|
| `site.passOptions` | object | `{}` |
|
|
767
765
|
| `site.backfillSections` | boolean | `false` |
|
|
@@ -771,10 +769,19 @@ Any other key under `docs.itemFields` is refused:
|
|
|
771
769
|
|
|
772
770
|
How much of a package reaches the web at all is **not** here — it is
|
|
773
771
|
[`publish.site`](#publish). `site` is framing: what a section is called,
|
|
774
|
-
which
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
772
|
+
which named pass bundle supplies the repository's own body rewrites, and the
|
|
773
|
+
residue of the [generated Hugo configuration](#the-generated-hugo-configuration)
|
|
774
|
+
that is genuinely this repository's own.
|
|
775
|
+
|
|
776
|
+
What the site publishes is the content tree, and nothing beside it. A page of
|
|
777
|
+
documentation is a note — `type: doc`, addressed by its shortcode, and
|
|
778
|
+
`pack: none` where it compiles into no Foundry document — so there is no
|
|
779
|
+
second mechanism for mounting a directory of markdown, and a configuration
|
|
780
|
+
that names one is refused with a message saying where the page goes instead:
|
|
781
|
+
|
|
782
|
+
> ``package-build config: `site.trees` is retired — a page is a note in the content tree. Give each page `type: doc`, a `shortcode` and `pack: none`, file it under `assets/content/`, and declare the section that lists it under `site.sections`.``
|
|
783
|
+
|
|
784
|
+
> ``package-build config: `site.readmeSections` is retired — a page is a note in the content tree. Give each page `type: doc`, a `shortcode` and `pack: none`, file it under `assets/content/`, and declare the section that lists it under `site.sections`.``
|
|
778
785
|
|
|
779
786
|
Where the Hugo tree is written is not a choice. `content-build site` writes
|
|
780
787
|
the whole Hugo source tree under `build/hugo/` — the generated `hugo.toml`,
|
|
@@ -786,7 +793,7 @@ published. A `site.out` is refused by name:
|
|
|
786
793
|
|
|
787
794
|
> ``package-build config: `site` must be a mapping.``
|
|
788
795
|
|
|
789
|
-
> ``package-build config: `site.<key>` is not a recognized option (expected one of: base, assets, description, packages, sections,
|
|
796
|
+
> ``package-build config: `site.<key>` is not a recognized option (expected one of: base, assets, description, packages, sections, landing, pass, passOptions, backfillSections, list, notfound, hugo).``
|
|
790
797
|
|
|
791
798
|
`site.assets` is the host every package's imagery is served from, and it is
|
|
792
799
|
the one address in this file that is not this repository's own. A note names
|
|
@@ -832,25 +839,8 @@ names a registry):
|
|
|
832
839
|
|
|
833
840
|
> ``package-build config: `site.packages[<index>]` must be a non-empty string.``
|
|
834
841
|
|
|
835
|
-
`site.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
| Key (under `site.trees[]`) | Type | Required |
|
|
839
|
-
| -------------------------- | ------ | -------- |
|
|
840
|
-
| `site.trees[].from` | string | yes |
|
|
841
|
-
| `site.trees[].section` | string | yes |
|
|
842
|
-
|
|
843
|
-
> ``package-build config: `site.trees` must be a list.``
|
|
844
|
-
|
|
845
|
-
> ``package-build config: `site.trees[<index>]` must be a mapping.``
|
|
846
|
-
|
|
847
|
-
> ``package-build config: `site.trees[<index>].from` must be a non-empty string.``
|
|
848
|
-
|
|
849
|
-
> ``package-build config: `site.trees[<index>].<key>` is not a recognized option (expected one of: from, section).``
|
|
850
|
-
|
|
851
|
-
`site.sections` (and `site.readmeSections`, the same shape) is a closed
|
|
852
|
-
vocabulary — a section's _only_ place to speak, since it exists solely as
|
|
853
|
-
the generated `_index.md` this build writes for it:
|
|
842
|
+
`site.sections` is a closed vocabulary — a section's _only_ place to speak,
|
|
843
|
+
since it exists solely as the generated `_index.md` this build writes for it:
|
|
854
844
|
|
|
855
845
|
| Key (under `site.sections.<name>`) | Type | Required | Default |
|
|
856
846
|
| ---------------------------------- | -------------------------- | -------- | ------- |
|
|
@@ -1336,9 +1326,9 @@ its content tree's addresses mount inside the package.
|
|
|
1336
1326
|
Every HeroicLands package publishes at least an authored homepage at
|
|
1337
1327
|
`https://www.heroiclands.org/<contentPackage>/` — there is no value meaning
|
|
1338
1328
|
_no web presence at all_. `homepage` is the floor: the authored homepage
|
|
1339
|
-
and nothing else, no content-tree walk, no `site.sections` / `site.
|
|
1340
|
-
|
|
1341
|
-
|
|
1329
|
+
and nothing else, no content-tree walk, no `site.sections` / `site.landing`
|
|
1330
|
+
output. `content` is the homepage plus every page the content tree
|
|
1331
|
+
publishes. `publishesContentPages(config)`, exported from
|
|
1342
1332
|
`content-config.mjs` alongside [`compilesFoundryDocuments`](#packagekind),
|
|
1343
1333
|
answers the one question every reader of the mode actually asks — the site
|
|
1344
1334
|
build, to decide whether to walk the tree at all, and the content index, to
|
package/docs/content-format.md
CHANGED
|
@@ -801,13 +801,43 @@ hm3:
|
|
|
801
801
|
Where no pack of that type is the default, the build refuses rather than
|
|
802
802
|
guessing, and names the candidates.
|
|
803
803
|
|
|
804
|
-
|
|
804
|
+
**`pack: none` compiles the note into no document.** The note is walked,
|
|
805
|
+
published as a page, present in the content index with an address and no
|
|
806
|
+
Foundry UUID, and addressable by wikilink like any other; every pack compiler
|
|
807
|
+
passes over it without a finding, and the check that reports a note nothing
|
|
808
|
+
compiles says nothing about it. Write it on a page that belongs on the website
|
|
809
|
+
and in no compendium — a page of developer documentation, a page about the
|
|
810
|
+
package itself:
|
|
805
811
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
812
|
+
```yaml
|
|
813
|
+
type: doc
|
|
814
|
+
subType: concept
|
|
815
|
+
shortcode: architecture
|
|
816
|
+
pack: none
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
A link to such a note resolves everywhere the note is addressable. On the web
|
|
820
|
+
it is an ordinary link to the page. In a compiled journal there is no document
|
|
821
|
+
to open, so the reader gets the link's text as prose and no `@UUID` — the
|
|
822
|
+
mirror of a link into a package that publishes documents and no pages.
|
|
823
|
+
|
|
824
|
+
It is accepted only on a type whose **sole document is the JournalEntry its
|
|
825
|
+
prose becomes** — `doc`, `place`, `lore` and `scenario`. On a type that
|
|
826
|
+
compiles an Item, an Actor, a Macro, a Scene or an Adventure it is refused by
|
|
827
|
+
name, because there it would drop the document the type exists to produce;
|
|
828
|
+
the message names that document. It is read per system like any other
|
|
829
|
+
`pack:`, so `<system>.pack: none` withholds one system's document while the
|
|
830
|
+
shared declaration names a pack for the rest, and a configured pack may not
|
|
831
|
+
be called `none`.
|
|
832
|
+
|
|
833
|
+
Four declarations are refused, each with the reason:
|
|
834
|
+
|
|
835
|
+
| written | why it is refused |
|
|
836
|
+
| ------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
837
|
+
| a **companion** pack | A companion is written by another pack's pass, so no note may be routed into one. |
|
|
838
|
+
| a pack **nothing answers to** | The message lists the configured packs of that document type. |
|
|
839
|
+
| a pack of **another document type** | A note's `pack:` names a pack of its own document type. |
|
|
840
|
+
| **`none`** on a type with a second document | The Item, Actor, Macro, Scene or Adventure the type compiles into would be dropped, by name. |
|
|
811
841
|
|
|
812
842
|
#### Template priority: which template wins
|
|
813
843
|
|
|
@@ -70,6 +70,7 @@ import { resolveNoteId } from "./note-ids.mjs";
|
|
|
70
70
|
import { compendiumUuid, currentType, packForType, pageUuid } from "./ids.mjs";
|
|
71
71
|
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
72
72
|
import { isHomepage } from "./homepage.mjs";
|
|
73
|
+
import { declaresNoPack } from "./pack-router.mjs";
|
|
73
74
|
import { assertNoDeclaredPackage } from "./note-package.mjs";
|
|
74
75
|
import { assertNoDeclaredFolder } from "./folder-notes.mjs";
|
|
75
76
|
import {
|
|
@@ -179,7 +180,17 @@ export function entriesForNote(fm, name, address, body, ctx) {
|
|
|
179
180
|
// and its id is hashed under the `folder` namespace against its own
|
|
180
181
|
// address rather than under `document`. Emitting one would publish an
|
|
181
182
|
// `Item` UUID for a `Folder`, at an id no document carries.
|
|
182
|
-
|
|
183
|
+
//
|
|
184
|
+
// And a note declaring **`pack: none`** is in no pack by its own
|
|
185
|
+
// statement: it publishes a page and compiles into nothing, so it has
|
|
186
|
+
// an address and no UUID. The shared declaration is what is read,
|
|
187
|
+
// because an entry names one document and this pass routes no system.
|
|
188
|
+
(
|
|
189
|
+
id &&
|
|
190
|
+
!NEVER_PACKED_TYPES.has(String(type)) &&
|
|
191
|
+
!DERIVED_PACKED_TYPES.has(String(type)) &&
|
|
192
|
+
!(routeFm && declaresNoPack(routeFm))
|
|
193
|
+
) ?
|
|
183
194
|
compendiumUuid(
|
|
184
195
|
foundryPackageId,
|
|
185
196
|
type,
|
|
@@ -1265,9 +1265,8 @@ export function lintNote(
|
|
|
1265
1265
|
message: draftRetiredMessage(),
|
|
1266
1266
|
});
|
|
1267
1267
|
}
|
|
1268
|
-
// Anchored at column 1 for the same reason `aliases` is:
|
|
1269
|
-
//
|
|
1270
|
-
// some other block is not this field.
|
|
1268
|
+
// Anchored at column 1 for the same reason `aliases` is: a nested
|
|
1269
|
+
// `section` under some other block is not this field.
|
|
1271
1270
|
if (Object.hasOwn(fm, "section")) {
|
|
1272
1271
|
findings.push({
|
|
1273
1272
|
file: note.file,
|
package/engine/helpers.mjs
CHANGED
|
@@ -38,7 +38,7 @@ import { foundryAddressProblem, pathnameProblem, resolvePathname } from "./pathn
|
|
|
38
38
|
import log from "loglevel";
|
|
39
39
|
|
|
40
40
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
41
|
-
import { packRouter } from "./pack-router.mjs";
|
|
41
|
+
import { declaresNoPack, packRouter } from "./pack-router.mjs";
|
|
42
42
|
import { contentPackage, foundryPackageId } from "./content-package.mjs";
|
|
43
43
|
import { searchableFrontmatter } from "./note-package.mjs";
|
|
44
44
|
import { PACKAGE_BASE } from "./content-address.mjs";
|
|
@@ -741,6 +741,9 @@ export function buildContentLinkIndex(
|
|
|
741
741
|
// its prose compiles into lands — two documents, two packs.
|
|
742
742
|
pack: router.resolveOrNull(fm, packForType(fm.type).docType),
|
|
743
743
|
docPack: router.resolveOrNull(fm, "JournalEntry"),
|
|
744
|
+
// Whether the note declares `pack: none` and so has no document
|
|
745
|
+
// at all: a link to it names a page, never a compendium entry.
|
|
746
|
+
none: declaresNoPack(fm),
|
|
744
747
|
shortcode: fm.shortcode ?? null,
|
|
745
748
|
// What the note *is*, carried so a caller resolving a reference
|
|
746
749
|
// can group by the family its target declares rather than only by
|