@heroiclands/package-build 20.3.0 → 20.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/CONTENT.md +111 -92
  3. package/README.md +2 -9
  4. package/bin/content-build.mjs +70 -61
  5. package/bin/package-build.mjs +2 -2
  6. package/config.mjs +6 -8
  7. package/content-config.mjs +187 -64
  8. package/docs/content-format.md +57 -68
  9. package/e2e.mjs +4 -4
  10. package/engine/actor-compiler.mjs +36 -36
  11. package/engine/address-charset.mjs +6 -6
  12. package/engine/address-diff.mjs +12 -11
  13. package/engine/anchored-sections.mjs +1 -1
  14. package/engine/anchors.mjs +1 -1
  15. package/engine/base-compiler.mjs +36 -38
  16. package/engine/bundle-notes.mjs +4 -4
  17. package/engine/bundles.mjs +8 -8
  18. package/engine/code-fences.mjs +3 -3
  19. package/engine/compendiums.mjs +8 -8
  20. package/engine/compile-corpus.mjs +4 -5
  21. package/engine/content-address.mjs +15 -15
  22. package/engine/content-charset.mjs +1 -1
  23. package/engine/content-format-check.mjs +24 -25
  24. package/engine/content-format.mjs +8 -8
  25. package/engine/content-html.mjs +202 -0
  26. package/engine/content-icons.mjs +225 -98
  27. package/engine/content-index.mjs +17 -17
  28. package/engine/content-links.mjs +30 -30
  29. package/engine/content-lint.mjs +20 -24
  30. package/engine/content-package.mjs +7 -9
  31. package/engine/content-slug.mjs +2 -2
  32. package/engine/content-tables.mjs +7 -8
  33. package/engine/content-tree.mjs +1 -1
  34. package/engine/derived-fields.mjs +174 -0
  35. package/engine/diagnostics.mjs +3 -4
  36. package/engine/document-subtypes.mjs +6 -7
  37. package/engine/field-reference.mjs +8 -8
  38. package/engine/field-spec.mjs +29 -30
  39. package/engine/folder-notes.mjs +14 -14
  40. package/engine/foreign-catalog.mjs +8 -9
  41. package/engine/foundry-entries.mjs +19 -20
  42. package/engine/frontmatter-lint.mjs +64 -69
  43. package/engine/frontmatter.mjs +11 -11
  44. package/engine/generate.mjs +39 -39
  45. package/engine/helpers.mjs +63 -54
  46. package/engine/homepage.mjs +20 -22
  47. package/engine/ids.mjs +15 -15
  48. package/engine/index-records.mjs +4 -4
  49. package/engine/index.mjs +14 -8
  50. package/engine/item-compiler.mjs +54 -29
  51. package/engine/item-docs.mjs +11 -13
  52. package/engine/item-registry.mjs +13 -13
  53. package/engine/journals.mjs +15 -15
  54. package/engine/macros.mjs +3 -3
  55. package/engine/map-notes.mjs +6 -6
  56. package/engine/metadata-index.mjs +5 -5
  57. package/engine/note-claims.mjs +36 -34
  58. package/engine/note-ids.mjs +5 -5
  59. package/engine/note-package.mjs +4 -4
  60. package/engine/note-renames.mjs +3 -3
  61. package/engine/note-schemas.mjs +3 -3
  62. package/engine/note-vocabulary.mjs +27 -26
  63. package/engine/pack-config.mjs +13 -13
  64. package/engine/pack-router.mjs +0 -0
  65. package/engine/prose-config.mjs +9 -9
  66. package/engine/prose-lint.mjs +12 -13
  67. package/engine/region-events.mjs +2 -2
  68. package/engine/retired-fields.mjs +24 -22
  69. package/engine/runtime-only-fields.mjs +1 -1
  70. package/engine/scene-levels.mjs +1 -1
  71. package/engine/scenes.mjs +19 -20
  72. package/engine/schema-check.mjs +20 -20
  73. package/engine/schema-extract.mjs +1 -1
  74. package/engine/site-build.mjs +45 -47
  75. package/engine/site-index.mjs +17 -18
  76. package/engine/sql-tables.mjs +21 -13
  77. package/engine/subtype-registry.mjs +5 -4
  78. package/engine/system-block.mjs +18 -19
  79. package/engine/systems.mjs +2 -2
  80. package/engine/web-wikilinks.mjs +35 -35
  81. package/engine/wikilink-syntax.mjs +16 -16
  82. package/engine/wikilinks.mjs +39 -39
  83. package/hm3/actors.mjs +22 -22
  84. package/hm3/document-subtypes.mjs +5 -5
  85. package/hm3/index.mjs +2 -2
  86. package/hm3/item-builders.mjs +1 -1
  87. package/hm3/item-fields.mjs +3 -3
  88. package/hm3/items.mjs +20 -8
  89. package/hm3/template-priority.mjs +2 -2
  90. package/lang.mjs +3 -3
  91. package/manifest.mjs +17 -20
  92. package/package.json +1 -2
  93. package/release.mjs +3 -3
  94. package/sohl/actors.mjs +28 -28
  95. package/sohl/affiliation-standings.mjs +2 -2
  96. package/sohl/being-info.mjs +5 -5
  97. package/sohl/default-item-art.mjs +5 -5
  98. package/sohl/document-subtypes.mjs +5 -5
  99. package/sohl/index.mjs +3 -3
  100. package/sohl/item-builders.mjs +7 -7
  101. package/sohl/item-fields.mjs +8 -9
  102. package/sohl/items.mjs +20 -6
  103. package/sohl/kb-passes.mjs +5 -5
  104. package/sohl/note-schemas.mjs +7 -7
  105. package/sohl/skill-base.mjs +1 -1
  106. package/types/content-config.d.mts +25 -24
  107. package/types/e2e.d.mts +3 -3
  108. package/types/engine/actor-compiler.d.mts +14 -13
  109. package/types/engine/address-charset.d.mts +6 -6
  110. package/types/engine/address-diff.d.mts +3 -3
  111. package/types/engine/base-compiler.d.mts +17 -19
  112. package/types/engine/bundle-notes.d.mts +3 -3
  113. package/types/engine/bundles.d.mts +1 -1
  114. package/types/engine/code-fences.d.mts +3 -3
  115. package/types/engine/compendiums.d.mts +3 -3
  116. package/types/engine/compile-corpus.d.mts +1 -1
  117. package/types/engine/content-address.d.mts +11 -11
  118. package/types/engine/content-format-check.d.mts +6 -6
  119. package/types/engine/content-format.d.mts +2 -2
  120. package/types/engine/content-html.d.mts +78 -0
  121. package/types/engine/content-icons.d.mts +150 -62
  122. package/types/engine/content-index.d.mts +2 -2
  123. package/types/engine/content-links.d.mts +8 -8
  124. package/types/engine/content-lint.d.mts +2 -2
  125. package/types/engine/content-package.d.mts +6 -8
  126. package/types/engine/derived-fields.d.mts +101 -0
  127. package/types/engine/diagnostics.d.mts +2 -2
  128. package/types/engine/document-subtypes.d.mts +3 -3
  129. package/types/engine/field-spec.d.mts +37 -39
  130. package/types/engine/folder-notes.d.mts +4 -5
  131. package/types/engine/foreign-catalog.d.mts +3 -3
  132. package/types/engine/foundry-entries.d.mts +6 -7
  133. package/types/engine/frontmatter-lint.d.mts +7 -7
  134. package/types/engine/frontmatter.d.mts +7 -7
  135. package/types/engine/generate.d.mts +7 -7
  136. package/types/engine/helpers.d.mts +25 -27
  137. package/types/engine/homepage.d.mts +16 -18
  138. package/types/engine/ids.d.mts +13 -13
  139. package/types/engine/index-records.d.mts +3 -3
  140. package/types/engine/index.d.mts +2 -0
  141. package/types/engine/item-compiler.d.mts +21 -5
  142. package/types/engine/item-docs.d.mts +2 -2
  143. package/types/engine/item-registry.d.mts +6 -6
  144. package/types/engine/journals.d.mts +3 -3
  145. package/types/engine/map-notes.d.mts +2 -2
  146. package/types/engine/metadata-index.d.mts +4 -4
  147. package/types/engine/note-claims.d.mts +16 -15
  148. package/types/engine/note-ids.d.mts +4 -4
  149. package/types/engine/note-package.d.mts +1 -1
  150. package/types/engine/note-renames.d.mts +3 -3
  151. package/types/engine/note-vocabulary.d.mts +9 -8
  152. package/types/engine/pack-config.d.mts +3 -3
  153. package/types/engine/pack-router.d.mts +1 -1
  154. package/types/engine/prose-config.d.mts +9 -9
  155. package/types/engine/prose-lint.d.mts +3 -4
  156. package/types/engine/region-events.d.mts +2 -2
  157. package/types/engine/retired-fields.d.mts +10 -9
  158. package/types/engine/schema-check.d.mts +11 -11
  159. package/types/engine/site-build.d.mts +18 -19
  160. package/types/engine/site-index.d.mts +3 -4
  161. package/types/engine/sql-tables.d.mts +11 -5
  162. package/types/engine/subtype-registry.d.mts +3 -3
  163. package/types/engine/system-block.d.mts +3 -3
  164. package/types/engine/web-wikilinks.d.mts +7 -7
  165. package/types/engine/wikilink-syntax.d.mts +17 -17
  166. package/types/engine/wikilinks.d.mts +13 -12
  167. package/types/hm3/actors.d.mts +1 -1
  168. package/types/hm3/document-subtypes.d.mts +1 -1
  169. package/types/hm3/items.d.mts +1 -2
  170. package/types/hm3/template-priority.d.mts +1 -1
  171. package/types/manifest.d.mts +8 -8
  172. package/types/sohl/actors.d.mts +2 -2
  173. package/types/sohl/affiliation-standings.d.mts +2 -2
  174. package/types/sohl/being-info.d.mts +2 -2
  175. package/types/sohl/document-subtypes.d.mts +1 -1
  176. package/types/sohl/items.d.mts +1 -2
  177. package/types/sohl/note-schemas.d.mts +1 -1
  178. package/MIGRATING.md +0 -608
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * The affiliation standings an authored `relation` map may use — one
3
- * affiliation's stance toward another (#1404).
3
+ * affiliation's stance toward another.
4
4
  *
