@heroiclands/package-build 20.3.1 → 20.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/CONTENT.md +111 -92
  3. package/README.md +2 -9
  4. package/bin/content-build.mjs +70 -61
  5. package/bin/package-build.mjs +2 -2
  6. package/config.mjs +6 -8
  7. package/content-config.mjs +187 -64
  8. package/docs/content-format.md +57 -68
  9. package/e2e.mjs +4 -4
  10. package/engine/actor-compiler.mjs +36 -36
  11. package/engine/address-charset.mjs +6 -6
  12. package/engine/address-diff.mjs +12 -11
  13. package/engine/anchored-sections.mjs +1 -1
  14. package/engine/anchors.mjs +1 -1
  15. package/engine/base-compiler.mjs +36 -38
  16. package/engine/bundle-notes.mjs +4 -4
  17. package/engine/bundles.mjs +8 -8
  18. package/engine/code-fences.mjs +3 -3
  19. package/engine/compendiums.mjs +8 -8
  20. package/engine/compile-corpus.mjs +4 -5
  21. package/engine/content-address.mjs +15 -15
  22. package/engine/content-charset.mjs +1 -1
  23. package/engine/content-format-check.mjs +24 -25
  24. package/engine/content-format.mjs +8 -8
  25. package/engine/content-html.mjs +202 -0
  26. package/engine/content-icons.mjs +196 -165
  27. package/engine/content-index.mjs +17 -17
  28. package/engine/content-links.mjs +30 -30
  29. package/engine/content-lint.mjs +20 -24
  30. package/engine/content-package.mjs +7 -9
  31. package/engine/content-slug.mjs +2 -2
  32. package/engine/content-tables.mjs +7 -8
  33. package/engine/content-tree.mjs +1 -1
  34. package/engine/derived-fields.mjs +174 -0
  35. package/engine/diagnostics.mjs +3 -4
  36. package/engine/document-subtypes.mjs +6 -7
  37. package/engine/field-reference.mjs +8 -8
  38. package/engine/field-spec.mjs +29 -30
  39. package/engine/folder-notes.mjs +14 -14
  40. package/engine/foreign-catalog.mjs +8 -9
  41. package/engine/foundry-entries.mjs +19 -20
  42. package/engine/frontmatter-lint.mjs +64 -69
  43. package/engine/frontmatter.mjs +11 -11
  44. package/engine/generate.mjs +39 -39
  45. package/engine/helpers.mjs +63 -54
  46. package/engine/homepage.mjs +20 -22
  47. package/engine/ids.mjs +15 -15
  48. package/engine/index-records.mjs +4 -4
  49. package/engine/index.mjs +14 -8
  50. package/engine/item-compiler.mjs +54 -29
  51. package/engine/item-docs.mjs +11 -13
  52. package/engine/item-registry.mjs +13 -13
  53. package/engine/journals.mjs +15 -15
  54. package/engine/macros.mjs +3 -3
  55. package/engine/map-notes.mjs +6 -6
  56. package/engine/metadata-index.mjs +5 -5
  57. package/engine/note-claims.mjs +36 -34
  58. package/engine/note-ids.mjs +5 -5
  59. package/engine/note-package.mjs +4 -4
  60. package/engine/note-renames.mjs +3 -3
  61. package/engine/note-schemas.mjs +3 -3
  62. package/engine/note-vocabulary.mjs +27 -26
  63. package/engine/pack-config.mjs +13 -13
  64. package/engine/pack-router.mjs +0 -0
  65. package/engine/prose-config.mjs +9 -9
  66. package/engine/prose-lint.mjs +12 -13
  67. package/engine/region-events.mjs +2 -2
  68. package/engine/retired-fields.mjs +24 -22
  69. package/engine/runtime-only-fields.mjs +1 -1
  70. package/engine/scene-levels.mjs +1 -1
  71. package/engine/scenes.mjs +19 -20
  72. package/engine/schema-check.mjs +20 -20
  73. package/engine/schema-extract.mjs +1 -1
  74. package/engine/site-build.mjs +45 -47
  75. package/engine/site-index.mjs +17 -18
  76. package/engine/sql-tables.mjs +21 -13
  77. package/engine/subtype-registry.mjs +5 -4
  78. package/engine/system-block.mjs +18 -19
  79. package/engine/systems.mjs +2 -2
  80. package/engine/web-wikilinks.mjs +35 -35
  81. package/engine/wikilink-syntax.mjs +16 -16
  82. package/engine/wikilinks.mjs +39 -39
  83. package/hm3/actors.mjs +22 -22
  84. package/hm3/document-subtypes.mjs +5 -5
  85. package/hm3/index.mjs +2 -2
  86. package/hm3/item-builders.mjs +1 -1
  87. package/hm3/item-fields.mjs +3 -3
  88. package/hm3/items.mjs +20 -8
  89. package/hm3/template-priority.mjs +2 -2
  90. package/lang.mjs +3 -3
  91. package/manifest.mjs +17 -20
  92. package/package.json +1 -2
  93. package/release.mjs +3 -3
  94. package/sohl/actors.mjs +28 -28
  95. package/sohl/affiliation-standings.mjs +2 -2
  96. package/sohl/being-info.mjs +5 -5
  97. package/sohl/default-item-art.mjs +5 -5
  98. package/sohl/document-subtypes.mjs +5 -5
  99. package/sohl/index.mjs +3 -3
  100. package/sohl/item-builders.mjs +7 -7
  101. package/sohl/item-fields.mjs +8 -9
  102. package/sohl/items.mjs +20 -6
  103. package/sohl/kb-passes.mjs +5 -5
  104. package/sohl/note-schemas.mjs +7 -7
  105. package/sohl/skill-base.mjs +1 -1
  106. package/types/content-config.d.mts +25 -24
  107. package/types/e2e.d.mts +3 -3
  108. package/types/engine/actor-compiler.d.mts +14 -13
  109. package/types/engine/address-charset.d.mts +6 -6
  110. package/types/engine/address-diff.d.mts +3 -3
  111. package/types/engine/base-compiler.d.mts +17 -19
  112. package/types/engine/bundle-notes.d.mts +3 -3
  113. package/types/engine/bundles.d.mts +1 -1
  114. package/types/engine/code-fences.d.mts +3 -3
  115. package/types/engine/compendiums.d.mts +3 -3
  116. package/types/engine/compile-corpus.d.mts +1 -1
  117. package/types/engine/content-address.d.mts +11 -11
  118. package/types/engine/content-format-check.d.mts +6 -6
  119. package/types/engine/content-format.d.mts +2 -2
  120. package/types/engine/content-html.d.mts +78 -0
  121. package/types/engine/content-icons.d.mts +139 -92
  122. package/types/engine/content-index.d.mts +2 -2
  123. package/types/engine/content-links.d.mts +8 -8
  124. package/types/engine/content-lint.d.mts +2 -2
  125. package/types/engine/content-package.d.mts +6 -8
  126. package/types/engine/derived-fields.d.mts +101 -0
  127. package/types/engine/diagnostics.d.mts +2 -2
  128. package/types/engine/document-subtypes.d.mts +3 -3
  129. package/types/engine/field-spec.d.mts +37 -39
  130. package/types/engine/folder-notes.d.mts +4 -5
  131. package/types/engine/foreign-catalog.d.mts +3 -3
  132. package/types/engine/foundry-entries.d.mts +6 -7
  133. package/types/engine/frontmatter-lint.d.mts +7 -7
  134. package/types/engine/frontmatter.d.mts +7 -7
  135. package/types/engine/generate.d.mts +7 -7
  136. package/types/engine/helpers.d.mts +25 -27
  137. package/types/engine/homepage.d.mts +16 -18
  138. package/types/engine/ids.d.mts +13 -13
  139. package/types/engine/index-records.d.mts +3 -3
  140. package/types/engine/index.d.mts +2 -0
  141. package/types/engine/item-compiler.d.mts +21 -5
  142. package/types/engine/item-docs.d.mts +2 -2
  143. package/types/engine/item-registry.d.mts +6 -6
  144. package/types/engine/journals.d.mts +3 -3
  145. package/types/engine/map-notes.d.mts +2 -2
  146. package/types/engine/metadata-index.d.mts +4 -4
  147. package/types/engine/note-claims.d.mts +16 -15
  148. package/types/engine/note-ids.d.mts +4 -4
  149. package/types/engine/note-package.d.mts +1 -1
  150. package/types/engine/note-renames.d.mts +3 -3
  151. package/types/engine/note-vocabulary.d.mts +9 -8
  152. package/types/engine/pack-config.d.mts +3 -3
  153. package/types/engine/pack-router.d.mts +1 -1
  154. package/types/engine/prose-config.d.mts +9 -9
  155. package/types/engine/prose-lint.d.mts +3 -4
  156. package/types/engine/region-events.d.mts +2 -2
  157. package/types/engine/retired-fields.d.mts +10 -9
  158. package/types/engine/schema-check.d.mts +11 -11
  159. package/types/engine/site-build.d.mts +18 -19
  160. package/types/engine/site-index.d.mts +3 -4
  161. package/types/engine/sql-tables.d.mts +11 -5
  162. package/types/engine/subtype-registry.d.mts +3 -3
  163. package/types/engine/system-block.d.mts +3 -3
  164. package/types/engine/web-wikilinks.d.mts +7 -7
  165. package/types/engine/wikilink-syntax.d.mts +17 -17
  166. package/types/engine/wikilinks.d.mts +13 -12
  167. package/types/hm3/actors.d.mts +1 -1
  168. package/types/hm3/document-subtypes.d.mts +1 -1
  169. package/types/hm3/items.d.mts +1 -2
  170. package/types/hm3/template-priority.d.mts +1 -1
  171. package/types/manifest.d.mts +8 -8
  172. package/types/sohl/actors.d.mts +2 -2
  173. package/types/sohl/affiliation-standings.d.mts +2 -2
  174. package/types/sohl/being-info.d.mts +2 -2
  175. package/types/sohl/document-subtypes.d.mts +1 -1
  176. package/types/sohl/items.d.mts +1 -2
  177. package/types/sohl/note-schemas.d.mts +1 -1
  178. package/MIGRATING.md +0 -608
