@heroiclands/package-build 23.1.0 → 23.2.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 (248) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/bin/content-commands.mjs +68 -149
  3. package/bin/package-build.mjs +56 -10
  4. package/ci/check-banners.mjs +42 -6
  5. package/ci/confirm-npm-version.mjs +135 -0
  6. package/ci/content-format-reference.mjs +14 -5
  7. package/config.mjs +1 -0
  8. package/container.mjs +185 -4
  9. package/content-config.mjs +333 -210
  10. package/deploy.mjs +116 -10
  11. package/docs/api.md +238 -246
  12. package/docs/authoring/assets.md +11 -5
  13. package/docs/authoring/frontmatter.md +1 -1
  14. package/docs/authoring/links-and-markup.md +87 -0
  15. package/docs/commands.md +138 -99
  16. package/docs/configuration.md +277 -125
  17. package/docs/content-format.md +2 -0
  18. package/docs/diagnostics.md +48 -13
  19. package/docs/guides/book.md +341 -21
  20. package/docs/guides/e2e.md +13 -2
  21. package/docs/guides/icons.md +14 -4
  22. package/docs/guides/module-project.md +18 -8
  23. package/docs/guides/release-and-deploy.md +54 -6
  24. package/docs/guides/site.md +5 -2
  25. package/docs/project-setup.md +30 -10
  26. package/docs/reference/format-details.md +373 -170
  27. package/docs/reference/note-types.md +128 -109
  28. package/docs/reference/theme-pages.md +31 -6
  29. package/e2e.mjs +140 -28
  30. package/engine/actor-compiler.mjs +22 -31
  31. package/engine/address-charset.mjs +4 -17
  32. package/engine/address-fields.mjs +1 -1
  33. package/engine/address-render.mjs +12 -1
  34. package/engine/address-values.mjs +6 -0
  35. package/engine/address.mjs +46 -33
  36. package/engine/art-fields.mjs +30 -5
  37. package/engine/art-slots.mjs +10 -1
  38. package/engine/asset-bindings.mjs +12 -1
  39. package/engine/base-compiler.mjs +34 -65
  40. package/engine/being-age.mjs +2 -7
  41. package/engine/being-measurements.mjs +12 -1
  42. package/engine/calendar-artifacts.mjs +6 -0
  43. package/engine/calendar-choice.mjs +12 -1
  44. package/engine/calendar-format.mjs +12 -1
  45. package/engine/calendar-human.mjs +12 -1
  46. package/engine/compile-corpus.mjs +3 -1
  47. package/engine/content-address.mjs +9 -12
  48. package/engine/content-alerts.mjs +13 -1
  49. package/engine/content-blocks.mjs +14 -3
  50. package/engine/content-embeds.mjs +11 -3
  51. package/engine/content-figures.mjs +9 -0
  52. package/engine/content-footnotes.mjs +12 -1
  53. package/engine/content-format-check.mjs +11 -262
  54. package/engine/content-format.yaml +5 -2
  55. package/engine/content-images.mjs +3 -0
  56. package/engine/content-index.mjs +443 -20
  57. package/engine/content-links.mjs +29 -35
  58. package/engine/content-lint.mjs +2 -3
  59. package/engine/content-poetry.mjs +12 -1
  60. package/engine/content-slug.mjs +2 -14
  61. package/engine/content-spans.mjs +13 -1
  62. package/engine/content-tables.mjs +12 -1
  63. package/engine/culture-choice.mjs +210 -13
  64. package/engine/data-keys.mjs +19 -16
  65. package/engine/date-conversion.mjs +12 -1
  66. package/engine/declaration-check.mjs +9 -0
  67. package/engine/derived-fields.mjs +6 -7
  68. package/engine/diagnostics.mjs +34 -4
  69. package/engine/docs-checks.mjs +9 -0
  70. package/engine/document-subtypes.mjs +47 -34
  71. package/engine/event-views.mjs +139 -14
  72. package/engine/exported-scene.mjs +92 -28
  73. package/engine/extension-attributes.mjs +12 -1
  74. package/engine/field-spec.mjs +11 -102
  75. package/engine/folder-notes.mjs +0 -64
  76. package/engine/foreign-catalog.mjs +2 -1
  77. package/engine/format-generated.mjs +9 -0
  78. package/engine/foundry-entries.mjs +10 -28
  79. package/engine/frontmatter-lint.mjs +83 -392
  80. package/engine/frontmatter.mjs +20 -70
  81. package/engine/generate.mjs +10 -15
  82. package/engine/heading-attributes.mjs +12 -1
  83. package/engine/helpers.mjs +41 -102
  84. package/engine/holdings.mjs +1 -1
  85. package/engine/homepage.mjs +3 -10
  86. package/engine/ids.mjs +1 -167
  87. package/engine/index-records.mjs +6 -4
  88. package/engine/index.mjs +0 -3
  89. package/engine/infobox-render.mjs +15 -3
  90. package/engine/infobox.mjs +13 -14
  91. package/engine/item-compiler.mjs +4 -10
  92. package/engine/item-docs.mjs +3 -5
  93. package/engine/item-registry.mjs +2 -10
  94. package/engine/journals.mjs +1 -5
  95. package/engine/literature-notes.mjs +4 -12
  96. package/engine/macros.mjs +1 -2
  97. package/engine/map-graphviz.mjs +12 -1
  98. package/engine/markdown-expressions.mjs +12 -1
  99. package/engine/markdownlint-run.mjs +567 -0
  100. package/engine/metadata-index.mjs +42 -12
  101. package/engine/note-addresses.mjs +143 -35
  102. package/engine/note-claims.mjs +14 -28
  103. package/engine/note-dates.mjs +3 -3
  104. package/engine/note-frontmatter.mjs +6 -0
  105. package/engine/note-ids.mjs +8 -14
  106. package/engine/note-package.mjs +8 -25
  107. package/engine/note-state.mjs +1 -3
  108. package/engine/note-vocabulary.mjs +148 -84
  109. package/engine/number-words.mjs +12 -1
  110. package/engine/pack-config.mjs +15 -25
  111. package/engine/pack-router.mjs +10 -16
  112. package/engine/pathnames.mjs +8 -22
  113. package/engine/pdf-build.mjs +773 -121
  114. package/engine/pdf-coverage.mjs +188 -0
  115. package/engine/pdf-index.mjs +54 -7
  116. package/engine/pdf-pages.mjs +247 -0
  117. package/engine/pdf-render.mjs +114 -4
  118. package/engine/pdf-toc.mjs +29 -32
  119. package/engine/project-init.mjs +46 -6
  120. package/engine/prose-config.mjs +1 -1
  121. package/engine/prose-lint.mjs +52 -88
  122. package/engine/readability-lint.mjs +9 -0
  123. package/engine/reckoning-markers.mjs +12 -1
  124. package/engine/reference-targets.mjs +7 -0
  125. package/engine/runtime-only-fields.mjs +5 -8
  126. package/engine/scene-levels.mjs +2 -2
  127. package/engine/scenes.mjs +1 -1
  128. package/engine/shortcode-references.mjs +7 -0
  129. package/engine/site-build.mjs +30 -16
  130. package/engine/site-index.mjs +10 -16
  131. package/engine/site-preview.mjs +6 -0
  132. package/engine/site-root.mjs +8 -1
  133. package/engine/social-tie-terms.mjs +12 -1
  134. package/engine/social-ties.mjs +3 -0
  135. package/engine/sql-tables.mjs +56 -20
  136. package/engine/standing-terms.mjs +11 -1
  137. package/engine/standings.mjs +11 -27
  138. package/engine/system-block.mjs +39 -141
  139. package/engine/system-vocabulary.mjs +6 -0
  140. package/engine/walk-files.mjs +69 -0
  141. package/engine/web-wikilinks.mjs +23 -3
  142. package/engine/wikilink-syntax.mjs +32 -21
  143. package/engine/wikilinks.mjs +27 -16
  144. package/engine/yaml-lint.mjs +7 -4
  145. package/githooks/attribution.mjs +8 -0
  146. package/githooks/commit-message.mjs +9 -0
  147. package/hm3/actors.mjs +9 -48
  148. package/hm3/default-item-art.mjs +9 -9
  149. package/hm3/item-fields.mjs +2 -9
  150. package/hm3/template-priority.mjs +6 -7
  151. package/hugo-theme/data/banner-kinds.yaml +78 -0
  152. package/hugo-theme/data/banners.yaml +2 -3
  153. package/hugo-theme/layouts/404.html +3 -3
  154. package/hugo-theme/layouts/partials/banner-url.html +31 -7
  155. package/hugo-theme/layouts/partials/hero-banner.html +13 -14
  156. package/hugo-theme/layouts/partials/related.html +1 -1
  157. package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
  158. package/hugo-theme/static/css/style.css +1 -1
  159. package/manifest.mjs +11 -4
  160. package/markdownlint-config.mjs +1 -1
  161. package/package.json +6 -3
  162. package/release.mjs +83 -18
  163. package/sohl/actors.mjs +16 -27
  164. package/sohl/default-item-art.mjs +3 -3
  165. package/sohl/document-subtypes.mjs +1 -16
  166. package/sohl/infobox.mjs +2 -3
  167. package/sohl/item-builders.mjs +5 -8
  168. package/sohl/item-fields.mjs +5 -35
  169. package/sohl/items.mjs +0 -4
  170. package/sohl/kb-passes.mjs +3 -3
  171. package/types/container.d.mts +116 -5
  172. package/types/content-config.d.mts +83 -35
  173. package/types/deploy.d.mts +42 -6
  174. package/types/e2e.d.mts +35 -3
  175. package/types/engine/address-charset.d.mts +4 -17
  176. package/types/engine/address.d.mts +17 -8
  177. package/types/engine/art-fields.d.mts +7 -4
  178. package/types/engine/art-slots.d.mts +6 -0
  179. package/types/engine/base-compiler.d.mts +15 -17
  180. package/types/engine/content-address.d.mts +9 -12
  181. package/types/engine/content-embeds.d.mts +4 -2
  182. package/types/engine/content-format-check.d.mts +0 -64
  183. package/types/engine/content-index.d.mts +82 -3
  184. package/types/engine/content-links.d.mts +2 -4
  185. package/types/engine/culture-choice.d.mts +51 -8
  186. package/types/engine/data-keys.d.mts +2 -2
  187. package/types/engine/derived-fields.d.mts +1 -2
  188. package/types/engine/diagnostics.d.mts +27 -4
  189. package/types/engine/document-subtypes.d.mts +12 -31
  190. package/types/engine/event-views.d.mts +14 -2
  191. package/types/engine/exported-scene.d.mts +3 -2
  192. package/types/engine/field-spec.d.mts +13 -122
  193. package/types/engine/folder-notes.d.mts +0 -39
  194. package/types/engine/foreign-catalog.d.mts +2 -1
  195. package/types/engine/foundry-entries.d.mts +4 -5
  196. package/types/engine/frontmatter-lint.d.mts +2 -2
  197. package/types/engine/frontmatter.d.mts +19 -45
  198. package/types/engine/generate.d.mts +1 -1
  199. package/types/engine/helpers.d.mts +25 -42
  200. package/types/engine/homepage.d.mts +3 -10
  201. package/types/engine/ids.d.mts +0 -114
  202. package/types/engine/index-records.d.mts +6 -4
  203. package/types/engine/index.d.mts +0 -1
  204. package/types/engine/infobox-render.d.mts +7 -1
  205. package/types/engine/infobox.d.mts +3 -2
  206. package/types/engine/item-docs.d.mts +1 -1
  207. package/types/engine/item-registry.d.mts +2 -4
  208. package/types/engine/literature-notes.d.mts +1 -1
  209. package/types/engine/markdownlint-run.d.mts +40 -0
  210. package/types/engine/metadata-index.d.mts +9 -1
  211. package/types/engine/note-addresses.d.mts +27 -2
  212. package/types/engine/note-claims.d.mts +3 -5
  213. package/types/engine/note-ids.d.mts +2 -4
  214. package/types/engine/note-package.d.mts +1 -1
  215. package/types/engine/note-state.d.mts +1 -3
  216. package/types/engine/note-vocabulary.d.mts +28 -21
  217. package/types/engine/pack-config.d.mts +2 -6
  218. package/types/engine/pack-router.d.mts +4 -4
  219. package/types/engine/pathnames.d.mts +3 -3
  220. package/types/engine/pdf-build.d.mts +98 -12
  221. package/types/engine/pdf-coverage.d.mts +60 -0
  222. package/types/engine/pdf-index.d.mts +34 -5
  223. package/types/engine/pdf-pages.d.mts +143 -0
  224. package/types/engine/pdf-render.d.mts +45 -1
  225. package/types/engine/pdf-toc.d.mts +12 -14
  226. package/types/engine/prose-config.d.mts +1 -1
  227. package/types/engine/prose-lint.d.mts +11 -10
  228. package/types/engine/runtime-only-fields.d.mts +2 -2
  229. package/types/engine/site-build.d.mts +9 -5
  230. package/types/engine/site-index.d.mts +4 -5
  231. package/types/engine/site-root.d.mts +1 -1
  232. package/types/engine/sql-tables.d.mts +31 -2
  233. package/types/engine/standing-terms.d.mts +2 -1
  234. package/types/engine/standings.d.mts +7 -9
  235. package/types/engine/system-block.d.mts +10 -44
  236. package/types/engine/walk-files.d.mts +15 -0
  237. package/types/engine/wikilink-syntax.d.mts +18 -19
  238. package/types/engine/yaml-lint.d.mts +5 -4
  239. package/types/hm3/actors.d.mts +1 -2
  240. package/types/hm3/item-fields.d.mts +2 -4
  241. package/types/hm3/template-priority.d.mts +6 -7
  242. package/types/manifest.d.mts +3 -2
  243. package/types/release.d.mts +12 -7
  244. package/types/sohl/actors.d.mts +2 -1
  245. package/types/sohl/document-subtypes.d.mts +1 -2
  246. package/types/sohl/item-fields.d.mts +3 -7
  247. package/engine/retired-fields.mjs +0 -631
  248. package/types/engine/retired-fields.d.mts +0 -354