5
5
  * Plain ESM for the same reason `./default-item-art.mjs` is: the pack scripts
6
6
  * run under bare `node`, so they cannot read the runtime's TypeScript
7
7
  * `AFFILIATION_STANDING`. This module is where the pipeline reads the list
8
- * from, rather than restating it inside a compiler (#1510).
8
+ * from, rather than restating it inside a compiler.
9
9
  *
10
10
  * The runtime keeps its own `defineType("SOHL.Affiliation.Standing", { … })`
11
11
  * declaration in `src/utils/constants.ts`, because that literal is *parsed out
@@ -36,7 +36,7 @@ export function deriveBeingInfo(sohl: object | null | undefined, index: Map<stri
36
36
  /**
37
37
  * The note `type` whose pages carry a being info block.
38
38
  *
39
- * One name, since #1580 merged `character` and `creature` into the `being` they
39
+ * One name: `character` and `creature` are the `being` they
40
40
  * had always compiled into. The retired names are deliberately **not** accepted
41
41
  * as aliases: they throw elsewhere in the system, and tolerating them here
42
42
  * would hide the next drift of this kind rather than surface it.
@@ -50,7 +50,7 @@ export const BEING_TYPE: "being";
50
50
  * added in a single place rather than in each consumer's site build.
51
51
  *
52
52
  * Keyed by **note** type, which is what a being's embedded `(type, shortcode)`
53
- * references spell — and since #78 three of those are no longer the document
53
+ * references spell — and three of those are not the document
54
54
  * subtype they compile into. A reference still on a renamed spelling is
55
55
  * normalised at the lookup below rather than given a second row here.
56
56
  *
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The Item rows are the thirteen types the item registry declares; the one
6
6
  * Actor row is `being`, which was two types (`character` and `creature`)
7
- * compiling to the same actor until they were retired in SoHL#1580. Types this
7
+ * compiling to the same actor before they were retired. Types this
8
8
  * map does not name — `doc`, `macro`, the three map types — compile into
9
9
  * documents that carry no system subtype at all, so they have no row and never
10
10
  * needed one.
@@ -1,8 +1,7 @@
1
1
  export class Items extends SystemItemCompiler {
2
2
  /**
3
3
  * SoHL's note-type → document-subtype map — the one declaration that says
4
- * which block this pass reads, which notes it claims, and what each becomes
5
- * (#58/#79).
4
+ * which block this pass reads, which notes it claims, and what each becomes.
6
5
  *
7
6
  * @type {import("../engine/document-subtypes.mjs").DocumentSubtypeMap}
8
7
  */
@@ -6,7 +6,7 @@
6
6
  * one vocabulary rather than two. They are declared there rather than here
7
7
  * because they are note-format knowledge — a `homepage` carries no `system`
8
8
  * block and would mean the same thing for a game system that is not SoHL — and
9
- * because a package declaring no `itemBuilders` never reaches this file (#51).
9
+ * because a package declaring no `itemBuilders` never reaches this file.
10
10
  *
11
11
  * @type {Readonly<Record<string, readonly import("../engine/field-spec.mjs").FieldSpec[]>>}
12
12
  */
package/MIGRATING.md DELETED
@@ -1,608 +0,0 @@
1
- # Migrating to `@heroiclands/package-build` 15.0.0
2
-
3
- **Three unrelated changes ship in this major, and they ask different
4
- repositories for different things.** A page's `url:` front matter is now stated
5
- relative to the **site root** (#217), which a site-publishing repository answers
6
- by deleting one line; an empty art path now means the opposite of an absent one
7
- (#218), which a content tree answers by sweeping `img: ""` and `portrait: ""` to
8
- `null`; and a note's own `title` no longer reaches an `affiliation` item's
9
- `system.title` (#218), which asks nothing of any repository that exists today.
10
-
11
- Route yourself by what you have. A repository that publishes no site skips
12
- §1–§2. One that authors no empty art path skips §3–§4. One with no
13
- `type: affiliation` notes skips §6 — and §5, which is only the seam between the
14
- two halves of #218.
15
-
16
- ## 1. Drop the `site.base: "/"` stopgap (#217)
17
-
18
- If your repository set it to stop every page publishing at
19
- `/<package>/<package>/<address>/`, delete the line — the default,
20
- `/<contentPackage>/`, is now right for both halves:
21
-
22
- ```yaml
23
- site:
24
- out: kb/content
25
- # base: "/" ← delete this
26
- ```
27
-
28
- Keeping it is no longer harmless: the addresses stay correct either way, but
29
- every same-package link this build renders into a page body stays short
30
- (`/doc-skills/` rather than `/sohl/doc-skills/`) and 404s.
31
-
32
- A repository that is genuinely served somewhere other than
33
- `/<contentPackage>/` still says so here, and that value still reaches every
34
- `href`.
35
-
36
- ## 2. What the `url:` change does _not_ touch
37
-
38
- - No note edits, and no configuration key added or removed.
39
- - Every link-manifest entry, including each entry's `path`, is byte-identical.
40
- - Every compiled compendium document is unchanged.
41
- - `trees` pages and section landings state no `url:` and do not move.
42
-
43
- The published address of every content page **does** move — from
44
- `/<package>/<package>/<address>/`, where nothing linked, to
45
- `/<package>/<address>/`, which is what the link manifest, the sitemap and every
46
- inbound link already named.
47
-
48
- ## 3. Sweep `img: ""` and `portrait: ""` to `null` (#218)
49
-
50
- `resolveImg` opened with `if (!raw) return ""`, and every caller applied its own
51
- default to the result with `||`. So `""`, `null` and an absent key were one
52
- case: all three compiled to the type's default art, and a note had no way to say
53
- "ship no image" at all.
54
-
55
- They are now three values with two meanings:
56
-
57
- | a note writes | it means | it compiles with |
58
- | -------------- | ------------------------------------ | ---------------- |
59
- | nothing at all | _unset_ — name me no art | the type default |
60
- | `img: null` | the same thing, said out loud | the type default |
61
- | `img: ""` | _blank on purpose_ — I want no image | no image |
62
-
63
- So a note still carrying `img: ""` **loses its default art**. The same holds for
64
- `portrait:`, which a being carries independently of `img` and which resolves
65
- through the same function. Find them both:
66
-
67
- ```bash
68
- grep -rnE '^[[:space:]]*(img|portrait):[[:space:]]*""[[:space:]]*$' assets/content --include='*.md'
69
- ```
70
-
71
- and write `null` in each — unless the document really is meant to have no image,
72
- which is what `""` now says. Before this release `sohl-thalorna` swept forty-five
73
- `img: ""` notes and `sohl-kethira-basic` eleven `portrait: ""` beings; `sohl`
74
- authors neither.
75
-
76
- The frontmatter lint reports every one that is left, as a warning:
77
-
78
- ```
79
- Note.md:9:1: warning: `img: ""` means "ship no art at all" — it no longer falls
80
- back to this type's default. Write `img: null` for a note that simply names
81
- none; keep `""` only where the document is meant to have no image
82
- ```
83
-
84
- ## 4. Custom callers pair their default with `??`, not `||`
85
-
86
- `resolveImg` returns `string | null` now: `null` for an unset path, `""` for a
87
- deliberate blank. A consumer that calls it directly — a custom item builder, a
88
- compiler of its own — must switch:
89
-
90
- ```diff
91
- -img: resolveImg(fm.img) || MY_DEFAULT,
92
- +img: resolveImg(fm.img) ?? MY_DEFAULT,
93
- ```
94
-
95
- `||` still compiles and still looks right; it silently reinstates the old
96
- conflation, because `""` is falsy. Every caller in this package moved:
97
- `sohl/items.mjs`, three in `sohl/actors.mjs` (`img`, `portrait`, and the
98
- prototype token's `texture.src`), and `engine/macros.mjs`.
99
-
100
- `itemArt()` is unaffected — a registry entry with no art throws before the
101
- translation, so its result is never the unset case.
102
-
103
- ## 5. `title` is **not** on the art rule
104
-
105
- `resolveImg`'s rule reads as a general one about optional strings, and it is
106
- not — it belongs to the function, and `title` never goes through it. So do not
107
- extend §3's sweep to `title`, and do not read the table in §3 as saying anything
108
- about it.
109
-
110
- **The reason has changed since this section was first written, and the earlier
111
- one is no longer true.** It used to be that a note's top-level `title` was
112
- _simultaneously_ the page's heading and the shared source for an `affiliation`
113
- item's `system.title`, so `title: null` did not fall back — it stringified, and
114
- the compiled document shipped the literal string `"null"`. That collision is what
115
- §6 removes: the top-level key is no longer a source for the item field at all.
116
-
117
- So `title: null` is now simply a note declining to state a heading, and the site
118
- emitter's `fm.title ?? name` falls back to `name.full` as it always did. It
119
- reaches no document field and stringifies nothing.
120
-
121
- `title: ""` still publishes a **deliberately blank heading** — which is what cost
122
- fifteen `sohl-thalorna` notes their names (HeroicLands/sohl-thalorna#129) — and
123
- nothing warns about it yet. Whether the frontmatter lint should is #218's, still
124
- open, and deliberately not settled by the art-field warning in §3.
125
-
126
- ## 6. A note's own `title` no longer fills `system.title` (#218)
127
-
128
- **No note edit, no URL change, and no compiled document moves.** An
129
- `affiliation` item's `system.title` stops falling back to the note's own
130
- top-level `title`. The two were never the same quantity — a note's `title` is
131
- the heading its page publishes under, while `system.title` is the style of
132
- address an office carries, Ajaw or Warden — and no note in any content tree
133
- relied on the fallback, so `content-build package compile` emits byte-identical
134
- `build/packs-json` for every consumer.
135
-
136
- Only a `type: affiliation` note is affected, and only if it carries a top-level
137
- `title` it meant as the item's field rather than as the page's heading:
138
-
139
- ```bash
140
- grep -rl '^type: affiliation' assets/content --include='*.md' \
141
- | xargs grep -l '^title:'
142
- ```
143
-
144
- Anything that turns up wanted one of the two positions that describe the
145
- _document_ rather than the note — `sohl.system.title`, or `sohl.title`, the
146
- legacy in-block key most trees already write. A membership's title belongs on
147
- the entry in the being's `sohl.items`, as `system.title`.
148
-
149
- `data: { title: ... }` is not a position and never was: `title` is not a `data:`
150
- property any note type declares, so `content-build lint` refuses it.
151
-
152
- ### Regenerate the item field reference
153
-
154
- The generated page now prints, under each affected type's table, what the
155
- top-level key of a non-shared field means instead — so an author reading the
156
- table learns that writing `title:` at the top of a note will not fill this
157
- field. Re-run the generator and commit the result, or a repository that checks
158
- the page for staleness reports it stale:
159
-
160
- ```bash
161
- npx content-build docs item-fields --out <the path your repo uses>
162
- ```
163
-
164
- ### Declaring your own non-shared field
165
-
166
- A field in an `itemBuilders` `fields:` declaration may now carry
167
- `topLevelMeans`, whose value is _what the note's top-level key of that name
168
- means instead_. Declaring it removes the shared top-level position from that
169
- field's resolution order:
170
-
171
- ```js
172
- {
173
- name: "title",
174
- to: "title",
175
- ...STRING,
176
- default: "",
177
- topLevelMeans: "the note's own title — the heading its page is published under",
178
- describe: "The style of address the office carries.",
179
- }
180
- ```
181
-
182
- The value is the reason rather than a bare flag on purpose: the next person
183
- adding a field needs to know the question exists, and a boolean with a comment
184
- beside it is two statements of one rule.
185
-
186
- # Migrating to `@heroiclands/package-build` 12.0.0
187
-
188
- **No note edit, no URL change, and one thing to check in the Hugo layer.** A
189
- section is a Hugo directory concept that the note format no longer carries
190
- (#204): content pages are written **flat** under the content mount, named by
191
- their address, instead of into a `<section>/` directory.
192
-
193
- ## 1. Nothing in the content tree changes
194
-
195
- A page's address never contained a section, so **no published URL moves**. A
196
- `README.md` in the content tree stops being its section's landing and becomes an
197
- ordinary page addressed `<type>-<shortcode>/` like every other note; if a tree
198
- still has one that was serving as a landing, it now publishes at its own address.
199
- No tree in this project had one.
200
-
201
- A `doc` with no `subType` used to be refused ("no section, so there is nowhere to
202
- file the page") and now publishes. Its `subType` is a **genre** again — closed to
203
- `rules`, `userguide`, `reference` — because it no longer doubles as a section
204
- address on a `README`. That closed check runs after the two #206 added ahead of
205
- it: a retired spelling is a warning naming its replacement, a hyphenated value
206
- is an error, and only then is the type's own list the reason.
207
-
208
- ## 2. Declare every section your site links to
209
-
210
- `site.sections` is now the whole of what a section is. No page is filed into a
211
- section directory any more, so nothing else makes `/<package>/<prefix><section>/`
212
- exist:
213
-
214
- ```yaml
215
- site:
216
- sections:
217
- being: { title: Beings, banner: banners/creature.webp }
218
- weapongear: { title: Weapons, banner: banners/weapons.webp }
219
- ```
220
-
221
- A card, menu entry or breadcrumb pointing at a section nobody declares is a 404.
222
-
223
- ## 3. Check how a section landing lists its members
224
-
225
- A declared section's directory holds only its own `_index.md`, so a layout
226
- reading `.Pages` renders an empty listing. Query the site instead, on the page's
227
- own `type`:
228
-
229
- ```go-html-template
230
- {{- $pages := where site.RegularPages "Type" "weapongear" -}}
231
- ```
232
-
233
- That is the shape a content catalog wants regardless — it groups by what a page
234
- _is_ rather than by where its file happened to be written — and it is what
235
- `sohl`'s catalog layouts already do.
236
-
237
- A site rendering through a **shared theme** has no layout of its own to edit. As
238
- of 13.1.0 the section declares its query instead, and the theme runs it:
239
-
240
- ```yaml
241
- sections:
242
- weapongear: { title: Weapons, listType: weapongear }
243
- user-guide: { title: User Guide, listType: doc, listSubType: userguide }
244
- ```
245
-
246
- `listType` is the content type, not the section's name — the two need not agree,
247
- and `listSubType` narrows a type whose genres share it. See
248
- [What a section may declare](CONTENT.md#what-a-section-may-declare).
249
-
250
- ## What did not change
251
-
252
- - Every content page's `url:`, and so every published address.
253
- - Every link-manifest entry, including each entry's `path`.
254
- - Every compiled compendium document.
255
- - `site.sections` / `site.readmeSections` and what an entry may declare.
256
-
257
- # Migrating to `@heroiclands/package-build` 11.0.0
258
-
259
- **Two edits, and the second is one line per repository.** Every published page
260
- moves to its address (#181), and the package homepage becomes an ordinary
261
- addressed note (#182).
262
-
263
- ## 1. A page's URL is its address
264
-
265
- Every content page now serves at `/<package>/<type>-<shortcode>/` rather than at
266
- a slug derived from `name.full`. No content edit is required for it — the
267
- address is computed from fields every note already declares — but **every
268
- published URL moves**, so anything holding one (an external link, a bookmark, a
269
- citation in another repository) has to be re-derived.
270
-
271
- ## 2. Give the homepage a `shortcode`
272
-
273
- The homepage used to **refuse** `shortcode` and `name`, because a URL derived
274
- from a display name while a homepage's destination was fixed. That premise is
275
- gone, so both fields are permitted and `shortcode` is **required**:
276
-
277
- ```markdown
278
- ---
279
- type: homepage
280
- shortcode: root
281
- title: HârnMaster Kethira Basic
282
- ---
283
- ```
284
-
285
- `root` is the convention, not a rule. Without one, `content-build lint` and
286
- `content-build site` both refuse the note:
287
-
288
- ```text
289
- assets/content/homepage.md:3:7: error: a `type: homepage` note declares a `shortcode`, like every other note: it is addressed as `homepage-<shortcode>` and published at `/<package>/homepage-<shortcode>/`, which is where `[[homepage-<shortcode>|Text]]` lands. Write `shortcode: root` — the package landing is `homepage-root` in every package
290
- ```
291
-
292
- `id` is still refused, on ground this does not touch: a homepage compiles into
293
- no compendium document.
294
-
295
- ## 3. Author the `/<package>/` redirect
296
-
297
- The landing is published at `/<package>/homepage-root/`, and nothing is written
298
- at `/<package>/` any more. Add the redirect to the repository's own
299
- `_redirects`, and pin its lifetime in `_headers` — Cloudflare Pages sets no
300
- `Cache-Control` on a redirect it generates, and an unpinned 301 is cacheable
301
- indefinitely:
302
-
303
- ```text
304
- # _redirects
305
- /sohl/ /sohl/homepage-root/ 301
306
- /sohl /sohl/homepage-root/ 301
307
- ```
308
-
309
- ```text
310
- # _headers
311
- /sohl/
312
- Cache-Control: max-age=3600
313
- /sohl
314
- Cache-Control: max-age=3600
315
- ```
316
-
317
- Both path forms, because Pages matches the raw path and `/sohl` and `/sohl/` are
318
- distinct keys. See `CONTENT.md` for why the pairing works and how to verify it
319
- after a deploy.
320
-
321
- # Migrating to `@heroiclands/package-build` 6.0.0
322
-
323
- **No configuration change to make, and one build check that may now fail.**
324
- `packageBuild.manifest.packFolders` is compared against the `packs[]` the build
325
- derives, and a folder naming a pack the package does not ship is an error.
326
-
327
- ## 1. Check what `package-build manifest` says
328
-
329
- Nothing to edit up front — run it, and the build names anything wrong:
330
-
331
- ```text
332
- package-build.config.yaml:164:23: error: packFolders: folder "HârnMaster 3 System" names pack "character", which this package does not ship (packs: items, system-help)
333
- ```
334
-
335
- Every name in a folder's `packs` must appear in the top-level `packs:` list —
336
- companions included, since Foundry sees no difference. Delete a name that no
337
- longer resolves, or correct it. An error stops the manifest being written, so
338
- nothing half-right reaches the stage.
339
-
340
- ## 2. The advisory needs no action
341
-
342
- A pack no folder names is a **warning**, and the build continues:
343
-
344
- ```text
345
- package-build.config.yaml:162:13: warning: packFolders: pack "items" is named by no folder, so it ships outside every folder this package declares
346
- ```
347
-
348
- Shipping one pack at the root can be deliberate, so this is not an error. But a
349
- package that bothered to declare a folder rarely meant to leave one out — that
350
- is exactly how `HarnMaster-3-FoundryVTT` shipped 1,577 of 1,597 documents loose
351
- beside its folder. Add the pack to a folder, or leave it and take the advisory.
352
-
353
- A package that declares no `packFolders` at all is unaffected, and says nothing.
354
-
355
- ## 3. Nothing else
356
-
357
- - **No configuration key changed**, and no CLI command, flag or exit code beyond
358
- `manifest` failing on the error above.
359
- - `writeManifest` takes an optional `configFile`, so a finding names the line it
360
- is about. Omitting it costs the position, not the finding.
361
-
362
- # Migrating to `@heroiclands/package-build` 5.0.0
363
-
364
- **One configuration change: `publish.site` is a mode, not a boolean.** And one
365
- new authoring capability that needs no migration: a `type: homepage` note.
366
-
367
- ## 1. Respell `publish.site`
368
-
369
- ```yaml
370
- publish:
371
- site: content # was `site: true`
372
- ```
373
-
374
- ```yaml
375
- publish:
376
- site: homepage # was `site: false`, or absent
377
- ```
378
-
379
- `homepage` is the default, so a repository that never set the key needs no edit.
380
- A repository that set it to either boolean gets a `TypeError` at load naming the
381
- mode to write:
382
-
383
- ```text
384
- 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.
385
- ```
386
-
387
- Both spellings are **refused rather than mapped** onto the nearest mode. `false`
388
- read as _this package has no web presence_, which now describes no package at
389
- all, and a value silently reinterpreted reads to its author as though it still
390
- means what it said.
391
-
392
- Nothing else about publishing moved: `publish.address`, `publish.manifests` and
393
- the whole `site:` block are unchanged, and every address `sohl` and `thalorna`
394
- already publish is byte-identical across the upgrade.
395
-
396
- ## 2. Author a homepage (optional here, required by #52)
397
-
398
- Every package is reachable at `https://www.heroiclands.org/<contentPackage>/`,
399
- and the page there is a note in the content tree:
400
-
401
- ```markdown
402
- ---
403
- type: homepage
404
- title: HârnMaster Kethira Basic # optional; defaults to packageBuild.manifest.title
405
- ---
406
-
407
- What the module is, which system it needs, how to install it.
408
- ```
409
-
410
- It compiles into no compendium document, appears in no pack and in no link
411
- manifest, and is addressed by the package rather than by its own name. It is
412
- written to the root of `site.out`, one level above the content mount.
413
-
414
- A repository with no homepage note publishes none, and the site build says so in
415
- its count. Requiring exactly one is a separate change (#52).
416
-
417
- ## 3. What homepage-only means
418
-
419
- `homepage` mode does not merely leave the content configuration unused — it
420
- **fences the content surfaces off**. The tree is never walked for pages, and
421
- `site.sections`, `site.trees`, `site.landing` and `site.backfillSections` emit
422
- nothing even when they are declared.
423
-
424
- That is deliberate, and it is a licensing requirement rather than a preference.
425
- `sohl-kethira-basic` (Keléstia Productions' Fan Material Guidelines) and
426
- `harn-adventures` (HârnFanon under Lythia's terms) publish a homepage and no
427
- other page; the failure mode is silent — a `site:` block added later ships
428
- licensed content with nobody noticing — so the property is asserted by the code
429
- path rather than left to configuration.
430
-
431
- `publish.manifests.publish` is a separate decision and stays `false` for both: a
432
- link manifest is the dependency edge that would stop a module being withdrawable,
433
- and a homepage is not.
434
-
435
- # Migrating to `@heroiclands/package-build` 4.0.0
436
-
437
- **One authoring change: delete `package:` from every content note.** A note's
438
- package is the repository's configured `contentPackage`, and declaring the field
439
- is now a build error rather than a redundancy the build tolerated.
440
-
441
- ## 1. Sweep the content tree
442
-
443
- The field is a whole line, and nothing else reads it:
444
-
445
- ```bash
446
- find assets/content -name '*.md' -print0 | xargs -0 sed -i '' '/^package: /d'
447
- ```
448
-
449
- (GNU `sed`: `sed -i '/^package: /d'`.) Then compile — `content-build package
450
- compile` must produce byte-identical output to the run before the sweep, because
451
- the value the build derives is the value the notes restated.
452
-
453
- A note that still carries the field fails the build where it is:
454
-
455
- ```text
456
- 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.
457
- ```
458
-
459
- `content-build lint` reports every one of them in a single pass, so the sweep can
460
- be checked before it is compiled.
461
-
462
- ## 2. Nothing else
463
-
464
- - **`contentPackage` stays**, and is unchanged. It is the address namespace —
465
- the first segment of every canonical key, the name of the emitted link
466
- manifest, and the package a cross-package wikilink writes. Every address in
467
- every manifest is identical across this upgrade.
468
- - **A generated table's `WHERE … and package = "<pkg>"` clause keeps matching.**
469
- The package is synthesised into what the table search sees, from
470
- `contentPackage`; it was never the authored field that answered the clause
471
- after 3.3.0.
472
- - **No configuration key changed**, and no CLI command, flag or exit code.
473
-
474
- ## What this replaced
475
-
476
- A note used to be _selected_ by the field: it compiled when `package:` matched
477
- `contentPackage` and was skipped, silently and as "belongs to another pass",
478
- when it did not. A tree whose notes named a package no configuration answered to
479
- compiled **zero notes and exited 0** (#56). 3.3.0 made the field optional so
480
- every repository could be swept on a non-breaking version; this major removes it.
481
-
482
- # Migrating to `@heroiclands/package-build` 3.0.0
483
-
484
- `@heroiclands/content-build` and `@heroiclands/package-build` are one package.
485
- The content half now ships inside `@heroiclands/package-build` at 3.0.0;
486
- `@heroiclands/content-build` is deprecated and receives no further releases.
487
-
488
- Nothing about how a build _works_ changed. This is a packaging change: the same
489
- modules, the same CLI commands, the same configuration keys, reachable under one
490
- name.
491
-
492
- ## Why
493
-
494
- The two packages split by input — content-build read `assets/content/**`,
495
- package-build read `lang/`, `styles/`, `src/` and the manifest template — on the
496
- theory that a module would use one or the other. No consumer ever did. All three
497
- installed both, and package-build depended on content-build besides, so the
498
- packaging half dragged the content half in regardless.
499
-
500
- What the boundary cost was real: one configuration file with two owners, two
501
- CLIs with a colliding `manifest` command, and a two-repository dance for changes
502
- that touched a single idea.
503
-
504
- ## 1. Dependencies
505
-
506
- Drop `@heroiclands/content-build` and move to 3.0.0:
507
-
508
- ```diff
509
- "devDependencies": {
510
- - "@heroiclands/content-build": "^1.8.2",
511
- - "@heroiclands/package-build": "^0.6.1"
512
- + "@heroiclands/package-build": "^3.0.0"
513
- }
514
- ```
515
-
516
- ## 2. Import specifiers
517
-
518
- Every `@heroiclands/content-build/*` specifier becomes
519
- `@heroiclands/package-build/*`. Subpaths are otherwise unchanged — `engine/*`,
520
- `sohl/*`, `prettier` and `markdownlint` all keep their names:
521
-
522
- ```diff
523
- -import { positionOfLiteral } from "@heroiclands/content-build/engine/diagnostics";
524
- +import { positionOfLiteral } from "@heroiclands/package-build/engine/diagnostics";
525
- ```
526
-
527
- **One subpath moved.** Both packages exported a `./config`, meaning different
528
- things, so the content one is now `./content-config`. `./config` remains the
529
- packaging configuration it always was, and consumers of it need no change:
530
-
531
- ```diff
532
- -import { defineConfig } from "@heroiclands/content-build/config";
533
- +import { defineConfig } from "@heroiclands/package-build/content-config";
534
- ```
535
-
536
- `defineConfig` is also re-exported from the package root, so
537
- `import { defineConfig } from "@heroiclands/package-build"` works too.
538
-
539
- A mechanical pass over a consumer:
540
-
541
- ```bash
542
- git ls-files -z '*.mjs' '*.ts' | xargs -0 perl -pi -e \
543
- 's{\@heroiclands/content-build/config}{\@heroiclands/package-build/content-config}g;
544
- s{\@heroiclands/content-build}{\@heroiclands/package-build}g'
545
- ```
546
-
547
- Review the result rather than trusting it — a changelog or a historical comment
548
- that names the old package is usually meant to keep naming it.
549
-
550
- ## 3. The CLI
551
-
552
- **Both commands still exist and behave identically.** `content-build` and
553
- `package-build` are both bin entries of the merged package, so scripts calling
554
- either keep working:
555
-
556
- ```jsonc
557
- "scripts": {
558
- "build:compiledb": "content-build package compile", // unchanged
559
- "build:manifest": "package-build manifest" // unchanged
560
- }
561
- ```
562
-
563
- Unifying the two into one noun-namespaced CLI — and resolving the `manifest`
564
- collision, where `content-build manifest` emits the cross-package link manifest
565
- and `package-build manifest` generates `module.json` — is deliberately _not_
566
- part of this release. It is a behavior change and gets its own.
567
-
568
- ## 4. Rename the configuration file
569
-
570
- **Required.** The config stem follows the package:
571
-
572
- ```bash
573
- git mv content-build.config.yaml package-build.config.yaml # or .yml / .mjs
574
- ```
575
-
576
- The old stem is **not** accepted. A deprecation window would let a repository
577
- sit indefinitely on a filename naming a package that no longer exists, and this
578
- upgrade already requires touching the manifest and the imports — one more `git
579
- mv` is not what makes it expensive.
580
-
581
- If a build cannot find the file it says so by name:
582
-
583
- ```
584
- package-build: no package-build.config.yaml or package-build.config.yml or
585
- package-build.config.mjs found at or above …
586
- ```
587
-
588
- Two configs in one directory is still an error rather than a precedence
589
- question, so a half-finished rename fails loudly instead of quietly building
590
- from the file you stopped editing.
591
-
592
- **Every key inside the file is unchanged**, including the `packageBuild:`
593
- section — which is no longer a block reserved for a separate toolchain, just a
594
- section.
595
-
596
- The `CONTENT_BUILD_CONFIG` environment variable, which names the file
597
- explicitly when a repository keeps it somewhere else, is now
598
- `PACKAGE_BUILD_CONFIG`. The old name is not read.
599
-
600
- ## What did not change
601
-
602
- - Every configuration key, and the shape of the whole file — only its name moved.
603
- - Every CLI command, flag and exit code.
604
- - Every engine and `sohl` module, and what it exports.
605
- - The Foundry manifest, the packs it declares, and every compiled document id.
606
-
607
- A world that resolved `Compendium.<package>.<pack>.<Type>.<id>` before resolves
608
- it after; nothing about compiled output moved.