@@ -5,7 +5,7 @@
5
5
  * `body` is the trimmed raw markdown after the frontmatter block, and
6
6
  * `description` is `body` rendered to HTML. `bodyLine` / `bodyColumn` are the
7
7
  * 1-based **file** position of the body's first character, which is what turns
8
- * an offset within `body` into a position a diagnostic can name (#17) — see
8
+ * an offset within `body` into a position a diagnostic can name — see
9
9
  * {@link positionInBody}. If the file has no frontmatter block, returns
10
10
  * `{ frontmatter: null, body: "", description: "" }` with a warn log, and no
11
11
  * position: there is no body to have one.
@@ -28,13 +28,13 @@ export function parseMarkdownFile(filePath: any): {
28
28
  * Yields `{ frontmatter, body, description, file, absPath, bodyLine,
29
29
  * bodyColumn }` for each match — the last two from
30
30
  * {@link parseMarkdownFile}, so a caller can report a position inside the
31
- * body as a position in the file (#17).
31
+ * body as a position in the file.
32
32
  * Silently skips directories that don't exist.
33
33
  *
34
34
  * Directory names in `skipDirectories` are ignored wherever they appear. The
35
35
  * walk itself knows nothing about what they mean: `Templates/` is an Obsidian
36
36
  * templater convention this repository's vault happens to use, not a property
37
- * of a content tree, so it is configured rather than hard-coded (#1508).
37
+ * of a content tree, so it is configured rather than hard-coded.
38
38
  *
39
39
  * @param {string} rootDir - Root of the tree to walk.
40
40
  * @param {object} [opts]
@@ -44,7 +44,7 @@ export function parseMarkdownFile(filePath: any): {
44
44
  /**
45
45
  * Refuse a corpus read whose scope its caller did not state.
46
46
  *
47
- * The rule of #243 in one place, so every reader of the tree refuses the same
47
+ * The rule in one place, so every reader of the tree refuses the same
48
48
  * way and says so in the same words. It is shared rather than repeated because
49
49
  * the corpus is no longer read only by {@link walkMarkdownTree}: a pass that
50
50
  * reads the content index instead is making the identical claim about which
@@ -68,7 +68,7 @@ export function assertStatedScope(skipDirectories: readonly string[] | undefined
68
68
  * cycle. They take the records their caller already holds.
69
69
  *
70
70
  * That is not a workaround dressed up as a rule. A compile runs several passes
71
- * over one tree, and the whole of #243 is that they must not each answer "which
71
+ * over one tree, and the whole point is that they must not each answer "which
72
72
  * files are the corpus?" for themselves. Requiring the answer to be handed in
73
73
  * makes the sharing structural rather than remembered.
74
74
  *
@@ -97,7 +97,7 @@ export function walkMarkdownTree(rootDir: any, { skipDirectories }?: {}): Genera
97
97
  }, void, unknown>;
98
98
  /**
99
99
  * The template priority a note states, for a system that treats an unstated one
100
- * as "not a template" rather than as an authoring error (#266).
100
+ * as "not a template" rather than as an authoring error.
101
101
  *
102
102
  * Reads exactly the positions {@link resolveTemplatePriority} reads, including
103
103
  * the retiring `archetype` spelling, and refuses the same contradiction — so
@@ -119,7 +119,7 @@ export function statedTemplatePriority(fm: object, label: string, { block }?: {
119
119
  }): number | null;
120
120
  /**
121
121
  * Resolve the required `templatePriority` frontmatter for an Item/Actor entry
122
- * (the archetype contract, #604). The property is a nullable number that
122
+ * (the archetype contract). The property is a nullable number that
123
123
  * authors must state explicitly:
124
124
  * - a number → the document is a template of that priority.
125
125
  * - `null` → the document is not a template.
@@ -128,7 +128,7 @@ export function statedTemplatePriority(fm: object, label: string, { block }?: {
128
128
  *
129
129
  * Reads the positions {@link findTemplatePriority} lists: `data.templatePriority`
130
130
  * first — the specified home — then the system block and the top level, and
131
- * finally the retiring `archetype` spelling in the same two places (#266).
131
+ * finally the retiring `archetype` spelling in the same two places.
132
132
  *
133
133
  * @param {object} fm Parsed frontmatter.
134
134
  * @param {string} label Human-readable context for error messages.
@@ -143,8 +143,7 @@ export function resolveTemplatePriority(fm: object, label: string, { block }?: {
143
143
  }): number | undefined;
144
144
  /**
145
145
  * The value a document's `system.templatePriority` carries, from the required
146
- * `templatePriority` frontmatter (#126, sohl#1780, renamed off `archetype` by
147
- * #266 / sohl#1836).
146
+ * `templatePriority` frontmatter (`archetype` is the legacy spelling).
148
147
  *
149
148
  * A **schema field**, so the tri-state is written out in full rather than
150
149
  * expressed by a key's presence: a number is a template at that priority, and
@@ -174,7 +173,7 @@ export function makeFilename(name: any, id: any): string;
174
173
  *
175
174
  * Content frontmatter (`img` / `portrait`) authors a single path that has to
176
175
  * work for Foundry, the knowledgebase, and the website. **Its first segment
177
- * says which package owns the file** (#331), and there are exactly three
176
+ * says which package owns the file**, and there are exactly three
178
177
  * answers:
179
178
  *
180
179
  * | Authored path starts with | Owner | Emitted |
@@ -186,7 +185,7 @@ export function makeFilename(name: any, id: any): string;
186
185
  * So `icons/relic.svg` compiles to `systems/sohl/assets/icons/relic.svg` here
187
186
  * and to `modules/sohl-thalorna/assets/icons/relic.svg` in a module — the asset
188
187
  * root is derived from the configuration, and is the one place `systems/sohl`
189
- * is ever spelled (#1508). An authored
188
+ * is ever spelled. An authored
190
189
  * `systems/sohl/assets/icons/noun/shield.svg` is left exactly as written,
191
190
  * whichever package is compiling it.
192
191
  *
@@ -197,13 +196,13 @@ export function makeFilename(name: any, id: any): string;
197
196
  * `assets/artwork/`, so an authored `artwork/deity.webp` would have shipped
198
197
  * unprefixed: a 404 in Foundry, reported by nothing. That a package owns its
199
198
  * own tree is the fact; the directory names inside it are that package's
200
- * business (#331).
199
+ * business.
201
200
  *
202
201
  * **Off-install addresses pass through too**, which is the same rule rather
203
202
  * than a fourth: a URL, a `data:` URI, or a `/`-rooted path names something no
204
203
  * package owns. See {@link addressesAnotherPackage}.
205
204
  *
206
- * **`banner:` does not follow this rule, deliberately (#331).** It is not an
205
+ * **`banner:` does not follow this rule, deliberately.** It is not an
207
206
  * asset path inside a Foundry install at all: it reaches no compiled document,
208
207
  * and its only consumer is the Hugo theme, which prefixes a relative value with
209
208
  * `images/` and joins it onto `params.cdnBaseURL`. The two fields look alike
@@ -211,7 +210,7 @@ export function makeFilename(name: any, id: any): string;
211
210
  * the CDN serves — so they are documented apart rather than reconciled into one
212
211
  * rule that would be true of neither.
213
212
  *
214
- * **Two empties, and they mean opposite things (#218).** `null` — or an absent
213
+ * **Two empties, and they mean opposite things.** `null` — or an absent
215
214
  * key, which reaches here as `undefined` — means _unset_: the note names no art
216
215
  * and the caller's default applies. `""` means _blank on purpose_: the note
217
216
  * names no art **and wants none**, so no default may replace it. Both come back
@@ -230,7 +229,7 @@ export function makeFilename(name: any, id: any): string;
230
229
  * is `""` (`sohl/item-fields.mjs`), resolved from the very same shared top-level
231
230
  * key the site emitter reads as the page title — so `title: null` stringifies
232
231
  * into the compiled document as the literal `"null"`. One key, two destinations
233
- * that disagree about what empty means; see #218.
232
+ * that disagree about what empty means; see.
234
233
  *
235
234
  * This is translation only: the default for an unset path is domain-specific
236
235
  * (actors default differently from items, and gear differently again), so each
@@ -263,7 +262,7 @@ export function resolveName(fm: any, defaultValue?: string): any;
263
262
  * its migration shims on: a record stamped older than a shim is rewritten by it
264
263
  * on load. Every pack once shipped `coreVersion: "14"`, which sorts *below*
265
264
  * every v14 build and so left all shipped content permanently eligible for
266
- * every v14 migration (#1533).
265
+ * every v14 migration.
267
266
  *
268
267
  * **Read from the configuration, not from the shipped manifest.** It used to
269
268
  * open `paths.packageManifest` and take `compatibility.minimum` out of it,
@@ -289,7 +288,7 @@ export function supportedCoreVersion(config?: {
289
288
  /**
290
289
  * Default `_stats` block for compiled compendium entries.
291
290
  *
292
- * Every stamped identity is configuration (#1508): four compilers used to pass
291
+ * Every stamped identity is configuration: four compilers used to pass
293
292
  * the same frozen `"0.6.0"` literal, and `systemId` / `lastModifiedBy` were
294
293
  * written into this function. `coreVersion` alone is *not* configuration — it
295
294
  * comes from {@link supportedCoreVersion}, the configured Foundry floor,
@@ -313,13 +312,12 @@ export function buildStats(systemVersion?: string, config?: {
313
312
  };
314
313
  }): object;
315
314
  /**
316
- * The `_stats` block for one pack, stamped with the system that pack is for
317
- * (#48).
315
+ * The `_stats` block for one pack, stamped with the system that pack is for.
318
316
  *
319
317
  * **`systemId` travels with `systemVersion`.** They are one decision, so where
320
318
  * one is omitted both are. Stamping a per-pack version against a package-wide
321
319
  * id would emit `systemId: sohl, systemVersion: 1.6.3` on HM3 documents — a
322
- * *plausible lie*, which is worse than the missing value #43 fixed, because
320
+ * *plausible lie*, which is worse than a missing value, because
323
321
  * nothing about it looks wrong.
324
322
  *
325
323
  * Resolution, in order:
@@ -358,11 +356,11 @@ export function defaultStats(): object;
358
356
  *
359
357
  * Each note's pack is resolved here, once, and stored on its index entry: a
360
358
  * UUID carries a pack name, so a repository shipping several packs of one type
361
- * (#1566) would otherwise address every one of them as the first. A note whose
359
+ * would otherwise address every one of them as the first. A note whose
362
360
  * declaration is unroutable is indexed against the conventional name and left
363
361
  * for the compile pass to report — the index has no business failing a build,
364
362
  * and the pass fails it with a far better message. The one exception is a
365
- * **retired** content type (SoHL#1580): this walk is the first to see every
363
+ * **retired** content type: this walk is the first to see every
366
364
  * note together with its path, and unlike an unroutable declaration there is
367
365
  * no pass that would ever claim such a note and report it.
368
366
  *
@@ -381,7 +379,7 @@ export function buildContentLinkIndex(contentBase: string, router?: object, { sk
381
379
  * target in the content tree. Every compiler funnels through this so the
382
380
  * diagnostic text and the leave-it-alone fallback are identical everywhere.
383
381
  *
384
- * Each report names the **file, line and column** the link sits on (#17), so
382
+ * Each report names the **file, line and column** the link sits on, so
385
383
  * it can be opened and fixed — and so two identical links on one note are
386
384
  * tellable apart. That needs `file` and the note's `bodyLine` / `bodyColumn`;
387
385
  * without them the diagnostic still reports, one field shorter, rather than
@@ -426,7 +424,7 @@ export function collectContentDocs(contentBase: string, { skipDirectories, confi
426
424
  * A table searches the whole tree, which is one package's notes and nothing
427
425
  * else — so there is no longer a package to scope on. It used to filter, back
428
426
  * when a tree could hold several packages' notes and `package:` said which was
429
- * which; that field is retired and the filter with it (#56).
427
+ * which; that field is retired and the filter with it.
430
428
  *
431
429
  * @param {string} body - The note's markdown body.
432
430
  * @param {object} ctx
@@ -439,7 +437,7 @@ export function collectContentDocs(contentBase: string, { skipDirectories, confi
439
437
  * @returns {{markdown: string, lineMap: Array<{line: number,
440
438
  * generated: boolean}>}} The body with every table expanded, and where each
441
439
  * emitted line came from — which is what lets a diagnostic about the
442
- * expanded body name an authored position (#17).
440
+ * expanded body name an authored position.
443
441
  * @throws {Error} When a query is malformed or unsupported — the note fails to
444
442
  * compile rather than shipping a table-shaped hole. The error carries
445
443
  * `position`, the directive's own line.
@@ -468,7 +466,7 @@ export function folderFilename(name: any, id: any): string;
468
466
  * `html: true` is long-standing and load-bearing — notes carry raw blocks — and
469
467
  * it is also why {@link module:engine/content-icons} exists rather than an
470
468
  * instruction to write `<i class="fa-solid …">` by hand: that would render on
471
- * the two HTML surfaces and be silently dropped by the third (#378).
469
+ * the two HTML surfaces and be silently dropped by the third.
472
470
  */
473
471
  export const md: import("markdown-it").MarkdownIt;
474
472
  export { slugify } from "./content-slug.mjs";
@@ -2,7 +2,7 @@
2
2
  * The file a homepage is written to, relative to the package's site root.
3
3
  *
4
4
  * Its **address**, flat at the package root, and stated in the page's own `url`
5
- * — the same separation every other page has since #181, where the directory
5
+ * — the same separation every other page has, where the directory
6
6
  * decides the Hugo section and the front matter decides the URL. Flat rather
7
7
  * than inside a `homepage/` section directory, because a homepage is not one of
8
8
  * a kind: a section holding exactly one page would publish a landing at
@@ -45,8 +45,8 @@ export function isHomepage(fm: object | null | undefined): boolean;
45
45
  * **A refused field must be one the note *wrote*.** `resolveNoteId` fills
46
46
  * `fm.id` **in place** so every downstream reader sees one derived value —
47
47
  * deliberately, and documented as such — and this ran over the same object, so
48
- * a homepage that authors no `id` was told to delete one that is not there
49
- * (#319). Since the caller already owns the raw note text, it also answers
48
+ * a homepage that authors no `id` was told to delete one that is not there.
49
+ * Since the caller already owns the raw note text, it also answers
50
50
  * which keys the note actually declared; without an answer every key in `fm`
51
51
  * counts, which is the old behaviour and right for a caller holding authored
52
52
  * frontmatter only.
@@ -70,19 +70,17 @@ export function checkHomepageAddressFields(fm: object | null | undefined, { isAu
70
70
  message: string;
71
71
  }>;
72
72
  /**
73
- * Require exactly one homepage note in a content tree (#52).
73
+ * Require exactly one homepage note in a content tree.
74
74
  *
75
75
  * "Exactly one" is two rules, and they are **one severity** because they are
76
76
  * one defect: a package whose front page is not the page a person chose.
77
77
  *
78
78
  * - _None_ and the package serves nothing at `/<package>/`. That is the failure
79
- * #50 exists to prevent, and it is silent — the site build reports `wrote 0
79
+ * this exists to prevent, and it is silent — the site build reports `wrote 0
80
80
  * homepage(s)` and exits 0.
81
81
  * - _Two_ and it serves a page nobody chose. **This is a cardinality rule, and
82
- * since #182 it is only that.** It used to rest on the fixed destination
83
- * every homepage shared the second overwrote the first — so the address
84
- * rule enforced it as a side effect. A homepage is written at its own address
85
- * now, so two of them publish two pages and collide over nothing; the
82
+ * only that.** A homepage is written at its own address, so two of them
83
+ * publish two pages and collide over nothing; the
86
84
  * duplicate-address check catches only the pair that happen to share a
87
85
  * shortcode, and says nothing at all about a `homepage-root` beside a
88
86
  * `homepage-front`. Which of the two the redirect at `/<package>/` should
@@ -153,17 +151,17 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
153
151
  *
154
152
  * The note's own, plus the derived values every emitted page carries: the
155
153
  * resolved `title`, the package the build derived — no note declares one
156
- * (`package:` is retired, #56) and the theme's breadcrumb partial reads
154
+ * (`package:` is retired) and the theme's breadcrumb partial reads
157
155
  * `.Params.package` — and its **address**.
158
156
  *
159
157
  * The address is stated as `url` for the same reason every other page states
160
- * one (#181): Hugo publishes a page where its file sits unless told otherwise,
158
+ * one: Hugo publishes a page where its file sits unless told otherwise,
161
159
  * and a homepage's file sits at the package's site root. `slug` is written
162
160
  * beside it because it is the last segment of that address and Hugo's own key
163
161
  * for one; it decides nothing while `url` is present, but a page carrying only
164
162
  * `url` would report a slug Hugo had inferred from the filename.
165
163
  *
166
- * **Site-root relative, and so carrying no package base** (#217), exactly as
164
+ * **Site-root relative, and so carrying no package base**, exactly as
167
165
  * `pageFrontmatter` states a content page's: Hugo resolves a `url`
168
166
  * against `baseURL`, whose path is already where the package is served, so a
169
167
  * stated base was written twice and published the landing at
@@ -172,7 +170,7 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
172
170
  *
173
171
  * An authored `aliases` is dropped for the same reason it is on every other
174
172
  * page: Hugo reads it as URL redirects, so passing it through would publish a
175
- * redirect stub at each one. The field is retired (#180) and refused before a
173
+ * redirect stub at each one. The field is retired and refused before a
176
174
  * build reaches here, which makes this a guard rather than a working path.
177
175
  *
178
176
  * @param {object} fm - The note's frontmatter.
@@ -252,14 +250,14 @@ export const HOMEPAGE_FIELDS: readonly import("./field-spec.mjs").FieldSpec[];
252
250
  */
253
251
  export const HOMEPAGE_SHORTCODE: string;
254
252
  /**
255
- * The top-level field a homepage refuses, and what it would decide (#53).
253
+ * The top-level field a homepage refuses, and what it would decide.
256
254
  *
257
- * **One field, where there used to be three.** `name` and `shortcode` were
255
+ * **One field rather than three.** `name` and `shortcode` are
258
256
  * refused because a page's URL derived from `name.full` while a homepage's
259
257
  * destination was fixed, so the address a `shortcode` computed named a page the
260
- * site build never wrote. A page's URL is its address now (#181) and a homepage
258
+ * site build never wrote. A page's URL is its address now and a homepage
261
259
  * publishes at its own, so both fields decide exactly what they decide
262
- * everywhere else and are permitted (#182).
260
+ * everywhere else and are permitted.
263
261
  *
264
262
  * `id` is untouched by that, and stays: it is the Foundry document id a
265
263
  * compendium UUID is built from, and a homepage compiles into no document.
@@ -272,7 +270,7 @@ export const HOMEPAGE_SHORTCODE: string;
272
270
  * package-build release.
273
271
  *
274
272
  * `aliases` is deliberately not in the class: it is a **retired** field, refused
275
- * on every note whatever its type (#180), so it is answered there rather than
273
+ * on every note whatever its type, so it is answered there rather than
276
274
  * here.
277
275
  *
278
276
  * @type {ReadonlyMap<string, string>}
@@ -56,12 +56,12 @@ export function renamedTypeMessage(retired: string, current: string, where?: str
56
56
  * Item types are the open set — a new one is added whenever the system grows a
57
57
  * document type — so they are the **default** rather than an enumerated list. A
58
58
  * hand-maintained list is what made an entire content directory silently
59
- * unlinkable once (#1276); nothing to maintain, nothing to forget.
59
+ * unlinkable once; nothing to maintain, nothing to forget.
60
60
  *
61
61
  * The `docType` is the authority: it is a property of the *content type* and
62
62
  * holds however a repository names or splits its packs. The `pack` is the
63
63
  * conventional name only — a repository may rename its packs, or ship several
64
- * of one type (#1566), in which case the pack a particular note's document
64
+ * of one type, in which case the pack a particular note's document
65
65
  * lands in comes from `engine/pack-router.mjs` and is passed to
66
66
  * {@link compendiumUuid} explicitly. This module stays free of the
67
67
  * configuration so the link resolver above it can stay pure.
@@ -89,8 +89,8 @@ export function packForType(type: string): {
89
89
  * @param {string} id - The document's id.
90
90
  * @param {string} [packName] - The pack the document actually landed in, from
91
91
  * the pack router. Supplied wherever the note is known, because a repository
92
- * may ship several packs of one type and a UUID carries the pack name
93
- * (#1566). Omitted only where there is no note to route — the conventional
92
+ * may ship several packs of one type and a UUID carries the pack name.
93
+ * Omitted only where there is no note to route — the conventional
94
94
  * name from {@link packForType} then stands in.
95
95
  * @returns {string} `Compendium.<packageId>.<pack>.<DocumentType>.<id>`
96
96
  */
@@ -105,8 +105,8 @@ export function compendiumUuid(packageId: string, type: string, id: string, pack
105
105
  */
106
106
  export function pageUuid(entryUuid: string, pageId: string): string;
107
107
  /**
108
- * Every content type that compiles into a Foundry `Scene` — a **map note**
109
- * (#1525). The three differ only in derived canvas defaults, which is the map
108
+ * Every content type that compiles into a Foundry `Scene` — a **map note**.
109
+ * The three differ only in derived canvas defaults, which is the map
110
110
  * compiler's business; everything else treats them alike.
111
111
  *
112
112
  * Declared in this leaf module because several passes that must not depend on
@@ -120,7 +120,7 @@ export const MAP_TYPES: ReadonlySet<string>;
120
120
  /**
121
121
  * The map subTypes, which differ only in the canvas defaults derived for them.
122
122
  *
123
- * They were three *types* until #174, which cost three entries in the pack
123
+ * As three *types* they would cost three entries in the pack
124
124
  * router, three in the claims set and three in every consumer's section config
125
125
  * — for one idea that the specification had always described as one type.
126
126
  *
@@ -137,8 +137,8 @@ export const MAP_SUBTYPES: readonly string[];
137
137
  * addressed as `<type>` and `doc<type>`. These are one, so there is no
138
138
  * `docplace` and nothing synthesizes one.
139
139
  *
140
- * `doc` was the only member until #241. `place`, `lore` and `scenario` are in
141
- * the published content format and were declared for validation in #233, but
140
+ * `doc` was the only member until. `place`, `lore` and `scenario` are in
141
+ * the published content format and are declared for validation, but
142
142
  * nothing routed them: a note of one lint-ed clean and then compiled into
143
143
  * nothing, because {@link PACK_BY_TYPE} did not name it and the open-set
144
144
  * default sent it to the items pack. `sohl-thalorna` could not compile a single
@@ -157,7 +157,7 @@ export const JOURNAL_TYPES: ReadonlySet<string>;
157
157
  * building and not of the content: the same notes compiled by a different
158
158
  * repository belong to a different package. Baking the package into these
159
159
  * values is what made every link emitted by `sohl-thalorna` address the `sohl`
160
- * system (#1498) — correct here only by coincidence.
160
+ * system — correct here only by coincidence.
161
161
  *
162
162
  * @type {Readonly<Record<string, {pack: string, docType: string}>>}
163
163
  */
@@ -169,7 +169,7 @@ export const PACK_BY_TYPE: Readonly<Record<string, {
169
169
  * Content types that no longer exist, and what replaced each one.
170
170
  *
171
171
  * `character` and `creature` were retired in favour of the single `being` they
172
- * had always compiled into (SoHL#1580). They are recorded here rather than
172
+ * had always compiled into. They are recorded here rather than
173
173
  * simply deleted because deleting them is the one change that fails *quietly*:
174
174
  * every type not named in {@link PACK_BY_TYPE} falls through to the open item
175
175
  * set below, so a note or a link left on the old spelling would be routed to
@@ -193,7 +193,7 @@ export const RETIRED_TYPES: Readonly<Record<string, string>>;
193
193
  * old one compiles into exactly the document it always did: refusing it would
194
194
  * fail a build over a note that is not wrong.
195
195
  *
196
- * So these retire in the three steps `package:` took (#56), and this table is
196
+ * So these retire in the three steps `package:` took, and this table is
197
197
  * the **first**: both spellings resolve, the current one is canonical, and the
198
198
  * retired one is *reported* — never refused. The sweep of the content trees and
199
199
  * the refusal come after, once no tree writes the old name. That is the same
@@ -201,7 +201,7 @@ export const RETIRED_TYPES: Readonly<Record<string, string>>;
201
201
  * reason: a consumer must be able to adopt the new toolchain before its content
202
202
  * moves, and there are some 31,000 references to move.
203
203
  *
204
- * **Why these three, and why the other way round now.** #78 renamed
204
+ * **Why these three, and why this way round.** The specification renamed
205
205
  * `armorgear`, `concoctiongear` and `projectilegear` to `armor`, `concoction`
206
206
  * and `projectile`, on the argument that the suffix named the *SoHL document
207
207
  * subtype* a note compiled into rather than the thing the note is about. That
@@ -6,9 +6,9 @@
6
6
  * the machine that built the index, would differ between two checkouts of the
7
7
  * same tree, and would put someone's home directory in a published artifact. So
8
8
  * every pass that reads the index and then needs to open a note has to compose
9
- * the absolute form, and each of the readers converted for #243 had written its
9
+ * the absolute form, and each converted reader had written its
10
10
  * own `path.join(base, ...record.file.path.split("/"))`. Four copies of one
11
- * rule is what #243 exists to remove, so here it is once.
11
+ * rule is what this exists to remove, so here it is once.
12
12
  *
13
13
  * The split is on `"/"` rather than `path.sep` because the recorded path is
14
14
  * always POSIX — that is what makes the index identical on every platform.
@@ -54,7 +54,7 @@ export function isNoteRecord(record: Record<string, any>): boolean;
54
54
  * carry itself.
55
55
  *
56
56
  * `package` is the note's distribution unit — the configured `contentPackage`,
57
- * since a note declaring its own is a hard error (#56) — and it matches what
57
+ * since a note declaring its own is a hard error — and it matches what
58
58
  * the content-table expander puts on the same field, so a query reads the same
59
59
  * value from either. `file` namespaces the note's place in the tree, again
60
60
  * matching the expander's `file.*`.
@@ -11,6 +11,7 @@ export * as contentPackage from "./content-package.mjs";
11
11
  export * as notePackage from "./note-package.mjs";
12
12
  export * as retiredFields from "./retired-fields.mjs";
13
13
  export * as runtimeOnlyFields from "./runtime-only-fields.mjs";
14
+ export * as derivedFields from "./derived-fields.mjs";
14
15
  export * as homepage from "./homepage.mjs";
15
16
  export * as noteSchemas from "./note-schemas.mjs";
16
17
  export * as noteVocabulary from "./note-vocabulary.mjs";
@@ -27,6 +28,7 @@ export * as siteBuild from "./site-build.mjs";
27
28
  export * as contentLint from "./content-lint.mjs";
28
29
  export * as contentCharset from "./content-charset.mjs";
29
30
  export * as contentIcons from "./content-icons.mjs";
31
+ export * as contentHtml from "./content-html.mjs";
30
32
  export * as contentLinks from "./content-links.mjs";
31
33
  export * as webWikilinks from "./web-wikilinks.mjs";
32
34
  export * as contentTables from "./content-tables.mjs";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The description an item carries: a pointer to its **item doc**, the
3
- * JournalEntry the journals pass compiles this same body into (#1348).
3
+ * JournalEntry the journals pass compiles this same body into.
4
4
  *
5
5
  * The prose is not rendered into the item at all. Carrying it would duplicate
6
6
  * it onto every actor holding the item — 7.59 MB of copies across the actors
@@ -40,6 +40,22 @@ export class SystemItemCompiler extends BasePackCompiler {
40
40
  * @type {import("./document-subtypes.mjs").DocumentSubtypeMap|undefined}
41
41
  */
42
42
  static documentSubtypes: import("./document-subtypes.mjs").DocumentSubtypeMap | undefined;
43
+ /**
44
+ * The `system` keys this pass writes for itself, and where each is read
45
+ * from.
46
+ *
47
+ * A note authoring one is refused, because the compiler writes the key
48
+ * unconditionally: the authored value is overwritten, or survives as the
49
+ * wrong type in a shipped document. Declared here rather than named in the
50
+ * refusal so that a second system states its own — see
51
+ * {@link module:engine/derived-fields}.
52
+ *
53
+ * @type {readonly {key: string, from: string}[]}
54
+ */
55
+ static derivedSystemKeys: readonly {
56
+ key: string;
57
+ from: string;
58
+ }[];
43
59
  /**
44
60
  * The frontmatter block this pass reads, and the registry it addresses.
45
61
  *
@@ -60,7 +76,7 @@ export class SystemItemCompiler extends BasePackCompiler {
60
76
  * **Looked up, not inferred.** For every type this system declares, the
61
77
  * emitted subtype is the map's, so the note vocabulary and the document
62
78
  * vocabulary are two separately-stated things rather than one string
63
- * written twice (#79).
79
+ * written twice.
64
80
  *
65
81
  * **A type the map does not name belongs to the consumer**, and its
66
82
  * registry entry is the declaration: a repository shipping an item type of
@@ -69,7 +85,7 @@ export class SystemItemCompiler extends BasePackCompiler {
69
85
  * subtype of. That is an authored statement in the consumer's own
70
86
  * configuration, not a coincidence inside this package's source — and
71
87
  * refusing it here would silently drop every document of a type this system
72
- * has no opinion about (#7/#1563).
88
+ * has no opinion about.
73
89
  *
74
90
  * @param {object} fm - The note's frontmatter.
75
91
  * @returns {string} The document's `type`.
@@ -84,7 +100,7 @@ export class SystemItemCompiler extends BasePackCompiler {
84
100
  * Nothing by default, which is the honest position for a system that has
85
101
  * not said otherwise: a key written here lands on every document of every
86
102
  * type, so inventing one that the receiving DataModel does not declare
87
- * would be a finding on the whole pack (#155).
103
+ * would be a finding on the whole pack.
88
104
  *
89
105
  * @param {object} fm - The note's frontmatter.
90
106
  * @param {object} at - What the pass already knows about this note.
@@ -111,7 +127,7 @@ export class SystemItemCompiler extends BasePackCompiler {
111
127
  * field for it and an undeclared `system` key is discarded at load without
112
128
  * a word.
113
129
  *
114
- * **This is the one emitted key nothing else can check** (#283). A `system`
130
+ * **This is the one emitted key nothing else can check**. A `system`
115
131
  * key this pass invents is caught by the emitted-`system` check against the
116
132
  * receiving schema, but a flag is declared by no schema — so an omission
117
133
  * here is silent, and was: the Actor pass wrote the priority and this one
@@ -3,7 +3,7 @@
3
3
  * addressed by the virtual `doc<type>` qualifier.
4
4
  *
5
5
  * Every item type, plus `macro` — a macro note's body documents the script the
6
- * note also compiles into a Macro (#1514), which is the same shape as an item
6
+ * note also compiles into a Macro, which is the same shape as an item
7
7
  * and its description: one note, two documents, the prose living in the
8
8
  * journals pack.
9
9
  *
@@ -59,7 +59,7 @@ export function itemDocEntryId(itemId: string): string;
59
59
  * happens to contain a link, which the runtime would then show verbatim.
60
60
  *
61
61
  * @param {string} packageId - The Foundry package shipping the journals pack.
62
- * Supplied rather than assumed (#1498).
62
+ * Supplied rather than assumed.
63
63
  * @param {string} itemId - The item note's `id` frontmatter.
64
64
  * @param {string} name - The item's name, used as the link's label. It shows
65
65
  * only if the target ever fails to resolve, where a broken link naming the
@@ -6,9 +6,9 @@
6
6
  * **Derived, never authored.** These are the keys of the consuming
7
7
  * repository's `itemBuilders` registry, so the whitelist and the builder table
8
8
  * are the same list and cannot drift apart. They already had: `trait` was
9
- * whitelisted long after the item type was retired (#651), with no builder
9
+ * whitelisted long after the item type was retired, with no builder
10
10
  * behind it, so every `type: trait` note passed the gate and then failed to
11
- * compile (#1504).
11
+ * compile.
12
12
  *
13
13
  * An accessor rather than a hoisted constant, so that importing this module
14
14
  * needs no configuration (#2).
@@ -21,7 +21,7 @@ export function itemTypes(): ReadonlySet<string>;
21
21
  *
22
22
  * Unreachable through the compiler — its whitelist *is* this registry's keys —
23
23
  * so a throw here means a caller invented a type. It names the type rather than
24
- * failing as an anonymous `is not a function` (#1504).
24
+ * failing as an anonymous `is not a function`.
25
25
  *
26
26
  * @param {string} type - The note's `type` frontmatter.
27
27
  * @param {string} [system] - The system compiling it, where a build declares
@@ -35,9 +35,9 @@ export function itemBuilder(type: string, system?: string): (fm: object) => obje
35
35
  * The frontmatter fields a type's registry entry declares, if any.
36
36
  *
37
37
  * Sparse by design: a type whose entry declares none compiles normally and is
38
- * simply undocumented (#22). What reads it is the `system`-block passthrough,
38
+ * simply undocumented. What reads it is the `system`-block passthrough,
39
39
  * which has to know which paths a declared field already writes before it
40
- * writes the rest (#58).
40
+ * writes the rest.
41
41
  *
42
42
  * @param {string} type - The item type.
43
43
  * @param {string} [system] - The system compiling it, where a build declares
@@ -51,7 +51,7 @@ export function itemFields(type: string, system?: string): readonly object[] | u
51
51
  *
52
52
  * Read from the consuming repository's `itemBuilders` registry, the same place
53
53
  * the type itself is declared, so a consumer's own type can bring art a
54
- * SoHL-owned table could never hold. Art used to be looked up in
54
+ * SoHL-owned table could never hold. Art is not looked up in
55
55
  * `sohl/default-item-art.mjs` instead: a type was configurable while its
56
56
  * default art was not, so a second consumer's items compiled only if every one
57
57
  * of its notes set `img:` (#7).
@@ -29,7 +29,7 @@ export function splitPages(body: any, leadName?: string): {
29
29
  * on an identity alone:
30
30
  *
31
31
  * - **An anchor**, declared twice, has always collided.
32
- * - **A name**, repeated among the unanchored pages, collides since #268 took
32
+ * - **A name**, repeated among the unanchored pages, collides once
33
33
  * the index out of the key. `MD024` with `siblings_only` already makes two
34
34
  * sibling headings with the same text a lint error, so this is the same rule
35
35
  * restated where the build can enforce it — a lint is a separate command, and
@@ -69,7 +69,7 @@ export function assertUniqueAnchors(rawPages: Array<{
69
69
  * doc's first page without having compiled it (see
70
70
  * {@link sohl.utils.packs.itemDocPointer}).
71
71
  *
72
- * **It takes no index** (#268). A page used to be keyed by position *and* name,
72
+ * **It takes no index**. Keying a page by position *and* name,
73
73
  * so inserting a heading renumbered every page after it and a re-import created
74
74
  * new pages beside the old ones — while nothing about those pages had changed.
75
75
  * The anchored case above never took one, and is the shape this now shares.
@@ -134,7 +134,7 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
134
134
  * @param {object} [params.stats] - The `_stats` block to stamp. Passed by the
135
135
  * caller because it is a property of the *pack* being written, not of the
136
136
  * entry: a module may ship the same content for two systems, and each pack's
137
- * documents record the system version they were built against (#48). A
137
+ * documents record the system version they were built against. A
138
138
  * caller with no pack in hand gets the package-wide block.
139
139
  * @returns {object} The JournalEntry document, keyed for the pack.
140
140
  */
@@ -8,7 +8,7 @@ export function isMapType(type?: string): boolean;
8
8
  /**
9
9
  * The canvas profile for a map subType.
10
10
  *
11
- * Keyed on the subType rather than the type since #174: every map note is
11
+ * Keyed on the subType rather than the type: every map note is
12
12
  * `type: map`, and which canvas it derives is the one thing the three
13
13
  * spellings ever decided.
14
14
  *
@@ -253,7 +253,7 @@ export const MAP_SUBTYPE_PROFILES: Readonly<Record<string, object>>;
253
253
  */
254
254
  export const DEFAULT_LEVEL_ID: "defaultLevel0000";
255
255
  /**
256
- * The behaviour types a map note may carry (issue #1525, v1).
256
+ * The behaviour types a map note may carry (v1).
257
257
  *
258
258
  * @type {ReadonlySet<string>}
259
259
  */