@heroiclands/package-build 3.4.0 → 5.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/CONTENT.md +100 -22
  3. package/MIGRATING.md +120 -0
  4. package/bin/content-build.mjs +6 -2
  5. package/content-config.mjs +109 -6
  6. package/engine/base-compiler.mjs +33 -36
  7. package/engine/content-links.mjs +15 -13
  8. package/engine/content-package.mjs +3 -2
  9. package/engine/field-reference.mjs +2 -2
  10. package/engine/frontmatter-lint.mjs +26 -0
  11. package/engine/helpers.mjs +10 -12
  12. package/engine/homepage.mjs +150 -0
  13. package/engine/index.mjs +10 -1
  14. package/engine/journals.mjs +2 -3
  15. package/engine/macros.mjs +2 -3
  16. package/engine/manifest-emit.mjs +17 -12
  17. package/engine/note-package.mjs +75 -68
  18. package/engine/note-schemas.mjs +44 -0
  19. package/engine/pack-router.mjs +3 -3
  20. package/engine/retired-fields.mjs +123 -0
  21. package/engine/scenes.mjs +7 -11
  22. package/engine/site-build.mjs +151 -22
  23. package/engine/site-index.mjs +5 -5
  24. package/package.json +1 -1
  25. package/sohl/actors.mjs +2 -3
  26. package/sohl/items.mjs +2 -3
  27. package/sohl/note-schemas.mjs +8 -0
  28. package/types/content-config.d.mts +105 -4
  29. package/types/engine/base-compiler.d.mts +12 -17
  30. package/types/engine/content-package.d.mts +3 -2
  31. package/types/engine/helpers.d.mts +5 -8
  32. package/types/engine/homepage.d.mts +118 -0
  33. package/types/engine/index.d.mts +3 -0
  34. package/types/engine/manifest-emit.d.mts +7 -8
  35. package/types/engine/note-package.d.mts +29 -34
  36. package/types/engine/note-schemas.d.mts +6 -0
  37. package/types/engine/pack-router.d.mts +3 -3
  38. package/types/engine/retired-fields.d.mts +54 -0
  39. package/types/engine/site-build.d.mts +52 -4
  40. package/types/sohl/note-schemas.d.mts +6 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,187 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 4da0dbc: Every package publishes an authored homepage at `/<contentPackage>/`, and