package/docs/api.md CHANGED
@@ -98,26 +98,21 @@ console.log(addressSlug({ type: "weapongear", shortcode: "dagger" }));
98
98
 
99
99
  ### `engine.ids`
100
100
 
101
- Deterministic document ids, derived by hashing rather than stored, so compile passes that cannot see each other's output still agree on an id. Also holds the pack-name vocabulary — which content type compiles into which conventional pack and document type — and the retired/renamed type tables every type-keyed lookup normalizes through first.
102
-
103
- | Export | Signature | Returns | Use it when |
104
- | ---------------------- | ----------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
105
- | `makeId` | `makeId(namespace, value)` | `string` — a 16-character hexadecimal Foundry id | deriving a stable id from a namespace and a value, e.g. a heading id when none is supplied |
106
- | `MAP_TYPES` | `const MAP_TYPES` | — | enumerating the content types that compile into a Foundry `Scene` |
107
- | `isMapType` | `isMapType(type)` | `boolean` | asking whether a content note's type compiles into a Scene |
108
- | `MAP_SUBTYPES` | `const MAP_SUBTYPES` | — | reading the map subTypes, each of which carries an exported Scene |
109
- | `JOURNAL_TYPES` | `const JOURNAL_TYPES` | — | enumerating content types whose whole document _is_ a JournalEntry |
110
- | `NO_PACK` | `const NO_PACK` | — | spelling the `pack:` value (`none`) that routes a note's document into no compendium |
111
- | `PACK_BY_TYPE` | `const PACK_BY_TYPE` | — | looking up the conventional pack name and document type for a content type |
112
- | `RETIRED_TYPES` | `const RETIRED_TYPES` | — | looking up what a retired content type was replaced by |
113
- | `assertTypeNotRetired` | `assertTypeNotRetired(type, where)` | throws | refusing a note whose type has been retired outright |
114
- | `RENAMED_TYPES` | `const RENAMED_TYPES` | — | looking up what a renamed content type is now called |
115
- | `currentType` | `currentType(type)` | the current spelling | normalizing a type to its current spelling before any keyed lookup |
116
- | `renamedTypeMessage` | `renamedTypeMessage(retired, current, where)` | `string` — the finding message | building the one message every reporter of a renamed type shares |
117
- | `ITEM_PACK` | `const ITEM_PACK` | — | naming the pack every open-set item type compiles into by default |
118
- | `packForType` | `packForType(type)` | `{pack, docType}` | resolving the pack and document type an item type's documents live in |
119
- | `compendiumUuid` | `compendiumUuid(packageId, type, id, packName)` | `string` — `Compendium.<packageId>.<pack>.<DocumentType>.<id>` | composing a document's full compendium UUID in the one place it is spelled |
120
- | `pageUuid` | `pageUuid(entryUuid, pageId)` | `string` — the page's UUID | composing the UUID of a JournalEntry page |
101
+ Deterministic document ids, derived by hashing rather than stored, so compile passes that cannot see each other's output still agree on an id. Also holds the pack-name vocabulary — which content type compiles into which conventional pack and document type.
102
+
103
+ | Export | Signature | Returns | Use it when |
104
+ | ---------------- | ----------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
105
+ | `makeId` | `makeId(namespace, value)` | `string` — a 16-character hexadecimal Foundry id | deriving a stable id from a namespace and a value, e.g. a heading id when none is supplied |
106
+ | `MAP_TYPES` | `const MAP_TYPES` | — | enumerating the content types that compile into a Foundry `Scene` |
107
+ | `isMapType` | `isMapType(type)` | `boolean` | asking whether a content note's type compiles into a Scene |
108
+ | `MAP_SUBTYPES` | `const MAP_SUBTYPES` | — | reading the map subTypes, each of which carries an exported Scene |
109
+ | `JOURNAL_TYPES` | `const JOURNAL_TYPES` | — | enumerating content types whose whole document _is_ a JournalEntry |
110
+ | `NO_PACK` | `const NO_PACK` | — | spelling the `pack:` value (`none`) that routes a note's document into no compendium |
111
+ | `PACK_BY_TYPE` | `const PACK_BY_TYPE` | — | looking up the conventional pack name and document type for a content type |
112
+ | `ITEM_PACK` | `const ITEM_PACK` | — | naming the pack every open-set item type compiles into by default |
113
+ | `packForType` | `packForType(type)` | `{pack, docType}` | resolving the pack and document type an item type's documents live in |
114
+ | `compendiumUuid` | `compendiumUuid(packageId, type, id, packName)` | `string` — `Compendium.<packageId>.<pack>.<DocumentType>.<id>` | composing a document's full compendium UUID in the one place it is spelled |
115
+ | `pageUuid` | `pageUuid(entryUuid, pageId)` | `string` — the page's UUID | composing the UUID of a JournalEntry page |
121
116
 
122
117
  ### `engine.formatGenerated`
123
118
 
@@ -131,32 +126,31 @@ can write the result directly without introducing formatting drift.
131
126
 
132
127
  ### `engine.systemBlock`
133
128
 
134
- The per-system frontmatter block: how one note feeds more than one game system through properties named after that system (`<system>.system`, `<system>.type`, `<system>.img`, `<system>.items` on actors). Resolves a field's value through the block, the shared top level, and a retiring position in that order, and merges an authored `<system>.system` onto a compiler-built one without disturbing what the builder already wrote.
135
-
136
- | Export | Signature | Returns | Use it when |
137
- | --------------------------- | --------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
138
- | `SYSTEM_DATA_KEY` | `const SYSTEM_DATA_KEY` | — | naming the key inside a system block whose contents are the system's own vocabulary |
139
- | `BLOCK_DOCUMENT_PROPERTIES` | `const BLOCK_DOCUMENT_PROPERTIES` | — | mapping a system-block key to the document property it becomes |
140
- | `BLOCK_DIRECTIVES` | `const BLOCK_DIRECTIVES` | — | recognizing a build directive (e.g. `pack`) that names no document property |
141
- | `SYSTEM_BLOCK_KEYS` | `const SYSTEM_BLOCK_KEYS` | — | enumerating every key any system block may carry |
142
- | `setPath` | `setPath(target, dotted, value)` | `object` — `target`, for chaining | writing a value at a dotted path in a document's `system` block |
143
- | `systemBlock` | `systemBlock(fm, block)` | `Record<string, unknown> \| undefined` | reading one system's whole block off a note, or nothing when it is absent or malformed |
144
- | `carriesSystemBlock` | `carriesSystemBlock(fm, block)` | `boolean` | deciding whether a pack that declares a system should compile a note at all |
145
- | `systemData` | `systemData(fm, block)` | `Record<string, unknown>` | reading a system block's `system` sub-block, `{}` when absent |
146
- | `blockField` | `blockField(fm, block, key, defaultValue)` | the value | reading a key from one system's block, falling back to the top level |
147
- | `sharedProperty` | `sharedProperty(fm, source, defaultValue)` | the value | reading a shared top-level property by a possibly-dotted path, blind to every system block |
148
- | `blockProperty` | `blockProperty(fm, block, key, defaultValue)` | the value | reading a property a system block may override, else the shared top-level one |
149
- | `resolveDataProperty` | `resolveDataProperty(fm, block, key, defaultValue)` | `{value, from}` | resolving a shared `data:` key every type accepts — a system override, then shared `data`, then a top-level value — and reporting which one answered |
150
- | `blockDataProperty` | `blockDataProperty(fm, block, key, defaultValue)` | the value | `resolveDataProperty`'s value alone, in the same order |
151
- | `legacyKeyOf` | `legacyKeyOf(field)` | `string \| undefined` | finding the key a field is authored at inside a system block |
152
- | `retiredTopLevelKey` | `retiredTopLevelKey(field)` | `string \| undefined` | finding the bare top-level key a `data:`-sourced field is being swept off |
153
- | `authoredKey` | `authoredKey(name)` | `string` | reading a shared source as the key an author writes, without the `data:` container holding it |
154
- | `resolveFieldValue` | `resolveFieldValue(field, fm, ...)` | `{value, from}` | resolving one declared field against a note, in the declared source order |
155
- | `systemDataPaths` | `systemDataPaths(data, prefix)` | `string[]` | listing every path a note authors under `<system>.system`, containers included |
156
- | `undeclaredPaths` | `undeclaredPaths(data, declared, prefix)` | `string[]` | finding authored paths a system's published schema does not declare |
157
- | `unknownBlockKeys` | `unknownBlockKeys(fm, block, ...)` | `string[]` | finding keys directly under a system block that neither this format nor the system recognizes |
158
- | `claimedPaths` | `claimedPaths(fields)` | `Set<string>` | listing the exact `system` paths a field declaration writes, for `mergeSystemData` |
159
- | `mergeSystemData` | `mergeSystemData(built, fm, ...)` | `object` — `built`, for chaining | merging a note's `<system>.system` verbatim onto a built `system` block |
129
+ The per-system frontmatter block: how one note feeds more than one game system through properties named after that system (`<system>.system`, `<system>.type`, `<system>.img`, `<system>.items` on actors). Resolves a field's value through the destination, the block and the declared shared source in that order, and merges an authored `<system>.system` onto a compiler-built one without disturbing what the builder already wrote.
130
+
131
+ | Export | Signature | Returns | Use it when |
132
+ | --------------------------- | --------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
133
+ | `SYSTEM_DATA_KEY` | `const SYSTEM_DATA_KEY` | — | naming the key inside a system block whose contents are the system's own vocabulary |
134
+ | `BLOCK_DOCUMENT_PROPERTIES` | `const BLOCK_DOCUMENT_PROPERTIES` | — | mapping a system-block key to the document property it becomes |
135
+ | `BLOCK_DIRECTIVES` | `const BLOCK_DIRECTIVES` | — | recognizing a build directive (e.g. `pack`) that names no document property |
136
+ | `SYSTEM_BLOCK_KEYS` | `const SYSTEM_BLOCK_KEYS` | — | enumerating every key any system block may carry |
137
+ | `setPath` | `setPath(target, dotted, value)` | `object` — `target`, for chaining | writing a value at a dotted path in a document's `system` block |
138
+ | `systemBlock` | `systemBlock(fm, block)` | `Record<string, unknown> \| undefined` | reading one system's whole block off a note, or nothing when it is absent or malformed |
139
+ | `carriesSystemBlock` | `carriesSystemBlock(fm, block)` | `boolean` | deciding whether a pack that declares a system should compile a note at all |
140
+ | `systemData` | `systemData(fm, block)` | `Record<string, unknown>` | reading a system block's `system` sub-block, `{}` when absent |
141
+ | `blockField` | `blockField(fm, block, key, defaultValue)` | the value | reading a key from one system's block, falling back to the top level |
142
+ | `sharedProperty` | `sharedProperty(fm, source, defaultValue)` | the value | reading a shared top-level property by a possibly-dotted path, blind to every system block |
143
+ | `blockProperty` | `blockProperty(fm, block, key, defaultValue)` | the value | reading a property a system block may override, else the shared top-level one |
144
+ | `resolveDataProperty` | `resolveDataProperty(fm, block, key, defaultValue)` | `{value, from}` | resolving a shared `data:` key every type accepts — a system override, then shared `data` — and reporting which one answered |
145
+ | `blockDataProperty` | `blockDataProperty(fm, block, key, defaultValue)` | the value | `resolveDataProperty`'s value alone, in the same order |
146
+ | `inBlockKey` | `inBlockKey(field)` | `string \| undefined` | finding the key a field is authored at inside a system block — none for a name that is a path into `data:` |
147
+ | `authoredKey` | `authoredKey(name)` | `string` | reading a shared source as the key an author writes, without the `data:` container holding it |
148
+ | `resolveFieldValue` | `resolveFieldValue(field, fm, ...)` | `{value, from}` | resolving one declared field against a note, in the declared source order |
149
+ | `systemDataPaths` | `systemDataPaths(data, prefix)` | `string[]` | listing every path a note authors under `<system>.system`, containers included |
150
+ | `undeclaredPaths` | `undeclaredPaths(data, declared, prefix)` | `string[]` | finding authored paths a system's published schema does not declare |
151
+ | `unknownBlockKeys` | `unknownBlockKeys(fm, block, ...)` | `string[]` | finding keys directly under a system block that neither this format nor the system recognizes |
152
+ | `claimedPaths` | `claimedPaths(fields)` | `Set<string>` | listing the exact `system` paths a field declaration writes, for `mergeSystemData` |
153
+ | `mergeSystemData` | `mergeSystemData(built, fm, ...)` | `object` — `built`, for chaining | merging a note's `<system>.system` verbatim onto a built `system` block |
160
154
 
