@heroiclands/package-build 11.1.0 → 14.0.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/CONTENT.md CHANGED
@@ -138,7 +138,6 @@ publish:
138
138
  manifests: { publish: true, consume: true }
139
139
  address:
140
140
  prefix: kb/
141
- landing: readme
142
141
 
143
142
  # How this repository frames the website `content-build site` publishes.
144
143
  # Framing only: addresses come from `publish.address` above.
@@ -796,6 +795,26 @@ values. A `weapon` declares none — SoHL distinguishes a weapon's uses by strik
796
795
  mode rather than by kind — so `subType` on one is a finding; a `skill` declares
797
796
  ten, so `subType: crafte` is a finding naming `craft`.
798
797
 
798
+ **A `type` and a `subType` are both held to `^[A-Za-z0-9]+$`** (#206) — the same
799
+ constant a `shortcode` is held to, read rather than restated. A type is the
800
+ first segment of every address, so a hyphen in one is read back as a segment
801
+ boundary nobody wrote. A `subType` reaches no address since #204 retired
802
+ sections, and keeps the rule anyway: it is a vocabulary term the toolchain keys
803
+ on, and one charset that holds for every term is a rule an author can state. The
804
+ rule is checked ahead of the closed-set check, which is what makes it reach a
805
+ type whose values are declared but not yet enumerated:
806
+
807
+ ```text
808
+ assets/content/Beings/Folk.md:3:1: error: `subType` "common-folk" is not a well-formed subType — a subType is letters and digits only (^[A-Za-z0-9]+$), the same charset a type, a shortcode and a contentPackage are held to. …
809
+ ```
810
+
811
+ One declared value broke that rule: a `doc`'s `user-guide`, now **`userguide`**.
812
+ The old spelling was accepted for one transitional release, as a warning naming
813
+ the replacement, because an error would have redded every tree that took the
814
+ release before it had a chance to sweep. Every tree has swept, so the acceptance
815
+ is gone (#210): `user-guide` is refused by the charset check like any other
816
+ hyphenated value, and nothing retirement-specific was left to remove.
817
+
799
818
  The vocabulary lives in `engine/note-vocabulary.mjs`, one entry per note type,
800
819
  taken from the content-format specification. It is note-format knowledge rather
801
820
  than any system's: `data:` holds what is true of the thing, and what a system
@@ -1206,21 +1225,29 @@ package's fixed mounts (`/<package>/` for the landing page, `/<package>/api/` fo
1206
1225
  generated API docs), neither of which contains a hyphen or names a type. So the
1207
1226
  namespace is provably disjoint rather than conventionally so.
1208
1227
 
1209
- **Sections stay, as directories.** Hugo derives a page's section from where the
1210
- file is written, not from its URL, and that section is what gives a landing page,
1211
- `.CurrentSection` and the per-section layout lookup. So a page is still written
1212
- into `<section>/`, and carries a front-matter `url:` publishing it at its
1213
- address. The two are free to differ, and do.
1214
-
1215
- **A landing page is the one exception**, because it is not addressed by
1216
- `(type, shortcode)` at all: it _is_ its section, so it addresses the section —
1217
- under the content mount, where the section directories live (`kb/rules/`).
1228
+ **A page is written flat, named by its address** (#204). It used to be filed
1229
+ into `<section>/`, because Hugo derives a page's section from where the file is
1230
+ written and a section gave it a landing page, `.CurrentSection` and a per-section
1231
+ layout lookup. But a section appears in no address, so the note format was
1232
+ carrying a key, a filename convention, a landing rule and a synthesis pass in
1233
+ order to satisfy a rendering engine's directory semantics. The file is now
1234
+ `<mount>/<type>-<shortcode>.md` and the front-matter `url:` still publishes it at
1235
+ the package root, one level above.
1236
+
1237
+ **There is no landing page.** A `README.md` was its section's landing and
1238
+ addressed the section itself; that is retired with the section. A page that
1239
+ introduces the notes of a type is an ordinary note addressed `doc-<type>`, with
1240
+ no build path of its own — exactly as the package's own front page is
1241
+ `homepage-root` (#182).
1242
+
1243
+ **Sections stay, as configuration.** `site.sections` still writes an `_index.md`
1244
+ per section, and that is now the _only_ thing that makes one exist — see
1245
+ [What a section may declare](#what-a-section-may-declare).
1218
1246
 
1219
1247
  ### The address scheme
1220
1248
 
1221
- Where the content tree mounts _inside the package_, and which note addresses a
1222
- whole section rather than a page within one, differ between repositories and are
1223
- both load-bearing. They are one setting, read by this command **and** by the page
1249
+ Where the content tree mounts _inside the package_ differs between repositories
1250
+ and is load-bearing. It is one setting, read by this command **and** by the page
1224
1251
  emitter, so the address a manifest publishes is the address a page is emitted at
1225
1252
  — stating it twice is how a manifest comes to assert a URL that resolves at build
1226
1253
  time and 404s for the reader.
@@ -1231,49 +1258,29 @@ publish:
1231
1258
  manifests: { publish: true, consume: true }
1232
1259
  address:
1233
1260
  prefix: kb/ # default: "" — the package root
1234
- landing: readme # default: readme
1235
1261
  ```
1236
1262
 
1237
- - **`prefix`** — the content tree's mount within the package: where its section
1238
- directories and their landing pages live. `sohl` publishes a knowledgebase
1239
- alongside generated API docs, so its sections sit under `kb/` (`kb/affliction/`)
1240
- while its pages address the package root (`affliction-aconite/`); `thalorna`'s
1241
- site is nothing but its content, so it has no prefix. It must end in a slash and
1242
- must not begin with one — where the _package_ is mounted is the consuming
1243
- build's knowledge and is never recorded here.
1244
- - **`landing`** — which note is a section's landing page, and so is addressed by
1245
- the section rather than by `(type, shortcode)`:
1246
- - `readme` a `README.md` addresses its section. A `doc` note then routes by
1247
- its `category` like any other, so a `category: collection` note publishes
1248
- under a literal `collection/` section.
1249
- - `collection` a `doc` note whose `category` is `collection` addresses the
1250
- section it introduces, named by its authored `section`.
1251
-
1252
- The two are alternatives rather than a pair that could both apply: each live
1253
- content tree holds notes the other rule would move.
1254
-
1255
- **Under `readme`, a landing's `subType` is an address rather than a genre.** The
1256
- segment the `README` lands at is what `sectionOf` reads for a `doc`, its
1257
- `subType` so `Weapons/README.md` writes `subType: weapongear` and publishes at
1258
- `weapongear/`. `content-build lint` checks that value against the sections that
1259
- can exist, which is three sets: every **content type** the format declares
1260
- (`sectionOf` returns a non-`doc` note's own type, so `being` and `weapongear` are
1261
- sections by construction), the **subtypes the type declares** (`rules`,
1262
- `user-guide`, `reference`), and any section named in
1263
- [`site.sections` / `site.readmeSections`](#what-a-section-may-declare). A
1264
- misspelt one is still refused, by name and against all three, with the near miss
1265
- suggested. This applies to a `README` only: every other note's `subType` stays
1266
- closed to the values its type declares.
1267
-
1268
- Configuring the section is **not** a prerequisite — `site.sections` is framing,
1269
- and a package that renders its own site need declare no `site:` block at all.
1270
-
1271
- A note the scheme yields no address for — one carrying no `shortcode`, a `doc`
1272
- with no subtype (so no section to be filed under), a collection note naming no
1273
- section — is **reported and omitted**, never guessed. The command
1274
- prints one located diagnostic per note and still writes the file, because a note
1275
- with no address is ordinary while a manifest entry pointing at a page that does
1276
- not exist is not.
1263
+ - **`prefix`** — the content tree's mount within the package: the Hugo directory
1264
+ its pages are written under. `sohl` publishes a knowledgebase alongside
1265
+ generated API docs, so its tree sits under `kb/` while its pages address the
1266
+ package root (`affliction-aconite/`); `thalorna`'s site is nothing but its
1267
+ content, so it has no prefix. It must end in a slash and must not begin with
1268
+ one — where the _package_ is mounted is the consuming build's knowledge and is
1269
+ never recorded here.
1270
+
1271
+ `prefix` is the whole scheme. The `collection` subtype and the top-level
1272
+ `section:` key are refused by name (#202).
1273
+
1274
+ A note's `subType` is checked against the values its type declares, and only
1275
+ those. It briefly had a second reading a `README` landing's `subType` was the
1276
+ _address_ it landed at, so the closed genre list could not answer for it (#197,
1277
+ #198, #200, #201) — and #204 removed the cause rather than widening the
1278
+ vocabulary again.
1279
+
1280
+ The only note the scheme yields no address for is one carrying no `shortcode`.
1281
+ It is **reported and omitted**, never guessed: the command prints one located
1282
+ diagnostic per note and still writes the file, because a note with no address is
1283
+ ordinary while a manifest entry pointing at a page that does not exist is not.
1277
1284
 
1278
1285
  ## Publishing a website
1279
1286
 
@@ -1286,7 +1293,7 @@ The sibling of `package compile`: the same content tree, rendered as pages
1286
1293
  instead of compiled into packs. It does the walk, the frontmatter read, the
1287
1294
  address derivation, the address index, table expansion, wikilink resolution,
1288
1295
  code-fence protection, the foreign-manifest merge, the page emission and the
1289
- section-landing backfill.
1296
+ section-landing synthesis.
1290
1297
 
1291
1298
  ### The homepage, and how much else is published
1292
1299
 
@@ -1316,8 +1323,9 @@ still refuses `id`. Everything else about its address is ordinary: it declares a
1316
1323
  [The homepage is addressed like every other note](#the-homepage-is-addressed-like-every-other-note)).
1317
1324
  `/<contentPackage>/` itself is a redirect the package authors, not a page this
1318
1325
  build writes. It is dispatched on `type` like every other note, not on a
1319
- filename: `README.md` is already a section landing under `landing: readme`, and
1320
- in `sohl-thalorna` it is a developer explainer about the source tree.
1326
+ filename nothing in this format is decided by a file's name, which is why
1327
+ `sohl-thalorna` can keep a `README.md` in its content tree as a developer
1328
+ explainer about the source tree.
1321
1329
 
1322
1330
  `type: homepage` is declared by the **engine**, not by the `sohl` item registry,
1323
1331
  so a package that configures no `itemBuilders` at all — `HarnMaster-3-FoundryVTT`
@@ -1354,8 +1362,8 @@ module being withdrawable, and a homepage is one row in a routing table.
1354
1362
  The homepage's file is written at the root of `site.out` — the package's own
1355
1363
  site root, one level above the content mount, which is where
1356
1364
  `publish.address.prefix` puts everything else — under the name its address gives
1357
- it, `homepage-root.md`. As with every other page, the file's location decides
1358
- the Hugo section and the front matter's `url` decides where it publishes.
1365
+ it, `homepage-root.md`. As with every other page, the front matter's `url`
1366
+ decides where it publishes.
1359
1367
 
1360
1368
  **What it does not do is decide addresses.** Those come from `publish.address`,
1361
1369
  the same setting the link manifest reads, so a page and its manifest entry cannot
@@ -1391,36 +1399,97 @@ site:
1391
1399
  | `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
1392
1400
  | `base` | Where the package is served. Defaults to `/<contentPackage>/`. |
1393
1401
  | `packages` | Which content packages this site renders. Defaults to its own. |
1394
- | `sections` | What each section says about itself on its landing — see below. |
1395
- | `readmeSections` | The same, for a section whose landing comes from a `README`. |
1402
+ | `sections` | The Hugo sections this site declares, and what each says about itself — see below. |
1403
+ | `readmeSections` | The same, for a `trees` entry, whose landing comes from its own `README`. |
1396
1404
  | `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
1397
- | `backfillSections` | Write a bare `_index.md` for any other section directly under the mount. |
1405
+ | `backfillSections` | Write a bare `_index.md` for any other directory directly under the mount. |
1398
1406
  | `trees` | Extra source trees published beside the content, preserving their source layout below a section. |
1399
1407
  | `pass` | A named bundle of this repository's own body rewrites. |
1400
1408
  | `passOptions` | That bundle's options. |
1401
1409
 
1402
1410
  ### What a section may declare
1403
1411
 
1404
- A generated section landing is the **only** place a section can describe itself.
1405
- A content package authors no `_index.md` for `weapongear` or `affliction`, so
1406
- the file the theme reads is the one this build writes from `sections` — and
1407
- whatever that entry may carry is the whole of what the section can say.
1412
+ **`sections` is what a section _is_ now** (#204). A content page is addressed
1413
+ `(type, shortcode)` and written flat under the mount, so no page creates a
1414
+ directory and nothing else makes `/<package>/<prefix><section>/` answer at all.
1415
+ A site that wants that address says so here, and this build writes the
1416
+ `_index.md` that makes Hugo agree it is a section.
1417
+
1418
+ Two consequences follow, and neither is optional:
1419
+
1420
+ - **Declare every section the site links to.** A card, a menu entry or a
1421
+ breadcrumb pointing at a section nobody declared is a 404.
1422
+ - **A section landing lists no child pages.** Its directory holds only its own
1423
+ `_index.md`, so a layout reading `.Pages` finds nothing. A layout that queries
1424
+ `site.RegularPages` by `Params.type` is unaffected, and that is the shape a
1425
+ content catalog wants anyway — it groups by what a page _is_, not by where its
1426
+ file happened to be written. A section that wants that query run for it says
1427
+ so with `listType` — see below.
1428
+
1429
+ Whatever the entry may carry is the whole of what the section can say.
1408
1430
 
1409
1431
  | Key | Required | What it does |
1410
1432
  | ------------- | -------- | ------------------------------------------------------------------------- |
1411
1433
  | `title` | yes | The landing's heading, so it matches the card that links to it. |
1412
1434
  | `banner` | no | The hero image, resolved as a CDN asset like any other `banner:`. |
1413
1435
  | `description` | no | The hero standfirst under the heading, and the blurb a landing card uses. |
1436
+ | `listType` | no | The content **type** whose pages this section lists. |
1437
+ | `listSubType` | no | Narrows that to one **subType**. Only with a `listType`. |
1438
+
1439
+ `readmeSections` takes the same keys, for a **`trees`** entry: those pages keep
1440
+ their source layout below a named section, so the tree's own `README.md` is that
1441
+ section's landing. What the section declares wins over what the `README` happens
1442
+ to carry — the landing has to match the card that links to it. No content note
1443
+ reaches this map any more.
1444
+
1445
+ #### Saying what a section lists
1446
+
1447
+ A generic list layout has nothing to render, because the membership a section
1448
+ landing used to get free from Hugo's page tree no longer exists anywhere the
1449
+ theme can read: not on the page, not on the landing, not in any URL — only here.
1450
+ So a section states its own query, and a layout substitutes it when `.Pages` is
1451
+ empty:
1452
+
1453
+ ```yaml
1454
+ sections:
1455
+ being: { title: Beings, listType: being }
1456
+ rules: { title: Rules, listType: doc, listSubType: rules }
1457
+ user-guide: { title: User Guide, listType: doc, listSubType: userguide }
1458
+ ```
1459
+
1460
+ emits, for the last of those, `user-guide/_index.md`:
1461
+
1462
+ ```yaml
1463
+ ---
1464
+ title: User Guide
1465
+ listType: doc
1466
+ listSubType: userguide
1467
+ ---
1468
+ ```
1414
1469
 
1415
- `readmeSections` takes the same three, for a section whose landing comes from a
1416
- `README` rather than from nothing. What the section declares wins over what the
1417
- `README` happens to carry the landing has to match the card that links to it.
1470
+ Three things about the spelling, each of them load-bearing:
1471
+
1472
+ - **Not `type`.** On an `_index.md` that is Hugo's own layout selector a
1473
+ landing carrying `type: doc` is rendered by `layouts/doc/list.html` rather
1474
+ than the default list template — so writing the content type there would
1475
+ silently change which template serves the landing. This build already relies
1476
+ on that behaviour for the mount's own `landing`.
1477
+ - **Not inferred from the section's name.** A section is named for a URL the
1478
+ site chose; a type and a subType are addresses. They need not agree, and on
1479
+ `sohl` they do not: the section is `user-guide`, because that is a published
1480
+ URL, while the subType is `userguide` because an address segment is
1481
+ alphanumeric (#207). Both values are checked against that charset here, so
1482
+ copying the section's name in is refused rather than quietly matching nothing.
1483
+ - **`listSubType` needs a `listType`.** A subType only tells pages apart within
1484
+ a type — `rules`, `userguide` and `reference` are all `doc` — so alone it
1485
+ names no query.
1418
1486
 
1419
1487
  **The vocabulary is closed, and a key outside it is refused by name:**
1420
1488
 
1421
1489
  ```text
1422
1490
  package-build config: `site.sections.affliction.descrption` is not a
1423
- recognized option (expected one of: title, banner, description).
1491
+ recognized option (expected one of: title, banner, description, listType,
1492
+ listSubType).
1424
1493
  ```
1425
1494
 
1426
1495
  That refusal is the point. `landing` is passed through unvalidated because it is
@@ -1429,7 +1498,10 @@ entry is written fourteen to twenty times per build against a contract every
1429
1498
  package and every section shares, so an unbounded one would let a mistyped
1430
1499
  `descrption:` publish into front matter, be read by nobody, and say nothing to
1431
1500
  anyone. Refusing it costs one line here when the vocabulary genuinely grows, and
1432
- buys a build that cannot quietly emit a key no theme reads.
1501
+ buys a build that cannot quietly emit a key no theme reads. `listType` and
1502
+ `listSubType` are that growth: two named keys, checked, rather than an open
1503
+ passthrough in which `listTpye:` would publish and no landing would list
1504
+ anything.
1433
1505
 
1434
1506
  The **writers** name no keys: a section's `_index.md` is whatever the entry
1435
1507
  resolved to, `title` first. So extending the vocabulary is a change to the
@@ -1593,7 +1665,7 @@ path now rides on the error, and the loader that read the file resolves it
1593
1665
  against the YAML, so all of them come out located:
1594
1666
 
1595
1667
  ```text
1596
- package-build.config.yaml:382:64: error: package-build config: `site.sections.being.descrption` is not a recognized option (expected one of: title, banner).
1668
+ package-build.config.yaml:382:64: error: package-build config: `site.sections.being.descrption` is not a recognized option (expected one of: title, banner, description, listType, listSubType).
1597
1669
  ```
1598
1670
 
1599
1671
  The same two rules apply. A key the file never declares — a required one that is
package/MIGRATING.md CHANGED
@@ -1,3 +1,74 @@
1
+ # Migrating to `@heroiclands/package-build` 12.0.0
2
+
3
+ **No note edit, no URL change, and one thing to check in the Hugo layer.** A
4
+ section is a Hugo directory concept that the note format no longer carries
5
+ (#204): content pages are written **flat** under the content mount, named by
6
+ their address, instead of into a `<section>/` directory.
7
+
8
+ ## 1. Nothing in the content tree changes
9
+
10
+ A page's address never contained a section, so **no published URL moves**. A
11
+ `README.md` in the content tree stops being its section's landing and becomes an
12
+ ordinary page addressed `<type>-<shortcode>/` like every other note; if a tree
13
+ still has one that was serving as a landing, it now publishes at its own address.
14
+ No tree in this project had one.
15
+
16
+ A `doc` with no `subType` used to be refused ("no section, so there is nowhere to
17
+ file the page") and now publishes. Its `subType` is a **genre** again — closed to
18
+ `rules`, `userguide`, `reference` — because it no longer doubles as a section
19
+ address on a `README`. That closed check runs after the two #206 added ahead of
20
+ it: a retired spelling is a warning naming its replacement, a hyphenated value
21
+ is an error, and only then is the type's own list the reason.
22
+
23
+ ## 2. Declare every section your site links to
24
+
25
+ `site.sections` is now the whole of what a section is. No page is filed into a
26
+ section directory any more, so nothing else makes `/<package>/<prefix><section>/`
27
+ exist:
28
+
29
+ ```yaml
30
+ site:
31
+ sections:
32
+ being: { title: Beings, banner: banners/creature.webp }
33
+ weapongear: { title: Weapons, banner: banners/weapons.webp }
34
+ ```
35
+
36
+ A card, menu entry or breadcrumb pointing at a section nobody declares is a 404.
37
+
38
+ ## 3. Check how a section landing lists its members
39
+
40
+ A declared section's directory holds only its own `_index.md`, so a layout
41
+ reading `.Pages` renders an empty listing. Query the site instead, on the page's
42
+ own `type`:
43
+
44
+ ```go-html-template
45
+ {{- $pages := where site.RegularPages "Type" "weapongear" -}}
46
+ ```
47
+
48
+ That is the shape a content catalog wants regardless — it groups by what a page
49
+ _is_ rather than by where its file happened to be written — and it is what
50
+ `sohl`'s catalog layouts already do.
51
+
52
+ A site rendering through a **shared theme** has no layout of its own to edit. As
53
+ of 13.1.0 the section declares its query instead, and the theme runs it:
54
+
55
+ ```yaml
56
+ sections:
57
+ weapongear: { title: Weapons, listType: weapongear }
58
+ user-guide: { title: User Guide, listType: doc, listSubType: userguide }
59
+ ```
60
+
61
+ `listType` is the content type, not the section's name — the two need not agree,
62
+ and `listSubType` narrows a type whose genres share it. See
63
+ [What a section may declare](CONTENT.md#what-a-section-may-declare).
64
+
65
+ ## What did not change
66
+
67
+ - Every content page's `url:`, and so every published address.
68
+ - Every link-manifest entry, including each entry's `path`.
69
+ - Every compiled compendium document.
70
+ - `site.sections` / `site.readmeSections` and what an entry may declare.
71
+
1
72
  # Migrating to `@heroiclands/package-build` 11.0.0
2
73
 
3
74
  **Two edits, and the second is one line per repository.** Every published page
@@ -72,10 +72,6 @@ import {
72
72
  import { renderItemFieldReference } from "../engine/field-reference.mjs";
73
73
  import { lintContentTree } from "../engine/content-lint.mjs";
74
74
  import { lintFrontmatter } from "../engine/frontmatter-lint.mjs";
75
- // The sections this repository declares — the open set a `README` landing's
76
- // `subType` names (#197). Read from the resolved configuration the site build
77
- // renders those landings from, so neither can name a section the other does not.
78
- import { declaredSections } from "../content-config.mjs";
79
75
  import { loadContentFormat } from "../engine/content-format.mjs";
80
76
  import {
81
77
  checkDeclaredFields,
@@ -740,22 +736,6 @@ function lintCommand() {
740
736
  // decides which vocabulary a tree is held to.
741
737
  vocabulary: NOTE_VOCABULARY,
742
738
  references: argv.references,
743
- // How this repository addresses a section landing, and the
744
- // sections that can exist — the open vocabulary a
745
- // `README`'s `subType` is read against (#197, #200).
746
- // Passed in for the same reason the schemas are: the
747
- // linter checks a note against what it is handed.
748
- //
749
- // The types come from the **specification**, not from
750
- // `schemas`: a landing's `subType` is an address, and a
751
- // type the format declares is a section whether or not
752
- // this build carries a schema to check its notes' fields
753
- // with. Reading `schemas` here would refuse `Lore/README.md`
754
- // for a reason that is not about addresses, and report one
755
- // gap twice in two vocabularies.
756
- landing: config.publish.address.landing,
757
- types: [...loadContentFormat().types.keys()],
758
- sections: declaredSections(config),
759
739
  });
760
740
 
761
741
  // What the builders emit, against what the receiving system