8
+ `publish.site` becomes a mode rather than a boolean (#51, #55).
9
+
10
+ **A `type: homepage` note (#51)**
11
+
12
+ A new engine-level content type that compiles into a **page** rather than into a
13
+ compendium document. Its whole frontmatter envelope is `type` and an optional
14
+ `title`, defaulting to `packageBuild.manifest.title` so the package's name is not
15
+ written twice:
16
+
17
+ ```markdown
18
+ ---
19
+ type: homepage
20
+ title: HârnMaster Kethira Basic
21
+ ---
22
+
23
+ What the module is, which system it needs, how to install it.
24
+ ```
25
+
26
+ It compiles into no document, appears in no pack and in no link manifest, and is
27
+ addressed by the **package** — `/<contentPackage>/` — rather than by a slug
28
+ derived from its name, so `name.full`, `shortcode` and `id` decide nothing on it.
29
+ It is written at the root of `site.out`, one level above the content mount.
30
+
31
+ The page is _authored, not assembled_. Deriving it from the manifest, the release
32
+ address and `relationships` was the obvious shortcut and produces a page nobody
33
+ chose the contents of — it cannot express that Kethira requires buying the book
34
+ from Keléstia, or which of twenty sections a reader should start with.
35
+
36
+ It is declared in `engine/note-schemas.mjs` rather than in the `sohl` item
37
+ registry: the `engine/` ÷ `sohl/` line is note-format knowledge against
38
+ game-system knowledge, and a homepage carries no `system` block and mirrors no
39
+ item builder. Reachability is the symptom that makes it obvious —
40
+ `HarnMaster-3-FoundryVTT` declares no `itemBuilders` at all, so a type living in
41
+ the SoHL registry would be unavailable to HM3 and every HM3 module, which is most
42
+ of the packages that need a homepage and nothing else.
43
+
44
+ **`publish.site` is a mode (#55) — breaking**
45
+
46
+ | Was | Write |
47
+ | ------------- | ---------------------------------- |
48
+ | `site: true` | `site: content` |
49
+ | `site: false` | `site: homepage` |
50
+ | absent | absent — the default is `homepage` |
51
+
52
+ `homepage` publishes the authored homepage and **no other page**; `content`
53
+ publishes it plus every page the content tree compiles to. There is no value
54
+ meaning "no web presence", because every package publishes its homepage.
55
+
56
+ Both booleans are **refused rather than mapped** onto the nearest mode, naming
57
+ the mode to write. `false` read as _this package has no web presence_, which
58
+ describes no package now, and a value silently reinterpreted reads to its author
59
+ as though it still means what it said.
60
+
61
+ **Homepage-only is a first-class mode, not an accommodation.**
62
+ `sohl-kethira-basic` (Keléstia Productions' Fan Material Guidelines) and
63
+ `harn-adventures` (HârnFanon under Lythia's terms) each publish a homepage and
64
+ nothing beneath it — two packages under two different fan-content licences. The
65
+ boundary is _published content_: journal text, artwork, item descriptions,
66
+ compiled notes. Because the failure mode is silent — a `site:` block added later
67
+ ships licensed content with nobody noticing — the mode **fences the content
68
+ surfaces off**: in `homepage` mode the tree is never walked for pages, and
69
+ `sections`, `trees`, `landing` and `backfillSections` emit nothing even when they
70
+ are declared. Measured against the real `sohl-kethira-basic` tree — 363 notes,
71
+ and a `site:` block deliberately declaring sections and a landing — the build
72
+ emits exactly one file.
73
+
74
+ `publish.manifests.publish` is a separate decision and stays `false` for both, for
75
+ an unrelated reason: a link manifest is the dependency edge that would stop a
76
+ module being withdrawable, and a homepage is one row in a routing table.
77
+
78
+ **Nothing else moves.** `publish.address`, `publish.manifests` and the whole
79
+ `site:` block are unchanged, and every address `sohl` already publishes is
80
+ byte-identical across the upgrade. Verified against the real tree: 1,669 emitted
81
+ files before, 1,670 after, the one addition being `kb/content/_index.md`; the
82
+ link manifest's 2,989 entries and all 3,126 compiled pack documents are
83
+ byte-identical with and without the homepage note.
84
+
85
+ ## 4.0.0
86
+
87
+ ### Major Changes
88
+
89
+ - 8cfa834: Reject `package:` in a note's frontmatter (#56).
90
+
91
+ A note's package is the repository's configured `contentPackage`, full stop. A
92
+ note that declares the field fails the build, naming the file — **whatever the
93
+ value says**. An agreeing declaration is refused exactly as a disagreeing one
94
+ is: there is no value that makes writing the field correct, and a field accepted
95
+ while it agrees is a field that grows back one note at a time.
96
+
97
+ ```text
98
+ assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter field — delete it. A note's package is this repository's configured `contentPackage` ("sohl", in package-build.config.yaml), and every note in the tree belongs to it.
99
+ ```
100
+
101
+ `content-build lint` reports every such note in one pass, so a tree can be
102
+ checked before it is compiled; `package compile` and `manifest` refuse it.
103
+
104
+ **This is the third and last step**, and the two before it are already released
105
+ and adopted. 3.3.0 made the field optional so an absent one was normal and a
106
+ disagreeing one was an error; every content tree on the org was then swept on
107
+ that version — `sohl` (1,639 files), `thalorna` (1,716), `kethira` (363),
108
+ `harnensemble` (2,517). Nothing this release refuses is authored anywhere today.
109
+
110
+ **Why a major.** Consumers resolve `^3`, so a minor would reach every repository
111
+ on the next Dependabot run. A major is adopted deliberately, one repository at a
112
+ time, in a pull request that can also delete the field if any grew back — which
113
+ is the whole mechanism that made the deprecate → migrate → remove sequence safe.
114
+
115
+ **Migrating** is one line, and nothing else reads the field:
116
+
117
+ ```bash
118
+ find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
119
+ ```
120
+
121
+ `package compile` then produces byte-identical output, because the value the
122
+ build derives is the value the notes restated. See `MIGRATING.md`.
123
+
124
+ **`contentPackage` is unaffected, and is not vestigial.** It is the address
125
+ namespace — the first segment of every canonical key, the name of the emitted
126
+ link manifest, and the package a cross-package wikilink writes. Retiring the
127
+ frontmatter field is what leaves it as the single source of that value: every
128
+ key is now derived from the configuration, where it used to come from two
129
+ sources that happened to agree.
130
+
131
+ **A generated table's `WHERE … and package = "<pkg>"` clause keeps matching** —
132
+ 45 such clauses across `sohl` and `thalorna` depend on it. The package is
133
+ _synthesised_ into what the table search sees, from `contentPackage`; it is a
134
+ search value, never an authored one.
135
+
136
+ **API.** `engine/note-package.mjs` no longer exports `notePackage` — every call
137
+ site takes `contentPackage` directly, so no key is derived from frontmatter
138
+ anywhere — and `assertNotePackage` is now `assertNoDeclaredPackage`, which
139
+ asserts the field's absence rather than answering which package a note belongs
140
+ to. `expandNoteTables` no longer takes `pkg`: a table searches the whole tree,
141
+ which is one package's notes and nothing else.
142
+ - ad7691f: Remove `draft:` from a note's frontmatter (#69).
143
+
144
+ The field excluded a note from the compiled packs, from the link manifest and
145
+ from a consuming site build — and **nothing reported the consequence**.
146
+ `content-links.mjs`, `site-index.mjs` and `content-lint.mjs` never read it, so a
147
+ wikilink into a drafted note was indistinguishable from a link to a note that
148
+ does not exist, and no checker could say which. Its entire effect was to move a
149
+ note from _published_ to _unresolvable_, in silence. It also suppressed real
150
+ build failures: a note the compilers never reached could not fail on the defects
151
+ it carried.
152
+
153
+ Nothing used it. Across every HeroicLands content repository — `sohl`,
154
+ `sohl-thalorna`, `sohl-kethira-basic`, `harn-ensemble`, `harn-adventures` — not
155
+ one note declared it.
156
+
157
+ **What changed**
158
+
159
+ - The three readers are gone: the compile loop, the link-manifest walk, and the
160
+ scenes pass's map collection. So are the `skippedDraft` tally, its `PassStats`
161
+ field, and the `Skipped N draft(s)` log line.
162
+ - A note declaring `draft:` now **fails the build**, naming the file and the
163
+ line, whatever the value says — `draft: false` included, since it reads as
164
+ "publish this note", which is what happens either way. A field left merely
165
+ ignored reads to its author as though it still works, which is the same
166
+ silence in a different place.
167
+ - `content-build lint` reports it too, so a whole tree is answered at once
168
+ rather than one note per build.
169
+
170
+ **The `draft` _tag_ is untouched.** It is an authoring marker, read only by the
171
+ generated-table pass for `FROM #draft` queries, and 268 `sohl-thalorna` notes
172
+ carry it. An unfinished page is honest about being unfinished; a dropped link is
173
+ silent.
174
+
175
+ **Adopting**
176
+
177
+ Nothing to sweep — no note in the org declares the field. A consumer that
178
+ carries one deletes the line.
179
+
180
+ This lands in the same major as the `package:` rejection (#56 step 3), so the
181
+ two retired fields are one adoption rather than two. They are refused the same
182
+ way, through the same diagnostic format and the same positioning, and the
183
+ locator both need is now shared rather than written twice.
184
+
3
185
  ## 3.4.0
4
186
 
5
187
  ### Minor Changes
package/CONTENT.md CHANGED
@@ -128,9 +128,11 @@ packageBuild:
128
128
  - { from: assets/icons, to: assets/icons }
129
129
 
130
130
  # Three independent switches — every combination is real — plus the address
131
- # scheme both `manifest` and `site` derive addresses under.
131
+ # scheme both `manifest` and `site` derive addresses under. `site` is a mode,
132
+ # not a boolean: `homepage` (the default) publishes the authored homepage and
133
+ # no other page; `content` publishes it plus every page the tree compiles to.
132
134
  publish:
133
- site: true
135
+ site: content
134
136
  manifests: { publish: true, consume: true }
135
137
  address:
136
138
  prefix: kb/
@@ -147,8 +149,8 @@ site:
147
149
 
148
150
  The loader validates the document, resolves every path against the directory
149
151
  the file sits in, fills the optional halves with their defaults
150
- (`skipDirectories: []`, `packageBuild: {}`, the conventional `paths`, every
151
- publishing switch off),
152
+ (`skipDirectories: []`, `packageBuild: {}`, the conventional `paths`, both
153
+ manifest switches off and `publish.site` at its `homepage` floor),
152
154
  derives `assetRoot`, `packDirectories`, `itemTypes` and `docEntryTypes`, and
153
155
  freezes the result. A malformed configuration throws a `TypeError` naming the
154
156
  offending field, so it fails at load rather than as an empty pack much later.
@@ -178,27 +180,44 @@ something migrates on it.
178
180
  ### A note's package is the repository's, not the note's
179
181
 
180
182
  `contentPackage` is the **address namespace** every note in the tree is
181
- published under. It is not a filter, and a note does not restate it.
183
+ published under: the first segment of every canonical key (`sohl-skill-clmb`),
184
+ the name of the link manifest this build emits (`sohl.json`), and the package a
185
+ cross-package wikilink writes to reach one of these notes. It is the
186
+ repository's identity in the address space — not a filter — and a note does not
187
+ restate it.
188
+
189
+ **`package:` in a note's frontmatter is retired, and declaring it fails the
190
+ build**, naming the file, whatever the value says. An agreeing declaration is
191
+ refused exactly as a disagreeing one is: there is no value that makes writing
192
+ the field correct. The diagnostic says so, and says where the value comes from
193
+ instead:
182
194
 
183
- A note may still carry `package:` — every note written before this did — and it
184
- is accepted while it **agrees** with `contentPackage`. One that disagrees is an
185
- error naming the file, in the compile, in the link manifest, and anywhere else
186
- a note is read. The field is redundant, and it is being retired in three steps:
187
- optional now, swept out of every content tree on this version, and rejected
188
- outright in a later major.
195
+ ```text
196
+ assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter field — delete it. A note's package is this repository's configured `contentPackage` ("sohl", in package-build.config.yaml), and every note in the tree belongs to it.
197
+ ```
198
+
199
+ `content-build lint` reports every such note in one pass; `content-build
200
+ package compile` and `content-build manifest` refuse the tree.
189
201
 
190
202
  A generated table that scopes itself with `WHERE … and package = "<pkg>"` keeps
191
- working either way: a note's package is supplied to the table search whether or
192
- not the note declares it, so deleting the field never turns a table into an
193
- empty one.
203
+ working: the package is **synthesised** into what the table search sees,
204
+ supplied from `contentPackage` rather than read off the note. It is a search
205
+ value, never an authored one.
194
206
 
195
207
  It used to **select**: a note compiled when its `package:` matched and was
196
208
  skipped when it did not. Every content tree is single-package — each is
197
209
  single-sourced in the repository that ships it — so the field restated one
198
210
  constant thousands of times, while a tree whose notes named a package no
199
211
  configuration answered to compiled **zero notes and exited 0**. Deleting the
200
- field from a note is safe on this version and is the fix; deleting the
201
- _configured_ value is not, since every address derives from it.
212
+ field from a note is the fix; deleting the _configured_ value is not, since
213
+ every address derives from it.
214
+
215
+ Sweeping a tree is mechanical — the field is a whole line, and nothing else
216
+ reads it:
217
+
218
+ ```bash
219
+ find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
220
+ ```
202
221
 
203
222
  ### A registry of your own
204
223
 
@@ -577,10 +596,11 @@ It reads its whole input from configuration and takes nothing else:
577
596
  | `publish.address` | The address scheme those paths are derived under — see below. |
578
597
 
579
598
  **Both addresses are optional, independently.** A note that compiles into no
580
- document has no `uuid`, and a package that ships compendiums and publishes no
581
- site (`publish.site: false`) has no `path` on any entry. Neither is an error, and
582
- neither is guessed: inventing the missing one asserts a target that does not
583
- exist, which is the silent dead link the manifest exists to prevent.
599
+ document has no `uuid`, and a package that ships compendiums and publishes only
600
+ a homepage (`publish.site: homepage`) has no `path` on any entry its notes are
601
+ not pages. Neither is an error, and neither is guessed: inventing the missing one
602
+ asserts a target that does not exist, which is the silent dead link the manifest
603
+ exists to prevent.
584
604
 
585
605
  **`publish.manifests.publish` is a declaration, not a preference.** The file is
586
606
  vendored by other repositories and read as authoritative, so emitting one is a
@@ -598,7 +618,7 @@ time and 404s for the reader.
598
618
 
599
619
  ```yaml
600
620
  publish:
601
- site: true
621
+ site: content
602
622
  manifests: { publish: true, consume: true }
603
623
  address:
604
624
  prefix: kb/ # default: "" — the package root
@@ -641,6 +661,64 @@ address derivation, the address index, table expansion, wikilink resolution,
641
661
  code-fence protection, the foreign-manifest merge, the page emission and the
642
662
  section-landing backfill.
643
663
 
664
+ ### The homepage, and how much else is published
665
+
666
+ Every package is reachable at `https://www.heroiclands.org/<contentPackage>/`,
667
+ and what a reader finds there is a note in the content tree — one markdown file,
668
+ written by a person:
669
+
670
+ ```markdown
671
+ ---
672
+ type: homepage
673
+ title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.title
674
+ ---
675
+
676
+ What the module is, which system it needs, how to install it.
677
+ ```
678
+
679
+ That is the whole envelope. A homepage **compiles into no compendium
680
+ document**, appears in no pack and in no link manifest, and is addressed by the
681
+ _package_ rather than by its own name — so `name.full`, `shortcode` and `id`
682
+ decide nothing on it. It is dispatched on `type` like every other note, not on a
683
+ filename: `README.md` is already a section landing under `landing: readme`, and
684
+ in `sohl-thalorna` it is a developer explainer about the source tree.
685
+
686
+ `type: homepage` is declared by the **engine**, not by the `sohl` item registry,
687
+ so a package that configures no `itemBuilders` at all — `HarnMaster-3-FoundryVTT`
688
+ and every HM3 module — can author one. The `engine/` ÷ `sohl/` line is
689
+ note-format knowledge against game-system knowledge, and a homepage carries no
690
+ `system` block.
691
+
692
+ `publish.site` then says how much _else_ is published:
693
+
694
+ | Mode | What is published |
695
+ | ---------- | ------------------------------------------------------------------------------- |
696
+ | `homepage` | The authored homepage, and no other page. **The default, and the floor.** |
697
+ | `content` | The homepage plus every page the content tree compiles to, and its extra trees. |
698
+
699
+ There is no value meaning "no web presence": every package publishes its
700
+ homepage. It was a boolean until 5.0.0, and both spellings are now refused
701
+ naming the mode to write instead — see [MIGRATING.md](MIGRATING.md).
702
+
703
+ **Homepage-only is a first-class mode, not an accommodation.**
704
+ `sohl-kethira-basic` (unofficial Hârn fan material under Keléstia Productions'
705
+ Fan Material Guidelines) and `harn-adventures` (HârnFanon under Lythia's terms)
706
+ must each publish a homepage and nothing beneath it. The boundary is _published
707
+ content_ — journal text, artwork, item descriptions, compiled notes — and a page
708
+ announcing the module discloses none of it. Because the failure mode is silent,
709
+ the mode **fences the content surfaces off** rather than trusting a
710
+ configuration to stay empty: in `homepage` mode the tree is never walked for
711
+ pages, and `sections`, `trees`, `landing` and `backfillSections` emit nothing
712
+ even when they are declared.
713
+
714
+ That is separate from `publish.manifests.publish`, which stays off for both for
715
+ an unrelated reason: a link manifest is the dependency edge that would stop the
716
+ module being withdrawable, and a homepage is one row in a routing table.
717
+
718
+ The homepage is written at the root of `site.out` — the package's own address —
719
+ one level above the content mount, which is where `publish.address.prefix` puts
720
+ everything else.
721
+
644
722
  **What it does not do is decide addresses.** Those come from `publish.address`,
645
723
  the same setting the link manifest reads, so a page and its manifest entry cannot
646
724
  disagree about where the page is. Everything under `site:` is _framing_ —
@@ -669,7 +747,7 @@ site:
669
747
 
670
748
  | Key | What it decides |
671
749
  | ------------------ | ------------------------------------------------------------------------------------------------ |
672
- | `out` | The Hugo content root. **Required**, and wiped on every run — see the safety note below. |
750
+ | `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
673
751
  | `base` | Where the package is served. Defaults to `/<contentPackage>/`. |
674
752
  | `packages` | Which content packages this site renders. Defaults to its own. |
675
753
  | `sections` | Landing title and hero per section, so a landing matches the card that links to it. |
package/MIGRATING.md CHANGED
@@ -1,3 +1,123 @@
1
+ # Migrating to `@heroiclands/package-build` 5.0.0
2
+
3
+ **One configuration change: `publish.site` is a mode, not a boolean.** And one
4
+ new authoring capability that needs no migration: a `type: homepage` note.
5
+
6
+ ## 1. Respell `publish.site`
7
+
8
+ ```yaml
9
+ publish:
10
+ site: content # was `site: true`
11
+ ```
12
+
13
+ ```yaml
14
+ publish:
15
+ site: homepage # was `site: false`, or absent
16
+ ```
17
+
18
+ `homepage` is the default, so a repository that never set the key needs no edit.
19
+ A repository that set it to either boolean gets a `TypeError` at load naming the
20
+ mode to write:
21
+
22
+ ```text
23
+ package-build config: `publish.site` is no longer a boolean — write `site: content`. Every package publishes an authored homepage at /<contentPackage>/, so no value means "no web presence": `homepage` publishes that page and nothing else, and `content` publishes it plus every page the content tree compiles to.
24
+ ```
25
+
26
+ Both spellings are **refused rather than mapped** onto the nearest mode. `false`
27
+ read as _this package has no web presence_, which now describes no package at
28
+ all, and a value silently reinterpreted reads to its author as though it still
29
+ means what it said.
30
+
31
+ Nothing else about publishing moved: `publish.address`, `publish.manifests` and
32
+ the whole `site:` block are unchanged, and every address `sohl` and `thalorna`
33
+ already publish is byte-identical across the upgrade.
34
+
35
+ ## 2. Author a homepage (optional here, required by #52)
36
+
37
+ Every package is reachable at `https://www.heroiclands.org/<contentPackage>/`,
38
+ and the page there is a note in the content tree:
39
+
40
+ ```markdown
41
+ ---
42
+ type: homepage
43
+ title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.title
44
+ ---
45
+
46
+ What the module is, which system it needs, how to install it.
47
+ ```
48
+
49
+ It compiles into no compendium document, appears in no pack and in no link
50
+ manifest, and is addressed by the package rather than by its own name. It is
51
+ written to the root of `site.out`, one level above the content mount.
52
+
53
+ A repository with no homepage note publishes none, and the site build says so in
54
+ its count. Requiring exactly one is a separate change (#52).
55
+
56
+ ## 3. What homepage-only means
57
+
58
+ `homepage` mode does not merely leave the content configuration unused — it
59
+ **fences the content surfaces off**. The tree is never walked for pages, and
60
+ `site.sections`, `site.trees`, `site.landing` and `site.backfillSections` emit
61
+ nothing even when they are declared.
62
+
63
+ That is deliberate, and it is a licensing requirement rather than a preference.
64
+ `sohl-kethira-basic` (Keléstia Productions' Fan Material Guidelines) and
65
+ `harn-adventures` (HârnFanon under Lythia's terms) publish a homepage and no
66
+ other page; the failure mode is silent — a `site:` block added later ships
67
+ licensed content with nobody noticing — so the property is asserted by the code
68
+ path rather than left to configuration.
69
+
70
+ `publish.manifests.publish` is a separate decision and stays `false` for both: a
71
+ link manifest is the dependency edge that would stop a module being withdrawable,
72
+ and a homepage is not.
73
+
74
+ # Migrating to `@heroiclands/package-build` 4.0.0
75
+
76
+ **One authoring change: delete `package:` from every content note.** A note's
77
+ package is the repository's configured `contentPackage`, and declaring the field
78
+ is now a build error rather than a redundancy the build tolerated.
79
+
80
+ ## 1. Sweep the content tree
81
+
82
+ The field is a whole line, and nothing else reads it:
83
+
84
+ ```bash
85
+ find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
86
+ ```
87
+
88
+ (GNU `sed`: `sed -i '/^package: /d'`.) Then compile — `content-build package
89
+ compile` must produce byte-identical output to the run before the sweep, because
90
+ the value the build derives is the value the notes restated.
91
+
92
+ A note that still carries the field fails the build where it is:
93
+
94
+ ```text
95
+ assets/content/Gear/Axe.md:12:1: error: `package: sohl` is a retired frontmatter field — delete it. A note's package is this repository's configured `contentPackage` ("sohl", in package-build.config.yaml), and every note in the tree belongs to it.
96
+ ```
97
+
98
+ `content-build lint` reports every one of them in a single pass, so the sweep can
99
+ be checked before it is compiled.
100
+
101
+ ## 2. Nothing else
102
+
103
+ - **`contentPackage` stays**, and is unchanged. It is the address namespace —
104
+ the first segment of every canonical key, the name of the emitted link
105
+ manifest, and the package a cross-package wikilink writes. Every address in
106
+ every manifest is identical across this upgrade.
107
+ - **A generated table's `WHERE … and package = "<pkg>"` clause keeps matching.**
108
+ The package is synthesised into what the table search sees, from
109
+ `contentPackage`; it was never the authored field that answered the clause
110
+ after 3.3.0.
111
+ - **No configuration key changed**, and no CLI command, flag or exit code.
112
+
113
+ ## What this replaced
114
+
115
+ A note used to be _selected_ by the field: it compiled when `package:` matched
116
+ `contentPackage` and was skipped, silently and as "belongs to another pass",
117
+ when it did not. A tree whose notes named a package no configuration answered to
118
+ compiled **zero notes and exited 0** (#56). 3.3.0 made the field optional so
119
+ every repository could be swept on a non-breaking version; this major removes it.
120
+
1
121
  # Migrating to `@heroiclands/package-build` 3.0.0
2
122
 
3
123
  `@heroiclands/content-build` and `@heroiclands/package-build` are one package.
@@ -76,6 +76,9 @@ import { lintFrontmatter } from "../engine/frontmatter-lint.mjs";
76
76
  // set — an adventure module ships skills, beings and magic swords — so no
77
77
  // consumer gets a subset (#19, #20).
78
78
  import { NOTE_SCHEMAS } from "../sohl/note-schemas.mjs";
79
+ // The engine's own types, merged under the registry's so the vocabulary stands
80
+ // in a package that configures no `itemBuilders` at all (#51).
81
+ import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
79
82
  import { checkFormatting, lintMarkdown } from "../engine/prose-lint.mjs";
80
83
  import { emitLinkManifest } from "../engine/manifest-emit.mjs";
81
84
  import {
@@ -346,7 +349,7 @@ function lintCommand() {
346
349
  skipDirectories: config.skipDirectories,
347
350
  });
348
351
  const frontmatter = lintFrontmatter(index, {
349
- schemas: NOTE_SCHEMAS,
352
+ schemas: { ...ENGINE_NOTE_SCHEMAS, ...NOTE_SCHEMAS },
350
353
  references: argv.references,
351
354
  });
352
355
 
@@ -823,7 +826,8 @@ function siteCommand() {
823
826
 
824
827
  const s = result.stats;
825
828
  log.info(
826
- `wrote ${s.content ?? 0} content page(s) + ` +
829
+ `wrote ${s.homepages ?? 0} homepage(s) + ` +
830
+ `${s.content ?? 0} content page(s) + ` +
827
831
  `${s.tree ?? 0} tree page(s) + ${s.landings} ` +
828
832
  `landing(s) to ${path.relative(process.cwd(), s.out)}`,
829
833
  );