161
155
  ### `engine.codeFences`
162
156
 
@@ -245,18 +239,17 @@ Which note types a configuration compiles at all, and the two findings that foll
245
239
 
246
240
  Folders, authored as notes rather than bespoke `*-folders.yaml` configuration, so a folder's parent is an address the link resolver already understands, its id is derived like any other document's, and an unclaimed folder is a finding like any other unclaimed note.
247
241
 
248
- | Export | Signature | Returns | Use it when |
249
- | ------------------------ | ----------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- |
250
- | `FOLDER_TYPE` | `const FOLDER_TYPE` | — | naming the note type a folder is authored as |
251
- | `FOLDER_ID_NAMESPACE` | `const FOLDER_ID_NAMESPACE` | — | naming the id namespace a derived folder id is hashed under |
252
- | `DEFAULT_PARENT` | `const DEFAULT_PARENT` | — | naming the key a per-pack `parent` map uses for "everywhere else" |
253
- | `bareAddress` | `bareAddress(value)` | `string \| null` | stripping wikilink brackets and a label off an authored `parent` value |
254
- | `folderAddress` | `folderAddress(pkg, shortcode)` | `string` — `<pkg>-none-folder-<shortcode>` | composing the canonical address of a folder note |
255
- | `folderDocId` | `folderDocId(pkg, shortcode)` | `string` — the folder's 16-character Foundry id | deriving the Foundry `_id` a folder note's documents file under |
256
- | `collectFolderNotes` | `collectFolderNotes(notes, pkg)` | `FolderNote[]` | collecting every folder note in a content tree, in walk order |
257
- | `buildFolderNoteIndex` | `buildFolderNoteIndex(folders)` | `{byKey, folders, resolve, ancestorsOf, ...}` | indexing folder notes by every form an author may address one by, checking invariants |
258
- | `folderDocument` | `folderDocument(folder, parent, documentType, stats)` | `object` — the Folder document | compiling the Foundry `Folder` document one folder note produces for one pack |
259
- | `assertNoDeclaredFolder` | `assertNoDeclaredFolder(fm, ...)` | throws | refusing a note that declares the retired `folder:` spelling |
242
+ | Export | Signature | Returns | Use it when |
243
+ | ---------------------- | ----------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- |
244
+ | `FOLDER_TYPE` | `const FOLDER_TYPE` | — | naming the note type a folder is authored as |
245
+ | `FOLDER_ID_NAMESPACE` | `const FOLDER_ID_NAMESPACE` | — | naming the id namespace a derived folder id is hashed under |
246
+ | `DEFAULT_PARENT` | `const DEFAULT_PARENT` | — | naming the key a per-pack `parent` map uses for "everywhere else" |
247
+ | `bareAddress` | `bareAddress(value)` | `string \| null` | stripping wikilink brackets and a label off an authored `parent` value |
248
+ | `folderAddress` | `folderAddress(pkg, shortcode)` | `string` — `<pkg>-none-folder-<shortcode>` | composing the canonical address of a folder note |
249
+ | `folderDocId` | `folderDocId(pkg, shortcode)` | `string` — the folder's 16-character Foundry id | deriving the Foundry `_id` a folder note's documents file under |
250
+ | `collectFolderNotes` | `collectFolderNotes(notes, pkg)` | `FolderNote[]` | collecting every folder note in a content tree, in walk order |
251
+ | `buildFolderNoteIndex` | `buildFolderNoteIndex(folders)` | `{byKey, folders, resolve, ancestorsOf, ...}` | indexing folder notes by every form an author may address one by, checking invariants |
252
+ | `folderDocument` | `folderDocument(folder, parent, documentType, stats)` | `object` — the Folder document | compiling the Foundry `Folder` document one folder note produces for one pack |
260
253
 
261
254
  ### `engine.contentPackage`
262
255
 
@@ -276,32 +269,9 @@ Which content package a note belongs to: the repository's configured `contentPac
276
269
  | `searchableFrontmatter` | `searchableFrontmatter(fm, configured)` | frontmatter with the derived package present | making a note's package visible to a generated table search, though no note declares one |
277
270
  | `assertNoDeclaredPackage` | `assertNoDeclaredPackage(fm, ...)` | throws | refusing a note that declares `package:` at all, present or empty |
278
271
 
279
- ### `engine.retiredFields`
280
-
281
- Frontmatter fields a note may no longer declare — `draft:`, a top-level `aliases:`, `section:`, a top-level `traits:` block, and any field with a renamed spelling or in-block position — each refused by presence alone, naming the file and line and saying what to write instead.
282
-
283
- | Export | Signature | Returns | Use it when |
284
- | ----------------------------- | ------------------------------------------------ | ------------------------ | -------------------------------------------------------------------------------------------------- |
285
- | `draftRetiredMessage` | `draftRetiredMessage(file)` | `string` | building the one message shared by the compile-time refusal and the frontmatter lint for `draft:` |
286
- | `assertNoDraftField` | `assertNoDraftField(fm, ...)` | throws | refusing a note that declares `draft:` at all |
287
- | `aliasesRetiredMessage` | `aliasesRetiredMessage(file)` | `string` | building the shared message for a top-level `aliases:` |
288
- | `assertNoAliasesField` | `assertNoAliasesField(fm, ...)` | throws | refusing a note that declares a top-level `aliases:` |
289
- | `declaresRetiredAliasesField` | `declaresRetiredAliasesField(fm)` | `boolean` | checking whether a note declares the retired top-level `aliases:`, distinct from `name.aliases` |
290
- | `sectionRetiredMessage` | `sectionRetiredMessage(file)` | `string` | building the shared message for `section:` |
291
- | `assertNoSectionField` | `assertNoSectionField(fm, ...)` | throws | refusing a note that declares `section:` at all |
292
- | `traitsRetiredMessage` | `traitsRetiredMessage(file)` | `string` | building the shared message for a top-level `traits:` block, stating where each key moved |
293
- | `assertNoTraitsField` | `assertNoTraitsField(fm, ...)` | throws | refusing a note that declares a top-level `traits:` block at all |
294
- | `locateFrontmatterKey` | `locateFrontmatterKey(absPath, key, value, ...)` | `{line?, column?}` | finding a frontmatter key's position in a note's file, by reading it |
295
- | `RETIRED_FIELD_ALIASES` | `const RETIRED_FIELD_ALIASES` | — | looking up the current field name a retired spelling was renamed to |
296
- | `retiredAliasMessage` | `retiredAliasMessage(retired, current, file)` | `string` | building the shared message for a note writing a renamed field |
297
- | `legacyKeyMessage` | `legacyKeyMessage(block, field, file)` | `string` | building the message for a field written at its legacy in-block position |
298
- | `retiredTopLevelMessage` | `retiredTopLevelMessage(field, file)` | `string` | building the message for a field written at the top-level key `data:` gathered it off |
299
- | `declaresRetiredAlias` | `declaresRetiredAlias(fm, current)` | `boolean` | checking whether a note writes the retired spelling of a field, in either region `sohlField` reads |
300
- | `readAliasedField` | `readAliasedField(fm, current)` | the value or `undefined` | reading a field that has a retired spelling, the current name winning |
301
-
302
272
  ### `engine.runtimeOnlyFields`
303
273
 
304
- Schema fields a note may never author, because the document writes them during play rather than at compile time — a permanent part of a type's schema that is simply not content, distinct from a retired field, which a note may not author either but for a different reason.
274
+ Schema fields a note may never author, because the document writes them during play rather than at compile time — a permanent part of a type's schema that is simply not content.
305
275
 
306
276
  | Export | Signature | Returns | Use it when |
307
277
  | --------------------------- | -------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------- |
@@ -429,15 +399,15 @@ The infobox declarations this toolchain ships, one per system, and the single ca
429
399
 
430
400
  Drawing a declared infobox: HTML for separate Foundry pages appended after authored pages, Typst panels after each note’s authored body in the book. The website's boxes travel in front matter and the site theme draws them, so there is no renderer for it here.
431
401
 
432
- | Export | Signature | Returns | Use it when |
433
- | ---------------------- | ------------------------------------------- | --------- | ------------------------------------------------------------------------ |
434
- | `infoboxTypstPreamble` | `infoboxTypstPreamble()` | `string` | emitting the panel definitions the book's bodies call, once per document |
435
- | `infoboxesToHtml` | `infoboxesToHtml(boxes, options)` | `string` | drawing the boxes as `<details>` disclosures, open by default |
436
- | `infoboxesToTypst` | `infoboxesToTypst(boxes, options)` | `string` | drawing the boxes as panels that break between their sections |
437
- | `linkToHtml` | `linkToHtml(value)` | `string` | drawing a link value as an anchor on its URL |
438
- | `linkToTypst` | `linkToTypst(value, links, labelFor, site)` | `string` | drawing a link value as a cross-reference into the book |
439
- | `linkToUuid` | `linkToUuid(value)` | `string` | drawing a link value as a Foundry document reference |
440
- | `sectionHasContent` | `sectionHasContent(section)` | `boolean` | deciding whether a section holds anything worth drawing |
402
+ | Export | Signature | Returns | Use it when |
403
+ | ---------------------- | ------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------- |
404
+ | `infoboxTypstPreamble` | `infoboxTypstPreamble()` | `string` | emitting the panel definitions the book's bodies call, once per document |
405
+ | `infoboxesToHtml` | `infoboxesToHtml(boxes, options)` | `string` | drawing the boxes as `<details>` disclosures, open by default |
406
+ | `infoboxesToTypst` | `infoboxesToTypst(boxes, options)` | `string` | drawing the boxes as panels that break between their sections |
407
+ | `linkToHtml` | `linkToHtml(value)` | `string` | drawing a link value as an anchor on its URL |
408
+ | `linkToTypst` | `linkToTypst(value, links, labelFor, site, elsewhere)` | `string` | drawing a link value as a cross-reference into the book, or as a book and page when another book prints it |
409
+ | `linkToUuid` | `linkToUuid(value)` | `string` | drawing a link value as a Foundry document reference |
410
+ | `sectionHasContent` | `sectionHasContent(section)` | `boolean` | deciding whether a section holds anything worth drawing |
441
411
 
442
412
  ### `engine.noteState`
443
413
 
@@ -695,13 +665,25 @@ The exact-day conversion functions use the corpus reckoning context. `dateFromCa
695
665
 
696
666
  ### `engine.cultureChoice`
697
667
 
698
- `checkCultureChoice(note, { index })` checks that a being's `data.culture`
699
- Address resolves to a lore note with `subType: culture`. The being vocabulary
700
- invokes the check during frontmatter lint.
701
-
702
- | Export | Signature | Returns | Use it when |
703
- | -------------------- | ----------------------------------- | -------- | ----------------------------------------- |
704
- | `checkCultureChoice` | `checkCultureChoice(note, options)` | findings | validating a being's primary culture note |
668
+ `data.culture` names the people a note belongs to, as a lore note with
669
+ `subType: culture`. `CULTURE_FIELD` is its one declaration, spread into the
670
+ `being`, `place`, `affiliation`, `lore` and `doc` vocabularies.
671
+ `checkCultureChoice(note, { index })` checks that the Address resolves to a
672
+ culture note. `checkCultureSubType(note, { index })` scopes the field by
673
+ subtype: a `doc` of `subType: settingguide` must state it, any other `doc`
674
+ subtype must not, and a `lore` note of `subType: culture` may state only its own
675
+ Address, compared in canonical form. The vocabulary invokes both during
676
+ frontmatter lint, which reads each note from its index record, so both see the
677
+ resolved value: the culture a place or affiliation inherits through
678
+ `data.parents`, written into the record by `resolveCultures`, documented under
679
+ `engine.contentIndex`.
680
+
681
+ | Export | Signature | Returns | Use it when |
682
+ | --------------------- | -------------------------------------- | --------- | ------------------------------------------------------------------------------------------- |
683
+ | `CULTURE_FIELD` | frozen field declaration | object | declaring `data.culture` on a type |
684
+ | `checkCultureChoice` | `checkCultureChoice(note, options)` | findings | validating that a note's culture names a culture note |
685
+ | `checkCultureSubType` | `checkCultureSubType(note, options)` | findings | requiring or refusing `data.culture` by the note's subtype |
686
+ | `cultureNamesItself` | `cultureNamesItself(fm, value, index)` | `boolean` | asking whether a `data.culture` value is the note's own Address, compared in canonical form |
705
687
 
706
688
  ### `engine.markdownExpressions`
707
689
 
@@ -830,22 +812,22 @@ The id a note's document is filed under: its pinned `id:`, when a note authors o
830
812
 
831
813
  The published content index — the artifact packages exchange addresses through. **A package publishes its own index; a consumer fetches the ones it depends on.** That is the whole mechanism, and it replaces a vendored link manifest that each repository committed a copy of every other repository's file into. Vendoring failed three ways, and only the last is about staleness:
832
814
 
833
- | Export | Signature | Returns | Use it when |
834
- | ------------------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
835
- | `METADATA_RELATIONSHIP_KINDS` | `const METADATA_RELATIONSHIP_KINDS` | — | The relationship kinds that are dependencies, and therefore citable. |
836
- | `metadataFileName` | `function metadataFileName(pkg)` | {string} The file name, e.g. | What a package's content index is called, wherever it is written or fetched. |
837
- | `metadataRelationships` | `function metadataRelationships(config)` | {Array<{id: string, manifest: string, kind: string, verified: string\|undefined}>} The dependencies, in declaration order. | Every dependency whose published index this build resolves addresses through. |
838
- | `noContentIndexPackages` | `function noContentIndexPackages(config)` | {ReadonlySet<string>} The content package names. | Every package a relationship declares `contentIndex: false` on, keyed by the content package name a link into it would use. |
839
- | `metadataCacheDir` | `function metadataCacheDir(config, id, version)` | {string} The directory. | The cache directory for one dependency's index at one version. |
840
- | `isComplete` | `const isComplete` | {boolean} True when it was fetched to completion. | Whether a dependency's cache is present and complete. |
841
- | `markComplete` | `function markComplete(dir)` | {void} | Mark a dependency's cache complete. |
842
- | `cachedMetadataFiles` | `function cachedMetadataFiles(config)` | {string[]} One index file per declared dependency. | The fetched index files this build resolves foreign addresses against. |
843
- | `cachedMetadataIndexes` | `function cachedMetadataIndexes(config)` | {Array<{id: string, file: string}>} One entry per declared dependency. | The same fetched indexes, each paired with the package that published it. |
844
- | `newestVersionDir` | `function newestVersionDir(dirs)` | {string} The newest one. | The newest cached version among several version-keyed cache directories. |
845
- | `loadForeignIndexes` | `function loadForeignIndexes(config, localPackages, bases` | `{{index: Map<string, object>, packages: Set<string>, stale: Array<{package: string, reason: string}>}}` The resolved addresses, which packages contributed, and what could not be read. | Resolve every foreign address this build can cite, from the fetched indexes. |
846
- | `cachedIndexPath` | `function cachedIndexPath(config, pkg)` | {string} A path to name in a diagnostic. | Where a dependency's fetched index sits, for naming it in a diagnostic. |
847
- | `unaddressableForeignPackages` | `function unaddressableForeignPackages(foreignIndex)` | {Array<{package: string, entries: number, sampleKey: string}>} One finding per drifted package, in the order the index first names each. | Whether a fetched index can still be _addressed_, as distinct from read. |
848
- | `formatUnaddressableFinding` | `function formatUnaddressableFinding(finding, config)` | {string} The formatted diagnostic, path first on the line. | One finding, in the standard `file:line:column: severity: message` form. |
815
+ | Export | Signature | Returns | Use it when |
816
+ | ------------------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
817
+ | `METADATA_RELATIONSHIP_KINDS` | `const METADATA_RELATIONSHIP_KINDS` | — | The relationship kinds that are dependencies, and therefore citable; a relationship of another kind declaring `itemCatalog: true` is read as well. |
818
+ | `metadataFileName` | `function metadataFileName(pkg)` | {string} The file name, e.g. | What a package's content index is called, wherever it is written or fetched. |
819
+ | `metadataRelationships` | `function metadataRelationships(config)` | {Array<{id: string, manifest: string, kind: string, verified: string\|undefined}>} The dependencies, in declaration order. | Every dependency whose published index this build resolves addresses through. |
820
+ | `noContentIndexPackages` | `function noContentIndexPackages(config)` | {ReadonlySet<string>} The content package names. | Every package a relationship declares `contentIndex: false` on, keyed by the content package name a link into it would use. |
821
+ | `metadataCacheDir` | `function metadataCacheDir(config, id, version)` | {string} The directory. | The cache directory for one dependency's index at one version. |
822
+ | `isComplete` | `const isComplete` | {boolean} True when it was fetched to completion. | Whether a dependency's cache is present and complete. |
823
+ | `markComplete` | `function markComplete(dir)` | {void} | Mark a dependency's cache complete. |
824
+ | `cachedMetadataFiles` | `function cachedMetadataFiles(config)` | {string[]} One index file per declared dependency. | The fetched index files this build resolves foreign addresses against. |
825
+ | `cachedMetadataIndexes` | `function cachedMetadataIndexes(config)` | {Array<{id: string, file: string}>} One entry per declared dependency. | The same fetched indexes, each paired with the package that published it. |
826
+ | `newestVersionDir` | `function newestVersionDir(dirs)` | {string} The newest one. | The newest cached version among several version-keyed cache directories. |
827
+ | `loadForeignIndexes` | `function loadForeignIndexes(config, localPackages, bases` | `{{index: Map<string, object>, packages: Set<string>, stale: Array<{package: string, reason: string}>}}` The resolved addresses, which packages contributed, and what could not be read. | Resolve every foreign address this build can cite, from the fetched indexes. |
828
+ | `cachedIndexPath` | `function cachedIndexPath(config, pkg)` | {string} A path to name in a diagnostic. | Where a dependency's fetched index sits, for naming it in a diagnostic. |
829
+ | `unaddressableForeignPackages` | `function unaddressableForeignPackages(foreignIndex)` | {Array<{package: string, entries: number, sampleKey: string}>} One finding per drifted package, in the order the index first names each. | Whether a fetched index can still be _addressed_, as distinct from read. |
830
+ | `formatUnaddressableFinding` | `function formatUnaddressableFinding(finding, config)` | {string} The formatted diagnostic, path first on the line. | One finding, in the standard `file:line:column: severity: message` form. |
849
831
 
850
832
  ### `engine.foundryEntries`
851
833
 
@@ -864,31 +846,32 @@ Emitting this package's cross-package link manifest. `engine/content-address.mjs
864
846
 
865
847
  Emitting this package's content index. Every content build already walks the whole note tree and parses every note's frontmatter — the pack compilers, the site build, and the content-table expander each do it — and every one of them throws the result away when it finishes. So nothing outside a build can ask a question about the content: "which beings carry no `kbcat`?", "what does this table actually select?", "did that type rename leave anything behind?" have no answer short of writing a throwaway script that re-walks the tree. Eight dead Bestiary tables shipped for weeks behind exactly that gap.
866
848
 
867
- | Export | Signature | Returns | Use it when |
868
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
869
- | `noteAddress` | `function noteAddress(frontmatter, contentPackage)` | {{slug: string, canonical: string}\|null} `slug` is what goes inside `[[…]]` within this package; `canonical` is the fully qualified key the manifest files the note under, carrying the package and the system as well. | The address a wikilink writes to reach a note, or `null` when it has none. |
870
- | `sortKeysDeep` | `function sortKeysDeep(value)` | {unknown} The value with every plain object's keys in sorted order. | Recursively sort an object's keys, so serialization is order-independent. |
871
- | `asciiName` | `function asciiName(name)` | {string\|null} The ASCII form, or `null` when there is no name, or nothing printable survives. | A note's display name reduced to printable 7-bit ASCII. |
872
- | `asciiAliases` | `function asciiAliases(aliases)` | {Array<string>} Possibly empty, never null: a note with no aliases has an empty set of them, which is a fact rather than a missing value, and a consumer iterating it should not have to check first. | A note's `name.aliases` reduced to printable 7-bit ASCII, in order. |
873
- | `serializeContentIndex` | `function serializeContentIndex(records)` | {string} One compact JSON object per line, newline-terminated. | Serialize records as JSON Lines. |
874
- | `indexRecordsFor` | `indexRecordsFor({ contentBase, config, skipDirectories, problems })` | {object[]} One record per note, plus one per documentation entry. | The index records for a content tree, without writing anything. |
875
- | `indexRecordsForNote` | `indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext })` | `object[]` | Derive the note's own record and its documentation record from parsed text. |
876
- | `emitContentIndex` | `emitContentIndex({ contentBase, outDir, config })` | `{{file: string, notes: number, assets: number, records: number, bytes: number, full: number, draft: number, stub: number}}` Where it was written, how many notes and how many assets it holds, how many records that is in all, its size, and the ladder those notes sit on. | Emit this package's content index. |
877
- | `buildIndexRecord` | `buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest })` | `Record<string, any>` — the record, with derived fields sorted deep | building one note's content-index record; refuses a note that authors a key the index derives itself (`package` among them) before building anything |
878
- | `collectContentIndex` | `collectContentIndex(contentBase, { contentPackage, skipDirectories, assetsBase, manifest, problems })` | `Array<Record<string, any>>` | reading a whole content tree into index records, in a total order independent of directory-read order; an item note yields two records (its own and its documentation journal's), and each asset root yields one per file |
849
+ | Export | Signature | Returns | Use it when |
850
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
851
+ | `noteAddress` | `function noteAddress(frontmatter, contentPackage)` | {{slug: string, canonical: string}\|null} `slug` is what goes inside `[[…]]` within this package; `canonical` is the fully qualified key the manifest files the note under, carrying the package and the system as well. | The address a wikilink writes to reach a note, or `null` when it has none. |
852
+ | `sortKeysDeep` | `function sortKeysDeep(value)` | {unknown} The value with every plain object's keys in sorted order. | Recursively sort an object's keys, so serialization is order-independent. |
853
+ | `asciiName` | `function asciiName(name)` | {string\|null} The ASCII form, or `null` when there is no name, or nothing printable survives. | A note's display name reduced to printable 7-bit ASCII. |
854
+ | `asciiAliases` | `function asciiAliases(aliases)` | {Array<string>} Possibly empty, never null: a note with no aliases has an empty set of them, which is a fact rather than a missing value, and a consumer iterating it should not have to check first. | A note's `name.aliases` reduced to printable 7-bit ASCII, in order. |
855
+ | `serializeContentIndex` | `function serializeContentIndex(records)` | {string} One compact JSON object per line, newline-terminated. | Serialize records as JSON Lines. |
856
+ | `indexRecordsFor` | `indexRecordsFor({ contentBase, config, skipDirectories, problems })` | {object[]} One record per note, plus one per documentation entry. | The index records for a content tree, without writing anything. |
857
+ | `indexRecordsForNote` | `indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext })` | `object[]` | Derive the note's own record and its documentation record from parsed text. |
858
+ | `emitContentIndex` | `emitContentIndex({ contentBase, outDir, config })` | `{{file: string, notes: number, assets: number, records: number, bytes: number, full: number, draft: number, stub: number}}` Where it was written, how many notes and how many assets it holds, how many records that is in all, its size, and the ladder those notes sit on. | Emit this package's content index. |
859
+ | `buildIndexRecord` | `buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest })` | `Record<string, any>` — the record, with derived fields sorted deep | building one note's content-index record; refuses a note that authors a key the index derives itself (`package` among them) before building anything |
860
+ | `collectContentIndex` | `collectContentIndex(contentBase, { contentPackage, skipDirectories, assetsBase, manifest, problems, foreignRecord })` | `Array<Record<string, any>>` | reading a whole content tree into index records, in a total order independent of directory-read order; an item note yields two records (its own and its documentation journal's), and each asset root yields one per file; each note carries its resolved culture as `data.culture` |
861
+ | `resolveCultures` | `resolveCultures(records, { contentPackage, contentBase, foreignRecord, problems })` | `Array<Record<string, any>>` — the same records | writing each note's resolved culture into its record as `data.culture`: its own value, else its parents', else for a polity its seat's and governed places', pooled; parents that disagree give a finding into `problems`, or one thrown Error listing every conflict |
879
862
 
880
863
  `engine.contentIndex` also re-exports eight names from neighbouring leaf modules, at this same import path:
881
864
 
882
- | Export | Signature | Returns | Use it when |
883
- | --------------------- | -------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
884
- | `collectAnchors` | `collectAnchors(body, bodyLine)` | `Array<{slug: string, line: number, kind: string}>` | reading a note body's anchors, each with its kind, shared with the link checker and every build that emits a link |
885
- | `DERIVED_KEYS` | `const DERIVED_KEYS` | — | reading which keys the content index adds to a record, and which a note therefore may not author itself |
886
- | `noteFile` | `noteFile(contentBase, record)` | `string` — the note's absolute path | composing an index record's `file.path` (recorded relative, for a byte-stable artifact) back into an openable absolute path |
887
- | `authoredFrontmatter` | `authoredFrontmatter(record)` | `Record<string, any>` — the frontmatter without `DERIVED_KEYS` | reading back exactly what a note authored from its index record, so a pass can lint or compile from the index without reasoning about derived fields as if the author wrote them |
888
- | `isNoteRecord` | `isNoteRecord(record)` | `boolean` | telling a note's own record apart from its documentation journal's and from an asset's, when enumerating the corpus |
889
- | `isStub` | `isStub(record)` | `boolean` | telling a stub — a note with an empty body — apart from a note that publishes a page; asked of the absent address, which is the whole signal |
890
- | `isAssetRecord` | `isAssetRecord(record)` | `boolean` | telling a record that addresses a file apart from one that addresses a note; the `asset` block is what marks it |
891
- | `recordPath` | `recordPath(record)` | `string` — the path the record names inside its package | ordering or reporting across both record shapes: a note names the `.md` it was parsed from, an asset the file it is |
865
+ | Export | Signature | Returns | Use it when |
866
+ | --------------------- | -------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
867
+ | `collectAnchors` | `collectAnchors(body, bodyLine)` | `Array<{slug: string, line: number, kind: string}>` | reading a note body's anchors, each with its kind, shared with the link checker and every build that emits a link |
868
+ | `DERIVED_KEYS` | `const DERIVED_KEYS` | — | reading which keys the content index adds to a record, and which a note therefore may not author itself |
869
+ | `noteFile` | `noteFile(contentBase, record)` | `string` — the note's absolute path | composing an index record's `file.path` (recorded relative, for a byte-stable artifact) back into an openable absolute path |
870
+ | `authoredFrontmatter` | `authoredFrontmatter(record)` | `Record<string, any>` — the frontmatter without `DERIVED_KEYS` | reading a note's frontmatter back from its index record, its resolved `data.culture` included, so a pass can lint or compile from the index without reasoning about derived keys as if the author wrote them |
871
+ | `isNoteRecord` | `isNoteRecord(record)` | `boolean` | telling a note's own record apart from its documentation journal's and from an asset's, when enumerating the corpus |
872
+ | `isStub` | `isStub(record)` | `boolean` | telling a stub — a note with an empty body — apart from a note that publishes a page; asked of the absent address, which is the whole signal |
873
+ | `isAssetRecord` | `isAssetRecord(record)` | `boolean` | telling a record that addresses a file apart from one that addresses a note; the `asset` block is what marks it |
874
+ | `recordPath` | `recordPath(record)` | `string` — the path the record names inside its package | ordering or reporting across both record shapes: a note names the `.md` it was parsed from, an asset the file it is |
892
875
 
893
876
  ### `engine.assetTypes`
894
877
 
@@ -1160,9 +1143,9 @@ Shared helpers for the pack compilers in `packages/package-build/`. The HeroicLa
1160
1143
  | `assertSuppliedCorpus` | `function assertSuppliedCorpus(records, who)` | {void} | Refuse a corpus read whose records its caller did not supply. |
1161
1144
  | `statedTemplatePriority` | `function statedTemplatePriority(fm, label,` | {number\|null} The priority, or `null` when the note is not a template or states nothing. | The template priority a note states, for a system that treats an unstated one as "not a template" rather than as an authoring error. |
1162
1145
  | `resolveTemplatePriority` | `function resolveTemplatePriority(fm, label,` | {number\|undefined} The template priority, or `undefined` when null. | Resolve the required `templatePriority` frontmatter for an Item/Actor entry (the archetype contract). |
1163
- | `systemTemplatePriority` | `function systemTemplatePriority(fm, label)` | {number\|null} The template priority, or `null` for a document that is not a template. | The value a document's `system.templatePriority` carries, from the required `templatePriority` frontmatter (`archetype` is the legacy spelling). |
1146
+ | `systemTemplatePriority` | `function systemTemplatePriority(fm, label)` | {number\|null} The template priority, or `null` for a document that is not a template. | The value a document's `system.templatePriority` carries, from the required `templatePriority` frontmatter. |
1164
1147
  | `makeFilename` | `function makeFilename(name, id)` | — | Generates a compendium-source filename: `Name_id.json` with non- alphanumeric runs replaced by underscores. |
1165
- | `resolveImg` | `function resolveImg(raw, config` | {string \| null} the Foundry-relative path; `""` for a deliberate blank, and `null` when the note names no art at all. | Translate a content-relative image path into its Foundry-relative form. |
1148
+ | `resolveImg` | `function resolveImg(raw, config` | {string \| null} the Foundry-relative path, and `null` when the note names no art at all. | Translate a content-relative image path into its Foundry-relative form. |
1166
1149
  | `resolveName` | `function resolveName(fm, defaultValue` | — | Resolves the display name from frontmatter, preferring `name.full`, falling back to `name` (if string), then `defaultValue`. |
1167
1150
  | `supportedCoreVersion` | `function supportedCoreVersion(config` | {string} The declared `compatibility.minimum`. | The oldest Foundry core this package supports, stamped into every compiled document as `_stats.coreVersion`. |
1168
1151
  | `buildStats` | `function buildStats(systemVersion` | {object} The `_stats` block. | Default `_stats` block for compiled compendium entries. |
@@ -1273,20 +1256,21 @@ The document tree a PDF is built from, and the plan it resolves to. The packs an
1273
1256
 
1274
1257
  A note's markdown, and a document plan, rendered as Typst source. **This module emits text and reads nothing.** It takes markdown and a plan and returns a `.typ` document; the filesystem, the note bodies and the compiler that turns the result into a PDF all live in {@link module:engine/pdf-build}. That split is what lets the outline, the table of contents, every anchor and every link destination be asserted in a unit test with no renderer installed — which is most of what a book has to get right, and all of what a test can check without eyes.
1275
1258
 
1276
- | Export | Signature | Returns | Use it when |
1277
- | ----------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
1278
- | `escapeTypst` | `function escapeTypst(text)` | {string} The same text, inert. | Escape literal text for Typst markup. |
1279
- | `escapeTypstString` | `function escapeTypstString(text)` | {string} The same value, quotable. | Escape a string going inside Typst string quotes, as a `#link` URL does. |
1280
- | `labelFor` | `function labelFor(anchor)` | {string} A Typst label name. | A Typst label, from a plan anchor. |
1281
- | `createParser` | `function createParser(registry)` | {object} A markdown-it instance. | A markdown-it configured to parse, not to render. |
1282
- | `markdownToTypst` | `function markdownToTypst(markdown, opts` | {string} Typst markup. | Render markdown as Typst content. |
1283
- | `renderBook` | `renderBook({ plan, bodies, title, subtitle, front, fonts, version, preamble, banners })` | {string} A complete `.typ` document. | The whole book, as one Typst document. |
1284
- | `resolveDanglingLabels` | `function resolveDanglingLabels(source, findings` | {string} The same document, with no reference left dangling. | Point every internal link at a label the document actually declares. |
1285
- | `iconNamesIn` | `function iconNamesIn(markdown)` | {string[]} The names, in order of appearance, with repeats. | Every icon name a body uses, so a build can resolve them once. |
1286
- | `BOOK_IMAGE_WIDTHS` | `const BOOK_IMAGE_WIDTHS` | — | The book measure requested by each named image size. |
1287
- | `bookTypstPreamble` | `function bookTypstPreamble()` | {string} Typst markup. | The Typst definitions the book's page furniture is drawn with. |
1288
- | `stripHtmlComments` | `function stripHtmlComments(markdown)` | {string} The same body, its comments replaced by newlines. | A markdown body with its HTML comments taken out. |
1289
- | `absolutePageUrl` | `function absolutePageUrl(url, site)` | {string} An absolute URL a reader of the book can follow. | A page address as a reader of the book can follow it. |
1259
+ | Export | Signature | Returns | Use it when |
1260
+ | ----------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
1261
+ | `escapeTypst` | `function escapeTypst(text)` | {string} The same text, inert. | Escape literal text for Typst markup. |
1262
+ | `escapeTypstString` | `function escapeTypstString(text)` | {string} The same value, quotable. | Escape a string going inside Typst string quotes, as a `#link` URL does. |
1263
+ | `labelFor` | `function labelFor(anchor)` | {string} A Typst label name. | A Typst label, from a plan anchor. |
1264
+ | `createParser` | `function createParser(registry)` | {object} A markdown-it instance. | A markdown-it configured to parse, not to render. |
1265
+ | `markdownToTypst` | `function markdownToTypst(markdown, opts` | {string} Typst markup. | Render markdown as Typst content. |
1266
+ | `renderBook` | `renderBook({ plan, bodies, title, subtitle, front, fonts, version, preamble, banners })` | {string} A complete `.typ` document. | The whole book, as one Typst document. |
1267
+ | `resolveDanglingLabels` | `function resolveDanglingLabels(source, findings` | {string} The same document, with no reference left dangling. | Point every internal link at a label the document actually declares. |
1268
+ | `iconNamesIn` | `function iconNamesIn(markdown)` | {string[]} The names, in order of appearance, with repeats. | Every icon name a body uses, so a build can resolve them once. |
1269
+ | `BOOK_IMAGE_WIDTHS` | `const BOOK_IMAGE_WIDTHS` | — | The book measure requested by each named image size. |
1270
+ | `bookTypstPreamble` | `function bookTypstPreamble()` | {string} Typst markup. | The Typst definitions the book's page furniture is drawn with. |
1271
+ | `stripHtmlComments` | `function stripHtmlComments(markdown)` | {string} The same body, its comments replaced by newlines. | A markdown body with its HTML comments taken out. |
1272
+ | `absolutePageUrl` | `function absolutePageUrl(url, site)` | {string} An absolute URL a reader of the book can follow. | A page address as a reader of the book can follow it. |
1273
+ | `crossBookReference` | `function crossBookReference(inner, away)` | {string} Typst markup. | A reference to a note another book prints: the link's words, then the book and the page. |
1290
1274
 
1291
1275
  ### `engine.pdfFonts`
1292
1276
 
@@ -1302,16 +1286,18 @@ Which glyph an icon name resolves to, read from the font that carries it. {@link
1302
1286
 
1303
1287
  The content tree, built into a book. The I/O half of the PDF surface: it reads the configuration, the document tree and the notes, drives the passes the site build already owns, hands the result to {@link module:engine/pdf-render} and runs Typst over what comes back. Everything about _what the book says_ is decided in the pure half; this module is where the filesystem and the compiler live.
1304
1288
 
1305
- | Export | Signature | Returns | Use it when |
1306
- | ----------------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
1307
- | `pdfFileName` | `function pdfFileName(artifact, version)` | {string} `<artifact>-<version>.pdf`, or `<artifact>.pdf` unversioned. | The file name a downloaded book identifies itself by. |
1308
- | `buildPdf` | `async buildPdf({ config, out, version, compile })` | {Promise<object>} `{ built, reason, findings, typ, pdf, stats }`. | Build the book. |
1309
- | `stagedImagePath` | `function stagedImagePath(src, config)` | {{from: string, to: string}\|null} The file, and where under the output directory it is staged. | The file on disk an authored image address names, or `null`. |
1310
- | `stageBanners` | `function stageBanners(entries, config, outDir, findings)` | {Map<string, string>} Declared path → the staged file's path, relative to the `.typ`. | Copy every banner the document tree names into the output directory. |
1311
- | `compileTypst` | `function compileTypst(typPath, pdfPath, pdf` | `{{ok: boolean, message: string, findings: object[]}}` What happened. | Run Typst over the emitted source. |
1312
- | `typstArgs` | `function typstArgs(typPath, pdfPath, pdf)` | {string[]} The arguments, in order. | The command line the compile runs, as data. |
1313
- | `typstWarnings` | `function typstWarnings(output)` | {object[]} One finding per warning the compiler wrote. | The compiler's own warnings, as findings. |
1314
- | `BOOK_FONTS_PATH` | `const BOOK_FONTS_PATH` | {string} The directory holding them. | The faces the book is set in, shipped with this package. |
1289
+ | Export | Signature | Returns | Use it when |
1290
+ | ----------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
1291
+ | `pdfFileName` | `function pdfFileName(stem, version)` | {string} `<stem>-<version>.pdf`, or `<stem>.pdf` unversioned. | The file name a downloaded book identifies itself by. |
1292
+ | `buildPdf` | `async buildPdf({ config, configPath, book, out, version, compile, readPages })` | {Promise<object>} `{ built, reason, findings, imageReports, books }`, each book `{ id, title, built, typ, pdf, stats }`. | Build every declared book, or the one `book` names. |
1293
+ | `listBookAssets` | `function listBookAssets({ config, version, out })` | {Array<{id: string, title: string, file: string}>} One entry per book. | The path each declared book is written to, without building. |
1294
+ | `stagedImagePath` | `function stagedImagePath(src, config)` | {{from: string, to: string}\|null} The file, and where under the output directory it is staged. | The file on disk an authored image address names, or `null`. |
1295
+ | `stageBanners` | `function stageBanners(entries, config, outDir, findings, treeFile)` | {Map<string, string>} Declared path → the staged file's path, relative to the `.typ`. | Copy every banner the document tree names into the output directory. |
1296
+ | `compileTypst` | `function compileTypst(typPath, pdfPath, pdf` | `{{ok: boolean, message: string, findings: object[]}}` What happened. | Run Typst over the emitted source. |
1297
+ | `typstArgs` | `function typstArgs(typPath, pdfPath, pdf)` | {string[]} The arguments, in order. | The command line the compile runs, as data. |
1298
+ | `typstFontArgs` | `function typstFontArgs(pdf)` | {string[]} The arguments, in order. | The flags that decide which faces a layout uses, shared by the compile and the page measurement. |
1299
+ | `typstWarnings` | `function typstWarnings(output)` | {object[]} One finding per warning the compiler wrote. | The compiler's own warnings, as findings. |
1300
+ | `BOOK_FONTS_PATH` | `const BOOK_FONTS_PATH` | {string} The directory holding them. | The faces the book is set in, shipped with this package. |
1315
1301
 
1316
1302
  ### `engine.baseCompiler`
1317
1303
 
@@ -1354,7 +1340,7 @@ Macros pack compiler — produces JSON pack files for the "macros" Foundry compe
1354
1340
 
1355
1341
  ### `engine.exportedScene`
1356
1342
 
1357
- An exported Foundry Scene's compile. A map note carries its Scene as exported from Foundry at `data.scene`; this passes it through, applying `data.fixup`, keying each embedded document that carries an `_id`, and binding pins marked `#anchor` to the note's own journal pages. Nothing else inside the Scene is checked.
1343
+ An exported Foundry Scene's compile. A map note carries its Scene as exported from Foundry at `data.scene`; this passes it through, applying its fixups — `data.fixup`, and each map field that declares a Scene property, such as `data.thumbnail` — keying each embedded document that carries an `_id`, and binding pins marked `#anchor` to the note's own journal pages. Nothing else inside the Scene is checked.
1358
1344
 
1359
1345
  | Export | Signature | Returns | Use it when |
1360
1346
  | -------------------- | -------------------------------------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------- |
@@ -1406,18 +1392,18 @@ Adventure pack compiler — `type: bundle` notes → Foundry `Adventure` documen
1406
1392
 
1407
1393
  Pack JSON generation — in-repo Markdown → per-entry JSON (build-only). Reads the authoritative content tree at the configured content root and compiles each pack's entries to per-entry JSON under its build directory (`build/packs-json/<pack>/` in this repository). The JSON is a disposable build intermediate consumed by `build:compiledb` (which turns it into the shipped LevelDB packs) — it is never committed.
1408
1394
 
1409
- | Export | Signature | Returns | Use it when |
1410
- | ----------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
1411
- | `systemCompilers` | `function systemCompilers(passes)` | {Readonly<Record<string, Readonly<Record<string, Function>>>>} System id → document class → pass. | The system-specific compilers, derived from the passes themselves. |
1412
- | `compilerFor` | `function compilerFor(docType, system` | {Function\|undefined} The compiler class, or `undefined` for a document type nothing here compiles — which {@link generatePack} reports rather than defaulting past. | The compiler class a pack of one document type and one system gets. |
1413
- | `emittedArtFor` | `function emittedArtFor(type)` | {{document: string\|null, art: readonly string[]}\|null} What the type compiles into and the art it carries there, or `null` where no claim can be made — a retired type, which is reported as retired instead. | The art fields a note of one content type reaches its document through, and the document it reaches. |
1414
- | `packJsonDir` | `const packJsonDir` | {string} The pack's JSON directory. | Root of the build-only JSON tree for one pack. |
1415
- | `itemPackJsonDirs` | `function itemPackJsonDirs(config` | {string[]} Each Item pack's JSON directory. | The generated JSON of **every** configured Item pack — what the actors pass reads its predefined items from. |
1416
- | `bundleSourceJsonDirs` | `function bundleSourceJsonDirs(config` | {Record<string, string[]>} Each readable pack's JSON directory, by the Foundry document type it holds. | The compiled JSON a bundle may hold copies of, by document type. |
1417
- | `orderPassesByDependency` | `function orderPassesByDependency(packs)` | {object[]} A new list, in compile order. | The passes to run, ordered so that each one follows the output it reads. |
1418
- | `unsatisfiedPassDependencies` | `function unsatisfiedPassDependencies(running, config)` | {string[]} One message per unsatisfiable dependency. | The dependencies this run cannot satisfy by ordering, because the pass that would produce them is not in it. |
1419
- | `emptyPassErrors` | `function emptyPassErrors(passes)` | {string[]} One message per pass that must not have been empty. | The passes that compiled nothing when they were expected to compile something — a build failure, not a quiet no-op. |
1420
- | `generatePacksJson` | `async generatePacksJson({ only, config })` | {Promise<number>} Total error count across the generated packs. | Generate the build-only JSON for every pack (or one, when `only` is given). |
1395
+ | Export | Signature | Returns | Use it when |
1396
+ | ----------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
1397
+ | `systemCompilers` | `function systemCompilers(passes)` | {Readonly<Record<string, Readonly<Record<string, Function>>>>} System id → document class → pass. | The system-specific compilers, derived from the passes themselves. |
1398
+ | `compilerFor` | `function compilerFor(docType, system` | {Function\|undefined} The compiler class, or `undefined` for a document type nothing here compiles — which {@link generatePack} reports rather than defaulting past. | The compiler class a pack of one document type and one system gets. |
1399
+ | `emittedArtFor` | `function emittedArtFor(type)` | {{document: string\|null, art: readonly string[]}\|null} What the type compiles into and the art it carries there, or `null` where no claim can be made. | The art fields a note of one content type reaches its document through, and the document it reaches. |
1400
+ | `packJsonDir` | `const packJsonDir` | {string} The pack's JSON directory. | Root of the build-only JSON tree for one pack. |
1401
+ | `itemPackJsonDirs` | `function itemPackJsonDirs(config` | {string[]} Each Item pack's JSON directory. | The generated JSON of **every** configured Item pack — what the actors pass reads its predefined items from. |
1402
+ | `bundleSourceJsonDirs` | `function bundleSourceJsonDirs(config` | {Record<string, string[]>} Each readable pack's JSON directory, by the Foundry document type it holds. | The compiled JSON a bundle may hold copies of, by document type. |
1403
+ | `orderPassesByDependency` | `function orderPassesByDependency(packs)` | {object[]} A new list, in compile order. | The passes to run, ordered so that each one follows the output it reads. |
1404
+ | `unsatisfiedPassDependencies` | `function unsatisfiedPassDependencies(running, config)` | {string[]} One message per unsatisfiable dependency. | The dependencies this run cannot satisfy by ordering, because the pass that would produce them is not in it. |
1405
+ | `emptyPassErrors` | `function emptyPassErrors(passes)` | {string[]} One message per pass that must not have been empty. | The passes that compiled nothing when they were expected to compile something — a build failure, not a quiet no-op. |
1406
+ | `generatePacksJson` | `async generatePacksJson({ only, config })` | {Promise<number>} Total error count across the generated packs. | Generate the build-only JSON for every pack (or one, when `only` is given). |
1421
1407
 
1422
1408
  ### `engine.compendiums`
1423
1409
 
@@ -1535,7 +1521,7 @@ Every module beneath `./hm3` is also reachable as its own entry point, e.g. `@he
1535
1521
  import { hm3DefaultItemArt, documentSubtypes } from "@heroiclands/package-build/hm3";
1536
1522
 
1537
1523
  console.log(hm3DefaultItemArt("weapongear"));
1538
- // -> "systems/hm3/images/icons/svg/sword.svg"
1524
+ // -> "hm3/assets/icons/sword.svg"
1539
1525
  console.log(documentSubtypes.HM3_TYPE_KEY);
1540
1526
  // -> "type"
1541
1527
  ```
@@ -1631,12 +1617,12 @@ const config = defineConfig({
1631
1617
  | `defineConfig` | `defineConfig(config)` | `ContentBuildConfig` — the frozen, defaulted configuration | validating and normalizing a content configuration; every configuration reaches this function whether authored as YAML or as `.mjs` |
1632
1618
  | `PACKAGE_KINDS` | `const PACKAGE_KINDS` | — | reading the three kinds of Foundry package a content module can be built into — `systems`, `modules`, and `documentation`, the kind that compiles no Foundry document at all (also the Foundry install directory name for the first two) |
1633
1619
  | `DOCUMENTATION_KIND` | `const DOCUMENTATION_KIND` | — | naming the `packageKind` value that compiles no Foundry documents, spelled once so the validator, the CLI and the compile passes cannot disagree about what it means |
1620
+ | `AUTHOR_ROLES` | `const AUTHOR_ROLES` | `readonly string[]` | reading the closed list an `authors` entry's `role` is drawn from — the configuration check refuses any other value |
1634
1621
  | `RELATIONSHIP_KEYS` | `const RELATIONSHIP_KEYS` | `readonly string[]` | reading every key a declared relationship may carry — the configuration check refuses anything else, and the manifest writer drops the ones Foundry's own schema does not name |
1635
1622
  | `compilesFoundryDocuments` | `compilesFoundryDocuments(config)` | `boolean` | checking whether a resolved configuration compiles Foundry documents at all — false only for a `documentation` package |
1636
1623
  | `PACK_DOCUMENT_TYPES` | `const PACK_DOCUMENT_TYPES` | — | reading the Foundry document types this toolchain is able to compile a compendium pack of |
1637
1624
  | `DEFAULT_PATHS` | `const DEFAULT_PATHS` | — | reading the conventional directory layout a build reads from and writes to, relative to `rootDir` |
1638
1625
  | `DEFAULT_ADDRESS_SCHEME` | `const DEFAULT_ADDRESS_SCHEME` | — | reading an unconfigured repository's address-scheme defaults (`prefix`, where the content tree mounts inside the package) |
1639
- | `RETIRED_ADDRESS_KEYS` | `const RETIRED_ADDRESS_KEYS` | — | reading which address-scheme keys a configuration may no longer declare (e.g. `landing`) — declaring one is a refusal, not a silent no-op |
1640
1626
  | `DERIVED_SYSTEM_VERSION` | `const DERIVED_SYSTEM_VERSION` | — | the loader-only symbol key `defineConfig` uses internally to receive a resolved system version; not something a configuration author writes |
1641
1627
  | `publishesContentPages` | `publishesContentPages(config)` | `boolean` | checking whether the authored content tree contains notes beyond its homepage, shared by site, PDF, index, and Foundry address emission |
1642
1628
  | `DERIVED_HUGO_KEYS` | `const DERIVED_HUGO_KEYS` | — | reading which Hugo keys `site.hugo` may not declare because the site build generates them, each naming its source (declaring one is an error naming the key); a dotted key covers everything beneath it |
@@ -1676,7 +1662,7 @@ export { default } from "@heroiclands/package-build/prettier";
1676
1662
 
1677
1663
  ## `./markdownlint`
1678
1664
 
1679
- The shared markdownlint rules, as a markdownlint-cli2 options module, applied without a consumer declaring anything by `package-build markdown`. Exists for an editor's markdownlint extension, and for a consumer that wants to extend rather than replace the set.
1665
+ The shared markdownlint rules, as a `.markdownlint-cli2.mjs` options module, applied without a consumer declaring anything by `package-build markdown`. Exists for an editor's markdownlint extension, and for a consumer that wants to extend rather than replace the set.
1680
1666
 
1681
1667
  ```js
1682
1668
  // .markdownlint-cli2.mjs
@@ -1684,9 +1670,9 @@ import shared from "@heroiclands/package-build/markdownlint";
1684
1670
  export default { ...shared, config: { ...shared.config, MD013: true } };
1685
1671
  ```
1686
1672
 
1687
- | Export | Signature | Returns | Use it when |
1688
- | --------- | ------------------------------------------------------------ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1689
- | `default` | `export default { config, globs, ignores, gitignore: true }` | The shared markdownlint-cli2 options object | spreading it into a consumer's own options module to extend rather than replace the shared rules; `config` is frozen, so it must be spread rather than mutated |
1673
+ | Export | Signature | Returns | Use it when |
1674
+ | --------- | ------------------------------------------------------------ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1675
+ | `default` | `export default { config, globs, ignores, gitignore: true }` | The shared markdownlint options object | spreading it into a consumer's own options module to extend rather than replace the shared rules; `config` is frozen, so it must be spread rather than mutated |
1690
1676
 
1691
1677
  ## `./bundle`
1692
1678
 
@@ -1719,31 +1705,36 @@ console.log(dataEnvVar("dev"));
1719
1705
  // -> "FOUNDRYVTT_DEV_DATA"
1720
1706
  ```
1721
1707
 
1722
- | Export | Signature | Returns | Use it when |
1723
- | -------------------------- | --------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1724
- | `DEFAULT_STAGE_PORTS` | `const DEFAULT_STAGE_PORTS` | — | reading the host port each conventional stage (`dev`/`qa`/`prod`/`test`) publishes by default, chosen so all four can run at once |
1725
- | `CONTAINER_PORT` | `const CONTAINER_PORT` | — | the fixed port Foundry listens on inside the container |
1726
- | `CACHE_MOUNT` | `const CACHE_MOUNT` | — | the path a host-provided download cache is mounted at inside the container |
1727
- | `CONTAINER_ACTIONS` | `const CONTAINER_ACTIONS` | — | the closed set of actions `package-build container` accepts |
1728
- | `dataEnvVar` | `dataEnvVar(stage)` | `string` | deriving the environment variable that names a stage's Foundry data root, without hand-tabulating one per stage |
1729
- | `containerName` | `containerName(packageId, stage, base = null)` | `string` | computing the stable container name a stage runs under, so a signed Foundry licence (bound to the hostname) survives a recreate; pass `base` to share one container/licence across packages |
1730
- | `resolveStagePort` | `resolveStagePort(stage, { env, stages } = {})` | `number` | resolving the host port for a stage, honoring `FOUNDRYVTT_<STAGE>_PORT` over a declared or conventional default |
1731
- | `resolveFoundryVersion` | `resolveFoundryVersion(stage, { env, stages, compatibilityMinimum, e2eStage = "test" } = {})` | `string\|null` | pinning the exact Foundry build a stage runs, deriving the end-to-end stage's pin from the package's own `compatibility.minimum` so the floor is actually exercised |
1732
- | `resolveImage` | `resolveImage({ env, image, version, compatibilityMinimum } = {})` | `string` | resolving the `felddy/foundryvtt` image reference a run uses |
1733
- | `resolveWorld` | `resolveWorld(stage, { env, stages } = {})` | `string\|null` | resolving which world a stage auto-launches — `""` declares "never auto-launch", distinct from `null` (leave `FOUNDRY_WORLD` alone) |
1734
- | `resolveLicenseKey` | `resolveLicenseKey(stage, env = process.env)` | `string\|null` | finding a stage's dedicated Foundry licence key, since Foundry is single-seat and two stages running at once need two keys |
1735
- | `resolveDataRoot` | `resolveDataRoot(stage, { env } = {})` | `string` | resolving the local Foundry data root path for a stage, checked for what a bind mount needs |
1736
- | `passthroughEnv` | `passthroughEnv(env = process.env)` | `[string, string][]` | collecting the image's own `FOUNDRY_*`/`CONTAINER_*` environment pairs to pass through (deliberately excludes `CONTAINER_CACHE`, which `dockerRunArgs` sets itself) |
1737
- | `dockerRunArgs` | `dockerRunArgs({ name, image, port, dataRoot, env, cacheDir, version, world, licenseKey })` | `string[]` | building the full `docker run` argument vector for a stage's container |
1738
- | `runDocker` | `runDocker(args)` | `number` | running the `docker` CLI with inherited stdio and getting back its exit status |
1739
- | `captureDocker` | `captureDocker(args)` | `string` | running `docker` and capturing trimmed stdout, tolerating failure (returns `""`) |
1740
- | `containerExists` | `containerExists(name, runningOnly = false)` | `boolean` | checking whether a container with exactly this name exists (optionally, only counting a running one) |
1741
- | `runningFoundryContainers` | `runningFoundryContainers(except = "")` | `string[]` | listing HeroicLands-convention Foundry containers currently running, to warn about a licence clash before a run starts |
1742
- | `clearStaleLock` | `clearStaleLock(dataRoot, log = () => {})` | — | removing a data-root lock left behind by a container that did not shut down cleanly; only safe to call while the container is stopped |
1743
- | `resolveContainer` | `resolveContainer({ stage, config, env = process.env })` | `ResolvedContainer` | resolving all of a stage's container settings in one call, from the package-build configuration |
1744
- | `startContainer` | `startContainer(container, { dataRoot, env, log })` | `number` | starting a stage's container, creating it first if it does not exist |
1745
- | `removeContainer` | `removeContainer(name, log = () => {})` | — | stopping and removing a container, tolerating "not running" and "no such container" |
1746
- | `containerAction` | `containerAction({ action, stage, config, env, log })` | `number` | performing one `CONTAINER_ACTIONS` action for a stage end to end |
1708
+ | Export | Signature | Returns | Use it when |
1709
+ | -------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1710
+ | `DEFAULT_STAGE_PORTS` | `const DEFAULT_STAGE_PORTS` | — | reading the host port each conventional stage (`dev`/`qa`/`prod`/`test`) publishes by default, chosen so all four can run at once |
1711
+ | `CONTAINER_PORT` | `const CONTAINER_PORT` | — | the fixed port Foundry listens on inside the container |
1712
+ | `CACHE_MOUNT` | `const CACHE_MOUNT` | — | the path a host-provided download cache is mounted at inside the container |
1713
+ | `CONTAINER_ACTIONS` | `const CONTAINER_ACTIONS` | — | the closed set of actions `package-build container` accepts |
1714
+ | `dataEnvVar` | `dataEnvVar(stage)` | `string` | deriving the environment variable that names a stage's Foundry data root, without hand-tabulating one per stage |
1715
+ | `containerName` | `containerName(packageId, stage, base = null)` | `string` | computing the stable container name a stage runs under, so a signed Foundry licence (bound to the hostname) survives a recreate; pass `base` to share one container/licence across packages |
1716
+ | `resolveStagePort` | `resolveStagePort(stage, { env, stages } = {})` | `number` | resolving the host port for a stage, honoring `FOUNDRYVTT_<STAGE>_PORT` over a declared or conventional default |
1717
+ | `resolveFoundryVersion` | `resolveFoundryVersion(stage, { env, stages, compatibilityMinimum, e2eStage = "test" } = {})` | `string\|null` | pinning the exact Foundry build a stage runs, deriving the end-to-end stage's pin from the package's own `compatibility.minimum` so the floor is actually exercised |
1718
+ | `resolveImage` | `resolveImage({ env, image, version, compatibilityMinimum } = {})` | `string` | resolving the `felddy/foundryvtt` image reference a run uses — the pinned build's own tag when a build is pinned |
1719
+ | `resolveWorld` | `resolveWorld(stage, { env, stages } = {})` | `string\|null` | resolving which world a stage auto-launches — `""` declares "never auto-launch", distinct from `null` (leave `FOUNDRY_WORLD` alone) |
1720
+ | `resolveLicenseKey` | `resolveLicenseKey(stage, env = process.env)` | `string\|null` | finding a stage's dedicated Foundry licence key, since Foundry is single-seat and two stages running at once need two keys |
1721
+ | `resolveDataRoot` | `resolveDataRoot(stage, { env } = {})` | `string` | resolving the local Foundry data root path for a stage, checked for what a bind mount needs |
1722
+ | `passthroughEnv` | `passthroughEnv(env = process.env)` | `[string, string][]` | collecting the image's own `FOUNDRY_*`/`CONTAINER_*` environment pairs to pass through (deliberately excludes `CONTAINER_CACHE`, which `dockerRunArgs` sets itself) |
1723
+ | `needsFoundryAccount` | `needsFoundryAccount({ cacheDir, dataRoot, version, licenseKey, fileExists })` | `boolean` | deciding whether the image needs `FOUNDRY_USERNAME`/`FOUNDRY_PASSWORD`: only when the pinned build's archive is not cached, or the licence must come from the account |
1724
+ | `dockerRunArgs` | `dockerRunArgs({ name, image, port, dataRoot, env, cacheDir, version, world, licenseKey, account, user })` | `string[]` | building the full `docker run` argument vector for a stage's container; `account: false` withholds the foundryvtt.com account |
1725
+ | `runDocker` | `runDocker(args)` | `number` | running the `docker` CLI with inherited stdio and getting back its exit status |
1726
+ | `captureDocker` | `captureDocker(args)` | `string` | running `docker` and capturing trimmed stdout, tolerating failure (returns `""`) |
1727
+ | `dataRootScriptArgs` | `dataRootScriptArgs({ image, dataRoot, script, args, user })` | `string[]` | building the `docker run` vector for a throwaway container that runs a shell script against a stage's data root, mounted at `/data` |
1728
+ | `runDataRootScript` | `runDataRootScript(opts)` | `string` | running that script and getting its stdout, throwing on a non-zero exit; the way to remove or hand over files that the Foundry container's user owns |
1729
+ | `containerTreeRemover` | `containerTreeRemover({ container, runScript })` | `(tree, { dataRoot }) => void` | removing a tree beneath a stage's data root that the Foundry container's user owns, as root in a throwaway container; the remover `package-build deploy` passes to `deployStage` |
1730
+ | `containerRemovalCommand` | `containerRemovalCommand({ image, dataRoot, tree })` | `string` | writing out the `docker run` command that removes such a tree, for a person to paste |
1731
+ | `containerExists` | `containerExists(name, runningOnly = false)` | `boolean` | checking whether a container with exactly this name exists (optionally, only counting a running one) |
1732
+ | `runningFoundryContainers` | `runningFoundryContainers(except = "")` | `string[]` | listing HeroicLands-convention Foundry containers currently running, to warn about a licence clash before a run starts |
1733
+ | `clearStaleLock` | `clearStaleLock(dataRoot, log = () => {})` | — | removing a data-root lock left behind by a container that did not shut down cleanly; only safe to call while the container is stopped |
1734
+ | `resolveContainer` | `resolveContainer({ stage, config, env = process.env })` | `ResolvedContainer` | resolving all of a stage's container settings in one call, from the package-build configuration |
1735
+ | `startContainer` | `startContainer(container, { dataRoot, env, log })` | `number` | starting a stage's container, creating it first if it does not exist |
1736
+ | `removeContainer` | `removeContainer(name, log = () => {})` | — | stopping and removing a container, tolerating "not running" and "no such container" |
1737
+ | `containerAction` | `containerAction({ action, stage, config, env, log })` | `number` | performing one `CONTAINER_ACTIONS` action for a stage end to end |
1747
1738
 
1748
1739
  ## `./coverage`
1749
1740
 
@@ -1766,7 +1757,7 @@ console.log(keyRootsOf(["SOHL.Item.Name", "SOHL.Actor.Name"]));
1766
1757
 
1767
1758
  ## `./deploy`
1768
1759
 
1769
- Deploying a staged package into a Foundry data directory, always via a staged, atomic swap rather than a write in place — because a running Foundry holds its LevelDB compendium packs open, and replacing pack files underneath a live server corrupts them. The build lands in a sibling `…-staging-<pid>` directory and is renamed into place. Two transports are chosen from the destination string itself: a local path is copied, and a `[user@]host:/path` target is uploaded over SFTP, authenticating through the running SSH agent by default with no secret read from disk. The resolution rules are pure functions over data; the functions that touch a filesystem or network are named for it.
1760
+ Deploying a staged package into a Foundry data directory, always via a staged, atomic swap rather than a write in place — because a running Foundry holds its LevelDB compendium packs open, and replacing pack files underneath a live server corrupts them. The build lands in a sibling `…-staging-<pid>` directory and is moved into place. Two transports are chosen from the destination string itself: a local path is copied, and a `[user@]host:/path` target is uploaded over SFTP, authenticating through the running SSH agent by default with no secret read from disk. The resolution rules are pure functions over data; the functions that touch a filesystem or network are named for it.
1770
1761
 
1771
1762
  ```js
1772
1763
  import { isRemoteTarget, parseRemote } from "@heroiclands/package-build/deploy";
@@ -1777,18 +1768,18 @@ console.log(parseRemote("user@example.org:/srv/foundry"));
1777
1768
  // -> { username: "user", host: "example.org", remotePath: "/srv/foundry" }
1778
1769
  ```
1779
1770
 
1780
- | Export | Signature | Returns | Use it when |
1781
- | ----------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
1782
- | `STAGE_ENV_MAP` | `const STAGE_ENV_MAP` | — | reading the environment variable that names each stage's Foundry data root |
1783
- | `resolveStage` | `resolveStage(stageArg)` | `string` | normalising a raw stage argument to its trimmed, lowercased form (`""` when absent) |
1784
- | `packageSubpath` | `packageSubpath(packageKind, packageId)` | `string[]` | deriving where a package installs beneath a Foundry data root, from its kind and id |
1785
- | `isRemoteTarget` | `isRemoteTarget(target)` | `boolean` | deciding whether a configured destination is a remote SFTP target or a local directory (correctly excluding a Windows drive letter like `C:\Foundry\Data`) |
1786
- | `parseRemote` | `parseRemote(target)` | `{username: string\|undefined, host: string, remotePath: string}` | splitting a `[user@]host:/path` remote target into its parts |
1787
- | `resolveAgent` | `resolveAgent(env, stageUpper, prefix = "SOHL")` | `string\|undefined` | locating the SSH agent endpoint cross-platform, falling back to a key file when `undefined` |
1788
- | `buildConnection` | `async buildConnection(stageUpper, remote, { env, prefix = "SOHL" } = {})` | `Promise<object>` | assembling an `ssh2-sftp-client` connection config for a stage, defaulting to agent auth so no secret is read from disk |
1789
- | `deployLocal` | `async deployLocal(srcAbs, destDir)` | `Promise<void>` | mirroring a staged build into a local directory via the staged, atomic swap |
1790
- | `deployRemote` | `async deployRemote(conn, srcAbs, remoteDir, { onUpload } = {})` | `Promise<void>` | mirroring a staged build into a remote directory over SFTP, with the same staged swap `deployLocal` performs |
1791
- | `deployStage` | `async deployStage({ stage, source, packageKind, packageId, env, prefix = "SOHL", log })` | `Promise<{stage: string, destination: string, remote: boolean}>` | deploying a staged package to one named stage, choosing the transport from the configured destination automatically |
1771
+ | Export | Signature | Returns | Use it when |
1772
+ | ----------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1773
+ | `STAGE_ENV_MAP` | `const STAGE_ENV_MAP` | — | reading the environment variable that names each stage's Foundry data root |
1774
+ | `resolveStage` | `resolveStage(stageArg)` | `string` | normalising a raw stage argument to its trimmed, lowercased form (`""` when absent) |
1775
+ | `packageSubpath` | `packageSubpath(packageKind, packageId)` | `string[]` | deriving where a package installs beneath a Foundry data root, from its kind and id |
1776
+ | `isRemoteTarget` | `isRemoteTarget(target)` | `boolean` | deciding whether a configured destination is a remote SFTP target or a local directory (correctly excluding a Windows drive letter like `C:\Foundry\Data`) |
1777
+ | `parseRemote` | `parseRemote(target)` | `{username: string\|undefined, host: string, remotePath: string}` | splitting a `[user@]host:/path` remote target into its parts |
1778
+ | `resolveAgent` | `resolveAgent(env, stageUpper, prefix = "SOHL")` | `string\|undefined` | locating the SSH agent endpoint cross-platform, falling back to a key file when `undefined` |
1779
+ | `buildConnection` | `async buildConnection(stageUpper, remote, { env, prefix = "SOHL" } = {})` | `Promise<object>` | assembling an `ssh2-sftp-client` connection config for a stage, defaulting to agent auth so no secret is read from disk |
1780
+ | `deployLocal` | `async deployLocal(srcAbs, destDir, { removeTree } = {})` | `Promise<{leftovers: LeftoverTree[]}>` | mirroring a staged build into a local directory via the staged, atomic swap; every previous `<id>.old-<pid>` tree beside it is removed, one the host user is refused is handed to `removeTree`, and one that still cannot be removed is returned in `leftovers` rather than thrown |
1781
+ | `deployRemote` | `async deployRemote(conn, srcAbs, remoteDir, { onUpload } = {})` | `Promise<{leftovers: LeftoverTree[]}>` | mirroring a staged build into a remote directory over SFTP, with the same staged swap `deployLocal` performs |
1782
+ | `deployStage` | `async deployStage({ stage, source, packageKind, packageId, env, prefix = "SOHL", log, removeTree })` | `Promise<{stage: string, destination: string, remote: boolean, leftovers: LeftoverTree[]}>` | deploying a staged package to one named stage, choosing the transport from the configured destination automatically; `removeTree(tree, { dataRoot })` removes a local previous tree the host user is refused |
1792
1783
 
1793
1784
  ## `./e2e`
1794
1785
 
@@ -1803,32 +1794,33 @@ console.log(E2E_MODES);
1803
1794
  // -> ["run", "open"]
1804
1795
  ```
1805
1796
 
1806
- | Export | Signature | Returns | Use it when |
1807
- | ----------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
1808
- | `E2E_GM_ID` | `const E2E_GM_ID` | — | reading the fixed Gamemaster document id every seeded world carries |
1809
- | `E2E_SCENE_ID` | `const E2E_SCENE_ID` | — | reading the seeded, pre-activated default scene's id |
1810
- | `E2E_MODES` | `const E2E_MODES` | — | the closed set of modes the suite may run under (`"run"` / `"open"`) |
1811
- | `hashPassword` | `hashPassword(password, salt)` | `string` — the hex hash | hashing a password the way Foundry's own `core/auth.mjs` does, to seed a login that actually works |
1812
- | `resolveE2EWorld` | `resolveE2EWorld(config, env = process.env)` | `E2EWorld` | resolving the seeded world's identity from configuration, with an environment override under the repository's own variable prefix |
1813
- | `worldManifest` | `worldManifest({ worldId, worldTitle, worldDescription, systemId, systemVersion, coreVersion })` | `object` — the world manifest | building the `world.json` a seeded world carries |
1814
- | `gmDocument` | `gmDocument({ id, name, password, salt })` | `object` — the user document | building the seeded world's single Gamemaster user document |
1815
- | `defaultSceneDocument` | `defaultSceneDocument()` | `object` — the scene document | building the one pre-activated scene every seeded world carries |
1816
- | `moduleConfigurationDocument` | `moduleConfigurationDocument(packageId)` | `object` — the setting document | building the world setting that switches a module package on (a module must be activated; a system does not) |
1817
- | `isWorldActive` | `isWorldActive(body)` | `boolean` | checking whether a `/join` response body shows a world that is actually active, not merely reachable |
1818
- | `resolveSweepVersion` | `resolveSweepVersion(argv)` | `string` — the exact build, trimmed | reading the exact Foundry build a sweep was asked to run against; there is deliberately no default |
1819
- | `parseFastArgs` | `parseFastArgs(argv, build)` | `FastArgs` | parsing the fast loop's CLI arguments against the repository's declared build table, in declaration order |
1820
- | `seedTestWorld` | `async seedTestWorld({ config, packageJson, env = process.env, log = () => {} })` | `Promise<{worldDir: string, world: E2EWorld}>` | seeding the disposable world into the end-to-end stage's data root, wiping and rewriting the world directory each time |
1821
- | `waitForWorld` | `async waitForWorld({ url, container, stage, timeoutMs = 180_000, log = () => {} })` | `Promise<void>` | polling until a seeded world is active, or failing with a diagnosis (a licence failure is detected from the container log, not a timeout) |
1822
- | `suiteExecutables` | `suiteExecutables(command)` | `string[]` | reading every executable a suite command needs to exist before it can run at all |
1823
- | `findExecutable` | `findExecutable(name, { cwd, env = process.env } = {})` | `string\|null` | finding an executable the way the child process would (`node_modules/.bin` first, then `PATH`) |
1824
- | `missingExecutables` | `missingExecutables({ command, cwd, env = process.env })` | `string[]` | checking which of a suite command's executables are not there — asked again mid-run, since an install can pull the runner out from under a run in progress |
1825
- | `freshResults` | `freshResults({ paths, since, cwd })` | `string[]` | finding which declared result paths a suite run actually wrote to since it was spawned (existence alone is not evidence) |
1826
- | `suiteVerdict` | `suiteVerdict({ status, vanished = [], declared = [], fresh = [] })` | `SuiteVerdict` | deciding what a finished suite run is worth given its exit status and what it left behind; can only ever downgrade a verdict, never upgrade one |
1827
- | `runSuite` | `runSuite({ command, args = [], cwd, results = [], env = process.env, log = () => {} })` | `number` — the suite's exit status | running the repository's suite, bracketed by before/after executable checks and a fresh-results check rather than trusting the exit status alone |
1828
- | `suiteCommand` | `suiteCommand(config, mode)` | `string[]` | reading the declared suite command for a mode (`"run"` or `"open"`), or failing clearly when none is declared |
1829
- | `e2eRun` | `async e2eRun({ config, packageJson, mode = "run", suiteArgs = [], env = process.env, log = () => {} })` | `Promise<number>` | running a full, from-scratch end-to-end pass: deploy, reseed, recreate the container, wait, run the suite, tear down (or leave it serving in `"open"` mode) |
1830
- | `e2eFast` | `async e2eFast({ config, argv = [], env = process.env, log = () => {} })` | `Promise<number>` | iterating quickly during development: rebuild what changed, redeploy, cycle the world, wait, re-run |
1831
- | `e2eSweep` | `async e2eSweep({ config, packageJson, argv = [], env = process.env, log = () => {} })` | `Promise<number>` | running the full suite against a build the repository does not pin, to license moving `compatibility.verified` forward |
1797
+ | Export | Signature | Returns | Use it when |
1798
+ | ----------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1799
+ | `E2E_GM_ID` | `const E2E_GM_ID` | — | reading the fixed Gamemaster document id every seeded world carries |
1800
+ | `E2E_SCENE_ID` | `const E2E_SCENE_ID` | — | reading the seeded, pre-activated default scene's id |
1801
+ | `E2E_MODES` | `const E2E_MODES` | — | the closed set of modes the suite may run under (`"run"` / `"open"`) |
1802
+ | `hashPassword` | `hashPassword(password, salt)` | `string` — the hex hash | hashing a password the way Foundry's own `core/auth.mjs` does, to seed a login that actually works |
1803
+ | `resolveE2EWorld` | `resolveE2EWorld(config, env = process.env)` | `E2EWorld` | resolving the seeded world's identity from configuration, with an environment override under the repository's own variable prefix |
1804
+ | `worldManifest` | `worldManifest({ worldId, worldTitle, worldDescription, systemId, systemVersion, coreVersion })` | `object` — the world manifest | building the `world.json` a seeded world carries |
1805
+ | `gmDocument` | `gmDocument({ id, name, password, salt })` | `object` — the user document | building the seeded world's single Gamemaster user document |
1806
+ | `defaultSceneDocument` | `defaultSceneDocument()` | `object` — the scene document | building the one pre-activated scene every seeded world carries |
1807
+ | `moduleConfigurationDocument` | `moduleConfigurationDocument(packageId)` | `object` — the setting document | building the world setting that switches a module package on (a module must be activated; a system does not) |
1808
+ | `isWorldActive` | `isWorldActive(body)` | `boolean` | checking whether a `/join` response body shows a world that is actually active, not merely reachable |
1809
+ | `resolveSweepVersion` | `resolveSweepVersion(argv)` | `string` — the exact build, trimmed | reading the exact Foundry build a sweep was asked to run against; there is deliberately no default |
1810
+ | `parseFastArgs` | `parseFastArgs(argv, build)` | `FastArgs` | parsing the fast loop's CLI arguments against the repository's declared build table, in declaration order |
1811
+ | `seedTestWorld` | `async seedTestWorld({ config, packageJson, env = process.env, log = () => {}, runScript })` | `Promise<{worldDir: string, world: E2EWorld}>` | seeding the disposable world into the end-to-end stage's data root, replacing the world directory through a throwaway container so the Foundry container's user owns it |
1812
+ | `deployForContainer` | `async deployForContainer({ config, env = process.env, log = () => {}, runScript })` | `Promise<void>` | deploying the staged package to the end-to-end stage, removing the old install and handing the new one to the Foundry container's user through a throwaway container |
1813
+ | `waitForWorld` | `async waitForWorld({ url, container, stage, timeoutMs = 180_000, log = () => {} })` | `Promise<void>` | polling until a seeded world is active, or failing with a diagnosis (a licence failure is detected from the container log, not a timeout) |
1814
+ | `suiteExecutables` | `suiteExecutables(command)` | `string[]` | reading every executable a suite command needs to exist before it can run at all |
1815
+ | `findExecutable` | `findExecutable(name, { cwd, env = process.env } = {})` | `string\|null` | finding an executable the way the child process would (`node_modules/.bin` first, then `PATH`) |
1816
+ | `missingExecutables` | `missingExecutables({ command, cwd, env = process.env })` | `string[]` | checking which of a suite command's executables are not there — asked again mid-run, since an install can pull the runner out from under a run in progress |
1817
+ | `freshResults` | `freshResults({ paths, since, cwd })` | `string[]` | finding which declared result paths a suite run actually wrote to since it was spawned (existence alone is not evidence) |
1818
+ | `suiteVerdict` | `suiteVerdict({ status, vanished = [], declared = [], fresh = [] })` | `SuiteVerdict` | deciding what a finished suite run is worth given its exit status and what it left behind; can only ever downgrade a verdict, never upgrade one |
1819
+ | `runSuite` | `runSuite({ command, args = [], cwd, results = [], env = process.env, log = () => {} })` | `number` — the suite's exit status | running the repository's suite, bracketed by before/after executable checks and a fresh-results check rather than trusting the exit status alone |
1820
+ | `suiteCommand` | `suiteCommand(config, mode)` | `string[]` | reading the declared suite command for a mode (`"run"` or `"open"`), or failing clearly when none is declared |
1821
+ | `e2eRun` | `async e2eRun({ config, packageJson, mode = "run", suiteArgs = [], env = process.env, log = () => {} })` | `Promise<number>` | running a full, from-scratch end-to-end pass: deploy, reseed, recreate the container, wait, run the suite, tear down (or leave it serving in `"open"` mode) |
1822
+ | `e2eFast` | `async e2eFast({ config, argv = [], env = process.env, log = () => {} })` | `Promise<number>` | iterating quickly during development: rebuild what changed, redeploy, cycle the world, wait, re-run |
1823
+ | `e2eSweep` | `async e2eSweep({ config, packageJson, argv = [], env = process.env, log = () => {} })` | `Promise<number>` | running the full suite against a build the repository does not pin, to license moving `compatibility.verified` forward |
1832
1824
 
1833
1825
  ## `./lang`
1834
1826
 
@@ -1958,7 +1950,7 @@ The engine wildcard subpath also exposes the body parsers below. Their figure ex
1958
1950
  | Module | Exports | Contract |
1959
1951
  | ---------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1960
1952
  | `engine/content-figures.mjs` | `FIGURE_NAMES`, `FIGURE_CLASSES`, `scanFigures`, `numberFigures`, `renderFigureBlocks` | Scan leading captions and their next supported blocks; `numbered` distinguishes `:` and `:@`. `type` may override inference; `example` is supported. `FIGURE_CLASSES` retains built-in styled classes, not a closed author vocabulary. |
1961
- | `engine/content-blocks.mjs` | `BLOCK_NAMES`, `BLOCK_CONTAINERS`, `scanBlocks`, `renderBlocks`, `renderWithheldSections` | Read div and secret fences; info/warn names are retired; generic div records have `name: "div"` and no generated title. Scanning includes poetry errors. |
1953
+ | `engine/content-blocks.mjs` | `BLOCK_NAMES`, `BLOCK_CONTAINERS`, `scanBlocks`, `renderBlocks`, `renderWithheldSections` | Read div and secret fences; generic div records have `name: "div"` and no generated title. Scanning includes poetry errors. |
1962
1954
  | `engine/content-poetry.mjs` | `verseLines`, `poetryStanzas`, `inlineVerse`, `poetryMarkdown`, `scanPoetry`, `renderPoetry` | Read `poetry` code fences, validate authored metadata and tabs, and preserve relative indent levels and stanzas. |
1963
1955
  | `engine/content-spans.mjs` | `spanMarkdownPlugin`, `scanSpans`, `renderSpans` | Parse bracket spans after recognized links. The scanner returns spans and located errors; the renderer preserves inline Markdown in attributed HTML spans. |
1964
1956